Skip to content

Cabinet: Favorites + Settings

Scope

Two features for the personal cabinet:

  1. Favorites — API integration, UI buttons, favorites page
  2. Settings — profile editing, avatar, password, sessions, business profile

1. Favorites

Store refactoring (app/stores/favorites.ts)

Current store holds only Set<number> locally. Refactor to:

  • Initialization: in app/plugins/auth.ts after fetchUser(), if authenticated — call GET /vendor/favorites, save IDs to Set and full product data to Map<number, Product>
  • toggle(productId): optimistic UI — update Set immediately, then POST /vendor/favorites or DELETE /vendor/favorites/{product_id}. On error — rollback
  • items: computed from Map values for the favorites page

API calls

GET    /vendor/favorites              → load all favorites (Product[])
POST   /vendor/favorites              → { product_id } — add
DELETE /vendor/favorites/{product_id} → remove

FavoriteButton component

Location: app/features/favorites/ui/FavoriteButton.vue

  • Props: productId: number
  • Renders heart icon (outline/filled)
  • v-if="isAuthenticated" — hidden for guests
  • On click: favoritesStore.toggle(productId)
  • Scale transition animation on toggle

Integration points

  • ProductCard.vue — add FavoriteButton in top-right corner
  • /product/[id].vue — add next to price

Favorites page (/cabinet/favorites)

  • Header — "Избранное" + counter ("12 товаров")
  • Grid — same ProductCard as catalog. Grid: 1 col mobile, 2 on sm, 3 on lg
  • Empty state — heart icon + "Вы пока ничего не добавили в избранное" + "Перейти в каталог" button
  • Data from favoritesStore.items (loaded on init)
  • On unfavorite — card disappears immediately (optimistic)

2. Settings

Routing

/cabinet/settings           → redirect to /cabinet/settings/profile
/cabinet/settings/profile   → profile (name, phone, avatar, geo)
/cabinet/settings/security  → password + active sessions
/cabinet/settings/business  → business profile (if account_type === 'business')

Horizontal tabs (UTabs) at top of settings area for switching between profile / security / business. "Бизнес" tab visible only when account_type === 'business'.

File structure

app/pages/cabinet/settings/
├── index.vue           → redirect to profile
├── profile.vue
├── security.vue
└── business.vue

app/features/cabinet-settings/
├── composables/
│   ├── useProfileForm.ts
│   ├── useSecurityForm.ts
│   └── useBusinessForm.ts
└── ui/
    └── SettingsTabs.vue    → shared tabs navigation

Profile page (/cabinet/settings/profile)

Fields:

  • Avatar — current photo + "Изменить" button. Upload via POST /vendor/me/avatar (max 5MB). Local preview before upload
  • Display name (display_name) — text input, required
  • Phone (phone) — text input with mask
  • Geo — GeoSelect cascade (region → city → district → metro). Reuse existing GeoSelect from features/geo-select
  • Account type (account_type) — URadioGroup: «Частное лицо» / «Бизнес». When switched to 'business', "Бизнес" tab appears in settings tabs. Saved via PUT /vendor/me along with other profile fields

Behavior:

  • Load data: GET /vendor/me on mount
  • Save: PUT /vendor/me — "Сохранить" button, toast on success
  • Validation: Zod schema, backend errors via useApiError
  • Avatar uploaded separately, updates authStore.user.avatar_url

Security page (/cabinet/settings/security)

Password change:

  • Three fields: current password, new password, confirmation
  • Validation: Zod schema matching backend rules (8-128 chars, uppercase, lowercase, digit, special char, no 3+ identical/sequential)
  • API: PUT /vendor/me with current_password + password
  • On success: toast + clear fields

Active sessions:

  • Session list: GET /vendor/sessions — list with device info, IP, last activity
  • Current session marked with "Текущая" badge
  • "Завершить" button per session: DELETE /vendor/sessions/{id}
  • "Завершить все кроме текущей" button: POST /auth/logout-all

Business profile page (/cabinet/settings/business)

Access:

  • Tab visible only when authStore.user.account_type === 'business' (switched on profile page)
  • If personal — tab hidden, direct URL access redirects to /cabinet/settings/profile

Fields:

  • Company name (company_name) — required
  • INN (inn) — text input with mask (10 or 12 digits)
  • Address (address) — text input

Behavior:

  • Data from GET /vendor/me (field business_profile)
  • Save: PUT /vendor/me with nested business_profile

Guest behavior

  • Favorite button (FavoriteButton) hidden for unauthenticated users (v-if="isAuthenticated")
  • All cabinet pages protected by auth middleware