adata-ui 2.0.27 → 2.0.29

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.
@@ -4,8 +4,8 @@ import * as z from 'zod'
4
4
  import { useUIValidation } from '#adata-ui/composables/useUIValidation'
5
5
 
6
6
  const props = defineProps<{
7
- name: string
8
- email: string
7
+ name?: string
8
+ email?: string
9
9
  }>()
10
10
 
11
11
  const emit = defineEmits<{
@@ -16,7 +16,7 @@ const { t } = useI18n()
16
16
 
17
17
  const mask = new Mask({ mask: '# (###) ### ## ##' })
18
18
 
19
- const isOpen = defineModel<boolean>('isOpen', { required: true })
19
+ const isOpen = defineModel<boolean>()
20
20
  const form: any = reactive({
21
21
  name: '',
22
22
  email: '',
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import OtpInput from '#adata-ui/components/modals/two-factor/otp-input.vue'
2
+ import OtpInput from '#adata-ui/components/modals/id/otp-input.vue'
3
3
  import { useAuthStore } from '#adata-ui/stores/auth.store'
4
4
 
5
5
  const { $toast } = useNuxtApp()
@@ -17,6 +17,7 @@ const otp = ref(['', '', '', '', '', ''])
17
17
  const otpFormatted = computed(() => {
18
18
  return otp.value.join('')
19
19
  })
20
+ const attempts = ref(5)
20
21
  const showError = ref(false)
21
22
  const isLoading = ref(false)
22
23
 
@@ -28,7 +29,7 @@ async function onResend() {
28
29
  headers: {
29
30
  lang: locale.value,
30
31
  },
31
- body: {
32
+ params: {
32
33
  email: intermediateState.value.email,
33
34
  },
34
35
  })
@@ -59,6 +60,12 @@ async function onConfirm() {
59
60
  confirmSuccessfulModal.value = true
60
61
  }
61
62
  catch (error) {
63
+ if (error.data?.retry_after) {
64
+ attempts.value = 0
65
+ }
66
+ else {
67
+ attempts.value--
68
+ }
62
69
  showError.value = true
63
70
  $toast.error(error.data.message)
64
71
  }
@@ -70,12 +77,14 @@ async function onConfirm() {
70
77
  function onClose() {
71
78
  otp.value = ['', '', '', '', '', '']
72
79
  showError.value = false
80
+ attempts.value = 5
73
81
  confirmAccountOtpModal.value = false
74
82
  }
75
83
 
76
84
  const timer = ref(60)
77
85
 
78
86
  function runTimer() {
87
+ timer.value = 60
79
88
  const intervalId = setInterval(() => {
80
89
  if (!timer.value) clearInterval(intervalId)
81
90
  return timer.value--
@@ -101,7 +110,7 @@ onBeforeUnmount(() => {
101
110
  <template>
102
111
  <div class="flex flex-col items-center gap-4 text-center">
103
112
  <h2 class="text-2xl font-bold">
104
- {{ t('register.modal.title') }}
113
+ {{ t('modals.id.confirmAccount.title') }}
105
114
  </h2>
106
115
 
107
116
  <a-icon-hand-with-phone-light class="size-32 dark:hidden" />
@@ -113,7 +122,7 @@ onBeforeUnmount(() => {
113
122
  </p>
114
123
 
115
124
  <div v-if="timer > 0" class="text-2xl font-bold">
116
- {{ timer }} {{ t('register.modal.seconds') }}
125
+ {{ timer }} {{ t('modals.id.resetPasswordOtp.seconds') }}
117
126
  </div>
118
127
  <button
119
128
  v-else
@@ -124,7 +133,11 @@ onBeforeUnmount(() => {
124
133
  </button>
125
134
  </div>
126
135
 
127
- <otp-input v-model="otp" v-model:error="showError" />
136
+ <otp-input
137
+ v-model="otp"
138
+ v-model:error="showError"
139
+ :attempts="attempts"
140
+ />
128
141
 
129
142
  <div class="flex w-full gap-2">
130
143
  <a-button
@@ -137,7 +150,7 @@ onBeforeUnmount(() => {
137
150
  <a-button
138
151
  block
139
152
  :loading="isLoading"
140
- :disabled="otpFormatted.length < 6"
153
+ :disabled="otpFormatted.length < 6 || attempts === 0"
141
154
  @click="onConfirm"
142
155
  >
143
156
  {{ t('actions.confirm') }}
@@ -1,34 +1,30 @@
1
1
  <script setup lang="ts">
2
- import TwoFactor from '#adata-ui/components/modals/two-factor/two-factor.vue'
3
2
  import { useAuthStore } from '#adata-ui/stores/auth.store'
4
3
  import { navigateToLocalizedPage } from '#adata-ui/utils/localizedNavigation'
5
- import { useToggle } from '@vueuse/shared'
6
4
  import * as z from 'zod'
7
5
 
6
+ const { $toast } = useNuxtApp()
8
7
  const { myLayer, commonAuth } = useAppConfig()
9
- const authApiURL = commonAuth.authApiURL
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 showOtpError = ref(false)
16
8
  const { t, locale } = useI18n()
17
- const submitted = ref(false)
18
- const accessToken = ref(null)
19
- const { loginModal, registrationModal, recoveryModal, confirmAccountOtpModal } = useIdModals()
20
9
 
21
10
  const authStore = useAuthStore()
22
11
  const { intermediateState } = storeToRefs(authStore)
23
12
 
13
+ const { loginModal, registrationModal, recoveryModal, confirmAccountOtpModal, twoFactorModal } = useIdModals()
14
+
15
+ const authApiURL = commonAuth.authApiURL
16
+
17
+ const submitted = ref(false)
18
+ const accessToken = ref(null)
19
+
24
20
  export interface ILoginForm {
25
21
  username: string
26
22
  password: string
27
23
  }
28
24
 
29
25
  const loginSchema = z.object({
30
- username: z.string().nonempty(t('register.form.errors.required')).email(t('register.form.errors.email')),
31
- password: z.string().nonempty(t('register.form.errors.required')),
26
+ username: z.string().nonempty(t('error.required')).email(t('error.email')),
27
+ password: z.string().nonempty(t('error.required')),
32
28
  })
33
29
 
34
30
  const validation = computed(() => {
@@ -78,34 +74,38 @@ async function submit() {
78
74
  }),
79
75
  })
80
76
  const { data, message } = await login.json().catch(() => ({}))
77
+
78
+ intermediateState.value.email = form.username
79
+ intermediateState.value.password = form.password
80
+
81
81
  if (data) accessToken.value = data?.access_token
82
82
  if (login.status > 202) {
83
83
  if (login.status === 422) {
84
84
  $toast.error(t('error.validation'))
85
85
  }
86
- else {
87
- intermediateState.value.email = form.username
88
- intermediateState.value.password = form.password
86
+ if (login.status === 206) {
87
+ confirmAccountOtpModal.value = true
89
88
 
90
- await fetch(`${authApiURL}/email/resend-otp`, {
89
+ $fetch(`${authApiURL}/email/resend-otp`, {
91
90
  method: 'GET',
92
91
  credentials: 'include',
93
92
  headers: {
94
- 'Content-Type': 'application/json',
95
- 'lang': locale.value,
93
+ lang: locale.value,
96
94
  },
97
- body: JSON.stringify({
95
+ params: {
98
96
  email: intermediateState.value.email,
99
- }),
97
+ },
100
98
  }).catch(() => {})
101
-
102
- confirmAccountOtpModal.value = true
99
+ }
100
+ else {
101
+ $toast.error(message)
103
102
  }
104
103
  }
105
104
  else {
106
105
  rememberMe.value ? savePassAndLogin(form) : clearAuthCookie()
107
106
  if (data.is_2fa_enabled) {
108
- isTwoFactorOpen.value = true
107
+ loginModal.value = false
108
+ twoFactorModal.value = true
109
109
  }
110
110
  else if (data.email_is_verified) {
111
111
  const response = await fetch(`${authApiURL}/access/cookie`, {
@@ -120,15 +120,19 @@ async function submit() {
120
120
  if (cookiesData) {
121
121
  const { access_token, expire_in } = cookiesData
122
122
  const hostname = location.hostname.split('.').reverse()
123
- document.cookie = `accessToken=${access_token}; max-age=${expire_in}; domain=.${hostname[1]}.${hostname[0]}; path=/`
123
+
124
+ useCookie('accessToken', {
125
+ maxAge: expire_in,
126
+ domain: `.${hostname[1]}.${hostname[0]}`,
127
+ path: '/',
128
+ secure: true,
129
+ }).value = access_token
124
130
  }
125
131
  $toast.success(t('login.successfully'))
126
132
  loginModal.value = false
127
133
  window.location.reload()
128
134
  }
129
135
  else {
130
- intermediateState.value.email = form.username
131
- intermediateState.value.password = form.password
132
136
  confirmAccountOtpModal.value = true
133
137
 
134
138
  $fetch(`${authApiURL}/email/resend-otp`, {
@@ -137,7 +141,7 @@ async function submit() {
137
141
  headers: {
138
142
  lang: locale.value,
139
143
  },
140
- body: {
144
+ params: {
141
145
  email: intermediateState.value.email,
142
146
  },
143
147
  }).catch(() => {})
@@ -154,13 +158,6 @@ function authWithSocial(social: string) {
154
158
  document.location.replace(`https://auth.${mode}.kz/api/login/social?source=${social}`)
155
159
  }
156
160
 
157
- async function onResend() {
158
- const { data, success, message } = await $api.auth.emailResend()
159
- if (success) {
160
- $toast.success(message)
161
- }
162
- }
163
-
164
161
  function onRegister() {
165
162
  loginModal.value = false
166
163
  registrationModal.value = true
@@ -179,64 +176,8 @@ onMounted(() => {
179
176
  rememberMe.value = !!useCookie('username').value && !!useCookie('password').value
180
177
  })
181
178
 
182
- function confirmationEmailResend() {
183
- isConfirmationEmailModal.value = false
184
- toggleResendModal()
185
- }
186
- async function handleConfirmOtp(otpCode: string) {
187
- isLoadingOtp.value = true
188
- const login = await fetch(`${authApiURL}/login`, {
189
- method: 'POST',
190
- credentials: 'include',
191
- headers: {
192
- 'Content-Type': 'application/json',
193
- 'lang': locale.value,
194
- },
195
- body: JSON.stringify({
196
- 'username': intermediateState.value.email.trim(),
197
- 'password': intermediateState.value.password.toString(),
198
- '2fa_code': otpCode,
199
- }),
200
- })
201
- const { data, message } = await login.json().catch(() => ({}))
202
-
203
- if (login.status > 202) {
204
- if (login.status === 403) {
205
- showOtpError.value = true
206
- isLoadingOtp.value = false
207
- }
208
- }
209
- else {
210
- const response = await fetch(`${authApiURL}/access/cookie`, {
211
- method: 'GET',
212
- credentials: 'include',
213
- headers: {
214
- Authorization: `Bearer ${accessToken.value}`,
215
- lang: locale.value,
216
- },
217
- })
218
- const { data: cookiesData } = await response.json()
219
- if (cookiesData?.access_token) {
220
- const { access_token, expire_in } = cookiesData
221
- const hostname = location.hostname.split('.').reverse()
222
- document.cookie = `accessToken=${access_token}; max-age=${expire_in}; domain=.${hostname[1]}.${hostname[0]}; path=/`
223
- }
224
- $toast.success(t('login.successfully'))
225
- loginModal.value = false
226
- window.location.reload()
227
-
228
- isTwoFactorOpen.value = false
229
- }
230
- isLoadingOtp.value = false
231
- }
232
-
233
179
  function handleEnter(e: KeyboardEvent) {
234
- if (
235
- e.key === 'Enter'
236
- && !isTwoFactorOpen.value
237
- && !isConfirmationEmailModal.value
238
- && !isResendModal.value
239
- ) {
180
+ if (e.key === 'Enter') {
240
181
  submit()
241
182
  }
242
183
  }
@@ -258,21 +199,21 @@ onBeforeUnmount(() => {
258
199
  <template>
259
200
  <div class="flex flex-col gap-5">
260
201
  <h1 class="heading-02 text-center">
261
- {{ $t('login.form.title') }}
202
+ {{ t('modals.id.login.title') }}
262
203
  </h1>
263
204
  <p class="body-400 text-center">
264
- {{ $t('login.form.subtitle') }}
205
+ {{ t('modals.id.login.subtitle') }}
265
206
  </p>
266
207
  <div class="flex flex-col gap-4">
267
208
  <a-input-standard
268
209
  v-model="form.username"
269
210
  type="email"
270
- :label="$t('login.form.labels.email')"
211
+ :label="t('modals.id.login.labels.email')"
271
212
  :error="getError('username')"
272
213
  />
273
214
  <a-input-password
274
215
  v-model="form.password"
275
- :label="$t('login.form.labels.password')"
216
+ :label="t('modals.id.login.labels.password')"
276
217
  :error="getError('password')"
277
218
  />
278
219
  <div class="flex items-center justify-between">
@@ -285,14 +226,14 @@ onBeforeUnmount(() => {
285
226
  for="remember_me"
286
227
  class="cursor-pointer"
287
228
  >{{
288
- $t('login.form.remember_me')
229
+ t('modals.id.login.remember_me')
289
230
  }}</label>
290
231
  </div>
291
232
  <button
292
233
  class="link-s-400"
293
234
  @click="onForgotPassword"
294
235
  >
295
- {{ $t('login.form.forget_password') }}
236
+ {{ t('modals.id.login.forget_password') }}
296
237
  </button>
297
238
  </div>
298
239
  </div>
@@ -325,10 +266,10 @@ onBeforeUnmount(() => {
325
266
  type="submit"
326
267
  @click="submit"
327
268
  >
328
- {{ $t('actions.login') }}
269
+ {{ t('actions.login') }}
329
270
  </a-button>
330
271
  <p class="body-400 text-center">
331
- {{ $t('login.form.first_time') }}
272
+ {{ t('modals.id.login.first_time') }}
332
273
  </p>
333
274
 
334
275
  <a-button
@@ -337,7 +278,7 @@ onBeforeUnmount(() => {
337
278
  class="w-full"
338
279
  @click="onRegister"
339
280
  >
340
- {{ $t('actions.register') }}
281
+ {{ t('actions.register') }}
341
282
  </a-button>
342
283
 
343
284
  <a-button
@@ -346,40 +287,16 @@ onBeforeUnmount(() => {
346
287
  class="w-full"
347
288
  @click="toTariffs"
348
289
  >
349
- {{ $t('actions.toTariffs') }}
290
+ {{ t('actions.toTariffs') }}
350
291
  </a-button>
351
292
 
352
293
  <a-alert
353
294
  class="max-w-screen-sm !text-[10px]"
354
295
  size="xs"
355
296
  >
356
- {{ $t('info.userAgreement') }}
297
+ {{ t('info.userAgreement') }}
357
298
  </a-alert>
358
299
  </div>
359
-
360
- <!-- <a-modal v-model="isResendModal"> -->
361
- <!-- <resend -->
362
- <!-- v-if="isResendModal" -->
363
- <!-- @close="isResendModal = false" -->
364
- <!-- @resend="onResend" -->
365
- <!-- /> -->
366
- <!-- </a-modal> -->
367
-
368
- <!-- <a-modal v-model="isConfirmationEmailModal"> -->
369
- <!-- <a-confirmation-email -->
370
- <!-- v-if="isConfirmationEmailModal" -->
371
- <!-- @close="isConfirmationEmailModal = false" -->
372
- <!-- @resend="confirmationEmailResend" -->
373
- <!-- /> -->
374
- <!-- </a-modal> -->
375
-
376
- <two-factor
377
- v-model="isTwoFactorOpen"
378
- v-model:error="showOtpError"
379
- :loading="isLoadingOtp"
380
- @confirm="handleConfirmOtp"
381
- @close="isTwoFactorOpen = false"
382
- />
383
300
  </template>
384
301
 
385
302
  <style scoped></style>
@@ -7,6 +7,7 @@ import IdPasswordSuccessfulModal from '#adata-ui/components/modals/id/IdPassword
7
7
  import IdRecoveryModal from '#adata-ui/components/modals/id/IdRecoveryModal.vue'
8
8
  import IdRegistrationModal from '#adata-ui/components/modals/id/IdRegistrationModal.vue'
9
9
  import IdResetPasswordOtpModal from '#adata-ui/components/modals/id/IdResetPasswordOtpModal.vue'
10
+ import IdTwoFactorModal from '#adata-ui/components/modals/id/IdTwoFactorModal.vue'
10
11
 
11
12
  const {
12
13
  loginModal,
@@ -25,11 +26,14 @@ const {
25
26
  <a-modal v-model="loginModal">
26
27
  <id-login-modal />
27
28
  </a-modal>
29
+ <a-modal v-model="twoFactorModal">
30
+ <id-two-factor-modal />
31
+ </a-modal>
28
32
 
29
33
  <a-modal v-model="registrationModal">
30
34
  <id-registration-modal v-if="registrationModal" />
31
35
  </a-modal>
32
- <a-modal v-model="confirmAccountOtpModal">
36
+ <a-modal v-model="confirmAccountOtpModal" prevent-close>
33
37
  <id-confirm-account-otp-modal v-if="confirmAccountOtpModal" />
34
38
  </a-modal>
35
39
  <a-modal v-model="confirmSuccessfulModal">
@@ -21,16 +21,18 @@ const loading = ref(false)
21
21
  const resetSchema = z.object({
22
22
  password: z
23
23
  .string()
24
- .min(8, t('register.form.errors.low_security', { length: 8 }))
25
- .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
24
+ .nonempty(t('error.required'))
25
+ .min(8, t('modals.id.register.errors.low_security', { length: 8 }))
26
+ .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
26
27
  password_confirmation: z
27
28
  .string()
28
- .min(8, t('register.form.errors.low_security', { length: 8 }))
29
- .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
29
+ .nonempty(t('error.required'))
30
+ .min(8, t('modals.id.register.errors.low_security', { length: 8 }))
31
+ .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
30
32
  }).refine(
31
33
  data => data.password === data.password_confirmation,
32
34
  {
33
- message: t('register.form.errors.sameAs'),
35
+ message: t('modals.id.register.errors.sameAs'),
34
36
  path: ['password_confirmation'],
35
37
  },
36
38
  )
@@ -34,21 +34,21 @@ const agreement = ref(false)
34
34
  const loading = ref(false)
35
35
 
36
36
  const registerSchema = z.object({
37
- email: z.string().nonempty(t('register.form.errors.required')).email(t('register.form.errors.email')),
37
+ email: z.string().nonempty(t('error.required')).email(t('error.email')),
38
38
  password: z
39
39
  .string()
40
- .nonempty(t('register.form.errors.required'))
41
- .min(8, t('register.form.errors.low_security', { length: 8 }))
42
- .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
40
+ .nonempty(t('error.required'))
41
+ .min(8, t('modals.id.register.errors.low_security', { length: 8 }))
42
+ .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
43
43
  password_confirmation: z
44
44
  .string()
45
- .nonempty(t('register.form.errors.required'))
46
- .min(8, t('register.form.errors.low_security', { length: 8 }))
47
- .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
45
+ .nonempty(t('error.required'))
46
+ .min(8, t('modals.id.register.errors.low_security', { length: 8 }))
47
+ .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
48
48
  }).refine(
49
49
  data => data.password === data.password_confirmation,
50
50
  {
51
- message: t('register.form.errors.sameAs'),
51
+ message: t('modals.id.register.errors.sameAs'),
52
52
  path: ['password_confirmation'],
53
53
  },
54
54
  )
@@ -152,31 +152,31 @@ onBeforeUnmount(() => {
152
152
  @submit.prevent="onSubmit"
153
153
  >
154
154
  <h2 class="text-center text-2xl font-bold">
155
- {{ t('register.form.title') }}
155
+ {{ t('modals.id.register.title') }}
156
156
  </h2>
157
157
  <p class="text-center text-sm">
158
- {{ t('register.form.subtitle') }}
158
+ {{ t('modals.id.register.subtitle') }}
159
159
  </p>
160
160
  <a-input-standard
161
161
  v-model="form.email"
162
- :label="t('register.form.labels.email')"
162
+ :label="t('modals.id.register.labels.email')"
163
163
  :error="getError('email')"
164
164
  type="email"
165
165
  />
166
166
 
167
167
  <a-input-password
168
168
  v-model="form.password"
169
- :label="t('register.form.labels.password')"
169
+ :label="t('modals.id.register.labels.password')"
170
170
  :error="getError('password')"
171
171
  />
172
172
 
173
173
  <a-input-password
174
174
  v-model="form.password_confirmation"
175
- :label="t('register.form.labels.password_confirmation')"
175
+ :label="t('modals.id.register.labels.password_confirmation')"
176
176
  :error="getError('password_confirmation')"
177
177
  />
178
178
  <a-alert color="blue">
179
- {{ t('register.form.alert') }}
179
+ {{ t('modals.id.register.alert') }}
180
180
  <template #icon>
181
181
  <a-icon-info-circle />
182
182
  </template>
@@ -185,13 +185,13 @@ onBeforeUnmount(() => {
185
185
  v-model="agreement"
186
186
  side="right"
187
187
  >
188
- <i18n-t keypath="register.form.agreement.text">
188
+ <i18n-t keypath="modals.id.register.agreement.text">
189
189
  <template #link>
190
190
  <nuxt-link-locale
191
191
  class="text-blue-700"
192
192
  @click="getUrl"
193
193
  >
194
- {{ t('register.form.agreement.link') }}
194
+ {{ t('modals.id.register.agreement.link') }}
195
195
  </nuxt-link-locale>
196
196
  </template>
197
197
  </i18n-t>
@@ -200,17 +200,17 @@ onBeforeUnmount(() => {
200
200
  :disabled="!agreement"
201
201
  :loading="loading"
202
202
  >
203
- {{ t('register.form.continue') }}
203
+ {{ t('modals.id.register.continue') }}
204
204
  </a-button>
205
205
  <p class="text-center text-sm">
206
- {{ t('register.form.haveAcc') }}
206
+ {{ t('modals.id.register.haveAcc') }}
207
207
  </p>
208
208
  <a-button
209
209
  type="button"
210
210
  view="outline"
211
211
  @click="onLogin"
212
212
  >
213
- {{ t('register.form.enter') }}
213
+ {{ t('modals.id.register.enter') }}
214
214
  </a-button>
215
215
  </form>
216
216
  </template>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import OtpInput from '#adata-ui/components/modals/two-factor/otp-input.vue'
2
+ import OtpInput from '#adata-ui/components/modals/id/otp-input.vue'
3
3
  import { useAuthStore } from '#adata-ui/stores/auth.store'
4
4
 
5
5
  const { $toast } = useNuxtApp()
@@ -17,6 +17,7 @@ const otp = ref(['', '', '', '', '', ''])
17
17
  const otpFormatted = computed(() => {
18
18
  return otp.value.join('')
19
19
  })
20
+ const attempts = ref(5)
20
21
  const showError = ref(false)
21
22
  const isLoading = ref(false)
22
23
 
@@ -43,6 +44,12 @@ async function onConfirm() {
43
44
  newPasswordModal.value = true
44
45
  }
45
46
  catch (error) {
47
+ if (error.data?.retry_after) {
48
+ attempts.value = 0
49
+ }
50
+ else {
51
+ attempts.value--
52
+ }
46
53
  showError.value = true
47
54
  $toast.error(error.data.message)
48
55
  }
@@ -72,6 +79,7 @@ async function onResend() {
72
79
 
73
80
  function onClose() {
74
81
  otp.value = ['', '', '', '', '', '']
82
+ attempts.value = 5
75
83
  showError.value = false
76
84
  resetPasswordOtpModal.value = false
77
85
  }
@@ -85,6 +93,7 @@ function handleEnter(e: KeyboardEvent) {
85
93
  const timer = ref(60)
86
94
 
87
95
  function runTimer() {
96
+ timer.value = 60
88
97
  const intervalId = setInterval(() => {
89
98
  if (!timer.value) clearInterval(intervalId)
90
99
  return timer.value--
@@ -116,7 +125,7 @@ onBeforeUnmount(() => {
116
125
  </p>
117
126
 
118
127
  <div v-if="timer > 0" class="text-2xl font-bold">
119
- {{ timer }} {{ t('register.modal.seconds') }}
128
+ {{ timer }} {{ t('modals.id.resetPasswordOtp.seconds') }}
120
129
  </div>
121
130
  <button
122
131
  v-else
@@ -127,7 +136,11 @@ onBeforeUnmount(() => {
127
136
  </button>
128
137
  </div>
129
138
 
130
- <otp-input v-model="otp" v-model:error="showError" />
139
+ <otp-input
140
+ v-model="otp"
141
+ v-model:error="showError"
142
+ :attempts="attempts"
143
+ />
131
144
 
132
145
  <div class="flex w-full gap-2">
133
146
  <a-button
@@ -140,7 +153,7 @@ onBeforeUnmount(() => {
140
153
  <a-button
141
154
  block
142
155
  :loading="isLoading"
143
- :disabled="otpFormatted.length < 6"
156
+ :disabled="otpFormatted.length < 6 || attempts === 0"
144
157
  @click="onConfirm"
145
158
  >
146
159
  {{ t('actions.confirm') }}