Files
dating-app-frontend/README.md
2026-06-08 15:09:53 +03:00

13 KiB
Raw Permalink Blame History

Dating — Frontend

Vue 3 + Vite + Tauri v2. Работает как PWA в браузере и как нативное десктопное приложение (Windows / macOS / Linux).

Стек

Слой Технология
UI framework Vue 3 (Composition API, <script setup>)
Build tool Vite 6
Desktop shell Tauri v2
State management Composables
Routing Vue Router 4
HTTP client Axios (сгенерированный клиент src/api/api.ts)
Форм-валидация Vuelidate
Анимации GSAP 3
Карты Leaflet
Утилиты VueUse
CSS SCSS + Tailwind v4 (reset-only)
Package manager pnpm
TypeScript строгий режим, no any

Быстрый старт

Зависимости

  • Node.js ≥ 20
  • pnpm ≥ 9 (npm i -g pnpm)
  • Rust + cargo (для Tauri: rustup.rs)
  • Tauri CLI устанавливается через pnpm как dev-зависимость
# Клонировать репо
git clone <repo-url>
cd dating-app-frontend

# Установить зависимости
pnpm install

# Запустить в браузере (порт 3000) (old 1420)
pnpm dev

# Запустить с Tauri (нативное окно)
pnpm tauri dev

Переменные окружения

Создайте .env.local в корне:

VITE_API_BASE_URL=http://localhost:3000

По умолчанию фоллбэк — http://localhost:3000.


Структура проекта

dating-app-frontend/
├── src/
│   ├── api/
│   │   ├── api.ts          # Сгенерированный клиент (не трогать)
│   │   └── client.ts       # Axios-инстанс + интерцептор refresh-токена
│   ├── assets/
│   │   └── grain.svg       # SVG-текстура шума для фона
│   ├── components/
│   │   ├── common/         # AppButton, AppInput, AppModal, AppDrawer,
│   │   │                   # AppToast, LoadingSpinner, EmptyState
│   │   ├── layout/         # AppShell, SideNav, BottomNav, TauriTitlebar
│   │   ├── feed/           # FeedCard, FeedCardStack, FeedFilters, ProfileBadge
│   │   ├── chat/           # ChatBubble, ChatInput, VoiceRecorder, MediaMessage
│   │   ├── profile/        # ProfileEditor, MediaGallery
│   │   ├── dates/          # MapPicker, DateProposalForm
│   │   └── reports/        # ReportModal
│   ├── composables/        # useAuth, useFeed, useChat, useMedia, useGeolocation
│   ├── router/
│   │   └── index.ts        # Маршруты + guards + восстановление сессии
│   ├── stores/
│   │   ├── auth.store.ts   # user, accessToken (in-memory), profiles
│   │   ├── feed.store.ts   # cards, filters, pagination
│   │   ├── chat.store.ts   # chats, messages, polling
│   │   ├── profile.store.ts
│   │   └── ui.store.ts     # toasts, sidebar state, reference data (tags/cities)
│   ├── styles/
│   │   ├── _variables.scss # CSS custom properties + SCSS breakpoint-миксины
│   │   ├── _typography.scss
│   │   ├── _animations.scss
│   │   ├── main.scss       # Глобальные стили (без Tailwind)
│   │   └── tailwind.css    # @import "tailwindcss" — отдельно от SCSS
│   ├── views/              # LoginView, RegisterView, ProfileSetupView,
│   │                       # FeedView, MatchesView, ChatsListView, ChatRoomView,
│   │                       # DatesView, MyProfileView, ProfileDetailView,
│   │                       # ReportsView (admin)
│   ├── App.vue
│   ├── main.ts
│   └── vite-env.d.ts
├── src-tauri/
│   ├── src/
│   │   ├── lib.rs          # Tauri plugins init
│   │   └── main.rs         # Windows entry (no console window)
│   ├── capabilities/
│   │   └── default.json    # Tauri permissions
│   ├── Cargo.toml
│   ├── build.rs
│   └── tauri.conf.json
├── PRODUCT.md              # Стратегический документ для AI-ассистентов
├── vite.config.ts
├── tsconfig.json
└── package.json

Маршрутизация

Путь Компонент Guard
/ redirect → /feed
/login LoginView guest only
/register RegisterView guest only
/setup ProfileSetupView auth
/feed FeedView auth
/matches MatchesView auth
/chats ChatsListView auth
/chats/:chatId ChatRoomView auth
/dates DatesView auth
/profile/me MyProfileView auth
/profile/:profileId ProfileDetailView auth
/admin/reports ReportsView auth + admin

Логика guard:

  1. При первой навигации пытается восстановить сессию через refreshToken из localStorage.
  2. Если пользователь авторизован, но профилей нет — редирект на /setup.
  3. Если токен истёк — silent refresh через POST /api/v1/auth/refresh.

Работа с токенами

Access token  → хранится только в памяти (Pinia store / переменная модуля)
Refresh token → localStorage ('refreshToken')

src/api/client.ts содержит Axios response interceptor: при получении 401 пытается один раз обновить токен. Если не удаётся — чистит сессию и редиректит на /login. Параллельные запросы ставятся в очередь и резолвятся с новым токеном.


