Commit graph

23 commits

Author SHA1 Message Date
nns fc2cbee3d7 fix(validation): validatePassword проверяет заглавную и цифру (соответствует хинту)
Some checks failed
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Docker Public / Build + push Public (push) Has been cancelled
Docker Public / Deploy Public on stage (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-18 12:52:59 +05:00
nns 259706e21f fix(signup): onBlur валидация через e.target.value, ре-валидация вместо сброса ошибки в onChange
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Public / Build + push Public (push) Waiting to run
Docker Public / Deploy Public on stage (push) Blocked by required conditions
- onBlur читает e.target.value напрямую из DOM (нет stale closure)
- onChange не очищает ошибку, а ре-валидирует (только если ошибка уже показана)
- Устраняет баг на мобильном: blur иногда стреляет раньше последнего onChange

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-18 12:43:53 +05:00
nns ff44afc202 feat(ux): onBlur валидация полей во всех формах
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Public / Build + push Public (push) Waiting to run
Docker Public / Deploy Public on stage (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Ошибки полей теперь показываются сразу после потери фокуса — без
нажатия «Сохранить». При начале ввода ошибка убирается (onChange).
Submit-валидация остаётся без изменений.

Охват: SignupForm (public), LoginPage, ForgotPasswordPage,
ResetPasswordPage, EmployeesPage, CounterpartiesPage, StoresPage,
OrganizationSettingsPage, SuperAdminOrgCreatePage, PriceTypesPage,
EmployeeRolesPage, RetailPointsPage.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-17 23:38:56 +05:00
nurdotnet 8eceff0bb5 fix(phone): сохранять позицию курсора после нормализации
Some checks failed
CI / Backend (.NET 8) (push) Successful in 1m3s
CI / Web (React + Vite) (push) Successful in 41s
Docker Public / Build + push Public (push) Successful in 26s
Docker Web / Build + push Web (push) Successful in 32s
Docker Public / Deploy Public on stage (push) Successful in 10s
Docker Web / Deploy Web on stage (push) Successful in 11s
CI / POS (WPF, Windows) (push) Has been cancelled
Курсор не должен прыгать в конец после редактирования — он должен оставаться там, где юзер только что внёс изменение.

Корень проблемы: после onChange я вызываю onChange с каноничным «+77…», parent перерендеривает с этим значением, React переписывает input.value на новый display — а нативное поведение браузера в этом случае: при programmatic value-set курсор сбрасывается в конец.

Фикс: сохраняем «сколько цифр стояло до курсора» в момент редактирования, после ре-рендера в useLayoutEffect ставим курсор сразу после той же по счёту цифры в новом display. Учёт по цифрам (а не по абсолютной позиции) корректен даже когда format добавляет/убирает пробелы (например при переходе между «+7 700 1» и «+7 700 12»).

useLayoutEffect (а не useEffect) — чтобы курсор восстанавливался синхронно до paint, без визуального флика.
2026-05-03 18:25:56 +05:00
nurdotnet 1264a91e2c fix(phone): нативное редактирование, фильтр не-цифр через onBeforeInput
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m7s
CI / Web (React + Vite) (push) Successful in 41s
Docker Public / Build + push Public (push) Successful in 27s
Docker Web / Build + push Web (push) Successful in 33s
Docker Public / Deploy Public on stage (push) Successful in 10s
Docker Web / Deploy Web on stage (push) Successful in 11s
Прошлая попытка вручную обрабатывать каждую клавишу через onKeyDown ломала привычное поведение: selection + Backspace удалял только 1 цифру, нельзя было редактировать в середине, и т.д.

Новая модель совсем простая:
- Префикс «+7 7» (4 символа) залочен — onSelect клампит курсор/selection на digit-зону.
- Не-цифры блокируются через onBeforeInput (e.preventDefault если data содержит \D). Это покрывает и печать, и paste, и IME, и drag-drop.
- Всё остальное — нативное поведение браузера: selection + delete, click anywhere, arrow keys, Cmd+A, Cmd+V, drag-drop. После любого изменения onChange нормализует значение (rawToUserDigits извлекает 9 пользовательских цифр, отрезая залоченные «+7 7»).
- Display всегда «+7 7» + format(userDigits) — если юзер случайно стер часть префикса, она восстанавливается на ре-рендере.

Канон наружу: «+77XXXXXXXXX» (12 chars при полном номере), пустая строка если ничего не введено. Совместимо с существующим validatePhone (^77\d{9}$).
2026-05-03 17:21:50 +05:00
nurdotnet 3beaec214a fix(phone): редактирование на месте курсора, как в обычном поле
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m3s
CI / Web (React + Vite) (push) Successful in 39s
Docker Public / Build + push Public (push) Successful in 25s
Docker Web / Build + push Web (push) Successful in 31s
Docker Public / Deploy Public on stage (push) Successful in 10s
Docker Web / Deploy Web on stage (push) Successful in 11s
Курсор больше не «прыгает в конец» — его можно ставить куда угодно в digit-зоне (после префикса «+7 »), а Backspace/Delete/ввод цифры работают относительно позиции курсора, как в любом нормальном текстовом поле.

Реализация:
- cursorToDigitIdx — мапит позицию курсора в display к индексу в массиве цифр (пробелы форматирования пропускаются).
- digitIdxToCursor — обратное: после операции ставит курсор сразу после последней изменённой цифры (через пробел если он там есть, чтобы не было визуального скачка).
- Backspace на позиции idx удаляет цифру (idx-1); Delete удаляет цифру idx; ввод цифры вставляет в позицию idx.
- Префикс «+7 » защищён: onSelect ловит попытку курсора влезть в позиции 0..2 и мягко переставляет на 3.
2026-05-03 17:01:20 +05:00
nurdotnet a7130f3116 fix(phone): полностью переписать на простую модель — цифры как single source of truth
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m5s
CI / Web (React + Vite) (push) Successful in 38s
Docker Public / Build + push Public (push) Successful in 27s
Docker Web / Build + push Web (push) Successful in 32s
Docker Public / Deploy Public on stage (push) Successful in 10s
Docker Web / Deploy Web on stage (push) Successful in 11s
Предыдущие итерации пытались парсить промежуточный raw input от браузера и постоянно ловили баги: курсор не там → парсер путал префикс с введёнными цифрами; Backspace на странной позиции → блокировался не там где надо.

Новая модель: фронт полностью контролирует input, нативный ввод запрещён. Все клавиши обрабатываются вручную в onKeyDown:
- Цифра → если набрано <10, добавить в конец.
- Backspace/Delete → если есть цифры, удалить последнюю.
- Любой другой символ → игнор (preventDefault).
- Шорткаты (Cmd/Ctrl+что-то), стрелки, Tab, Enter, Esc → пропускаем.

Курсор автоматически держим в конце display'а (после фокуса/клика/изменения). Paste обрабатывается отдельно через onPaste с нормализацией. onChange = no-op (React-warning заглушка).

Бенефиты: Backspace теперь всегда удаляет цифру, без оглядки на позицию. Не-цифры физически невозможно ввести. Префикс «+7 » никогда не повреждается.
2026-05-03 16:56:52 +05:00
nurdotnet 16fe7580af fix(phone): блокировать ввод не-цифр на уровне keyDown
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m4s
CI / Web (React + Vite) (push) Failing after 37s
Docker Public / Build + push Public (push) Successful in 26s
Docker Web / Build + push Web (push) Failing after 26s
Docker Web / Deploy Web on stage (push) Has been skipped
Docker Public / Deploy Public on stage (push) Successful in 10s
Предыдущий fix не покрывал кейс когда курсор оказывался не сразу после «+7 » (в начале строки или в середине). parseRawInput не находил префикс на нужной позиции и «7» снова попадала в подсчёт.

Корректное решение: блокировать любой не-цифровой символ через preventDefault в onKeyDown — тогда он вообще не доходит до input, парсер видит только валидные цифры. Сохранены: Backspace/Delete (с защитой префикса), стрелки и навигация, Cmd/Ctrl+A/C/V/X для копи-пасты, Enter для submit. Paste произвольной строки по-прежнему работает через onChange.
2026-05-03 16:15:27 +05:00
nurdotnet 47c349818f fix(phone): не считать «7» из префикса как введённую цифру
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m5s
CI / Web (React + Vite) (push) Failing after 37s
Docker Public / Build + push Public (push) Successful in 27s
Docker Web / Build + push Web (push) Failing after 27s
Docker Web / Deploy Web on stage (push) Has been skipped
Docker Public / Deploy Public on stage (push) Successful in 11s
Баг: при нажатии любой не-цифры (буквы, пробела) поле подставляло «7». Корень: extractDigits парсил полное e.target.value включая префикс «+7», и его «7» попадала в d, добавляясь как введённый юзером символ. Backspace на пустом поле не работал по той же причине.

Фикс: новая функция parseRawInput отделяет префикс «+7 » ДО извлечения цифр. Применена в handleChange обоих PhoneInput (web + public). Кейс paste без префикса (8XXXXXXXXXX / 7XXXXXXXXXX из 11 цифр) сохранён.
2026-05-03 15:56:27 +05:00
nurdotnet 2301446b06 feat(phone): единый PhoneInput с зашитым «+7» и ФЛК Казахстана
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m1s
CI / Web (React + Vite) (push) Failing after 37s
Docker Public / Build + push Public (push) Successful in 27s
Docker Web / Build + push Web (push) Failing after 25s
Docker Web / Deploy Web on stage (push) Has been skipped
Docker Public / Deploy Public on stage (push) Successful in 10s
Поле телефона во всех формах (web + public) теперь использует общий компонент:
- Префикс «+7 » всегда виден и не удаляется (Backspace/Delete на позиции ≤3 блокируется).
- Принимаются только цифры (буквы и спецсимволы автоматически отфильтровываются).
- Авто-форматирование при вводе: «+7 7XX XXX XX XX».
- Paste произвольного формата нормализуется (поддерживается ведущая «8», «+7…», скобки, дефисы).
- Наружу через onChange отдаётся каноничное «+7XXXXXXXXXX».

Подключено в:
- food-market.public/SignupForm
- food-market.web/CounterpartiesPage (контрагенты)
- food-market.web/EmployeesPage (сотрудники)
- food-market.web/SuperAdminOrgEmployeesPage (управление сотрудниками SuperAdmin)
- food-market.web/SuperAdminOrgCreatePage (создание организации SuperAdmin)
2026-05-03 11:01:55 +05:00
nurdotnet fd7df631e1 feat(signup): телефон обязателен + ФЛК Казахстана (77XXXXXXXXX)
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m0s
CI / Web (React + Vite) (push) Successful in 40s
Docker API / Build + push API (push) Successful in 1m20s
Docker Public / Build + push Public (push) Successful in 28s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Public / Deploy Public on stage (push) Successful in 11s
Фронт:
- SignupForm: убрал «(необязательно)» из лейбла, добавил required + autoComplete=tel.
- validation.ts: validatePhone теперь возвращает ошибку при пустом значении и валидирует строго KZ-мобильный (^77\d{9}$); ведущая «8» нормализуется в «7». «79…» (РФ) отвергается.

Бэк:
- AuthSignupController: SignupInput.Phone теперь string (не nullable). Добавлен NormalizeKzPhone — единая нормализация на сервере, защита от обхода фронтового валидатора. На запись в Organization.Phone уходит каноничная форма «+7XXXXXXXXXX».
2026-05-03 02:52:58 +05:00
nns 2a026c589c fix(public): кнопка «Войти» вела на 410-Gone zat.kz
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m2s
CI / Web (React + Vite) (push) Successful in 40s
Docker Public / Build + push Public (push) Successful in 26s
Docker Public / Deploy Public on stage (push) Successful in 10s
Bug: на test.food-market.kz href кнопки «Войти» показывал
https://app.food-market.zat.kz/login → 410 Gone (старый stage-домен
давно убран). Корни:

— Header.astro имел дефолт PUBLIC_APP_URL = https://test.food-market.kz
  (после revert-коммита остался только public-домен, но Войти ведёт
  на админку — должно быть admin.food-market.kz). Поправил, теперь
  совпадает с SignupForm.tsx.
— Dockerfile ARG PUBLIC_APP_URL тоже был test.food-market.kz —
  заменил на admin.food-market.kz.
— Добавил .dockerignore (node_modules / dist / .astro / .env / .git)
  чтобы старый локальный dist/ не попадал в build-контекст и не
  застревал в layer cache. Раньше это давало stale бандл с
  app.food-market.zat.kz даже после изменений в src/.

Build/deploy: docker build --no-cache + compose pull --force-recreate.
Smoke на проде:
- href Войти   → https://admin.food-market.kz/login
- Никаких .zat.kz упоминаний в /usr/share/nginx/html (grep пуст).
- og:image     → https://test.food-market.kz/og/home.png
2026-05-02 22:15:18 +05:00
nns 0f59dfee69 feat(brand): новый логотип food-market wordmark + apple mark
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m23s
CI / Web (React + Vite) (push) Successful in 38s
Docker Public / Build + push Public (push) Successful in 28s
Docker Web / Build + push Web (push) Successful in 33s
Docker Public / Deploy Public on stage (push) Successful in 10s
Docker Web / Deploy Web on stage (push) Successful in 11s
Источник — assets/brand/food-market-source.svg (1080x1080, FOOD #0DB70D
с яблоком вместо O, MARKET #4C4F54). Из source извлечены векторные
пути (Adobe-preview PNG-image теги вырезаны), собраны три варианта,
оптимизированы svgo.

Варианты в public/ обоих пакетов (food-market.web, food-market.public):
- logo.svg          (2.1 KB) — full wordmark для светлого фона
- logo-light.svg    (2.1 KB) — full wordmark белым (для тёмного фона)
- favicon.svg       (1.0 KB) — mark-only: яблоко в зелёном круге,
                              читается с 16px (Astro favicon, web favicon)

Дополнительно (PWA maskable, food-market.web):
- logo-bg.svg       (140 B) — solid #0DB70D 456x456 фон
- logo-fg.svg       (1.0 KB) — белое яблоко на прозрачном, в safe-zone

Logo-компоненты переписаны:
- Logo.tsx (web) — было div+span с FOOD/MARKET буквами; теперь
  <img src=/logo.svg|/logo-light.svg /> с одним пропом variant.
  Сохранён API: variant=light|dark, className.
- Logo.astro (public) — то же; добавил пропс class для override-а.

Header.astro public-пакета использует <Logo />, не содержит
hardcoded текста — менять не нужно.

Все файлы под 2.2 KB после svgo.
2026-05-02 00:26:42 +05:00
nns 79406e304e revert(domains): публичный сайт → test.food-market.kz, apex 404 до релиза
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 53s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Successful in 1m9s
Docker Public / Build + push Public (push) Successful in 28s
Docker Web / Build + push Web (push) Successful in 33s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Public / Deploy Public on stage (push) Successful in 10s
Docker Web / Deploy Web on stage (push) Successful in 11s
Публичный сайт ещё в разработке — выносим его с food-market.kz на
test.food-market.kz. На корневом домене food-market.kz пока 404.
admin.food-market.kz остаётся как есть.

— Заменены https-URL в src/** и deploy/:
  https://food-market.kzhttps://test.food-market.kz
  (admin.food-market.kz, app.food-market.kz и emails @food-market.kz
  не трогаем — sed строго по https-префиксу).
— public Dockerfile ARG PUBLIC_SITE_URL → test.food-market.kz.
— SignupForm/Header/NoOrganizationPage указывают на admin.food-market.kz
  для API (без изменений с прошлого коммита).
— appsettings.json CORS: test + admin.food-market.kz.

Nginx (на сервере):
- /etc/nginx/conf.d/test.food-market.kz.conf — новый, серт LE issued.
- food-market.kz.conf — apex теперь 404 (HTTPS), серт переиспользует
  пару (food-market.kz + admin.food-market.kz).
- food-market.zat.kz и app.food-market.zat.kz — 301 на test/admin
  соответственно.

Smoke: test/, /signup/, admin/health, admin/login = 200; apex = 404;
zat → test/admin 301; sitemap.xml отдаёт https://test.food-market.kz/.
2026-05-01 18:06:31 +05:00
nns 58df887f1c feat(domains): миграция на food-market.kz / admin.food-market.kz
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 58s
CI / Web (React + Vite) (push) Successful in 42s
Docker API / Build + push API (push) Successful in 1m38s
Docker Public / Build + push Public (push) Successful in 49s
Docker Web / Build + push Web (push) Successful in 37s
Docker API / Deploy API on stage (push) Successful in 18s
Docker Public / Deploy Public on stage (push) Successful in 9s
Docker Web / Deploy Web on stage (push) Successful in 11s
- Заменил все хардкоды URL в src/** и deploy/:
  food-market.zat.kz       → food-market.kz       (публичный сайт)
  app.food-market.zat.kz   → admin.food-market.kz (админ-API + SPA)
- public/SignupForm и Header: дефолт PUBLIC_APP_URL теперь
  https://admin.food-market.kz (раньше указывал на сам публичный домен,
  что было багом — фронт стучался не туда после переезда зон).
- public/Dockerfile ARG PUBLIC_APP_URL → admin.food-market.kz.
- API appsettings.json CORS — оставил только два прода-origin (localhost
  для dev живёт там же).
- Program.cs: добавил opts.SetIssuer(uri) если задан OpenIddict:Issuer
  в конфиге — иначе iss вычислялся из текущего HTTP-запроса и ломался
  при nginx-прокси без X-Forwarded-Proto.
- docker-compose стейджа: env OpenIddict__Issuer=https://admin.food-market.kz/
  + Cors__AllowedOrigins[0,1].

Nginx (на сервере, не в репе):
- /etc/nginx/conf.d/food-market.kz.conf, admin.food-market.kz.conf —
  новые конфиги с certbot-выданными сертификатами на оба домена
  (LetsEncrypt --webroot, действителен до 2026-07-29).
- Старые food-market.zat.kz / app.food-market.zat.kz переведены в
  301-редирект на новые домены (HTTP+HTTPS), серты zat.kz пока
  оставлены чтобы handshake шёл нормально.
2026-04-30 13:56:51 +05:00
nns 691448201d fix: пароль/orphan signup/tenant-guard toast/dashboard счётчик
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 48s
CI / Web (React + Vite) (push) Successful in 39s
Docker API / Build + push API (push) Successful in 1m9s
Docker Public / Build + push Public (push) Successful in 41s
Docker Web / Build + push Web (push) Successful in 31s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Public / Deploy Public on stage (push) Successful in 11s
Docker Web / Deploy Web on stage (push) Successful in 12s
- validation: убрана клиентская проверка «должна быть заглавная/цифра»
  — она расходилась с серверной политикой Identity и блокировала
  валидные пароли. Серверный Identity сам валидирует и возвращает
  конкретное сообщение в общий error-bar формы. Клиент проверяет
  только длину 8.

- /api/auth/signup: если AppUser с таким email уже есть, но он
  orphan (org удалена / архивирована / IsActive=false) — реактивируем
  его и привязываем к новой org вместо отказа «уже зарегистрирован».
  SuperAdmin не реактивируется (ему сценарий неактуален).
  Пароль перезаписывается на тот, что юзер ввёл сейчас.

- TenantRouteGuard: убран alert «Откройте конкретную организацию через
  Открыть как…». На каждом редиректе SuperAdmin'а из tenant-роута
  всплывал window.alert — раздражал. Поведение редиректа сохранено.
  AppLayout чистит legacy ключ из sessionStorage.

- SuperAdmin dashboard: KPI «Пользователей» теперь показывает количество
  активных, а в подсказке — сколько деактивированных. Раньше показывал
  total (включая orphan) — юзер видел «2», но в реальных списках их не
  было, потому что orphan-юзеры уже деактивированы.
2026-04-27 18:38:56 +05:00
nns ed24f5e354 fix(public): нейтральный placeholder в поле названия организации
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m4s
CI / Web (React + Vite) (push) Successful in 39s
Docker Public / Build + push Public (push) Successful in 49s
Docker Public / Deploy Public on stage (push) Successful in 11s
«Например: Магазин «Береке»» → «Наименование организации». Юзер не
хочет видеть выдуманные примеры в плейсхолдере регистрационной формы.
2026-04-27 09:45:03 +05:00
nns ff991a7101 feat(validation): русская локализация и строгий email с TLD
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m2s
CI / Web (React + Vite) (push) Successful in 39s
Docker Public / Build + push Public (push) Successful in 31s
Docker Web / Build + push Web (push) Successful in 33s
Docker Public / Deploy Public on stage (push) Successful in 11s
Docker Web / Deploy Web on stage (push) Successful in 12s
— Общий модуль `src/lib/validation.ts` в обоих пакетах (public + web):
  validateEmail (требует TLD ≥2 букв), validatePassword (8+, буква+цифра),
  validatePhone (+7/8, 10 цифр), validateRequired, и
  localizeNativeValidation — слушатель invalid/input для setCustomValidity
  на русском (required, typeMismatch, patternMismatch, tooShort и т.д.).

— public SignupForm: noValidate, валидация на onSubmit с русскими ошибками
  под каждым полем; placeholder email→ "name@example.kz", phone→ "+7 700
  123 45 67". Старый HTML5 required+minLength+type=email убран —
  теперь всё через нашу схему.

— web LoginPage: noValidate + локальная проверка email/password перед
  login(); красная подсказка появляется в самом поле.

— web Field/TextInput: useEffect с localizeNativeValidation на ref —
  все остальные админские формы (SuperAdminSetup, SuperAdminOrgCreate,
  EmployeesPage, CounterpartiesPage) автоматически получают русские
  нативные подсказки через общий компонент, без правки каждой страницы.

Acceptance: на /signup/ ввод "name@domain" без TLD блокируется
сообщением «Email должен содержать доменную зону…». Нативные браузерные
подсказки больше не на английском.
2026-04-27 08:29:22 +05:00
nns 1f2cf2a28d fix(public): убрать русские имена/ИП из placeholder регистрации
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m5s
CI / Web (React + Vite) (push) Successful in 39s
Docker Public / Build + push Public (push) Successful in 29s
Docker Public / Deploy Public on stage (push) Successful in 11s
В SignupForm.tsx плейсхолдер «ИП Иванов / Магазин у дома» →
«Например: Магазин «Береке»». Не используем выдуманных русских
персонажей в маркетинговых интерфейсах для KZ-аудитории.

Грэп всего публичного пакета и админки на типичные RU-фамилии
(Иванов/Петров/Сидоров и пр.), эл.почты mail.ru/yandex,
формы юр.лиц ОАО/ЗАО — других вхождений не найдено.
2026-04-27 08:23:17 +05:00
nns dcc3f9d61c content(public): живое наполнение — скриншоты, фото, OG, баннеры
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m5s
CI / Web (React + Vite) (push) Successful in 39s
Docker Public / Build + push Public (push) Successful in 46s
Docker Public / Deploy Public on stage (push) Successful in 10s
— 6 реальных скриншотов админки в режиме tenant override (FOOD MARKET):
  dashboard, аналитика, каталог 29 540 SKU, карточка товара, контрагенты,
  форма приёмки. Снято Playwright через app.food-market.zat.kz.

— 14 stock-фото с Unsplash (Unsplash License) для Hero-секций и CTA:
  pos, import, about, 6 вертикалей, blog covers, integrations, cta-banner.
  CREDITS.md с авторами/profile-ссылками.

— 11 OG-картинок 1200x630 через satori + @resvg/resvg-js: home, pos,
  pricing, import, about, 6 вертикалей. Inter Bold/Regular, emerald
  градиент, лого + заголовок + sub + CTA-кнопка.

— Hero-секции переделаны: full-width фото с dark-overlay-градиентом и
  белая card поверх; вертикали /for-* с одинаковым layout-баннером;
  /pricing с зелёным gradient hero; финальный CTA с emerald overlay 75%.

— BaseLayout: ogImage передаётся пропом, twitter:image добавлен.
  Каждая страница указывает свой /og/<slug>.png.

— Контентная зачистка: «торговые весы» вместо конкретных брендов,
  модели весов убраны со всех материалов. Блог-пост cash-with-scales
  переписан без брендов.

Build: 30 страниц, smoke 20 URL — 200.
2026-04-26 21:08:05 +05:00
nns 5f7cfa0d5b content(public): нейтральный тон, без упоминаний сторонних систем
— Все материалы (главная, /pos, /about, FAQ, kb, blog) переведены на
  нейтральные формулировки: «другие системы учёта», без имён.
— Новая страница /import — единая точка входа по миграции каталога;
  описывает Excel/CSV, REST API и выгрузку 1С.
— Удалены публичные kb/blog-статьи, целиком построенные вокруг
  миграции с конкретного продукта.
— /migration-from-other-system убран из sitemap; nginx делает 301 на /import.
— blog schema расширена optional-полями (author, category, cover_image),
  чтобы новый frontmatter валидировался content collection.
— Грамматические правки: «Импорт из других систем».

Финальный grep по пакету по списку имён конкурирующих SaaS-учётных
продуктов — пусто. Smoke по 9 ключевым URL — 200; старый URL — 301.
2026-04-26 20:19:57 +05:00
nns f5a232a32e content(public): naполнить блог + KB + about/contacts; убрать упоминания сторонних систем
Phase 6 контентная часть — частично:

Блог (3 поста из /tmp/content/blog-and-kb.md, content collection):
- launch — запуск Food Market
- import-other-system — пошаговый гид миграции (внутренняя инструкция, frontmatter)
- cash-with-scales — почему касса с весами Масса-К из коробки

База знаний (5 статей, content collection с category + order):
- quickstart, import-other-system, pos-setup, billing, faq

Страницы /blog и /kb перерисованы — рендерят список из коллекций
с группировкой по категориям, отдельные [slug].astro template'ы
рендерят markdown с типографикой prose-md.

/about и /contacts наполнены реальным контентом из
/tmp/content/about-contacts-pricing-features.md (без заглушек).
Контакты — 4 канала (email/phone/чат/реквизиты) с placeholder-ами
для будущих контактов после регистрации юр.лица.

Очистка от упоминаний сторонних систем (по правилу: не сравнивать с
конкретными системами и не выводить чужие цены публично):
- Hero/FAQ на главной — заменено «Импорт из сторонняя система» на «импорт
  каталога одной кнопкой», убран FAQ-вопрос «Чем отличаетесь от...»,
  заменён на общий «Чем хорош Food Market?».
- Footer: пункт «Импорт из сторонняя система» → «Импорт каталога».
- /migration-from-other-system удалён (содержал сравнительную таблицу
  с ценами сторонняя система). Возможен возврат как KB-инструкция позже.
- features/pos/pricing/integrations/changelog/for-grocery/for-pharmacy
  — sed-замена «сторонняя система» → «другие системы», ссылки на
  /migration-from-other-system → /features.
- В content-collections блог/KB остались упоминания (юзер пришлёт
  PATCH-версии текстов отдельно).

Контейнер пересобран и задеплоен на стенд: 30 страниц, smoke на /,
/blog, /kb, /about, /blog/launch — все 200.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:53:41 +05:00
nns ad09d48a89 feat(public): Phase 6 — публичный маркетинговый сайт food-market.public на Astro
Новый пакет src/food-market.public/ — отдельный фронт для маркетинга и
самостоятельной регистрации магазинов-клиентов в SaaS Food Market.
Существующая админка food-market.web НЕ затронута.

Стек: Astro 4 + React 19 islands + Tailwind v3 (палитра идентична
food-market.web — единый бренд), TypeScript 6, content collections для
юр.документов. Static-сайт через nginx, gzip + immutable cache на assets.

Карта страниц (23):
- /                            — главная (Hero + 3 выгоды + скриншот +
                                   6 вертикалей + 6 модулей + Касса +
                                   Интеграции + 3 тарифа + соцпруф +
                                   FAQ + финальный CTA)
- /features                    — модули по сценариям
- /pricing                     — тарифы + интерактивный конструктор
                                   «Бизнес» (per-unit: 10000 база +
                                   2000/магазин + 500/касса + 500/склад,
                                   слайдеры, передача params в /signup)
- /pos                         — УТП лендинг кассы для Windows + весов
- /migration-from-other-system     — УТП лендинг миграции с сторонняя система
                                   (сравнительная таблица + 3 шага)
- /integrations                — список интеграций
- /for-grocery|pharmacy|cafe|alcohol|clothing|household — 6 вертикалей
                                   с уникальными фишками (весовой,
                                   серии/сроки, модификаторы и комбо,
                                   акцизные марки, размерные сетки,
                                   гарантийные сроки)
- /signup                      — регистрация (React-island форма)
- /about /contacts /kb /blog /status /changelog — компания + ресурсы
- /legal/{offer,privacy,consent,requisites} — реальные юр.документы
                                   из /tmp/legal/ как Astro content
                                   collection (markdown с frontmatter,
                                   динамический [slug].astro template,
                                   720px max-width, line-height 1.7,
                                   prose-legal стили)
- /sitemap.xml                 — ручной генератор (sitemap-плагин
                                   конфликтует с Astro 4.16, заменён
                                   на простой APIRoute)

React-острова (3):
- BusinessTariffBuilder — слайдеры + расчёт total + ссылка на signup
- SignupForm — email/password/orgName/phone/plan + валидация + agree
- FAQ — accordion 7 вопросов

API: новый POST /api/auth/signup создаёт Organization + AppUser
(Identity Admin role) + Owner Employee + полный bootstrap через
DevDataSeeder.SeedTenantReferencesAsync (units, price-types, store,
cassa, 6 ролей). Токены НЕ выпускает — фронт сразу делает обычный
/connect/token (password grant) и получает access/refresh без
дублирования OpenIddict-логики. На signup-форме — auth-bridge:
токены передаются через URL fragment в админку
APP_URL/auth-bridge#access=...&refresh=...&welcome=1, AuthBridgePage
кладёт в localStorage и редиректит на /?welcome=signup.

URL-домены через env-переменные (юзер ещё выбирает финальный):
- PUBLIC_SITE_URL — canonical/OG/sitemap (default https://food-market.kz)
- PUBLIC_APP_URL — admin/API endpoint (default https://food-market.zat.kz)
Nginx-конфиг для деплоя сайта — заготовка-template в
deploy/nginx/food-market-public.conf.template, не применён —
ждёт решения по домену.

Dockerfile multi-stage (node:20-alpine build → nginx:1.27 runtime),
build-args PUBLIC_SITE_URL/PUBLIC_APP_URL, deploy/nginx.conf gzip +
immutable cache + try_files для pretty URLs.

SEO: OG-теги, twitter-card, canonical, JSON-LD SoftwareApplication
схема, robots.txt → sitemap-index, lang=ru-KZ.

Admin-side: /auth-bridge route в food-market.web — принимает токены
из URL fragment, кладёт в localStorage (fm.access_token / fm.refresh_token),
редиректит на /. Fragment чтобы access_token не попадал в Referer.

23 страницы билдятся без ошибок. Контейнер собирается. Деплой на
конкретный домен — отдельным шагом после решения юзера.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:11:34 +05:00