adata-ui 2.1.41-beta.12 → 2.1.41-beta.13
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/forms/input/AInputV2.vue +21 -1
- package/components/forms/input/password/AInputPasswordV2.vue +72 -0
- package/components/forms/password/APasswordRequirements.vue +50 -0
- package/components/modals/id/IdConfirmAccountOtpModal.vue +19 -22
- package/components/modals/id/IdConfirmSuccessfulModal.vue +18 -15
- package/components/modals/id/IdLoginModal.vue +150 -119
- package/components/modals/id/IdModals.vue +66 -24
- package/components/modals/id/IdNewPasswordModal.vue +93 -52
- package/components/modals/id/IdOtpInput.vue +14 -14
- package/components/modals/id/IdPasswordSuccessfulModal.vue +11 -11
- package/components/modals/id/IdRecoveryModal.vue +57 -42
- package/components/modals/id/IdRegistrationModal.vue +136 -94
- package/components/modals/id/IdResetPasswordOtpModal.vue +19 -30
- package/components/modals/id/IdTwoFactorModal.vue +14 -29
- package/icons/google.vue +10 -32
- package/icons/mailru.vue +12 -27
- package/icons/yandex.vue +10 -20
- package/lang/en.ts +30 -4
- package/lang/kk.ts +30 -4
- package/lang/ru.ts +30 -4
- package/package.json +1 -1
- package/utils/apiErrorMessage.ts +22 -0
- package/utils/passwordRules.ts +19 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
|
|
3
3
|
import { writeAccessTokenCookie } from '#adata-ui/composables/useAccessTokenCookie'
|
|
4
|
+
|
|
5
|
+
const emit = defineEmits<{
|
|
6
|
+
(e: 'closed', value: boolean): void
|
|
7
|
+
}>()
|
|
4
8
|
// Каждое из этих окон рендерится только под своим v-if, но статический import
|
|
5
9
|
// всё равно тащил все десять (плюс zod, который они используют для валидации)
|
|
6
10
|
// в стартовый чанк каждого приложения-потребителя. defineAsyncComponent грузит
|
|
@@ -16,10 +20,6 @@ const IdRegistrationModal = defineAsyncComponent(() => import('#adata-ui/compone
|
|
|
16
20
|
const IdResetPasswordOtpModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdResetPasswordOtpModal.vue'))
|
|
17
21
|
const IdTwoFactorModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdTwoFactorModal.vue'))
|
|
18
22
|
|
|
19
|
-
const emit = defineEmits<{
|
|
20
|
-
(e: 'closed', value: boolean): void
|
|
21
|
-
}>()
|
|
22
|
-
|
|
23
23
|
const {
|
|
24
24
|
loginModal,
|
|
25
25
|
registrationModal,
|
|
@@ -30,7 +30,7 @@ const {
|
|
|
30
30
|
passwordSuccessfulModal,
|
|
31
31
|
confirmSuccessfulModal,
|
|
32
32
|
twoFactorModal,
|
|
33
|
-
autoLogoutModal
|
|
33
|
+
autoLogoutModal,
|
|
34
34
|
} = useIdModals()
|
|
35
35
|
|
|
36
36
|
const { locale } = useI18n()
|
|
@@ -86,42 +86,84 @@ watch(isAuthenticated, (val) => {
|
|
|
86
86
|
}
|
|
87
87
|
})
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
function autoLoginClose() {
|
|
90
90
|
emit('closed', true)
|
|
91
91
|
autoLogoutModal.value = false
|
|
92
92
|
}
|
|
93
93
|
</script>
|
|
94
94
|
|
|
95
95
|
<template>
|
|
96
|
-
<a-modal
|
|
96
|
+
<lazy-a-modal-v2
|
|
97
|
+
v-model="loginModal"
|
|
98
|
+
width="424px"
|
|
99
|
+
hide-header
|
|
100
|
+
data-test-id="login-form-modal"
|
|
101
|
+
>
|
|
97
102
|
<id-login-modal v-if="loginModal" />
|
|
98
|
-
</a-modal>
|
|
99
|
-
<a-modal
|
|
103
|
+
</lazy-a-modal-v2>
|
|
104
|
+
<lazy-a-modal-v2
|
|
105
|
+
v-model="twoFactorModal"
|
|
106
|
+
width="424px"
|
|
107
|
+
hide-header
|
|
108
|
+
>
|
|
100
109
|
<id-two-factor-modal v-if="twoFactorModal" />
|
|
101
|
-
</a-modal>
|
|
110
|
+
</lazy-a-modal-v2>
|
|
102
111
|
|
|
103
|
-
<a-modal
|
|
112
|
+
<lazy-a-modal-v2
|
|
113
|
+
v-model="registrationModal"
|
|
114
|
+
width="424px"
|
|
115
|
+
hide-header
|
|
116
|
+
>
|
|
104
117
|
<id-registration-modal v-if="registrationModal" />
|
|
105
|
-
</a-modal>
|
|
106
|
-
<a-modal
|
|
118
|
+
</lazy-a-modal-v2>
|
|
119
|
+
<lazy-a-modal-v2
|
|
120
|
+
v-model="confirmAccountOtpModal"
|
|
121
|
+
width="424px"
|
|
122
|
+
hide-header
|
|
123
|
+
persistent
|
|
124
|
+
>
|
|
107
125
|
<id-confirm-account-otp-modal v-if="confirmAccountOtpModal" />
|
|
108
|
-
</a-modal>
|
|
109
|
-
<a-modal
|
|
126
|
+
</lazy-a-modal-v2>
|
|
127
|
+
<lazy-a-modal-v2
|
|
128
|
+
v-model="confirmSuccessfulModal"
|
|
129
|
+
width="424px"
|
|
130
|
+
hide-header
|
|
131
|
+
persistent
|
|
132
|
+
>
|
|
110
133
|
<id-confirm-successful-modal v-if="confirmSuccessfulModal" />
|
|
111
|
-
</a-modal>
|
|
134
|
+
</lazy-a-modal-v2>
|
|
112
135
|
|
|
113
|
-
<a-modal
|
|
136
|
+
<lazy-a-modal-v2
|
|
137
|
+
v-model="recoveryModal"
|
|
138
|
+
width="424px"
|
|
139
|
+
hide-header
|
|
140
|
+
>
|
|
114
141
|
<id-recovery-modal v-if="recoveryModal" />
|
|
115
|
-
</a-modal>
|
|
116
|
-
<a-modal
|
|
142
|
+
</lazy-a-modal-v2>
|
|
143
|
+
<lazy-a-modal-v2
|
|
144
|
+
v-model="resetPasswordOtpModal"
|
|
145
|
+
width="424px"
|
|
146
|
+
hide-header
|
|
147
|
+
persistent
|
|
148
|
+
>
|
|
117
149
|
<id-reset-password-otp-modal v-if="resetPasswordOtpModal" />
|
|
118
|
-
</a-modal>
|
|
119
|
-
<a-modal
|
|
150
|
+
</lazy-a-modal-v2>
|
|
151
|
+
<lazy-a-modal-v2
|
|
152
|
+
v-model="newPasswordModal"
|
|
153
|
+
width="424px"
|
|
154
|
+
hide-header
|
|
155
|
+
persistent
|
|
156
|
+
>
|
|
120
157
|
<id-new-password-modal v-if="newPasswordModal" />
|
|
121
|
-
</a-modal>
|
|
122
|
-
<a-modal
|
|
158
|
+
</lazy-a-modal-v2>
|
|
159
|
+
<lazy-a-modal-v2
|
|
160
|
+
v-model="passwordSuccessfulModal"
|
|
161
|
+
width="424px"
|
|
162
|
+
hide-header
|
|
163
|
+
persistent
|
|
164
|
+
>
|
|
123
165
|
<id-password-successful-modal v-if="passwordSuccessfulModal" />
|
|
124
|
-
</a-modal>
|
|
166
|
+
</lazy-a-modal-v2>
|
|
125
167
|
|
|
126
168
|
<id-auto-logout-modal v-if="autoLogoutModal" @closed="autoLoginClose" />
|
|
127
169
|
</template>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
|
|
3
|
+
import AIconLock from '#adata-ui/icons/lock.vue'
|
|
4
|
+
import { apiErrorMessage } from '#adata-ui/utils/apiErrorMessage'
|
|
5
|
+
import { PASSWORD_MIN_LENGTH, PASSWORD_RULES } from '#adata-ui/utils/passwordRules'
|
|
3
6
|
import * as z from 'zod'
|
|
4
7
|
|
|
5
8
|
const { $toast } = useNuxtApp()
|
|
@@ -15,17 +18,21 @@ const form = reactive({
|
|
|
15
18
|
})
|
|
16
19
|
const loading = ref(false)
|
|
17
20
|
|
|
21
|
+
/* Правила берутся из PASSWORD_RULES — того же источника, что и чеклист
|
|
22
|
+
<a-password-requirements>, поэтому валидация и подсказки не расходятся. */
|
|
23
|
+
function passwordField() {
|
|
24
|
+
return PASSWORD_RULES.reduce(
|
|
25
|
+
(schema: z.ZodType<string>, rule) => schema.refine(
|
|
26
|
+
rule.test,
|
|
27
|
+
t(`modals.id.register.errors.${rule.key}`, { length: PASSWORD_MIN_LENGTH }),
|
|
28
|
+
),
|
|
29
|
+
z.string().nonempty(t('error.required')) as z.ZodType<string>,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
const resetSchema = z.object({
|
|
19
|
-
password:
|
|
20
|
-
|
|
21
|
-
.nonempty(t('error.required'))
|
|
22
|
-
.min(8, t('modals.id.register.errors.low_security', { length: 8 }))
|
|
23
|
-
.regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
|
|
24
|
-
password_confirmation: z
|
|
25
|
-
.string()
|
|
26
|
-
.nonempty(t('error.required'))
|
|
27
|
-
.min(8, t('modals.id.register.errors.low_security', { length: 8 }))
|
|
28
|
-
.regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
|
|
34
|
+
password: passwordField(),
|
|
35
|
+
password_confirmation: passwordField(),
|
|
29
36
|
}).refine(
|
|
30
37
|
data => data.password === data.password_confirmation,
|
|
31
38
|
{
|
|
@@ -36,6 +43,10 @@ const resetSchema = z.object({
|
|
|
36
43
|
|
|
37
44
|
const submitted = ref(false)
|
|
38
45
|
|
|
46
|
+
const uid = useId()
|
|
47
|
+
const passwordFieldId = `new-password-${uid}`
|
|
48
|
+
const confirmationFieldId = `new-password-confirmation-${uid}`
|
|
49
|
+
|
|
39
50
|
const validation = computed(() => {
|
|
40
51
|
if (!submitted.value) return null
|
|
41
52
|
const result = resetSchema.safeParse(form)
|
|
@@ -71,7 +82,7 @@ async function onSubmit() {
|
|
|
71
82
|
}
|
|
72
83
|
}
|
|
73
84
|
catch (error) {
|
|
74
|
-
$toast.error(error
|
|
85
|
+
$toast.error(apiErrorMessage(error, t('error.validation')))
|
|
75
86
|
}
|
|
76
87
|
finally {
|
|
77
88
|
loading.value = false
|
|
@@ -81,49 +92,79 @@ async function onSubmit() {
|
|
|
81
92
|
|
|
82
93
|
<template>
|
|
83
94
|
<form
|
|
84
|
-
class="flex flex-col
|
|
95
|
+
class="flex flex-col gap-6 p-6"
|
|
85
96
|
novalidate
|
|
86
97
|
@submit.prevent="onSubmit"
|
|
87
98
|
>
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
99
|
+
<div class="flex flex-col gap-1">
|
|
100
|
+
<h2 class="text-center text-h2 text-primary">
|
|
101
|
+
{{ t('modals.id.newPassword.title') }}
|
|
102
|
+
</h2>
|
|
103
|
+
<p class="text-center text-body-md text-secondary">
|
|
104
|
+
{{ t('modals.id.newPassword.content') }}
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div class="flex flex-col gap-4">
|
|
109
|
+
<div class="flex flex-col gap-1.5">
|
|
110
|
+
<label :for="passwordFieldId" class="text-body-md-strong text-primary">
|
|
111
|
+
{{ t('modals.id.newPassword.labels.password') }}
|
|
112
|
+
</label>
|
|
113
|
+
<a-input-password-v2
|
|
114
|
+
v-model="form.password"
|
|
115
|
+
:input-id="passwordFieldId"
|
|
116
|
+
size="lg"
|
|
117
|
+
autocomplete="new-password"
|
|
118
|
+
:start-icon="AIconLock"
|
|
119
|
+
:placeholder="t('modals.id.newPassword.placeholder.1')"
|
|
120
|
+
:error="getError('password')"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<a-password-requirements :password="form.password" />
|
|
125
|
+
|
|
126
|
+
<div class="flex flex-col gap-1.5">
|
|
127
|
+
<label :for="confirmationFieldId" class="text-body-md-strong text-primary">
|
|
128
|
+
{{ t('modals.id.newPassword.labels.password_confirmation') }}
|
|
129
|
+
</label>
|
|
130
|
+
<a-input-password-v2
|
|
131
|
+
v-model="form.password_confirmation"
|
|
132
|
+
:input-id="confirmationFieldId"
|
|
133
|
+
size="lg"
|
|
134
|
+
autocomplete="new-password"
|
|
135
|
+
:start-icon="AIconLock"
|
|
136
|
+
:placeholder="t('modals.id.newPassword.placeholder.2')"
|
|
137
|
+
:error="getError('password_confirmation')"
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<a-button-v2
|
|
142
|
+
type="submit"
|
|
143
|
+
size="lg"
|
|
144
|
+
block
|
|
145
|
+
:loading="loading"
|
|
146
|
+
>
|
|
147
|
+
{{ t('actions.set') }}
|
|
148
|
+
</a-button-v2>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div class="flex flex-col gap-4">
|
|
152
|
+
<div class="flex items-center gap-3">
|
|
153
|
+
<span class="h-px flex-1 bg-(--border-color-default)" />
|
|
154
|
+
<span class="shrink-0 text-body-sm text-secondary">
|
|
155
|
+
{{ t('reuse.or') }}
|
|
156
|
+
</span>
|
|
157
|
+
<span class="h-px flex-1 bg-(--border-color-default)" />
|
|
158
|
+
</div>
|
|
159
|
+
<a-button-v2
|
|
160
|
+
view="outline"
|
|
161
|
+
color="gray"
|
|
162
|
+
size="lg"
|
|
163
|
+
block
|
|
164
|
+
@click="newPasswordModal = false"
|
|
165
|
+
>
|
|
166
|
+
{{ t('actions.cancel') }}
|
|
167
|
+
</a-button-v2>
|
|
168
|
+
</div>
|
|
128
169
|
</form>
|
|
129
170
|
</template>
|
|
@@ -4,9 +4,9 @@ const emit = defineEmits<{
|
|
|
4
4
|
}>()
|
|
5
5
|
|
|
6
6
|
const length = 6 // Длина OTP
|
|
7
|
-
const showError = defineModel('error')
|
|
8
|
-
const otp = defineModel()
|
|
9
|
-
const otpRefs = ref([])
|
|
7
|
+
const showError = defineModel<boolean>('error', { default: false })
|
|
8
|
+
const otp = defineModel<string[]>({ required: true })
|
|
9
|
+
const otpRefs = ref<(HTMLInputElement | null)[]>([])
|
|
10
10
|
const { t } = useI18n()
|
|
11
11
|
|
|
12
12
|
function checkCompletion() {
|
|
@@ -15,8 +15,8 @@ function checkCompletion() {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function onInput(event, index) {
|
|
19
|
-
const value = event.target.value
|
|
18
|
+
function onInput(event: Event, index: number) {
|
|
19
|
+
const value = (event.target as HTMLInputElement).value
|
|
20
20
|
showError.value = false
|
|
21
21
|
|
|
22
22
|
// Разрешить только цифры
|
|
@@ -35,7 +35,7 @@ function onInput(event, index) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function onBackspace(index, event) {
|
|
38
|
+
function onBackspace(index: number, event: KeyboardEvent) {
|
|
39
39
|
showError.value = false
|
|
40
40
|
if (otp.value[index] || index > 0) {
|
|
41
41
|
if (otp.value[index] !== '') {
|
|
@@ -55,9 +55,9 @@ function onBackspace(index, event) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function onPaste(event) {
|
|
58
|
+
function onPaste(event: ClipboardEvent) {
|
|
59
59
|
showError.value = false
|
|
60
|
-
const pastedData = event.clipboardData
|
|
60
|
+
const pastedData = (event.clipboardData?.getData('text') ?? '').slice(0, length)
|
|
61
61
|
|
|
62
62
|
if (/\D/.test(pastedData)) {
|
|
63
63
|
event.preventDefault()
|
|
@@ -95,9 +95,9 @@ function autoTarget() {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// макс сайз = 1 и если в ячейке есть цифра, она перезаписывается
|
|
98
|
-
function inputValid(index: number, event:
|
|
98
|
+
function inputValid(index: number, event: Event) {
|
|
99
99
|
if (otp.value[index]) {
|
|
100
|
-
event.target.value = ''
|
|
100
|
+
(event.target as HTMLInputElement).value = ''
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -121,15 +121,15 @@ function moveFocus(index: number, to: string) {
|
|
|
121
121
|
<input
|
|
122
122
|
v-for="(digit, index) in otp"
|
|
123
123
|
:key="index"
|
|
124
|
-
:ref="el => otpRefs[index] = el"
|
|
124
|
+
:ref="el => otpRefs[index] = el as HTMLInputElement"
|
|
125
125
|
v-model="otp[index]"
|
|
126
126
|
type="text"
|
|
127
127
|
inputmode="numeric"
|
|
128
128
|
maxlength="1"
|
|
129
|
-
class="size-10 rounded bg-muted text-center text-lg caret-transparent focus:outline-hidden"
|
|
129
|
+
class="size-10 rounded-10 border border-transparent bg-muted text-center text-lg text-primary caret-transparent focus:outline-hidden"
|
|
130
130
|
:class="{
|
|
131
|
-
'border
|
|
132
|
-
'focus:border
|
|
131
|
+
'border-danger bg-danger-subtle': showError,
|
|
132
|
+
'focus:border-focus': !showError,
|
|
133
133
|
}"
|
|
134
134
|
@input="onInput($event, index)"
|
|
135
135
|
@keydown.backspace="onBackspace(index, $event)"
|
|
@@ -4,22 +4,22 @@ const { t } = useI18n()
|
|
|
4
4
|
</script>
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
|
-
<div class="flex flex-col gap-4 text-center">
|
|
8
|
-
<h2 class="text-h2">
|
|
7
|
+
<div class="flex flex-col gap-4 p-6 text-center">
|
|
8
|
+
<h2 class="text-h2 text-primary">
|
|
9
9
|
{{ t('modals.id.passwordSuccessful.title') }}
|
|
10
10
|
</h2>
|
|
11
|
-
<
|
|
11
|
+
<div class="mx-auto flex size-24 items-center justify-center rounded-full bg-muted">
|
|
12
12
|
<a-ill-ok />
|
|
13
|
-
</
|
|
14
|
-
<p class="text-body-md">
|
|
13
|
+
</div>
|
|
14
|
+
<p class="text-body-md text-secondary">
|
|
15
15
|
{{ t('modals.id.passwordSuccessful.content') }}
|
|
16
16
|
</p>
|
|
17
|
-
<a-button
|
|
17
|
+
<a-button-v2
|
|
18
|
+
size="lg"
|
|
19
|
+
block
|
|
20
|
+
@click="passwordSuccessfulModal = false"
|
|
21
|
+
>
|
|
18
22
|
{{ t('actions.close') }}
|
|
19
|
-
</a-button>
|
|
23
|
+
</a-button-v2>
|
|
20
24
|
</div>
|
|
21
25
|
</template>
|
|
22
|
-
|
|
23
|
-
<style scoped>
|
|
24
|
-
|
|
25
|
-
</style>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
|
|
3
|
+
import AIconMail from '#adata-ui/icons/mail.vue'
|
|
4
|
+
import { apiErrorMessage } from '#adata-ui/utils/apiErrorMessage'
|
|
3
5
|
import * as z from 'zod'
|
|
4
6
|
|
|
5
7
|
const { $toast } = useNuxtApp()
|
|
@@ -12,6 +14,8 @@ const emailField = ref('')
|
|
|
12
14
|
const loading = ref(false)
|
|
13
15
|
const submitted = ref(false)
|
|
14
16
|
|
|
17
|
+
const emailFieldId = `recovery-email-${useId()}`
|
|
18
|
+
|
|
15
19
|
const emailSchema = z.object({
|
|
16
20
|
emailField: z.string().nonempty(t('error.required')).email(t('error.email')),
|
|
17
21
|
})
|
|
@@ -48,19 +52,13 @@ async function onSubmit() {
|
|
|
48
52
|
resetPasswordOtpModal.value = true
|
|
49
53
|
}
|
|
50
54
|
catch (error) {
|
|
51
|
-
$toast.error(error.
|
|
55
|
+
$toast.error(apiErrorMessage(error, t('error.validation')))
|
|
52
56
|
}
|
|
53
57
|
finally {
|
|
54
58
|
loading.value = false
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
function handleEnter(e: KeyboardEvent) {
|
|
59
|
-
if (e.key === 'Enter') {
|
|
60
|
-
onSubmit()
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
62
|
function onCancel() {
|
|
65
63
|
recoveryModal.value = false
|
|
66
64
|
loginModal.value = true
|
|
@@ -71,47 +69,64 @@ watch(recoveryModal, (value) => {
|
|
|
71
69
|
emailField.value = ''
|
|
72
70
|
}
|
|
73
71
|
})
|
|
74
|
-
|
|
75
|
-
onMounted(() => {
|
|
76
|
-
document.addEventListener('keyup', handleEnter)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
onBeforeUnmount(() => {
|
|
80
|
-
document.removeEventListener('keyup', handleEnter)
|
|
81
|
-
})
|
|
82
72
|
</script>
|
|
83
73
|
|
|
84
74
|
<template>
|
|
85
75
|
<form
|
|
86
|
-
class="flex flex-col
|
|
76
|
+
class="flex flex-col gap-6 p-6"
|
|
87
77
|
novalidate
|
|
88
78
|
@submit.prevent="onSubmit"
|
|
89
79
|
>
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
</
|
|
80
|
+
<div class="flex flex-col gap-1">
|
|
81
|
+
<h2 class="text-center text-h2 text-primary">
|
|
82
|
+
{{ t('modals.id.recovery.title') }}
|
|
83
|
+
</h2>
|
|
84
|
+
<p class="text-center text-body-md text-secondary">
|
|
85
|
+
{{ t('modals.id.recovery.content') }}
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="flex flex-col gap-1.5">
|
|
90
|
+
<label :for="emailFieldId" class="text-body-md-strong text-primary">
|
|
91
|
+
{{ t('modals.id.recovery.label') }}
|
|
92
|
+
</label>
|
|
93
|
+
<a-input-v2
|
|
94
|
+
v-model="emailField"
|
|
95
|
+
:input-id="emailFieldId"
|
|
96
|
+
type="email"
|
|
97
|
+
size="lg"
|
|
98
|
+
autocomplete="email"
|
|
99
|
+
:start-icon="AIconMail"
|
|
100
|
+
:placeholder="t('modals.id.recovery.placeholder')"
|
|
101
|
+
:error="getError('emailField')"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="flex flex-col gap-4">
|
|
106
|
+
<a-button-v2
|
|
107
|
+
type="submit"
|
|
108
|
+
size="lg"
|
|
109
|
+
block
|
|
110
|
+
:loading="loading"
|
|
111
|
+
>
|
|
112
|
+
{{ t('actions.recover') }}
|
|
113
|
+
</a-button-v2>
|
|
114
|
+
<div class="flex items-center gap-3">
|
|
115
|
+
<span class="h-px flex-1 bg-(--border-color-default)" />
|
|
116
|
+
<span class="shrink-0 text-body-sm text-secondary">
|
|
117
|
+
{{ t('reuse.or') }}
|
|
118
|
+
</span>
|
|
119
|
+
<span class="h-px flex-1 bg-(--border-color-default)" />
|
|
120
|
+
</div>
|
|
121
|
+
<a-button-v2
|
|
122
|
+
view="outline"
|
|
123
|
+
color="gray"
|
|
124
|
+
size="lg"
|
|
125
|
+
block
|
|
126
|
+
@click="onCancel"
|
|
127
|
+
>
|
|
128
|
+
{{ t('actions.cancel') }}
|
|
129
|
+
</a-button-v2>
|
|
130
|
+
</div>
|
|
114
131
|
</form>
|
|
115
132
|
</template>
|
|
116
|
-
|
|
117
|
-
<style scoped></style>
|