new fields in meili indexing
This commit is contained in:
@@ -82,18 +82,27 @@ type ProductFilters struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ScannedCategory struct {
|
type ScannedCategory struct {
|
||||||
CategoryID uint `gorm:"column:ID;primaryKey"`
|
CategoryID uint `gorm:"column:ID;primaryKey"`
|
||||||
Name string `gorm:"column:name"`
|
Name string `gorm:"column:name"`
|
||||||
Active uint `gorm:"column:active"`
|
Active uint `gorm:"column:active"`
|
||||||
Position uint `gorm:"column:position"`
|
Position uint `gorm:"column:position"`
|
||||||
ParentID uint `gorm:"column:id_parent"`
|
ParentID uint `gorm:"column:id_parent"`
|
||||||
IsRoot uint `gorm:"column:is_root_category"`
|
IsRoot uint `gorm:"column:is_root_category"`
|
||||||
|
LinkRewrite string `gorm:"column:link_rewrite"`
|
||||||
|
IsoCode string `gorm:"column:iso_code"`
|
||||||
}
|
}
|
||||||
type Category struct {
|
type Category struct {
|
||||||
CategoryID uint `json:"category_id" form:"category_id"`
|
CategoryID uint `json:"category_id" form:"category_id"`
|
||||||
Name string `json:"name" form:"name"`
|
Label string `json:"label" form:"label"`
|
||||||
Active uint `json:"active" form:"active"`
|
// Active bool `json:"active" form:"active"`
|
||||||
Subcategories []Category `json:"subcategories" form:"subcategories"`
|
Params CategpryParams `json:"params" form:"params"`
|
||||||
|
Children []Category `json:"children" form:"children"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CategpryParams struct {
|
||||||
|
CategoryID uint `json:"category_id" form:"category_id"`
|
||||||
|
LinkRewrite string `json:"link_rewrite" form:"link_rewrite"`
|
||||||
|
Locale string `json:"locale" form:"locale"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeatVal = map[uint][]uint
|
type FeatVal = map[uint][]uint
|
||||||
|
|||||||
@@ -26,15 +26,14 @@ func (repo *CategoriesRepo) GetAllCategories(id_lang uint) ([]model.ScannedCateg
|
|||||||
ps_category.active AS active,
|
ps_category.active AS active,
|
||||||
ps_category_shop.position AS position,
|
ps_category_shop.position AS position,
|
||||||
ps_category.id_parent AS id_parent,
|
ps_category.id_parent AS id_parent,
|
||||||
ps_category.is_root_category AS is_root_category
|
ps_category.is_root_category AS is_root_category,
|
||||||
|
ps_category_lang.link_rewrite AS link_rewrite,
|
||||||
|
ps_lang.iso_code AS iso_code
|
||||||
FROM ps_category
|
FROM ps_category
|
||||||
LEFT JOIN ps_category_lang
|
LEFT JOIN ps_category_lang ON ps_category_lang.id_category = ps_category.id_category AND ps_category_lang.id_shop = ? AND ps_category_lang.id_lang = ?
|
||||||
ON ps_category_lang.id_category = ps_category.id_category
|
LEFT JOIN ps_category_shop ON ps_category_shop.id_category = ps_category.id_category AND ps_category_shop.id_shop = ?
|
||||||
AND ps_category_lang.id_shop = ?
|
JOIN ps_lang ON ps_lang.id_lang = ps_category_lang.id_lang
|
||||||
AND ps_category_lang.id_lang = ?
|
`,
|
||||||
LEFT JOIN ps_category_shop
|
|
||||||
ON ps_category_shop.id_category = ps_category.id_category
|
|
||||||
AND ps_category_shop.id_shop = ?`,
|
|
||||||
constdata.SHOP_ID, id_lang, constdata.SHOP_ID).
|
constdata.SHOP_ID, id_lang, constdata.SHOP_ID).
|
||||||
Scan(&allCategories).Error
|
Scan(&allCategories).Error
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCate
|
|||||||
node := s.scannedToNormalCategory((*all_categories)[index])
|
node := s.scannedToNormalCategory((*all_categories)[index])
|
||||||
|
|
||||||
for i := 0; i < len((*children_indices)[index]); i++ {
|
for i := 0; i < len((*children_indices)[index]); i++ {
|
||||||
node.Subcategories = append(node.Subcategories, s.createTree((*children_indices)[index][i].Index, all_categories, children_indices))
|
node.Children = append(node.Children, s.createTree((*children_indices)[index][i].Index, all_categories, children_indices))
|
||||||
}
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
@@ -72,10 +72,12 @@ func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCate
|
|||||||
|
|
||||||
func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category {
|
func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category {
|
||||||
var normal model.Category
|
var normal model.Category
|
||||||
normal.Active = scanned.Active
|
// normal.Active = scanned.Active
|
||||||
normal.CategoryID = scanned.CategoryID
|
normal.CategoryID = scanned.CategoryID
|
||||||
normal.Name = scanned.Name
|
normal.Label = scanned.Name
|
||||||
normal.Subcategories = []model.Category{}
|
// normal.Active = scanned.Active == 1
|
||||||
|
normal.Params = model.CategpryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode}
|
||||||
|
normal.Children = []model.Category{}
|
||||||
return normal
|
return normal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
bo/components.d.ts
vendored
1
bo/components.d.ts
vendored
@@ -41,6 +41,7 @@ declare module 'vue' {
|
|||||||
UInput: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Input.vue')['default']
|
UInput: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Input.vue')['default']
|
||||||
UInputNumber: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/InputNumber.vue')['default']
|
UInputNumber: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/InputNumber.vue')['default']
|
||||||
UModal: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Modal.vue')['default']
|
UModal: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Modal.vue')['default']
|
||||||
|
UNavigationMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/NavigationMenu.vue')['default']
|
||||||
UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default']
|
UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default']
|
||||||
USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
|
USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
|
||||||
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
||||||
|
|||||||
@@ -1,14 +1,56 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TopBar from '@/components/TopBar.vue';
|
import TopBar from '@/components/TopBar.vue';
|
||||||
|
import { getMenu } from '@/router/menu'
|
||||||
|
import type { NavigationMenuItem } from '@nuxt/ui';
|
||||||
|
import { on } from 'events';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
let menu = await getMenu() as NavigationMenuItem[]
|
||||||
|
|
||||||
|
function adaptMenu(menu) {
|
||||||
|
for (const item of menu) {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
adaptMenu(item.children);
|
||||||
|
// item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label ,to: { name: 'category', params: item.params }})
|
||||||
|
// item.type = 'trigger'
|
||||||
|
// item.icon = 'i-lucide-book-open'
|
||||||
|
// item.onSelect(e){ console.log(e, 'adasdasdas'); }
|
||||||
|
} else {
|
||||||
|
// console.log(item.params);
|
||||||
|
item.to = { name: 'category', params: item.params };
|
||||||
|
item.icon = 'i-lucide-file-text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu = adaptMenu(menu.children)
|
||||||
|
|
||||||
|
console.log(menu);
|
||||||
|
|
||||||
|
const items = ref<NavigationMenuItem[][]>([
|
||||||
|
[
|
||||||
|
|
||||||
|
...menu
|
||||||
|
],
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
||||||
<!-- <header class="w-full bg-gray-100 text-primary shadow border-b-gray-300 p-4 mb-8">Header</header> -->
|
<main class="p-10">
|
||||||
<main class="p-10">
|
<div class="mt-24 bg-accented w-1/5 rounded-2xl">
|
||||||
<TopBar/>
|
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4">
|
||||||
<router-view />
|
</UNavigationMenu>
|
||||||
</main>
|
|
||||||
|
</div>
|
||||||
|
<!-- <template #item>{{ item }}</template> -->
|
||||||
|
|
||||||
|
|
||||||
|
<TopBar/>
|
||||||
|
<slot></slot>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<main :key="i18n.locale.value">
|
<main :key="i18n.locale.value">
|
||||||
<TopBarLogin />
|
<TopBarLogin />
|
||||||
<router-view />
|
<slot></slot>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import Empty from '@/layouts/empty.vue'
|
|
||||||
import { currentLang, langs } from './langs'
|
import { currentLang, langs } from './langs'
|
||||||
import { getSettings } from './settings'
|
import { getSettings } from './settings'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import Default from '@/layouts/default.vue'
|
import Default from '@/layouts/default.vue'
|
||||||
|
import { getMenu } from './menu'
|
||||||
|
|
||||||
function isAuthenticated(): boolean {
|
function isAuthenticated(): boolean {
|
||||||
if (typeof document === 'undefined') return false
|
if (typeof document === 'undefined') return false
|
||||||
@@ -12,6 +12,7 @@ function isAuthenticated(): boolean {
|
|||||||
|
|
||||||
|
|
||||||
await getSettings()
|
await getSettings()
|
||||||
|
// await getMenu()
|
||||||
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -24,6 +25,7 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/:locale',
|
path: '/:locale',
|
||||||
children: [
|
children: [
|
||||||
|
{ path: 'category/:category_id-:link_rewrite', component: () => import('../views/CategoryView.vue'), name: 'category' },
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: Default,
|
component: Default,
|
||||||
@@ -36,49 +38,88 @@ const router = createRouter({
|
|||||||
{ path: 'cart', component: () => import('../components/customer/PageCart.vue'), name: 'cart' },
|
{ path: 'cart', component: () => import('../components/customer/PageCart.vue'), name: 'cart' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{ path: 'login', component: () => import('@/views/LoginView.vue'), name: 'login', meta: { guest: true, } },
|
||||||
|
{ path: 'register', component: () => import('@/views/RegisterView.vue'), name: 'register', meta: { guest: true } },
|
||||||
|
{ path: 'password-recovery', component: () => import('@/views/PasswordRecoveryView.vue'), name: 'password-recovery', meta: { guest: true } },
|
||||||
|
{ path: 'reset-password', component: () => import('@/views/ResetPasswordForm.vue'), name: 'reset-password', meta: { guest: true } },
|
||||||
|
{ path: 'verify-email', component: () => import('@/views/VerifyEmailView.vue'), name: 'verify-email', meta: { guest: true } },
|
||||||
|
// {
|
||||||
|
// path: '',
|
||||||
|
// component: Empty,
|
||||||
|
// children: [
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
path: '',
|
path: '/:pathMatch(.*)*',
|
||||||
component: Empty,
|
component: () => import('@/views/NotFoundView.vue'),
|
||||||
children: [
|
name: 'not-found',
|
||||||
{ path: 'login', component: () => import('@/views/LoginView.vue'), name: 'login', meta: { guest: true } },
|
|
||||||
{ path: 'register', component: () => import('@/views/RegisterView.vue'), name: 'register', meta: { guest: true } },
|
|
||||||
{ path: 'password-recovery', component: () => import('@/views/PasswordRecoveryView.vue'), name: 'password-recovery', meta: { guest: true } },
|
|
||||||
{ path: 'reset-password', component: () => import('@/views/ResetPasswordForm.vue'), name: 'reset-password', meta: { guest: true } },
|
|
||||||
{ path: 'verify-email', component: () => import('@/views/VerifyEmailView.vue'), name: 'verify-email', meta: { guest: true } },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
component: () => import('@/views/NotFoundView.vue'),
|
||||||
|
name: 'not-found',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
// router.beforeEach((to, from, next) => {
|
||||||
|
// const locale = to.params.locale as string
|
||||||
|
// const localeLang = langs.find((x) => x.iso_code == locale)
|
||||||
|
|
||||||
|
// if (locale && langs.length > 0) {
|
||||||
|
// const authStore = useAuthStore()
|
||||||
|
// // console.log(authStore.isAuthenticated, to, from)
|
||||||
|
// // if()
|
||||||
|
// const validLocale = langs.find((l) => l.lang_code === locale)
|
||||||
|
|
||||||
|
// if (validLocale) {
|
||||||
|
// currentLang.value = localeLang
|
||||||
|
// if (!to.meta?.guest && !isAuthenticated()) {
|
||||||
|
// return next({ name: 'login', params: { locale } })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // return next()
|
||||||
|
// return next()
|
||||||
|
// } else if (locale) {
|
||||||
|
// return next(`/${currentLang.value?.iso_code}${to.path.replace(`/${locale}`, '') || '/'}`)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!locale && to.path !== '/') {
|
||||||
|
// return next(`/${currentLang.value?.iso_code}${to.path}`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// next()
|
||||||
|
// })
|
||||||
|
|
||||||
|
router.beforeEach((to, from) => {
|
||||||
const locale = to.params.locale as string
|
const locale = to.params.locale as string
|
||||||
const localeLang = langs.find((x) => x.iso_code == locale)
|
const localeLang = langs.find((x) => x.iso_code === locale)
|
||||||
|
|
||||||
if (locale && langs.length > 0) {
|
if (locale && langs.length > 0) {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
console.log(authStore.isAuthenticated, to, from)
|
|
||||||
// if()
|
|
||||||
const validLocale = langs.find((l) => l.lang_code === locale)
|
const validLocale = langs.find((l) => l.lang_code === locale)
|
||||||
|
|
||||||
if (validLocale) {
|
if (validLocale) {
|
||||||
currentLang.value = localeLang
|
currentLang.value = localeLang
|
||||||
if (!to.meta?.guest && !isAuthenticated()) {
|
|
||||||
return next({ name: 'login', params: { locale } })
|
if (!to.meta?.guest && !authStore.isAuthenticated) {
|
||||||
|
return { name: 'login', params: { locale } }
|
||||||
}
|
}
|
||||||
|
|
||||||
return next()
|
return true
|
||||||
} else if (locale) {
|
} else if (locale) {
|
||||||
return next(`/${currentLang.value?.iso_code}${to.path.replace(`/${locale}`, '') || '/'}`)
|
return `/${currentLang.value?.iso_code}${to.path.replace(`/${locale}`, '') || '/'}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!locale && to.path !== '/') {
|
if (!locale && to.path !== '/') {
|
||||||
return next(`/${currentLang.value?.iso_code}${to.path}`)
|
return `/${currentLang.value?.iso_code}${to.path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
next()
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
8
bo/src/router/menu.ts
Normal file
8
bo/src/router/menu.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { useFetchJson } from "@/composable/useFetchJson";
|
||||||
|
|
||||||
|
export const getMenu = async () => {
|
||||||
|
const resp = await useFetchJson('/api/v1/restricted/menu/get-menu');
|
||||||
|
|
||||||
|
return resp.items
|
||||||
|
|
||||||
|
}
|
||||||
21
bo/src/views/CategoryView.vue
Normal file
21
bo/src/views/CategoryView.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
<div class="container mt-24">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h1>Category</h1>
|
||||||
|
{{ $route.params }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// import { useRoute } from 'vue-router';
|
||||||
|
import Default from '@/layouts/default.vue';
|
||||||
|
// const route = useRoute()
|
||||||
|
// console.log(route);
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -4,8 +4,8 @@ import { useRouter, useRoute } from 'vue-router'
|
|||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useValidation } from '@/composable/useValidation'
|
import { useValidation } from '@/composable/useValidation'
|
||||||
import type { FormError } from '@nuxt/ui'
|
import type { FormError } from '@nuxt/ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
|
import Empty from '@/layouts/empty.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -52,9 +52,11 @@ const PrivacyComponent = computed(() =>
|
|||||||
import(`@/components/terms/${i18n.locale.value}_PrivacyPolicyView.vue`).catch(() => import('@/components/terms/en_PrivacyPolicyView.vue')),
|
import(`@/components/terms/${i18n.locale.value}_PrivacyPolicyView.vue`).catch(() => import('@/components/terms/en_PrivacyPolicyView.vue')),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<component :is="Empty || 'div'">
|
||||||
<UDrawer v-model:open="showTherms" :overlay="false">
|
<UDrawer v-model:open="showTherms" :overlay="false">
|
||||||
<template #body>
|
<template #body>
|
||||||
<component :is="TermsComponent" />
|
<component :is="TermsComponent" />
|
||||||
@@ -163,4 +165,5 @@ const PrivacyComponent = computed(() =>
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
7
bo/src/views/NotFoundView.vue
Normal file
7
bo/src/views/NotFoundView.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center justify-center h-screen">
|
||||||
|
<h1 class="text-4xl font-bold text-gray-800">404</h1>
|
||||||
|
<p class="mt-4 text-lg text-gray-600">Page not found</p>
|
||||||
|
<router-link to="/" class="mt-6 text-blue-500 hover:underline">Go back home</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/auth'
|
|||||||
import { useValidation } from '@/composable/useValidation'
|
import { useValidation } from '@/composable/useValidation'
|
||||||
import type { FormError } from '@nuxt/ui'
|
import type { FormError } from '@nuxt/ui'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
|
import Empty from '@/layouts/empty.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -36,70 +37,74 @@ function validate(): FormError[] {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-[100vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
<component :is="Empty || 'div'">
|
||||||
<div class="text-center mb-15">
|
<div class="h-[100vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
<div
|
<div class="text-center mb-15">
|
||||||
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
|
<div
|
||||||
<UIcon name="i-heroicons-clock" class="w-8 h-8" />
|
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
|
||||||
|
<UIcon name="i-heroicons-clock" class="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1>
|
<div class="w-full max-w-md flex flex-col gap-4">
|
||||||
</div>
|
|
||||||
<div class="w-full max-w-md flex flex-col gap-4">
|
|
||||||
|
|
||||||
<template v-if="submitted">
|
<template v-if="submitted">
|
||||||
<div class="text-center flex flex-col gap-4">
|
<div class="text-center flex flex-col gap-4">
|
||||||
<UIcon name="i-heroicons-envelope" class="w-12 h-12 mx-auto text-primary-500" />
|
<UIcon name="i-heroicons-envelope" class="w-12 h-12 mx-auto text-primary-500" />
|
||||||
<h2 class="text-xl font-semibold dark:text-white text-black">{{ $t('general.check_your_email') }}</h2>
|
<h2 class="text-xl font-semibold dark:text-white text-black">{{ $t('general.check_your_email') }}</h2>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ $t('general.password_reset_link_sent_notice') }}
|
{{ $t('general.password_reset_link_sent_notice') }}
|
||||||
</p>
|
</p>
|
||||||
<UButton color="neutral" variant="outline" block @click="goToLogin"
|
<UButton color="neutral" variant="outline" block @click="goToLogin"
|
||||||
class="dark:text-white text-black cursor-pointer">
|
class="dark:text-white text-black cursor-pointer">
|
||||||
{{ $t('general.back_to_sign_in') }}
|
{{ $t('general.back_to_sign_in') }}
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ $t('general.enter_email_for_password_reset') }}
|
{{ $t('general.enter_email_for_password_reset') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UForm :validate="validate" @submit="handleRecover" class="flex flex-col gap-3">
|
<UForm :validate="validate" @submit="handleRecover" class="flex flex-col gap-3">
|
||||||
<UAlert v-if="authStore.error" color="error" variant="subtle" icon="i-heroicons-exclamation-triangle"
|
<UAlert v-if="authStore.error" color="error" variant="subtle" icon="i-heroicons-exclamation-triangle"
|
||||||
:title="authStore.error" :close-button="{ icon: 'i-heroicons-x-mark-20-solid', variant: 'link' }"
|
:title="authStore.error" :close-button="{ icon: 'i-heroicons-x-mark-20-solid', variant: 'link' }"
|
||||||
@close="authStore.clearError" />
|
@close="authStore.clearError" />
|
||||||
|
|
||||||
<UFormField :label="$t('general.email_address')" name="email" required
|
<UFormField :label="$t('general.email_address')" name="email" required
|
||||||
class="w-full dark:text-white text-black">
|
class="w-full dark:text-white text-black">
|
||||||
<UInput v-model="email" :placeholder="$t('general.enter_your_email')" :disabled="authStore.loading"
|
<UInput v-model="email" :placeholder="$t('general.enter_your_email')" :disabled="authStore.loading"
|
||||||
class="w-full dark:text-white text-black placeholder:text-(--placeholder)" />
|
class="w-full dark:text-white text-black placeholder:text-(--placeholder)" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<UButton type="submit" block :loading="authStore.loading"
|
<UButton type="submit" block :loading="authStore.loading"
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) cursor-pointer">
|
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) cursor-pointer">
|
||||||
{{ $t('general.send_password_reset_link') }}
|
{{ $t('general.send_password_reset_link') }}
|
||||||
</UButton>
|
</UButton>
|
||||||
</UForm>
|
</UForm>
|
||||||
|
|
||||||
<div class="text-center flex flex-col gap-3 border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
<div
|
||||||
<button color="neutral" variant="outline" :loading="authStore.loading"
|
class="text-center flex flex-col gap-3 border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
||||||
class="w-full flex items-center gap-2 justify-center text-[15px] dark:text-white text-black cursor-pointer"
|
<button color="neutral" variant="outline" :loading="authStore.loading"
|
||||||
@click="goToLogin">
|
class="w-full flex items-center gap-2 justify-center text-[15px] dark:text-white text-black cursor-pointer"
|
||||||
<UIcon name="mingcute:arrow-left-line" class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[16px]" />
|
@click="goToLogin">
|
||||||
{{ $t('general.back_to_sign_in') }}
|
<UIcon name="mingcute:arrow-left-line"
|
||||||
</button>
|
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[16px]" />
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
{{ $t('general.back_to_sign_in') }}
|
||||||
{{ $t('general.dont_have_an_account') }}
|
|
||||||
<button variant="link" size="sm" @click="goToRegister"
|
|
||||||
class=" text-[15px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">{{
|
|
||||||
$t('general.create_account_now') }}
|
|
||||||
</button>
|
</button>
|
||||||
</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
</div>
|
{{ $t('general.dont_have_an_account') }}
|
||||||
</template>
|
<button variant="link" size="sm" @click="goToRegister"
|
||||||
|
class=" text-[15px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">{{
|
||||||
|
$t('general.create_account_now') }}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<component :is="Empty || 'div'">
|
||||||
<UDrawer v-model:open="showTherms" :overlay="false">
|
<UDrawer v-model:open="showTherms" :overlay="false">
|
||||||
<template #body>
|
<template #body>
|
||||||
<component :is="TermsComponent" />
|
<component :is="TermsComponent" />
|
||||||
@@ -110,6 +111,7 @@
|
|||||||
</UForm>
|
</UForm>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -120,6 +122,8 @@ import { useValidation } from '@/composable/useValidation'
|
|||||||
import type { FormError } from '@nuxt/ui'
|
import type { FormError } from '@nuxt/ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
|
import Empty from '@/layouts/empty.vue'
|
||||||
|
|
||||||
|
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/auth'
|
|||||||
import { useValidation } from '@/composable/useValidation'
|
import { useValidation } from '@/composable/useValidation'
|
||||||
import type { FormError } from '@nuxt/ui'
|
import type { FormError } from '@nuxt/ui'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
|
import Empty from '@/layouts/empty.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -49,80 +50,84 @@ function validate(): FormError[] {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-[100vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
<component :is="Empty || 'div'">
|
||||||
<div class="text-center mb-15">
|
<div class="h-[100vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
<div
|
<div class="text-center mb-15">
|
||||||
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
|
<div
|
||||||
<UIcon name="i-heroicons-clock" class="w-8 h-8" />
|
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
|
||||||
</div>
|
<UIcon name="i-heroicons-clock" class="w-8 h-8" />
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1>
|
|
||||||
</div>
|
|
||||||
<div class="w-full max-w-md flex flex-col gap-4">
|
|
||||||
|
|
||||||
<template v-if="submitted">
|
|
||||||
<div class="text-center flex flex-col gap-4">
|
|
||||||
<UIcon name="i-heroicons-check-circle" class="w-12 h-12 mx-auto text-green-800" />
|
|
||||||
<h2 class="text-xl font-semibold dark:text-white text-black">
|
|
||||||
{{ $t('general.password_updated') }}
|
|
||||||
</h2>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{{ $t('general.password_updated_description') }}
|
|
||||||
</p>
|
|
||||||
<UButton block @click="goToLogin" class="dark:text-white text-black">
|
|
||||||
{{ $t('general.back_to_sign_in') }}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full max-w-md flex flex-col gap-4">
|
||||||
|
|
||||||
<template v-else>
|
<template v-if="submitted">
|
||||||
<UForm :validate="validate" @submit="handleReset" class="flex flex-col gap-3">
|
<div class="text-center flex flex-col gap-4">
|
||||||
<UAlert v-if="authStore.error" color="error" variant="subtle"
|
<UIcon name="i-heroicons-check-circle" class="w-12 h-12 mx-auto text-green-800" />
|
||||||
icon="i-heroicons-exclamation-triangle" :title="authStore.error"
|
<h2 class="text-xl font-semibold dark:text-white text-black">
|
||||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', variant: 'link' }"
|
{{ $t('general.password_updated') }}
|
||||||
@close="authStore.clearError" />
|
</h2>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
<UFormField :label="$t('general.new_password')" name="new_password" required
|
{{ $t('general.password_updated_description') }}
|
||||||
class="w-full dark:text-white text-black">
|
</p>
|
||||||
<UInput v-model="new_password" :type="showNewPassword ? 'text' : 'password'"
|
<UButton block @click="goToLogin" class="dark:text-white text-black">
|
||||||
:placeholder="$t('general.enter_your_new_password')" :disabled="authStore.loading"
|
|
||||||
class="w-full dark:text-white text-black placeholder:text-(--placeholder)" :ui="{ trailing: 'pe-1' }">
|
|
||||||
<template #trailing>
|
|
||||||
<UIcon color="neutral" variant="link" size="sm"
|
|
||||||
:name="showNewPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
|
||||||
:aria-label="showNewPassword ? 'Hide password' : 'Show password'"
|
|
||||||
:aria-pressed="showNewPassword" aria-controls="new_password"
|
|
||||||
@click="showNewPassword = !showNewPassword" class="mr-2"/>
|
|
||||||
</template>
|
|
||||||
</UInput>
|
|
||||||
</UFormField>
|
|
||||||
|
|
||||||
<UFormField :label="$t('general.confirm_password')" name="confirm_new_password" required
|
|
||||||
class="w-full dark:text-white text-black">
|
|
||||||
<UInput v-model="confirm_new_password" :type="showConfirmPassword ? 'text' : 'password'"
|
|
||||||
:placeholder="$t('general.confirm_your_new_password')" :disabled="authStore.loading"
|
|
||||||
class="w-full dark:text-white text-black placeholder:text-(--placeholder)" :ui="{ trailing: 'pe-1' }">
|
|
||||||
<template #trailing>
|
|
||||||
<UIcon color="neutral" variant="ghost" size="sm"
|
|
||||||
:name="showConfirmPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
|
||||||
@click="showConfirmPassword = !showConfirmPassword" class="mr-2"/>
|
|
||||||
</template>
|
|
||||||
</UInput>
|
|
||||||
</UFormField>
|
|
||||||
|
|
||||||
<UButton type="submit" block :loading="authStore.loading"
|
|
||||||
class="text-white! bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) cursor-pointer">
|
|
||||||
{{ $t('general.reset_password') }}
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
<div class="text-center border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
|
||||||
<button color="neutral" variant="ghost" @click="goToLogin"
|
|
||||||
class="text-[15px] flex items-center gap-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">
|
|
||||||
<UIcon name="mingcute:arrow-left-line" />
|
|
||||||
{{ $t('general.back_to_sign_in') }}
|
{{ $t('general.back_to_sign_in') }}
|
||||||
</button>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</UForm>
|
</template>
|
||||||
</template>
|
|
||||||
|
<template v-else>
|
||||||
|
<UForm :validate="validate" @submit="handleReset" class="flex flex-col gap-3">
|
||||||
|
<UAlert v-if="authStore.error" color="error" variant="subtle"
|
||||||
|
icon="i-heroicons-exclamation-triangle" :title="authStore.error"
|
||||||
|
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', variant: 'link' }"
|
||||||
|
@close="authStore.clearError" />
|
||||||
|
|
||||||
|
<UFormField :label="$t('general.new_password')" name="new_password" required
|
||||||
|
class="w-full dark:text-white text-black">
|
||||||
|
<UInput v-model="new_password" :type="showNewPassword ? 'text' : 'password'"
|
||||||
|
:placeholder="$t('general.enter_your_new_password')" :disabled="authStore.loading"
|
||||||
|
class="w-full dark:text-white text-black placeholder:text-(--placeholder)"
|
||||||
|
:ui="{ trailing: 'pe-1' }">
|
||||||
|
<template #trailing>
|
||||||
|
<UIcon color="neutral" variant="link" size="sm"
|
||||||
|
:name="showNewPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||||
|
:aria-label="showNewPassword ? 'Hide password' : 'Show password'"
|
||||||
|
:aria-pressed="showNewPassword" aria-controls="new_password"
|
||||||
|
@click="showNewPassword = !showNewPassword" class="mr-2" />
|
||||||
|
</template>
|
||||||
|
</UInput>
|
||||||
|
</UFormField>
|
||||||
|
|
||||||
|
<UFormField :label="$t('general.confirm_password')" name="confirm_new_password" required
|
||||||
|
class="w-full dark:text-white text-black">
|
||||||
|
<UInput v-model="confirm_new_password" :type="showConfirmPassword ? 'text' : 'password'"
|
||||||
|
:placeholder="$t('general.confirm_your_new_password')" :disabled="authStore.loading"
|
||||||
|
class="w-full dark:text-white text-black placeholder:text-(--placeholder)"
|
||||||
|
:ui="{ trailing: 'pe-1' }">
|
||||||
|
<template #trailing>
|
||||||
|
<UIcon color="neutral" variant="ghost" size="sm"
|
||||||
|
:name="showConfirmPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||||
|
@click="showConfirmPassword = !showConfirmPassword" class="mr-2" />
|
||||||
|
</template>
|
||||||
|
</UInput>
|
||||||
|
</UFormField>
|
||||||
|
|
||||||
|
<UButton type="submit" block :loading="authStore.loading"
|
||||||
|
class="text-white! bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) cursor-pointer">
|
||||||
|
{{ $t('general.reset_password') }}
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<div class="text-center border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
||||||
|
<button color="neutral" variant="ghost" @click="goToLogin"
|
||||||
|
class="text-[15px] flex items-center gap-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">
|
||||||
|
<UIcon name="mingcute:arrow-left-line" />
|
||||||
|
{{ $t('general.back_to_sign_in') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</UForm>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useRouter, useRoute } from 'vue-router'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
|
import Empty from '@/layouts/empty.vue'
|
||||||
|
|
||||||
const { t, te } = useI18n()
|
const { t, te } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -66,81 +67,84 @@ function goToLogin() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<component :is="Empty || 'div'">
|
||||||
class="min-h-screen bg-gradient-to-br from-primary-50 via-white to-primary-100 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
|
<div
|
||||||
<div class="pt-20 pb-8 flex items-center justify-center px-4 sm:px-6 lg:px-8">
|
class="min-h-screen bg-gradient-to-br from-primary-50 via-white to-primary-100 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
|
||||||
<div class="w-full max-w-md">
|
<div class="pt-20 pb-8 flex items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
<div class="text-center mb-8">
|
<div class="w-full max-w-md">
|
||||||
<div
|
<div class="text-center mb-8">
|
||||||
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
|
<div
|
||||||
<UIcon name="i-heroicons-clock" class="w-8 h-8" />
|
class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-500 text-white mb-4 shadow-lg shadow-primary-500/30">
|
||||||
|
<UIcon name="i-heroicons-clock" class="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">TimeTracker</h1>
|
|
||||||
|
<UCard class="shadow-xl shadow-gray-200/50 dark:shadow-gray-900/50">
|
||||||
|
<template #header>
|
||||||
|
<div class="text-center">
|
||||||
|
<div v-if="verificationInProgress && loading">
|
||||||
|
<UIcon name="i-heroicons-arrow-path" class="w-8 h-8 animate-spin text-primary-500 mx-auto mb-4" />
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
|
{{ $t('verify_email.verifying') }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="success">
|
||||||
|
<div
|
||||||
|
class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-green-100 text-green-600 mb-4">
|
||||||
|
<UIcon name="i-heroicons-check-circle" class="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
|
{{ $t('verify_email.success_title') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{{ $t('verify_email.success_message') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error">
|
||||||
|
<div
|
||||||
|
class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 text-red-600 mb-4">
|
||||||
|
<UIcon name="i-heroicons-exclamation-circle" class="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
|
{{ $t('verify_email.error_title') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{{ $t('verify_email.error_message') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="success" class="text-center py-4">
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-4">{{ $t('verify_email.redirect_message') }}</p>
|
||||||
|
<UButton color="primary" @click="goToLogin">{{ $t('verify_email.go_to_login') }}</UButton>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error" class="text-center py-4">
|
||||||
|
<UAlert :color="'error'" variant="subtle" icon="i-heroicons-exclamation-triangle" :title="error"
|
||||||
|
class="mb-4" />
|
||||||
|
<UButton color="primary" @click="goToLogin" class="cursor-pointer">{{ $t('verify_email.go_to_login') }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="verificationInProgress && loading" class="text-center py-4">
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">{{ $t('verify_email.please_wait') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ $t('verify_email.already_registered') }}
|
||||||
|
<button variant="link" size="sm" @click="goToLogin"
|
||||||
|
class="cursor-pointer text-(--accent-blue-light) dark:text-(--accent-blue-dark)"> {{
|
||||||
|
$t('general.sign_in')
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UCard class="shadow-xl shadow-gray-200/50 dark:shadow-gray-900/50">
|
|
||||||
<template #header>
|
|
||||||
<div class="text-center">
|
|
||||||
<div v-if="verificationInProgress && loading">
|
|
||||||
<UIcon name="i-heroicons-arrow-path" class="w-8 h-8 animate-spin text-primary-500 mx-auto mb-4" />
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
{{ $t('verify_email.verifying') }}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="success">
|
|
||||||
<div
|
|
||||||
class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-green-100 text-green-600 mb-4">
|
|
||||||
<UIcon name="i-heroicons-check-circle" class="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
{{ $t('verify_email.success_title') }}
|
|
||||||
</h2>
|
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
{{ $t('verify_email.success_message') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="error">
|
|
||||||
<div
|
|
||||||
class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 text-red-600 mb-4">
|
|
||||||
<UIcon name="i-heroicons-exclamation-circle" class="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
{{ $t('verify_email.error_title') }}
|
|
||||||
</h2>
|
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
{{ $t('verify_email.error_message') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-if="success" class="text-center py-4">
|
|
||||||
<p class="text-gray-600 dark:text-gray-400 mb-4">{{ $t('verify_email.redirect_message') }}</p>
|
|
||||||
<UButton color="primary" @click="goToLogin">{{ $t('verify_email.go_to_login') }}</UButton>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="error" class="text-center py-4">
|
|
||||||
<UAlert :color="'error'" variant="subtle" icon="i-heroicons-exclamation-triangle" :title="error"
|
|
||||||
class="mb-4" />
|
|
||||||
<UButton color="primary" @click="goToLogin" class="cursor-pointer">{{ $t('verify_email.go_to_login') }}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="verificationInProgress && loading" class="text-center py-4">
|
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ $t('verify_email.please_wait') }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="text-center">
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{{ $t('verify_email.already_registered') }}
|
|
||||||
<button variant="link" size="sm" @click="goToLogin"
|
|
||||||
class="cursor-pointer text-(--accent-blue-light) dark:text-(--accent-blue-dark)"> {{ $t('general.sign_in')
|
|
||||||
}}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</UCard>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user