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>
This commit is contained in:
parent
fc3f63c49a
commit
ad09d48a89
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -90,3 +90,6 @@ postgres-data/
|
||||||
|
|
||||||
## Claude Code personal settings
|
## Claude Code personal settings
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
|
src/food-market.public/.astro/
|
||||||
|
src/food-market.public/dist/
|
||||||
|
src/food-market.public/node_modules/
|
||||||
|
|
|
||||||
32
deploy/nginx/food-market-public.conf.template
Normal file
32
deploy/nginx/food-market-public.conf.template
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Шаблон nginx-конфига для публичного сайта food-market.public.
|
||||||
|
# НЕ ПРИМЕНЯТЬ ПОКА ЮЗЕР НЕ ВЫБЕРЕТ ДОМЕН.
|
||||||
|
#
|
||||||
|
# Сборка контейнера: docker compose --build food-market-public (см.
|
||||||
|
# deploy/docker-compose.yml; контейнер слушает на 127.0.0.1:8082).
|
||||||
|
#
|
||||||
|
# Использование (когда домен решится):
|
||||||
|
# 1. Заменить SERVER_NAME ниже на финальный домен.
|
||||||
|
# 2. Скопировать в /etc/nginx/conf.d/food-market-public.conf.
|
||||||
|
# 3. sudo certbot --nginx -d <SERVER_NAME>.
|
||||||
|
# 4. sudo nginx -t && sudo systemctl reload nginx.
|
||||||
|
#
|
||||||
|
# Архитектура после переезда (план):
|
||||||
|
# <PUBLIC_DOMAIN> → этот блок (публичный Astro)
|
||||||
|
# app.<PUBLIC_DOMAIN> → существующий блок food-market-stage.conf (админка)
|
||||||
|
# API остаётся на app.* под /api/*.
|
||||||
|
|
||||||
|
server {
|
||||||
|
server_name SERVER_NAME;
|
||||||
|
location /.well-known/acme-challenge/ { root /var/www/html; }
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8082;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
}
|
||||||
96
src/food-market.api/Controllers/AuthSignupController.cs
Normal file
96
src/food-market.api/Controllers/AuthSignupController.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
using foodmarket.Api.Seed;
|
||||||
|
using foodmarket.Domain.Organizations;
|
||||||
|
using foodmarket.Infrastructure.Identity;
|
||||||
|
using foodmarket.Infrastructure.Persistence;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace foodmarket.Api.Controllers;
|
||||||
|
|
||||||
|
/// <summary>Самообслуживание: регистрация новой организации с публичного
|
||||||
|
/// маркетингового сайта. Создаёт Organization + bootstrap (Stores, Roles,
|
||||||
|
/// Units, PriceTypes, Cassa) + первого Owner-Employee-AppUser-Admin.
|
||||||
|
///
|
||||||
|
/// Токены НЕ выпускаются здесь — фронт получает их обычным запросом
|
||||||
|
/// /connect/token (password grant) сразу после успешного signup. Это
|
||||||
|
/// убирает дублирование с OpenIddict и упрощает контракт. Phase 6: без
|
||||||
|
/// email-верификации (переедет в Phase 7).</summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/auth")]
|
||||||
|
public class AuthSignupController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _db;
|
||||||
|
private readonly UserManager<User> _userMgr;
|
||||||
|
|
||||||
|
public AuthSignupController(AppDbContext db, UserManager<User> userMgr)
|
||||||
|
{
|
||||||
|
_db = db; _userMgr = userMgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record SignupInput(string Email, string Password, string OrganizationName, string? Phone, string? Plan);
|
||||||
|
public record SignupResult(Guid OrganizationId, string Email);
|
||||||
|
|
||||||
|
[HttpPost("signup")]
|
||||||
|
public async Task<ActionResult<SignupResult>> Signup([FromBody] SignupInput input, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input.Email) || string.IsNullOrWhiteSpace(input.Password)
|
||||||
|
|| string.IsNullOrWhiteSpace(input.OrganizationName))
|
||||||
|
return BadRequest(new { error = "Email, пароль и название обязательны." });
|
||||||
|
if (input.Password.Length < 8)
|
||||||
|
return BadRequest(new { error = "Пароль минимум 8 символов." });
|
||||||
|
|
||||||
|
var existing = await _userMgr.FindByEmailAsync(input.Email);
|
||||||
|
if (existing is not null)
|
||||||
|
return BadRequest(new { error = "Пользователь с таким email уже зарегистрирован." });
|
||||||
|
|
||||||
|
// 1. Organization + полный bootstrap tenant-сущностей.
|
||||||
|
var kzt = await _db.Currencies.FirstOrDefaultAsync(c => c.Code == "KZT", ct);
|
||||||
|
var org = new Organization
|
||||||
|
{
|
||||||
|
Name = input.OrganizationName.Trim(),
|
||||||
|
CountryCode = "KZ",
|
||||||
|
DefaultCurrencyId = kzt?.Id,
|
||||||
|
Phone = string.IsNullOrWhiteSpace(input.Phone) ? null : input.Phone.Trim(),
|
||||||
|
Email = input.Email.Trim(),
|
||||||
|
};
|
||||||
|
_db.Organizations.Add(org);
|
||||||
|
await _db.SaveChangesAsync(ct);
|
||||||
|
await DevDataSeeder.SeedTenantReferencesAsync(_db, org.Id, ct);
|
||||||
|
|
||||||
|
// 2. AppUser в роли Identity Admin, привязан к этой организации.
|
||||||
|
var user = new User
|
||||||
|
{
|
||||||
|
UserName = input.Email.Trim(),
|
||||||
|
Email = input.Email.Trim(),
|
||||||
|
EmailConfirmed = true,
|
||||||
|
FullName = input.OrganizationName.Trim(),
|
||||||
|
OrganizationId = org.Id,
|
||||||
|
IsActive = true,
|
||||||
|
};
|
||||||
|
var ur = await _userMgr.CreateAsync(user, input.Password);
|
||||||
|
if (!ur.Succeeded)
|
||||||
|
{
|
||||||
|
// Откат: убираем органзацию чтобы не оставить orphan.
|
||||||
|
_db.Organizations.Remove(org);
|
||||||
|
await _db.SaveChangesAsync(ct);
|
||||||
|
return BadRequest(new { error = string.Join("; ", ur.Errors.Select(e => e.Description)) });
|
||||||
|
}
|
||||||
|
await _userMgr.AddToRoleAsync(user, "Admin");
|
||||||
|
|
||||||
|
// 3. Owner Employee с системной ролью «Администратор».
|
||||||
|
var adminRole = await _db.EmployeeRoles.IgnoreQueryFilters()
|
||||||
|
.FirstAsync(r => r.OrganizationId == org.Id && r.IsSystem && r.Name == "Администратор", ct);
|
||||||
|
_db.Employees.Add(new Employee
|
||||||
|
{
|
||||||
|
OrganizationId = org.Id, UserId = user.Id,
|
||||||
|
LastName = input.OrganizationName.Trim(), FirstName = "Owner",
|
||||||
|
Position = "Владелец", Email = input.Email.Trim(),
|
||||||
|
RoleId = adminRole.Id, IsActive = true,
|
||||||
|
});
|
||||||
|
org.AccountOwnerUserId = user.Id;
|
||||||
|
await _db.SaveChangesAsync(ct);
|
||||||
|
|
||||||
|
return new SignupResult(org.Id, user.Email);
|
||||||
|
}
|
||||||
|
}
|
||||||
183
src/food-market.public/.astro/astro/content.d.ts
vendored
Normal file
183
src/food-market.public/.astro/astro/content.d.ts
vendored
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
declare module 'astro:content' {
|
||||||
|
interface RenderResult {
|
||||||
|
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
||||||
|
headings: import('astro').MarkdownHeading[];
|
||||||
|
remarkPluginFrontmatter: Record<string, any>;
|
||||||
|
}
|
||||||
|
interface Render {
|
||||||
|
'.md': Promise<RenderResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RenderedContent {
|
||||||
|
html: string;
|
||||||
|
metadata?: {
|
||||||
|
imagePaths: Array<string>;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'astro:content' {
|
||||||
|
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
||||||
|
|
||||||
|
export type CollectionKey = keyof AnyEntryMap;
|
||||||
|
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
|
||||||
|
|
||||||
|
export type ContentCollectionKey = keyof ContentEntryMap;
|
||||||
|
export type DataCollectionKey = keyof DataEntryMap;
|
||||||
|
|
||||||
|
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
||||||
|
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
|
||||||
|
ContentEntryMap[C]
|
||||||
|
>['slug'];
|
||||||
|
|
||||||
|
/** @deprecated Use `getEntry` instead. */
|
||||||
|
export function getEntryBySlug<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
// Note that this has to accept a regular string too, for SSR
|
||||||
|
entrySlug: E,
|
||||||
|
): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
|
||||||
|
/** @deprecated Use `getEntry` instead. */
|
||||||
|
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
|
||||||
|
collection: C,
|
||||||
|
entryId: E,
|
||||||
|
): Promise<CollectionEntry<C>>;
|
||||||
|
|
||||||
|
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
|
||||||
|
collection: C,
|
||||||
|
filter?: (entry: CollectionEntry<C>) => entry is E,
|
||||||
|
): Promise<E[]>;
|
||||||
|
export function getCollection<C extends keyof AnyEntryMap>(
|
||||||
|
collection: C,
|
||||||
|
filter?: (entry: CollectionEntry<C>) => unknown,
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(entry: {
|
||||||
|
collection: C;
|
||||||
|
slug: E;
|
||||||
|
}): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof DataEntryMap,
|
||||||
|
E extends keyof DataEntryMap[C] | (string & {}),
|
||||||
|
>(entry: {
|
||||||
|
collection: C;
|
||||||
|
id: E;
|
||||||
|
}): E extends keyof DataEntryMap[C]
|
||||||
|
? Promise<DataEntryMap[C][E]>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
slug: E,
|
||||||
|
): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof DataEntryMap,
|
||||||
|
E extends keyof DataEntryMap[C] | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
id: E,
|
||||||
|
): E extends keyof DataEntryMap[C]
|
||||||
|
? Promise<DataEntryMap[C][E]>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
|
||||||
|
/** Resolve an array of entry references from the same collection */
|
||||||
|
export function getEntries<C extends keyof ContentEntryMap>(
|
||||||
|
entries: {
|
||||||
|
collection: C;
|
||||||
|
slug: ValidContentEntrySlug<C>;
|
||||||
|
}[],
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
export function getEntries<C extends keyof DataEntryMap>(
|
||||||
|
entries: {
|
||||||
|
collection: C;
|
||||||
|
id: keyof DataEntryMap[C];
|
||||||
|
}[],
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
|
||||||
|
export function render<C extends keyof AnyEntryMap>(
|
||||||
|
entry: AnyEntryMap[C][string],
|
||||||
|
): Promise<RenderResult>;
|
||||||
|
|
||||||
|
export function reference<C extends keyof AnyEntryMap>(
|
||||||
|
collection: C,
|
||||||
|
): import('astro/zod').ZodEffects<
|
||||||
|
import('astro/zod').ZodString,
|
||||||
|
C extends keyof ContentEntryMap
|
||||||
|
? {
|
||||||
|
collection: C;
|
||||||
|
slug: ValidContentEntrySlug<C>;
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
collection: C;
|
||||||
|
id: keyof DataEntryMap[C];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
// Allow generic `string` to avoid excessive type errors in the config
|
||||||
|
// if `dev` is not running to update as you edit.
|
||||||
|
// Invalid collection names will be caught at build time.
|
||||||
|
export function reference<C extends string>(
|
||||||
|
collection: C,
|
||||||
|
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
|
||||||
|
|
||||||
|
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
||||||
|
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
|
||||||
|
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
||||||
|
>;
|
||||||
|
|
||||||
|
type ContentEntryMap = {
|
||||||
|
"legal": {
|
||||||
|
"consent.md": {
|
||||||
|
id: "consent.md";
|
||||||
|
slug: "consent";
|
||||||
|
body: string;
|
||||||
|
collection: "legal";
|
||||||
|
data: InferEntrySchema<"legal">
|
||||||
|
} & { render(): Render[".md"] };
|
||||||
|
"offer.md": {
|
||||||
|
id: "offer.md";
|
||||||
|
slug: "offer";
|
||||||
|
body: string;
|
||||||
|
collection: "legal";
|
||||||
|
data: InferEntrySchema<"legal">
|
||||||
|
} & { render(): Render[".md"] };
|
||||||
|
"privacy.md": {
|
||||||
|
id: "privacy.md";
|
||||||
|
slug: "privacy";
|
||||||
|
body: string;
|
||||||
|
collection: "legal";
|
||||||
|
data: InferEntrySchema<"legal">
|
||||||
|
} & { render(): Render[".md"] };
|
||||||
|
"requisites.md": {
|
||||||
|
id: "requisites.md";
|
||||||
|
slug: "requisites";
|
||||||
|
body: string;
|
||||||
|
collection: "legal";
|
||||||
|
data: InferEntrySchema<"legal">
|
||||||
|
} & { render(): Render[".md"] };
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type DataEntryMap = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type AnyEntryMap = ContentEntryMap & DataEntryMap;
|
||||||
|
|
||||||
|
export type ContentConfig = typeof import("../../src/content/config.js");
|
||||||
|
}
|
||||||
2
src/food-market.public/.astro/types.d.ts
vendored
Normal file
2
src/food-market.public/.astro/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/// <reference types="astro/client" />
|
||||||
|
/// <reference path="astro/content.d.ts" />
|
||||||
5
src/food-market.public/.env.example
Normal file
5
src/food-market.public/.env.example
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Публичный URL самого сайта (canonical, OG, sitemap)
|
||||||
|
PUBLIC_SITE_URL=https://food-market.kz
|
||||||
|
|
||||||
|
# Админка / API — куда уходит юзер после signup, и куда шлются POST /api/auth/signup
|
||||||
|
PUBLIC_APP_URL=https://food-market.zat.kz
|
||||||
22
src/food-market.public/Dockerfile
Normal file
22
src/food-market.public/Dockerfile
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Multi-stage build для Astro static site.
|
||||||
|
# Build envs:
|
||||||
|
# PUBLIC_SITE_URL — публичный URL сайта (canonical/OG/sitemap), default https://food-market.kz
|
||||||
|
# PUBLIC_APP_URL — admin/API endpoint (для Header «Войти» и signup-формы)
|
||||||
|
FROM node:20-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json ./
|
||||||
|
# pnpm 10 — как в основной web-build'е
|
||||||
|
RUN corepack enable && corepack prepare pnpm@10 --activate
|
||||||
|
COPY pnpm-lock.yaml* ./
|
||||||
|
RUN pnpm install --frozen-lockfile || pnpm install
|
||||||
|
COPY . .
|
||||||
|
ARG PUBLIC_SITE_URL=https://food-market.kz
|
||||||
|
ARG PUBLIC_APP_URL=https://food-market.zat.kz
|
||||||
|
ENV PUBLIC_SITE_URL=$PUBLIC_SITE_URL
|
||||||
|
ENV PUBLIC_APP_URL=$PUBLIC_APP_URL
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
FROM nginx:1.27-alpine AS runtime
|
||||||
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
14
src/food-market.public/astro.config.mjs
Normal file
14
src/food-market.public/astro.config.mjs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { defineConfig } from 'astro/config'
|
||||||
|
import react from '@astrojs/react'
|
||||||
|
import tailwind from '@astrojs/tailwind'
|
||||||
|
|
||||||
|
// sitemap-плагин временно отключён — конфликтует с Astro 4.16 (bug в
|
||||||
|
// astro:build:done hook'е). Добавим обратно после фикса плагина или
|
||||||
|
// заменим на ручную генерацию sitemap.xml.
|
||||||
|
export default defineConfig({
|
||||||
|
site: process.env.PUBLIC_SITE_URL || 'https://food-market.kz',
|
||||||
|
integrations: [react(), tailwind({ applyBaseStyles: false })],
|
||||||
|
output: 'static',
|
||||||
|
build: { inlineStylesheets: 'auto' },
|
||||||
|
prefetch: { prefetchAll: false, defaultStrategy: 'hover' },
|
||||||
|
})
|
||||||
22
src/food-market.public/deploy/nginx.conf
Normal file
22
src/food-market.public/deploy/nginx.conf
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Astro static — gzip + длинный кэш для assets с хешами в имени.
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/css application/javascript application/json image/svg+xml text/plain;
|
||||||
|
gzip_min_length 1000;
|
||||||
|
|
||||||
|
location ~* ^/assets/.*\.(js|css|woff2?|svg|png|jpg|webp)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pretty URLs: /pricing → /pricing/index.html или /pricing.html
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ $uri.html /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/food-market.public/package.json
Normal file
23
src/food-market.public/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "food-market.public",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"astro": "^4.16.18",
|
||||||
|
"@astrojs/react": "^3.6.3",
|
||||||
|
"@astrojs/sitemap": "^3.2.1",
|
||||||
|
"@astrojs/tailwind": "^5.1.4",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"react": "^19.2.5",
|
||||||
|
"react-dom": "^19.2.5",
|
||||||
|
"@types/react": "^19.2.14",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"lucide-react": "^1.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
4093
src/food-market.public/pnpm-lock.yaml
Normal file
4093
src/food-market.public/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
1
src/food-market.public/public/favicon.svg
Normal file
1
src/food-market.public/public/favicon.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#00B207"/><text x="32" y="44" font-family="Inter,system-ui,sans-serif" font-weight="900" font-size="28" fill="#fff" text-anchor="middle">FM</text></svg>
|
||||||
|
After Width: | Height: | Size: 256 B |
3
src/food-market.public/public/robots.txt
Normal file
3
src/food-market.public/public/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
Sitemap: https://food-market.zat.kz/sitemap-index.xml
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
const BASE = 10000
|
||||||
|
const PER_SHOP = 2000
|
||||||
|
const PER_CASH = 500
|
||||||
|
const PER_WAREHOUSE = 500
|
||||||
|
|
||||||
|
interface SliderProps {
|
||||||
|
label: string; value: number; min: number; max: number; onChange: (v: number) => void; suffix?: string
|
||||||
|
}
|
||||||
|
function Slider({ label, value, min, max, onChange, suffix }: SliderProps) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-baseline justify-between">
|
||||||
|
<label className="text-sm font-medium">{label}</label>
|
||||||
|
<span className="text-2xl font-bold tabular-nums">{value}{suffix && <span className="text-sm text-slate-500">{suffix}</span>}</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range" min={min} max={max} value={value}
|
||||||
|
onChange={(e) => onChange(Number(e.target.value))}
|
||||||
|
className="w-full mt-2 accent-[#00B207]"
|
||||||
|
/>
|
||||||
|
<div className="flex justify-between text-xs text-slate-400 mt-1"><span>{min}</span><span>{max}</span></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BusinessTariffBuilder() {
|
||||||
|
const [shops, setShops] = useState(1)
|
||||||
|
const [cashes, setCashes] = useState(1)
|
||||||
|
const [warehouses, setWarehouses] = useState(1)
|
||||||
|
const [employees, setEmployees] = useState(5)
|
||||||
|
|
||||||
|
const total = useMemo(() => {
|
||||||
|
return BASE
|
||||||
|
+ Math.max(0, shops - 1) * PER_SHOP
|
||||||
|
+ Math.max(0, cashes - 1) * PER_CASH
|
||||||
|
+ Math.max(0, warehouses - 1) * PER_WAREHOUSE
|
||||||
|
}, [shops, cashes, warehouses])
|
||||||
|
|
||||||
|
const fmt = new Intl.NumberFormat('ru')
|
||||||
|
const signupHref = `/signup?plan=business&shops=${shops}&cashes=${cashes}&warehouses=${warehouses}&employees=${employees}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-xl border-2 border-brand bg-white p-6">
|
||||||
|
<div className="flex items-baseline justify-between mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold">Конструктор «Бизнес»</h3>
|
||||||
|
<p className="text-xs text-slate-500 mt-0.5">База 10 000 ₸ + ваши параметры</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="text-3xl font-extrabold">{fmt.format(total)} ₸</div>
|
||||||
|
<div className="text-xs text-slate-500">в месяц</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-5">
|
||||||
|
<Slider label="Магазинов (точек продаж)" value={shops} min={1} max={3} onChange={setShops} />
|
||||||
|
<Slider label="Касс" value={cashes} min={1} max={5} onChange={setCashes} />
|
||||||
|
<Slider label="Складов" value={warehouses} min={1} max={5} onChange={setWarehouses} />
|
||||||
|
<Slider label="Сотрудников" value={employees} min={5} max={10} onChange={setEmployees} />
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 text-xs text-slate-500 grid grid-cols-3 gap-2">
|
||||||
|
<div>+{fmt.format(PER_SHOP)} ₸ / магазин</div>
|
||||||
|
<div>+{fmt.format(PER_CASH)} ₸ / касса</div>
|
||||||
|
<div>+{fmt.format(PER_WAREHOUSE)} ₸ / склад</div>
|
||||||
|
</div>
|
||||||
|
<a href={signupHref} className="block text-center mt-6 px-4 py-3 bg-brand text-white rounded-md font-semibold hover:bg-[#009305]">
|
||||||
|
Выбрать конфигурацию · {fmt.format(total)} ₸/мес
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
33
src/food-market.public/src/components/FAQ.tsx
Normal file
33
src/food-market.public/src/components/FAQ.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
const ITEMS = [
|
||||||
|
{ q: 'Что такое Food Market?', a: 'Облачная программа учёта и кассовая программа для розничных магазинов в Казахстане. Включает товарный учёт, склад, кассу с поддержкой весов, импорт из МойСклада и интеграции с банками и ОФД РК.' },
|
||||||
|
{ q: 'Чем отличаетесь от МойСклад / UMag?', a: 'Касса с поддержкой весов Масса-К из коробки, импорт из МойСклада за 1 клик, единый тариф без скрытых доплат за CRM/финансы/лояльность, цены в тенге, локальная поддержка KZ.' },
|
||||||
|
{ q: 'Нужно ли покупать железо?', a: 'Касса работает на любом Windows-компьютере. Весы Масса-К подключаются по USB или COM-порту. Сканер штрихкодов и чековый принтер можно купить за 30-50 тыс. тг отдельно.' },
|
||||||
|
{ q: 'Как импортировать товары из МойСклад?', a: 'Подключаем ваш токен МойСклад API в настройках, нажимаете «Импортировать» — товары, группы, штрихкоды и остатки переносятся автоматически за 5–10 минут.' },
|
||||||
|
{ q: 'Можно ли отменить подписку?', a: 'Да, в любой момент. Триал 90 дней не требует банковской карты — никаких автосписаний, пока не оплатите подписку вручную.' },
|
||||||
|
{ q: 'Есть ли мобильное приложение?', a: 'Веб-админка адаптирована под мобильные браузеры. Нативное приложение для iOS/Android — в дорожной карте на 2026.' },
|
||||||
|
{ q: 'Где вы хранитe данные?', a: 'Данные хранятся в дата-центре в Казахстане. Резервные копии шифруются и хранятся отдельно. Соответствие ЗРК «О персональных данных».' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function FAQ() {
|
||||||
|
const [open, setOpen] = useState<number | null>(0)
|
||||||
|
return (
|
||||||
|
<ul className="divide-y divide-slate-200 border border-slate-200 rounded-xl bg-white">
|
||||||
|
{ITEMS.map((it, i) => (
|
||||||
|
<li key={i}>
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(open === i ? null : i)}
|
||||||
|
className="w-full flex items-center justify-between gap-3 px-5 py-4 text-left hover:bg-slate-50"
|
||||||
|
>
|
||||||
|
<span className="font-medium">{it.q}</span>
|
||||||
|
<span className={`text-slate-400 transition ${open === i ? 'rotate-45' : ''}`}>+</span>
|
||||||
|
</button>
|
||||||
|
{open === i && (
|
||||||
|
<div className="px-5 pb-4 text-sm text-slate-600">{it.a}</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
58
src/food-market.public/src/components/Footer.astro
Normal file
58
src/food-market.public/src/components/Footer.astro
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
import Logo from './Logo.astro'
|
||||||
|
---
|
||||||
|
<footer class="bg-slate-50 border-t border-slate-200 mt-16">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-10 grid grid-cols-2 md:grid-cols-5 gap-6 text-sm">
|
||||||
|
<div class="col-span-2 md:col-span-1">
|
||||||
|
<Logo />
|
||||||
|
<p class="text-slate-500 mt-3 text-xs">
|
||||||
|
Программа учёта и касса для розничных магазинов в Казахстане.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xs font-semibold uppercase text-slate-500 mb-2">Продукт</h3>
|
||||||
|
<ul class="space-y-1.5">
|
||||||
|
<li><a href="/features" class="text-slate-700 hover:text-brand">Возможности</a></li>
|
||||||
|
<li><a href="/pos" class="text-slate-700 hover:text-brand">Касса для Windows</a></li>
|
||||||
|
<li><a href="/migration-from-moysklad" class="text-slate-700 hover:text-brand">Импорт из МойСклад</a></li>
|
||||||
|
<li><a href="/integrations" class="text-slate-700 hover:text-brand">Интеграции</a></li>
|
||||||
|
<li><a href="/pricing" class="text-slate-700 hover:text-brand">Тарифы</a></li>
|
||||||
|
<li><a href="/changelog" class="text-slate-700 hover:text-brand">Дорожная карта</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xs font-semibold uppercase text-slate-500 mb-2">Для кого</h3>
|
||||||
|
<ul class="space-y-1.5">
|
||||||
|
<li><a href="/for-grocery" class="text-slate-700 hover:text-brand">Продуктовый магазин</a></li>
|
||||||
|
<li><a href="/for-pharmacy" class="text-slate-700 hover:text-brand">Аптека</a></li>
|
||||||
|
<li><a href="/for-cafe" class="text-slate-700 hover:text-brand">Кафе и общепит</a></li>
|
||||||
|
<li><a href="/for-alcohol" class="text-slate-700 hover:text-brand">Алкоголь</a></li>
|
||||||
|
<li><a href="/for-clothing" class="text-slate-700 hover:text-brand">Одежда</a></li>
|
||||||
|
<li><a href="/for-household" class="text-slate-700 hover:text-brand">Дом и быт</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xs font-semibold uppercase text-slate-500 mb-2">Ресурсы</h3>
|
||||||
|
<ul class="space-y-1.5">
|
||||||
|
<li><a href="/kb" class="text-slate-700 hover:text-brand">База знаний</a></li>
|
||||||
|
<li><a href="/blog" class="text-slate-700 hover:text-brand">Блог</a></li>
|
||||||
|
<li><a href="/status" class="text-slate-700 hover:text-brand">Status</a></li>
|
||||||
|
<li><a href="/changelog" class="text-slate-700 hover:text-brand">Changelog</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xs font-semibold uppercase text-slate-500 mb-2">Компания</h3>
|
||||||
|
<ul class="space-y-1.5">
|
||||||
|
<li><a href="/about" class="text-slate-700 hover:text-brand">О нас</a></li>
|
||||||
|
<li><a href="/contacts" class="text-slate-700 hover:text-brand">Контакты</a></li>
|
||||||
|
<li><a href="/legal/offer" class="text-slate-700 hover:text-brand">Оферта</a></li>
|
||||||
|
<li><a href="/legal/privacy" class="text-slate-700 hover:text-brand">Политика ПДн</a></li>
|
||||||
|
<li><a href="/legal/consent" class="text-slate-700 hover:text-brand">Согласие ПДн</a></li>
|
||||||
|
<li><a href="/legal/requisites" class="text-slate-700 hover:text-brand">Реквизиты</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-slate-200 py-4 text-center text-xs text-slate-400">
|
||||||
|
© {new Date().getFullYear()} Food Market. Все права защищены.
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
34
src/food-market.public/src/components/Header.astro
Normal file
34
src/food-market.public/src/components/Header.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import Logo from './Logo.astro'
|
||||||
|
const APP_URL = import.meta.env.PUBLIC_APP_URL || 'https://food-market.zat.kz'
|
||||||
|
---
|
||||||
|
<header class="sticky top-0 z-40 bg-white/95 backdrop-blur border-b border-slate-200">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 h-14 flex items-center justify-between gap-4">
|
||||||
|
<a href="/" class="flex items-center gap-2"><Logo /></a>
|
||||||
|
|
||||||
|
<nav class="hidden lg:flex items-center gap-1 text-sm">
|
||||||
|
<a href="/features" class="px-3 py-2 hover:text-brand transition">Возможности</a>
|
||||||
|
<a href="/pos" class="px-3 py-2 hover:text-brand transition">Касса</a>
|
||||||
|
<div class="relative group">
|
||||||
|
<button class="px-3 py-2 hover:text-brand transition">Для кого ▾</button>
|
||||||
|
<div class="absolute hidden group-hover:block top-full left-0 w-56 bg-white border border-slate-200 rounded-lg shadow-lg p-1.5">
|
||||||
|
<a href="/for-grocery" class="block px-3 py-1.5 rounded hover:bg-slate-50 text-sm">Продуктовый магазин</a>
|
||||||
|
<a href="/for-pharmacy" class="block px-3 py-1.5 rounded hover:bg-slate-50 text-sm">Аптека</a>
|
||||||
|
<a href="/for-cafe" class="block px-3 py-1.5 rounded hover:bg-slate-50 text-sm">Кафе и общепит</a>
|
||||||
|
<a href="/for-alcohol" class="block px-3 py-1.5 rounded hover:bg-slate-50 text-sm">Алкоголь</a>
|
||||||
|
<a href="/for-clothing" class="block px-3 py-1.5 rounded hover:bg-slate-50 text-sm">Одежда</a>
|
||||||
|
<a href="/for-household" class="block px-3 py-1.5 rounded hover:bg-slate-50 text-sm">Дом и быт</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="/integrations" class="px-3 py-2 hover:text-brand transition">Интеграции</a>
|
||||||
|
<a href="/pricing" class="px-3 py-2 hover:text-brand transition">Тарифы</a>
|
||||||
|
<a href="/kb" class="px-3 py-2 hover:text-brand transition">База знаний</a>
|
||||||
|
<a href="/blog" class="px-3 py-2 hover:text-brand transition">Блог</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<a href={`${APP_URL}/login`} class="px-3 py-1.5 text-sm text-slate-700 hover:text-brand">Войти</a>
|
||||||
|
<a href="/signup" class="px-3 py-1.5 text-sm font-medium bg-brand text-white rounded-md hover:bg-brand-hover">Начать бесплатно</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
9
src/food-market.public/src/components/Logo.astro
Normal file
9
src/food-market.public/src/components/Logo.astro
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
interface Props { variant?: 'light' | 'dark' }
|
||||||
|
const { variant = 'light' } = Astro.props
|
||||||
|
const isDark = variant === 'dark'
|
||||||
|
---
|
||||||
|
<div class="inline-flex flex-col leading-none select-none">
|
||||||
|
<span class={`font-black tracking-[0.08em] text-base ${isDark ? 'text-slate-50' : 'text-slate-900'}`}>FOOD</span>
|
||||||
|
<span class="font-black text-[11px] tracking-[0.24em] mt-0.5" style={`color: ${isDark ? '#34D399' : '#00B207'}`}>MARKET</span>
|
||||||
|
</div>
|
||||||
119
src/food-market.public/src/components/SignupForm.tsx
Normal file
119
src/food-market.public/src/components/SignupForm.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
// Админский / API endpoint — переключается через PUBLIC_APP_URL на этапе
|
||||||
|
// билда. Дефолт пока — текущий рабочий food-market.zat.kz.
|
||||||
|
const APP_URL = (import.meta.env.PUBLIC_APP_URL as string | undefined) ?? 'https://food-market.zat.kz'
|
||||||
|
const API_URL = APP_URL
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
defaultPlan?: string
|
||||||
|
defaultShops?: number
|
||||||
|
defaultCashes?: number
|
||||||
|
defaultWarehouses?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SignupForm({ defaultPlan = 'start' }: Props) {
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [orgName, setOrgName] = useState('')
|
||||||
|
const [phone, setPhone] = useState('')
|
||||||
|
const [plan, setPlan] = useState(defaultPlan)
|
||||||
|
const [agree, setAgree] = useState(false)
|
||||||
|
const [busy, setBusy] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const submit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!agree) { setError('Подтвердите согласие с офертой и политикой ПДн.'); return }
|
||||||
|
if (password.length < 8) { setError('Пароль минимум 8 символов.'); return }
|
||||||
|
setError(null); setBusy(true)
|
||||||
|
try {
|
||||||
|
// 1. Создаём организацию + Owner-Employee на /api/auth/signup.
|
||||||
|
const res = await fetch(`${API_URL}/api/auth/signup`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ email, password, organizationName: orgName, phone: phone || null, plan }),
|
||||||
|
})
|
||||||
|
if (!res.ok) {
|
||||||
|
const j = await res.json().catch(() => ({}))
|
||||||
|
throw new Error(j.error ?? `HTTP ${res.status}`)
|
||||||
|
}
|
||||||
|
// 2. Получаем JWT через стандартный password-grant — тот же путь, что
|
||||||
|
// делает админка при обычном логине. Никакого дублирования OpenIddict.
|
||||||
|
const tokRes = await fetch(`${API_URL}/connect/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: new URLSearchParams({
|
||||||
|
grant_type: 'password', username: email, password,
|
||||||
|
client_id: 'food-market-web',
|
||||||
|
scope: 'openid profile email roles api offline_access',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if (!tokRes.ok) throw new Error('Регистрация прошла, но токен не выпущен. Войдите вручную.')
|
||||||
|
const tok = await tokRes.json() as { access_token: string; refresh_token: string }
|
||||||
|
// 3. Auth-bridge: передаём токены через URL fragment в админку.
|
||||||
|
const url = `${APP_URL}/auth-bridge#access=${encodeURIComponent(tok.access_token)}&refresh=${encodeURIComponent(tok.refresh_token)}&welcome=1`
|
||||||
|
window.location.assign(url)
|
||||||
|
} catch (e) {
|
||||||
|
setError(e instanceof Error ? e.message : 'Ошибка регистрации')
|
||||||
|
} finally { setBusy(false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={submit} className="space-y-4">
|
||||||
|
{error && (
|
||||||
|
<div className="p-3 rounded-md bg-red-50 text-red-700 text-sm border border-red-200">{error}</div>
|
||||||
|
)}
|
||||||
|
<Field label="Email">
|
||||||
|
<input type="email" required value={email} onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="w-full h-10 rounded-md border border-slate-300 px-3 text-sm focus:outline-none focus:ring-2 focus:ring-brand" autoComplete="email" />
|
||||||
|
</Field>
|
||||||
|
<Field label="Пароль" hint="Минимум 8 символов, цифра и заглавная.">
|
||||||
|
<input type="password" required minLength={8} value={password} onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="w-full h-10 rounded-md border border-slate-300 px-3 text-sm focus:outline-none focus:ring-2 focus:ring-brand" autoComplete="new-password" />
|
||||||
|
</Field>
|
||||||
|
<Field label="Название магазина">
|
||||||
|
<input type="text" required value={orgName} onChange={(e) => setOrgName(e.target.value)} placeholder="ИП Иванов / Магазин у дома"
|
||||||
|
className="w-full h-10 rounded-md border border-slate-300 px-3 text-sm focus:outline-none focus:ring-2 focus:ring-brand" />
|
||||||
|
</Field>
|
||||||
|
<Field label="Телефон (необязательно)">
|
||||||
|
<input type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="+7 ..."
|
||||||
|
className="w-full h-10 rounded-md border border-slate-300 px-3 text-sm focus:outline-none focus:ring-2 focus:ring-brand" />
|
||||||
|
</Field>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-medium mb-2">Тариф</div>
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
{[
|
||||||
|
{ id: 'start', name: 'Старт', price: '5 000 ₸' },
|
||||||
|
{ id: 'business', name: 'Бизнес', price: 'от 10 000 ₸' },
|
||||||
|
{ id: 'network', name: 'Сеть', price: 'по запросу' },
|
||||||
|
].map((p) => (
|
||||||
|
<label key={p.id} className={`p-2.5 rounded-md border cursor-pointer text-center text-sm ${plan === p.id ? 'border-brand bg-brand/5' : 'border-slate-200'}`}>
|
||||||
|
<input type="radio" name="plan" className="hidden" checked={plan === p.id} onChange={() => setPlan(p.id)} />
|
||||||
|
<div className="font-semibold">{p.name}</div>
|
||||||
|
<div className="text-xs text-slate-500">{p.price}</div>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label className="flex items-start gap-2 text-sm">
|
||||||
|
<input type="checkbox" checked={agree} onChange={(e) => setAgree(e.target.checked)} className="mt-1" />
|
||||||
|
<span>Я согласен с <a href="/legal/offer" className="text-brand underline">офертой</a> и <a href="/legal/privacy" className="text-brand underline">политикой обработки ПДн</a>.</span>
|
||||||
|
</label>
|
||||||
|
<button type="submit" disabled={busy} className="w-full px-4 py-3 bg-brand text-white font-semibold rounded-md hover:bg-[#009305] disabled:opacity-60">
|
||||||
|
{busy ? 'Регистрация…' : 'Начать бесплатно (90 дней)'}
|
||||||
|
</button>
|
||||||
|
<p className="text-xs text-center text-slate-500">Уже есть аккаунт? <a href={`${APP_URL}/login`} className="text-brand underline">Войти</a></p>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Field({ label, hint, children }: { label: string; hint?: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<label className="block">
|
||||||
|
<span className="text-sm font-medium block mb-1.5">{label}</span>
|
||||||
|
{children}
|
||||||
|
{hint && <span className="text-xs text-slate-500 block mt-1">{hint}</span>}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
13
src/food-market.public/src/content/config.ts
Normal file
13
src/food-market.public/src/content/config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { defineCollection, z } from 'astro:content'
|
||||||
|
|
||||||
|
const legal = defineCollection({
|
||||||
|
type: 'content',
|
||||||
|
// Astro резервирует поле `slug` — если оно есть в frontmatter, оно
|
||||||
|
// используется как slug страницы, но в схеме его описывать нельзя.
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
last_updated: z.coerce.date(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const collections = { legal }
|
||||||
113
src/food-market.public/src/content/legal/consent.md
Normal file
113
src/food-market.public/src/content/legal/consent.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
---
|
||||||
|
title: Согласие на сбор и обработку персональных данных
|
||||||
|
slug: consent
|
||||||
|
last_updated: 2026-04-26
|
||||||
|
---
|
||||||
|
|
||||||
|
# Согласие на сбор и обработку персональных данных
|
||||||
|
|
||||||
|
> **Внимание:** Настоящий документ является типовой формой согласия и подлежит правовой экспертизе перед публичным использованием.
|
||||||
|
|
||||||
|
**Дата публикации:** 26.04.2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Настоящим я, как субъект персональных данных, регистрируясь в сервисе **Food Market** (далее — «**Сервис**»), размещённом на сайте https://food-market.kz, и проставляя отметку в чекбоксе «Согласен с офертой и политикой обработки персональных данных» при создании учётной записи, в соответствии со статьёй 8 Закона Республики Казахстан от 21 мая 2013 года № 94-V «О персональных данных и их защите», свободно, своей волей и в своём интересе даю своё согласие **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], юридический адрес: [Юр.адрес] (далее — «**Оператор**»), на сбор, обработку, хранение, использование, обезличивание и уничтожение моих персональных данных в нижеуказанном объёме и для нижеуказанных целей.
|
||||||
|
|
||||||
|
## 1. Перечень персональных данных, на обработку которых даётся согласие
|
||||||
|
|
||||||
|
- адрес электронной почты;
|
||||||
|
- фамилия, имя, отчество (при указании);
|
||||||
|
- контактный телефон (при указании);
|
||||||
|
- должность (при указании);
|
||||||
|
- наименование организации (юридического лица или индивидуального предпринимателя);
|
||||||
|
- идентификатор учётной записи в Сервисе;
|
||||||
|
- пароль (хранится в виде криптографического хеша);
|
||||||
|
- IP-адрес устройства, с которого осуществляется доступ к Сервису;
|
||||||
|
- технические данные устройства и браузера: тип браузера, версия, операционная система, разрешение экрана;
|
||||||
|
- данные о действиях в Сервисе (журналы операций) — для целей обеспечения безопасности и аудита;
|
||||||
|
- иные данные, предоставленные мной добровольно при использовании Сервиса.
|
||||||
|
|
||||||
|
## 2. Цели обработки персональных данных
|
||||||
|
|
||||||
|
Согласие даётся на обработку персональных данных в следующих целях:
|
||||||
|
|
||||||
|
2.1. Регистрация и идентификация в Сервисе.
|
||||||
|
|
||||||
|
2.2. Предоставление функциональных возможностей Сервиса в соответствии с выбранным тарифом.
|
||||||
|
|
||||||
|
2.3. Исполнение обязательств Оператора по Публичной оферте на использование Сервиса.
|
||||||
|
|
||||||
|
2.4. Обеспечение технической поддержки и связи с пользователем.
|
||||||
|
|
||||||
|
2.5. Информирование о новых функциях, обновлениях и условиях использования Сервиса (сервисные уведомления).
|
||||||
|
|
||||||
|
2.6. Обеспечение безопасности учётной записи и защита от несанкционированного доступа, мошенничества, злоупотреблений.
|
||||||
|
|
||||||
|
2.7. Расчёт стоимости услуг, выставление счетов, проведение платежей.
|
||||||
|
|
||||||
|
2.8. Подготовка финансовой и налоговой отчётности в соответствии с законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
2.9. Анализ качества работы Сервиса, улучшение пользовательского опыта (на основе обезличенных данных).
|
||||||
|
|
||||||
|
2.10. Соблюдение требований законодательства Республики Казахстан.
|
||||||
|
|
||||||
|
## 3. Действия с персональными данными
|
||||||
|
|
||||||
|
Согласие даётся на совершение следующих действий с персональными данными:
|
||||||
|
|
||||||
|
- сбор;
|
||||||
|
- запись;
|
||||||
|
- систематизация;
|
||||||
|
- накопление;
|
||||||
|
- хранение;
|
||||||
|
- уточнение (обновление, изменение);
|
||||||
|
- извлечение;
|
||||||
|
- использование;
|
||||||
|
- передача (предоставление, доступ) — лицам, указанным в Политике обработки персональных данных Оператора;
|
||||||
|
- обезличивание;
|
||||||
|
- блокирование;
|
||||||
|
- удаление;
|
||||||
|
- уничтожение.
|
||||||
|
|
||||||
|
Обработка персональных данных осуществляется как с использованием средств автоматизации, так и без них.
|
||||||
|
|
||||||
|
## 4. Передача персональных данных третьим лицам
|
||||||
|
|
||||||
|
Я согласен с тем, что для целей предоставления Сервиса Оператор вправе передавать мои персональные данные третьим лицам — поставщикам услуг, обеспечивающим функционирование Сервиса (хостинг, платёжные системы, провайдеры email/SMS-уведомлений, провайдеры аналитики), при условии обеспечения адекватного уровня защиты данных.
|
||||||
|
|
||||||
|
Передача персональных данных в иностранные государства осуществляется только при условии обеспечения адекватного уровня защиты прав субъектов персональных данных в соответствии со статьёй 16 Закона о ПДн.
|
||||||
|
|
||||||
|
## 5. Срок действия согласия
|
||||||
|
|
||||||
|
5.1. Настоящее согласие действует с момента его предоставления и до момента отзыва субъектом персональных данных или до момента прекращения деятельности Оператора.
|
||||||
|
|
||||||
|
5.2. Я уведомлён, что вправе отозвать согласие на обработку персональных данных в любой момент путём направления соответствующего письменного уведомления Оператору по адресу электронной почты [privacy@food-market.kz] или удаления учётной записи в Сервисе.
|
||||||
|
|
||||||
|
5.3. Я уведомлён, что отзыв согласия влечёт за собой прекращение возможности использования Сервиса, поскольку обработка персональных данных является необходимым условием его предоставления.
|
||||||
|
|
||||||
|
5.4. Я уведомлён, что Оператор вправе продолжить обработку персональных данных без согласия субъекта в случаях, прямо предусмотренных Законом о ПДн (статья 9), в том числе для исполнения обязательств, предусмотренных законодательством Республики Казахстан, защиты прав и законных интересов Оператора.
|
||||||
|
|
||||||
|
## 6. Ознакомление с правами
|
||||||
|
|
||||||
|
Я подтверждаю, что ознакомлен с:
|
||||||
|
|
||||||
|
6.1. Политикой обработки персональных данных Оператора, размещённой на сайте https://food-market.kz/legal/privacy.
|
||||||
|
|
||||||
|
6.2. Своими правами, предусмотренными Законом о ПДн, в том числе правом:
|
||||||
|
- получать информацию об обработке моих персональных данных;
|
||||||
|
- требовать уточнения, блокирования или уничтожения моих персональных данных;
|
||||||
|
- отзывать настоящее согласие;
|
||||||
|
- обжаловать действия Оператора в уполномоченный государственный орган или в суд.
|
||||||
|
|
||||||
|
## 7. Подтверждение согласия
|
||||||
|
|
||||||
|
Подтверждение настоящего согласия осуществляется в электронной форме путём проставления отметки в чекбоксе «Согласен с офертой и политикой обработки персональных данных» при регистрации в Сервисе.
|
||||||
|
|
||||||
|
В соответствии с пунктом 4 статьи 7 Закона Республики Казахстан «Об электронном документе и электронной цифровой подписи» подтверждение согласия в электронной форме признаётся юридически равнозначным подтверждению на бумажном носителе.
|
||||||
|
|
||||||
|
Дата и время предоставления согласия фиксируются Оператором автоматически в момент регистрации с указанием IP-адреса субъекта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Настоящая форма согласия составлена в соответствии с законодательством Республики Казахстан и применяется с даты, указанной в шапке документа.*
|
||||||
194
src/food-market.public/src/content/legal/offer.md
Normal file
194
src/food-market.public/src/content/legal/offer.md
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
---
|
||||||
|
title: Публичная оферта на использование сервиса Food Market
|
||||||
|
slug: offer
|
||||||
|
last_updated: 2026-04-26
|
||||||
|
---
|
||||||
|
|
||||||
|
# Публичная оферта на использование сервиса Food Market
|
||||||
|
|
||||||
|
> **Внимание:** Настоящий документ является типовой публичной офертой и подлежит правовой экспертизе перед публичным использованием. Финальная редакция утверждается уполномоченным лицом Лицензиара.
|
||||||
|
|
||||||
|
**Дата публикации:** 26.04.2026
|
||||||
|
**Действует с:** 26.04.2026
|
||||||
|
**Юрисдикция:** Республика Казахстан
|
||||||
|
|
||||||
|
## 1. Общие положения
|
||||||
|
|
||||||
|
1.1. Настоящий документ является публичной офертой (далее — «**Оферта**») в соответствии со статьями 395, 396 Гражданского кодекса Республики Казахстан (далее — «**ГК РК**»). Оферта адресована любому физическому или юридическому лицу (далее — «**Пользователь**»), желающему получить доступ к программно-аппаратному комплексу «Food Market» (далее — «**Сервис**»).
|
||||||
|
|
||||||
|
1.2. Лицензиаром Сервиса выступает **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН], зарегистрированное в соответствии с законодательством Республики Казахстан (далее — «**Лицензиар**»). Полные реквизиты Лицензиара указаны в разделе «Реквизиты» сайта.
|
||||||
|
|
||||||
|
1.3. Безусловным акцептом настоящей Оферты в соответствии с пунктом 3 статьи 396 ГК РК являются следующие действия Пользователя, совершённые в совокупности:
|
||||||
|
- регистрация учётной записи на сайте https://food-market.kz (или ином доменном имени, используемом Лицензиаром);
|
||||||
|
- проставление отметки в чекбоксе «Согласен с офертой и политикой обработки персональных данных»;
|
||||||
|
- начало использования Сервиса.
|
||||||
|
|
||||||
|
1.4. С момента акцепта между Лицензиаром и Пользователем заключается лицензионный договор присоединения на условиях, изложенных в настоящей Оферте (далее — «**Договор**»).
|
||||||
|
|
||||||
|
1.5. Лицензиар вправе в одностороннем порядке вносить изменения в Оферту с публикацией новой редакции на сайте не менее чем за 14 (четырнадцать) календарных дней до вступления изменений в силу. Продолжение использования Сервиса после вступления изменений в силу означает их акцепт Пользователем.
|
||||||
|
|
||||||
|
## 2. Термины и определения
|
||||||
|
|
||||||
|
**Сервис** — программный продукт «Food Market», представляющий собой облачную систему учёта розничной торговли с функциями управления товарами, складом, продажами, кассовыми операциями, аналитикой и сопутствующими сервисами, размещённый по адресу https://food-market.kz и связанных с ним поддоменах.
|
||||||
|
|
||||||
|
**Учётная запись** — совокупность данных Пользователя, обеспечивающих его идентификацию и авторизацию в Сервисе.
|
||||||
|
|
||||||
|
**Организация** — обособленное рабочее пространство Пользователя в Сервисе, в рамках которого ведётся учёт деятельности одного или нескольких розничных магазинов.
|
||||||
|
|
||||||
|
**Тариф** — план обслуживания, определяющий объём предоставляемых функций, лимиты использования и стоимость.
|
||||||
|
|
||||||
|
**Подписка** — оплаченный период использования Сервиса в рамках выбранного Тарифа.
|
||||||
|
|
||||||
|
**Триал** — пробный бесплатный период использования Сервиса продолжительностью 90 (девяносто) календарных дней, предоставляемый Пользователю при первичной регистрации без обязательства оплаты.
|
||||||
|
|
||||||
|
**Контент Пользователя** — любые данные, загруженные Пользователем в Сервис: справочники товаров, контрагентов, операции, документы и иная информация.
|
||||||
|
|
||||||
|
## 3. Предмет договора
|
||||||
|
|
||||||
|
3.1. Лицензиар предоставляет Пользователю на условиях простой (неисключительной) лицензии право использования Сервиса в объёме функциональности, предусмотренной выбранным Тарифом, в течение оплаченного срока Подписки или периода Триала.
|
||||||
|
|
||||||
|
3.2. Сервис предоставляется по модели SaaS (Software as a Service): Пользователь получает доступ к Сервису посредством сети Интернет; экземпляр программного обеспечения не передаётся Пользователю.
|
||||||
|
|
||||||
|
3.3. Право использования Сервиса предоставляется на территории Республики Казахстан и иных государств, в которых Сервис технически доступен и не запрещён к использованию законодательством соответствующего государства.
|
||||||
|
|
||||||
|
## 4. Регистрация и учётная запись
|
||||||
|
|
||||||
|
4.1. Для использования Сервиса Пользователь обязан создать Учётную запись, предоставив достоверные данные: адрес электронной почты, наименование Организации, контактный телефон (при необходимости), а также установить пароль.
|
||||||
|
|
||||||
|
4.2. Пользователь обязуется:
|
||||||
|
- использовать только достоверные данные при регистрации;
|
||||||
|
- не передавать данные Учётной записи третьим лицам;
|
||||||
|
- незамедлительно уведомлять Лицензиара о любом несанкционированном доступе к Учётной записи;
|
||||||
|
- актуализировать данные Учётной записи при их изменении.
|
||||||
|
|
||||||
|
4.3. Лицензиар не несёт ответственности за действия третьих лиц, получивших доступ к Учётной записи Пользователя по причине нарушения Пользователем обязанностей, установленных пунктом 4.2 настоящей Оферты.
|
||||||
|
|
||||||
|
## 5. Триал
|
||||||
|
|
||||||
|
5.1. При первичной регистрации Учётной записи Пользователю автоматически предоставляется бесплатный пробный период использования Сервиса продолжительностью 90 (девяносто) календарных дней.
|
||||||
|
|
||||||
|
5.2. В течение Триала Пользователь имеет доступ ко всем функциям выбранного Тарифа без ограничений по объёму, за исключением функций, явно отмеченных как недоступные в Триале.
|
||||||
|
|
||||||
|
5.3. По истечении Триала доступ к функциям Сервиса ограничивается до момента активации платной Подписки. Контент Пользователя сохраняется в течение 30 (тридцати) календарных дней после окончания Триала; по истечении указанного срока Лицензиар вправе удалить Контент Пользователя без дополнительного уведомления.
|
||||||
|
|
||||||
|
5.4. Триал предоставляется без обязательства Пользователя по оплате; реквизиты платёжной карты при активации Триала не требуются.
|
||||||
|
|
||||||
|
## 6. Тарифы и порядок оплаты
|
||||||
|
|
||||||
|
6.1. Стоимость использования Сервиса определяется выбранным Пользователем Тарифом. Актуальная сетка Тарифов и их состав публикуются на сайте Лицензиара по адресу https://food-market.kz/pricing.
|
||||||
|
|
||||||
|
6.2. Оплата Подписки производится авансом за выбранный период (1, 3, 6 или 12 месяцев) посредством платёжных систем, указанных на сайте Лицензиара.
|
||||||
|
|
||||||
|
6.3. Все платежи осуществляются в национальной валюте Республики Казахстан — тенге (KZT). Стоимость указана с учётом всех применимых налогов в соответствии с законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
6.4. Лицензиар вправе изменять Тарифы с уведомлением Пользователя не менее чем за 30 (тридцать) календарных дней до вступления изменений в силу. Изменения не распространяются на ранее оплаченные Подписки до окончания их срока действия.
|
||||||
|
|
||||||
|
6.5. Возврат денежных средств за неиспользованный период Подписки осуществляется в случаях, предусмотренных законодательством Республики Казахстан, по письменному заявлению Пользователя на основании пункта 1 статьи 401 ГК РК.
|
||||||
|
|
||||||
|
6.6. В случае непоступления оплаты в течение 7 (семи) календарных дней с даты окончания текущего оплаченного периода Подписки доступ к Сервису ограничивается. Контент Пользователя сохраняется в течение срока, установленного системными настройками Лицензиара (но не менее 30 календарных дней), после чего может быть удалён без дополнительного уведомления.
|
||||||
|
|
||||||
|
## 7. Права и обязанности сторон
|
||||||
|
|
||||||
|
### 7.1. Лицензиар обязуется:
|
||||||
|
|
||||||
|
7.1.1. Обеспечивать круглосуточный доступ Пользователя к Сервису, за исключением периодов проведения регламентных и аварийных работ.
|
||||||
|
|
||||||
|
7.1.2. Предпринимать разумные технические и организационные меры для обеспечения сохранности Контента Пользователя и защиты от несанкционированного доступа.
|
||||||
|
|
||||||
|
7.1.3. Оказывать техническую поддержку Пользователя по каналам, указанным на сайте Лицензиара, в режиме и в сроки, соответствующие выбранному Тарифу.
|
||||||
|
|
||||||
|
7.1.4. Обрабатывать персональные данные Пользователя в соответствии с Политикой обработки персональных данных, опубликованной на сайте Лицензиара.
|
||||||
|
|
||||||
|
### 7.2. Лицензиар вправе:
|
||||||
|
|
||||||
|
7.2.1. Проводить регламентные технические работы с предварительным уведомлением Пользователя не менее чем за 24 часа.
|
||||||
|
|
||||||
|
7.2.2. Ограничивать или приостанавливать доступ Пользователя к Сервису в случае нарушения Пользователем условий настоящей Оферты, а также при наличии обоснованных подозрений в нарушении.
|
||||||
|
|
||||||
|
7.2.3. Привлекать третьих лиц для исполнения отдельных обязательств по настоящей Оферте, неся ответственность за их действия как за свои собственные.
|
||||||
|
|
||||||
|
### 7.3. Пользователь обязуется:
|
||||||
|
|
||||||
|
7.3.1. Использовать Сервис в соответствии с условиями настоящей Оферты и законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
7.3.2. Своевременно оплачивать выбранный Тариф.
|
||||||
|
|
||||||
|
7.3.3. Не использовать Сервис для:
|
||||||
|
- осуществления противоправной деятельности;
|
||||||
|
- хранения или распространения информации, нарушающей права третьих лиц или законодательство;
|
||||||
|
- попыток несанкционированного доступа к Сервису или его компонентам;
|
||||||
|
- автоматизированного сбора данных, нагружающего инфраструктуру Лицензиара;
|
||||||
|
- модификации, декомпиляции, обратной инженерии Сервиса.
|
||||||
|
|
||||||
|
7.3.4. Самостоятельно нести ответственность за достоверность, законность и точность Контента Пользователя.
|
||||||
|
|
||||||
|
### 7.4. Пользователь вправе:
|
||||||
|
|
||||||
|
7.4.1. В любой момент прекратить использование Сервиса с предварительной выгрузкой собственного Контента средствами, предоставляемыми Сервисом.
|
||||||
|
|
||||||
|
7.4.2. Запрашивать у Лицензиара информацию о хранимых персональных данных, требовать их исправления или удаления в соответствии с действующим законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
## 8. Интеллектуальная собственность
|
||||||
|
|
||||||
|
8.1. Все исключительные права на Сервис, его компоненты, дизайн, программный код, документацию, торговые знаки, используемые в Сервисе, принадлежат Лицензиару или его правообладателям-партнёрам.
|
||||||
|
|
||||||
|
8.2. Пользователь не приобретает каких-либо прав на Сервис, кроме права использования в объёме, предусмотренном настоящей Офертой.
|
||||||
|
|
||||||
|
8.3. Контент Пользователя является собственностью Пользователя. Лицензиар обрабатывает Контент Пользователя исключительно для целей предоставления Сервиса и не претендует на права интеллектуальной собственности на Контент Пользователя.
|
||||||
|
|
||||||
|
## 9. Ответственность сторон
|
||||||
|
|
||||||
|
9.1. Лицензиар не гарантирует абсолютной бесперебойной работы Сервиса. Допустимый уровень доступности Сервиса соответствует выбранному Тарифу и публикуется на сайте Лицензиара.
|
||||||
|
|
||||||
|
9.2. Лицензиар не несёт ответственности за:
|
||||||
|
- убытки Пользователя, возникшие вследствие неправомерных действий третьих лиц, получивших доступ к Учётной записи Пользователя;
|
||||||
|
- упущенную выгоду Пользователя;
|
||||||
|
- содержание и достоверность Контента Пользователя;
|
||||||
|
- утрату данных, не подкреплённых регулярным резервным копированием со стороны Пользователя (при наличии такой возможности в рамках Тарифа).
|
||||||
|
|
||||||
|
9.3. Совокупная ответственность Лицензиара перед Пользователем по настоящему Договору ограничена суммой, фактически уплаченной Пользователем за последний календарный месяц использования Сервиса.
|
||||||
|
|
||||||
|
9.4. Стороны освобождаются от ответственности за неисполнение обязательств вследствие обстоятельств непреодолимой силы (форс-мажор) в соответствии со статьёй 359 ГК РК.
|
||||||
|
|
||||||
|
## 10. Конфиденциальность и защита данных
|
||||||
|
|
||||||
|
10.1. Лицензиар обязуется обеспечивать конфиденциальность Контента Пользователя и не раскрывать его третьим лицам, кроме случаев, предусмотренных законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
10.2. Обработка персональных данных Пользователя осуществляется в соответствии с Законом Республики Казахстан от 21 мая 2013 года № 94-V «О персональных данных и их защите» и Политикой обработки персональных данных Лицензиара.
|
||||||
|
|
||||||
|
10.3. Лицензиар применяет шифрование данных при передаче (TLS), резервное копирование, контроль доступа и иные меры защиты, соответствующие отраслевым стандартам.
|
||||||
|
|
||||||
|
## 11. Срок действия и прекращение договора
|
||||||
|
|
||||||
|
11.1. Договор вступает в силу с момента акцепта Оферты Пользователем и действует в течение всего периода использования Сервиса.
|
||||||
|
|
||||||
|
11.2. Договор может быть прекращён:
|
||||||
|
- по инициативе Пользователя — путём удаления Учётной записи в Сервисе или направления соответствующего уведомления Лицензиару;
|
||||||
|
- по инициативе Лицензиара — в случае нарушения Пользователем условий Оферты с уведомлением не менее чем за 7 календарных дней;
|
||||||
|
- по соглашению Сторон;
|
||||||
|
- в иных случаях, предусмотренных законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
11.3. После прекращения Договора Контент Пользователя сохраняется в течение 30 календарных дней для возможной выгрузки, после чего удаляется без возможности восстановления.
|
||||||
|
|
||||||
|
## 12. Применимое право и порядок разрешения споров
|
||||||
|
|
||||||
|
12.1. К отношениям сторон по настоящему Договору применяется материальное право Республики Казахстан.
|
||||||
|
|
||||||
|
12.2. Все споры и разногласия, возникающие из настоящего Договора или в связи с ним, подлежат разрешению путём переговоров.
|
||||||
|
|
||||||
|
12.3. В случае невозможности разрешения спора путём переговоров в течение 30 (тридцати) календарных дней с момента предъявления претензии спор подлежит передаче на рассмотрение в специализированный межрайонный экономический суд по месту нахождения Лицензиара.
|
||||||
|
|
||||||
|
## 13. Заключительные положения
|
||||||
|
|
||||||
|
13.1. Если какое-либо положение настоящей Оферты будет признано недействительным судом, это не повлияет на действительность остальных положений.
|
||||||
|
|
||||||
|
13.2. Уведомления, направляемые Пользователю в рамках исполнения Договора, считаются доставленными:
|
||||||
|
- при отправке на адрес электронной почты, указанный в Учётной записи, — с момента отправки;
|
||||||
|
- при публикации в личном кабинете Пользователя — с момента публикации;
|
||||||
|
- при опубликовании на сайте Лицензиара — с момента опубликования.
|
||||||
|
|
||||||
|
13.3. Реквизиты Лицензиара публикуются на сайте Лицензиара в разделе «Реквизиты» и являются неотъемлемой частью настоящей Оферты.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Настоящая Оферта составлена в соответствии с законодательством Республики Казахстан и применяется с даты, указанной в шапке документа.*
|
||||||
230
src/food-market.public/src/content/legal/privacy.md
Normal file
230
src/food-market.public/src/content/legal/privacy.md
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
---
|
||||||
|
title: Политика обработки персональных данных
|
||||||
|
slug: privacy
|
||||||
|
last_updated: 2026-04-26
|
||||||
|
---
|
||||||
|
|
||||||
|
# Политика обработки персональных данных
|
||||||
|
|
||||||
|
> **Внимание:** Настоящий документ является типовой политикой и подлежит правовой экспертизе перед публичным использованием. Финальная редакция утверждается уполномоченным лицом Лицензиара.
|
||||||
|
|
||||||
|
**Дата публикации:** 26.04.2026
|
||||||
|
**Действует с:** 26.04.2026
|
||||||
|
|
||||||
|
## 1. Общие положения
|
||||||
|
|
||||||
|
1.1. Настоящая Политика обработки персональных данных (далее — «**Политика**») определяет порядок обработки и защиты персональных данных, осуществляемой **ТОО «[НАЗВАНИЕ ТОО]»**, БИН [БИН] (далее — «**Оператор**»), при предоставлении доступа к программно-аппаратному комплексу «Food Market» (далее — «**Сервис**»).
|
||||||
|
|
||||||
|
1.2. Политика разработана в соответствии с:
|
||||||
|
- Конституцией Республики Казахстан;
|
||||||
|
- Законом Республики Казахстан от 21 мая 2013 года № 94-V «О персональных данных и их защите» (далее — «**Закон о ПДн**»);
|
||||||
|
- Гражданским кодексом Республики Казахстан;
|
||||||
|
- Законом Республики Казахстан «Об электронном документе и электронной цифровой подписи»;
|
||||||
|
- иными нормативными правовыми актами Республики Казахстан.
|
||||||
|
|
||||||
|
1.3. Настоящая Политика применяется ко всем персональным данным, обрабатываемым Оператором с использованием средств автоматизации и без таковых.
|
||||||
|
|
||||||
|
1.4. Действующая редакция Политики постоянно доступна на сайте Оператора по адресу https://food-market.kz/legal/privacy.
|
||||||
|
|
||||||
|
## 2. Термины и определения
|
||||||
|
|
||||||
|
**Персональные данные (ПДн)** — сведения, относящиеся к определённому или определяемому на их основании субъекту персональных данных, зафиксированные на электронном, бумажном и (или) ином материальном носителе.
|
||||||
|
|
||||||
|
**Субъект персональных данных** — физическое лицо, к которому относятся персональные данные.
|
||||||
|
|
||||||
|
**Оператор** — ТОО «[НАЗВАНИЕ ТОО]», осуществляющее сбор, обработку и защиту персональных данных.
|
||||||
|
|
||||||
|
**Обработка персональных данных** — действия, направленные на сбор, накопление, хранение, изменение, использование, распространение, обезличивание, блокирование и уничтожение персональных данных.
|
||||||
|
|
||||||
|
**Согласие субъекта** — свободное и осознанное волеизъявление субъекта персональных данных на обработку его персональных данных.
|
||||||
|
|
||||||
|
## 3. Принципы обработки персональных данных
|
||||||
|
|
||||||
|
Оператор осуществляет обработку персональных данных на основании следующих принципов:
|
||||||
|
|
||||||
|
3.1. Соблюдение конституционных прав и свобод человека и гражданина при обработке его ПДн.
|
||||||
|
|
||||||
|
3.2. Законность, целесообразность, открытость и прозрачность процесса обработки ПДн.
|
||||||
|
|
||||||
|
3.3. Обработка ПДн только для конкретных, заранее определённых и законных целей.
|
||||||
|
|
||||||
|
3.4. Соответствие содержания и объёма обрабатываемых ПДн заявленным целям обработки.
|
||||||
|
|
||||||
|
3.5. Обеспечение точности, достаточности, актуальности и достоверности ПДн.
|
||||||
|
|
||||||
|
3.6. Хранение ПДн не дольше, чем этого требуют цели обработки.
|
||||||
|
|
||||||
|
3.7. Обеспечение конфиденциальности и защиты ПДн.
|
||||||
|
|
||||||
|
## 4. Категории субъектов и состав обрабатываемых данных
|
||||||
|
|
||||||
|
### 4.1. Пользователи Сервиса
|
||||||
|
|
||||||
|
**Цель обработки:** регистрация и идентификация в Сервисе, предоставление функций Сервиса, выполнение договорных обязательств, оказание технической поддержки.
|
||||||
|
|
||||||
|
**Состав данных:**
|
||||||
|
- адрес электронной почты;
|
||||||
|
- имя, фамилия, отчество (при указании);
|
||||||
|
- контактный телефон (при указании);
|
||||||
|
- должность в организации (при указании);
|
||||||
|
- наименование организации (юридического лица или индивидуального предпринимателя);
|
||||||
|
- идентификатор учётной записи (ID), пароль (в зашифрованном виде);
|
||||||
|
- IP-адреса, данные о браузере, операционной системе, устройстве, дата и время использования Сервиса;
|
||||||
|
- информация об активности в Сервисе (логи действий) — для целей безопасности и аудита.
|
||||||
|
|
||||||
|
**Правовое основание:** согласие субъекта (статья 8 Закона о ПДн), исполнение договора, стороной которого является субъект (статья 9 Закона о ПДн).
|
||||||
|
|
||||||
|
### 4.2. Сотрудники Пользователей Сервиса
|
||||||
|
|
||||||
|
**Цель обработки:** обеспечение функционирования учётных записей сотрудников в рамках Организации Пользователя.
|
||||||
|
|
||||||
|
**Состав данных:**
|
||||||
|
- адрес электронной почты;
|
||||||
|
- имя, фамилия, отчество;
|
||||||
|
- должность;
|
||||||
|
- роль в Организации;
|
||||||
|
- идентификатор учётной записи, пароль (в зашифрованном виде);
|
||||||
|
- логи действий в Сервисе — для целей аудита и безопасности.
|
||||||
|
|
||||||
|
**Правовое основание:** согласие субъекта (через согласие, оформляемое работодателем — Пользователем Сервиса, выступающим Оператором по отношению к собственным сотрудникам).
|
||||||
|
|
||||||
|
### 4.3. Контрагенты, клиенты Пользователей Сервиса
|
||||||
|
|
||||||
|
При использовании функционала ведения базы клиентов и контрагентов Пользователь самостоятельно загружает в Сервис персональные данные третьих лиц.
|
||||||
|
|
||||||
|
В этом случае Пользователь Сервиса является Оператором персональных данных в отношении таких лиц и несёт самостоятельную ответственность за получение их согласия и соблюдение Закона о ПДн. Оператор Сервиса (ТОО) выступает в роли Оператора Обработчика, действующего по поручению Пользователя.
|
||||||
|
|
||||||
|
### 4.4. Лица, обращающиеся к Оператору через формы обратной связи
|
||||||
|
|
||||||
|
**Цель обработки:** ответ на обращение, обработка заявок на консультацию, маркетинговые рассылки (при наличии отдельного согласия).
|
||||||
|
|
||||||
|
**Состав данных:**
|
||||||
|
- имя;
|
||||||
|
- адрес электронной почты;
|
||||||
|
- телефон;
|
||||||
|
- содержание сообщения.
|
||||||
|
|
||||||
|
**Правовое основание:** согласие субъекта.
|
||||||
|
|
||||||
|
## 5. Источники персональных данных
|
||||||
|
|
||||||
|
5.1. Персональные данные предоставляются субъектами ПДн непосредственно Оператору при:
|
||||||
|
- регистрации в Сервисе;
|
||||||
|
- использовании функционала Сервиса;
|
||||||
|
- заполнении форм обратной связи на сайте;
|
||||||
|
- направлении обращений по электронной почте, телефону, в мессенджерах.
|
||||||
|
|
||||||
|
5.2. Оператор не получает персональные данные субъектов из иных источников, кроме случаев, прямо предусмотренных законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
## 6. Сроки хранения персональных данных
|
||||||
|
|
||||||
|
6.1. Персональные данные обрабатываются Оператором в течение срока, необходимого для достижения целей обработки, а также в течение сроков, установленных законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
6.2. Персональные данные Пользователей хранятся в течение всего срока действия учётной записи, а после прекращения договора (отказа от использования Сервиса) — в течение 30 (тридцати) календарных дней для возможной выгрузки и восстановления, после чего уничтожаются.
|
||||||
|
|
||||||
|
6.3. Логи действий и иные служебные данные, необходимые для обеспечения безопасности, хранятся в течение 1 (одного) года.
|
||||||
|
|
||||||
|
6.4. Финансовая и бухгалтерская документация, содержащая ПДн, хранится в течение сроков, установленных Налоговым кодексом Республики Казахстан и законодательством о бухгалтерском учёте.
|
||||||
|
|
||||||
|
## 7. Передача персональных данных третьим лицам
|
||||||
|
|
||||||
|
7.1. Оператор не продаёт, не сдаёт в аренду и иным образом не передаёт персональные данные третьим лицам в коммерческих целях.
|
||||||
|
|
||||||
|
7.2. Оператор передаёт персональные данные третьим лицам только в следующих случаях:
|
||||||
|
|
||||||
|
**7.2.1. Поставщикам услуг,** обеспечивающим функционирование Сервиса:
|
||||||
|
- хостинг-провайдеры (с заключением соглашения о защите данных);
|
||||||
|
- провайдеры платёжных систем (для обработки платежей);
|
||||||
|
- провайдеры электронной почты и SMS-уведомлений;
|
||||||
|
- провайдеры аналитики и мониторинга качества Сервиса.
|
||||||
|
|
||||||
|
При передаче данных подрядчикам Оператор требует от них соблюдения уровня защиты, не ниже установленного законодательством Республики Казахстан.
|
||||||
|
|
||||||
|
**7.2.2. По требованию государственных органов** Республики Казахстан в случаях и в порядке, предусмотренных законом.
|
||||||
|
|
||||||
|
**7.2.3. С согласия субъекта персональных данных.**
|
||||||
|
|
||||||
|
7.3. Трансграничная передача персональных данных в иностранные государства осуществляется при условии обеспечения адекватного уровня защиты прав субъектов ПДн в соответствии со статьёй 16 Закона о ПДн.
|
||||||
|
|
||||||
|
## 8. Меры по защите персональных данных
|
||||||
|
|
||||||
|
Оператор реализует следующие технические и организационные меры защиты ПДн:
|
||||||
|
|
||||||
|
### 8.1. Технические меры
|
||||||
|
|
||||||
|
- шифрование передаваемых данных по протоколу TLS 1.2 и выше;
|
||||||
|
- хеширование паролей с использованием стойких криптографических алгоритмов;
|
||||||
|
- разграничение доступа к информационным системам;
|
||||||
|
- регулярное резервное копирование данных;
|
||||||
|
- мониторинг и аудит действий пользователей;
|
||||||
|
- защита от несанкционированного доступа, вредоносного программного обеспечения, DDoS-атак;
|
||||||
|
- использование межсетевых экранов и систем обнаружения вторжений;
|
||||||
|
- регулярное обновление программного обеспечения и устранение уязвимостей.
|
||||||
|
|
||||||
|
### 8.2. Организационные меры
|
||||||
|
|
||||||
|
- назначение лица, ответственного за организацию обработки персональных данных;
|
||||||
|
- утверждение внутренних документов, регулирующих процессы обработки и защиты ПДн;
|
||||||
|
- обучение сотрудников Оператора правилам работы с ПДн;
|
||||||
|
- ограничение круга лиц, имеющих доступ к ПДн, минимально необходимым;
|
||||||
|
- заключение соглашений о неразглашении с сотрудниками и подрядчиками;
|
||||||
|
- регулярные аудиты состояния защиты ПДн.
|
||||||
|
|
||||||
|
## 9. Права субъекта персональных данных
|
||||||
|
|
||||||
|
В соответствии с Законом о ПДн субъект персональных данных имеет право:
|
||||||
|
|
||||||
|
9.1. Получать информацию, касающуюся обработки его ПДн (статья 24 Закона о ПДн), включая:
|
||||||
|
- цели и сроки обработки;
|
||||||
|
- источник получения данных;
|
||||||
|
- перечень обрабатываемых данных;
|
||||||
|
- сведения о третьих лицах, которым передаются данные.
|
||||||
|
|
||||||
|
9.2. Требовать от Оператора уточнения, блокирования или уничтожения ПДн, если они являются неполными, устаревшими, недостоверными, незаконно полученными или не являются необходимыми для заявленных целей обработки (статья 25 Закона о ПДн).
|
||||||
|
|
||||||
|
9.3. Отзывать согласие на обработку ПДн (статья 26 Закона о ПДн). Отзыв согласия влечёт прекращение использования Сервиса в отношении субъекта.
|
||||||
|
|
||||||
|
9.4. Обжаловать действия (бездействие) Оператора в уполномоченный орган по защите ПДн или в суд.
|
||||||
|
|
||||||
|
9.5. Требовать защиты своих прав и законных интересов, в том числе возмещения убытков и (или) компенсации морального вреда в судебном порядке.
|
||||||
|
|
||||||
|
## 10. Порядок реализации прав субъекта
|
||||||
|
|
||||||
|
10.1. Для реализации прав, предусмотренных разделом 9 настоящей Политики, субъект ПДн направляет Оператору письменное обращение по адресу электронной почты **[email-адрес для запросов по ПДн]** или почтовому адресу, указанному в реквизитах Оператора.
|
||||||
|
|
||||||
|
10.2. Обращение должно содержать:
|
||||||
|
- фамилию, имя, отчество субъекта;
|
||||||
|
- сведения, позволяющие идентифицировать субъекта в системе Оператора (адрес электронной почты, ID учётной записи);
|
||||||
|
- суть требования;
|
||||||
|
- подпись субъекта (для письменного обращения).
|
||||||
|
|
||||||
|
10.3. Оператор рассматривает обращение и направляет ответ в срок не позднее 15 (пятнадцати) календарных дней с даты поступления обращения.
|
||||||
|
|
||||||
|
10.4. В случае отказа в удовлетворении обращения Оператор направляет субъекту мотивированный ответ с указанием причин отказа.
|
||||||
|
|
||||||
|
## 11. Cookies и аналогичные технологии
|
||||||
|
|
||||||
|
11.1. Сайт и Сервис Оператора используют файлы cookies и аналогичные технологии для:
|
||||||
|
- обеспечения функционирования Сервиса (необходимые cookies — авторизация, сохранение пользовательских настроек);
|
||||||
|
- анализа использования Сервиса (аналитические cookies);
|
||||||
|
- персонализации контента и маркетинга (с согласия субъекта).
|
||||||
|
|
||||||
|
11.2. Пользователь может управлять настройками cookies через интерфейс своего браузера. Отключение необходимых cookies может привести к неполной функциональности Сервиса.
|
||||||
|
|
||||||
|
## 12. Изменения в Политике
|
||||||
|
|
||||||
|
12.1. Оператор вправе вносить изменения в настоящую Политику. Актуальная версия размещается на сайте Оператора по адресу https://food-market.kz/legal/privacy с указанием даты последнего обновления.
|
||||||
|
|
||||||
|
12.2. Существенные изменения, затрагивающие права субъектов ПДн, вступают в силу не ранее чем через 14 (четырнадцать) календарных дней после публикации новой редакции, с уведомлением Пользователей по адресу электронной почты, указанному в учётной записи.
|
||||||
|
|
||||||
|
## 13. Контактная информация
|
||||||
|
|
||||||
|
**Оператор:** ТОО «[НАЗВАНИЕ ТОО]»
|
||||||
|
**БИН:** [БИН]
|
||||||
|
**Юридический адрес:** [Юр.адрес]
|
||||||
|
**Адрес электронной почты для вопросов по ПДн:** [privacy@food-market.kz]
|
||||||
|
**Лицо, ответственное за организацию обработки ПДн:** [ФИО, должность]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Настоящая Политика составлена в соответствии с законодательством Республики Казахстан и применяется с даты, указанной в шапке документа.*
|
||||||
60
src/food-market.public/src/content/legal/requisites.md
Normal file
60
src/food-market.public/src/content/legal/requisites.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
---
|
||||||
|
title: Реквизиты компании
|
||||||
|
slug: requisites
|
||||||
|
last_updated: 2026-04-26
|
||||||
|
---
|
||||||
|
|
||||||
|
# Реквизиты компании
|
||||||
|
|
||||||
|
> **Внимание:** Настоящий раздел содержит placeholder-значения. Перед публикацией сайта в продакшене значения должны быть заполнены реальными реквизитами ТОО.
|
||||||
|
|
||||||
|
## Юридическое лицо
|
||||||
|
|
||||||
|
**Полное наименование:** Товарищество с ограниченной ответственностью «[НАЗВАНИЕ ТОО]»
|
||||||
|
**Сокращённое наименование:** ТОО «[НАЗВАНИЕ ТОО]»
|
||||||
|
|
||||||
|
## Регистрационные данные
|
||||||
|
|
||||||
|
**БИН:** [12-значный БИН]
|
||||||
|
**Дата государственной регистрации:** [ДД.ММ.ГГГГ]
|
||||||
|
**Орган регистрации:** [Министерство юстиции РК / иное]
|
||||||
|
|
||||||
|
## Адрес
|
||||||
|
|
||||||
|
**Юридический адрес:** [Индекс], Республика Казахстан, [Город], [улица, дом, офис]
|
||||||
|
**Фактический адрес:** [то же или иной]
|
||||||
|
**Почтовый адрес:** [то же или иной]
|
||||||
|
|
||||||
|
## Банковские реквизиты
|
||||||
|
|
||||||
|
**Банк-получатель:** [Название банка]
|
||||||
|
**БИК:** [BIK]
|
||||||
|
**ИИК (IBAN):** [KZxx xxxx xxxx xxxx xxxx]
|
||||||
|
**Кбе:** [Код бенефициара, обычно 17 для ТОО]
|
||||||
|
**Валюта счёта:** KZT (тенге)
|
||||||
|
|
||||||
|
## Налоговая информация
|
||||||
|
|
||||||
|
**Режим налогообложения:** [Общеустановленный / Упрощённый / иной]
|
||||||
|
**Плательщик НДС:** [Да / Нет]
|
||||||
|
**Свидетельство о постановке на учёт по НДС:** [серия, номер, дата] *(если применимо)*
|
||||||
|
|
||||||
|
## Руководство
|
||||||
|
|
||||||
|
**Директор:** [ФИО]
|
||||||
|
**Действует на основании:** Устава
|
||||||
|
|
||||||
|
## Контактная информация
|
||||||
|
|
||||||
|
**Email общий:** [info@food-market.kz]
|
||||||
|
**Email по вопросам персональных данных:** [privacy@food-market.kz]
|
||||||
|
**Email технической поддержки:** [support@food-market.kz]
|
||||||
|
**Телефон:** [+7 (___) ___-__-__]
|
||||||
|
|
||||||
|
## Часы работы поддержки
|
||||||
|
|
||||||
|
**Поддержка пользователей:** ежедневно с [09:00] до [18:00] по времени Астаны (UTC+5)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Реквизиты могут уточняться. Актуальная редакция всегда размещена по адресу https://food-market.kz/legal/requisites.*
|
||||||
1
src/food-market.public/src/env.d.ts
vendored
Normal file
1
src/food-market.public/src/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
53
src/food-market.public/src/layouts/BaseLayout.astro
Normal file
53
src/food-market.public/src/layouts/BaseLayout.astro
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
import '@/styles/global.css'
|
||||||
|
import Header from '@/components/Header.astro'
|
||||||
|
import Footer from '@/components/Footer.astro'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
ogImage?: string
|
||||||
|
}
|
||||||
|
const { title, description = 'Программа учёта и касса для розничных магазинов в Казахстане. Бесплатно 90 дней.', ogImage = '/og.png' } = Astro.props
|
||||||
|
const canonical = new URL(Astro.url.pathname, Astro.site).toString()
|
||||||
|
---
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="ru-KZ">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<title>{title} · Food Market</title>
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
<link rel="canonical" href={canonical} />
|
||||||
|
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:title" content={`${title} · Food Market`} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:url" content={canonical} />
|
||||||
|
<meta property="og:image" content={new URL(ogImage, Astro.site).toString()} />
|
||||||
|
<meta property="og:locale" content="ru_KZ" />
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content={`${title} · Food Market`} />
|
||||||
|
<meta name="twitter:description" content={description} />
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" />
|
||||||
|
|
||||||
|
<script type="application/ld+json" set:html={JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'SoftwareApplication',
|
||||||
|
name: 'Food Market',
|
||||||
|
applicationCategory: 'BusinessApplication',
|
||||||
|
operatingSystem: 'Web, Windows',
|
||||||
|
offers: { '@type': 'Offer', price: '5000', priceCurrency: 'KZT' },
|
||||||
|
})} />
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen flex flex-col">
|
||||||
|
<Header />
|
||||||
|
<main class="flex-1"><slot /></main>
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
26
src/food-market.public/src/pages/about.astro
Normal file
26
src/food-market.public/src/pages/about.astro
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
---
|
||||||
|
<BaseLayout title="О нас" description="Food Market — программа учёта и касса для розничных магазинов Казахстана.">
|
||||||
|
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h1 class="text-4xl font-extrabold">О Food Market</h1>
|
||||||
|
<p class="mt-6 text-lg text-slate-700">
|
||||||
|
Food Market — облачная программа учёта и кассовая программа для розничных магазинов Казахстана.
|
||||||
|
</p>
|
||||||
|
<p class="mt-4 text-slate-600">
|
||||||
|
Мы делаем продукт для тех, кому надоели тяжёлые российские системы с доплатами за каждую функцию.
|
||||||
|
У нас один тариф — все возможности. Касса с весами из коробки, импорт из МойСклад одной кнопкой,
|
||||||
|
KZ-локализация на всех уровнях (тенге, ОКЕИ, ОФД РК).
|
||||||
|
</p>
|
||||||
|
<h2 class="text-2xl font-bold mt-12 mb-4">Чем мы отличаемся</h2>
|
||||||
|
<ul class="space-y-3 text-slate-700">
|
||||||
|
<li>✓ <strong>Сделано в Казахстане</strong> — для казахстанских магазинов, с учётом местных требований и интеграций.</li>
|
||||||
|
<li>✓ <strong>Без скрытых доплат</strong> — CRM, лояльность, финансы, поддержка включены во все тарифы.</li>
|
||||||
|
<li>✓ <strong>Касса с весами Масса-К</strong> из коробки — не отдельный модуль и не сторонняя интеграция.</li>
|
||||||
|
<li>✓ <strong>Импорт за 1 клик</strong> — переход с МойСклада за 5–10 минут.</li>
|
||||||
|
<li>✓ <strong>90 дней триал без карты</strong> — без автосписаний и сюрпризов.</li>
|
||||||
|
</ul>
|
||||||
|
<h2 class="text-2xl font-bold mt-12 mb-4">Дорожная карта</h2>
|
||||||
|
<p class="text-slate-600">Что готово сейчас и что мы планируем — в разделе <a href="/changelog" class="text-brand underline">Changelog</a>.</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
12
src/food-market.public/src/pages/blog/index.astro
Normal file
12
src/food-market.public/src/pages/blog/index.astro
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
---
|
||||||
|
<BaseLayout title="Блог" description="Блог Food Market: новости, релизы, лучшие практики автоматизации розницы.">
|
||||||
|
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14 text-center">
|
||||||
|
<h1 class="text-4xl font-extrabold">Блог</h1>
|
||||||
|
<p class="text-slate-500 mt-3">Новости, релизы, кейсы и практические статьи.</p>
|
||||||
|
<div class="mt-12 rounded-xl border border-dashed border-slate-300 bg-slate-50 p-10 text-slate-500">
|
||||||
|
✍️ Раздел в разработке. Скоро появятся первые посты.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
27
src/food-market.public/src/pages/changelog.astro
Normal file
27
src/food-market.public/src/pages/changelog.astro
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const releases = [
|
||||||
|
{ date: '2026-04', title: 'Phase 6 — Публичный сайт', items: ['Маркетинговый сайт food-market.zat.kz','Регистрация без админа платформы','Тарифы Старт / Бизнес-конструктор / Сеть'] },
|
||||||
|
{ date: '2026-04', title: 'Phase 4 — SuperAdmin консоль', items: ['Управление организациями','Журнал действий','Read-only «открыть как…»','Edit-mode с reason + audit-trail','Настраиваемый retention period'] },
|
||||||
|
{ date: '2026-04', title: 'Phase 3 — Цены и роли', items: ['Расширенная модель цен','Сотрудники и роли','Системные роли (Администратор, Кассир)'] },
|
||||||
|
{ date: '2026-04', title: 'Phase 2 — Закупки и приёмки', items: ['Документ приёмки со сканером','Skользящее среднее себестоимости','Inline quick-add позиций'] },
|
||||||
|
{ date: '2026-03', title: 'Phase 1 — Каталог и tenant-изоляция', items: ['Multi-tenant архитектура','Товары, группы, штрихкоды, остатки','Импорт из МойСклад'] },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Changelog" description="История релизов Food Market: что нового в продукте.">
|
||||||
|
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h1 class="text-4xl font-extrabold">Changelog</h1>
|
||||||
|
<p class="text-slate-500 mt-2">История релизов Food Market.</p>
|
||||||
|
<div class="mt-10 space-y-8">
|
||||||
|
{releases.map((r) => (
|
||||||
|
<article class="rounded-xl border border-slate-200 bg-white p-5">
|
||||||
|
<div class="flex items-baseline justify-between mb-3">
|
||||||
|
<h2 class="font-bold text-lg">{r.title}</h2>
|
||||||
|
<span class="text-xs text-slate-500 font-mono">{r.date}</span>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-1.5 text-sm">{r.items.map((it) => <li>• {it}</li>)}</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
23
src/food-market.public/src/pages/contacts.astro
Normal file
23
src/food-market.public/src/pages/contacts.astro
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
---
|
||||||
|
<BaseLayout title="Контакты" description="Контакты Food Market: техподдержка, отдел продаж, юридический адрес.">
|
||||||
|
<section class="max-w-2xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h1 class="text-4xl font-extrabold">Контакты</h1>
|
||||||
|
<div class="mt-8 space-y-6">
|
||||||
|
<div class="rounded-xl border border-slate-200 bg-white p-5">
|
||||||
|
<h2 class="font-semibold">Техническая поддержка</h2>
|
||||||
|
<p class="text-sm text-slate-500 mt-1">Для клиентов с активным аккаунтом — через чат в админке. Здесь будет email и телефон позже.</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl border border-slate-200 bg-white p-5">
|
||||||
|
<h2 class="font-semibold">Отдел продаж и партнёрство</h2>
|
||||||
|
<p class="text-sm text-slate-500 mt-1">Тариф «Сеть», корпоративные внедрения, white-label партнёрство — пишите по контактам ниже.</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl border border-slate-200 bg-white p-5">
|
||||||
|
<h2 class="font-semibold">Юридический адрес и реквизиты</h2>
|
||||||
|
<p class="text-sm text-slate-500 mt-1"><a href="/legal/requisites" class="text-brand underline">Реквизиты компании</a> — отдельная страница.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-400 mt-10 italic">Раздел в разработке. Финальные контакты появятся после регистрации юр.лица и запуска поддержки.</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
30
src/food-market.public/src/pages/features.astro
Normal file
30
src/food-market.public/src/pages/features.astro
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const sections = [
|
||||||
|
{ id: 'catalog', icon: '📦', title: 'Товары и каталог', items: ['Иерархические группы и подгруппы','Штрихкоды EAN-13/EAN-8/Code128','Цены: розничная, оптовая, эталонная','Автогенерация артикула и штрихкода','Импорт из МойСклад','Картинки товаров','Атрибуты и характеристики'] },
|
||||||
|
{ id: 'sales', icon: '💳', title: 'Продажи и касса', items: ['Касса для Windows','Поддержка весов Масса-К','Сканер штрихкодов USB','Чековые принтеры ESC/POS','Kaspi Pay интеграция','Скидки и акции','Возвраты на кассе'] },
|
||||||
|
{ id: 'stock', icon: '🏬', title: 'Склад и остатки', items: ['Несколько складов','Приёмка со сканером','Автоматический расчёт себестоимости','Инвентаризация','Списание (брак/просрочка)','Оприходование','Перемещения между складами','История движений'] },
|
||||||
|
{ id: 'purchase', icon: '🚚', title: 'Закупки и поставщики', items: ['База контрагентов','Заказы поставщикам','Документы приёмки','Скользящее среднее себестоимости','Эталонная цена'] },
|
||||||
|
{ id: 'crm', icon: '👥', title: 'Клиенты и лояльность', items: ['База клиентов','Скидочные карты (скоро)','Программы лояльности (скоро)','Сегментация (скоро)','SMS-рассылки (скоро)'] },
|
||||||
|
{ id: 'finance', icon: '📊', title: 'Финансы и аналитика', items: ['Дашборд продаж','Отчёт по марже','Отчёт по остаткам','Аналитика клиентов (скоро)','API для BI-инструментов'] },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Возможности" description="Полный список возможностей Food Market: каталог, касса, склад, закупки, CRM, аналитика.">
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-12">
|
||||||
|
<h1 class="text-4xl font-extrabold text-center">Возможности</h1>
|
||||||
|
<p class="text-center text-slate-500 mt-3">6 модулей вместо 40 функций. Группируем по сценариям.</p>
|
||||||
|
<div class="space-y-8 mt-12">
|
||||||
|
{sections.map((s) => (
|
||||||
|
<section id={s.id} class="rounded-xl border border-slate-200 bg-white p-6">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<span class="text-3xl">{s.icon}</span>
|
||||||
|
<h2 class="text-2xl font-bold">{s.title}</h2>
|
||||||
|
</div>
|
||||||
|
<ul class="grid sm:grid-cols-2 gap-1.5 text-sm">
|
||||||
|
{s.items.map((it) => <li>✓ {it}</li>)}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
34
src/food-market.public/src/pages/for-alcohol.astro
Normal file
34
src/food-market.public/src/pages/for-alcohol.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const features = [
|
||||||
|
{ icon: '🏷️', title: 'Акцизные марки', text: 'Сканирование марок при приёмке, привязка к товарным позициям, контроль продаж.' },
|
||||||
|
{ icon: '📜', title: 'ЕГАИС-ready', text: 'Архитектура совместима с ЕГАИС-РК (когда систему развернут полностью). Подготовлены отчёты.' },
|
||||||
|
{ icon: '🕐', title: 'Контроль времени продаж', text: 'Запрет продаж в часы после 23:00 / до 9:00 (по требованию законодательства).' },
|
||||||
|
{ icon: '🆔', title: 'Проверка возраста', text: 'Запрос подтверждения возраста на кассе перед продажей алкогольных позиций.' },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Для алкогольных магазинов" description="Food Market для алкоголя: акцизные марки, ЕГАИС-ready, контроль времени продаж, проверка возраста.">
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/30 via-white to-white">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<span class="text-3xl">🍷</span>
|
||||||
|
<h1 class="text-4xl font-extrabold mt-2">Программа учёта для алкоголя</h1>
|
||||||
|
<p class="mt-4 text-lg text-slate-600">Акцизные марки, ЕГАИС-ready, контроль времени продаж и возраста.</p>
|
||||||
|
<a href="/signup?plan=start" class="inline-block mt-7 px-5 py-3 bg-brand text-white font-semibold rounded-md">Начать бесплатно</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-10">Что важно для алкомаркета</h2>
|
||||||
|
<div class="grid sm:grid-cols-2 gap-5">
|
||||||
|
{features.map((f) => (
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">{f.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2">{f.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1.5">{f.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="bg-slate-50 py-14 text-center">
|
||||||
|
<p class="italic text-slate-700 max-w-2xl mx-auto px-4">«Раздел кейсов в разработке.»</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
34
src/food-market.public/src/pages/for-cafe.astro
Normal file
34
src/food-market.public/src/pages/for-cafe.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const features = [
|
||||||
|
{ icon: '🍔', title: 'Модификаторы и комбо', text: 'Опции к блюдам (без лука, добавить сыр), комбо-меню с автоматической ценой.' },
|
||||||
|
{ icon: '👨🍳', title: 'Технологические карты', text: 'Состав блюд, нормы списания ингредиентов, автоматический пересчёт остатков продуктов.' },
|
||||||
|
{ icon: '🧾', title: 'Заказы по столикам', text: 'Открытие чека на столик, перемещение между столами, разделение чека.' },
|
||||||
|
{ icon: '📈', title: 'Анализ блюд', text: 'Какие блюда продаются, какие нет. Маржинальность по позиции.' },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Для кафе и общепита" description="Food Market для кафе и общепита: модификаторы, комбо, тех.карты, заказы по столикам.">
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/30 via-white to-white">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<span class="text-3xl">☕</span>
|
||||||
|
<h1 class="text-4xl font-extrabold mt-2">Программа учёта для кафе и общепита</h1>
|
||||||
|
<p class="mt-4 text-lg text-slate-600">Модификаторы, комбо, технологические карты, заказы по столикам. Списание ингредиентов автоматом.</p>
|
||||||
|
<a href="/signup?plan=start" class="inline-block mt-7 px-5 py-3 bg-brand text-white font-semibold rounded-md">Начать бесплатно</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-10">Что важно для кафе</h2>
|
||||||
|
<div class="grid sm:grid-cols-2 gap-5">
|
||||||
|
{features.map((f) => (
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">{f.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2">{f.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1.5">{f.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="bg-slate-50 py-14 text-center">
|
||||||
|
<p class="italic text-slate-700 max-w-2xl mx-auto px-4">«Раздел кейсов в разработке.»</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
34
src/food-market.public/src/pages/for-clothing.astro
Normal file
34
src/food-market.public/src/pages/for-clothing.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const features = [
|
||||||
|
{ icon: '📏', title: 'Размерные сетки', text: 'Один товар — много размеров и цветов. Учёт остатков по каждой комбинации.' },
|
||||||
|
{ icon: '🎨', title: 'Цвета и принты', text: 'Атрибуты товара: цвет, материал, сезон. Поиск и фильтры по атрибутам.' },
|
||||||
|
{ icon: '🛍️', title: 'Маркетплейсы', text: 'Выгрузка на Kaspi Magazin, Ozon, Wildberries (скоро) — синхронизация остатков.' },
|
||||||
|
{ icon: '🔄', title: 'Возвраты', text: 'Удобный возврат на кассе с возвратом денег или обменом на другой размер.' },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Для магазина одежды" description="Food Market для одежды: размерные сетки, цвета, маркетплейсы, удобные возвраты.">
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/30 via-white to-white">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<span class="text-3xl">👔</span>
|
||||||
|
<h1 class="text-4xl font-extrabold mt-2">Программа учёта для магазина одежды</h1>
|
||||||
|
<p class="mt-4 text-lg text-slate-600">Размерные сетки, цвета, выгрузка на маркетплейсы, удобные возвраты.</p>
|
||||||
|
<a href="/signup?plan=start" class="inline-block mt-7 px-5 py-3 bg-brand text-white font-semibold rounded-md">Начать бесплатно</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-10">Что важно для одежды</h2>
|
||||||
|
<div class="grid sm:grid-cols-2 gap-5">
|
||||||
|
{features.map((f) => (
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">{f.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2">{f.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1.5">{f.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="bg-slate-50 py-14 text-center">
|
||||||
|
<p class="italic text-slate-700 max-w-2xl mx-auto px-4">«Раздел кейсов в разработке.»</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
34
src/food-market.public/src/pages/for-grocery.astro
Normal file
34
src/food-market.public/src/pages/for-grocery.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const features = [
|
||||||
|
{ icon: '⚖️', title: 'Весовой товар', text: 'Касса принимает вес с весов Масса-К напрямую — стоимость пересчитывается сразу.' },
|
||||||
|
{ icon: '📅', title: 'Скоропорт и сроки', text: 'Контроль сроков годности, уведомление за N дней, списание просрочки одним кликом.' },
|
||||||
|
{ icon: '🏷️', title: 'Печать ценников и штрихкодов', text: 'Внутренняя нумерация EAN-13 для весовых товаров, автогенерация при заведении.' },
|
||||||
|
{ icon: '📊', title: 'ABC-анализ', text: 'Ходовые и неходовые позиции по сумме / марже / штукам. Решения по матрице за минуту.' },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Для продуктового магазина" description="Food Market для продуктового магазина: весовой товар, штрихкоды, скоропорт, ABC-анализ.">
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/30 via-white to-white">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<span class="text-3xl">🛒</span>
|
||||||
|
<h1 class="text-4xl font-extrabold mt-2">Программа учёта для продуктового магазина</h1>
|
||||||
|
<p class="mt-4 text-lg text-slate-600">Весовой товар, скоропорт, штрихкоды, касса с весами Масса-К из коробки. Импорт из МойСклада.</p>
|
||||||
|
<a href="/signup?plan=start" class="inline-block mt-7 px-5 py-3 bg-brand text-white font-semibold rounded-md">Начать бесплатно</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-10">Что важно для продуктового</h2>
|
||||||
|
<div class="grid sm:grid-cols-2 gap-5">
|
||||||
|
{features.map((f) => (
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">{f.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2">{f.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1.5">{f.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="bg-slate-50 py-14 text-center">
|
||||||
|
<p class="italic text-slate-700 max-w-2xl mx-auto px-4">«Раздел кейсов в разработке. Здесь появятся истории магазинов «у дома» и минимаркетов.»</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
34
src/food-market.public/src/pages/for-household.astro
Normal file
34
src/food-market.public/src/pages/for-household.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const features = [
|
||||||
|
{ icon: '🛡️', title: 'Гарантийные сроки', text: 'Учёт сроков гарантии на товар, печать гарантийного талона при продаже.' },
|
||||||
|
{ icon: '🔧', title: 'Серийные номера', text: 'Привязка серийного номера к продаже для возможности возврата по гарантии.' },
|
||||||
|
{ icon: '🎁', title: 'Комплекты товаров', text: 'Продажа набора (например посуда) одной позицией с автоматическим списанием компонентов.' },
|
||||||
|
{ icon: '📤', title: 'Маркетплейсы', text: 'Выгрузка на Kaspi Magazin, синхронизация цен и остатков (скоро).' },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Для магазина дом и быт" description="Food Market для магазина товаров для дома: гарантийные сроки, серийные номера, комплекты, маркетплейсы.">
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/30 via-white to-white">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<span class="text-3xl">🏠</span>
|
||||||
|
<h1 class="text-4xl font-extrabold mt-2">Программа учёта для дома и быта</h1>
|
||||||
|
<p class="mt-4 text-lg text-slate-600">Гарантийные сроки, серийные номера, комплекты, выгрузка на маркетплейсы.</p>
|
||||||
|
<a href="/signup?plan=start" class="inline-block mt-7 px-5 py-3 bg-brand text-white font-semibold rounded-md">Начать бесплатно</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-10">Что важно для дом/быт</h2>
|
||||||
|
<div class="grid sm:grid-cols-2 gap-5">
|
||||||
|
{features.map((f) => (
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">{f.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2">{f.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1.5">{f.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="bg-slate-50 py-14 text-center">
|
||||||
|
<p class="italic text-slate-700 max-w-2xl mx-auto px-4">«Раздел кейсов в разработке.»</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
34
src/food-market.public/src/pages/for-pharmacy.astro
Normal file
34
src/food-market.public/src/pages/for-pharmacy.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const features = [
|
||||||
|
{ icon: '🧪', title: 'Серии и сроки годности', text: 'Партионный учёт с серийными номерами и контролем сроков. Просрочка не пробивается на кассе.' },
|
||||||
|
{ icon: '💊', title: 'Рецептурный учёт', text: 'Отметка рецептурных препаратов, требование подтверждения отпуска фармацевтом.' },
|
||||||
|
{ icon: '📋', title: 'Группы товаров по МНН', text: 'Международные непатентованные наименования, поиск аналогов, замена при отсутствии.' },
|
||||||
|
{ icon: '🔒', title: 'Контроль доступа', text: 'Роли «Фармацевт» и «Заведующий» с разными правами. Логирование всех операций.' },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Для аптеки" description="Food Market для аптеки: партионный учёт, серии и сроки годности, рецептурные препараты, МНН-группы.">
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/30 via-white to-white">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<span class="text-3xl">💊</span>
|
||||||
|
<h1 class="text-4xl font-extrabold mt-2">Программа учёта для аптеки</h1>
|
||||||
|
<p class="mt-4 text-lg text-slate-600">Серии и сроки годности, рецептурный отпуск, группировка по МНН. Импорт из МойСклада.</p>
|
||||||
|
<a href="/signup?plan=start" class="inline-block mt-7 px-5 py-3 bg-brand text-white font-semibold rounded-md">Начать бесплатно</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-10">Что важно для аптеки</h2>
|
||||||
|
<div class="grid sm:grid-cols-2 gap-5">
|
||||||
|
{features.map((f) => (
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">{f.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2">{f.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1.5">{f.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="bg-slate-50 py-14 text-center">
|
||||||
|
<p class="italic text-slate-700 max-w-2xl mx-auto px-4">«Раздел кейсов в разработке.»</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
226
src/food-market.public/src/pages/index.astro
Normal file
226
src/food-market.public/src/pages/index.astro
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
import FAQ from '@/components/FAQ.tsx'
|
||||||
|
|
||||||
|
const verticals = [
|
||||||
|
{ href: '/for-grocery', title: 'Продуктовый магазин', icon: '🛒', desc: 'Весовой товар, штрихкоды, скоропорт' },
|
||||||
|
{ href: '/for-pharmacy', title: 'Аптека', icon: '💊', desc: 'Серии и сроки годности' },
|
||||||
|
{ href: '/for-cafe', title: 'Кафе и общепит', icon: '☕', desc: 'Модификаторы и комбо' },
|
||||||
|
{ href: '/for-alcohol', title: 'Алкоголь', icon: '🍷', desc: 'Акцизные марки, ЕГАИС-ready' },
|
||||||
|
{ href: '/for-clothing', title: 'Одежда', icon: '👔', desc: 'Размерные сетки и цвета' },
|
||||||
|
{ href: '/for-household', title: 'Дом и быт', icon: '🏠', desc: 'Гарантийные сроки' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const modules = [
|
||||||
|
{ icon: '📦', title: 'Товары и каталог', text: 'Группы, штрихкоды, цены, остатки, импорт из МойСклад одной кнопкой.' },
|
||||||
|
{ icon: '💳', title: 'Продажи и касса', text: 'Касса для Windows с поддержкой весов Масса-К, штрихкодов, чековых принтеров.' },
|
||||||
|
{ icon: '🏬', title: 'Склад и приёмки', text: 'Несколько складов, приёмка со сканером, инвентаризация, списание, оприходование.' },
|
||||||
|
{ icon: '🚚', title: 'Закупки и поставщики', text: 'Документы приёмки с автоматическим расчётом себестоимости (скользящее среднее).' },
|
||||||
|
{ icon: '👥', title: 'Клиенты и лояльность', text: 'База контрагентов, скидки, акции, программы лояльности (скоро).' },
|
||||||
|
{ icon: '📊', title: 'Финансы и аналитика', text: 'Отчёты по продажам, остаткам, прибыли. Дашборд кассира и владельца.' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const integrations = [
|
||||||
|
'Kaspi Pay', 'Halyk Bank', 'Jusan Bank', 'Forte Bank', 'ОФД РК', 'Kaspi Magazin', 'Ozon', 'Wildberries',
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout
|
||||||
|
title="Программа учёта для розничных магазинов"
|
||||||
|
description="Программа учёта и касса для розничных магазинов в Казахстане. Касса с весами, импорт из МойСклада, интеграция с Kaspi и ОФД РК. Бесплатно 90 дней без карты."
|
||||||
|
>
|
||||||
|
{/* 1. Hero */}
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/40 via-white to-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-12 sm:py-20 grid lg:grid-cols-2 gap-10 items-center">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-4xl sm:text-5xl font-extrabold leading-tight">
|
||||||
|
Программа учёта и касса для розничных магазинов в Казахстане
|
||||||
|
</h1>
|
||||||
|
<p class="mt-5 text-lg text-slate-600">
|
||||||
|
Касса с весами Масса-К, импорт из МойСклада, интеграция с Kaspi и ОФД РК.
|
||||||
|
<strong>Бесплатно 90 дней без карты.</strong>
|
||||||
|
</p>
|
||||||
|
<div class="mt-7 flex flex-wrap gap-3">
|
||||||
|
<a href="/signup" class="px-5 py-3 bg-brand text-white text-sm font-semibold rounded-md hover:bg-brand-hover">Начать бесплатно</a>
|
||||||
|
<a href="#screenshot" class="px-5 py-3 border border-slate-300 text-sm font-semibold rounded-md hover:bg-slate-50">Посмотреть демо</a>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3 text-xs text-slate-500">Без банковской карты · 90 дней триал · Подписка от 5 000 ₸/мес</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl border border-slate-200 bg-white shadow-xl overflow-hidden">
|
||||||
|
{/* Реальный скриншот админки. Пока placeholder с типографическим mock'ом дашборда. */}
|
||||||
|
<div class="aspect-[16/10] bg-gradient-to-br from-slate-50 to-slate-100 p-4 grid grid-cols-2 gap-3">
|
||||||
|
<div class="bg-white rounded-lg border border-slate-200 p-3"><div class="text-[10px] uppercase text-slate-400">Организаций</div><div class="text-2xl font-bold mt-1">2</div></div>
|
||||||
|
<div class="bg-white rounded-lg border border-slate-200 p-3"><div class="text-[10px] uppercase text-slate-400">Товаров</div><div class="text-2xl font-bold mt-1">29 540</div></div>
|
||||||
|
<div class="bg-white rounded-lg border border-slate-200 p-3"><div class="text-[10px] uppercase text-slate-400">Приёмок / мес</div><div class="text-2xl font-bold mt-1">187</div></div>
|
||||||
|
<div class="bg-white rounded-lg border border-slate-200 p-3"><div class="text-[10px] uppercase text-slate-400">Чеков сегодня</div><div class="text-2xl font-bold mt-1 text-brand">432</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 2. 3 ключевых выгоды */}
|
||||||
|
<section class="max-w-7xl mx-auto px-4 sm:px-6 py-12 grid sm:grid-cols-3 gap-4">
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">🛒</div>
|
||||||
|
<h3 class="font-semibold mt-2">Касса с весами</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1">Поддержка весов Масса-К из коробки. Чёткие чеки в формате ОФД РК.</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">⬇️</div>
|
||||||
|
<h3 class="font-semibold mt-2">Импорт из МойСклада</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1">Перенос товаров, остатков, контрагентов одной кнопкой за 10 минут.</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">🎁</div>
|
||||||
|
<h3 class="font-semibold mt-2">90 дней бесплатно</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1">Триал без банковской карты. Тариф «Старт» от 5 000 ₸/мес.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 3. Скриншот продукта */}
|
||||||
|
<section id="screenshot" class="max-w-7xl mx-auto px-4 sm:px-6 py-12">
|
||||||
|
<div class="rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-6">
|
||||||
|
<div class="aspect-[16/9] bg-white rounded-lg border border-slate-200 flex items-center justify-center text-slate-400 text-sm">
|
||||||
|
Скриншот админки · реальный экран /dashboard
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 4. Для кого */}
|
||||||
|
<section class="max-w-7xl mx-auto px-4 sm:px-6 py-12">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-2">Для кого Food Market</h2>
|
||||||
|
<p class="text-center text-slate-500 mb-8">Шесть вертикалей. Свои фишки в каждой.</p>
|
||||||
|
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{verticals.map((v) => (
|
||||||
|
<a href={v.href} class="rounded-xl border border-slate-200 p-5 hover:shadow-md hover:border-brand transition group">
|
||||||
|
<div class="text-3xl">{v.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2 group-hover:text-brand">{v.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1">{v.desc}</p>
|
||||||
|
<span class="text-xs text-brand mt-3 inline-block">Подробнее →</span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 5. Возможности */}
|
||||||
|
<section class="bg-slate-50 py-14">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-2">6 модулей вместо 40 функций</h2>
|
||||||
|
<p class="text-center text-slate-500 mb-10">Группируем по сценариям работы, а не списком из 40 пунктов.</p>
|
||||||
|
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{modules.map((m) => (
|
||||||
|
<div class="bg-white rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-2xl">{m.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2">{m.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1.5">{m.text}</p>
|
||||||
|
<a href="/features" class="text-xs text-brand mt-3 inline-block">Подробнее →</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 6. Касса */}
|
||||||
|
<section class="max-w-7xl mx-auto px-4 sm:px-6 py-14 grid lg:grid-cols-2 gap-10 items-center">
|
||||||
|
<div>
|
||||||
|
<span class="inline-block text-xs uppercase tracking-wider text-brand font-semibold">Касса для Windows</span>
|
||||||
|
<h2 class="text-3xl font-bold mt-2">Кассовая программа с поддержкой весов Масса-К</h2>
|
||||||
|
<p class="mt-4 text-slate-600">
|
||||||
|
Установщик для Windows 10/11. Работает офлайн, синхронизируется с админкой,
|
||||||
|
принимает Kaspi Pay, печатает фискальные чеки в ОФД РК.
|
||||||
|
</p>
|
||||||
|
<div class="mt-6 flex gap-3">
|
||||||
|
<a href="/pos" class="px-5 py-3 bg-brand text-white text-sm font-semibold rounded-md hover:bg-brand-hover">Подробнее о кассе</a>
|
||||||
|
<span class="px-5 py-3 border border-slate-300 text-sm font-semibold rounded-md text-slate-400 cursor-not-allowed" title="В разработке">Скачать установщик</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="aspect-video rounded-xl border border-slate-200 bg-slate-900 flex items-center justify-center text-slate-500 text-sm">
|
||||||
|
Видео работы кассы · скоро
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 7. Интеграции */}
|
||||||
|
<section class="bg-slate-50 py-14">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-2">Интеграции</h2>
|
||||||
|
<p class="text-center text-slate-500 mb-8">Кассы, банки, ОФД, маркетплейсы. Подключаем по запросу.</p>
|
||||||
|
<div class="flex flex-wrap justify-center gap-3">
|
||||||
|
{integrations.map((label) => (
|
||||||
|
<span class="px-4 py-2 bg-white border border-slate-200 rounded-md text-sm font-medium">{label}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 8. Тарифы */}
|
||||||
|
<section class="max-w-7xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-2">Простые тарифы. Никаких доплат.</h2>
|
||||||
|
<p class="text-center text-slate-500 mb-10">CRM / финансы / лояльность включены во все тарифы.</p>
|
||||||
|
<div class="grid lg:grid-cols-3 gap-4">
|
||||||
|
<div class="rounded-xl border border-slate-200 p-6 bg-white">
|
||||||
|
<h3 class="font-bold text-lg">Старт</h3>
|
||||||
|
<p class="text-3xl font-bold mt-2">5 000 ₸<span class="text-sm font-normal text-slate-500">/мес</span></p>
|
||||||
|
<p class="text-xs text-slate-500 mt-1">90 дней триал</p>
|
||||||
|
<ul class="text-sm space-y-1.5 mt-5">
|
||||||
|
<li>1 магазин · 1 касса · 1 склад</li>
|
||||||
|
<li>2 сотрудника</li>
|
||||||
|
<li>Без лимита товаров</li>
|
||||||
|
<li>Касса с весами</li>
|
||||||
|
<li>Импорт из МойСклад</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/signup?plan=start" class="block text-center mt-6 px-4 py-2.5 bg-brand text-white rounded-md font-semibold">Начать</a>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl border-2 border-brand p-6 bg-white relative">
|
||||||
|
<span class="absolute -top-3 left-1/2 -translate-x-1/2 bg-brand text-white text-xs px-2 py-0.5 rounded-full">Популярный</span>
|
||||||
|
<h3 class="font-bold text-lg">Бизнес</h3>
|
||||||
|
<p class="text-3xl font-bold mt-2">от 10 000 ₸<span class="text-sm font-normal text-slate-500">/мес</span></p>
|
||||||
|
<p class="text-xs text-slate-500 mt-1">Конструктор по объёму бизнеса</p>
|
||||||
|
<ul class="text-sm space-y-1.5 mt-5">
|
||||||
|
<li>До 3 магазинов</li>
|
||||||
|
<li>До 5 касс и складов</li>
|
||||||
|
<li>До 10 сотрудников</li>
|
||||||
|
<li>API + все интеграции</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/pricing" class="block text-center mt-6 px-4 py-2.5 bg-brand text-white rounded-md font-semibold">Настроить тариф</a>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl border border-slate-200 p-6 bg-white">
|
||||||
|
<h3 class="font-bold text-lg">Сеть</h3>
|
||||||
|
<p class="text-3xl font-bold mt-2">По запросу</p>
|
||||||
|
<p class="text-xs text-slate-500 mt-1">Демо + индивидуальный SLA</p>
|
||||||
|
<ul class="text-sm space-y-1.5 mt-5">
|
||||||
|
<li>Unlimited магазины / кассы</li>
|
||||||
|
<li>Выделенный менеджер</li>
|
||||||
|
<li>Расширенный API</li>
|
||||||
|
<li>Гарантии доступности</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/contacts" class="block text-center mt-6 px-4 py-2.5 border border-slate-300 rounded-md font-semibold">Связаться</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-center text-sm text-slate-500 mt-8">
|
||||||
|
Никаких доплат за CRM, финансы, лояльность, поддержку. Всё включено в каждый тариф.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 9. Соцпруф */}
|
||||||
|
<section class="bg-slate-50 py-14">
|
||||||
|
<div class="max-w-3xl mx-auto px-4 sm:px-6 text-center">
|
||||||
|
<p class="text-xs uppercase tracking-wider text-slate-500 mb-4">Кейсы</p>
|
||||||
|
<p class="text-xl text-slate-700 italic">«Раздел в разработке. Здесь появятся истории магазинов, которые перешли на Food Market.»</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 10. FAQ */}
|
||||||
|
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-8">Частые вопросы</h2>
|
||||||
|
<FAQ client:visible />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 11. Финальный CTA */}
|
||||||
|
<section class="bg-brand-light/30 py-16">
|
||||||
|
<div class="max-w-3xl mx-auto px-4 sm:px-6 text-center">
|
||||||
|
<h2 class="text-3xl sm:text-4xl font-extrabold">Запустите магазин за 15 минут</h2>
|
||||||
|
<p class="mt-3 text-slate-600">Регистрация — минута. Импорт товаров — десять минут. Касса работает на следующий день.</p>
|
||||||
|
<a href="/signup" class="inline-block mt-7 px-7 py-3 bg-brand text-white font-semibold rounded-md hover:bg-brand-hover">Начать бесплатно — 90 дней</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
27
src/food-market.public/src/pages/integrations.astro
Normal file
27
src/food-market.public/src/pages/integrations.astro
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const groups = [
|
||||||
|
{ title: 'Платёжные системы', items: ['Kaspi Pay', 'Halyk Bank', 'Jusan Bank', 'Forte Bank'] },
|
||||||
|
{ title: 'Фискализация', items: ['ОФД РК (все операторы)', 'Чековые принтеры ESC/POS'] },
|
||||||
|
{ title: 'Маркетплейсы (скоро)', items: ['Kaspi Magazin', 'Ozon', 'Wildberries'] },
|
||||||
|
{ title: 'Учётные системы', items: ['МойСклад (импорт каталога)', 'Excel CSV-импорт'] },
|
||||||
|
{ title: 'Оборудование', items: ['Весы Масса-К (USB/COM)', 'Сканеры штрихкодов USB', 'Денежные ящики', 'Дисплей покупателя'] },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Интеграции" description="Кассы, банки, ОФД, маркетплейсы, учётные системы — все интеграции Food Market.">
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-12">
|
||||||
|
<h1 class="text-4xl font-extrabold text-center">Интеграции</h1>
|
||||||
|
<p class="text-center text-slate-500 mt-3">Стандартные подключения для розницы Казахстана.</p>
|
||||||
|
<div class="grid sm:grid-cols-2 gap-5 mt-10">
|
||||||
|
{groups.map((g) => (
|
||||||
|
<div class="rounded-xl border border-slate-200 bg-white p-5">
|
||||||
|
<h2 class="font-semibold mb-3">{g.title}</h2>
|
||||||
|
<ul class="space-y-1 text-sm text-slate-700">
|
||||||
|
{g.items.map((it) => <li>• {it}</li>)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p class="text-center text-sm text-slate-500 mt-10">Нужна интеграция, которой нет в списке? <a href="/contacts" class="text-brand underline">Напишите нам</a> — добавим в дорожную карту.</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
12
src/food-market.public/src/pages/kb/index.astro
Normal file
12
src/food-market.public/src/pages/kb/index.astro
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
---
|
||||||
|
<BaseLayout title="База знаний" description="База знаний Food Market: статьи и инструкции для пользователей.">
|
||||||
|
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14 text-center">
|
||||||
|
<h1 class="text-4xl font-extrabold">База знаний</h1>
|
||||||
|
<p class="text-slate-500 mt-3">Статьи и инструкции по работе с Food Market.</p>
|
||||||
|
<div class="mt-12 rounded-xl border border-dashed border-slate-300 bg-slate-50 p-10 text-slate-500">
|
||||||
|
📚 Раздел в разработке. Здесь появятся пошаговые инструкции по приёмке, инвентаризации, кассе и интеграциям.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
53
src/food-market.public/src/pages/legal/[slug].astro
Normal file
53
src/food-market.public/src/pages/legal/[slug].astro
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
import { getCollection } from 'astro:content'
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const docs = await getCollection('legal')
|
||||||
|
return docs.map((d) => ({ params: { slug: d.slug }, props: { doc: d } }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const { doc } = Astro.props
|
||||||
|
const { Content } = await doc.render()
|
||||||
|
const updated = doc.data.last_updated.toLocaleDateString('ru-KZ', { day: '2-digit', month: 'long', year: 'numeric' })
|
||||||
|
---
|
||||||
|
<BaseLayout title={doc.data.title} description={`Юридический документ Food Market: ${doc.data.title}.`}>
|
||||||
|
<article class="max-w-[720px] mx-auto px-4 sm:px-6 py-12">
|
||||||
|
<p class="text-xs text-slate-500 mb-2">Обновлено: {updated}</p>
|
||||||
|
<div class="prose-legal">
|
||||||
|
<Content />
|
||||||
|
</div>
|
||||||
|
<p class="mt-12 pt-6 border-t border-slate-200 text-xs text-slate-500">
|
||||||
|
По вопросам — <a href="/contacts" class="text-brand underline">контакты</a>.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
.prose-legal {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
.prose-legal h1 { font-size: 1.875rem; font-weight: 800; line-height: 1.2; margin-top: 1.5rem; margin-bottom: 1rem; }
|
||||||
|
.prose-legal h2 { font-size: 1.375rem; font-weight: 700; line-height: 1.3; margin-top: 2rem; margin-bottom: 0.75rem; }
|
||||||
|
.prose-legal h3 { font-size: 1.125rem; font-weight: 600; line-height: 1.4; margin-top: 1.5rem; margin-bottom: 0.5rem; }
|
||||||
|
.prose-legal p { margin: 0.75rem 0; }
|
||||||
|
.prose-legal ul, .prose-legal ol { margin: 0.75rem 0 0.75rem 1.5rem; }
|
||||||
|
.prose-legal li { margin: 0.25rem 0; }
|
||||||
|
.prose-legal blockquote {
|
||||||
|
border-left: 3px solid #fbbf24;
|
||||||
|
background: #fffbeb;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin: 1.25rem 0;
|
||||||
|
color: #92400e;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.prose-legal a { color: var(--color-brand, #00B207); text-decoration: underline; }
|
||||||
|
.prose-legal hr { margin: 2rem 0; border: 0; border-top: 1px solid #e2e8f0; }
|
||||||
|
.prose-legal code { background: #f1f5f9; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875em; }
|
||||||
|
.prose-legal table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.9rem; }
|
||||||
|
.prose-legal th, .prose-legal td { border: 1px solid #e2e8f0; padding: 0.5rem 0.75rem; text-align: left; }
|
||||||
|
.prose-legal th { background: #f8fafc; font-weight: 600; }
|
||||||
|
.prose-legal strong { font-weight: 600; }
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const compare = [
|
||||||
|
{ row: 'Цена за базовый тариф', moy: 'от 4 900 ₽ ≈ 30 000 ₸', us: '5 000 ₸' },
|
||||||
|
{ row: 'Касса с весами Масса-К', moy: '— (отдельный модуль)', us: '✓ из коробки' },
|
||||||
|
{ row: 'CRM и лояльность', moy: '+ доплата', us: '✓ включено' },
|
||||||
|
{ row: 'Финансовый учёт', moy: '+ доплата', us: '✓ включено' },
|
||||||
|
{ row: 'Скорость интерфейса', moy: '~3 сек загрузка', us: '<1 сек' },
|
||||||
|
{ row: 'Поддержка KZ-локализации', moy: 'базовая', us: 'полная (тенге, ОКЕИ, ОФД РК)' },
|
||||||
|
{ row: 'Триал', moy: '14 дней', us: '90 дней без карты' },
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Переход с МойСклада" description="Перенос товаров, штрихкодов, контрагентов и остатков из МойСклада в Food Market за один клик.">
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/40 via-white to-white">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h1 class="text-4xl font-extrabold">Переходите с МойСклада<br/>за один клик</h1>
|
||||||
|
<p class="mt-4 text-lg text-slate-600">Импорт товаров, групп, контрагентов, остатков и штрихкодов через ваш существующий API-токен МойСклада. 5–10 минут — и магазин уже работает в Food Market.</p>
|
||||||
|
<a href="/signup?plan=start" class="inline-block mt-7 px-5 py-3 bg-brand text-white font-semibold rounded-md hover:bg-brand-hover">Начать миграцию</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="max-w-5xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-8">МойСклад vs Food Market</h2>
|
||||||
|
<div class="overflow-x-auto rounded-xl border border-slate-200 bg-white">
|
||||||
|
<table class="w-full text-sm">
|
||||||
|
<thead class="bg-slate-50">
|
||||||
|
<tr>
|
||||||
|
<th class="text-left px-4 py-3 font-semibold">Параметр</th>
|
||||||
|
<th class="text-left px-4 py-3 font-semibold">МойСклад</th>
|
||||||
|
<th class="text-left px-4 py-3 font-semibold text-brand">Food Market</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
{compare.map((c) => (
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-3 font-medium">{c.row}</td>
|
||||||
|
<td class="px-4 py-3 text-slate-600">{c.moy}</td>
|
||||||
|
<td class="px-4 py-3 text-slate-900 font-medium">{c.us}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="bg-slate-50 py-14">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-2">Что переносится</h2>
|
||||||
|
<p class="text-center text-slate-500 mb-8">Один токен — все данные.</p>
|
||||||
|
<div class="grid sm:grid-cols-2 gap-3 text-sm">
|
||||||
|
{['Товары и услуги','Группы и категории','Штрихкоды и артикулы','Цены (розничные + оптовые)','Контрагенты (поставщики и клиенты)','Остатки по складам','Единицы измерения','Атрибуты товаров'].map((x) => (
|
||||||
|
<div class="bg-white rounded-md border border-slate-200 px-4 py-3">✓ {x}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="max-w-3xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-10">3 шага до запуска</h2>
|
||||||
|
<ol class="space-y-6">
|
||||||
|
<li class="flex gap-4"><div class="flex-shrink-0 w-10 h-10 rounded-full bg-brand text-white font-bold flex items-center justify-center">1</div>
|
||||||
|
<div><h3 class="font-semibold">Зарегистрируйтесь в Food Market</h3><p class="text-sm text-slate-600 mt-1">Создайте аккаунт за минуту. 90 дней триал без банковской карты.</p></div></li>
|
||||||
|
<li class="flex gap-4"><div class="flex-shrink-0 w-10 h-10 rounded-full bg-brand text-white font-bold flex items-center justify-center">2</div>
|
||||||
|
<div><h3 class="font-semibold">Подключите токен МойСклада</h3><p class="text-sm text-slate-600 mt-1">Настройки → Интеграции → МойСклад → ваш API-токен. Кнопка «Импортировать».</p></div></li>
|
||||||
|
<li class="flex gap-4"><div class="flex-shrink-0 w-10 h-10 rounded-full bg-brand text-white font-bold flex items-center justify-center">3</div>
|
||||||
|
<div><h3 class="font-semibold">Откройте кассу и работайте</h3><p class="text-sm text-slate-600 mt-1">Скачайте установщик кассы для Windows, подключите весы и сканер. Магазин запущен.</p></div></li>
|
||||||
|
</ol>
|
||||||
|
<div class="text-center mt-10">
|
||||||
|
<a href="/signup?plan=start" class="px-5 py-3 bg-brand text-white font-semibold rounded-md hover:bg-brand-hover">Начать миграцию</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
60
src/food-market.public/src/pages/pos.astro
Normal file
60
src/food-market.public/src/pages/pos.astro
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
const features = [
|
||||||
|
{ icon: '⚖️', title: 'Весы Масса-К', text: 'Подключение по USB/COM. Вес автоматически попадает в чек, цена пересчитывается на лету.' },
|
||||||
|
{ icon: '📡', title: 'Офлайн-режим', text: 'Касса работает при отсутствии интернета. Чеки синхронизируются после восстановления связи.' },
|
||||||
|
{ icon: '🧾', title: 'Фискальные чеки ОФД РК', text: 'Чеки в формате ОФД Казахстана с автоматической отправкой в налоговую.' },
|
||||||
|
{ icon: '💳', title: 'Kaspi Pay и наличные', text: 'Оплата картой через Kaspi POS, наличными, смешанная оплата в одном чеке.' },
|
||||||
|
{ icon: '🎫', title: 'Скидки и акции', text: 'Скидка % или фиксированная сумма, скан карт лояльности, накопительные программы.' },
|
||||||
|
{ icon: '📦', title: 'Сканер штрихкодов', text: 'Любой USB-сканер, EAN-13/EAN-8/Code128/Datamatrix. Поиск товара за миллисекунды.' },
|
||||||
|
]
|
||||||
|
const hardware = [
|
||||||
|
'Весы Масса-К серий MK / 4D / Tiger',
|
||||||
|
'Сканеры штрихкодов USB (любые HID)',
|
||||||
|
'Чековые принтеры ESC/POS (Xprinter, Sam4s)',
|
||||||
|
'Денежный ящик RJ-11/RJ-12',
|
||||||
|
'Дисплей покупателя VFD',
|
||||||
|
]
|
||||||
|
---
|
||||||
|
<BaseLayout title="Касса для Windows" description="Кассовая программа Food Market для Windows: поддержка весов Масса-К, офлайн-режим, фискальные чеки ОФД РК, Kaspi Pay.">
|
||||||
|
<section class="bg-gradient-to-br from-brand-light/40 via-white to-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<span class="inline-block text-xs uppercase tracking-wider text-brand font-semibold">Касса</span>
|
||||||
|
<h1 class="text-4xl font-extrabold mt-2">Касса для Windows<br/>с поддержкой весов Масса-К</h1>
|
||||||
|
<p class="mt-4 max-w-xl text-slate-600">
|
||||||
|
Установщик для Windows 10/11. Работает офлайн. Печатает фискальные чеки в ОФД РК.
|
||||||
|
Принимает Kaspi Pay, наличные и смешанные платежи.
|
||||||
|
</p>
|
||||||
|
<div class="mt-7 flex gap-3">
|
||||||
|
<span class="px-5 py-3 bg-slate-200 text-slate-500 rounded-md font-semibold cursor-not-allowed" title="В разработке">Скачать установщик</span>
|
||||||
|
<a href="/pricing" class="px-5 py-3 border border-slate-300 rounded-md font-semibold hover:bg-slate-50">Тарифы</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="max-w-7xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<div class="aspect-video rounded-xl bg-slate-900 flex items-center justify-center text-slate-500">Видео работы кассы · скоро</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="max-w-7xl mx-auto px-4 sm:px-6 py-12">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-10">Что внутри</h2>
|
||||||
|
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||||
|
{features.map((f) => (
|
||||||
|
<div class="rounded-xl border border-slate-200 p-5">
|
||||||
|
<div class="text-3xl">{f.icon}</div>
|
||||||
|
<h3 class="font-semibold mt-2">{f.title}</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-1.5">{f.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="bg-slate-50 py-14">
|
||||||
|
<div class="max-w-3xl mx-auto px-4 sm:px-6">
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-6">Поддерживаемое железо</h2>
|
||||||
|
<ul class="bg-white rounded-xl border border-slate-200 divide-y divide-slate-100">
|
||||||
|
{hardware.map((h) => <li class="px-5 py-3.5 text-sm">✓ {h}</li>)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
53
src/food-market.public/src/pages/pricing.astro
Normal file
53
src/food-market.public/src/pages/pricing.astro
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
import BusinessTariffBuilder from '@/components/BusinessTariffBuilder.tsx'
|
||||||
|
---
|
||||||
|
<BaseLayout title="Тарифы" description="Простые тарифы Food Market: Старт от 5 000 ₸/мес, Бизнес-конструктор, Сеть по запросу. 90 дней бесплатно без банковской карты.">
|
||||||
|
<section class="max-w-6xl mx-auto px-4 sm:px-6 py-12">
|
||||||
|
<h1 class="text-4xl font-extrabold text-center">Тарифы Food Market</h1>
|
||||||
|
<p class="text-center text-slate-500 mt-3">Никаких скрытых доплат. CRM, финансы, лояльность включены во все тарифы.</p>
|
||||||
|
|
||||||
|
<div class="grid lg:grid-cols-3 gap-5 mt-10">
|
||||||
|
<div class="rounded-xl border border-slate-200 p-6 bg-white">
|
||||||
|
<h2 class="font-bold text-lg">Старт</h2>
|
||||||
|
<p class="text-3xl font-bold mt-2">5 000 ₸<span class="text-sm font-normal text-slate-500">/мес</span></p>
|
||||||
|
<p class="text-xs text-slate-500 mt-1">90 дней бесплатно, без карты</p>
|
||||||
|
<ul class="text-sm space-y-1.5 mt-5 text-slate-700">
|
||||||
|
<li>✓ 1 магазин · 1 касса · 1 склад</li>
|
||||||
|
<li>✓ 2 сотрудника</li>
|
||||||
|
<li>✓ Без лимита товаров</li>
|
||||||
|
<li>✓ Касса с весами Масса-К</li>
|
||||||
|
<li>✓ Импорт из МойСклад</li>
|
||||||
|
<li>✓ Интеграции Kaspi / ОФД</li>
|
||||||
|
<li class="text-slate-400">— API</li>
|
||||||
|
<li class="text-slate-400">— SLA</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/signup?plan=start" class="block text-center mt-6 px-4 py-2.5 bg-brand text-white rounded-md font-semibold">Начать</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lg:row-span-2">
|
||||||
|
<BusinessTariffBuilder client:load />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-xl border border-slate-200 p-6 bg-white">
|
||||||
|
<h2 class="font-bold text-lg">Сеть</h2>
|
||||||
|
<p class="text-3xl font-bold mt-2">По запросу</p>
|
||||||
|
<p class="text-xs text-slate-500 mt-1">Демо + индивидуальный SLA</p>
|
||||||
|
<ul class="text-sm space-y-1.5 mt-5 text-slate-700">
|
||||||
|
<li>✓ Unlimited магазины / кассы / склады</li>
|
||||||
|
<li>✓ Unlimited сотрудники</li>
|
||||||
|
<li>✓ Расширенный API</li>
|
||||||
|
<li>✓ Выделенный менеджер</li>
|
||||||
|
<li>✓ Гарантии доступности</li>
|
||||||
|
<li>✓ Приоритетная поддержка</li>
|
||||||
|
</ul>
|
||||||
|
<a href="/contacts" class="block text-center mt-6 px-4 py-2.5 border border-slate-300 rounded-md font-semibold">Связаться</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-12 rounded-xl bg-brand-light/30 p-6 text-center">
|
||||||
|
<h3 class="font-bold">Никаких доплат за модули</h3>
|
||||||
|
<p class="text-sm text-slate-600 mt-2">CRM, финансы, лояльность, импорт, поддержка — всё включено в каждый тариф. Без сюрпризов в счёте.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
16
src/food-market.public/src/pages/signup.astro
Normal file
16
src/food-market.public/src/pages/signup.astro
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
import SignupForm from '@/components/SignupForm.tsx'
|
||||||
|
|
||||||
|
const url = Astro.url
|
||||||
|
const plan = url.searchParams.get('plan') ?? 'start'
|
||||||
|
---
|
||||||
|
<BaseLayout title="Регистрация" description="Регистрация в Food Market — 90 дней бесплатно, без банковской карты.">
|
||||||
|
<section class="max-w-md mx-auto px-4 sm:px-6 py-12">
|
||||||
|
<h1 class="text-3xl font-extrabold text-center">Создать аккаунт</h1>
|
||||||
|
<p class="text-center text-slate-500 mt-2">90 дней бесплатно. Без банковской карты.</p>
|
||||||
|
<div class="mt-8 rounded-xl border border-slate-200 p-6 bg-white">
|
||||||
|
<SignupForm client:load defaultPlan={plan} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
18
src/food-market.public/src/pages/sitemap.xml.ts
Normal file
18
src/food-market.public/src/pages/sitemap.xml.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { APIRoute } from 'astro'
|
||||||
|
|
||||||
|
const PAGES = [
|
||||||
|
'', 'features', 'pricing', 'pos', 'migration-from-moysklad', 'integrations',
|
||||||
|
'for-grocery', 'for-pharmacy', 'for-cafe', 'for-alcohol', 'for-clothing', 'for-household',
|
||||||
|
'signup', 'about', 'contacts', 'kb', 'blog', 'status', 'changelog',
|
||||||
|
'legal/offer', 'legal/privacy', 'legal/consent', 'legal/requisites',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ site }) => {
|
||||||
|
const base = (site?.toString() ?? 'https://food-market.kz').replace(/\/$/, '')
|
||||||
|
const urls = PAGES.map((p) => `<url><loc>${base}/${p}</loc></url>`).join('\n ')
|
||||||
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
${urls}
|
||||||
|
</urlset>`
|
||||||
|
return new Response(xml, { headers: { 'Content-Type': 'application/xml' } })
|
||||||
|
}
|
||||||
15
src/food-market.public/src/pages/status.astro
Normal file
15
src/food-market.public/src/pages/status.astro
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
---
|
||||||
|
<BaseLayout title="Status" description="Статус систем Food Market: API, админка, касса.">
|
||||||
|
<section class="max-w-2xl mx-auto px-4 sm:px-6 py-14">
|
||||||
|
<h1 class="text-4xl font-extrabold text-center">Статус систем</h1>
|
||||||
|
<ul class="mt-10 divide-y divide-slate-200 border border-slate-200 rounded-xl bg-white">
|
||||||
|
<li class="flex items-center justify-between px-5 py-4"><span>API</span><span class="text-emerald-600 text-sm">● Работает</span></li>
|
||||||
|
<li class="flex items-center justify-between px-5 py-4"><span>Админка</span><span class="text-emerald-600 text-sm">● Работает</span></li>
|
||||||
|
<li class="flex items-center justify-between px-5 py-4"><span>База данных</span><span class="text-emerald-600 text-sm">● Работает</span></li>
|
||||||
|
<li class="flex items-center justify-between px-5 py-4"><span>Касса (Windows)</span><span class="text-slate-400 text-sm">○ В разработке</span></li>
|
||||||
|
</ul>
|
||||||
|
<p class="text-xs text-slate-400 mt-6 text-center">Раздел в разработке. Скоро добавим автоматический мониторинг и историю инцидентов.</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
12
src/food-market.public/src/styles/global.css
Normal file
12
src/food-market.public/src/styles/global.css
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
|
body { background: #fff; color: #0f172a; }
|
||||||
23
src/food-market.public/tailwind.config.ts
Normal file
23
src/food-market.public/tailwind.config.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { Config } from 'tailwindcss'
|
||||||
|
|
||||||
|
// Палитра совпадает с food-market.web — тот же бренд (зелёный 00B207).
|
||||||
|
export default {
|
||||||
|
content: ['./src/**/*.{astro,tsx,ts,jsx,js,md,mdx,html}'],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
brand: {
|
||||||
|
DEFAULT: '#00B207',
|
||||||
|
hover: '#009305',
|
||||||
|
dark: '#007605',
|
||||||
|
light: '#E8F5E9',
|
||||||
|
tint: '#D7F2D9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter', 'system-ui', '-apple-system', 'Segoe UI', 'Roboto', 'sans-serif'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
} satisfies Config
|
||||||
9
src/food-market.public/tsconfig.json
Normal file
9
src/food-market.public/tsconfig.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "react",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": { "@/*": ["src/*"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { LoginPage } from '@/pages/LoginPage'
|
import { LoginPage } from '@/pages/LoginPage'
|
||||||
|
import { AuthBridgePage } from '@/pages/AuthBridgePage'
|
||||||
import { DashboardPage } from '@/pages/DashboardPage'
|
import { DashboardPage } from '@/pages/DashboardPage'
|
||||||
import { OnboardingPage } from '@/pages/OnboardingPage'
|
import { OnboardingPage } from '@/pages/OnboardingPage'
|
||||||
import { SuperAdminDashboardPage } from '@/pages/SuperAdminDashboardPage'
|
import { SuperAdminDashboardPage } from '@/pages/SuperAdminDashboardPage'
|
||||||
|
|
@ -48,6 +49,7 @@ export default function App() {
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route path="/auth-bridge" element={<AuthBridgePage />} />
|
||||||
<Route element={<ProtectedRoute />}>
|
<Route element={<ProtectedRoute />}>
|
||||||
{/* SuperAdmin консоль — отдельный layout c индиго-сайдбаром,
|
{/* SuperAdmin консоль — отдельный layout c индиго-сайдбаром,
|
||||||
* системными разделами и быстрым «Открыть организацию» в topbar.
|
* системными разделами и быстрым «Открыть организацию» в topbar.
|
||||||
|
|
|
||||||
47
src/food-market.web/src/pages/AuthBridgePage.tsx
Normal file
47
src/food-market.web/src/pages/AuthBridgePage.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
/** Принимает токены из URL fragment (#access=...&refresh=...&welcome=1)
|
||||||
|
* — приходит сюда после успешного signup на публичном сайте — кладёт
|
||||||
|
* в localStorage в формате который ожидает auth.ts, и редиректит в /.
|
||||||
|
*
|
||||||
|
* Через fragment чтобы access_token не попадал в server-логи / Referer
|
||||||
|
* заголовки (URL fragment не отправляется на бэк). */
|
||||||
|
export function AuthBridgePage() {
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const hash = (window.location.hash || '').replace(/^#/, '')
|
||||||
|
const params = new URLSearchParams(hash)
|
||||||
|
const access = params.get('access')
|
||||||
|
const refresh = params.get('refresh')
|
||||||
|
const welcome = params.get('welcome') === '1'
|
||||||
|
if (!access) { setError('Токен не получен. Войдите вручную.'); return }
|
||||||
|
localStorage.setItem('fm.access_token', access)
|
||||||
|
if (refresh) localStorage.setItem('fm.refresh_token', refresh)
|
||||||
|
// Чистим fragment чтобы при F5 страница не пыталась повторно «принять»
|
||||||
|
// токены из URL.
|
||||||
|
const target = welcome ? '/?welcome=signup' : '/'
|
||||||
|
window.location.replace(target)
|
||||||
|
} catch (e) {
|
||||||
|
setError((e as Error).message)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-slate-50">
|
||||||
|
<div className="rounded-xl border border-slate-200 bg-white p-8 max-w-md text-center">
|
||||||
|
{error ? (
|
||||||
|
<>
|
||||||
|
<p className="text-red-600 font-semibold">{error}</p>
|
||||||
|
<a href="/login" className="mt-4 inline-block px-4 py-2 bg-[var(--color-brand)] text-white rounded-md">К странице входа</a>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="w-12 h-12 mx-auto rounded-full bg-emerald-100 text-emerald-600 flex items-center justify-center text-xl">✓</div>
|
||||||
|
<p className="mt-3 font-semibold">Регистрация прошла</p>
|
||||||
|
<p className="text-sm text-slate-500 mt-1">Перенаправляем в админку…</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue