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

433 lines
15 KiB
Markdown

# Dating App Frontend — Claude Code Master Prompt
## Context & Orientation
You are building the complete frontend for a dating application called **Dating**. The backend is already implemented. The generated API client lives at `src/api/api.ts` — use it as the source of truth for all endpoint shapes, DTOs, and types. Do not re-declare types that already exist in the API client; import them.
Project root: `C:\MyApps\dating-app-frontend`
Package manager: **pnpm**
Stack: **Vue 3 (Composition API + `<script setup>`) + Vite + Tauri v2**
The app must work in three contexts simultaneously:
1. Browser (dev/PWA) — desktop viewport
2. Browser — mobile viewport (responsive, ≥ 375 px)
3. Tauri desktop window (Windows / macOS / Linux)
---
All user-facing text (button labels, placeholders, error messages,
toast notifications, empty states, navigation items, form hints,
section headings) must be written in Russian.
Brand name, logo, and any decorative/editorial text remain in English.
---
## Design Skills — Read Before Writing a Single Line
Before writing any code, internalize these two skill files in full:
```
.claude/skills/impeccable/SKILL.md (+ all 7 files in reference/)
.claude/skills/taste-skill/SKILL.md
```
Apply **Impeccable** design principles throughout every component.
Apply **Taste-skill** with these parameters:
- `DESIGN_VARIANCE: 8`
- `MOTION_INTENSITY: 6`
- `VISUAL_DENSITY: 4`
### Aesthetic Direction
The visual language is **"warm brutalism meets editorial intimacy"**:
- Dark base (`#0d0d0d`) with warm cream (`#f0ebe0`) as the primary text/accent surface
- A single vivid signal color — deep terracotta `#c45c3a` — used sparingly for CTAs, likes, active states
- Typography: pair **`Instrument Serif`** (display, headers, profile names) with **`DM Mono`** (UI labels, metadata, counts). Load both from Google Fonts.
- Layouts that feel editorial — asymmetric cards, overlapping text on images, offset grids
- Micro-interactions via CSS transitions + Vue `<Transition>` + GSAP (install it). Nothing gratuitous — motion serves information hierarchy.
- Grain texture overlay (SVG `<feTurbulence>`) on the app shell background for tactility
- NO: purple gradients, Inter/Roboto, glassmorphism clichés, floating hearts, soft pastels, rounded-rectangle cards that look like every other dating app
This should feel like an independent editorial magazine for human connection — not Tinder, not Bumble.
---
## Project Bootstrap
```bash
# If scaffolding from scratch:
pnpm create tauri-app@latest dating-app-frontend --template vue-ts
cd dating-app-frontend
# Core dependencies
pnpm add vue-router@4 pinia @vueuse/core axios gsap @vuelidate/core @vuelidate/validators
# UI primitives (no opinionated component library — build custom)
pnpm add @floating-ui/vue
# Dev
pnpm add -D sass tailwindcss @tailwindcss/vite autoprefixer
```
Configure Tailwind in **CSS-only mode** (no JIT utility classes in templates — use scoped SCSS instead; Tailwind is for reset and baseline only).
---
## File & Folder Architecture
```
src/
├── api/
│ └── api.ts # ← generated, do not touch
├── assets/
│ ├── fonts.css # @font-face / Google Fonts import
│ └── grain.svg # noise texture
├── composables/
│ ├── useAuth.ts
│ ├── useFeed.ts
│ ├── useChat.ts
│ ├── useMedia.ts
│ └── useGeolocation.ts
├── stores/
│ ├── auth.store.ts # Pinia
│ ├── profile.store.ts
│ ├── feed.store.ts
│ ├── chat.store.ts
│ └── ui.store.ts
├── router/
│ └── index.ts # vue-router with guards
├── views/
│ ├── auth/
│ │ ├── LoginView.vue
│ │ └── RegisterView.vue
│ ├── onboarding/
│ │ └── ProfileSetupView.vue
│ ├── feed/
│ │ └── FeedView.vue
│ ├── matches/
│ │ └── MatchesView.vue
│ ├── chat/
│ │ ├── ChatsListView.vue
│ │ └── ChatRoomView.vue
│ ├── dates/
│ │ └── DatesView.vue
│ ├── profile/
│ │ ├── MyProfileView.vue
│ │ └── ProfileDetailView.vue
│ └── admin/
│ └── ReportsView.vue
├── components/
│ ├── layout/
│ │ ├── AppShell.vue # grain bg, Tauri titlebar, nav
│ │ ├── BottomNav.vue # mobile navigation
│ │ ├── SideNav.vue # desktop sidebar navigation
│ │ └── TauriTitlebar.vue # custom Tauri drag region
│ ├── feed/
│ │ ├── FeedCard.vue # the main profile card
│ │ ├── FeedCardStack.vue # swipe stack with GSAP
│ │ ├── FeedFilters.vue # filter drawer/panel
│ │ └── LikeButton.vue
│ ├── profile/
│ │ ├── ProfileAvatar.vue
│ │ ├── ProfileBadge.vue # tags rendered as editorial labels
│ │ ├── ProfileEditor.vue # form for create/edit profile
│ │ └── MediaGallery.vue
│ ├── chat/
│ │ ├── ChatBubble.vue
│ │ ├── ChatInput.vue # text + media attach
│ │ ├── VoiceRecorder.vue
│ │ └── MediaMessage.vue
│ ├── dates/
│ │ ├── DateCard.vue
│ │ ├── DateProposalForm.vue
│ │ └── MapPicker.vue # Leaflet for lat/lng picking
│ ├── common/
│ │ ├── AppButton.vue
│ │ ├── AppInput.vue
│ │ ├── AppModal.vue
│ │ ├── AppDrawer.vue # bottom sheet on mobile, side panel on desktop
│ │ ├── AppToast.vue
│ │ ├── LoadingSpinner.vue
│ │ └── EmptyState.vue
│ └── reports/
│ └── ReportModal.vue
└── styles/
├── _variables.scss
├── _typography.scss
├── _animations.scss
└── main.scss
```
---
## API Integration — Full Coverage
Implement every endpoint from `src/api/api.ts`. Below is the complete feature map:
### Auth (`/api/v1/auth/`)
- `POST /register` — registration form with phone + password, validation via Vuelidate
- `POST /login` — login form
- `POST /logout` — clear store + redirect
- `POST /refresh` — silent token refresh interceptor in Axios (auto-retry once on 401)
- `POST /fcm-token` — call after login (stub FCM token for web; use Tauri plugin for desktop)
Store access token in memory (Pinia), refresh token in `localStorage`. Never store access token in `localStorage`.
### Users (`/api/v1/users/`)
- `GET /me` — populate auth store on app load
- `GET /:id` — view any user (admin use)
- `PATCH /:id/ban` and `/activate` — admin panel actions
### Profiles (`/api/v1/profiles/`)
- `POST /` — profile creation wizard (multi-step: basic info → location → tags → photo)
- `GET /my` — list own profiles in MyProfileView
- `GET /:profileId` — public profile detail page
- `PUT /:profileId` — profile editor
- `DELETE /:profileId` — with confirmation modal
### Media (`/api/v1/profiles/:profileId/media/`)
- `POST /upload?type=` — drag-and-drop + file picker; show upload progress bar
- `GET /` — grid gallery with lightbox
- `DELETE /:mediaId` — with optimistic UI removal
### Feed (`/api/v1/feed`)
- Infinite scroll OR card-stack swipe (implement BOTH modes, togglable via UI switch)
- Filter panel: cityId, districtId, ageMin/ageMax, keyword, tagIds (multi-select chips)
- Show "search paused" banner when match limit is exceeded (detect via API response or a dedicated flag)
### Likes (`/api/v1/likes`)
- `POST /` — like/dislike with swipe gesture (GSAP drag) or button
- `GET /matches?profileId=` — matches list with match animation (confetti-lite or editorial flash)
### Chat (`/api/v1/chats`)
- `POST /` — open chat from a match card
- `GET /?profileId=` — chats list; lock icon on inactive chats (only one active at a time)
- `GET /:chatId/messages?profileId=` — message history with virtual scroll
- `POST /:chatId/messages?profileId=` — send text/media
- `DELETE /:chatId?profileId=` — close chat with confirmation
- Implement **polling** (2 s interval) for new messages; note in code where WebSocket would replace this
### Dates (`/api/v1/dates`)
- `POST /` — proposal form: map picker (Leaflet) for lat/lng, datetime picker, optional statusId
- `GET /?profileId=` — list with status chips
- `PATCH /:id/status?profileId=` — accept / decline / complete actions
- `GET /statuses` — fetch on mount, cache in Pinia
### Reports (`/api/v1/reports`)
- `POST /``ReportModal` triggered from profile or chat bubble context menu
- `GET /` — admin table view with pagination
### Tags, Cities, Greetings
- `GET /tags`, `GET /cities`, `GET /cities/:cityId/districts`, `GET /greetings` — fetch on app init, cache in Pinia
- Admin: create/delete tags, cities, districts, greetings
---
## Component Implementation Standards
### FeedCard.vue
The centerpiece. Execute this with exceptional care:
- Full-bleed image background
- Profile name in large Instrument Serif, overlapping bottom of image
- Age, city, tags as DM Mono micro-labels
- Drag-to-like: GSAP Draggable on desktop; touch events on mobile
- Color bleed on drag direction: terracotta tint for right (like), cool grey tint for left (dislike)
- Discard animation: card flies off screen with rotation, next card scales up from behind
- Tap to expand into ProfileDetailView
### ChatRoomView.vue
- Messages grouped by date separator
- Own messages: right-aligned, cream background
- Partner messages: left-aligned, dark card with subtle border
- Voice messages: custom waveform player (Web Audio API for visualization)
- Video messages: inline `<video>` with poster frame
- Photos: open in lightbox modal
- Locked chat state: blurred message list + overlay "You have X other open chats. Close one to unlock."
- Input bar: text area auto-grows, attach button opens media type picker
### AppShell.vue
- Desktop: left sidebar (64px collapsed / 240px expanded) + main content area
- Mobile: top header + bottom nav (5 items)
- Tauri: custom titlebar with drag region, traffic lights on macOS (use `data-tauri-drag-region`)
- Grain SVG filter applied as a pseudo-element over the entire shell
### Responsive breakpoints
```scss
$mobile: 375px;
$tablet: 768px;
$desktop: 1024px;
$wide: 1440px;
```
Use `@container` queries inside card components where possible.
---
## Routing & Guards
```
/ → redirect → /feed (if authed) or /login
/login
/register
/setup → profile creation wizard (redirect here if user has no profiles)
/feed
/matches
/chats
/chats/:chatId
/dates
/profile/me
/profile/:profileId → public view
/admin/reports → role guard (admin only)
```
Navigation guard: check auth store on every route change. Refresh token silently if access token expired.
---
## State Management (Pinia)
### `auth.store.ts`
```ts
state: { user, accessToken, profiles, activeProfileId }
actions: login, logout, register, refreshToken, setActiveProfile
```
### `feed.store.ts`
```ts
state: { cards[], filters, page, hasMore, searchPaused }
actions: fetchNextPage, applyFilters, likeCard, dislikeCard
```
### `chat.store.ts`
```ts
state: { chats[], activeChat, messages[], polling: NodeJS.Timer | null }
actions: openChat, closeChat, sendMessage, fetchMessages, startPolling, stopPolling
```
---
## Tauri-Specific
- Use `@tauri-apps/api/window` for window controls in TauriTitlebar.vue
- Detect Tauri context: `window.__TAURI__` exists → show custom titlebar, hide browser-nav elements
- File upload: use Tauri `dialog.open()` for native file picker; fall back to `<input type="file">` in browser
- Deep link handling skeleton (for future push notification routing)
---
## CSS Architecture
`src/styles/main.scss` imports in order:
1. `_variables.scss` — all CSS custom properties
2. `_typography.scss` — font definitions, heading scale
3. `_animations.scss` — keyframes, transition utilities
4. Tailwind base/reset
```scss
// _variables.scss
:root {
--color-base: #0d0d0d;
--color-surface: #161614;
--color-surface-2: #1e1e1b;
--color-cream: #f0ebe0;
--color-muted: #6b6860;
--color-signal: #c45c3a;
--color-signal-dim: #7a3822;
--color-border: rgba(240, 235, 224, 0.08);
--font-display: 'Instrument Serif', Georgia, serif;
--font-mono: 'DM Mono', 'Courier New', monospace;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--shadow-card: 0 2px 24px rgba(0, 0, 0, 0.6);
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-base: 280ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-spring: 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
```
---
## Quality Bar
Every component must meet these standards before considered done:
- [ ] Works in Chrome mobile emulation (375px) without horizontal scroll
- [ ] Works in desktop browser at 1280px+
- [ ] Works in Tauri window
- [ ] Loading states: skeleton loaders (not spinners) for feed and chat history
- [ ] Empty states: custom illustrated SVG + copy (no generic "Nothing here yet")
- [ ] Error states: inline error with retry action
- [ ] All forms validated client-side via Vuelidate before API call
- [ ] API errors surfaced as toasts (not console.error)
- [ ] Keyboard navigable (Tab order, Enter to submit, Escape to close modals)
- [ ] No `any` TypeScript types — use types from the generated API client
- [ ] GSAP animations respect `prefers-reduced-motion` (wrap in `matchMedia` check)
---
## Implementation Order (follow strictly)
1. Project scaffold + Tauri config + pnpm install
2. Styles: variables, fonts, grain texture, AppShell skeleton
3. Auth stores + Login/Register views + router guards
4. API interceptor (Axios instance with token refresh)
5. Pinia stores: auth, profile, ui
6. Profile setup wizard (onboarding)
7. Feed: store, FeedCard, FeedCardStack with swipe
8. Feed filters drawer
9. Matches view
10. Chat: store (with polling), ChatsListView, ChatRoomView, media messages
11. Dates: form with Leaflet map, list, status updates
12. My Profile view + editor + media gallery
13. Public profile view
14. Reports modal
15. Admin views (reports table, tags/cities management)
16. Tauri titlebar + native file picker integration
17. Polish pass: animations, transitions, empty states, error states
18. `pnpm tauri build` smoke test
---
## Commands Reference
```bash
pnpm dev # Vite dev server (browser)
pnpm tauri dev # Tauri dev window
pnpm build # Vite build
pnpm tauri build # Tauri production build
```
---
## Final Note
Do not scaffold placeholder components. Every component you create must be fully implemented — real logic, real styles, real API calls. If a feature is complex, implement it incrementally but completely within each file. The goal is a shippable, visually distinctive dating app that feels nothing like a template.
Start with step 1 and work sequentially. Announce each step as you begin it.