Управление состоянием

auth.store

state: { user, accessToken (in-memory ref), activeProfileId }
actions: login, logout, register, fetchMe, setActiveProfile,
         addProfile, updateProfile, removeProfile

feed.store

state: { cards[], filters, page, hasMore, searchPaused, loading }
actions: fetchNextPage(profileId), applyFilters(filters), removeCard(profileId)

chat.store

state: { chats[], activeChat, messages[], pollingTimer }
actions: fetchChats, fetchMessages, sendMessage, openChat, closeChat,
         startPolling, stopPolling
// Polling интервал: 2000 мс. Место замены на WebSocket помечено комментарием.

ui.store

state: { toasts[], sidebarExpanded, tags[], cities[], districts{}, greetings[] }
actions: addToast(message, type, duration), removeToast(id), setTags/Cities/...

SCSS-архитектура

Tailwind v4 импортируется отдельно от SCSS:

// main.ts — порядок важен
import '@/styles/tailwind.css' // @import "tailwindcss"
import '@/styles/main.scss' // custom styles

Каждый SCSS-парциал (_typography.scss, _animations.scss) включает в начале:

@use 'variables' as *;

Это нужно, так как Sass module system (@use) имеет пофайловую область видимости — миксины не наследуются транзитивно.

Компоненты Vue используют <style scoped lang="scss">. Vite через additionalData в vite.config.ts автоматически инжектирует @use "@/styles/_variables.scss" as *; во все <style lang="scss"> блоки компонентов (кроме самих файлов стилей, чтобы избежать цикличности).

Дизайн-токены (CSS custom properties)

--color-base:
  #0d0d0d // фон приложения
  --color-surface: #161614 // поверхность карточек
  --color-surface-2: #1e1e1b // инпуты, вторичные поверхности
  --color-cream: #f0ebe0 // основной текст
  --color-muted: #6b6860 // второстепенный текст
  --color-signal: #c45c3a // акцент (CTA, лайк, активный стейт)
  --font-display: 'Instrument Serif',
  serif --font-mono: 'DM Mono', monospace;

Шрифты загружаются из Google Fonts в index.html.


Tauri

Обнаружение среды

const isTauri = typeof window !== 'undefined' && !!window.__TAURI__

Кастомный тайтлбар

TauriTitlebar.vue рендерится только при isTauri === true. Контейнер AppShell.vue использует data-tauri-drag-region для нативного drag окна.

Файловый диалог

В ChatInput.vue и MediaGallery.vue при isTauri используется @tauri-apps/plugin-dialog (open()), иначе — стандартный <input type="file">.

Конфигурация окна

src-tauri/tauri.conf.json:

  • decorations: false — убирает системный тайтлбар
  • minWidth: 375 — соответствует мобильному брейкпойнту
  • Permissions: dialog:allow-open, dialog:allow-save, shell:allow-open

API-клиент

src/api/api.ts — сгенерированный файл (swagger-typescript-api). Не редактировать вручную.

src/api/client.ts — обёртка:

import { apiClient } from '@/api/client'

// Пример вызова
const feed = await apiClient.api.feedControllerGetFeed({
  profileId: 'xxx',
  page: 1,
  limit: 20,
})

Все методы возвращают Promise<void> по типам (ограничение генератора) — кастуйте через as unknown as YourType.


Скрипты

pnpm dev              # Vite dev server, порт 3000
pnpm build            # vue-tsc + vite build → dist/
pnpm preview          # Превью prod-сборки
pnpm tauri dev        # Tauri dev window (запускает pnpm dev внутри)
pnpm tauri build      # Нативный инсталлятор → src-tauri/target/release/bundle/

Разработка компонентов

Новый view

  1. Создать файл в src/views/<section>/.
  2. Добавить маршрут в src/router/index.ts с нужными meta (auth, admin).
  3. Лениво импортировать: component: () => import('@/views/...').

Новый компонент

  • <script setup lang="ts"> — обязательно.
  • <style scoped lang="scss"> — изолированные стили.
  • Никаких any — типы из src/api/api.ts или кастомные интерфейсы в сторе/компоненте.
  • Ошибки API — через uiStore.addToast(message, 'error'), не console.error.
  • Скелетоны вместо спиннеров для контента с историей (лента, чат, список).

Анимации

GSAP используется в FeedCard.vue для drag-механики. Все GSAP-вызовы оборачивайте в проверку:

const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
if (!prefersReducedMotion) {
  gsap.to(el, { ... })
}

Известные ограничения

  • Типы API: генератор возвращает Promise<void> для всех методов. Реальные типы ответов приходится кастовать через as unknown as T. При регенерации клиента это сохраняется.
  • Polling: чат поллит каждые 2 секунды. Место замены на WebSocket отмечено комментарием // TODO: replace with WebSocket subscription в chat.store.ts.
  • FCM: POST /api/v1/auth/fcm-token вызывается как заглушка. Реальная интеграция требует Tauri-плагина для push-уведомлений.
  • Upload: mediaControllerUpload в MediaGallery.vue отправляет запрос без тела — нужно доработать под реальный multipart/form-data с файлом.