diff --git a/package.json b/package.json index 5b4a789..a73828f 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint-plugin-vue": "^9.32.0", "knip": "^5.45.0", "openapi-typescript": "^7.6.1", + "postcss-html": "^1.8.0", "postcss-preset-env": "^10.1.5", "prettier": "^3.5.2", "stylelint": "^16.15.0", @@ -53,6 +54,6 @@ "vite-plugin-vuetify": "^2.1.0", "vite-svg-loader": "^5.1.0", "vue-tsc": "^2.2.6", - "vuetify": "^3.7.14" + "vuetify": "^3.9.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9625b8d..0637a84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: openapi-typescript: specifier: ^7.6.1 version: 7.6.1(typescript@5.8.2) + postcss-html: + specifier: ^1.8.0 + version: 1.8.0 postcss-preset-env: specifier: ^10.1.5 version: 10.1.5(postcss@8.5.3) @@ -86,7 +89,7 @@ importers: version: 15.0.0(stylelint@16.15.0(typescript@5.8.2)) stylelint-config-recommended-vue: specifier: ^1.6.0 - version: 1.6.0(postcss-html@1.7.0)(stylelint@16.15.0(typescript@5.8.2)) + version: 1.6.0(postcss-html@1.8.0)(stylelint@16.15.0(typescript@5.8.2)) stylelint-config-standard: specifier: ^37.0.0 version: 37.0.0(stylelint@16.15.0(typescript@5.8.2)) @@ -107,7 +110,7 @@ importers: version: 6.0.0(postcss@8.5.3)(rollup@4.34.9)(stylelint@16.15.0(typescript@5.8.2))(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)) vite-plugin-vuetify: specifier: ^2.1.0 - version: 2.1.0(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2))(vue@3.5.13(typescript@5.8.2))(vuetify@3.7.14) + version: 2.1.0(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2))(vue@3.5.13(typescript@5.8.2))(vuetify@3.9.5) vite-svg-loader: specifier: ^5.1.0 version: 5.1.0(vue@3.5.13(typescript@5.8.2)) @@ -115,8 +118,8 @@ importers: specifier: ^2.2.6 version: 2.2.6(typescript@5.8.2) vuetify: - specifier: ^3.7.14 - version: 3.7.14(typescript@5.8.2)(vite-plugin-vuetify@2.1.0)(vue@3.5.13(typescript@5.8.2)) + specifier: ^3.9.5 + version: 3.9.5(typescript@5.8.2)(vite-plugin-vuetify@2.1.0)(vue@3.5.13(typescript@5.8.2)) packages: @@ -1898,8 +1901,8 @@ packages: peerDependencies: postcss: ^8.4 - postcss-html@1.7.0: - resolution: {integrity: sha512-MfcMpSUIaR/nNgeVS8AyvyDugXlADjN9AcV7e5rDfrF1wduIAGSkL4q2+wgrZgA3sHVAHLDO9FuauHhZYW2nBw==} + postcss-html@1.8.0: + resolution: {integrity: sha512-5mMeb1TgLWoRKxZ0Xh9RZDfwUUIqRrcxO2uXO+Ezl1N5lqpCiSU5Gk6+1kZediBfBHFtPCdopr2UZ2SgUsKcgQ==} engines: {node: ^12 || >=14} postcss-image-set-function@7.0.0: @@ -2384,14 +2387,14 @@ packages: typescript: optional: true - vuetify@3.7.14: - resolution: {integrity: sha512-L5oD0x3Wrp49Khp+16dRykAFUZq6WXqX7OHLTwXnyEliJ48ERC9WfEtN0YOuEIhor/b2IOZXl+11fs/m452/Lw==} + vuetify@3.9.5: + resolution: {integrity: sha512-rJBSo1FeKcJJaqTfVHbOdpFXCmgeEIGzrh6HBEMGjSflan6voPIMSiK2dTCUU4t9JeghwvJtoAE5eBuqYIkFVA==} engines: {node: ^12.20 || >=14.13} peerDependencies: typescript: '>=4.7' - vite-plugin-vuetify: '>=1.0.0' - vue: ^3.3.0 - webpack-plugin-vuetify: '>=2.0.0' + vite-plugin-vuetify: '>=2.1.0' + vue: ^3.5.0 + webpack-plugin-vuetify: '>=3.1.0' peerDependenciesMeta: typescript: optional: true @@ -3223,11 +3226,11 @@ snapshots: '@vue/shared@3.5.13': {} - '@vuetify/loader-shared@2.1.0(vue@3.5.13(typescript@5.8.2))(vuetify@3.7.14)': + '@vuetify/loader-shared@2.1.0(vue@3.5.13(typescript@5.8.2))(vuetify@3.9.5)': dependencies: upath: 2.0.1 vue: 3.5.13(typescript@5.8.2) - vuetify: 3.7.14(typescript@5.8.2)(vite-plugin-vuetify@2.1.0)(vue@3.5.13(typescript@5.8.2)) + vuetify: 3.9.5(typescript@5.8.2)(vite-plugin-vuetify@2.1.0)(vue@3.5.13(typescript@5.8.2)) acorn-jsx@5.3.2(acorn@8.14.0): dependencies: @@ -4209,7 +4212,7 @@ snapshots: dependencies: postcss: 8.5.3 - postcss-html@1.7.0: + postcss-html@1.8.0: dependencies: htmlparser2: 8.0.2 js-tokens: 9.0.1 @@ -4504,17 +4507,17 @@ snapshots: strip-json-comments@5.0.1: {} - stylelint-config-html@1.1.0(postcss-html@1.7.0)(stylelint@16.15.0(typescript@5.8.2)): + stylelint-config-html@1.1.0(postcss-html@1.8.0)(stylelint@16.15.0(typescript@5.8.2)): dependencies: - postcss-html: 1.7.0 + postcss-html: 1.8.0 stylelint: 16.15.0(typescript@5.8.2) - stylelint-config-recommended-vue@1.6.0(postcss-html@1.7.0)(stylelint@16.15.0(typescript@5.8.2)): + stylelint-config-recommended-vue@1.6.0(postcss-html@1.8.0)(stylelint@16.15.0(typescript@5.8.2)): dependencies: - postcss-html: 1.7.0 + postcss-html: 1.8.0 semver: 7.7.1 stylelint: 16.15.0(typescript@5.8.2) - stylelint-config-html: 1.1.0(postcss-html@1.7.0)(stylelint@16.15.0(typescript@5.8.2)) + stylelint-config-html: 1.1.0(postcss-html@1.8.0)(stylelint@16.15.0(typescript@5.8.2)) stylelint-config-recommended: 15.0.0(stylelint@16.15.0(typescript@5.8.2)) stylelint-config-recommended@15.0.0(stylelint@16.15.0(typescript@5.8.2)): @@ -4680,14 +4683,14 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-vuetify@2.1.0(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2))(vue@3.5.13(typescript@5.8.2))(vuetify@3.7.14): + vite-plugin-vuetify@2.1.0(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2))(vue@3.5.13(typescript@5.8.2))(vuetify@3.9.5): dependencies: - '@vuetify/loader-shared': 2.1.0(vue@3.5.13(typescript@5.8.2))(vuetify@3.7.14) + '@vuetify/loader-shared': 2.1.0(vue@3.5.13(typescript@5.8.2))(vuetify@3.9.5) debug: 4.4.0(supports-color@9.4.0) upath: 2.0.1 vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2) vue: 3.5.13(typescript@5.8.2) - vuetify: 3.7.14(typescript@5.8.2)(vite-plugin-vuetify@2.1.0)(vue@3.5.13(typescript@5.8.2)) + vuetify: 3.9.5(typescript@5.8.2)(vite-plugin-vuetify@2.1.0)(vue@3.5.13(typescript@5.8.2)) transitivePeerDependencies: - supports-color @@ -4742,12 +4745,12 @@ snapshots: optionalDependencies: typescript: 5.8.2 - vuetify@3.7.14(typescript@5.8.2)(vite-plugin-vuetify@2.1.0)(vue@3.5.13(typescript@5.8.2)): + vuetify@3.9.5(typescript@5.8.2)(vite-plugin-vuetify@2.1.0)(vue@3.5.13(typescript@5.8.2)): dependencies: vue: 3.5.13(typescript@5.8.2) optionalDependencies: typescript: 5.8.2 - vite-plugin-vuetify: 2.1.0(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2))(vue@3.5.13(typescript@5.8.2))(vuetify@3.7.14) + vite-plugin-vuetify: 2.1.0(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2))(vue@3.5.13(typescript@5.8.2))(vuetify@3.9.5) wcwidth@1.0.1: dependencies: diff --git a/src/App.vue b/src/App.vue index 402c126..e9d7466 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,24 +1,24 @@ - - - - + + + + + + + + diff --git a/src/components/achievements/AchievementCreateForm.vue b/src/components/achievements/AchievementCreateForm.vue new file mode 100644 index 0000000..384f165 --- /dev/null +++ b/src/components/achievements/AchievementCreateForm.vue @@ -0,0 +1,34 @@ + + + + + + Название + + + + Описание + + + + Иконка + + + Создать + + + + diff --git a/src/components/achievements/AchievementDeleteButton.vue b/src/components/achievements/AchievementDeleteButton.vue new file mode 100644 index 0000000..2b5d8b1 --- /dev/null +++ b/src/components/achievements/AchievementDeleteButton.vue @@ -0,0 +1,39 @@ + + + + + + Удалить + + + + Удалить {{ id }}? + + Нет + Да + + + + + + diff --git a/src/components/achievements/AchievementEditForm.vue b/src/components/achievements/AchievementEditForm.vue new file mode 100644 index 0000000..04aca7e --- /dev/null +++ b/src/components/achievements/AchievementEditForm.vue @@ -0,0 +1,32 @@ + + + + + Изменить достижение + + + Название + + + + + Описание + + + Изменить + + + + diff --git a/src/components/achievements/AchievementGiveForm.vue b/src/components/achievements/AchievementGiveForm.vue new file mode 100644 index 0000000..784ee01 --- /dev/null +++ b/src/components/achievements/AchievementGiveForm.vue @@ -0,0 +1,50 @@ + + + + + + Номер достижения + + + + User id + + + Выдать + + + + diff --git a/src/components/achievements/AchievementPictureForm.vue b/src/components/achievements/AchievementPictureForm.vue new file mode 100644 index 0000000..0be08e1 --- /dev/null +++ b/src/components/achievements/AchievementPictureForm.vue @@ -0,0 +1,25 @@ + + + + + Загрузить новую картинку + + Иконка + + + Отправить + + + + diff --git a/src/components/achievements/AchievementReceiversTable.vue b/src/components/achievements/AchievementReceiversTable.vue new file mode 100644 index 0000000..e3f8b02 --- /dev/null +++ b/src/components/achievements/AchievementReceiversTable.vue @@ -0,0 +1,41 @@ + + + + Получатели достижения {{ achievementId }} + + + + User Id + Действия + + + + + + {{ user_id }} + + Отозвать + Подробнее + + + + + + + diff --git a/src/components/achievements/AchievementTable.vue b/src/components/achievements/AchievementTable.vue new file mode 100644 index 0000000..3861f12 --- /dev/null +++ b/src/components/achievements/AchievementTable.vue @@ -0,0 +1,40 @@ + + + + + + + ID + Название + Описание + Ссылка на иконку + ID создателя + Действия + + + + + + {{ id }} + {{ name }} + {{ description }} + {{ picture }} + {{ owner_user_id }} + + + + + + + + + diff --git a/src/components/achievements/UserAchievementsTable.vue b/src/components/achievements/UserAchievementsTable.vue new file mode 100644 index 0000000..8e2bfd8 --- /dev/null +++ b/src/components/achievements/UserAchievementsTable.vue @@ -0,0 +1,48 @@ + + + + Пользователь {{ userId }} + + + + ID + Название + Описание + Ссылка на иконку + ID создателя + Действия + + + + + + {{ id }} + {{ name }} + {{ description }} + {{ picture }} + {{ owner_user_id }} + + Отозвать + + + + + + + diff --git a/src/models/achievements.ts b/src/models/achievements.ts new file mode 100644 index 0000000..4ac6fa3 --- /dev/null +++ b/src/models/achievements.ts @@ -0,0 +1,11 @@ +export interface Achievement { + id: number; + name: string; + description: string; + picture: string | null; + owner_user_id: number; +} + +export interface Receiver { + user_id: number; +} diff --git a/src/pages/DebugPage.vue b/src/pages/DebugPage.vue new file mode 100644 index 0000000..d2e5f15 --- /dev/null +++ b/src/pages/DebugPage.vue @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/pages/MainPage.vue b/src/pages/MainPage.vue deleted file mode 100644 index cc109f1..0000000 --- a/src/pages/MainPage.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - Привет, {{ profileStore.full_name }}! - - - Не удалось получить твое имя из - Userdata API - =( - - Твой id: {{ profileStore.id }} - - - Документация к этому коду находится по адресу {{ location }} - - - - diff --git a/src/pages/StartScreen.vue b/src/pages/StartScreen.vue new file mode 100644 index 0000000..8305620 --- /dev/null +++ b/src/pages/StartScreen.vue @@ -0,0 +1,46 @@ + + + + Админка Твой ФФ + + Сетап админской сессии + + Токен + + + Отправить + + + Ачивки + + + diff --git a/src/pages/achievements/AchievementCreate.vue b/src/pages/achievements/AchievementCreate.vue new file mode 100644 index 0000000..077ddf8 --- /dev/null +++ b/src/pages/achievements/AchievementCreate.vue @@ -0,0 +1,10 @@ + + + + Создание достижения + + + + diff --git a/src/pages/achievements/AchievementGive.vue b/src/pages/achievements/AchievementGive.vue new file mode 100644 index 0000000..b2fb0f9 --- /dev/null +++ b/src/pages/achievements/AchievementGive.vue @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/pages/achievements/AchievementView.vue b/src/pages/achievements/AchievementView.vue new file mode 100644 index 0000000..d849131 --- /dev/null +++ b/src/pages/achievements/AchievementView.vue @@ -0,0 +1,46 @@ + + + + {{ id }}. {{ name }} | Создана {{ owner_user_id }} + {{ description }} + + + + + + + + + + diff --git a/src/pages/achievements/AchievementsList.vue b/src/pages/achievements/AchievementsList.vue new file mode 100644 index 0000000..e4253d2 --- /dev/null +++ b/src/pages/achievements/AchievementsList.vue @@ -0,0 +1,22 @@ + + + + + Список достижений + + + + Создать + + + Выдать + + + + + + + + diff --git a/src/pages/achievements/UserAchievements.vue b/src/pages/achievements/UserAchievements.vue new file mode 100644 index 0000000..9d6d2aa --- /dev/null +++ b/src/pages/achievements/UserAchievements.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index ca3a585..42882f1 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -3,7 +3,31 @@ import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router'; const routes: RouteRecordRaw[] = [ { path: '/', - component: () => import('../pages/MainPage.vue'), + component: () => import('../pages/StartScreen.vue'), + }, + { + path: '/achievements', + component: () => import('../pages/achievements/AchievementsList.vue'), + }, + { + path: '/achievements/:id', + component: () => import('../pages/achievements/AchievementView.vue'), + }, + { + path: '/achievements/create', + component: () => import('../pages/achievements/AchievementCreate.vue'), + }, + { + path: '/achievements/give', + component: () => import('../pages/achievements/AchievementGive.vue'), + }, + { + path: '/achievements/user/:id', + component: () => import('../pages/achievements/UserAchievements.vue'), + }, + { + path: '/debug', + component: () => import('../pages/DebugPage.vue'), }, ]; diff --git a/src/store/achievementStore.ts b/src/store/achievementStore.ts new file mode 100644 index 0000000..91ba461 --- /dev/null +++ b/src/store/achievementStore.ts @@ -0,0 +1,166 @@ +import apiClient from '@/api'; +import { Achievement, Receiver } from '@/models/achievements'; +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { useToastStore } from './toastStore'; + +export const useAchievementStore = defineStore('achievement', () => { + const toastStore = useToastStore(); + + const defaultAchievement: Achievement = { + id: 0, + name: 'Дефолтная ачивка', + description: 'Когда ничего другого не загрузилось', + picture: null, + owner_user_id: 177, + }; + + const allAchievements = ref([]); + const achievement = ref(defaultAchievement); + const receivers = ref([]); + const userAchivements = ref([]); + + async function requestAllAchievements() { + const { data, error } = await apiClient.GET('/achievement/achievement'); + if (error) { + toastStore.error(error); + return; + } + allAchievements.value = data.sort((a, b) => a.id - b.id); + } + + async function requestAchievement(id: number) { + const { data, error } = await apiClient.GET('/achievement/achievement/{id}', { + params: { path: { id } }, + }); + if (error) { + toastStore.error(error.detail![0].msg); + return; + } + achievement.value = data; + } + + async function create(name: string, description: string, picture_file: File | null) { + const { data, error } = await apiClient.POST('/achievement/achievement', { + body: { name, description }, + }); + if (error) { + toastStore.error(error.detail![0].msg); + return; + } + if (!picture_file) { + toastStore.success(`Достижение ${data.id} создано! Фотографию можно дозагрузить на странице достижения`); + return data; + } + toastStore.success(`Достижение ${data.id} создано! Загружаем иконку...`); + return changePicture(data.id, picture_file); + } + + async function edit(id: number, name: string, description: string) { + const { data, error } = await apiClient.PATCH('/achievement/achievement/{id}', { + params: { path: { id } }, + body: { name, description }, + }); + if (error) { + toastStore.error(error.detail![0].msg); + return; + } + achievement.value = data; + toastStore.success(`Достижение ${id} изменено. Иконка меняется отдельно`); + } + + async function changePicture(id: number, picture_file: File) { + const formData = new FormData(); + formData.append('picture_file', picture_file); + const { data, error } = await apiClient.PATCH('/achievement/achievement/{id}/picture', { + params: { path: { id } }, + body: formData as never, + }); + if (error) { + toastStore.error(error.detail![0].msg); + return; + } + toastStore.success(`Фотография достижения ${id} изменена`); + achievement.value = data; + return data; + } + + async function deleteAchievement(id: number) { + const { error } = await apiClient.DELETE('/achievement/achievement/{id}', { + params: { path: { id } }, + }); + if (error) { + toastStore.error(error.detail![0].msg); + return; + } + toastStore.success(`Достижение ${id} удалено`); + } + + async function requestReceivers(achievement_id: number) { + const { data, error } = await apiClient.GET('/achievement/achievement/{achievement_id}/reciever', { + params: { path: { achievement_id } }, + }); + if (error) { + toastStore.error(error.detail![0].msg); + return; + } + receivers.value = data.recievers; + } + + async function createReceiver(achievement_id: number, user_id: number) { + const { data, error } = await apiClient.POST('/achievement/achievement/{achievement_id}/reciever/{user_id}', { + params: { path: { achievement_id, user_id } }, + }); + if (error) { + toastStore.error(error.detail![0].msg); + return; + } + toastStore.success(`Ачивка ${achievement_id} выдана пользователю ${user_id}`); + receivers.value = data.recievers; + } + + async function revokeReceiver(achievement_id: number, user_id: number) { + const { data, error } = await apiClient.DELETE('/achievement/achievement/{achievement_id}/reciever/{user_id}', { + params: { path: { achievement_id, user_id } }, + }); + if (error) { + toastStore.error(error.detail![0].msg); + return; + } + toastStore.success(`Ачивка ${achievement_id} забрана у пользователю ${user_id}`); + receivers.value = data.recievers; + await requestUserAchievements(user_id); + } + + async function requestUserAchievements(user_id: number) { + const { data, error } = await apiClient.GET('/achievement/user/{user_id}', { + params: { path: { user_id } }, + }); + if (error) { + toastStore.error(error.detail![0].msg ?? 'Неизвестная ошибка'); + return; + } + userAchivements.value = data.achievement.sort((a, b) => a.id - b.id); + } + + return { + allAchievements, + achievement, + receivers, + userAchivements, + + requestAllAchievements, + + requestAchievement, + create, + edit, + changePicture, + deleteAchievement, + + requestReceivers, + createReceiver, + revokeReceiver, + + requestUserAchievements, + }; +}); diff --git a/src/store/index.ts b/src/store/index.ts index 21feb3f..765d627 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1 +1,3 @@ +export { useAchievementStore } from './achievementStore'; export { useProfileStore } from './profileStore'; +export { useToastStore } from './toastStore'; diff --git a/src/store/profileStore.ts b/src/store/profileStore.ts index 28d0c8a..03f886e 100644 --- a/src/store/profileStore.ts +++ b/src/store/profileStore.ts @@ -1,8 +1,13 @@ import { defineStore } from 'pinia'; -import { ref } from 'vue'; +import { computed, ref } from 'vue'; +import { setupAuth } from '@profcomff/api-uilib'; +import apiClient from '@/api'; +import { useToastStore } from './toastStore'; export const useProfileStore = defineStore('profile', () => { - const id = ref(null); + const toastStore = useToastStore(); + + const user_id = ref(null); const email = ref(null); const token = ref(null); const groups = ref(null); @@ -21,15 +26,72 @@ export const useProfileStore = defineStore('profile', () => { token.value = currToken; } if (currId) { - id.value = +currId; + user_id.value = +currId; } if (currScopes) { sessionScopes.value = currScopes.split(','); } }; + // if debugging, paste it here to avoid relogging + const TVOI_FF_TEST_TOKEN = ''; + + async function setupAdminSession(tvff_token: string | null) { + const storedToken = localStorage.getItem('token'); + if (storedToken) { + const { data } = await apiClient.GET('/auth/me'); + if (data) { + setupAuth(storedToken); + toastStore.success('Логин из сохраненной сессии успешен'); + return; + } + toastStore.error('Сохраненная сессия истекла, залогиньтесь заново'); + localStorage.removeItem('token'); + } + + setupAuth(tvff_token ?? TVOI_FF_TEST_TOKEN); + + const serviceScopes = [ + 'achievement.edit', + 'achievement.give', + 'achievement.revoke', + 'achievement.delete', + 'achievement.create', + ]; + const serviceName = 'achievements'; + const scopes = serviceScopes.map(value => `${serviceName}.${value}`); + const { data, error } = await apiClient.POST('/auth/session', { + body: { + scopes, + expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), + }, + }); + + if (error) { + toastStore.error((error.detail as unknown as string) ?? 'Что-то не так'); + return; + } + + if (data) { + user_id.value = data.id; + token.value = data.token || ''; + sessionScopes.value = data.session_scopes ?? []; + + setupAuth(data.token || ''); + localStorage.setItem('token', data.token || ''); + toastStore.success('Сессия установлена и сохранена'); + } + } + + async function setupUserSession(tvff_token: string | null) { + setupAuth(tvff_token ?? TVOI_FF_TEST_TOKEN); + toastStore.success('user logged'); + } + + const isLogged = computed(() => token.value && token.value !== ''); + return { - id, + user_id, email, token, groups, @@ -39,6 +101,10 @@ export const useProfileStore = defineStore('profile', () => { full_name, + isLogged, + fromUrl, + setupAdminSession, + setupUserSession, }; }); diff --git a/src/store/toastStore.ts b/src/store/toastStore.ts new file mode 100644 index 0000000..1125a31 --- /dev/null +++ b/src/store/toastStore.ts @@ -0,0 +1,39 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; + +export const useToastStore = defineStore('toast', () => { + interface Toast { + text: string; + timeout?: number; + color?: string; + } + + const queue = ref([]); + + function add(message: Toast) { + queue.value.push(message); + } + + function success(text: string) { + queue.value.push({ + text, + timeout: 2000, + color: 'success', + }); + } + + function error(text: string) { + queue.value.push({ + text, + timeout: 2000, + color: 'error', + }); + } + + return { + queue, + add, + error, + success, + }; +}); diff --git a/src/vuetify.ts b/src/vuetify.ts index 6a6c506..a825683 100644 --- a/src/vuetify.ts +++ b/src/vuetify.ts @@ -1,4 +1,5 @@ import '@mdi/font/css/materialdesignicons.css'; +import 'vuetify/styles'; import { createVuetify } from 'vuetify'; import { aliases, mdi } from 'vuetify/iconsets/mdi'; @@ -31,6 +32,10 @@ export const vuetify = createVuetify({ }, }, defaults: { + VBtn: { + variant: 'flat', + color: 'primary', + }, VBtnToggle: { color: 'rgb(0, 1, 76)', elevation: 1, diff --git a/tsconfig.json b/tsconfig.json index 0cc7fa6..070084c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,14 +9,14 @@ "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, - "lib": ["ESNext", "DOM"], + "lib": ["ESNext", "DOM", "DOM.Iterable"], "skipLibCheck": true, "noEmit": true, "forceConsistentCasingInFileNames": true, "baseUrl": ".", "paths": { "@/*": ["src/*"] - }, + } }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [
+ Название + +
+ Описание + +
+ Иконка + +
+ Нет + Да +
+ Номер достижения + +
+ User id + +
- Не удалось получить твое имя из - Userdata API - =( -
Твой id: {{ profileStore.id }}
- Документация к этому коду находится по адресу {{ location }} -
+ Токен + +
{{ description }}