merge: свести ветку визиток из Gitea с локальной работой по лендингу

Две линии разработки разъехались и месяц жили порознь: в Gitea уехали
электронные визитки с QR-кодом, а в рабочей копии на fortress остались шесть
коммитов, которые до репозитория не дошли и, значит, не попали на сайт —
английская версия с переключателем языков, карточка Бонитировки, логотипы
продуктов, реквизиты компании в подвале, убранная кнопка Telegram, имя
директора латиницей. Плюс незакоммиченное бургер-меню.

Что разрешено вручную:

1. Landing.tsx, блок контактов. Столкнулись наша расстановка (почта главной
   кнопкой с подписью из cta_primary, телефон второй) и правка из Gitea,
   переводящая оба контакта с href на onClick, чтобы адрес и номер не лежали
   в разметке и их не собирали боты. Взято и то, и другое: защита от ботов
   сохранена, порядок и оформление наши, адрес почты в подписи не светится.

2. Коллизия номеров миграций: обе стороны завели номер 003. На живой базе
   применены 001, 002 и 003_business_cards, а 003_product_url_and_requisites
   не выполнялась никогда — она и переименована в 004, чтобы раннер (он
   отслеживает миграции по имени файла) отработал её штатно.

3. package-lock пересобран под слитый манифест, а не выбран со стороны.

Проверено: tsc чист, vite build проходит, 118 модулей, 241 КБ.
This commit is contained in:
zeroday
2026-08-25 22:27:25 +03:00
15 changed files with 1209 additions and 204 deletions
+34
View File
@@ -29,6 +29,23 @@ export interface ApproachItem {
description_en?: string | null;
}
export interface BusinessCard {
id: number;
slug: string;
name: string;
role: string | null;
company: string | null;
phone: string | null;
email: string | null;
telegram: string | null;
photo: string | null;
product_title: string | null;
product_subtitle: string | null;
product_url: string | null;
site_url: string | null;
is_published?: boolean;
}
export interface LandingContent {
sections: Record<string, Record<string, unknown>>;
sections_en?: Record<string, Record<string, unknown>>;
@@ -106,4 +123,21 @@ export const api = {
method: 'PUT',
body: JSON.stringify({ value }),
}),
getCard: (slug: string) =>
request<{ card: BusinessCard }>(`/api/cards/${slug}`),
listCards: () =>
request<{ cards: BusinessCard[] }>('/api/admin/cards'),
createCard: (data: Partial<BusinessCard>) =>
request<{ card: BusinessCard }>('/api/admin/cards', {
method: 'POST',
body: JSON.stringify(data),
}),
updateCard: (id: number, data: Partial<BusinessCard>) =>
request<{ card: BusinessCard }>(`/api/admin/cards/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
}),
deleteCard: (id: number) =>
request<{ ok: true }>(`/api/admin/cards/${id}`, { method: 'DELETE' }),
};