From 78fe3b5a988e80742ce96b7e7f452bed37a7b4fc Mon Sep 17 00:00:00 2001 From: Oscar Date: Tue, 9 Jun 2026 10:29:52 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(src/composables/useFeed.ts):?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D1=83=20=D0=B1?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=BE=D0=BD=D0=B5=D1=87=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BA=D1=80=D1=83=D1=82=D0=BA=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BB=D0=B5=D0=BD=D1=82=D1=8B=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BE=D1=81=D1=82=D0=B5=D0=B9=20=E2=9C=A8=20feat(package?= =?UTF-8?q?.json):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5?= =?UTF-8?q?=D1=82=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D1=81=D0=BF=D1=80=D0=B0=D0=B9=D1=82=D0=B0=20=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?=F0=9F=94=A7=20refactor(src/composables/useChat.ts):=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=89=D0=B0=D0=B5=D1=82=20=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D0=BA=D1=83=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=87=D0=B0=D1=82=D0=BE=D0=BC,=20=D1=83?= =?UTF-8?q?=D0=B1=D0=B8=D1=80=D0=B0=D0=B5=D1=82=20=D1=83=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=88=D0=B8=D0=B5=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=F0=9F=94=A7=20refactor(src/views/chat/Cha?= =?UTF-8?q?tRoomView.vue):=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20=D1=81=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=D0=BC=20=D0=BD=D0=BE=D0=B2=D1=8B=D1=85=20=D1=85=D1=83?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=20=F0=9F=94=A7=20refactor(src/views/matches/?= =?UTF-8?q?MatchesView.vue):=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D1=83?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B2=D0=BF=D0=B0=D0=B4=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=20=D1=85=D1=83=D0=BA=D0=BE=D0=B2=20=E2=9C=A8=20feat(src/?= =?UTF-8?q?App.vue):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D1=83=20?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D1=87=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D1=81=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=D0=BC=20Vue=20Query=20=E2=9C=A8=20feat(src/main.ts):=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B8=D1=80=D1=83=D0=B5?= =?UTF-8?q?=D1=82=20Vue=20Query=20=D0=B2=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=BE=D0=B2=20=E2=9C=A8=20feat(s?= =?UTF-8?q?rc/views/profile/MyProfileView.vue):=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BC=D1=83=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE=D1=84=D0=B8=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Vue=20Query=20?= =?UTF-8?q?=F0=9F=94=A7=20refactor(src/components/layout/BottomNav.vue):?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D1=8F=D0=B5=D1=82=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D0=BA=D0=B8=20=D0=BD=D0=B0=20=D0=B1=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=D0=B5=20=D1=83=D0=BD=D0=B8=D0=B2=D0=B5=D1=80=D1=81=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=82=20AppIcon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- pnpm-lock.yaml | 38 +++++++++ scripts/generate-sprite.js | 49 +++++++++++ src/App.vue | 43 +++++----- src/assets/icons/calendar.svg | 3 + src/assets/icons/chat.svg | 3 + src/assets/icons/check.svg | 3 + src/assets/icons/chevron-left.svg | 3 + src/assets/icons/chevron-right.svg | 3 + src/assets/icons/edit.svg | 4 + src/assets/icons/flag.svg | 3 + src/assets/icons/grid.svg | 3 + src/assets/icons/heart.svg | 3 + src/assets/icons/icon-names.ts | 19 +++++ src/assets/icons/image.svg | 5 ++ src/assets/icons/mic.svg | 5 ++ src/assets/icons/person.svg | 4 + src/assets/icons/plus.svg | 3 + src/assets/icons/search.svg | 4 + src/assets/icons/send.svg | 4 + src/assets/icons/sprite.svg | 64 ++++++++++++++ src/assets/icons/trash.svg | 5 ++ src/assets/icons/x.svg | 3 + src/components/common/AppIcon.vue | 37 ++++++++ src/components/feed/FeedCardStack.vue | 15 ++-- src/components/feed/FeedFilters.vue | 4 +- src/components/layout/BottomNav.vue | 26 +----- src/components/layout/SideNav.vue | 34 ++------ src/composables/useChat.ts | 108 +---------------------- src/composables/useFeed.ts | 109 ++++++++++++++---------- src/main.ts | 20 +++++ src/queries/keys.ts | 17 ++++ src/queries/useChatsQuery.ts | 44 ++++++++++ src/queries/useDatesQuery.ts | 34 ++++++++ src/queries/useMatchesQuery.ts | 26 ++++++ src/queries/useMessagesQuery.ts | 38 +++++++++ src/queries/useProfileQuery.ts | 24 ++++++ src/queries/useReportsQuery.ts | 16 ++++ src/views/admin/ReportsView.vue | 24 ++---- src/views/chat/ChatRoomView.vue | 44 ++++------ src/views/chat/ChatsListView.vue | 29 ++----- src/views/dates/DatesView.vue | 47 +++------- src/views/feed/FeedView.vue | 11 +-- src/views/matches/MatchesView.vue | 55 +++--------- src/views/profile/MyProfileView.vue | 11 +-- src/views/profile/ProfileDetailView.vue | 23 +---- 46 files changed, 658 insertions(+), 416 deletions(-) create mode 100644 scripts/generate-sprite.js create mode 100644 src/assets/icons/calendar.svg create mode 100644 src/assets/icons/chat.svg create mode 100644 src/assets/icons/check.svg create mode 100644 src/assets/icons/chevron-left.svg create mode 100644 src/assets/icons/chevron-right.svg create mode 100644 src/assets/icons/edit.svg create mode 100644 src/assets/icons/flag.svg create mode 100644 src/assets/icons/grid.svg create mode 100644 src/assets/icons/heart.svg create mode 100644 src/assets/icons/icon-names.ts create mode 100644 src/assets/icons/image.svg create mode 100644 src/assets/icons/mic.svg create mode 100644 src/assets/icons/person.svg create mode 100644 src/assets/icons/plus.svg create mode 100644 src/assets/icons/search.svg create mode 100644 src/assets/icons/send.svg create mode 100644 src/assets/icons/sprite.svg create mode 100644 src/assets/icons/trash.svg create mode 100644 src/assets/icons/x.svg create mode 100644 src/components/common/AppIcon.vue create mode 100644 src/queries/keys.ts create mode 100644 src/queries/useChatsQuery.ts create mode 100644 src/queries/useDatesQuery.ts create mode 100644 src/queries/useMatchesQuery.ts create mode 100644 src/queries/useMessagesQuery.ts create mode 100644 src/queries/useProfileQuery.ts create mode 100644 src/queries/useReportsQuery.ts diff --git a/package.json b/package.json index fee7b58..39d6abb 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,15 @@ "private": true, "scripts": { "dev": "vite", - "build": "vue-tsc && vite build", + "build": "node scripts/generate-sprite.js && vue-tsc && vite build", + "icons": "node scripts/generate-sprite.js", "preview": "vite preview", "tauri": "tauri", "lint": "eslint . --fix", "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": { + "@tanstack/vue-query": "^5.101.0", "@floating-ui/vue": "^1.1.5", "@tauri-apps/api": "^2", "@tauri-apps/plugin-dialog": "^2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ee07cb..59c8019 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ importers: '@floating-ui/vue': specifier: ^1.1.5 version: 1.1.11(vue@3.5.35(typescript@5.9.3)) + '@tanstack/vue-query': + specifier: ^5.101.0 + version: 5.101.0(vue@3.5.35(typescript@5.9.3)) '@tauri-apps/api': specifier: ^2 version: 2.11.0 @@ -1086,6 +1089,22 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 + '@tanstack/match-sorter-utils@8.19.4': + resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==} + engines: {node: '>=12'} + + '@tanstack/query-core@5.101.0': + resolution: {integrity: sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==} + + '@tanstack/vue-query@5.101.0': + resolution: {integrity: sha512-sZeW0RvfEZ9QRiaXirE/HQZsFT5saMlPZVfeYvjPX6lqSBS9lkD7wfnCfzOBns6HD2f34Gx9cazkuU3Xj6hl/A==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.6.0 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + '@tauri-apps/api@2.11.0': resolution: {integrity: sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==} @@ -2457,6 +2476,9 @@ packages: resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + reserved-identifiers@1.2.0: resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} engines: {node: '>=18'} @@ -3373,6 +3395,20 @@ snapshots: tailwindcss: 4.3.0 vite: 6.4.3(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.100.0)(yaml@2.9.0) + '@tanstack/match-sorter-utils@8.19.4': + dependencies: + remove-accents: 0.5.0 + + '@tanstack/query-core@5.101.0': {} + + '@tanstack/vue-query@5.101.0(vue@3.5.35(typescript@5.9.3))': + dependencies: + '@tanstack/match-sorter-utils': 8.19.4 + '@tanstack/query-core': 5.101.0 + '@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)) + '@tauri-apps/api@2.11.0': {} '@tauri-apps/cli-darwin-arm64@2.11.2': @@ -4998,6 +5034,8 @@ snapshots: dependencies: jsesc: 3.1.0 + remove-accents@0.5.0: {} + reserved-identifiers@1.2.0: {} resolve-pkg-maps@1.0.0: {} diff --git a/scripts/generate-sprite.js b/scripts/generate-sprite.js new file mode 100644 index 0000000..5e79593 --- /dev/null +++ b/scripts/generate-sprite.js @@ -0,0 +1,49 @@ +import { readFileSync, writeFileSync, readdirSync } from 'fs' +import { join, basename } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const iconsDir = join(__dirname, '../src/assets/icons') + +const svgFiles = readdirSync(iconsDir) + .filter(f => f.endsWith('.svg')) + .sort() + +if (!svgFiles.length) { + console.error('No SVG files found in', iconsDir) + process.exit(1) +} + +// Extract viewBox and inner content from each SVG file +const symbols = svgFiles.map((file) => { + const name = basename(file, '.svg') + const raw = readFileSync(join(iconsDir, file), 'utf-8') + + const viewBox = (raw.match(/viewBox="([^"]+)"/) ?? [])[1] ?? '0 0 24 24' + + // Everything between and + const inner = (raw.match(/]*>([\s\S]*?)<\/svg>/i) ?? [])[1] + ?.trim() + .replace(/\n\s*/g, '\n ') ?? '' + + return ` \n ${inner}\n ` +}) + +// --- sprite.svg --- +const sprite = ` + +${symbols.join('\n')} + +` +writeFileSync(join(iconsDir, 'sprite.svg'), sprite) + +// --- icon-names.ts (IconName union type) --- +const names = svgFiles.map(f => `'${basename(f, '.svg')}'`).join('\n | ') +const meta = `// Generated by scripts/generate-sprite.js — do not edit manually +export type IconName = + | ${names} +` +writeFileSync(join(iconsDir, 'icon-names.ts'), meta) + +console.log(`✓ sprite.svg — ${svgFiles.length} icons`) +console.log('✓ icon-names.ts') diff --git a/src/App.vue b/src/App.vue index d7af000..a63d9b1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,34 +15,37 @@ diff --git a/src/components/feed/FeedCardStack.vue b/src/components/feed/FeedCardStack.vue index adb32d1..bf3fa19 100644 --- a/src/components/feed/FeedCardStack.vue +++ b/src/components/feed/FeedCardStack.vue @@ -56,14 +56,14 @@ async function handleLike(profileId: string) { const activeProfile = authStore.activeProfile if (!activeProfile) return + feedStore.removeCard(profileId) + checkRefill() try { await apiClient.api.likesControllerCreateLike({ sourceProfileId: activeProfile.id, targetProfileId: profileId, type: 'like', }) - feedStore.removeCard(profileId) - checkRefill() } catch { uiStore.addToast('Не удалось отправить лайк', 'error') @@ -74,26 +74,23 @@ async function handleDislike(profileId: string) { const activeProfile = authStore.activeProfile if (!activeProfile) return + feedStore.removeCard(profileId) + checkRefill() try { await apiClient.api.likesControllerCreateLike({ sourceProfileId: activeProfile.id, targetProfileId: profileId, type: 'dislike', }) - feedStore.removeCard(profileId) - checkRefill() } catch { - feedStore.removeCard(profileId) + // Dislike failures are silent — card is already removed } } function checkRefill() { - const activeProfile = authStore.activeProfile - if (!activeProfile) - return if (feedStore.cards.length < 5 && feedStore.hasMore) { - feedStore.fetchNextPage(activeProfile.id) + feedStore.fetchNextPage() } } diff --git a/src/components/feed/FeedFilters.vue b/src/components/feed/FeedFilters.vue index e2580df..32cf654 100644 --- a/src/components/feed/FeedFilters.vue +++ b/src/components/feed/FeedFilters.vue @@ -122,9 +122,7 @@ function toggleTag(tagId: string) { function apply() { feedStore.applyFilters({ ...filters }) - const profileId = authStore.activeProfile?.id - if (profileId) - feedStore.fetchNextPage(profileId) + feedStore.fetchNextPage() emit('close') } diff --git a/src/components/layout/BottomNav.vue b/src/components/layout/BottomNav.vue index baa5597..0776026 100644 --- a/src/components/layout/BottomNav.vue +++ b/src/components/layout/BottomNav.vue @@ -10,7 +10,7 @@ :aria-current="isActive(item.path) ? 'page' : undefined" > - + {{ item.label }} @@ -19,10 +19,12 @@ - -