adata-ui 2.0.22 → 2.0.24
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/.editorconfig +12 -12
- package/app.config.ts +3 -2
- package/components/elements/curve-block/ACurveBlock.vue +3 -6
- package/components/elements/feature-description/AFeatureDescription.vue +2 -2
- package/components/elements/select/ASelect.vue +50 -5
- package/components/modals/SubmitApplicationModal.vue +3 -0
- package/components/modals/id/AuthModal.vue +76 -0
- package/components/modals/id/IdEmailModal.vue +29 -0
- package/components/{forms/login/ALogin.vue → modals/id/IdLoginModal.vue} +38 -38
- package/components/modals/id/IdModals.vue +19 -0
- package/components/modals/id/IdNewPasswordModal.vue +137 -0
- package/components/modals/id/IdPasswordSuccessfulModal.vue +26 -0
- package/components/modals/id/IdRecoveryModal.vue +114 -0
- package/components/{forms/registration/ARegistration.vue → modals/id/IdRegistrationModal.vue} +14 -17
- package/components/modals/two-factor/two-factor.vue +9 -8
- package/components/navigation/bottom-navigation/ABottomNavigation.vue +33 -29
- package/components/navigation/footer/NewFooter.vue +3 -2
- package/components/navigation/header/AHeader.vue +32 -39
- package/components/navigation/header/AlmatyContacts.vue +3 -1
- package/components/navigation/header/AstanaContacts.vue +3 -1
- package/composables/modalsState.ts +0 -3
- package/composables/useIdModals.ts +17 -0
- package/eslint.config.mjs +45 -0
- package/lang/ru.ts +139 -107
- package/nuxt.config.ts +15 -7
- package/package.json +7 -5
- package/.eslintrc.cjs +0 -4
- package/.prettierignore +0 -24
- package/.prettierrc +0 -10
- package/components/modals/AuthModal.vue +0 -50
package/.editorconfig
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
root = true
|
|
2
|
-
|
|
3
|
-
[*]
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
end_of_line = lf
|
|
7
|
-
charset = utf-8
|
|
8
|
-
trim_trailing_whitespace = true
|
|
9
|
-
insert_final_newline = true
|
|
10
|
-
|
|
11
|
-
[*.md]
|
|
12
|
-
trim_trailing_whitespace = false
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
indent_style = space
|
|
5
|
+
indent_size = 2
|
|
6
|
+
end_of_line = lf
|
|
7
|
+
charset = utf-8
|
|
8
|
+
trim_trailing_whitespace = true
|
|
9
|
+
insert_final_newline = true
|
|
10
|
+
|
|
11
|
+
[*.md]
|
|
12
|
+
trim_trailing_whitespace = false
|
package/app.config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default defineAppConfig({
|
|
2
2
|
myLayer: {
|
|
3
|
-
name: 'Adata UI v2'
|
|
4
|
-
}
|
|
3
|
+
name: 'Adata UI v2',
|
|
4
|
+
},
|
|
5
5
|
})
|
|
6
6
|
|
|
7
7
|
declare module '@nuxt/schema' {
|
|
@@ -22,6 +22,7 @@ declare module '@nuxt/schema' {
|
|
|
22
22
|
complianceUrl?: string
|
|
23
23
|
counterParty?: string
|
|
24
24
|
langIsOn?: boolean
|
|
25
|
+
authMode?: 'local' | 'separate'
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -13,12 +13,11 @@ const props = defineProps<{
|
|
|
13
13
|
<component
|
|
14
14
|
:is="to ? NuxtLinkLocale : 'button'"
|
|
15
15
|
:to="to"
|
|
16
|
-
class="curve-block relative w-
|
|
16
|
+
class="curve-block relative w-full cursor-pointer"
|
|
17
17
|
active-class="active-item"
|
|
18
18
|
>
|
|
19
19
|
<svg
|
|
20
|
-
|
|
21
|
-
height="182"
|
|
20
|
+
class="w-full"
|
|
22
21
|
viewBox="-1 -1 308 184"
|
|
23
22
|
xmlns="http://www.w3.org/2000/svg"
|
|
24
23
|
>
|
|
@@ -79,9 +78,7 @@ const props = defineProps<{
|
|
|
79
78
|
</div>
|
|
80
79
|
|
|
81
80
|
<svg
|
|
82
|
-
class="absolute bottom-0 right-0"
|
|
83
|
-
width="48"
|
|
84
|
-
height="48"
|
|
81
|
+
class="absolute bottom-0 right-0 size-12 2xl:size-14"
|
|
85
82
|
viewBox="0 0 48 48"
|
|
86
83
|
xmlns="http://www.w3.org/2000/svg"
|
|
87
84
|
>
|
|
@@ -72,7 +72,7 @@ onUnmounted(() => clearTimer())
|
|
|
72
72
|
/>
|
|
73
73
|
</div>
|
|
74
74
|
<p
|
|
75
|
-
class="font-semibold text-xl transition-colors duration-300 ease-in-out"
|
|
75
|
+
class="font-semibold text-base lg:text-xl transition-colors duration-300 ease-in-out"
|
|
76
76
|
:class="selected === idx ? 'dark:text-[#E3E5E8]' : 'text-gray-600 dark:text-gray-200'"
|
|
77
77
|
>
|
|
78
78
|
{{ item.title }}
|
|
@@ -88,7 +88,7 @@ onUnmounted(() => clearTimer())
|
|
|
88
88
|
<div
|
|
89
89
|
v-if="selected === idx"
|
|
90
90
|
>
|
|
91
|
-
<p class="text-sm pt-3">
|
|
91
|
+
<p class="text-xs lg:text-sm pt-3">
|
|
92
92
|
{{ item.description }}
|
|
93
93
|
</p>
|
|
94
94
|
</div>
|
|
@@ -6,6 +6,8 @@ import { onClickOutside, useToggle } from '@vueuse/core'
|
|
|
6
6
|
import { isEqual } from 'lodash-es'
|
|
7
7
|
import type { Ref } from 'vue'
|
|
8
8
|
|
|
9
|
+
import { useFloating, offset, flip, shift, size as floatingSize, autoUpdate } from '@floating-ui/vue'
|
|
10
|
+
|
|
9
11
|
type Props = {
|
|
10
12
|
label: string
|
|
11
13
|
required?: boolean
|
|
@@ -61,6 +63,32 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
61
63
|
|
|
62
64
|
const emits = defineEmits<Emits>()
|
|
63
65
|
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
const reference = ref<HTMLElement | null>(null)
|
|
69
|
+
const floating = ref<HTMLElement | null>(null)
|
|
70
|
+
const { x, y, strategy, update } = useFloating(reference, floating, {
|
|
71
|
+
placement: 'bottom-start',
|
|
72
|
+
strategy: 'absolute',
|
|
73
|
+
middleware: [
|
|
74
|
+
offset(6),
|
|
75
|
+
flip({ fallbackPlacements: ['top-start'] }),
|
|
76
|
+
shift({ padding: 8 }),
|
|
77
|
+
floatingSize({
|
|
78
|
+
apply({ availableHeight, elements, rects }) {
|
|
79
|
+
const el = elements.floating as HTMLElement
|
|
80
|
+
el.style.width = `${rects.reference.width}px`
|
|
81
|
+
el.style.maxHeight = `${Math.max(140, Math.min(availableHeight, 320))}px`
|
|
82
|
+
el.style.overflowY = 'auto'
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
let cleanup: (() => void) | null = null
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
64
92
|
const modelValue = defineModel<T | T[] | null | any>({ required: true })
|
|
65
93
|
const search = defineModel<T | null | any>('search')
|
|
66
94
|
const isOpen = defineModel<T | null | any>('open', {
|
|
@@ -235,10 +263,21 @@ onUpdated(() => (countedVisibleItems.value = countOfVisibleItems.value))
|
|
|
235
263
|
defineExpose({
|
|
236
264
|
onClear
|
|
237
265
|
})
|
|
266
|
+
watch(() => isOpen.value, (open) => {
|
|
267
|
+
if (open && reference.value && floating.value) {
|
|
268
|
+
cleanup = autoUpdate(reference.value, floating.value, update)
|
|
269
|
+
} else {
|
|
270
|
+
cleanup?.()
|
|
271
|
+
cleanup = null
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
onUnmounted(() => cleanup?.())
|
|
238
276
|
</script>
|
|
239
277
|
<template>
|
|
240
278
|
<div ref="wrapper" class="relative w-full rounded-md text-sm">
|
|
241
279
|
<button
|
|
280
|
+
ref="reference"
|
|
242
281
|
:class="[
|
|
243
282
|
clearable ? 'pr-16' : 'pr-9',
|
|
244
283
|
{
|
|
@@ -262,7 +301,7 @@ defineExpose({
|
|
|
262
301
|
</span>
|
|
263
302
|
<component :is="startIcon" v-if="startIcon" />
|
|
264
303
|
|
|
265
|
-
<span :class="{ 'py-1': size === 'md' }" class="w-full text-start">
|
|
304
|
+
<span :class="{ 'py-1': size === 'md' }" class=" w-full text-start">
|
|
266
305
|
<span v-if="valueToShow">
|
|
267
306
|
<span v-if="!multiple" class="line-clamp-1 text-deepblue-900 dark:text-gray-200">
|
|
268
307
|
<slot name="single-selected" :value="valueToShow">{{ valueToShow[keyToShow] }}</slot>
|
|
@@ -310,10 +349,16 @@ defineExpose({
|
|
|
310
349
|
</span>
|
|
311
350
|
</button>
|
|
312
351
|
<transition>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
352
|
+
<div
|
|
353
|
+
v-if="isOpen"
|
|
354
|
+
ref="floating"
|
|
355
|
+
class="z-[10000] rounded-md bg-white shadow-[0_1px_8px_0_rgba(139,146,156,0.3)] dark:bg-gray-900"
|
|
356
|
+
:style="{
|
|
357
|
+
position: strategy,
|
|
358
|
+
left: x != null ? `${x}px` : '',
|
|
359
|
+
top: y != null ? `${y}px` : '',
|
|
360
|
+
}"
|
|
361
|
+
>
|
|
317
362
|
<slot :options="options" name="options">
|
|
318
363
|
<ul class="select m-2 max-h-[250px] overflow-y-auto overflow-x-hidden">
|
|
319
364
|
<li v-if="searchFromOut" class="p-1">
|
|
@@ -64,11 +64,13 @@ defineExpose({
|
|
|
64
64
|
v-model="form.name"
|
|
65
65
|
:label="t('modals.submit_application_modal.labels.name')"
|
|
66
66
|
:error="getError('name')"
|
|
67
|
+
required
|
|
67
68
|
/>
|
|
68
69
|
<a-input-standard
|
|
69
70
|
v-model="form.email"
|
|
70
71
|
:label="t('modals.submit_application_modal.labels.email')"
|
|
71
72
|
:error="getError('email')"
|
|
73
|
+
required
|
|
72
74
|
/>
|
|
73
75
|
<a-input-standard
|
|
74
76
|
v-model="form.phone"
|
|
@@ -83,6 +85,7 @@ defineExpose({
|
|
|
83
85
|
v-model="form.comment"
|
|
84
86
|
:label="t('modals.submit_application_modal.labels.comment')"
|
|
85
87
|
:error="getError('comment')"
|
|
88
|
+
required
|
|
86
89
|
/>
|
|
87
90
|
</div>
|
|
88
91
|
</div>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const emit = defineEmits<{
|
|
3
|
+
(e: 'close'): void
|
|
4
|
+
}>()
|
|
5
|
+
const { t } = useI18n()
|
|
6
|
+
const isOpen = useAuthModal()
|
|
7
|
+
const { myLayer } = useAppConfig()
|
|
8
|
+
const loginUrl = myLayer.loginUrl
|
|
9
|
+
|
|
10
|
+
const { loginModal, registrationModal } = useIdModals()
|
|
11
|
+
|
|
12
|
+
function goAuth() {
|
|
13
|
+
if (myLayer.authMode !== 'local') {
|
|
14
|
+
if (window) {
|
|
15
|
+
let fullPath = encodeURIComponent(window.location.toString())
|
|
16
|
+
if (fullPath.includes('basic-info')) {
|
|
17
|
+
fullPath = fullPath.replace('%2Fcounterparty%2Fmain', '').replace('%2Fbasic-info', '')
|
|
18
|
+
}
|
|
19
|
+
window.location.href = `${loginUrl}?url=${fullPath}`
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
isOpen.value = false
|
|
24
|
+
loginModal.value = true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function goRegister() {
|
|
29
|
+
if (myLayer.authMode !== 'local') {
|
|
30
|
+
if (window) {
|
|
31
|
+
let fullPath = encodeURIComponent(window.location.toString())
|
|
32
|
+
if (fullPath.includes('basic-info')) {
|
|
33
|
+
fullPath = fullPath.replace('%2Fcounterparty%2Fmain', '').replace('%2Fbasic-info', '')
|
|
34
|
+
}
|
|
35
|
+
window.location.href = `${loginUrl}register?url=${fullPath}`
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
isOpen.value = false
|
|
40
|
+
registrationModal.value = true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<a-modal v-model="isOpen" :title="t('modals.auth.title')">
|
|
47
|
+
<div class="flex flex-col items-center gap-5 text-center text-sm">
|
|
48
|
+
<a-ill-door />
|
|
49
|
+
<div>{{ t('modals.auth.content') }}</div>
|
|
50
|
+
<a-button
|
|
51
|
+
block
|
|
52
|
+
variant="success"
|
|
53
|
+
@click="goAuth"
|
|
54
|
+
>
|
|
55
|
+
{{ t('modals.buttons.logIn') }}
|
|
56
|
+
</a-button>
|
|
57
|
+
<div>{{ t('modals.auth.firstTime') }}</div>
|
|
58
|
+
</div>
|
|
59
|
+
<template #footer>
|
|
60
|
+
<div class="flex gap-2">
|
|
61
|
+
<a-button
|
|
62
|
+
view="outline"
|
|
63
|
+
block
|
|
64
|
+
@click="emit('close')"
|
|
65
|
+
>
|
|
66
|
+
{{ t('modals.buttons.close') }}
|
|
67
|
+
</a-button>
|
|
68
|
+
<a-button block @click="goRegister">
|
|
69
|
+
{{ t('modals.buttons.register') }}
|
|
70
|
+
</a-button>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
</a-modal>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<style scoped></style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { emailModal } = useIdModals()
|
|
3
|
+
const { t } = useI18n()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<a-modal v-model="emailModal">
|
|
8
|
+
<div class="flex flex-col items-stretch gap-5 rounded-lg text-center">
|
|
9
|
+
<h2 class="text-center text-2xl font-bold">
|
|
10
|
+
{{ t('modals.id.email.title') }}
|
|
11
|
+
</h2>
|
|
12
|
+
<div class="flex justify-center">
|
|
13
|
+
<span class="flex size-[100px] items-center justify-center rounded-full bg-[#BDC7CE26]">
|
|
14
|
+
<a-ill-info />
|
|
15
|
+
</span>
|
|
16
|
+
</div>
|
|
17
|
+
<p class="text-center text-sm">
|
|
18
|
+
{{ t('modals.id.email.content') }}
|
|
19
|
+
</p>
|
|
20
|
+
<a-button @click="emailModal = false">
|
|
21
|
+
{{ t('actions.close') }}
|
|
22
|
+
</a-button>
|
|
23
|
+
</div>
|
|
24
|
+
</a-modal>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<style scoped>
|
|
28
|
+
|
|
29
|
+
</style>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useToggle } from '@vueuse/shared'
|
|
3
|
-
import * as z from 'zod'
|
|
4
2
|
import AConfirmationEmail from '#adata-ui/components/modals/AConfirmationEmail.vue'
|
|
5
3
|
import Resend from '#adata-ui/components/modals/Resend.vue'
|
|
6
4
|
import TwoFactor from '#adata-ui/components/modals/two-factor/two-factor.vue'
|
|
7
5
|
import { navigateToLocalizedPage } from '#adata-ui/utils/localizedNavigation'
|
|
6
|
+
import { useToggle } from '@vueuse/shared'
|
|
7
|
+
import * as z from 'zod'
|
|
8
8
|
|
|
9
9
|
const { myLayer, commonAuth } = useAppConfig()
|
|
10
10
|
const authApiURL = commonAuth.authApiURL
|
|
@@ -19,8 +19,7 @@ const { t, locale } = useI18n()
|
|
|
19
19
|
const route = useRoute()
|
|
20
20
|
const submitted = ref(false)
|
|
21
21
|
const accessToken = ref(null)
|
|
22
|
-
const loginModal =
|
|
23
|
-
const registrationModal = useRegistrationModal()
|
|
22
|
+
const { loginModal, registrationModal, recoveryModal } = useIdModals()
|
|
24
23
|
|
|
25
24
|
export interface ILoginForm {
|
|
26
25
|
username: string
|
|
@@ -29,7 +28,7 @@ export interface ILoginForm {
|
|
|
29
28
|
|
|
30
29
|
const loginSchema = z.object({
|
|
31
30
|
username: z.string().nonempty(t('register.form.errors.required')).email(t('register.form.errors.email')),
|
|
32
|
-
password: z.string().nonempty(t('register.form.errors.required'))
|
|
31
|
+
password: z.string().nonempty(t('register.form.errors.required')),
|
|
33
32
|
})
|
|
34
33
|
|
|
35
34
|
const validation = computed(() => {
|
|
@@ -39,13 +38,13 @@ const validation = computed(() => {
|
|
|
39
38
|
})
|
|
40
39
|
|
|
41
40
|
function getError(path: string) {
|
|
42
|
-
return validation.value?.issues.find(
|
|
41
|
+
return validation.value?.issues.find(issue => issue.path[0] === path)?.message
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
function savePassAndLogin(form: { username: string
|
|
44
|
+
function savePassAndLogin(form: { username: string, password: string }) {
|
|
46
45
|
const { username, password } = form
|
|
47
46
|
const cookieOptions = {
|
|
48
|
-
expires: rememberMe.value ? new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) : undefined // Срок действия 14 дней
|
|
47
|
+
expires: rememberMe.value ? new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) : undefined, // Срок действия 14 дней
|
|
49
48
|
}
|
|
50
49
|
useCookie('username', cookieOptions).value = username
|
|
51
50
|
useCookie('password', cookieOptions).value = password
|
|
@@ -58,7 +57,7 @@ function clearAuthCookie() {
|
|
|
58
57
|
const loading = ref(false)
|
|
59
58
|
const form: ILoginForm = reactive({
|
|
60
59
|
username: useCookie('username').value || '',
|
|
61
|
-
password: useCookie('password').value || ''
|
|
60
|
+
password: useCookie('password').value || '',
|
|
62
61
|
})
|
|
63
62
|
const rememberMe = ref(false)
|
|
64
63
|
|
|
@@ -71,33 +70,36 @@ async function submit() {
|
|
|
71
70
|
credentials: 'include',
|
|
72
71
|
headers: {
|
|
73
72
|
'Content-Type': 'application/json',
|
|
74
|
-
lang: locale.value
|
|
73
|
+
'lang': locale.value,
|
|
75
74
|
},
|
|
76
75
|
body: JSON.stringify({
|
|
77
76
|
username: form.username.trim().toLowerCase(),
|
|
78
|
-
password: form.password.toString()
|
|
79
|
-
})
|
|
77
|
+
password: form.password.toString(),
|
|
78
|
+
}),
|
|
80
79
|
})
|
|
81
80
|
const { data, message } = await login.json().catch(() => ({}))
|
|
82
81
|
if (data) accessToken.value = data?.access_token
|
|
83
82
|
if (login.status > 202) {
|
|
84
83
|
if (login.status === 422) {
|
|
85
84
|
$toast.error(t('error.validation'))
|
|
86
|
-
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
87
|
$toast.error(message)
|
|
88
88
|
}
|
|
89
|
-
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
90
91
|
rememberMe.value ? savePassAndLogin(form) : clearAuthCookie()
|
|
91
92
|
if (data.is_2fa_enabled) {
|
|
92
93
|
isTwoFactorOpen.value = true
|
|
93
|
-
}
|
|
94
|
+
}
|
|
95
|
+
else if (data.email_is_verified) {
|
|
94
96
|
const response = await fetch(`${authApiURL}/access/cookie`, {
|
|
95
97
|
method: 'GET',
|
|
96
98
|
credentials: 'include',
|
|
97
99
|
headers: {
|
|
98
100
|
Authorization: `Bearer ${accessToken.value}`,
|
|
99
|
-
lang: locale.value
|
|
100
|
-
}
|
|
101
|
+
lang: locale.value,
|
|
102
|
+
},
|
|
101
103
|
})
|
|
102
104
|
const { data: cookiesData } = await response.json()
|
|
103
105
|
if (cookiesData) {
|
|
@@ -108,7 +110,8 @@ async function submit() {
|
|
|
108
110
|
$toast.success(t('login.successfully'))
|
|
109
111
|
loginModal.value = false
|
|
110
112
|
window.location.reload()
|
|
111
|
-
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
112
115
|
isConfirmationEmailModal.value = true
|
|
113
116
|
}
|
|
114
117
|
}
|
|
@@ -140,7 +143,7 @@ function toTariffs() {
|
|
|
140
143
|
locale,
|
|
141
144
|
projectUrl: myLayer.landingUrl,
|
|
142
145
|
path: '/tariffs',
|
|
143
|
-
target: '_self'
|
|
146
|
+
target: '_self',
|
|
144
147
|
})
|
|
145
148
|
}
|
|
146
149
|
|
|
@@ -159,13 +162,13 @@ async function handleConfirmOtp(otpCode: string) {
|
|
|
159
162
|
credentials: 'include',
|
|
160
163
|
headers: {
|
|
161
164
|
'Content-Type': 'application/json',
|
|
162
|
-
lang: locale.value
|
|
165
|
+
'lang': locale.value,
|
|
163
166
|
},
|
|
164
167
|
body: JSON.stringify({
|
|
165
|
-
username: form.username.trim(),
|
|
166
|
-
password: form.password.toString(),
|
|
167
|
-
'2fa_code': otpCode
|
|
168
|
-
})
|
|
168
|
+
'username': form.username.trim(),
|
|
169
|
+
'password': form.password.toString(),
|
|
170
|
+
'2fa_code': otpCode,
|
|
171
|
+
}),
|
|
169
172
|
})
|
|
170
173
|
const { data, message } = await login.json().catch(() => ({}))
|
|
171
174
|
|
|
@@ -174,14 +177,15 @@ async function handleConfirmOtp(otpCode: string) {
|
|
|
174
177
|
showOtpError.value = true
|
|
175
178
|
isLoadingOtp.value = false
|
|
176
179
|
}
|
|
177
|
-
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
178
182
|
const response = await fetch(`${authApiURL}/access/cookie`, {
|
|
179
183
|
method: 'GET',
|
|
180
184
|
credentials: 'include',
|
|
181
185
|
headers: {
|
|
182
186
|
Authorization: `Bearer ${accessToken.value}`,
|
|
183
|
-
lang: locale.value
|
|
184
|
-
}
|
|
187
|
+
lang: locale.value,
|
|
188
|
+
},
|
|
185
189
|
})
|
|
186
190
|
const { data: cookiesData } = await response.json()
|
|
187
191
|
if (cookiesData?.access_token) {
|
|
@@ -199,22 +203,18 @@ async function handleConfirmOtp(otpCode: string) {
|
|
|
199
203
|
|
|
200
204
|
function handleEnter(e: KeyboardEvent) {
|
|
201
205
|
if (
|
|
202
|
-
e.key === 'Enter'
|
|
203
|
-
!isTwoFactorOpen.value
|
|
204
|
-
!isConfirmationEmailModal.value
|
|
205
|
-
!isResendModal.value
|
|
206
|
+
e.key === 'Enter'
|
|
207
|
+
&& !isTwoFactorOpen.value
|
|
208
|
+
&& !isConfirmationEmailModal.value
|
|
209
|
+
&& !isResendModal.value
|
|
206
210
|
) {
|
|
207
211
|
submit()
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
214
|
|
|
211
215
|
function onForgotPassword() {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
projectUrl: myLayer.loginUrl.slice(0, -1),
|
|
215
|
-
path: `/password-recovery`,
|
|
216
|
-
target: '_self',
|
|
217
|
-
})
|
|
216
|
+
loginModal.value = false
|
|
217
|
+
recoveryModal.value = true
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
watch(loginModal, (value) => {
|
|
@@ -236,7 +236,7 @@ onBeforeUnmount(() => {
|
|
|
236
236
|
|
|
237
237
|
<template>
|
|
238
238
|
<a-modal v-model="loginModal">
|
|
239
|
-
<div class="flex flex-col gap-5
|
|
239
|
+
<div class="flex flex-col gap-5">
|
|
240
240
|
<h1 class="heading-02 text-center">
|
|
241
241
|
{{ $t('login.form.title') }}
|
|
242
242
|
</h1>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import IdEmailModal from '#adata-ui/components/modals/id/IdEmailModal.vue'
|
|
3
|
+
import IdLoginModal from '#adata-ui/components/modals/id/IdLoginModal.vue'
|
|
4
|
+
import IdNewPasswordModal from '#adata-ui/components/modals/id/IdNewPasswordModal.vue'
|
|
5
|
+
import IdPasswordSuccessfulModal from '#adata-ui/components/modals/id/IdPasswordSuccessfulModal.vue'
|
|
6
|
+
import IdRecoveryModal from '#adata-ui/components/modals/id/IdRecoveryModal.vue'
|
|
7
|
+
import IdRegistrationModal from '#adata-ui/components/modals/id/IdRegistrationModal.vue'
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<id-login-modal />
|
|
12
|
+
<id-registration-modal />
|
|
13
|
+
<id-recovery-modal />
|
|
14
|
+
<id-email-modal />
|
|
15
|
+
<id-new-password-modal />
|
|
16
|
+
<id-password-successful-modal />
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<style scoped></style>
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import * as z from 'zod'
|
|
3
|
+
|
|
4
|
+
const { $toast } = useNuxtApp()
|
|
5
|
+
const { commonAuth } = useAppConfig()
|
|
6
|
+
const route = useRoute()
|
|
7
|
+
const { t, locale } = useI18n()
|
|
8
|
+
|
|
9
|
+
const { email, token } = route.query
|
|
10
|
+
const authApiURL = commonAuth.authApiURL
|
|
11
|
+
const { loginModal, newPasswordModal, passwordSuccessfulModal } = useIdModals()
|
|
12
|
+
|
|
13
|
+
const form = reactive({
|
|
14
|
+
password: '',
|
|
15
|
+
password_confirmation: '',
|
|
16
|
+
})
|
|
17
|
+
const loading = ref(false)
|
|
18
|
+
|
|
19
|
+
const resetSchema = z.object({
|
|
20
|
+
password: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(8, t('register.form.errors.low_security', { length: 8 }))
|
|
23
|
+
.regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
|
|
24
|
+
password_confirmation: z
|
|
25
|
+
.string()
|
|
26
|
+
.min(8, t('register.form.errors.low_security', { length: 8 }))
|
|
27
|
+
.regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
|
|
28
|
+
}).refine(
|
|
29
|
+
data => data.password === data.password_confirmation,
|
|
30
|
+
{
|
|
31
|
+
message: t('register.form.errors.sameAs'),
|
|
32
|
+
path: ['password_confirmation'],
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const submitted = ref(false)
|
|
37
|
+
|
|
38
|
+
const validation = computed(() => {
|
|
39
|
+
if (!submitted.value) return null
|
|
40
|
+
const result = resetSchema.safeParse(form)
|
|
41
|
+
return result.success ? null : result.error
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
function getError(path: string) {
|
|
45
|
+
return validation.value?.issues?.find(issue => issue.path[0] === path)?.message
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function onSubmit() {
|
|
49
|
+
try {
|
|
50
|
+
submitted.value = true
|
|
51
|
+
if (validation.value) return
|
|
52
|
+
loading.value = true
|
|
53
|
+
|
|
54
|
+
const { data } = await $fetch(`${authApiURL}/password/reset`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
credentials: 'include',
|
|
57
|
+
headers: {
|
|
58
|
+
lang: locale.value,
|
|
59
|
+
},
|
|
60
|
+
body: {
|
|
61
|
+
token,
|
|
62
|
+
email,
|
|
63
|
+
...form,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
if (data?.success) {
|
|
67
|
+
newPasswordModal.value = false
|
|
68
|
+
passwordSuccessfulModal.value = true
|
|
69
|
+
$toast.success(data.message)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
$toast.error(error?.data.message.password as string)
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
loading.value = false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// onMounted(() => {
|
|
81
|
+
// if (!email || !token) {
|
|
82
|
+
// newPasswordModal.value = false
|
|
83
|
+
// loginModal.value = true
|
|
84
|
+
// }
|
|
85
|
+
// })
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<a-modal v-model="newPasswordModal">
|
|
90
|
+
<form
|
|
91
|
+
class="flex flex-col items-stretch gap-5"
|
|
92
|
+
novalidate
|
|
93
|
+
@submit.prevent="onSubmit"
|
|
94
|
+
>
|
|
95
|
+
<h2 class="text-center text-2xl font-bold">
|
|
96
|
+
{{ t('resetPassword.title') }}
|
|
97
|
+
</h2>
|
|
98
|
+
<p class="text-center text-sm">
|
|
99
|
+
{{ t('resetPassword.subtitle') }}
|
|
100
|
+
</p>
|
|
101
|
+
|
|
102
|
+
<a-input-password
|
|
103
|
+
v-model="form.password"
|
|
104
|
+
:label="t('register.form.labels.password')"
|
|
105
|
+
:error="getError('password')"
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<a-alert color="blue">
|
|
109
|
+
{{ t('register.form.alert') }}
|
|
110
|
+
</a-alert>
|
|
111
|
+
|
|
112
|
+
<a-input-password
|
|
113
|
+
v-model="form.password_confirmation"
|
|
114
|
+
:label="t('register.form.labels.password_confirmation')"
|
|
115
|
+
:error="getError('password_confirmation')"
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<a-button
|
|
119
|
+
:loading="loading"
|
|
120
|
+
>
|
|
121
|
+
{{ t('passwordRecovery.form.recover') }}
|
|
122
|
+
</a-button>
|
|
123
|
+
|
|
124
|
+
<p class="text-center text-sm">
|
|
125
|
+
{{ t('passwordRecovery.form.or') }}
|
|
126
|
+
</p>
|
|
127
|
+
|
|
128
|
+
<a-button
|
|
129
|
+
type="button"
|
|
130
|
+
view="outline"
|
|
131
|
+
@click="newPasswordModal = false"
|
|
132
|
+
>
|
|
133
|
+
{{ t('actions.cancel') }}
|
|
134
|
+
</a-button>
|
|
135
|
+
</form>
|
|
136
|
+
</a-modal>
|
|
137
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { passwordSuccessfulModal } = useIdModals()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<a-modal v-model="passwordSuccessfulModal">
|
|
7
|
+
<section class="flex flex-col items-stretch gap-4 text-center">
|
|
8
|
+
<h2 class="text-2xl font-bold">
|
|
9
|
+
{{ $t('resetPassword.successModal.title') }}
|
|
10
|
+
</h2>
|
|
11
|
+
<span class="mx-auto size-[100px] rounded-full bg-[#BDC7CE26]">
|
|
12
|
+
<a-ill-ok />
|
|
13
|
+
</span>
|
|
14
|
+
<p class="text-sm">
|
|
15
|
+
{{ $t('resetPassword.successModal.subtitle') }}
|
|
16
|
+
</p>
|
|
17
|
+
<a-button @click="passwordSuccessfulModal = false">
|
|
18
|
+
{{ $t('resetPassword.successModal.goToAuthorization') }}
|
|
19
|
+
</a-button>
|
|
20
|
+
</section>
|
|
21
|
+
</a-modal>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
|
|
26
|
+
</style>
|