refactor(composables): migrate stores →
composables, align with updated API
- Replace deleted Pinia stores with
module-level singleton composables
(useAuth, useChat, useFeed, useUi) — all
return reactive({...}) so
Refs auto-unwrap in both templates and
script code
- Align entire codebase with new
swagger-generated api.ts types:
· TagDto.value (was .name) — FeedCard,
FeedFilters, ProfileEditor,
ProfileSetupView, MyProfileView,
ProfileDetailView, useUi
· MediaItemDto[] / .path (was mediaUrls[],
avatarUrl) — FeedCard,
FeedView, MyProfileView,
ProfileDetailView
· ChatDto.status 'active'|'closed' (was
isActive: boolean) —
ChatRoomView, ChatsListView
· MessageDto.profileId (was senderId) —
ChatRoomView, ChatBubble
· MeResponseDto → fetchMe now calls /me +
/profiles/my in parallel
· Token refresh: res.data.data.accessToken
(nested wrapper) —
router/index.ts aligned with client.ts
interceptor
- Fix FeedCard, ChatBubble imports pointing
to deleted store files
- Fix ProfileSetupView form type to avoid
string|undefined on v-model
- Fix history.back() → window.history.back()
via goBack() helper
- Fix chat.unreadCount possibly-undefined
guard in ChatsListView
- Fix MapPicker Leaflet icon cast (as unknown
as Record<string, unknown>)
This commit is contained in:
28
README.md
28
README.md
@@ -4,21 +4,21 @@ Vue 3 + Vite + Tauri v2. Работает как PWA в браузере и ка
|
|||||||
|
|
||||||
## Стек
|
## Стек
|
||||||
|
|
||||||
| Слой | Технология |
|
| Слой | Технология |
|
||||||
|---|---|
|
|---|-------------------------------------------------|
|
||||||
| UI framework | Vue 3 (Composition API, `<script setup>`) |
|
| UI framework | Vue 3 (Composition API, `<script setup>`) |
|
||||||
| Build tool | Vite 6 |
|
| Build tool | Vite 6 |
|
||||||
| Desktop shell | Tauri v2 |
|
| Desktop shell | Tauri v2 |
|
||||||
| State management | Pinia |
|
| State management | Composables | |
|
||||||
| Routing | Vue Router 4 |
|
| Routing | Vue Router 4 |
|
||||||
| HTTP client | Axios (сгенерированный клиент `src/api/api.ts`) |
|
| HTTP client | Axios (сгенерированный клиент `src/api/api.ts`) |
|
||||||
| Форм-валидация | Vuelidate |
|
| Форм-валидация | Vuelidate |
|
||||||
| Анимации | GSAP 3 |
|
| Анимации | GSAP 3 |
|
||||||
| Карты | Leaflet |
|
| Карты | Leaflet |
|
||||||
| Утилиты | VueUse |
|
| Утилиты | VueUse |
|
||||||
| CSS | SCSS + Tailwind v4 (reset-only) |
|
| CSS | SCSS + Tailwind v4 (reset-only) |
|
||||||
| Package manager | pnpm |
|
| Package manager | pnpm |
|
||||||
| TypeScript | строгий режим, `no any` |
|
| TypeScript | строгий режим, `no any` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"build": "vue-tsc && vite build",
|
"build": "vue-tsc && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"get:api": "npx swagger-typescript-api@12.0.4 -p http://localhost:1337/api/docs-json -o ./src/api -n api.ts --axios --unwrap-response-data --extract-request-params --extract-request-body --single-http-client"
|
"gen:api": "npx swagger-typescript-api@12.0.4 -p http://localhost:1337/api/docs-json -o ./src/api -n api.ts --axios --unwrap-response-data --extract-request-params --extract-request-body --single-http-client"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/vue": "^1.1.5",
|
"@floating-ui/vue": "^1.1.5",
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
"esbuild": "^0.28.0",
|
"esbuild": "^0.28.0",
|
||||||
"gsap": "^3.12.5",
|
"gsap": "^3.12.5",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"pinia": "^2.2.2",
|
|
||||||
"vue": "^3.5.6",
|
"vue": "^3.5.6",
|
||||||
"vue-router": "^4.4.5"
|
"vue-router": "^4.4.5"
|
||||||
},
|
},
|
||||||
|
|||||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@@ -43,9 +43,6 @@ importers:
|
|||||||
leaflet:
|
leaflet:
|
||||||
specifier: ^1.9.4
|
specifier: ^1.9.4
|
||||||
version: 1.9.4
|
version: 1.9.4
|
||||||
pinia:
|
|
||||||
specifier: ^2.2.2
|
|
||||||
version: 2.3.1(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3))
|
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.6
|
specifier: ^3.5.6
|
||||||
version: 3.5.35(typescript@5.9.3)
|
version: 3.5.35(typescript@5.9.3)
|
||||||
@@ -1278,15 +1275,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
pinia@2.3.1:
|
|
||||||
resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==}
|
|
||||||
peerDependencies:
|
|
||||||
typescript: '>=4.4.4'
|
|
||||||
vue: ^2.7.0 || ^3.5.11
|
|
||||||
peerDependenciesMeta:
|
|
||||||
typescript:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
postcss-value-parser@4.2.0:
|
postcss-value-parser@4.2.0:
|
||||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||||
|
|
||||||
@@ -2347,16 +2335,6 @@ snapshots:
|
|||||||
|
|
||||||
picomatch@4.0.4: {}
|
picomatch@4.0.4: {}
|
||||||
|
|
||||||
pinia@2.3.1(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3)):
|
|
||||||
dependencies:
|
|
||||||
'@vue/devtools-api': 6.6.4
|
|
||||||
vue: 3.5.35(typescript@5.9.3)
|
|
||||||
vue-demi: 0.14.10(vue@3.5.35(typescript@5.9.3))
|
|
||||||
optionalDependencies:
|
|
||||||
typescript: 5.9.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@vue/composition-api'
|
|
||||||
|
|
||||||
postcss-value-parser@4.2.0: {}
|
postcss-value-parser@4.2.0: {}
|
||||||
|
|
||||||
postcss@8.5.15:
|
postcss@8.5.15:
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import { onMounted, onUnmounted } from 'vue';
|
import { onMounted, onUnmounted } from 'vue';
|
||||||
import AppShell from '@/components/layout/AppShell.vue';
|
import AppShell from '@/components/layout/AppShell.vue';
|
||||||
import AppToast from '@/components/common/AppToast.vue';
|
import AppToast from '@/components/common/AppToast.vue';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import type { Tag, City, Greeting } from '@/stores/ui.store';
|
import type { Tag, City, Greeting } from '@/composables/useUi';
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
async function loadReferences() {
|
async function loadReferences() {
|
||||||
if (uiStore.referencesLoaded) return;
|
if (uiStore.referencesLoaded) return;
|
||||||
|
|||||||
355
src/api/api.ts
355
src/api/api.ts
@@ -16,6 +16,13 @@ export interface RegisterDto {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TokensResponseDto {
|
||||||
|
/** @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." */
|
||||||
|
accessToken: string;
|
||||||
|
/** @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." */
|
||||||
|
refreshToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LoginDto {
|
export interface LoginDto {
|
||||||
/** @example "+79991234567" */
|
/** @example "+79991234567" */
|
||||||
phone: string;
|
phone: string;
|
||||||
@@ -23,10 +30,48 @@ export interface LoginDto {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MessageResponseDto {
|
||||||
|
/** @example "Operation successful" */
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RefreshTokenDto {
|
export interface RefreshTokenDto {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RoleDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfileSummaryDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
gender: "male" | "female";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MeResponseDto {
|
||||||
|
id: string;
|
||||||
|
phone: string;
|
||||||
|
status: "active" | "banned" | "pending";
|
||||||
|
roleId?: string | null;
|
||||||
|
tariffId?: string | null;
|
||||||
|
paymentId?: string | null;
|
||||||
|
fcmToken?: string | null;
|
||||||
|
role?: RoleDto | null;
|
||||||
|
profiles: ProfileSummaryDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserResponseDto {
|
||||||
|
id: string;
|
||||||
|
phone: string;
|
||||||
|
status: "active" | "banned" | "pending";
|
||||||
|
roleId?: string | null;
|
||||||
|
tariffId?: string | null;
|
||||||
|
paymentId?: string | null;
|
||||||
|
fcmToken?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateProfileDto {
|
export interface CreateProfileDto {
|
||||||
name: string;
|
name: string;
|
||||||
/** @example "1995-06-15" */
|
/** @example "1995-06-15" */
|
||||||
@@ -41,6 +86,52 @@ export interface CreateProfileDto {
|
|||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CityDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lat: string;
|
||||||
|
lng: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DistrictDto {
|
||||||
|
id: string;
|
||||||
|
cityId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TagDto {
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaItemDto {
|
||||||
|
id: string;
|
||||||
|
profileId: string;
|
||||||
|
path: string;
|
||||||
|
type: "photo" | "video" | "audio";
|
||||||
|
sortOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfileResponseDto {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
name: string;
|
||||||
|
/** @example "1995-06-15" */
|
||||||
|
birthDate: string;
|
||||||
|
gender: "male" | "female";
|
||||||
|
cityId?: string | null;
|
||||||
|
districtId?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
nation?: string | null;
|
||||||
|
height?: number | null;
|
||||||
|
weight?: number | null;
|
||||||
|
activeChatId?: string | null;
|
||||||
|
city?: CityDto | null;
|
||||||
|
district?: DistrictDto | null;
|
||||||
|
tags: TagDto[];
|
||||||
|
media: MediaItemDto[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateProfileDto {
|
export interface UpdateProfileDto {
|
||||||
name?: string;
|
name?: string;
|
||||||
/** @example "1995-06-15" */
|
/** @example "1995-06-15" */
|
||||||
@@ -63,6 +154,26 @@ export interface CreateLikeDto {
|
|||||||
type: "like" | "dislike";
|
type: "like" | "dislike";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LikeDto {
|
||||||
|
id: string;
|
||||||
|
sourceProfileId: string;
|
||||||
|
targetProfileId: string;
|
||||||
|
type: "like" | "dislike";
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MatchDto {
|
||||||
|
id: string;
|
||||||
|
profile1Id: string;
|
||||||
|
profile2Id: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateLikeResponseDto {
|
||||||
|
like: LikeDto;
|
||||||
|
match?: MatchDto | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateChatDto {
|
export interface CreateChatDto {
|
||||||
/** Your profile ID */
|
/** Your profile ID */
|
||||||
profileId: string;
|
profileId: string;
|
||||||
@@ -70,6 +181,23 @@ export interface CreateChatDto {
|
|||||||
matchId: string;
|
matchId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ChatDto {
|
||||||
|
id: string;
|
||||||
|
profile1Id: string;
|
||||||
|
profile2Id: string;
|
||||||
|
status: "active" | "closed";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageDto {
|
||||||
|
id: string;
|
||||||
|
chatId: string;
|
||||||
|
profileId: string;
|
||||||
|
text?: string | null;
|
||||||
|
mediaUrl?: string | null;
|
||||||
|
mediaType?: "photo" | "voice" | "video" | null;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SendMessageDto {
|
export interface SendMessageDto {
|
||||||
text?: string;
|
text?: string;
|
||||||
mediaUrl?: string;
|
mediaUrl?: string;
|
||||||
@@ -87,6 +215,26 @@ export interface CreateDateDto {
|
|||||||
statusId?: string;
|
statusId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DateDto {
|
||||||
|
id: string;
|
||||||
|
profile1Id: string;
|
||||||
|
profile2Id: string;
|
||||||
|
lat: string;
|
||||||
|
lng: string;
|
||||||
|
time: string;
|
||||||
|
statusId?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateStatusDto {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateWithStatusDto {
|
||||||
|
date: DateDto;
|
||||||
|
date_status?: DateStatusDto | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateDateStatusDto {
|
export interface UpdateDateStatusDto {
|
||||||
statusId: string;
|
statusId: string;
|
||||||
}
|
}
|
||||||
@@ -99,6 +247,56 @@ export interface CreateReportDto {
|
|||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReportDto {
|
||||||
|
id: string;
|
||||||
|
sourceProfileId: string;
|
||||||
|
entityId: string;
|
||||||
|
entityType: "profile" | "message";
|
||||||
|
description?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TagResponseDto {
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CityResponseDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lat: string;
|
||||||
|
lng: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DistrictResponseDto {
|
||||||
|
id: string;
|
||||||
|
cityId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCityDto {
|
||||||
|
/** @example "Москва" */
|
||||||
|
name: string;
|
||||||
|
/** @example 55.7558 */
|
||||||
|
lat: number;
|
||||||
|
/** @example 37.6173 */
|
||||||
|
lng: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateDistrictDto {
|
||||||
|
/** @example "Центральный" */
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GreetingDto {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthControllerUpdateFcmTokenPayload {
|
||||||
|
/** @example "firebase-token-abc123" */
|
||||||
|
fcmToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MediaControllerUploadParams {
|
export interface MediaControllerUploadParams {
|
||||||
type: string;
|
type: string;
|
||||||
profileId: string;
|
profileId: string;
|
||||||
@@ -129,6 +327,10 @@ export interface ChatControllerGetChatsParams {
|
|||||||
|
|
||||||
export interface ChatControllerGetMessagesParams {
|
export interface ChatControllerGetMessagesParams {
|
||||||
profileId: string;
|
profileId: string;
|
||||||
|
/** @default 50 */
|
||||||
|
limit?: number;
|
||||||
|
/** @default 1 */
|
||||||
|
page?: number;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +353,16 @@ export interface DatesControllerUpdateStatusParams {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TagsControllerCreatePayload {
|
||||||
|
/** @example "Спорт" */
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GreetingsControllerCreatePayload {
|
||||||
|
/** @example "Привет!" */
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
import axios, { AxiosInstance, AxiosRequestConfig, HeadersDefaults, ResponseType } from "axios";
|
import axios, { AxiosInstance, AxiosRequestConfig, HeadersDefaults, ResponseType } from "axios";
|
||||||
|
|
||||||
export type QueryParamsType = Record<string | number, any>;
|
export type QueryParamsType = Record<string | number, any>;
|
||||||
@@ -307,11 +519,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request POST:/api/v1/auth/register
|
* @request POST:/api/v1/auth/register
|
||||||
*/
|
*/
|
||||||
authControllerRegister: (data: RegisterDto, params: RequestParams = {}) =>
|
authControllerRegister: (data: RegisterDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<TokensResponseDto, any>({
|
||||||
path: `/api/v1/auth/register`,
|
path: `/api/v1/auth/register`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data,
|
body: data,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -324,11 +537,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request POST:/api/v1/auth/login
|
* @request POST:/api/v1/auth/login
|
||||||
*/
|
*/
|
||||||
authControllerLogin: (data: LoginDto, params: RequestParams = {}) =>
|
authControllerLogin: (data: LoginDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<TokensResponseDto, any>({
|
||||||
path: `/api/v1/auth/login`,
|
path: `/api/v1/auth/login`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data,
|
body: data,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -342,10 +556,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
authControllerLogout: (params: RequestParams = {}) =>
|
authControllerLogout: (params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/auth/logout`,
|
path: `/api/v1/auth/logout`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -358,11 +573,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request POST:/api/v1/auth/refresh
|
* @request POST:/api/v1/auth/refresh
|
||||||
*/
|
*/
|
||||||
authControllerRefresh: (data: RefreshTokenDto, params: RequestParams = {}) =>
|
authControllerRefresh: (data: RefreshTokenDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<TokensResponseDto, any>({
|
||||||
path: `/api/v1/auth/refresh`,
|
path: `/api/v1/auth/refresh`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data,
|
body: data,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -375,11 +591,14 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request POST:/api/v1/auth/fcm-token
|
* @request POST:/api/v1/auth/fcm-token
|
||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
authControllerUpdateFcmToken: (params: RequestParams = {}) =>
|
authControllerUpdateFcmToken: (data: AuthControllerUpdateFcmTokenPayload, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/auth/fcm-token`,
|
path: `/api/v1/auth/fcm-token`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -393,10 +612,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
usersControllerGetMe: (params: RequestParams = {}) =>
|
usersControllerGetMe: (params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MeResponseDto, any>({
|
||||||
path: `/api/v1/users/me`,
|
path: `/api/v1/users/me`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -410,10 +630,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
usersControllerFindOne: (id: string, params: RequestParams = {}) =>
|
usersControllerFindOne: (id: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<UserResponseDto, any>({
|
||||||
path: `/api/v1/users/${id}`,
|
path: `/api/v1/users/${id}`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -427,10 +648,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
usersControllerBan: (id: string, params: RequestParams = {}) =>
|
usersControllerBan: (id: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/users/${id}/ban`,
|
path: `/api/v1/users/${id}/ban`,
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -444,10 +666,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
usersControllerActivate: (id: string, params: RequestParams = {}) =>
|
usersControllerActivate: (id: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/users/${id}/activate`,
|
path: `/api/v1/users/${id}/activate`,
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -461,12 +684,13 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
profilesControllerCreate: (data: CreateProfileDto, params: RequestParams = {}) =>
|
profilesControllerCreate: (data: CreateProfileDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ProfileResponseDto, any>({
|
||||||
path: `/api/v1/profiles`,
|
path: `/api/v1/profiles`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data,
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -480,10 +704,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
profilesControllerGetMyProfiles: (params: RequestParams = {}) =>
|
profilesControllerGetMyProfiles: (params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ProfileResponseDto[], any>({
|
||||||
path: `/api/v1/profiles/my`,
|
path: `/api/v1/profiles/my`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -497,12 +722,13 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
profilesControllerUpdate: (profileId: string, data: UpdateProfileDto, params: RequestParams = {}) =>
|
profilesControllerUpdate: (profileId: string, data: UpdateProfileDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ProfileResponseDto, any>({
|
||||||
path: `/api/v1/profiles/${profileId}`,
|
path: `/api/v1/profiles/${profileId}`,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: data,
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -516,10 +742,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
profilesControllerFindOne: (profileId: string, params: RequestParams = {}) =>
|
profilesControllerFindOne: (profileId: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ProfileResponseDto, any>({
|
||||||
path: `/api/v1/profiles/${profileId}`,
|
path: `/api/v1/profiles/${profileId}`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -533,10 +760,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
profilesControllerDelete: (profileId: string, params: RequestParams = {}) =>
|
profilesControllerDelete: (profileId: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/profiles/${profileId}`,
|
path: `/api/v1/profiles/${profileId}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -550,11 +778,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
mediaControllerUpload: ({ profileId, ...query }: MediaControllerUploadParams, params: RequestParams = {}) =>
|
mediaControllerUpload: ({ profileId, ...query }: MediaControllerUploadParams, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MediaItemDto, any>({
|
||||||
path: `/api/v1/profiles/${profileId}/media/upload`,
|
path: `/api/v1/profiles/${profileId}/media/upload`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
query: query,
|
query: query,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -568,10 +797,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
mediaControllerGetMedia: (profileId: string, params: RequestParams = {}) =>
|
mediaControllerGetMedia: (profileId: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MediaItemDto[], any>({
|
||||||
path: `/api/v1/profiles/${profileId}/media`,
|
path: `/api/v1/profiles/${profileId}/media`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -585,10 +815,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
mediaControllerDeleteMedia: (mediaId: string, profileId: string, params: RequestParams = {}) =>
|
mediaControllerDeleteMedia: (mediaId: string, profileId: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/profiles/${profileId}/media/${mediaId}`,
|
path: `/api/v1/profiles/${profileId}/media/${mediaId}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -602,11 +833,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
feedControllerGetFeed: (query: FeedControllerGetFeedParams, params: RequestParams = {}) =>
|
feedControllerGetFeed: (query: FeedControllerGetFeedParams, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ProfileResponseDto[], any>({
|
||||||
path: `/api/v1/feed`,
|
path: `/api/v1/feed`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: query,
|
query: query,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -620,12 +852,13 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
likesControllerCreateLike: (data: CreateLikeDto, params: RequestParams = {}) =>
|
likesControllerCreateLike: (data: CreateLikeDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<CreateLikeResponseDto, any>({
|
||||||
path: `/api/v1/likes`,
|
path: `/api/v1/likes`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data,
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -639,11 +872,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
likesControllerGetMyMatches: (query: LikesControllerGetMyMatchesParams, params: RequestParams = {}) =>
|
likesControllerGetMyMatches: (query: LikesControllerGetMyMatchesParams, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MatchDto[], any>({
|
||||||
path: `/api/v1/likes/matches`,
|
path: `/api/v1/likes/matches`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: query,
|
query: query,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -657,12 +891,13 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
chatControllerCreateChat: (data: CreateChatDto, params: RequestParams = {}) =>
|
chatControllerCreateChat: (data: CreateChatDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ChatDto, any>({
|
||||||
path: `/api/v1/chats`,
|
path: `/api/v1/chats`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data,
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -676,11 +911,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
chatControllerGetChats: (query: ChatControllerGetChatsParams, params: RequestParams = {}) =>
|
chatControllerGetChats: (query: ChatControllerGetChatsParams, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ChatDto[], any>({
|
||||||
path: `/api/v1/chats`,
|
path: `/api/v1/chats`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: query,
|
query: query,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -694,11 +930,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
chatControllerGetMessages: ({ chatId, ...query }: ChatControllerGetMessagesParams, params: RequestParams = {}) =>
|
chatControllerGetMessages: ({ chatId, ...query }: ChatControllerGetMessagesParams, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageDto[], any>({
|
||||||
path: `/api/v1/chats/${chatId}/messages`,
|
path: `/api/v1/chats/${chatId}/messages`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: query,
|
query: query,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -716,13 +953,14 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
data: SendMessageDto,
|
data: SendMessageDto,
|
||||||
params: RequestParams = {},
|
params: RequestParams = {},
|
||||||
) =>
|
) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageDto, any>({
|
||||||
path: `/api/v1/chats/${chatId}/messages`,
|
path: `/api/v1/chats/${chatId}/messages`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
query: query,
|
query: query,
|
||||||
body: data,
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -736,11 +974,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
chatControllerCloseChat: ({ chatId, ...query }: ChatControllerCloseChatParams, params: RequestParams = {}) =>
|
chatControllerCloseChat: ({ chatId, ...query }: ChatControllerCloseChatParams, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/chats/${chatId}`,
|
path: `/api/v1/chats/${chatId}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
query: query,
|
query: query,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -754,12 +993,13 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
datesControllerCreate: (data: CreateDateDto, params: RequestParams = {}) =>
|
datesControllerCreate: (data: CreateDateDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<DateDto, any>({
|
||||||
path: `/api/v1/dates`,
|
path: `/api/v1/dates`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data,
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -773,11 +1013,12 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
datesControllerGetDates: (query: DatesControllerGetDatesParams, params: RequestParams = {}) =>
|
datesControllerGetDates: (query: DatesControllerGetDatesParams, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<DateWithStatusDto[], any>({
|
||||||
path: `/api/v1/dates`,
|
path: `/api/v1/dates`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: query,
|
query: query,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -795,13 +1036,14 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
data: UpdateDateStatusDto,
|
data: UpdateDateStatusDto,
|
||||||
params: RequestParams = {},
|
params: RequestParams = {},
|
||||||
) =>
|
) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<DateDto, any>({
|
||||||
path: `/api/v1/dates/${id}/status`,
|
path: `/api/v1/dates/${id}/status`,
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
query: query,
|
query: query,
|
||||||
body: data,
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -815,10 +1057,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
datesControllerGetStatuses: (params: RequestParams = {}) =>
|
datesControllerGetStatuses: (params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<DateStatusDto[], any>({
|
||||||
path: `/api/v1/dates/statuses`,
|
path: `/api/v1/dates/statuses`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -832,12 +1075,13 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
reportsControllerCreate: (data: CreateReportDto, params: RequestParams = {}) =>
|
reportsControllerCreate: (data: CreateReportDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ReportDto, any>({
|
||||||
path: `/api/v1/reports`,
|
path: `/api/v1/reports`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: data,
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
type: ContentType.Json,
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -851,10 +1095,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
reportsControllerGetAll: (params: RequestParams = {}) =>
|
reportsControllerGetAll: (params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<ReportDto[], any>({
|
||||||
path: `/api/v1/reports`,
|
path: `/api/v1/reports`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -867,9 +1112,10 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request GET:/api/v1/tags
|
* @request GET:/api/v1/tags
|
||||||
*/
|
*/
|
||||||
tagsControllerFindAll: (params: RequestParams = {}) =>
|
tagsControllerFindAll: (params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<TagResponseDto[], any>({
|
||||||
path: `/api/v1/tags`,
|
path: `/api/v1/tags`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -882,11 +1128,14 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request POST:/api/v1/tags
|
* @request POST:/api/v1/tags
|
||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
tagsControllerCreate: (params: RequestParams = {}) =>
|
tagsControllerCreate: (data: TagsControllerCreatePayload, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<TagResponseDto, any>({
|
||||||
path: `/api/v1/tags`,
|
path: `/api/v1/tags`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -900,10 +1149,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
tagsControllerDelete: (id: string, params: RequestParams = {}) =>
|
tagsControllerDelete: (id: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/tags/${id}`,
|
path: `/api/v1/tags/${id}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -916,9 +1166,10 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request GET:/api/v1/cities
|
* @request GET:/api/v1/cities
|
||||||
*/
|
*/
|
||||||
citiesControllerFindAll: (params: RequestParams = {}) =>
|
citiesControllerFindAll: (params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<CityResponseDto[], any>({
|
||||||
path: `/api/v1/cities`,
|
path: `/api/v1/cities`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -931,11 +1182,14 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request POST:/api/v1/cities
|
* @request POST:/api/v1/cities
|
||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
citiesControllerCreateCity: (params: RequestParams = {}) =>
|
citiesControllerCreateCity: (data: CreateCityDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<CityResponseDto, any>({
|
||||||
path: `/api/v1/cities`,
|
path: `/api/v1/cities`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -948,9 +1202,10 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request GET:/api/v1/cities/{cityId}/districts
|
* @request GET:/api/v1/cities/{cityId}/districts
|
||||||
*/
|
*/
|
||||||
citiesControllerFindDistricts: (cityId: string, params: RequestParams = {}) =>
|
citiesControllerFindDistricts: (cityId: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<DistrictResponseDto[], any>({
|
||||||
path: `/api/v1/cities/${cityId}/districts`,
|
path: `/api/v1/cities/${cityId}/districts`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -963,11 +1218,14 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request POST:/api/v1/cities/{cityId}/districts
|
* @request POST:/api/v1/cities/{cityId}/districts
|
||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
citiesControllerCreateDistrict: (cityId: string, params: RequestParams = {}) =>
|
citiesControllerCreateDistrict: (cityId: string, data: CreateDistrictDto, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<DistrictResponseDto, any>({
|
||||||
path: `/api/v1/cities/${cityId}/districts`,
|
path: `/api/v1/cities/${cityId}/districts`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -980,9 +1238,10 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request GET:/api/v1/greetings
|
* @request GET:/api/v1/greetings
|
||||||
*/
|
*/
|
||||||
greetingsControllerFindAll: (params: RequestParams = {}) =>
|
greetingsControllerFindAll: (params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<GreetingDto[], any>({
|
||||||
path: `/api/v1/greetings`,
|
path: `/api/v1/greetings`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -995,11 +1254,14 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @request POST:/api/v1/greetings
|
* @request POST:/api/v1/greetings
|
||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
greetingsControllerCreate: (params: RequestParams = {}) =>
|
greetingsControllerCreate: (data: GreetingsControllerCreatePayload, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<GreetingDto, any>({
|
||||||
path: `/api/v1/greetings`,
|
path: `/api/v1/greetings`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
body: data,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -1013,10 +1275,11 @@ export class Api<SecurityDataType extends unknown> {
|
|||||||
* @secure
|
* @secure
|
||||||
*/
|
*/
|
||||||
greetingsControllerDelete: (id: string, params: RequestParams = {}) =>
|
greetingsControllerDelete: (id: string, params: RequestParams = {}) =>
|
||||||
this.http.request<void, any>({
|
this.http.request<MessageResponseDto, any>({
|
||||||
path: `/api/v1/greetings/${id}`,
|
path: `/api/v1/greetings/${id}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
format: "json",
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import type { ChatMessage } from '@/stores/chat.store';
|
import type { ChatMessage } from '@/composables/useChat';
|
||||||
import MediaMessage from './MediaMessage.vue';
|
import MediaMessage from './MediaMessage.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import MapPicker from './MapPicker.vue';
|
import MapPicker from './MapPicker.vue';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
@@ -10,8 +10,8 @@ import AppInput from '@/components/common/AppInput.vue';
|
|||||||
const props = defineProps<{ partnerProfileId: string }>();
|
const props = defineProps<{ partnerProfileId: string }>();
|
||||||
const emit = defineEmits<{ close: []; created: [] }>();
|
const emit = defineEmits<{ close: []; created: [] }>();
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
time: '',
|
time: '',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ onMounted(async () => {
|
|||||||
await import('leaflet/dist/leaflet.css');
|
await import('leaflet/dist/leaflet.css');
|
||||||
|
|
||||||
// Fix Leaflet default icon paths
|
// Fix Leaflet default icon paths
|
||||||
delete (L.Icon.Default.prototype as Record<string, unknown>)._getIconUrl;
|
delete (L.Icon.Default.prototype as unknown as Record<string, unknown>)._getIconUrl;
|
||||||
L.Icon.Default.mergeOptions({
|
L.Icon.Default.mergeOptions({
|
||||||
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
|
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
|
||||||
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
|
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { gsap } from 'gsap';
|
import { gsap } from 'gsap';
|
||||||
import type { FeedProfile } from '@/stores/feed.store';
|
import type { FeedProfile } from '@/composables/useFeed';
|
||||||
import ProfileBadge from './ProfileBadge.vue';
|
import ProfileBadge from './ProfileBadge.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -28,7 +28,7 @@ const age = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const coverUrl = computed(() =>
|
const coverUrl = computed(() =>
|
||||||
props.profile.mediaUrls?.[currentImageIndex.value] || props.profile.avatarUrl || '',
|
props.profile.media?.[currentImageIndex.value]?.path ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
// ─── Drag / swipe mechanics ───────────────────────────────────────────────────
|
// ─── Drag / swipe mechanics ───────────────────────────────────────────────────
|
||||||
@@ -113,7 +113,7 @@ function openProfile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function nextImage() {
|
function nextImage() {
|
||||||
if (currentImageIndex.value < (props.profile.mediaUrls?.length ?? 1) - 1) {
|
if (currentImageIndex.value < (props.profile.media?.length ?? 1) - 1) {
|
||||||
currentImageIndex.value++;
|
currentImageIndex.value++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,9 +161,9 @@ function onTouchEnd(e: TouchEvent) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Image gallery dots -->
|
<!-- Image gallery dots -->
|
||||||
<div v-if="(profile.mediaUrls?.length ?? 0) > 1" class="feed-card__dots">
|
<div v-if="(profile.media?.length ?? 0) > 1" class="feed-card__dots">
|
||||||
<button
|
<button
|
||||||
v-for="(_, i) in profile.mediaUrls"
|
v-for="(_, i) in profile.media"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="feed-card__dot"
|
class="feed-card__dot"
|
||||||
:class="{ 'feed-card__dot--active': i === currentImageIndex }"
|
:class="{ 'feed-card__dot--active': i === currentImageIndex }"
|
||||||
@@ -192,7 +192,7 @@ function onTouchEnd(e: TouchEvent) {
|
|||||||
</div>
|
</div>
|
||||||
<h2 class="feed-card__name">{{ profile.name }}<span class="feed-card__age">, {{ age }}</span></h2>
|
<h2 class="feed-card__name">{{ profile.name }}<span class="feed-card__age">, {{ age }}</span></h2>
|
||||||
<div v-if="profile.tags?.length" class="feed-card__tags">
|
<div v-if="profile.tags?.length" class="feed-card__tags">
|
||||||
<ProfileBadge v-for="tag in profile.tags?.slice(0, 4)" :key="tag.id" :label="tag.name" />
|
<ProfileBadge v-for="tag in profile.tags?.slice(0, 4)" :key="tag.id" :label="tag.value" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, onMounted } from 'vue';
|
import { ref, computed, watch, onMounted } from 'vue';
|
||||||
import { gsap } from 'gsap';
|
import { gsap } from 'gsap';
|
||||||
import { useFeedStore } from '@/stores/feed.store';
|
import { useFeed } from '@/composables/useFeed';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import FeedCard from './FeedCard.vue';
|
import FeedCard from './FeedCard.vue';
|
||||||
import EmptyState from '@/components/common/EmptyState.vue';
|
import EmptyState from '@/components/common/EmptyState.vue';
|
||||||
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
||||||
|
|
||||||
const feedStore = useFeedStore();
|
const feedStore = useFeed();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const prefersReducedMotion = typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
const prefersReducedMotion = typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, watch } from 'vue';
|
import { reactive, ref, watch } from 'vue';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { useFeedStore } from '@/stores/feed.store';
|
import { useFeed } from '@/composables/useFeed';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import type { District } from '@/stores/ui.store';
|
import type { District } from '@/composables/useUi';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
import AppDrawer from '@/components/common/AppDrawer.vue';
|
import AppDrawer from '@/components/common/AppDrawer.vue';
|
||||||
|
|
||||||
defineProps<{ open: boolean }>();
|
defineProps<{ open: boolean }>();
|
||||||
const emit = defineEmits<{ close: [] }>();
|
const emit = defineEmits<{ close: [] }>();
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
const feedStore = useFeedStore();
|
const feedStore = useFeed();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
|
|
||||||
const filters = reactive({
|
const filters = reactive({
|
||||||
cityId: '',
|
cityId: '',
|
||||||
@@ -103,7 +103,7 @@ function reset() {
|
|||||||
class="filters__tag"
|
class="filters__tag"
|
||||||
:class="{ 'filters__tag--active': filters.tagIds.includes(tag.id) }"
|
:class="{ 'filters__tag--active': filters.tagIds.includes(tag.id) }"
|
||||||
@click="toggleTag(tag.id)"
|
@click="toggleTag(tag.id)"
|
||||||
>{{ tag.name }}</button>
|
>{{ tag.value }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { useRoute } from 'vue-router';
|
|||||||
import TauriTitlebar from './TauriTitlebar.vue';
|
import TauriTitlebar from './TauriTitlebar.vue';
|
||||||
import SideNav from './SideNav.vue';
|
import SideNav from './SideNav.vue';
|
||||||
import BottomNav from './BottomNav.vue';
|
import BottomNav from './BottomNav.vue';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
|
|
||||||
const isTauri = typeof window !== 'undefined' && !!window.__TAURI__;
|
const isTauri = typeof window !== 'undefined' && !!window.__TAURI__;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const authStore = useAuthStore();
|
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ path: '/feed', label: 'Лента', icon: 'grid' },
|
{ path: '/feed', label: 'Лента', icon: 'grid' },
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
|
|
||||||
interface MediaItem {
|
interface MediaItem {
|
||||||
@@ -12,7 +12,7 @@ interface MediaItem {
|
|||||||
const props = defineProps<{ profileId: string; editable?: boolean }>();
|
const props = defineProps<{ profileId: string; editable?: boolean }>();
|
||||||
const emit = defineEmits<{ updated: [] }>();
|
const emit = defineEmits<{ updated: [] }>();
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
const items = ref<MediaItem[]>([]);
|
const items = ref<MediaItem[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const uploading = ref(false);
|
const uploading = ref(false);
|
||||||
|
|||||||
@@ -2,21 +2,21 @@
|
|||||||
import { reactive, watch, ref } from 'vue';
|
import { reactive, watch, ref } from 'vue';
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { required, helpers } from '@vuelidate/validators';
|
import { required, helpers } from '@vuelidate/validators';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useProfileStore } from '@/stores/profile.store';
|
import { useProfile } from '@/composables/useProfile';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import type { UserProfile } from '@/stores/auth.store';
|
import type { UserProfile } from '@/composables/useAuth';
|
||||||
import type { District } from '@/stores/ui.store';
|
import type { District } from '@/composables/useUi';
|
||||||
import AppInput from '@/components/common/AppInput.vue';
|
import AppInput from '@/components/common/AppInput.vue';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
|
|
||||||
const props = defineProps<{ profile: UserProfile }>();
|
const props = defineProps<{ profile: UserProfile }>();
|
||||||
const emit = defineEmits<{ saved: [UserProfile]; cancel: [] }>();
|
const emit = defineEmits<{ saved: [UserProfile]; cancel: [] }>();
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const profileStore = useProfileStore();
|
const profileStore = useProfile();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: props.profile.name,
|
name: props.profile.name,
|
||||||
@@ -28,7 +28,7 @@ const form = reactive({
|
|||||||
nation: props.profile.nation ?? '',
|
nation: props.profile.nation ?? '',
|
||||||
height: props.profile.height ?? undefined as number | undefined,
|
height: props.profile.height ?? undefined as number | undefined,
|
||||||
weight: props.profile.weight ?? undefined as number | undefined,
|
weight: props.profile.weight ?? undefined as number | undefined,
|
||||||
tagIds: [...(props.profile.tagIds ?? [])],
|
tagIds: props.profile.tags?.map((t) => t.id) ?? [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const districts = ref<District[]>([]);
|
const districts = ref<District[]>([]);
|
||||||
@@ -136,7 +136,7 @@ async function save() {
|
|||||||
class="profile-editor__tag"
|
class="profile-editor__tag"
|
||||||
:class="{ 'profile-editor__tag--active': form.tagIds.includes(tag.id) }"
|
:class="{ 'profile-editor__tag--active': form.tagIds.includes(tag.id) }"
|
||||||
@click="toggleTag(tag.id)"
|
@click="toggleTag(tag.id)"
|
||||||
>{{ tag.name }}</button>
|
>{{ tag.value }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import AppModal from '@/components/common/AppModal.vue';
|
import AppModal from '@/components/common/AppModal.vue';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
@@ -14,8 +14,8 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits<{ close: [] }>();
|
const emit = defineEmits<{ close: [] }>();
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const form = reactive({ description: '' });
|
const form = reactive({ description: '' });
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
130
src/composables/useAuth.ts
Normal file
130
src/composables/useAuth.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { ref, computed, reactive } from 'vue';
|
||||||
|
import { apiClient, _setAccessToken, _clearAuth } from '@/api/client';
|
||||||
|
import type { LoginDto, RegisterDto, TagDto, MediaItemDto } from '@/api/api';
|
||||||
|
|
||||||
|
export interface UserProfile {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
name: string;
|
||||||
|
birthDate: string;
|
||||||
|
gender: 'male' | 'female';
|
||||||
|
cityId?: string | null;
|
||||||
|
districtId?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
nation?: string | null;
|
||||||
|
height?: number | null;
|
||||||
|
weight?: number | null;
|
||||||
|
activeChatId?: string | null;
|
||||||
|
tags: TagDto[];
|
||||||
|
media: MediaItemDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthUser {
|
||||||
|
id: string;
|
||||||
|
phone: string;
|
||||||
|
status: 'active' | 'banned' | 'pending';
|
||||||
|
roleId?: string | null;
|
||||||
|
role?: { id: string; name: string } | null;
|
||||||
|
profiles: UserProfile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = ref<AuthUser | null>(null);
|
||||||
|
const activeProfileId = ref<string | null>(null);
|
||||||
|
|
||||||
|
const isAuthenticated = computed(() => !!user.value);
|
||||||
|
const isAdmin = computed(() => user.value?.role?.name === 'admin');
|
||||||
|
const profiles = computed(() => user.value?.profiles ?? []);
|
||||||
|
const activeProfile = computed(() =>
|
||||||
|
profiles.value.find((p) => p.id === activeProfileId.value) ?? profiles.value[0] ?? null,
|
||||||
|
);
|
||||||
|
const hasProfiles = computed(() => profiles.value.length > 0);
|
||||||
|
|
||||||
|
async function login(dto: LoginDto) {
|
||||||
|
const res = await apiClient.api.authControllerLogin(dto) as unknown as {
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
};
|
||||||
|
_setAccessToken(res.accessToken);
|
||||||
|
localStorage.setItem('refreshToken', res.refreshToken);
|
||||||
|
await fetchMe();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function register(dto: RegisterDto) {
|
||||||
|
const res = await apiClient.api.authControllerRegister(dto) as unknown as {
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
};
|
||||||
|
_setAccessToken(res.accessToken);
|
||||||
|
localStorage.setItem('refreshToken', res.refreshToken);
|
||||||
|
await fetchMe();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
try {
|
||||||
|
await apiClient.api.authControllerLogout();
|
||||||
|
} catch {
|
||||||
|
// ignore errors on logout
|
||||||
|
}
|
||||||
|
_clearAuth();
|
||||||
|
user.value = null;
|
||||||
|
activeProfileId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchMe() {
|
||||||
|
const [meRes, profilesRes] = await Promise.all([
|
||||||
|
apiClient.api.usersControllerGetMe(),
|
||||||
|
apiClient.api.profilesControllerGetMyProfiles(),
|
||||||
|
]);
|
||||||
|
const fullProfiles = profilesRes as unknown as UserProfile[];
|
||||||
|
user.value = { ...meRes, profiles: fullProfiles } as unknown as AuthUser;
|
||||||
|
if (fullProfiles.length > 0 && !activeProfileId.value) {
|
||||||
|
activeProfileId.value = fullProfiles[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveProfile(profileId: string) {
|
||||||
|
activeProfileId.value = profileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addProfile(profile: UserProfile) {
|
||||||
|
if (user.value) {
|
||||||
|
user.value.profiles.push(profile);
|
||||||
|
activeProfileId.value = profile.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProfile(updated: UserProfile) {
|
||||||
|
if (user.value) {
|
||||||
|
const idx = user.value.profiles.findIndex((p) => p.id === updated.id);
|
||||||
|
if (idx !== -1) user.value.profiles[idx] = updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeProfile(profileId: string) {
|
||||||
|
if (user.value) {
|
||||||
|
user.value.profiles = user.value.profiles.filter((p) => p.id !== profileId);
|
||||||
|
if (activeProfileId.value === profileId) {
|
||||||
|
activeProfileId.value = user.value.profiles[0]?.id ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
return reactive({
|
||||||
|
user,
|
||||||
|
activeProfileId,
|
||||||
|
isAuthenticated,
|
||||||
|
isAdmin,
|
||||||
|
profiles,
|
||||||
|
activeProfile,
|
||||||
|
hasProfiles,
|
||||||
|
login,
|
||||||
|
register,
|
||||||
|
logout,
|
||||||
|
fetchMe,
|
||||||
|
setActiveProfile,
|
||||||
|
addProfile,
|
||||||
|
updateProfile,
|
||||||
|
removeProfile,
|
||||||
|
});
|
||||||
|
}
|
||||||
117
src/composables/useChat.ts
Normal file
117
src/composables/useChat.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { apiClient } from '@/api/client';
|
||||||
|
import type { SendMessageDto } from '@/api/api';
|
||||||
|
|
||||||
|
export interface ChatProfile {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Chat {
|
||||||
|
id: string;
|
||||||
|
profile1Id: string;
|
||||||
|
profile2Id: string;
|
||||||
|
status: 'active' | 'closed';
|
||||||
|
partner?: ChatProfile;
|
||||||
|
lastMessage?: ChatMessage;
|
||||||
|
unreadCount?: number;
|
||||||
|
createdAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatMessage {
|
||||||
|
id: string;
|
||||||
|
chatId: string;
|
||||||
|
profileId: string;
|
||||||
|
text?: string;
|
||||||
|
mediaUrl?: string;
|
||||||
|
mediaType?: 'photo' | 'voice' | 'video';
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polling interval — replace with WebSocket when backend supports it
|
||||||
|
const POLL_INTERVAL = 2000;
|
||||||
|
|
||||||
|
const chats = ref<Chat[]>([]);
|
||||||
|
const activeChat = ref<Chat | null>(null);
|
||||||
|
const messages = ref<ChatMessage[]>([]);
|
||||||
|
const pollingTimer = ref<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
async function fetchChats(profileId: string) {
|
||||||
|
const res = await apiClient.api.chatControllerGetChats({ profileId }) as unknown as Chat[];
|
||||||
|
chats.value = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchMessages(chatId: string, profileId: string) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId }) as unknown as ChatMessage[];
|
||||||
|
messages.value = res;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMessage(chatId: string, profileId: string, dto: SendMessageDto) {
|
||||||
|
const res = await apiClient.api.chatControllerSendMessage({ chatId, profileId }, dto) as unknown as ChatMessage;
|
||||||
|
messages.value.push(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openChat(profileId: string, matchId: string) {
|
||||||
|
const res = await apiClient.api.chatControllerCreateChat({ profileId, matchId }) as unknown as Chat;
|
||||||
|
const existing = chats.value.findIndex((c) => c.id === res.id);
|
||||||
|
if (existing === -1) chats.value.unshift(res);
|
||||||
|
activeChat.value = res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function closeChat(chatId: string, profileId: string) {
|
||||||
|
await apiClient.api.chatControllerCloseChat({ chatId, profileId });
|
||||||
|
chats.value = chats.value.filter((c) => c.id !== chatId);
|
||||||
|
if (activeChat.value?.id === chatId) activeChat.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPolling(chatId: string, profileId: string) {
|
||||||
|
stopPolling();
|
||||||
|
// TODO: replace with WebSocket subscription
|
||||||
|
pollingTimer.value = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId }) as unknown as ChatMessage[];
|
||||||
|
if (res.length > messages.value.length) {
|
||||||
|
messages.value = res;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// polling errors are silent
|
||||||
|
}
|
||||||
|
}, POLL_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPolling() {
|
||||||
|
if (pollingTimer.value) {
|
||||||
|
clearInterval(pollingTimer.value);
|
||||||
|
pollingTimer.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveChat(chat: Chat | null) {
|
||||||
|
activeChat.value = chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useChat() {
|
||||||
|
return reactive({
|
||||||
|
chats,
|
||||||
|
activeChat,
|
||||||
|
messages,
|
||||||
|
loading,
|
||||||
|
fetchChats,
|
||||||
|
fetchMessages,
|
||||||
|
sendMessage,
|
||||||
|
openChat,
|
||||||
|
closeChat,
|
||||||
|
startPolling,
|
||||||
|
stopPolling,
|
||||||
|
setActiveChat,
|
||||||
|
});
|
||||||
|
}
|
||||||
66
src/composables/useFeed.ts
Normal file
66
src/composables/useFeed.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { apiClient } from '@/api/client';
|
||||||
|
import type { FeedControllerGetFeedParams, TagDto, MediaItemDto } from '@/api/api';
|
||||||
|
|
||||||
|
export interface FeedProfile {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
birthDate: string;
|
||||||
|
gender: 'male' | 'female';
|
||||||
|
cityId?: string | null;
|
||||||
|
cityName?: string;
|
||||||
|
districtId?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
nation?: string | null;
|
||||||
|
height?: number | null;
|
||||||
|
weight?: number | null;
|
||||||
|
tags?: TagDto[];
|
||||||
|
media?: MediaItemDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cards = ref<FeedProfile[]>([]);
|
||||||
|
const filters = reactive<Partial<FeedControllerGetFeedParams>>({});
|
||||||
|
const page = ref(1);
|
||||||
|
const hasMore = ref(true);
|
||||||
|
const searchPaused = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
async function fetchNextPage(profileId: string) {
|
||||||
|
if (loading.value || !hasMore.value) return;
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await apiClient.api.feedControllerGetFeed({
|
||||||
|
profileId,
|
||||||
|
page: page.value,
|
||||||
|
limit: 20,
|
||||||
|
...filters,
|
||||||
|
}) as unknown as FeedProfile[];
|
||||||
|
|
||||||
|
if (page.value === 1) cards.value = res;
|
||||||
|
else cards.value.push(...res);
|
||||||
|
|
||||||
|
hasMore.value = res.length >= 20;
|
||||||
|
page.value++;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilters(newFilters: Partial<FeedControllerGetFeedParams>) {
|
||||||
|
Object.assign(filters, newFilters);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
cards.value = [];
|
||||||
|
page.value = 1;
|
||||||
|
hasMore.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCard(profileId: string) {
|
||||||
|
cards.value = cards.value.filter((c) => c.id !== profileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFeed() {
|
||||||
|
return reactive({ cards, filters, page, hasMore, searchPaused, loading, fetchNextPage, applyFilters, reset, removeCard });
|
||||||
|
}
|
||||||
39
src/composables/useProfile.ts
Normal file
39
src/composables/useProfile.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
import { apiClient } from '@/api/client';
|
||||||
|
import type { CreateProfileDto, UpdateProfileDto } from '@/api/api';
|
||||||
|
import type { UserProfile } from './useAuth';
|
||||||
|
|
||||||
|
const currentProfile = ref<UserProfile | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
async function fetchProfile(profileId: string) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await apiClient.api.profilesControllerFindOne(profileId) as unknown as UserProfile;
|
||||||
|
currentProfile.value = res;
|
||||||
|
return res;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createProfile(dto: CreateProfileDto) {
|
||||||
|
const res = await apiClient.api.profilesControllerCreate(dto) as unknown as UserProfile;
|
||||||
|
currentProfile.value = res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateProfile(profileId: string, dto: UpdateProfileDto) {
|
||||||
|
const res = await apiClient.api.profilesControllerUpdate(profileId, dto) as unknown as UserProfile;
|
||||||
|
currentProfile.value = res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteProfile(profileId: string) {
|
||||||
|
await apiClient.api.profilesControllerDelete(profileId);
|
||||||
|
currentProfile.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useProfile() {
|
||||||
|
return { currentProfile, loading, fetchProfile, createProfile, updateProfile, deleteProfile };
|
||||||
|
}
|
||||||
82
src/composables/useUi.ts
Normal file
82
src/composables/useUi.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
|
||||||
|
export type ToastType = 'success' | 'error' | 'info' | 'warning';
|
||||||
|
|
||||||
|
export interface Toast {
|
||||||
|
id: string;
|
||||||
|
type: ToastType;
|
||||||
|
message: string;
|
||||||
|
duration?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tag {
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface City {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface District {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
cityId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Greeting {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toasts = ref<Toast[]>([]);
|
||||||
|
const sidebarExpanded = ref(false);
|
||||||
|
const tags = ref<Tag[]>([]);
|
||||||
|
const cities = ref<City[]>([]);
|
||||||
|
const districts = reactive<Record<string, District[]>>({});
|
||||||
|
const greetings = ref<Greeting[]>([]);
|
||||||
|
const referencesLoaded = ref(false);
|
||||||
|
|
||||||
|
function addToast(message: string, type: ToastType = 'info', duration = 4000) {
|
||||||
|
const id = `${Date.now()}-${Math.random()}`;
|
||||||
|
toasts.value.push({ id, type, message, duration });
|
||||||
|
if (duration > 0) {
|
||||||
|
setTimeout(() => removeToast(id), duration);
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeToast(id: string) {
|
||||||
|
toasts.value = toasts.value.filter((t) => t.id !== id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSidebarExpanded(value: boolean) {
|
||||||
|
sidebarExpanded.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTags(data: Tag[]) { tags.value = data; }
|
||||||
|
function setCities(data: City[]) { cities.value = data; }
|
||||||
|
function setDistricts(cityId: string, data: District[]) { districts[cityId] = data; }
|
||||||
|
function setGreetings(data: Greeting[]) { greetings.value = data; }
|
||||||
|
function setReferencesLoaded() { referencesLoaded.value = true; }
|
||||||
|
|
||||||
|
export function useUi() {
|
||||||
|
return reactive({
|
||||||
|
toasts,
|
||||||
|
sidebarExpanded,
|
||||||
|
tags,
|
||||||
|
cities,
|
||||||
|
districts,
|
||||||
|
greetings,
|
||||||
|
referencesLoaded,
|
||||||
|
addToast,
|
||||||
|
removeToast,
|
||||||
|
setSidebarExpanded,
|
||||||
|
setTags,
|
||||||
|
setCities,
|
||||||
|
setDistricts,
|
||||||
|
setGreetings,
|
||||||
|
setReferencesLoaded,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import { createPinia } from 'pinia';
|
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import '@/styles/tailwind.css';
|
import '@/styles/tailwind.css';
|
||||||
import '@/styles/main.scss';
|
import '@/styles/main.scss';
|
||||||
|
|
||||||
const pinia = createPinia();
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(pinia);
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { _getAccessToken, _setAccessToken } from '@/api/client';
|
import { _getAccessToken, _setAccessToken } from '@/api/client';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ export const router = createRouter({
|
|||||||
let _initDone = false;
|
let _initDone = false;
|
||||||
|
|
||||||
router.beforeEach(async (to, _from, next) => {
|
router.beforeEach(async (to, _from, next) => {
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
|
|
||||||
// First navigation: try to restore session from localStorage refresh token
|
// First navigation: try to restore session from localStorage refresh token
|
||||||
if (!_initDone) {
|
if (!_initDone) {
|
||||||
@@ -53,12 +53,12 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
const storedRefresh = localStorage.getItem('refreshToken');
|
const storedRefresh = localStorage.getItem('refreshToken');
|
||||||
if (storedRefresh && !_getAccessToken()) {
|
if (storedRefresh && !_getAccessToken()) {
|
||||||
try {
|
try {
|
||||||
const res = await axios.post<{ accessToken: string; refreshToken: string }>(
|
const res = await axios.post<{ data: { accessToken: string; refreshToken: string } }>(
|
||||||
`${BASE_URL}/api/v1/auth/refresh`,
|
`${BASE_URL}/api/v1/auth/refresh`,
|
||||||
{ refreshToken: storedRefresh },
|
{ refreshToken: storedRefresh },
|
||||||
);
|
);
|
||||||
_setAccessToken(res.data.accessToken);
|
_setAccessToken(res.data.data.accessToken);
|
||||||
localStorage.setItem('refreshToken', res.data.refreshToken);
|
localStorage.setItem('refreshToken', res.data.data.refreshToken);
|
||||||
await authStore.fetchMe();
|
await authStore.fetchMe();
|
||||||
} catch {
|
} catch {
|
||||||
localStorage.removeItem('refreshToken');
|
localStorage.removeItem('refreshToken');
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
import { apiClient, _setAccessToken, _clearAuth } from '@/api/client';
|
|
||||||
import type { LoginDto, RegisterDto } from '@/api/api';
|
|
||||||
|
|
||||||
export interface UserProfile {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
birthDate: string;
|
|
||||||
gender: 'male' | 'female';
|
|
||||||
cityId?: string;
|
|
||||||
districtId?: string;
|
|
||||||
description?: string;
|
|
||||||
nation?: string;
|
|
||||||
height?: number;
|
|
||||||
weight?: number;
|
|
||||||
tagIds?: string[];
|
|
||||||
mediaUrls?: string[];
|
|
||||||
avatarUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuthUser {
|
|
||||||
id: string;
|
|
||||||
phone: string;
|
|
||||||
role: 'user' | 'admin';
|
|
||||||
isActive: boolean;
|
|
||||||
profiles: UserProfile[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
|
||||||
const user = ref<AuthUser | null>(null);
|
|
||||||
const activeProfileId = ref<string | null>(null);
|
|
||||||
|
|
||||||
const isAuthenticated = computed(() => !!user.value);
|
|
||||||
const isAdmin = computed(() => user.value?.role === 'admin');
|
|
||||||
const profiles = computed(() => user.value?.profiles ?? []);
|
|
||||||
const activeProfile = computed(() =>
|
|
||||||
profiles.value.find((p) => p.id === activeProfileId.value) ?? profiles.value[0] ?? null,
|
|
||||||
);
|
|
||||||
const hasProfiles = computed(() => profiles.value.length > 0);
|
|
||||||
|
|
||||||
async function login(dto: LoginDto) {
|
|
||||||
const res = await apiClient.api.authControllerLogin(dto) as unknown as {
|
|
||||||
accessToken: string;
|
|
||||||
refreshToken: string;
|
|
||||||
};
|
|
||||||
_setAccessToken(res.accessToken);
|
|
||||||
localStorage.setItem('refreshToken', res.refreshToken);
|
|
||||||
await fetchMe();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function register(dto: RegisterDto) {
|
|
||||||
const res = await apiClient.api.authControllerRegister(dto) as unknown as {
|
|
||||||
accessToken: string;
|
|
||||||
refreshToken: string;
|
|
||||||
};
|
|
||||||
_setAccessToken(res.accessToken);
|
|
||||||
localStorage.setItem('refreshToken', res.refreshToken);
|
|
||||||
await fetchMe();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
try {
|
|
||||||
await apiClient.api.authControllerLogout();
|
|
||||||
} catch {
|
|
||||||
// ignore errors on logout
|
|
||||||
}
|
|
||||||
_clearAuth();
|
|
||||||
user.value = null;
|
|
||||||
activeProfileId.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchMe() {
|
|
||||||
const res = await apiClient.api.usersControllerGetMe() as unknown as AuthUser;
|
|
||||||
user.value = res;
|
|
||||||
if (res.profiles?.length > 0 && !activeProfileId.value) {
|
|
||||||
activeProfileId.value = res.profiles[0].id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveProfile(profileId: string) {
|
|
||||||
activeProfileId.value = profileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addProfile(profile: UserProfile) {
|
|
||||||
if (user.value) {
|
|
||||||
user.value.profiles.push(profile);
|
|
||||||
activeProfileId.value = profile.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateProfile(updated: UserProfile) {
|
|
||||||
if (user.value) {
|
|
||||||
const idx = user.value.profiles.findIndex((p) => p.id === updated.id);
|
|
||||||
if (idx !== -1) user.value.profiles[idx] = updated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeProfile(profileId: string) {
|
|
||||||
if (user.value) {
|
|
||||||
user.value.profiles = user.value.profiles.filter((p) => p.id !== profileId);
|
|
||||||
if (activeProfileId.value === profileId) {
|
|
||||||
activeProfileId.value = user.value.profiles[0]?.id ?? null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
activeProfileId,
|
|
||||||
isAuthenticated,
|
|
||||||
isAdmin,
|
|
||||||
profiles,
|
|
||||||
activeProfile,
|
|
||||||
hasProfiles,
|
|
||||||
login,
|
|
||||||
register,
|
|
||||||
logout,
|
|
||||||
fetchMe,
|
|
||||||
setActiveProfile,
|
|
||||||
addProfile,
|
|
||||||
updateProfile,
|
|
||||||
removeProfile,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { apiClient } from '@/api/client';
|
|
||||||
import type { SendMessageDto } from '@/api/api';
|
|
||||||
|
|
||||||
export interface ChatProfile {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
avatarUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Chat {
|
|
||||||
id: string;
|
|
||||||
matchId: string;
|
|
||||||
isActive: boolean;
|
|
||||||
partner: ChatProfile;
|
|
||||||
lastMessage?: ChatMessage;
|
|
||||||
unreadCount: number;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
|
||||||
id: string;
|
|
||||||
chatId: string;
|
|
||||||
senderId: string;
|
|
||||||
text?: string;
|
|
||||||
mediaUrl?: string;
|
|
||||||
mediaType?: 'photo' | 'voice' | 'video';
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Polling interval — replace with WebSocket when backend supports it
|
|
||||||
const POLL_INTERVAL = 2000;
|
|
||||||
|
|
||||||
export const useChatStore = defineStore('chat', () => {
|
|
||||||
const chats = ref<Chat[]>([]);
|
|
||||||
const activeChat = ref<Chat | null>(null);
|
|
||||||
const messages = ref<ChatMessage[]>([]);
|
|
||||||
const pollingTimer = ref<ReturnType<typeof setInterval> | null>(null);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function fetchChats(profileId: string) {
|
|
||||||
const res = await apiClient.api.chatControllerGetChats({ profileId }) as unknown as Chat[];
|
|
||||||
chats.value = res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchMessages(chatId: string, profileId: string) {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId }) as unknown as ChatMessage[];
|
|
||||||
messages.value = res;
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendMessage(chatId: string, profileId: string, dto: SendMessageDto) {
|
|
||||||
const res = await apiClient.api.chatControllerSendMessage({ chatId, profileId }, dto) as unknown as ChatMessage;
|
|
||||||
messages.value.push(res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openChat(profileId: string, matchId: string) {
|
|
||||||
const res = await apiClient.api.chatControllerCreateChat({ profileId, matchId }) as unknown as Chat;
|
|
||||||
const existing = chats.value.findIndex((c) => c.id === res.id);
|
|
||||||
if (existing === -1) chats.value.unshift(res);
|
|
||||||
activeChat.value = res;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function closeChat(chatId: string, profileId: string) {
|
|
||||||
await apiClient.api.chatControllerCloseChat({ chatId, profileId });
|
|
||||||
chats.value = chats.value.filter((c) => c.id !== chatId);
|
|
||||||
if (activeChat.value?.id === chatId) activeChat.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startPolling(chatId: string, profileId: string) {
|
|
||||||
stopPolling();
|
|
||||||
// TODO: replace with WebSocket subscription
|
|
||||||
pollingTimer.value = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId }) as unknown as ChatMessage[];
|
|
||||||
if (res.length > messages.value.length) {
|
|
||||||
messages.value = res;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// polling errors are silent
|
|
||||||
}
|
|
||||||
}, POLL_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopPolling() {
|
|
||||||
if (pollingTimer.value) {
|
|
||||||
clearInterval(pollingTimer.value);
|
|
||||||
pollingTimer.value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveChat(chat: Chat | null) {
|
|
||||||
activeChat.value = chat;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
chats,
|
|
||||||
activeChat,
|
|
||||||
messages,
|
|
||||||
loading,
|
|
||||||
fetchChats,
|
|
||||||
fetchMessages,
|
|
||||||
sendMessage,
|
|
||||||
openChat,
|
|
||||||
closeChat,
|
|
||||||
startPolling,
|
|
||||||
stopPolling,
|
|
||||||
setActiveChat,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref, reactive } from 'vue';
|
|
||||||
import { apiClient } from '@/api/client';
|
|
||||||
import type { FeedControllerGetFeedParams } from '@/api/api';
|
|
||||||
|
|
||||||
export interface FeedProfile {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
birthDate: string;
|
|
||||||
age: number;
|
|
||||||
gender: 'male' | 'female';
|
|
||||||
cityId?: string;
|
|
||||||
cityName?: string;
|
|
||||||
districtId?: string;
|
|
||||||
districtName?: string;
|
|
||||||
description?: string;
|
|
||||||
nation?: string;
|
|
||||||
height?: number;
|
|
||||||
weight?: number;
|
|
||||||
tags?: Array<{ id: string; name: string }>;
|
|
||||||
mediaUrls: string[];
|
|
||||||
avatarUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useFeedStore = defineStore('feed', () => {
|
|
||||||
const cards = ref<FeedProfile[]>([]);
|
|
||||||
const filters = reactive<Partial<FeedControllerGetFeedParams>>({});
|
|
||||||
const page = ref(1);
|
|
||||||
const hasMore = ref(true);
|
|
||||||
const searchPaused = ref(false);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function fetchNextPage(profileId: string) {
|
|
||||||
if (loading.value || !hasMore.value) return;
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const res = await apiClient.api.feedControllerGetFeed({
|
|
||||||
profileId,
|
|
||||||
page: page.value,
|
|
||||||
limit: 20,
|
|
||||||
...filters,
|
|
||||||
}) as unknown as { items: FeedProfile[]; hasMore: boolean; searchPaused?: boolean };
|
|
||||||
|
|
||||||
if (page.value === 1) cards.value = res.items;
|
|
||||||
else cards.value.push(...res.items);
|
|
||||||
|
|
||||||
hasMore.value = res.hasMore;
|
|
||||||
searchPaused.value = res.searchPaused ?? false;
|
|
||||||
page.value++;
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyFilters(newFilters: Partial<FeedControllerGetFeedParams>) {
|
|
||||||
Object.assign(filters, newFilters);
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
cards.value = [];
|
|
||||||
page.value = 1;
|
|
||||||
hasMore.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeCard(profileId: string) {
|
|
||||||
cards.value = cards.value.filter((c) => c.id !== profileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { cards, filters, page, hasMore, searchPaused, loading, fetchNextPage, applyFilters, reset, removeCard };
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { apiClient } from '@/api/client';
|
|
||||||
import type { CreateProfileDto, UpdateProfileDto } from '@/api/api';
|
|
||||||
import type { UserProfile } from './auth.store';
|
|
||||||
|
|
||||||
export const useProfileStore = defineStore('profile', () => {
|
|
||||||
const currentProfile = ref<UserProfile | null>(null);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function fetchProfile(profileId: string) {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const res = await apiClient.api.profilesControllerFindOne(profileId) as unknown as UserProfile;
|
|
||||||
currentProfile.value = res;
|
|
||||||
return res;
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createProfile(dto: CreateProfileDto) {
|
|
||||||
const res = await apiClient.api.profilesControllerCreate(dto) as unknown as UserProfile;
|
|
||||||
currentProfile.value = res;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateProfile(profileId: string, dto: UpdateProfileDto) {
|
|
||||||
const res = await apiClient.api.profilesControllerUpdate(profileId, dto) as unknown as UserProfile;
|
|
||||||
currentProfile.value = res;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteProfile(profileId: string) {
|
|
||||||
await apiClient.api.profilesControllerDelete(profileId);
|
|
||||||
currentProfile.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { currentProfile, loading, fetchProfile, createProfile, updateProfile, deleteProfile };
|
|
||||||
});
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref, reactive } from 'vue';
|
|
||||||
|
|
||||||
export type ToastType = 'success' | 'error' | 'info' | 'warning';
|
|
||||||
|
|
||||||
export interface Toast {
|
|
||||||
id: string;
|
|
||||||
type: ToastType;
|
|
||||||
message: string;
|
|
||||||
duration?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Tag {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface City {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface District {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
cityId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Greeting {
|
|
||||||
id: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useUiStore = defineStore('ui', () => {
|
|
||||||
const toasts = ref<Toast[]>([]);
|
|
||||||
const sidebarExpanded = ref(false);
|
|
||||||
const tags = ref<Tag[]>([]);
|
|
||||||
const cities = ref<City[]>([]);
|
|
||||||
const districts = reactive<Record<string, District[]>>({});
|
|
||||||
const greetings = ref<Greeting[]>([]);
|
|
||||||
const referencesLoaded = ref(false);
|
|
||||||
|
|
||||||
function addToast(message: string, type: ToastType = 'info', duration = 4000) {
|
|
||||||
const id = `${Date.now()}-${Math.random()}`;
|
|
||||||
toasts.value.push({ id, type, message, duration });
|
|
||||||
if (duration > 0) {
|
|
||||||
setTimeout(() => removeToast(id), duration);
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeToast(id: string) {
|
|
||||||
toasts.value = toasts.value.filter((t) => t.id !== id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSidebarExpanded(value: boolean) {
|
|
||||||
sidebarExpanded.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTags(data: Tag[]) { tags.value = data; }
|
|
||||||
function setCities(data: City[]) { cities.value = data; }
|
|
||||||
function setDistricts(cityId: string, data: District[]) { districts[cityId] = data; }
|
|
||||||
function setGreetings(data: Greeting[]) { greetings.value = data; }
|
|
||||||
function setReferencesLoaded() { referencesLoaded.value = true; }
|
|
||||||
|
|
||||||
return {
|
|
||||||
toasts,
|
|
||||||
sidebarExpanded,
|
|
||||||
tags,
|
|
||||||
cities,
|
|
||||||
districts,
|
|
||||||
greetings,
|
|
||||||
referencesLoaded,
|
|
||||||
addToast,
|
|
||||||
removeToast,
|
|
||||||
setSidebarExpanded,
|
|
||||||
setTags,
|
|
||||||
setCities,
|
|
||||||
setDistricts,
|
|
||||||
setGreetings,
|
|
||||||
setReferencesLoaded,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
||||||
@@ -17,7 +17,7 @@ interface Report {
|
|||||||
reporterName?: string;
|
reporterName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
const reports = ref<Report[]>([]);
|
const reports = ref<Report[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import { reactive, ref } from 'vue';
|
|||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { required, helpers } from '@vuelidate/validators';
|
import { required, helpers } from '@vuelidate/validators';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import AppInput from '@/components/common/AppInput.vue';
|
import AppInput from '@/components/common/AppInput.vue';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const form = reactive({ phone: '', password: '' });
|
const form = reactive({ phone: '', password: '' });
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { reactive, ref } from 'vue';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { required, minLength, helpers } from '@vuelidate/validators';
|
import { required, minLength, helpers } from '@vuelidate/validators';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import AppInput from '@/components/common/AppInput.vue';
|
import AppInput from '@/components/common/AppInput.vue';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const form = reactive({ phone: '', password: '', confirmPassword: '' });
|
const form = reactive({ phone: '', password: '', confirmPassword: '' });
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useChatStore } from '@/stores/chat.store';
|
import { useChat } from '@/composables/useChat';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import type { ChatMessage } from '@/stores/chat.store';
|
import type { ChatMessage } from '@/composables/useChat';
|
||||||
import ChatBubble from '@/components/chat/ChatBubble.vue';
|
import ChatBubble from '@/components/chat/ChatBubble.vue';
|
||||||
import ChatInput from '@/components/chat/ChatInput.vue';
|
import ChatInput from '@/components/chat/ChatInput.vue';
|
||||||
import AppModal from '@/components/common/AppModal.vue';
|
import AppModal from '@/components/common/AppModal.vue';
|
||||||
@@ -13,9 +13,9 @@ import AppButton from '@/components/common/AppButton.vue';
|
|||||||
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChat();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const chatId = route.params.chatId as string;
|
const chatId = route.params.chatId as string;
|
||||||
const profileId = computed(() => authStore.activeProfile?.id ?? '');
|
const profileId = computed(() => authStore.activeProfile?.id ?? '');
|
||||||
@@ -23,7 +23,7 @@ const messagesEnd = ref<HTMLElement | null>(null);
|
|||||||
const confirmClose = ref(false);
|
const confirmClose = ref(false);
|
||||||
|
|
||||||
const chat = computed(() => chatStore.chats.find((c) => c.id === chatId));
|
const chat = computed(() => chatStore.chats.find((c) => c.id === chatId));
|
||||||
const isLocked = computed(() => chat.value && !chat.value.isActive);
|
const isLocked = computed(() => chat.value?.status === 'closed');
|
||||||
|
|
||||||
// Group messages by date
|
// Group messages by date
|
||||||
const groupedMessages = computed(() => {
|
const groupedMessages = computed(() => {
|
||||||
@@ -66,12 +66,14 @@ async function send(text: string, mediaUrl?: string, mediaType?: 'photo' | 'voic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goBack() { window.history.back(); }
|
||||||
|
|
||||||
async function doCloseChat() {
|
async function doCloseChat() {
|
||||||
if (!profileId.value) return;
|
if (!profileId.value) return;
|
||||||
try {
|
try {
|
||||||
await chatStore.closeChat(chatId, profileId.value);
|
await chatStore.closeChat(chatId, profileId.value);
|
||||||
confirmClose.value = false;
|
confirmClose.value = false;
|
||||||
history.back();
|
goBack();
|
||||||
} catch {
|
} catch {
|
||||||
uiStore.addToast('Не удалось закрыть чат', 'error');
|
uiStore.addToast('Не удалось закрыть чат', 'error');
|
||||||
}
|
}
|
||||||
@@ -82,20 +84,20 @@ async function doCloseChat() {
|
|||||||
<div class="chat-room">
|
<div class="chat-room">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="chat-room__header">
|
<header class="chat-room__header">
|
||||||
<button class="chat-room__back" @click="history.back()" aria-label="Назад">
|
<button class="chat-room__back" @click="goBack()" aria-label="Назад">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="20" height="20">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="20" height="20">
|
||||||
<path d="M19 12H5M12 5l-7 7 7 7"/>
|
<path d="M19 12H5M12 5l-7 7 7 7"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="chat-room__partner">
|
<div class="chat-room__partner">
|
||||||
<img
|
<img
|
||||||
v-if="chat?.partner.avatarUrl"
|
v-if="chat?.partner?.avatarUrl"
|
||||||
:src="chat.partner.avatarUrl"
|
:src="chat.partner.avatarUrl"
|
||||||
class="chat-room__avatar"
|
class="chat-room__avatar"
|
||||||
:alt="chat.partner.name"
|
:alt="chat.partner?.name"
|
||||||
/>
|
/>
|
||||||
<div v-else class="chat-room__avatar chat-room__avatar--placeholder" />
|
<div v-else class="chat-room__avatar chat-room__avatar--placeholder" />
|
||||||
<span class="chat-room__name">{{ chat?.partner.name ?? 'Чат' }}</span>
|
<span class="chat-room__name">{{ chat?.partner?.name ?? 'Чат' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="chat-room__close-btn"
|
class="chat-room__close-btn"
|
||||||
@@ -135,7 +137,7 @@ async function doCloseChat() {
|
|||||||
v-for="msg in group.messages"
|
v-for="msg in group.messages"
|
||||||
:key="msg.id"
|
:key="msg.id"
|
||||||
:message="msg"
|
:message="msg"
|
||||||
:is-mine="msg.senderId === authStore.activeProfile?.id"
|
:is-mine="msg.profileId === authStore.activeProfile?.id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div ref="messagesEnd" />
|
<div ref="messagesEnd" />
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useChatStore } from '@/stores/chat.store';
|
import { useChat } from '@/composables/useChat';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
||||||
import EmptyState from '@/components/common/EmptyState.vue';
|
import EmptyState from '@/components/common/EmptyState.vue';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChat();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -55,13 +55,13 @@ function formatTime(dateStr: string) {
|
|||||||
<RouterLink
|
<RouterLink
|
||||||
:to="`/chats/${chat.id}`"
|
:to="`/chats/${chat.id}`"
|
||||||
class="chat-item"
|
class="chat-item"
|
||||||
:class="{ 'chat-item--inactive': !chat.isActive }"
|
:class="{ 'chat-item--inactive': chat.status === 'closed' }"
|
||||||
>
|
>
|
||||||
<div class="chat-item__avatar-wrap">
|
<div class="chat-item__avatar-wrap">
|
||||||
<img
|
<img
|
||||||
v-if="chat.partner.avatarUrl"
|
v-if="chat.partner?.avatarUrl"
|
||||||
:src="chat.partner.avatarUrl"
|
:src="chat.partner.avatarUrl"
|
||||||
:alt="chat.partner.name"
|
:alt="chat.partner?.name"
|
||||||
class="chat-item__avatar"
|
class="chat-item__avatar"
|
||||||
/>
|
/>
|
||||||
<div v-else class="chat-item__avatar chat-item__avatar--placeholder">
|
<div v-else class="chat-item__avatar chat-item__avatar--placeholder">
|
||||||
@@ -70,7 +70,7 @@ function formatTime(dateStr: string) {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<!-- Lock icon for inactive chats -->
|
<!-- Lock icon for inactive chats -->
|
||||||
<div v-if="!chat.isActive" class="chat-item__lock" aria-label="Чат неактивен">
|
<div v-if="chat.status === 'closed'" class="chat-item__lock" aria-label="Чат неактивен">
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="12" height="12">
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="12" height="12">
|
||||||
<rect x="3" y="7" width="10" height="8" rx="1"/>
|
<rect x="3" y="7" width="10" height="8" rx="1"/>
|
||||||
<path d="M5 7V5a3 3 0 0 1 6 0v2"/>
|
<path d="M5 7V5a3 3 0 0 1 6 0v2"/>
|
||||||
@@ -80,7 +80,7 @@ function formatTime(dateStr: string) {
|
|||||||
|
|
||||||
<div class="chat-item__content">
|
<div class="chat-item__content">
|
||||||
<div class="chat-item__top">
|
<div class="chat-item__top">
|
||||||
<span class="chat-item__name">{{ chat.partner.name }}</span>
|
<span class="chat-item__name">{{ chat.partner?.name }}</span>
|
||||||
<span v-if="chat.lastMessage" class="chat-item__time meta">
|
<span v-if="chat.lastMessage" class="chat-item__time meta">
|
||||||
{{ formatTime(chat.lastMessage.createdAt) }}
|
{{ formatTime(chat.lastMessage.createdAt) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -91,7 +91,7 @@ function formatTime(dateStr: string) {
|
|||||||
<p v-else class="chat-item__preview chat-item__preview--empty">Начните переписку</p>
|
<p v-else class="chat-item__preview chat-item__preview--empty">Начните переписку</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="chat.unreadCount > 0" class="chat-item__badge">{{ chat.unreadCount }}</div>
|
<div v-if="(chat.unreadCount ?? 0) > 0" class="chat-item__badge">{{ chat.unreadCount }}</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
import AppModal from '@/components/common/AppModal.vue';
|
import AppModal from '@/components/common/AppModal.vue';
|
||||||
@@ -22,8 +22,8 @@ interface DateItem {
|
|||||||
isIncoming: boolean;
|
isIncoming: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const dates = ref<DateItem[]>([]);
|
const dates = ref<DateItem[]>([]);
|
||||||
const statuses = ref<DateStatus[]>([]);
|
const statuses = ref<DateStatus[]>([]);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { useFeedStore } from '@/stores/feed.store';
|
import { useFeed } from '@/composables/useFeed';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import FeedCardStack from '@/components/feed/FeedCardStack.vue';
|
import FeedCardStack from '@/components/feed/FeedCardStack.vue';
|
||||||
import FeedFilters from '@/components/feed/FeedFilters.vue';
|
import FeedFilters from '@/components/feed/FeedFilters.vue';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
|
|
||||||
const feedStore = useFeedStore();
|
const feedStore = useFeed();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
|
|
||||||
const filtersOpen = ref(false);
|
const filtersOpen = ref(false);
|
||||||
const viewMode = ref<'stack' | 'scroll'>('stack');
|
const viewMode = ref<'stack' | 'scroll'>('stack');
|
||||||
@@ -83,8 +83,8 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<RouterLink :to="`/profile/${profile.id}`" class="feed-grid__link">
|
<RouterLink :to="`/profile/${profile.id}`" class="feed-grid__link">
|
||||||
<img
|
<img
|
||||||
v-if="profile.avatarUrl"
|
v-if="profile.media?.[0]?.path"
|
||||||
:src="profile.avatarUrl"
|
:src="profile.media[0].path"
|
||||||
:alt="profile.name"
|
:alt="profile.name"
|
||||||
class="feed-grid__img"
|
class="feed-grid__img"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useChatStore } from '@/stores/chat.store';
|
import { useChat } from '@/composables/useChat';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
@@ -23,9 +23,9 @@ interface Match {
|
|||||||
hasChat: boolean;
|
hasChat: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChat();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const matches = ref<Match[]>([]);
|
const matches = ref<Match[]>([]);
|
||||||
|
|||||||
@@ -3,35 +3,35 @@ import { reactive, ref, computed, watch } from 'vue';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { required, helpers } from '@vuelidate/validators';
|
import { required, helpers } from '@vuelidate/validators';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import type { CreateProfileDto } from '@/api/api';
|
import type { CreateProfileDto } from '@/api/api';
|
||||||
import type { UserProfile } from '@/stores/auth.store';
|
import type { UserProfile } from '@/composables/useAuth';
|
||||||
import type { District } from '@/stores/ui.store';
|
import type { District } from '@/composables/useUi';
|
||||||
import AppInput from '@/components/common/AppInput.vue';
|
import AppInput from '@/components/common/AppInput.vue';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const step = ref(1);
|
const step = ref(1);
|
||||||
const totalSteps = 4;
|
const totalSteps = 4;
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const form = reactive<CreateProfileDto & { confirmStep?: number }>({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
birthDate: '',
|
birthDate: '',
|
||||||
gender: 'female',
|
gender: 'female' as 'female' | 'male',
|
||||||
cityId: '',
|
cityId: '',
|
||||||
districtId: '',
|
districtId: '',
|
||||||
description: '',
|
description: '',
|
||||||
nation: '',
|
nation: '',
|
||||||
height: undefined,
|
height: undefined as number | undefined | null,
|
||||||
weight: undefined,
|
weight: undefined as number | undefined | null,
|
||||||
tagIds: [],
|
tagIds: [] as string[],
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedTags = ref<string[]>([]);
|
const selectedTags = ref<string[]>([]);
|
||||||
@@ -85,7 +85,7 @@ async function finish() {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
form.tagIds = selectedTags.value;
|
form.tagIds = selectedTags.value;
|
||||||
const profile = await apiClient.api.profilesControllerCreate(form) as unknown as UserProfile;
|
const profile = await apiClient.api.profilesControllerCreate(form as unknown as CreateProfileDto) as unknown as UserProfile;
|
||||||
authStore.addProfile(profile);
|
authStore.addProfile(profile);
|
||||||
uiStore.addToast('Профиль создан', 'success');
|
uiStore.addToast('Профиль создан', 'success');
|
||||||
router.replace('/feed');
|
router.replace('/feed');
|
||||||
@@ -207,7 +207,7 @@ function skip() {
|
|||||||
:class="{ 'setup__tag--active': selectedTags.includes(tag.id) }"
|
:class="{ 'setup__tag--active': selectedTags.includes(tag.id) }"
|
||||||
@click="toggleTag(tag.id)"
|
@click="toggleTag(tag.id)"
|
||||||
>
|
>
|
||||||
{{ tag.name }}
|
{{ tag.value }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="uiStore.tags.length === 0" class="setup__hint setup__hint--muted">Теги загружаются...</p>
|
<p v-if="uiStore.tags.length === 0" class="setup__hint setup__hint--muted">Теги загружаются...</p>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import type { UserProfile } from '@/stores/auth.store';
|
import type { UserProfile } from '@/composables/useAuth';
|
||||||
import ProfileEditor from '@/components/profile/ProfileEditor.vue';
|
import ProfileEditor from '@/components/profile/ProfileEditor.vue';
|
||||||
import MediaGallery from '@/components/profile/MediaGallery.vue';
|
import MediaGallery from '@/components/profile/MediaGallery.vue';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
import AppModal from '@/components/common/AppModal.vue';
|
import AppModal from '@/components/common/AppModal.vue';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
|
|
||||||
const editing = ref(false);
|
const editing = ref(false);
|
||||||
const confirmDelete = ref(false);
|
const confirmDelete = ref(false);
|
||||||
@@ -41,12 +41,12 @@ function logout() {
|
|||||||
authStore.logout();
|
authStore.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cityName(cityId?: string) {
|
function cityName(cityId?: string | null) {
|
||||||
return uiStore.cities.find((c) => c.id === cityId)?.name ?? '';
|
return uiStore.cities.find((c) => c.id === cityId)?.name ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function tagNames(tagIds?: string[]) {
|
function tagValues(tags?: Array<{ id: string; value: string }>) {
|
||||||
return (tagIds ?? []).map((id) => uiStore.tags.find((t) => t.id === id)?.name ?? id);
|
return (tags ?? []).map((t) => t.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcAge(birthDate: string) {
|
function calcAge(birthDate: string) {
|
||||||
@@ -66,8 +66,8 @@ function calcAge(birthDate: string) {
|
|||||||
<div class="my-profile__hero">
|
<div class="my-profile__hero">
|
||||||
<div class="my-profile__avatar-wrap">
|
<div class="my-profile__avatar-wrap">
|
||||||
<img
|
<img
|
||||||
v-if="profile.avatarUrl"
|
v-if="profile.media?.[0]?.path"
|
||||||
:src="profile.avatarUrl"
|
:src="profile.media[0].path"
|
||||||
:alt="profile.name"
|
:alt="profile.name"
|
||||||
class="my-profile__avatar"
|
class="my-profile__avatar"
|
||||||
/>
|
/>
|
||||||
@@ -113,10 +113,10 @@ function calcAge(birthDate: string) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
<div v-if="profile.tagIds?.length" class="my-profile__section">
|
<div v-if="profile.tags?.length" class="my-profile__section">
|
||||||
<h3 class="my-profile__section-title">Интересы</h3>
|
<h3 class="my-profile__section-title">Интересы</h3>
|
||||||
<div class="my-profile__tags">
|
<div class="my-profile__tags">
|
||||||
<span v-for="name in tagNames(profile.tagIds)" :key="name" class="my-profile__tag">{{ name }}</span>
|
<span v-for="name in tagValues(profile.tags)" :key="name" class="my-profile__tag">{{ name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useAuthStore } from '@/stores/auth.store';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import { useUiStore } from '@/stores/ui.store';
|
import { useUi } from '@/composables/useUi';
|
||||||
import { useChatStore } from '@/stores/chat.store';
|
import { useChat } from '@/composables/useChat';
|
||||||
import { apiClient } from '@/api/client';
|
import { apiClient } from '@/api/client';
|
||||||
import type { UserProfile } from '@/stores/auth.store';
|
import type { UserProfile } from '@/composables/useAuth';
|
||||||
import AppButton from '@/components/common/AppButton.vue';
|
import AppButton from '@/components/common/AppButton.vue';
|
||||||
import AppModal from '@/components/common/AppModal.vue';
|
import AppModal from '@/components/common/AppModal.vue';
|
||||||
import ReportModal from '@/components/reports/ReportModal.vue';
|
import ReportModal from '@/components/reports/ReportModal.vue';
|
||||||
@@ -13,9 +13,9 @@ import DateProposalForm from '@/components/dates/DateProposalForm.vue';
|
|||||||
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuth();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUi();
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChat();
|
||||||
|
|
||||||
const profileId = route.params.profileId as string;
|
const profileId = route.params.profileId as string;
|
||||||
const profile = ref<UserProfile | null>(null);
|
const profile = ref<UserProfile | null>(null);
|
||||||
@@ -44,13 +44,18 @@ const age = computed(() => {
|
|||||||
return a;
|
return a;
|
||||||
});
|
});
|
||||||
|
|
||||||
const cityName = computed(() => uiStore.cities.find((c) => c.id === profile.value?.cityId)?.name ?? '');
|
const cityName = computed(() => {
|
||||||
const tagNames = computed(() => (profile.value?.tagIds ?? []).map((id) => uiStore.tags.find((t) => t.id === id)?.name ?? id));
|
const cid = profile.value?.cityId;
|
||||||
|
return cid ? uiStore.cities.find((c) => c.id === cid)?.name ?? '' : '';
|
||||||
|
});
|
||||||
|
const tagNames = computed(() => profile.value?.tags?.map((t) => t.value) ?? []);
|
||||||
const currentImageIndex = ref(0);
|
const currentImageIndex = ref(0);
|
||||||
|
|
||||||
const isOwnProfile = computed(() =>
|
const isOwnProfile = computed(() =>
|
||||||
authStore.profiles.some((p) => p.id === profileId),
|
authStore.profiles.some((p) => p.id === profileId),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function goBack() { window.history.back(); }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -63,8 +68,8 @@ const isOwnProfile = computed(() =>
|
|||||||
<!-- Image strip -->
|
<!-- Image strip -->
|
||||||
<div class="profile-detail__cover">
|
<div class="profile-detail__cover">
|
||||||
<img
|
<img
|
||||||
v-if="profile.mediaUrls?.[currentImageIndex]"
|
v-if="profile.media?.[currentImageIndex]?.path"
|
||||||
:src="profile.mediaUrls[currentImageIndex]"
|
:src="profile.media[currentImageIndex].path"
|
||||||
:alt="profile.name"
|
:alt="profile.name"
|
||||||
class="profile-detail__cover-img"
|
class="profile-detail__cover-img"
|
||||||
/>
|
/>
|
||||||
@@ -78,14 +83,14 @@ const isOwnProfile = computed(() =>
|
|||||||
aria-label="Предыдущее фото"
|
aria-label="Предыдущее фото"
|
||||||
>‹</button>
|
>‹</button>
|
||||||
<button
|
<button
|
||||||
v-if="currentImageIndex < (profile.mediaUrls?.length ?? 1) - 1"
|
v-if="currentImageIndex < (profile.media?.length ?? 1) - 1"
|
||||||
class="profile-detail__img-nav profile-detail__img-nav--next"
|
class="profile-detail__img-nav profile-detail__img-nav--next"
|
||||||
@click="currentImageIndex++"
|
@click="currentImageIndex++"
|
||||||
aria-label="Следующее фото"
|
aria-label="Следующее фото"
|
||||||
>›</button>
|
>›</button>
|
||||||
|
|
||||||
<!-- Back button -->
|
<!-- Back button -->
|
||||||
<button class="profile-detail__back" @click="history.back()" aria-label="Назад">
|
<button class="profile-detail__back" @click="goBack()" aria-label="Назад">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20">
|
||||||
<path d="M19 12H5M12 5l-7 7 7 7"/>
|
<path d="M19 12H5M12 5l-7 7 7 7"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
Reference in New Issue
Block a user