-
3
-
-
{getTranslation('publish_title')}
-
{getTranslation('publish_description')}
+
+ {/* Правая колонка */}
+
+ {/* Стикерный энтузиаст (вторая карточка) */}
+
handleBuyPack(tokenPacks[1])}
+ />
+
+ {/* Зеленая карточка с изображением (четвертая карточка) */}
+
+
+
+ {/* Стикерный магнат (пятая карточка, на всю ширину) */}
+
handleBuyPack(tokenPacks[3])}
+ />
+
+ {/* Бог стикеров (шестая карточка, на всю ширину) */}
+ handleBuyPack(tokenPacks[4])}
+ />
);
diff --git a/src/screens/onboarding/OnboardingWelcome.module.css b/src/screens/onboarding/OnboardingWelcome.module.css
new file mode 100644
index 0000000..3cb1924
--- /dev/null
+++ b/src/screens/onboarding/OnboardingWelcome.module.css
@@ -0,0 +1,203 @@
+.root {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ background: #fff;
+ box-sizing: border-box;
+ overflow-x: hidden;
+}
+
+.imageBlock {
+ width: 100%;
+ max-width: 100%;
+ margin: 0;
+ padding: 0;
+ overflow: visible;
+ position: relative;
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+}
+
+.image {
+ width: 100%;
+ min-width: 100%;
+ max-width: 100%;
+ height: auto;
+ display: block;
+ margin: 0;
+ padding: 0;
+ border-radius: 0;
+ box-shadow: none;
+ object-fit: cover;
+ flex-shrink: 0;
+ flex-grow: 0;
+ /* Сдвиг вверх для маленьких экранов будет через медиазапросы */
+}
+
+.contentWrapper {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 auto;
+ width: 100%;
+ padding: 0 16px;
+ box-sizing: border-box;
+ justify-content: flex-start;
+}
+
+.textContent {
+ margin: 0;
+ padding: 0 0 16px 0;
+}
+
+.title {
+ font-size: 28px;
+ font-weight: 800;
+ color: #111;
+ text-align: center;
+ margin-bottom: 18px;
+ margin-top: 0;
+ line-height: 1.18;
+ letter-spacing: -0.5px;
+}
+
+.description {
+ font-size: 18px;
+ color: #444;
+ text-align: center;
+ margin-bottom: 0;
+ margin-top: 0;
+ line-height: 1.5;
+ font-weight: 400;
+}
+
+.bottomContent {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: auto;
+ margin-bottom: 0;
+ padding-top: 24px;
+}
+
+.progress {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 16px;
+ width: 100%;
+}
+
+.dot {
+ width: 32px;
+ height: 8px;
+ border-radius: 4px;
+ background: #e0e0e0;
+ transition: background 0.2s;
+}
+
+.dotActive {
+ background: #16b100;
+}
+
+.button {
+ width: calc(100% - 32px);
+ max-width: 370px;
+ background: #16b100;
+ color: #fff;
+ font-size: 20px;
+ font-weight: 700;
+ border: none;
+ border-radius: 12px;
+ padding: 18px 0;
+ margin: 0 0 32px 0;
+ cursor: pointer;
+ box-shadow: 0 2px 8px rgba(22, 177, 0, 0.08);
+ transition: background 0.2s;
+}
+
+.button:active {
+ background: #129200;
+}
+
+/* --- Медиазапросы для адаптивности --- */
+@media (max-width: 480px) {
+ .imageBlock {
+ max-height: none;
+ min-height: 120px;
+ }
+ .image {
+ max-height: none;
+ margin-top: -5vh;
+ }
+ .contentWrapper {
+ justify-content: center;
+ flex: 1 1 0%;
+ min-height: 0;
+ }
+ .bottomContent {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+ .title {
+ font-size: 22px;
+ }
+ .description {
+ font-size: 15px;
+ }
+ .button {
+ font-size: 17px;
+ padding: 14px 0;
+ }
+}
+
+/* iPad и планшеты */
+@media (min-width: 600px) and (max-width: 1100px) {
+ .imageBlock {
+ min-height: 320px;
+ }
+ .image {
+ margin-top: -25vh;
+ }
+ .contentWrapper {
+ padding-top: 16px;
+ }
+}
+
+@media (max-height: 667px) {
+ .imageBlock {
+ max-height: none;
+ min-height: 100px;
+ }
+ .image {
+ max-height: none;
+ margin-top: -10vh;
+ }
+ .title {
+ font-size: 20px;
+ margin-bottom: 12px;
+ }
+ .description {
+ font-size: 14px;
+ }
+}
+
+@media (max-height: 568px) {
+ .imageBlock {
+ max-height: none;
+ min-height: 80px;
+ }
+ .image {
+ max-height: none;
+ margin-top: -12vh;
+ }
+ .textContent {
+ padding-bottom: 8px;
+ }
+ .bottomContent {
+ margin-top: 8px;
+ }
+}
diff --git a/src/screens/onboarding/OnboardingWelcome.tsx b/src/screens/onboarding/OnboardingWelcome.tsx
index 992c24e..906ac10 100644
--- a/src/screens/onboarding/OnboardingWelcome.tsx
+++ b/src/screens/onboarding/OnboardingWelcome.tsx
@@ -1,16 +1,15 @@
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
-import OnboardingLayout from '../../components/shared/OnboardingLayout';
import { images } from '../../assets';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getCurrentUserId } from '../../constants/user';
import { getTranslation } from '../../constants/translations';
+import styles from './OnboardingWelcome.module.css';
const OnboardingWelcome: React.FC = () => {
const navigate = useNavigate();
useEffect(() => {
- // Отслеживаем событие открытия экрана приветствия
customAnalyticsService.trackEvent({
telegram_id: getCurrentUserId(),
event_category: 'navigation',
@@ -19,40 +18,47 @@ const OnboardingWelcome: React.FC = () => {
}, []);
const handleNext = () => {
- // Отслеживаем событие нажатия кнопки "Далее"
customAnalyticsService.trackEvent({
telegram_id: getCurrentUserId(),
event_category: 'onboarding',
event_name: 'welcome_next_click'
});
-
navigate('/onboarding/how-to');
};
- const handleSkip = () => {
- // Отслеживаем событие нажатия кнопки "Пропустить"
- customAnalyticsService.trackEvent({
- telegram_id: getCurrentUserId(),
- event_category: 'onboarding',
- event_name: 'welcome_skip_click'
- });
-
- localStorage.setItem('hasSeenOnboarding', 'true');
- navigate('/');
- };
-
return (
-
+
+
+

+
+
+
+
+ Добро пожаловать
в Sticker Generator
+
+
+ Создавайте уникальные стикеры
+ из фотографий с помощью
+ искусственного интеллекта
+
+
+
+
+
);
};
diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts
index 89f6b9e..c2a7b12 100644
--- a/src/services/paymentService.ts
+++ b/src/services/paymentService.ts
@@ -3,6 +3,15 @@ import apiService from '../services/api';
import { getCurrentUserId } from '../constants/user';
import customAnalyticsService from './customAnalyticsService';
+// Словарь для маппинга названий пакетов к значениям description для API
+const packTitleToDescription: Record
= {
+ 'Стартовый набор': 'стартовый набор',
+ 'Стикерный энтузиаст': 'стикерный энтузиаст',
+ 'Стикерный запас': 'стикерный запас',
+ 'Стикерный магнат': 'стикерный магнат',
+ 'Бог стикеров': 'бог стикеров'
+};
+
export const paymentService = {
showBuyTokensPopup: async (pack: TokenPack, onSuccess?: (userData?: any) => void) => {
// Проверяем наличие Telegram WebApp
@@ -66,5 +75,76 @@ export const paymentService = {
console.error('Ошибка при создании инвойса:', error);
webApp.showAlert('Произошла ошибка при создании платежа. Пожалуйста, попробуйте позже.');
}
+ },
+
+ // Метод для оплаты рублями
+ showRubPaymentPopup: async (packTitle: string, onSuccess?: () => void) => {
+ // Проверяем наличие Telegram WebApp
+ if (!window.Telegram?.WebApp) {
+ console.error('Telegram WebApp не доступен');
+ return;
+ }
+
+ const webApp = window.Telegram.WebApp;
+ const userId = getCurrentUserId();
+
+ try {
+ // Получаем правильное описание пакета для API
+ const description = packTitleToDescription[packTitle] || packTitle.toLowerCase();
+
+ // Подготавливаем данные для запроса
+ const requestData = {
+ user_id: userId,
+ description: description
+ };
+
+ // Логируем данные запроса
+ console.log('Отправка запроса на создание платежа:');
+ console.log('URL:', 'https://stickerserver.gymnasticstuff.uk/create-payment');
+ console.log('Метод:', 'POST');
+ console.log('Заголовки:', { 'Content-Type': 'application/json' });
+ console.log('Тело запроса:', JSON.stringify(requestData, null, 2));
+
+ // Запрос к API для получения платежной ссылки
+ const response = await fetch('https://stickerserver.gymnasticstuff.uk/create-payment', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(requestData)
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to create payment');
+ }
+
+ const data = await response.json();
+
+ // Логируем ответ сервера
+ console.log('Ответ сервера:', {
+ status: response.status,
+ statusText: response.statusText,
+ data: data
+ });
+
+ const paymentUrl = data.payment_url;
+
+ // Отслеживаем событие начала оплаты
+ customAnalyticsService.trackEvent({
+ telegram_id: userId,
+ event_category: 'payment',
+ event_name: 'rub_payment_start',
+ unit: packTitle
+ });
+
+ // Открываем ссылку в браузере Telegram
+ webApp.openLink(paymentUrl);
+
+ // Вызываем callback успеха, если он предоставлен
+ if (onSuccess) {
+ onSuccess();
+ }
+ } catch (error) {
+ console.error('Ошибка при создании платежа:', error);
+ webApp.showAlert('Произошла ошибка при создании платежа. Пожалуйста, попробуйте позже.');
+ }
}
};
diff --git a/vite.config.ts b/vite.config.ts
index b689d5b..f1d69fa 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -4,6 +4,11 @@ import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
+ server: {
+ host: '0.0.0.0', // Делаем сервер доступным по локальной сети
+ port: 5173, // Фиксируем порт
+ strictPort: true, // Не пытаемся использовать другой порт, если 5173 занят
+ },
css: {
modules: {
localsConvention: 'camelCase',
diff --git a/Редактор_стикеров_план_реализации.md b/Редактор_стикеров_план_реализации.md
new file mode 100644
index 0000000..c8411aa
--- /dev/null
+++ b/Редактор_стикеров_план_реализации.md
@@ -0,0 +1,91 @@
+# План внедрения редактора стикеров для Telegram Mini App
+
+## 1. Кнопка "Редактировать" и роутинг
+
+- Добавить кнопку/иконку "Редактировать" на каждый стикер в галерее.
+- При нажатии — переход на экран `/edit/:stickerId`, где stickerId — идентификатор стикера (или file_id).
+- На экран редактора передаётся webp-стикер 512x512 с прозрачным фоном.
+
+---
+
+## 2. Экран редактора (MVP)
+
+- **Технология:** React + [react-konva](https://konvajs.org/docs/react/index.html) (canvas-редактирование, поддержка мобильных жестов, drag-n-drop, масштабирование, поворот).
+- **Холст:** фиксированный размер 512x512, webp-стикер как прозрачный фон.
+- **Инструменты:**
+ - Масштабирование и перемещение самого стикера (фонового слоя).
+ - Добавление текста (выбор шрифта из выпадающего списка, цвета из палитры, размер, позиция, поворот, обводка).
+ - Добавление PNG/эмодзи (drag-n-drop, resize, rotate, перемещение).
+ - Удаление объектов.
+- **История изменений:** стек undo/redo (реализуется через массив состояний редактора, можно использовать useReducer или отдельную библиотеку типа [use-undo](https://github.com/aaronpowell/react-use-undo)).
+- **UI:** крупные кнопки, панель инструментов снизу/сбоку, адаптация под мобильные (touch-жесты).
+
+---
+
+## 3. Сохранение результата
+
+- Экспорт canvas в webp 512x512 (react-konva поддерживает toDataURL с нужным форматом и размером).
+- Модальное окно подтверждения сохранения (в стиле NotificationModal, как в проекте).
+- Отправка изображения на сервер через ваш эндпоинт (с телеграм id).
+- После успешного сохранения — возврат в галерею (или показ уведомления).
+
+---
+
+## 4. Расширяемость и удобство
+
+- **Шрифты:**
+ - Список шрифтов хранить в конфиге (например, src/config/editorFonts.ts).
+ - Добавлять новые шрифты — просто дописывать в конфиг и подключать через Google Fonts.
+- **Палитра цветов:**
+ - Использовать готовый компонент color picker (например, [react-colorful](https://omgovich.github.io/react-colorful/)), палитру можно расширять.
+- **Набор эмодзи/картинок:**
+ - Хранить в src/assets/editor-stickers/ (SVG/PNG/WebP).
+ - Добавлять новые — просто копировать в папку и прописывать в конфиге.
+- **Предупреждение о несохранённых изменениях:**
+ - При попытке выхода из редактора без сохранения — показывать модальное окно с подтверждением.
+
+---
+
+## UX-детали
+
+- Пользователь всегда видит результат редактирования в реальном времени.
+- Все действия (масштабирование, перемещение, поворот) доступны для любого объекта, включая сам стикер.
+- История изменений (undo/redo) работает в рамках одной сессии редактирования.
+- Сохранение всегда в формате webp 512x512 с прозрачным фоном.
+- Мобильная адаптация — приоритет (крупные кнопки, поддержка touch-жестов).
+
+---
+
+## Сложность и сроки
+
+- MVP (редактирование, undo/redo, сохранение, мобильная адаптация): 2-3 недели.
+- Добавление новых шрифтов/эмодзи — не требует изменений в коде, только в конфиге/папке.
+- UI/UX — минимальные риски для остального приложения, редактор полностью изолирован.
+
+---
+
+## Вопросы и ответы
+
+- **Ограничение на количество объектов:** не требуется.
+- **Шрифты:** список в конфиге, легко расширять.
+- **Цвета:** палитра, легко расширять.
+- **Предпросмотр:** не нужен, пользователь видит результат сразу.
+- **Модальное окно подтверждения:** обязательно, в стиле NotificationModal.
+- **Набор эмодзи/картинок:** система через папку и конфиг, легко расширять.
+- **Предупреждение о несохранённых изменениях:** обязательно.
+
+---
+
+## Примерная структура компонентов
+
+- GallerySticker (с кнопкой "Редактировать")
+- StickerEditorScreen (отдельный экран)
+ - EditorCanvas (react-konva)
+ - Toolbar (инструменты: текст, эмодзи, undo/redo, сохранить)
+ - ColorPicker, FontPicker, EmojiPicker (модальные/панельные компоненты)
+ - ConfirmModal (подтверждение сохранения/выхода)
+
+---
+
+**Резюме:**
+Редактор внедряется как отдельный экран, не затрагивает остальной функционал. Использование react-konva обеспечивает мобильную совместимость и нужный функционал. Все ресурсы (шрифты, эмодзи) легко расширяемы через конфиг/папку. UX — максимально близок к мобильным привычкам.