nuxt-layer-core 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -0
- package/app/app.config.ts +5 -0
- package/app/app.vue +11 -0
- package/app/assets/css/main.css +113 -0
- package/app/components/.gitkeep +0 -0
- package/app/components/ui/sonner/Sonner.vue +62 -0
- package/app/components/ui/sonner/index.ts +1 -0
- package/app/composables/.gitkeep +0 -0
- package/app/layers/solution/app/assets/proxmox-css/main.css +89 -0
- package/app/layers/solution/app/assets/proxmox-css/proxmox-utils.css +68 -0
- package/app/layers/solution/app/components/ClusterDiagram.vue +72 -0
- package/app/layers/solution/app/components/CoursesSection.vue +246 -0
- package/app/layers/solution/app/components/CtaSection.vue +329 -0
- package/app/layers/solution/app/components/FeaturesSection.vue +67 -0
- package/app/layers/solution/app/components/HeroSection.vue +144 -0
- package/app/layers/solution/app/components/PricingPlansSection.vue +434 -0
- package/app/layers/solution/app/components/RegistrationForm.vue +256 -0
- package/app/layers/solution/app/components/TestimonialsSection.vue +58 -0
- package/app/layers/solution/app/components/TheFooter.vue +61 -0
- package/app/layers/solution/app/components/TheHeader.vue +189 -0
- package/app/layers/solution/app/components/proxmox/AppIcon.vue +45 -0
- package/app/layers/solution/app/components/proxmox/ComparisonSection.vue +54 -0
- package/app/layers/solution/app/components/proxmox/FaqSection.vue +58 -0
- package/app/layers/solution/app/components/proxmox/HciOverviewSection.vue +37 -0
- package/app/layers/solution/app/components/proxmox/HeroSection.vue +74 -0
- package/app/layers/solution/app/components/proxmox/TestimonialsSection.vue +44 -0
- package/app/layers/solution/app/components/proxmox/TrainingBenefitsSection.vue +37 -0
- package/app/layers/solution/app/components/proxmox/VeFeaturesSection.vue +55 -0
- package/app/layers/solution/app/composables/useActiveSection.ts +47 -0
- package/app/layers/solution/app/composables/useAuthApi.ts +244 -0
- package/app/layers/solution/app/composables/useAuthState.ts +16 -0
- package/app/layers/solution/app/composables/useCookie.ts +79 -0
- package/app/layers/solution/app/composables/useCookieHelper.ts +81 -0
- package/app/layers/solution/app/composables/useCrmApi.ts +32 -0
- package/app/layers/solution/app/composables/useDynamicCollection.ts +23 -0
- package/app/layers/solution/app/composables/useScrollAnimate.ts +40 -0
- package/app/layers/solution/app/composables/useScrollToSection.ts +13 -0
- package/app/layers/solution/app/composables/useSolutionApi.ts +55 -0
- package/app/layers/solution/app/composables/useToast.ts +14 -0
- package/app/layers/solution/app/data/proxmox-content.ts +158 -0
- package/app/layers/solution/app/images/Proxmox HCI.png +0 -0
- package/app/layers/solution/app/images/image_from_https_hercules_cdn.com_file_6dztkvkfnip0ud23tzzvu57c.png +0 -0
- package/app/layers/solution/app/layouts/default.vue +5 -0
- package/app/layers/solution/app/nuxt.config.ts +9 -0
- package/app/layers/solution/app/pages/cap-nhat-thong-tin/index.vue +302 -0
- package/app/layers/solution/app/pages/dang-ky/index.vue +627 -0
- package/app/layers/solution/app/pages/dang-nhap/index.vue +208 -0
- package/app/layers/solution/app/pages/doi-mat-khau/index.vue +237 -0
- package/app/layers/solution/app/pages/giai-phap-proxmox/[slug]/index.vue +37 -0
- package/app/layers/solution/app/pages/giai-phap-proxmox/[slug]/proxmox-hci/index.vue +45 -0
- package/app/layers/solution/app/pages/giai-phap-proxmox/dang-ky-khoa-hoc/[slug].vue +131 -0
- package/app/layers/solution/app/pages/quen-mat-khau/index.vue +136 -0
- package/app/layers/solution/app/pages/xac-thuc/index.vue +320 -0
- package/app/layers/solution/app/plugins/reveal.ts +56 -0
- package/app/layers/solution/app/public/images/image_from_https_hercules_cdn.com_file_6dztkvkfnip0ud23tzzvu57c.png +0 -0
- package/app/layers/solution/app/public/images/logo-lvs-1300x330.png +0 -0
- package/app/layouts/auth.vue +71 -0
- package/app/lib/utils.ts +7 -0
- package/app/middleware/.gitkeep +0 -0
- package/app/modules/layer-route-priority.ts +60 -0
- package/app/pages/index.vue +9 -0
- package/app/pages/san-pham-proxmox.vue +8 -0
- package/app/plugins/.gitkeep +0 -0
- package/app/plugins/sdk.ts +29 -0
- package/app/stores/.gitkeep +0 -0
- package/app/types/sdk.d.ts +15 -0
- package/components.json +24 -0
- package/nuxt.config.ts +104 -0
- package/package.json +52 -0
- package/public/.gitkeep +0 -0
- package/server/api/.gitkeep +0 -0
- package/server/utils/.gitkeep +0 -0
- package/tailwind.config.js +96 -0
- package/tsconfig.base.json +18 -0
- package/tsconfig.json +18 -0
- package/website-longvan-1.1.6.tgz +0 -0
- package/website-longvan-1.1.7.tgz +0 -0
- package/website-longvan-1.1.8.tgz +0 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<TheHeader />
|
|
4
|
+
<div class="min-h-[calc(50vh-4rem)] flex items-start justify-center pt-20 pb-16 px-6">
|
|
5
|
+
<div class="w-full max-w-lg">
|
|
6
|
+
<!-- Header -->
|
|
7
|
+
<div class="mb-8 text-center">
|
|
8
|
+
<h1 class="font-['Montserrat'] font-extrabold text-2xl text-slate-900 mb-2">Thông tin cá nhân</h1>
|
|
9
|
+
<p class="text-slate-500 text-sm">Cập nhật thông tin tài khoản và đặt mật khẩu.</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<!-- Loading skeleton -->
|
|
13
|
+
<div v-if="pageLoading" class="bg-white rounded-2xl shadow-sm border border-slate-200/80 p-8 space-y-5">
|
|
14
|
+
<div v-for="i in 4" :key="i" class="space-y-1.5">
|
|
15
|
+
<div class="h-4 w-24 bg-slate-200 rounded animate-pulse" />
|
|
16
|
+
<div class="h-11 bg-slate-100 rounded-[10px] animate-pulse" />
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Form card -->
|
|
21
|
+
<div v-else class="bg-white rounded-2xl shadow-sm border border-slate-200/80 p-8">
|
|
22
|
+
|
|
23
|
+
<!-- Not logged in -->
|
|
24
|
+
<div v-if="!accessToken" class="text-center space-y-4 py-4">
|
|
25
|
+
<div class="w-14 h-14 rounded-[10px] bg-amber-50 flex items-center justify-center mx-auto">
|
|
26
|
+
<Icon name="lucide:lock" class="text-amber-500 text-[24px]" />
|
|
27
|
+
</div>
|
|
28
|
+
<p class="text-slate-600 text-sm">Bạn cần đăng nhập để cập nhật thông tin.</p>
|
|
29
|
+
<NuxtLink to="/dang-nhap"
|
|
30
|
+
class="inline-flex items-center gap-2 bg-[#004AC6] hover:bg-[#1D4ED8] text-white font-bold px-6 py-3 rounded-[10px] transition-all">
|
|
31
|
+
Đăng nhập ngay
|
|
32
|
+
</NuxtLink>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<!-- Success state -->
|
|
36
|
+
<div v-else-if="success" class="text-center space-y-4 py-4">
|
|
37
|
+
<div class="w-16 h-16 rounded-[10px] bg-emerald-50 flex items-center justify-center mx-auto">
|
|
38
|
+
<Icon name="lucide:check-circle" class="text-emerald-500 text-[32px]" />
|
|
39
|
+
</div>
|
|
40
|
+
<h3 class="font-['Montserrat'] font-bold text-lg text-slate-900">Cập nhật thành công!</h3>
|
|
41
|
+
<p class="text-slate-500 text-sm">Thông tin tài khoản đã được lưu.</p>
|
|
42
|
+
<NuxtLink to="/"
|
|
43
|
+
class="inline-flex items-center gap-2 bg-[#004AC6] hover:bg-[#1D4ED8] text-white font-bold px-6 py-3 rounded-[10px] transition-all">
|
|
44
|
+
Về trang chủ
|
|
45
|
+
</NuxtLink>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- Update form -->
|
|
49
|
+
<form v-else @submit.prevent="onSubmit" class="space-y-5">
|
|
50
|
+
<!-- Avatar placeholder -->
|
|
51
|
+
<div class="flex items-center gap-4 pb-4 border-b border-slate-100">
|
|
52
|
+
<div
|
|
53
|
+
class="w-14 h-14 rounded-[10px] bg-gradient-to-br from-[#004AC6] to-[#1D4ED8] flex items-center justify-center text-white font-bold text-xl shrink-0">
|
|
54
|
+
{{ avatarInitial }}
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<p class="font-semibold text-slate-900 text-sm">{{ form.fullName || 'Chưa cập nhật' }}</p>
|
|
58
|
+
<p class="text-xs text-slate-400 mt-0.5">{{ form.phone }}</p>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Full name -->
|
|
63
|
+
<div>
|
|
64
|
+
<label for="fullName" class="block text-sm font-semibold text-slate-700 mb-1.5">Họ và tên</label>
|
|
65
|
+
<div class="relative">
|
|
66
|
+
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400">
|
|
67
|
+
<Icon name="lucide:user" class="text-[17px]" />
|
|
68
|
+
</span>
|
|
69
|
+
<input id="fullName" v-model="form.fullName" type="text" autocomplete="name" placeholder="Nhập họ và tên"
|
|
70
|
+
:class="[
|
|
71
|
+
'w-full pl-10 pr-4 py-3 rounded-[10px] border text-sm outline-none transition-all',
|
|
72
|
+
errors.fullName
|
|
73
|
+
? 'border-red-400 bg-red-50 focus:border-red-500 focus:ring-2 focus:ring-red-100'
|
|
74
|
+
: 'border-slate-200 bg-slate-50 focus:border-[#004AC6] focus:ring-2 focus:ring-blue-100 focus:bg-white'
|
|
75
|
+
]" />
|
|
76
|
+
</div>
|
|
77
|
+
<p v-if="errors.fullName" class="mt-1 text-xs text-red-500 flex items-center gap-1">
|
|
78
|
+
<Icon name="lucide:alert-circle" class="text-[13px]" /> {{ errors.fullName }}
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- Email -->
|
|
83
|
+
<div>
|
|
84
|
+
<label for="email" class="block text-sm font-semibold text-slate-700 mb-1.5">Email</label>
|
|
85
|
+
<div class="relative">
|
|
86
|
+
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400">
|
|
87
|
+
<Icon name="lucide:mail" class="text-[17px]" />
|
|
88
|
+
</span>
|
|
89
|
+
<input id="email" v-model="form.email" type="email" autocomplete="email" placeholder="email@example.com"
|
|
90
|
+
:class="[
|
|
91
|
+
'w-full pl-10 pr-4 py-3 rounded-[10px] border text-sm outline-none transition-all',
|
|
92
|
+
errors.email
|
|
93
|
+
? 'border-red-400 bg-red-50 focus:border-red-500 focus:ring-2 focus:ring-red-100'
|
|
94
|
+
: 'border-slate-200 bg-slate-50 focus:border-[#004AC6] focus:ring-2 focus:ring-blue-100 focus:bg-white'
|
|
95
|
+
]" />
|
|
96
|
+
</div>
|
|
97
|
+
<p v-if="errors.email" class="mt-1 text-xs text-red-500 flex items-center gap-1">
|
|
98
|
+
<Icon name="lucide:alert-circle" class="text-[13px]" /> {{ errors.email }}
|
|
99
|
+
</p>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Phone (readonly) -->
|
|
103
|
+
<div>
|
|
104
|
+
<label for="phone" class="block text-sm font-semibold text-slate-700 mb-1.5">Số điện thoại</label>
|
|
105
|
+
<div class="relative">
|
|
106
|
+
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400">
|
|
107
|
+
<Icon name="lucide:phone" class="text-[17px]" />
|
|
108
|
+
</span>
|
|
109
|
+
<input id="phone" :value="form.phone" type="tel" readonly
|
|
110
|
+
class="w-full pl-10 pr-4 py-3 rounded-[10px] border border-slate-200 bg-slate-100 text-slate-500 text-sm cursor-not-allowed" />
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Password -->
|
|
115
|
+
<div class="pt-2 border-t border-slate-100">
|
|
116
|
+
<p class="text-xs text-slate-400 mb-4">Đặt mật khẩu để đăng nhập lần sau</p>
|
|
117
|
+
|
|
118
|
+
<div class="space-y-5">
|
|
119
|
+
<div>
|
|
120
|
+
<label for="password" class="block text-sm font-semibold text-slate-700 mb-1.5">Mật khẩu</label>
|
|
121
|
+
<div class="relative">
|
|
122
|
+
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400">
|
|
123
|
+
<Icon name="lucide:lock" class="text-[17px]" />
|
|
124
|
+
</span>
|
|
125
|
+
<input
|
|
126
|
+
id="password"
|
|
127
|
+
v-model="form.password"
|
|
128
|
+
:type="showPassword ? 'text' : 'password'"
|
|
129
|
+
autocomplete="new-password"
|
|
130
|
+
placeholder="Tối thiểu 6 ký tự"
|
|
131
|
+
:class="[
|
|
132
|
+
'w-full pl-10 pr-11 py-3 rounded-[10px] border text-sm outline-none transition-all',
|
|
133
|
+
errors.password
|
|
134
|
+
? 'border-red-400 bg-red-50 focus:border-red-500 focus:ring-2 focus:ring-red-100'
|
|
135
|
+
: 'border-slate-200 bg-slate-50 focus:border-[#004AC6] focus:ring-2 focus:ring-blue-100 focus:bg-white'
|
|
136
|
+
]"
|
|
137
|
+
/>
|
|
138
|
+
<button type="button" @click="showPassword = !showPassword" class="absolute right-3.5 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors">
|
|
139
|
+
<Icon :name="showPassword ? 'lucide:eye-off' : 'lucide:eye'" class="text-[17px]" />
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
<p v-if="errors.password" class="mt-1 text-xs text-red-500 flex items-center gap-1">
|
|
143
|
+
<Icon name="lucide:alert-circle" class="text-[13px]" /> {{ errors.password }}
|
|
144
|
+
</p>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div>
|
|
148
|
+
<label for="confirmPassword" class="block text-sm font-semibold text-slate-700 mb-1.5">Xác nhận mật khẩu</label>
|
|
149
|
+
<div class="relative">
|
|
150
|
+
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400">
|
|
151
|
+
<Icon name="lucide:lock" class="text-[17px]" />
|
|
152
|
+
</span>
|
|
153
|
+
<input
|
|
154
|
+
id="confirmPassword"
|
|
155
|
+
v-model="form.confirmPassword"
|
|
156
|
+
:type="showConfirm ? 'text' : 'password'"
|
|
157
|
+
autocomplete="new-password"
|
|
158
|
+
placeholder="Nhập lại mật khẩu"
|
|
159
|
+
:class="[
|
|
160
|
+
'w-full pl-10 pr-11 py-3 rounded-[10px] border text-sm outline-none transition-all',
|
|
161
|
+
errors.confirmPassword
|
|
162
|
+
? 'border-red-400 bg-red-50 focus:border-red-500 focus:ring-2 focus:ring-red-100'
|
|
163
|
+
: 'border-slate-200 bg-slate-50 focus:border-[#004AC6] focus:ring-2 focus:ring-blue-100 focus:bg-white'
|
|
164
|
+
]"
|
|
165
|
+
/>
|
|
166
|
+
<button type="button" @click="showConfirm = !showConfirm" class="absolute right-3.5 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors">
|
|
167
|
+
<Icon :name="showConfirm ? 'lucide:eye-off' : 'lucide:eye'" class="text-[17px]" />
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
<p v-if="errors.confirmPassword" class="mt-1 text-xs text-red-500 flex items-center gap-1">
|
|
171
|
+
<Icon name="lucide:alert-circle" class="text-[13px]" /> {{ errors.confirmPassword }}
|
|
172
|
+
</p>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div v-if="apiError"
|
|
178
|
+
class="flex items-start gap-2 bg-red-50 border border-red-200 rounded-[10px] px-4 py-3 text-sm text-red-700">
|
|
179
|
+
<Icon name="lucide:alert-triangle" class="text-[16px] mt-0.5 shrink-0" />
|
|
180
|
+
<span>{{ apiError }}</span>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<!-- Submit -->
|
|
184
|
+
<button type="submit" :disabled="loading"
|
|
185
|
+
class="w-full flex items-center justify-center gap-2 bg-[#004AC6] hover:bg-[#1D4ED8] active:scale-[.98] text-white font-bold py-3.5 rounded-[10px] transition-all shadow-sm disabled:opacity-60 disabled:cursor-not-allowed">
|
|
186
|
+
<span v-if="!loading">Cập nhật thông tin</span>
|
|
187
|
+
<span v-else class="flex items-center gap-2">
|
|
188
|
+
<svg class="animate-spin w-4 h-4" viewBox="0 0 24 24" fill="none">
|
|
189
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
190
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z" />
|
|
191
|
+
</svg>
|
|
192
|
+
Đang lưu...
|
|
193
|
+
</span>
|
|
194
|
+
</button>
|
|
195
|
+
</form>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
<TheFooter/>
|
|
200
|
+
</div>
|
|
201
|
+
</template>
|
|
202
|
+
|
|
203
|
+
<script setup lang="ts">
|
|
204
|
+
definePageMeta({ layout: 'default' })
|
|
205
|
+
|
|
206
|
+
useHead({ title: 'Cập nhật thông tin — Long Vân' })
|
|
207
|
+
|
|
208
|
+
const { updateInfo, getUserByToken } = useAuthApi()
|
|
209
|
+
const { getCookie } = useCookieHelper()
|
|
210
|
+
const config = useRuntimeConfig()
|
|
211
|
+
|
|
212
|
+
const tokenKey = config.public.ACCESSTOKEN_NAME as string
|
|
213
|
+
const accessToken = ref('')
|
|
214
|
+
const pageLoading = ref(true)
|
|
215
|
+
const loading = ref(false)
|
|
216
|
+
const success = ref(false)
|
|
217
|
+
const apiError = ref('')
|
|
218
|
+
const showPassword = ref(false)
|
|
219
|
+
const showConfirm = ref(false)
|
|
220
|
+
|
|
221
|
+
const form = reactive({ fullName: '', email: '', phone: '', password: '', confirmPassword: '' })
|
|
222
|
+
const errors = reactive({ fullName: '', email: '', phone: '', password: '', confirmPassword: '' })
|
|
223
|
+
|
|
224
|
+
const avatarInitial = computed(() => {
|
|
225
|
+
const name = form.fullName.trim()
|
|
226
|
+
if (!name) return '?'
|
|
227
|
+
return name.charAt(0).toUpperCase()
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
function validate() {
|
|
231
|
+
errors.fullName = ''
|
|
232
|
+
errors.email = ''
|
|
233
|
+
errors.password = ''
|
|
234
|
+
errors.confirmPassword = ''
|
|
235
|
+
let valid = true
|
|
236
|
+
|
|
237
|
+
if (!form.fullName.trim()) {
|
|
238
|
+
errors.fullName = 'Vui lòng nhập họ và tên'
|
|
239
|
+
valid = false
|
|
240
|
+
}
|
|
241
|
+
if (form.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
|
|
242
|
+
errors.email = 'Email không hợp lệ'
|
|
243
|
+
valid = false
|
|
244
|
+
}
|
|
245
|
+
if (form.password && form.password.length < 6) {
|
|
246
|
+
errors.password = 'Mật khẩu tối thiểu 6 ký tự'
|
|
247
|
+
valid = false
|
|
248
|
+
}
|
|
249
|
+
if (form.password && form.password !== form.confirmPassword) {
|
|
250
|
+
errors.confirmPassword = 'Mật khẩu xác nhận không khớp'
|
|
251
|
+
valid = false
|
|
252
|
+
}
|
|
253
|
+
return valid
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function onSubmit() {
|
|
257
|
+
if (!validate()) return
|
|
258
|
+
loading.value = true
|
|
259
|
+
apiError.value = ''
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
await updateInfo(
|
|
263
|
+
accessToken.value,
|
|
264
|
+
{
|
|
265
|
+
fullName: form.fullName.trim(),
|
|
266
|
+
phone: form.phone,
|
|
267
|
+
email: form.email.trim() || undefined,
|
|
268
|
+
},
|
|
269
|
+
'',
|
|
270
|
+
form.password || ''
|
|
271
|
+
)
|
|
272
|
+
success.value = true
|
|
273
|
+
} catch (err: any) {
|
|
274
|
+
apiError.value = err?.message || 'Cập nhật thất bại. Vui lòng thử lại.'
|
|
275
|
+
} finally {
|
|
276
|
+
loading.value = false
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
onMounted(async () => {
|
|
281
|
+
const token = getCookie(tokenKey)
|
|
282
|
+
if (!token) {
|
|
283
|
+
pageLoading.value = false
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
accessToken.value = token
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const res = await getUserByToken(token)
|
|
291
|
+
if (res) {
|
|
292
|
+
form.fullName = res.fullName || ''
|
|
293
|
+
form.email = res.email || ''
|
|
294
|
+
form.phone = res.phone || ''
|
|
295
|
+
}
|
|
296
|
+
} catch (e) {
|
|
297
|
+
// Token expired or invalid
|
|
298
|
+
} finally {
|
|
299
|
+
pageLoading.value = false
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
</script>
|