adata-ui 2.0.22 → 2.0.23
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/feature-description/AFeatureDescription.vue +2 -2
- 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/header/AHeader.vue +32 -39
- 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 +6 -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
|
}
|
|
@@ -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>
|
|
@@ -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>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import * as z from 'zod'
|
|
3
|
+
|
|
4
|
+
const { $toast } = useNuxtApp()
|
|
5
|
+
const { commonAuth } = useAppConfig()
|
|
6
|
+
const { t, locale } = useI18n()
|
|
7
|
+
|
|
8
|
+
const { recoveryModal, emailModal } = useIdModals()
|
|
9
|
+
const authApiURL = commonAuth.authApiURL
|
|
10
|
+
const emailField = ref('')
|
|
11
|
+
const loading = ref(false)
|
|
12
|
+
const submitted = ref(false)
|
|
13
|
+
|
|
14
|
+
const emailSchema = z.object({
|
|
15
|
+
emailField: z.string().nonempty(t('error.required')).email(t('error.email')),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const validation = computed(() => {
|
|
19
|
+
if (!submitted.value) return null
|
|
20
|
+
const result = emailSchema.safeParse({ emailField: emailField.value })
|
|
21
|
+
return result.success ? null : result.error
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
function getError(path: string) {
|
|
25
|
+
return validation.value?.issues.find(issue => issue.path[0] === path)?.message
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function onSubmit() {
|
|
29
|
+
try {
|
|
30
|
+
submitted.value = true
|
|
31
|
+
if (validation.value) return
|
|
32
|
+
loading.value = true
|
|
33
|
+
|
|
34
|
+
await $fetch(`${authApiURL}/password/email`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
credentials: 'include',
|
|
37
|
+
headers: {
|
|
38
|
+
lang: locale.value,
|
|
39
|
+
},
|
|
40
|
+
body: {
|
|
41
|
+
email: emailField.value,
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
recoveryModal.value = false
|
|
45
|
+
emailModal.value = true
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
$toast.error(error.data.message)
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
loading.value = false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function handleEnter(e: KeyboardEvent) {
|
|
55
|
+
if (e.key === 'Enter') {
|
|
56
|
+
onSubmit()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function onCancel() {
|
|
61
|
+
recoveryModal.value = false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
watch(recoveryModal, (value) => {
|
|
65
|
+
if (!value) {
|
|
66
|
+
emailField.value = ''
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
onMounted(() => {
|
|
71
|
+
document.addEventListener('keyup', handleEnter)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
onBeforeUnmount(() => {
|
|
75
|
+
document.removeEventListener('keyup', handleEnter)
|
|
76
|
+
})
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<a-modal v-model="recoveryModal">
|
|
81
|
+
<form
|
|
82
|
+
class="flex flex-col items-stretch gap-5 rounded-lg text-center"
|
|
83
|
+
novalidate
|
|
84
|
+
@submit.prevent="onSubmit"
|
|
85
|
+
>
|
|
86
|
+
<h2 class="text-center text-2xl font-bold">
|
|
87
|
+
{{ t('modals.id.recovery.title') }}
|
|
88
|
+
</h2>
|
|
89
|
+
<p class="text-center text-sm">
|
|
90
|
+
{{ t('modals.id.recovery.content') }}
|
|
91
|
+
</p>
|
|
92
|
+
<a-input-standard
|
|
93
|
+
v-model="emailField"
|
|
94
|
+
:label="t('modals.id.recovery.placeholder')"
|
|
95
|
+
:error="getError('emailField')"
|
|
96
|
+
/>
|
|
97
|
+
<a-button :loading="loading">
|
|
98
|
+
{{ t('actions.recover') }}
|
|
99
|
+
</a-button>
|
|
100
|
+
<p class="text-center text-sm">
|
|
101
|
+
{{ t('reuse.or') }}
|
|
102
|
+
</p>
|
|
103
|
+
<a-button
|
|
104
|
+
type="button"
|
|
105
|
+
view="outline"
|
|
106
|
+
@click="onCancel"
|
|
107
|
+
>
|
|
108
|
+
{{ t('actions.cancel') }}
|
|
109
|
+
</a-button>
|
|
110
|
+
</form>
|
|
111
|
+
</a-modal>
|
|
112
|
+
</template>
|
|
113
|
+
|
|
114
|
+
<style scoped></style>
|