adata-ui 2.0.12 → 2.0.14
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/components/elements/select/ASelect.vue +8 -3
- package/components/forms/login/ALogin.vue +356 -0
- package/components/forms/registration/ARegistration.vue +230 -0
- package/components/modals/AConfirmationEmail.vue +40 -0
- package/components/modals/Accept.vue +45 -0
- package/components/modals/Resend.vue +81 -0
- package/components/modals/two-factor/otp-input.vue +140 -0
- package/components/modals/two-factor/two-factor.vue +78 -0
- package/composables/useHeaderNavigationLinks.ts +13 -13
- package/icons/browsers/browser-google.vue +7 -1
- package/icons/google.vue +41 -0
- package/icons/mailru.vue +34 -0
- package/icons/yandex.vue +28 -0
- package/lang/en.ts +77 -1
- package/lang/kk.ts +77 -1
- package/lang/ru.ts +80 -0
- package/package.json +1 -1
|
@@ -77,9 +77,14 @@ const disabledStyles =
|
|
|
77
77
|
'disabled:pointer-events-none disabled:opacity-40 disabled:bg-deepblue-50 disabled:text-deepblue-200 disabled:dark:text-gray-500'
|
|
78
78
|
|
|
79
79
|
const componentOptions = computed(() =>
|
|
80
|
-
props.options.filter((item: T) =>
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
props.options.filter((item: T) => {
|
|
81
|
+
if (Array.isArray(props.searchKey) && props.searchKey.length === 2) {
|
|
82
|
+
return item[props.searchKey[0] as keyof T].toLowerCase().includes(searchValue.value.toLowerCase()) ||
|
|
83
|
+
item[props.searchKey[1] as keyof T].toLowerCase().includes(searchValue.value.toLowerCase())
|
|
84
|
+
} else {
|
|
85
|
+
return item[props.searchKey as keyof T].toLowerCase().includes(searchValue.value.toLowerCase())
|
|
86
|
+
}
|
|
87
|
+
})
|
|
83
88
|
)
|
|
84
89
|
|
|
85
90
|
onClickOutside(wrapper, () => {
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useToggle } from '@vueuse/shared'
|
|
3
|
+
import * as z from 'zod'
|
|
4
|
+
import AConfirmationEmail from '#adata-ui/components/modals/AConfirmationEmail.vue'
|
|
5
|
+
import Resend from '#adata-ui/components/modals/Resend.vue'
|
|
6
|
+
import TwoFactor from '#adata-ui/components/modals/two-factor/two-factor.vue'
|
|
7
|
+
|
|
8
|
+
const appConfig = useAppConfig()
|
|
9
|
+
const landingUrl = appConfig.myLayer.landingUrl
|
|
10
|
+
const [isResendModal, toggleResendModal] = useToggle()
|
|
11
|
+
const isConfirmationEmailModal = ref(false)
|
|
12
|
+
const isTwoFactorOpen = ref(false)
|
|
13
|
+
const isLoadingOtp = ref(false)
|
|
14
|
+
const { $api, $toast } = useNuxtApp()
|
|
15
|
+
const localePath = useLocalePath()
|
|
16
|
+
const showOtpError = ref(false)
|
|
17
|
+
const { t, locale } = useI18n()
|
|
18
|
+
const route = useRoute()
|
|
19
|
+
const submitted = ref(false)
|
|
20
|
+
const accessToken = ref(null)
|
|
21
|
+
|
|
22
|
+
export interface ILoginForm {
|
|
23
|
+
username: string
|
|
24
|
+
password: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
const loginSchema = z.object({
|
|
29
|
+
username: z.string().nonempty(t('errors.required')).email(t('register.form.errors.email')),
|
|
30
|
+
password: z.string().nonempty(t('errors.required')),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const validation = computed(() => {
|
|
34
|
+
if (!submitted.value) return null
|
|
35
|
+
const result = loginSchema.safeParse(form)
|
|
36
|
+
return result.success ? null : result.error
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
function getError(path: string) {
|
|
40
|
+
return validation.value?.issues.find(issue => issue.path[0] === path)?.message
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function savePassAndLogin(form: { username: string, password: string }) {
|
|
44
|
+
const { username, password } = form
|
|
45
|
+
const cookieOptions = {
|
|
46
|
+
expires: rememberMe.value ? new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) : undefined, // Срок действия 14 дней
|
|
47
|
+
}
|
|
48
|
+
useCookie('username', cookieOptions).value = username
|
|
49
|
+
useCookie('password', cookieOptions).value = password
|
|
50
|
+
}
|
|
51
|
+
function clearAuthCookie() {
|
|
52
|
+
useCookie('username').value = null
|
|
53
|
+
useCookie('password').value = null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const loading = ref(false)
|
|
57
|
+
const form: ILoginForm = reactive({
|
|
58
|
+
username: useCookie('username').value || '',
|
|
59
|
+
password: useCookie('password').value || '',
|
|
60
|
+
})
|
|
61
|
+
const rememberMe = ref(false)
|
|
62
|
+
|
|
63
|
+
async function submit() {
|
|
64
|
+
submitted.value = true
|
|
65
|
+
if (!validation.value) {
|
|
66
|
+
loading.value = true
|
|
67
|
+
const login = await fetch(`https://auth.adtdev.kz/api/login`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
lang: locale.value,
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
username: form.username.trim().toLowerCase(),
|
|
75
|
+
password: form.password.toString()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
const { data, message } = await login.json().catch(() => ({}));
|
|
79
|
+
if (data) accessToken.value = data?.access_token
|
|
80
|
+
if (login.status > 202) {
|
|
81
|
+
if (login.status === 422) {
|
|
82
|
+
$toast.error(t('error.validation'))
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
$toast.error(message)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
rememberMe.value ? savePassAndLogin(form) : clearAuthCookie()
|
|
90
|
+
if (data.is_2fa_enabled) {
|
|
91
|
+
isTwoFactorOpen.value = true
|
|
92
|
+
}
|
|
93
|
+
else if (data.email_is_verified) {
|
|
94
|
+
const response = await fetch('https://auth.adtdev.kz/api/access/cookie',{
|
|
95
|
+
method: "GET",
|
|
96
|
+
credentials: 'include',
|
|
97
|
+
headers: {
|
|
98
|
+
Authorization: `Bearer ${accessToken.value}`,
|
|
99
|
+
lang: locale.value,
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
const { data: cookiesData } = await response.json()
|
|
103
|
+
if (cookiesData) {
|
|
104
|
+
const { access_token, expire_in } = cookiesData
|
|
105
|
+
const hostname = location.hostname.split('.').reverse()
|
|
106
|
+
document.cookie = `accessToken=${access_token}; max-age=${expire_in}; domain=.${hostname[1]}.${hostname[0]}; path=/`
|
|
107
|
+
}
|
|
108
|
+
$toast.success(t('login.successfully'))
|
|
109
|
+
if (route.query.url) {
|
|
110
|
+
await navigateTo(route.query.url as string, {
|
|
111
|
+
external: true,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
await navigateTo(landingUrl, {
|
|
116
|
+
external: true,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
isConfirmationEmailModal.value = true
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
loading.value = false
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function authWithSocial(social: string) {
|
|
129
|
+
const mode = (useNuxtApp().$config.public.authApiURL as string).includes('adata') ? 'adata' : 'adtdev'
|
|
130
|
+
document.location.replace(`https://auth.${mode}.kz/api/login/social?source=${social}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function onResend() {
|
|
134
|
+
const { data, success, message } = await $api.auth.emailResend()
|
|
135
|
+
if (success) {
|
|
136
|
+
$toast.success(message)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function onRegister() {
|
|
141
|
+
if (Object.keys(route.query).length) {
|
|
142
|
+
navigateTo(localePath({ path: `/register`, query: route.query }))
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
navigateTo(localePath(`/register`))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function toTariffs() {
|
|
150
|
+
return navigateToLocalizedPage({ locale, projectUrl: appConfig.myLayer.landingUrl, path: '/tariffs', target: '_self' })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onMounted(() => {
|
|
154
|
+
rememberMe.value = !!useCookie('username').value && !!useCookie('password').value
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
function confirmationEmailResend() {
|
|
158
|
+
isConfirmationEmailModal.value = false
|
|
159
|
+
toggleResendModal()
|
|
160
|
+
}
|
|
161
|
+
async function handleConfirmOtp(otpCode: string) {
|
|
162
|
+
isLoadingOtp.value = true
|
|
163
|
+
const login = await fetch(`https://auth.adtdev.kz/api/login`, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: {
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
lang: locale.value,
|
|
168
|
+
},
|
|
169
|
+
body: JSON.stringify({
|
|
170
|
+
'username': form.username.trim(),
|
|
171
|
+
'password': form.password.toString(),
|
|
172
|
+
'2fa_code': otpCode,
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
const { data, message } = await login.json().catch(() => ({}));
|
|
176
|
+
|
|
177
|
+
if (login.status > 202) {
|
|
178
|
+
if (login.status === 403) {
|
|
179
|
+
showOtpError.value = true
|
|
180
|
+
isLoadingOtp.value = false
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
|
|
185
|
+
const response = await fetch('https://auth.adtdev.kz/api/access/cookie',{
|
|
186
|
+
method: "GET",
|
|
187
|
+
credentials: 'include',
|
|
188
|
+
headers: {
|
|
189
|
+
'Authorization': `Bearer ${accessToken.value}`,
|
|
190
|
+
lang: locale.value,
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
const { data: cookiesData } = await response.json()
|
|
194
|
+
if (cookiesData?.access_token) {
|
|
195
|
+
const { access_token, expire_in } = cookiesData
|
|
196
|
+
const hostname = location.hostname.split('.').reverse()
|
|
197
|
+
document.cookie = `accessToken=${access_token}; max-age=${expire_in}; domain=.${hostname[1]}.${hostname[0]}; path=/`
|
|
198
|
+
}
|
|
199
|
+
$toast.success(t('login.successfully'))
|
|
200
|
+
if (route.query.url) {
|
|
201
|
+
await navigateTo(route.query.url as string, {
|
|
202
|
+
external: true,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
await navigateTo(landingUrl, {
|
|
207
|
+
external: true,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
isTwoFactorOpen.value = false
|
|
212
|
+
}
|
|
213
|
+
isLoadingOtp.value = false
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function handleEnter(e: KeyboardEvent) {
|
|
217
|
+
if (e.key === 'Enter' && !isTwoFactorOpen.value && !isConfirmationEmailModal.value && !isResendModal.value) {
|
|
218
|
+
submit()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
onMounted(() => {
|
|
223
|
+
document.addEventListener('keyup', handleEnter)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
onBeforeUnmount(() => {
|
|
227
|
+
document.removeEventListener('keyup', handleEnter)
|
|
228
|
+
})
|
|
229
|
+
</script>
|
|
230
|
+
|
|
231
|
+
<template>
|
|
232
|
+
<div class="flex flex-col gap-5 rounded-lg bg-white px-7 py-8 dark:bg-gray-900">
|
|
233
|
+
<h1 class="heading-02 text-center">
|
|
234
|
+
{{ $t('login.form.title') }}
|
|
235
|
+
</h1>
|
|
236
|
+
<p class="body-400 text-center">
|
|
237
|
+
{{ $t('login.form.subtitle') }}
|
|
238
|
+
</p>
|
|
239
|
+
<div class="flex flex-col gap-4">
|
|
240
|
+
<a-input-standard
|
|
241
|
+
v-model="form.username"
|
|
242
|
+
type="email"
|
|
243
|
+
:label="$t('login.form.labels.email')"
|
|
244
|
+
:error="getError('username')"
|
|
245
|
+
/>
|
|
246
|
+
<a-input-password
|
|
247
|
+
v-model="form.password"
|
|
248
|
+
:label="$t('login.form.labels.password')"
|
|
249
|
+
:error="getError('password')"
|
|
250
|
+
/>
|
|
251
|
+
<div class="flex items-center justify-between">
|
|
252
|
+
<div class="body-400 flex gap-2 ">
|
|
253
|
+
<a-checkbox
|
|
254
|
+
v-model="rememberMe"
|
|
255
|
+
name="remember_me"
|
|
256
|
+
/>
|
|
257
|
+
<label
|
|
258
|
+
for="remember_me"
|
|
259
|
+
class="cursor-pointer"
|
|
260
|
+
>{{ $t('login.form.remember_me') }}</label>
|
|
261
|
+
</div>
|
|
262
|
+
<nuxt-link-locale
|
|
263
|
+
class="link-s-400"
|
|
264
|
+
to="/password-recovery"
|
|
265
|
+
>
|
|
266
|
+
{{ $t('login.form.forget_password') }}
|
|
267
|
+
</nuxt-link-locale>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="flex items-center gap-4">
|
|
271
|
+
<div class="bg-deepblue-500/30 h-px w-full" />
|
|
272
|
+
<div class="flex shrink-0 gap-4">
|
|
273
|
+
<span
|
|
274
|
+
class="cursor-pointer"
|
|
275
|
+
@click="authWithSocial('google')"
|
|
276
|
+
>
|
|
277
|
+
<a-icon-google />
|
|
278
|
+
</span>
|
|
279
|
+
<span
|
|
280
|
+
class="cursor-pointer"
|
|
281
|
+
@click="authWithSocial('yandex')"
|
|
282
|
+
>
|
|
283
|
+
<a-icon-yandex />
|
|
284
|
+
</span>
|
|
285
|
+
<span
|
|
286
|
+
class="cursor-pointer"
|
|
287
|
+
@click="authWithSocial('mailru')"
|
|
288
|
+
>
|
|
289
|
+
<a-icon-mailru />
|
|
290
|
+
</span>
|
|
291
|
+
</div>
|
|
292
|
+
<div class="bg-deepblue-500/30 h-px w-full" />
|
|
293
|
+
</div>
|
|
294
|
+
<a-button
|
|
295
|
+
:loading="loading"
|
|
296
|
+
type="submit"
|
|
297
|
+
@click="submit"
|
|
298
|
+
>
|
|
299
|
+
{{ $t('actions.login') }}
|
|
300
|
+
</a-button>
|
|
301
|
+
<p class="body-400 text-center">
|
|
302
|
+
{{ $t('login.form.first_time') }}
|
|
303
|
+
</p>
|
|
304
|
+
|
|
305
|
+
<a-button
|
|
306
|
+
type="button"
|
|
307
|
+
view="outline"
|
|
308
|
+
class="w-full"
|
|
309
|
+
@click="onRegister"
|
|
310
|
+
>
|
|
311
|
+
{{ $t('actions.register') }}
|
|
312
|
+
</a-button>
|
|
313
|
+
|
|
314
|
+
<a-button
|
|
315
|
+
type="button"
|
|
316
|
+
view="transparent"
|
|
317
|
+
class="w-full"
|
|
318
|
+
@click="toTariffs"
|
|
319
|
+
>
|
|
320
|
+
{{ $t('actions.toTariffs') }}
|
|
321
|
+
</a-button>
|
|
322
|
+
|
|
323
|
+
<a-alert
|
|
324
|
+
class="max-w-screen-sm !text-[10px]"
|
|
325
|
+
size="xs"
|
|
326
|
+
>
|
|
327
|
+
{{ $t('info.userAgreement') }}
|
|
328
|
+
</a-alert>
|
|
329
|
+
|
|
330
|
+
<a-modal v-model="isResendModal">
|
|
331
|
+
<resend
|
|
332
|
+
v-if="isResendModal"
|
|
333
|
+
@close="isResendModal = false"
|
|
334
|
+
@resend="onResend"
|
|
335
|
+
/>
|
|
336
|
+
</a-modal>
|
|
337
|
+
<a-modal v-model="isConfirmationEmailModal">
|
|
338
|
+
<a-confirmation-email
|
|
339
|
+
v-if="isConfirmationEmailModal"
|
|
340
|
+
@close="isConfirmationEmailModal = false"
|
|
341
|
+
@resend="confirmationEmailResend"
|
|
342
|
+
/>
|
|
343
|
+
</a-modal>
|
|
344
|
+
<two-factor
|
|
345
|
+
v-model="isTwoFactorOpen"
|
|
346
|
+
v-model:error="showOtpError"
|
|
347
|
+
:loading="isLoadingOtp"
|
|
348
|
+
@confirm="handleConfirmOtp"
|
|
349
|
+
@close="isTwoFactorOpen = false"
|
|
350
|
+
/>
|
|
351
|
+
</div>
|
|
352
|
+
</template>
|
|
353
|
+
|
|
354
|
+
<style scoped>
|
|
355
|
+
|
|
356
|
+
</style>
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, reactive, ref } from 'vue'
|
|
3
|
+
import { useToggle } from '@vueuse/shared'
|
|
4
|
+
import { PAGES } from '#adata-ui/shared/constans/pages'
|
|
5
|
+
|
|
6
|
+
import Accept from '#adata-ui/components/modals/Accept.vue'
|
|
7
|
+
|
|
8
|
+
import * as z from 'zod'
|
|
9
|
+
|
|
10
|
+
const { $toast } = useNuxtApp()
|
|
11
|
+
const route = useRoute()
|
|
12
|
+
|
|
13
|
+
const localePath = useLocalePath()
|
|
14
|
+
const { t, locale} = useI18n()
|
|
15
|
+
const { myLayer }: any = useAppConfig()
|
|
16
|
+
const redirectUrl = useCookie('redirect_url')
|
|
17
|
+
|
|
18
|
+
export interface RegistrationForm {
|
|
19
|
+
email: string
|
|
20
|
+
password: string
|
|
21
|
+
password_confirmation: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const form = reactive<RegistrationForm>({
|
|
25
|
+
email: '',
|
|
26
|
+
password: '',
|
|
27
|
+
password_confirmation: '',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const [isAcceptModal, toggleAcceptModal] = useToggle()
|
|
31
|
+
const agreement = ref(false)
|
|
32
|
+
const loading = ref(false)
|
|
33
|
+
|
|
34
|
+
const registerSchema = z.object({
|
|
35
|
+
email: z.string().nonempty(t('register.form.errors.required')).email(t('register.form.errors.email')),
|
|
36
|
+
password: z
|
|
37
|
+
.string()
|
|
38
|
+
.nonempty(t('register.form.errors.required'))
|
|
39
|
+
.min(8, t('register.form.errors.low_security', { length: 8 }))
|
|
40
|
+
.regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
|
|
41
|
+
password_confirmation: z
|
|
42
|
+
.string()
|
|
43
|
+
.nonempty(t('register.form.errors.required'))
|
|
44
|
+
.min(8, t('register.form.errors.low_security', { length: 8 }))
|
|
45
|
+
.regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
|
|
46
|
+
}).refine(
|
|
47
|
+
data => data.password === data.password_confirmation,
|
|
48
|
+
{
|
|
49
|
+
message: t('register.form.errors.sameAs'),
|
|
50
|
+
path: ['password_confirmation'],
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const submitted = ref(false)
|
|
55
|
+
|
|
56
|
+
const validation = computed(() => {
|
|
57
|
+
if (!submitted.value) return null
|
|
58
|
+
const result = registerSchema.safeParse(form)
|
|
59
|
+
return result.success ? null : result.error
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
function getError(path: string) {
|
|
63
|
+
return validation.value?.issues?.find(issue => issue.path[0] === path)?.message
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function onSubmit() {
|
|
67
|
+
if (!agreement.value) return
|
|
68
|
+
|
|
69
|
+
submitted.value = true
|
|
70
|
+
|
|
71
|
+
if (validation.value) return
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
loading.value = true
|
|
75
|
+
const register = await fetch(`https://auth.adtdev.kz/api/register`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
lang: locale.value,
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify(form)
|
|
82
|
+
})
|
|
83
|
+
const { data, message, error } = await register.json().catch(() => ({}));
|
|
84
|
+
if (data) {
|
|
85
|
+
toggleAcceptModal()
|
|
86
|
+
$toast.success(message)
|
|
87
|
+
}
|
|
88
|
+
else if (error) {
|
|
89
|
+
$toast.error(error.data.message.email || error.data.message.password)
|
|
90
|
+
}
|
|
91
|
+
console.error(error.data)
|
|
92
|
+
loading.value = false
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
console.error(e)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (route.query.url) {
|
|
100
|
+
redirectUrl.value = route.query.url as string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function goBack() {
|
|
104
|
+
const url = new URL(redirectUrl.value)
|
|
105
|
+
window.location.href = `${url.protocol}//${url.hostname}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// onMounted(() => {
|
|
109
|
+
// v$.value.$touch();
|
|
110
|
+
// });
|
|
111
|
+
|
|
112
|
+
async function onResend() {
|
|
113
|
+
|
|
114
|
+
const emailResend = await fetch('https://auth.adtdev.kz/api/email/resend',{
|
|
115
|
+
method: "GET",
|
|
116
|
+
credentials: 'include',
|
|
117
|
+
headers: {
|
|
118
|
+
lang: locale.value,
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
const { data, success, message } = await emailResend.json()
|
|
122
|
+
if (success) {
|
|
123
|
+
toggleAcceptModal()
|
|
124
|
+
loading.value = false
|
|
125
|
+
$toast.success(message)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getUrl() {
|
|
130
|
+
return navigateTo(locale.value !== 'ru'
|
|
131
|
+
? myLayer.landingUrl + localePath('/') + PAGES.userAgreement
|
|
132
|
+
: myLayer.landingUrl + PAGES.userAgreement, {
|
|
133
|
+
external: true,
|
|
134
|
+
open: {
|
|
135
|
+
target: "_blank",
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function handleEnter(event: KeyboardEvent) {
|
|
141
|
+
if (event.key === 'Enter' && !isAcceptModal.value) {
|
|
142
|
+
onSubmit()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
onMounted(() => {
|
|
147
|
+
document.addEventListener('keyup', handleEnter)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
onBeforeUnmount(() => {
|
|
151
|
+
document.removeEventListener('keyup', handleEnter)
|
|
152
|
+
})
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<template>
|
|
156
|
+
<form
|
|
157
|
+
class="flex flex-col items-stretch gap-5 rounded-lg bg-white px-4 py-8 md:px-7 dark:bg-gray-900"
|
|
158
|
+
novalidate
|
|
159
|
+
@submit.prevent="onSubmit"
|
|
160
|
+
>
|
|
161
|
+
<h2 class="text-center text-2xl font-bold">
|
|
162
|
+
{{ t('register.form.title') }}
|
|
163
|
+
</h2>
|
|
164
|
+
<p class="text-center text-sm">
|
|
165
|
+
{{ t('register.form.subtitle') }}
|
|
166
|
+
</p>
|
|
167
|
+
<a-input-standard
|
|
168
|
+
v-model="form.email"
|
|
169
|
+
:label="t('register.form.labels.email')"
|
|
170
|
+
:error="getError('email')"
|
|
171
|
+
type="email"
|
|
172
|
+
/>
|
|
173
|
+
|
|
174
|
+
<a-input-password
|
|
175
|
+
v-model="form.password"
|
|
176
|
+
:label="t('register.form.labels.password')"
|
|
177
|
+
:error="getError('password')"
|
|
178
|
+
/>
|
|
179
|
+
|
|
180
|
+
<a-input-password
|
|
181
|
+
v-model="form.password_confirmation"
|
|
182
|
+
:label="t('register.form.labels.password_confirmation')"
|
|
183
|
+
:error="getError('password_confirmation')"
|
|
184
|
+
/>
|
|
185
|
+
<a-alert color="blue">
|
|
186
|
+
{{ t('register.form.alert') }}
|
|
187
|
+
<template #icon>
|
|
188
|
+
<a-icon-info-circle />
|
|
189
|
+
</template>
|
|
190
|
+
</a-alert>
|
|
191
|
+
<a-checkbox
|
|
192
|
+
v-model="agreement"
|
|
193
|
+
side="right"
|
|
194
|
+
>
|
|
195
|
+
<i18n-t keypath="register.form.agreement.text">
|
|
196
|
+
<template #link>
|
|
197
|
+
<nuxt-link-locale
|
|
198
|
+
class="text-blue-700"
|
|
199
|
+
@click="getUrl"
|
|
200
|
+
>
|
|
201
|
+
{{ t('register.form.agreement.link') }}
|
|
202
|
+
</nuxt-link-locale>
|
|
203
|
+
</template>
|
|
204
|
+
</i18n-t>
|
|
205
|
+
</a-checkbox>
|
|
206
|
+
<a-button
|
|
207
|
+
:disabled="!agreement"
|
|
208
|
+
:loading="loading"
|
|
209
|
+
>
|
|
210
|
+
{{ t('register.form.continue') }}
|
|
211
|
+
</a-button>
|
|
212
|
+
<p class="text-center text-sm">
|
|
213
|
+
{{ t('register.form.haveAcc') }}
|
|
214
|
+
</p>
|
|
215
|
+
<a-button
|
|
216
|
+
type="button"
|
|
217
|
+
view="outline"
|
|
218
|
+
:to="localePath('/')"
|
|
219
|
+
>
|
|
220
|
+
{{ t('register.form.enter') }}
|
|
221
|
+
</a-button>
|
|
222
|
+
</form>
|
|
223
|
+
<a-modal v-model="isAcceptModal">
|
|
224
|
+
<accept
|
|
225
|
+
v-if="isAcceptModal"
|
|
226
|
+
@back="goBack"
|
|
227
|
+
@repeated="onResend"
|
|
228
|
+
/>
|
|
229
|
+
</a-modal>
|
|
230
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const emit = defineEmits<{
|
|
3
|
+
(e: 'resend'): void
|
|
4
|
+
(e: 'close'): void
|
|
5
|
+
}>()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="flex flex-col justify-center items-center gap-5">
|
|
10
|
+
<p class="heading-02">
|
|
11
|
+
{{ $t('login.modal.title') }}
|
|
12
|
+
</p>
|
|
13
|
+
<a-ill-mail />
|
|
14
|
+
<p class="body-400 text-center">
|
|
15
|
+
{{ $t('login.modal.subtitle1') }}
|
|
16
|
+
</p>
|
|
17
|
+
<p class="body-400 text-center">
|
|
18
|
+
{{ $t('login.modal.confirmationEmail') }}
|
|
19
|
+
</p>
|
|
20
|
+
<div class="flex flex-col gap-2 w-full">
|
|
21
|
+
<a-button
|
|
22
|
+
class="w-full"
|
|
23
|
+
view="outline"
|
|
24
|
+
@click="emit('close')"
|
|
25
|
+
>
|
|
26
|
+
{{ $t('login.modal.back') }}
|
|
27
|
+
</a-button>
|
|
28
|
+
<a-button
|
|
29
|
+
class="w-full"
|
|
30
|
+
@click="emit('resend')"
|
|
31
|
+
>
|
|
32
|
+
{{ $t('login.modal.resend') }}
|
|
33
|
+
</a-button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<style scoped>
|
|
39
|
+
|
|
40
|
+
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
defineEmits<{ (e: 'back'): void, (e: 'repeated'): void }>()
|
|
4
|
+
|
|
5
|
+
const timer = ref(60)
|
|
6
|
+
|
|
7
|
+
function runTimer() {
|
|
8
|
+
const intervalId = setInterval(() => {
|
|
9
|
+
if (!timer.value) clearInterval(intervalId)
|
|
10
|
+
return timer.value--
|
|
11
|
+
}, 1000)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
onMounted(() => runTimer())
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<section class="flex flex-col items-stretch gap-4 py-4 py-8 text-center md:px-8">
|
|
19
|
+
<h2 class="text-2xl font-bold">
|
|
20
|
+
{{ $t('register.modal.title') }}
|
|
21
|
+
</h2>
|
|
22
|
+
<span class="mx-auto">
|
|
23
|
+
<a-ill-mail/>
|
|
24
|
+
</span>
|
|
25
|
+
<p class="text-sm">
|
|
26
|
+
{{ $t('register.modal.subtitle1') }}
|
|
27
|
+
</p>
|
|
28
|
+
<p class="text-sm">
|
|
29
|
+
{{ $t('register.modal.subtitle2') }}
|
|
30
|
+
</p>
|
|
31
|
+
<div v-if="timer > 0" class="text-2xl font-bold">
|
|
32
|
+
{{ timer }} {{ $t('register.modal.seconds') }}
|
|
33
|
+
</div>
|
|
34
|
+
<button
|
|
35
|
+
v-else
|
|
36
|
+
class="text-sm text-blue-500"
|
|
37
|
+
@click="$emit('repeated')"
|
|
38
|
+
>
|
|
39
|
+
{{ $t('register.modal.resend') }}
|
|
40
|
+
</button>
|
|
41
|
+
<a-button @click="$emit('back')">
|
|
42
|
+
{{ $t('register.modal.back') }}
|
|
43
|
+
</a-button>
|
|
44
|
+
</section>
|
|
45
|
+
</template>
|