adata-ui 2.0.23 → 2.0.25

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.
@@ -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-fit cursor-pointer"
16
+ class="curve-block relative w-full cursor-pointer"
17
17
  active-class="active-item"
18
18
  >
19
19
  <svg
20
- width="306"
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
  >
@@ -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
- <div
314
- v-if="isOpen"
315
- class="absolute z-20 mt-1 w-full rounded-md bg-white shadow-[0_1px_8px_0_rgba(139,146,156,0.3)] dark:bg-gray-900"
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">
@@ -0,0 +1,147 @@
1
+ <script setup lang="ts">
2
+ import OtpInput from '#adata-ui/components/modals/two-factor/otp-input.vue'
3
+ import { useAuthStore } from '#adata-ui/stores/auth.store'
4
+
5
+ const { $toast } = useNuxtApp()
6
+ const { t, locale } = useI18n()
7
+ const { commonAuth } = useAppConfig()
8
+
9
+ const authStore = useAuthStore()
10
+ const { intermediateState } = storeToRefs(authStore)
11
+
12
+ const { confirmAccountOtpModal, confirmSuccessfulModal } = useIdModals()
13
+
14
+ const authApiURL = commonAuth.authApiURL
15
+
16
+ const otp = ref(['', '', '', '', '', ''])
17
+ const otpFormatted = computed(() => {
18
+ return otp.value.join('')
19
+ })
20
+ const showError = ref(false)
21
+ const isLoading = ref(false)
22
+
23
+ async function onResend() {
24
+ try {
25
+ await $fetch(`${authApiURL}/email/resend-otp`, {
26
+ method: 'GET',
27
+ credentials: 'include',
28
+ headers: {
29
+ lang: locale.value,
30
+ },
31
+ body: {
32
+ email: intermediateState.value.email,
33
+ },
34
+ })
35
+ runTimer()
36
+ }
37
+ catch (error) {
38
+ $toast.error(error.data)
39
+ }
40
+ }
41
+
42
+ async function onConfirm() {
43
+ try {
44
+ isLoading.value = true
45
+
46
+ await $fetch(`${authApiURL}/email/verify-otp`, {
47
+ method: 'GET',
48
+ credentials: 'include',
49
+ headers: {
50
+ lang: locale.value,
51
+ },
52
+ params: {
53
+ email: intermediateState.value.email,
54
+ otp_code: otpFormatted.value,
55
+ },
56
+ })
57
+
58
+ confirmAccountOtpModal.value = false
59
+ confirmSuccessfulModal.value = true
60
+ }
61
+ catch (error) {
62
+ showError.value = true
63
+ $toast.error(error.data.message)
64
+ }
65
+ finally {
66
+ isLoading.value = false
67
+ }
68
+ }
69
+
70
+ function onClose() {
71
+ otp.value = ['', '', '', '', '', '']
72
+ showError.value = false
73
+ confirmAccountOtpModal.value = false
74
+ }
75
+
76
+ const timer = ref(60)
77
+
78
+ function runTimer() {
79
+ const intervalId = setInterval(() => {
80
+ if (!timer.value) clearInterval(intervalId)
81
+ return timer.value--
82
+ }, 1000)
83
+ }
84
+
85
+ function handleEnter(e: KeyboardEvent) {
86
+ if (e.key === 'Enter') {
87
+ onConfirm()
88
+ }
89
+ }
90
+
91
+ onMounted(() => {
92
+ runTimer()
93
+ document.addEventListener('keyup', handleEnter)
94
+ })
95
+
96
+ onBeforeUnmount(() => {
97
+ document.removeEventListener('keyup', handleEnter)
98
+ })
99
+ </script>
100
+
101
+ <template>
102
+ <div class="flex flex-col items-center gap-4 text-center">
103
+ <h2 class="text-2xl font-bold">
104
+ {{ t('register.modal.title') }}
105
+ </h2>
106
+
107
+ <a-icon-hand-with-phone-light class="size-32 dark:hidden" />
108
+ <a-icon-hand-with-phone-dark class="hidden size-32 dark:block" />
109
+
110
+ <div class="text-sm">
111
+ <p class="mb-1">
112
+ {{ t('modals.id.resetPasswordOtp.content') }}
113
+ </p>
114
+
115
+ <div v-if="timer > 0" class="text-2xl font-bold">
116
+ {{ timer }} {{ t('register.modal.seconds') }}
117
+ </div>
118
+ <button
119
+ v-else
120
+ class="text-blue-700 dark:text-blue-500"
121
+ @click="onResend"
122
+ >
123
+ {{ t('actions.resend') }}
124
+ </button>
125
+ </div>
126
+
127
+ <otp-input v-model="otp" v-model:error="showError" />
128
+
129
+ <div class="flex w-full gap-2">
130
+ <a-button
131
+ block
132
+ view="outline"
133
+ @click="onClose"
134
+ >
135
+ {{ t('actions.close') }}
136
+ </a-button>
137
+ <a-button
138
+ block
139
+ :loading="isLoading"
140
+ :disabled="otpFormatted.length < 6"
141
+ @click="onConfirm"
142
+ >
143
+ {{ t('actions.confirm') }}
144
+ </a-button>
145
+ </div>
146
+ </div>
147
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ const { confirmSuccessfulModal } = useIdModals()
3
+ const { t } = useI18n()
4
+ </script>
5
+
6
+ <template>
7
+ <div class="flex flex-col gap-4 text-center">
8
+ <h2 class="text-2xl font-bold">
9
+ {{ t('modals.id.confirmSuccessful.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('modals.id.confirmSuccessful.content') }}
16
+ </p>
17
+ <a-button view="outline" @click="confirmSuccessfulModal = false">
18
+ {{ t('actions.close') }}
19
+ </a-button>
20
+ </div>
21
+ </template>
22
+
23
+ <style scoped>
24
+
25
+ </style>
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
- import AConfirmationEmail from '#adata-ui/components/modals/AConfirmationEmail.vue'
3
- import Resend from '#adata-ui/components/modals/Resend.vue'
4
2
  import TwoFactor from '#adata-ui/components/modals/two-factor/two-factor.vue'
3
+ import { useAuthStore } from '#adata-ui/stores/auth.store'
5
4
  import { navigateToLocalizedPage } from '#adata-ui/utils/localizedNavigation'
6
5
  import { useToggle } from '@vueuse/shared'
7
6
  import * as z from 'zod'
@@ -13,13 +12,14 @@ const isConfirmationEmailModal = ref(false)
13
12
  const isTwoFactorOpen = ref(false)
14
13
  const isLoadingOtp = ref(false)
15
14
  const { $api, $toast } = useNuxtApp()
16
- const localePath = useLocalePath()
17
15
  const showOtpError = ref(false)
18
16
  const { t, locale } = useI18n()
19
- const route = useRoute()
20
17
  const submitted = ref(false)
21
18
  const accessToken = ref(null)
22
- const { loginModal, registrationModal, recoveryModal } = useIdModals()
19
+ const { loginModal, registrationModal, recoveryModal, confirmAccountOtpModal } = useIdModals()
20
+
21
+ const authStore = useAuthStore()
22
+ const { intermediateState } = storeToRefs(authStore)
23
23
 
24
24
  export interface ILoginForm {
25
25
  username: string
@@ -84,7 +84,22 @@ async function submit() {
84
84
  $toast.error(t('error.validation'))
85
85
  }
86
86
  else {
87
- $toast.error(message)
87
+ intermediateState.value.email = form.username
88
+ intermediateState.value.password = form.password
89
+
90
+ await fetch(`${authApiURL}/email/resend-otp`, {
91
+ method: 'GET',
92
+ credentials: 'include',
93
+ headers: {
94
+ 'Content-Type': 'application/json',
95
+ 'lang': locale.value,
96
+ },
97
+ body: JSON.stringify({
98
+ email: intermediateState.value.email,
99
+ }),
100
+ }).catch(() => {})
101
+
102
+ confirmAccountOtpModal.value = true
88
103
  }
89
104
  }
90
105
  else {
@@ -112,7 +127,20 @@ async function submit() {
112
127
  window.location.reload()
113
128
  }
114
129
  else {
115
- isConfirmationEmailModal.value = true
130
+ intermediateState.value.email = form.username
131
+ intermediateState.value.password = form.password
132
+ confirmAccountOtpModal.value = true
133
+
134
+ $fetch(`${authApiURL}/email/resend-otp`, {
135
+ method: 'GET',
136
+ credentials: 'include',
137
+ headers: {
138
+ lang: locale.value,
139
+ },
140
+ body: {
141
+ email: intermediateState.value.email,
142
+ },
143
+ }).catch(() => {})
116
144
  }
117
145
  }
118
146
  loading.value = false
@@ -165,8 +193,8 @@ async function handleConfirmOtp(otpCode: string) {
165
193
  'lang': locale.value,
166
194
  },
167
195
  body: JSON.stringify({
168
- 'username': form.username.trim(),
169
- 'password': form.password.toString(),
196
+ 'username': intermediateState.value.email.trim(),
197
+ 'password': intermediateState.value.password.toString(),
170
198
  '2fa_code': otpCode,
171
199
  }),
172
200
  })
@@ -195,6 +223,7 @@ async function handleConfirmOtp(otpCode: string) {
195
223
  }
196
224
  $toast.success(t('login.successfully'))
197
225
  loginModal.value = false
226
+ window.location.reload()
198
227
 
199
228
  isTwoFactorOpen.value = false
200
229
  }
@@ -217,14 +246,6 @@ function onForgotPassword() {
217
246
  recoveryModal.value = true
218
247
  }
219
248
 
220
- watch(loginModal, (value) => {
221
- if (!value) {
222
- form.username = ''
223
- form.password = ''
224
- rememberMe.value = false
225
- }
226
- })
227
-
228
249
  onMounted(() => {
229
250
  document.addEventListener('keyup', handleEnter)
230
251
  })
@@ -235,124 +256,122 @@ onBeforeUnmount(() => {
235
256
  </script>
236
257
 
237
258
  <template>
238
- <a-modal v-model="loginModal">
239
- <div class="flex flex-col gap-5">
240
- <h1 class="heading-02 text-center">
241
- {{ $t('login.form.title') }}
242
- </h1>
243
- <p class="body-400 text-center">
244
- {{ $t('login.form.subtitle') }}
245
- </p>
246
- <div class="flex flex-col gap-4">
247
- <a-input-standard
248
- v-model="form.username"
249
- type="email"
250
- :label="$t('login.form.labels.email')"
251
- :error="getError('username')"
252
- />
253
- <a-input-password
254
- v-model="form.password"
255
- :label="$t('login.form.labels.password')"
256
- :error="getError('password')"
257
- />
258
- <div class="flex items-center justify-between">
259
- <div class="body-400 flex gap-2">
260
- <a-checkbox
261
- v-model="rememberMe"
262
- name="remember_me"
263
- />
264
- <label
265
- for="remember_me"
266
- class="cursor-pointer"
267
- >{{
268
- $t('login.form.remember_me')
269
- }}</label>
270
- </div>
271
- <button
272
- class="link-s-400"
273
- @click="onForgotPassword"
274
- >
275
- {{ $t('login.form.forget_password') }}
276
- </button>
277
- </div>
278
- </div>
279
- <div class="flex items-center gap-4">
280
- <div class="h-px w-full bg-deepblue-500/30" />
281
- <div class="flex shrink-0 gap-4">
282
- <span
283
- class="cursor-pointer"
284
- @click="authWithSocial('google')"
285
- >
286
- <a-icon-google />
287
- </span>
288
- <span
289
- class="cursor-pointer"
290
- @click="authWithSocial('yandex')"
291
- >
292
- <a-icon-yandex />
293
- </span>
294
- <span
259
+ <div class="flex flex-col gap-5">
260
+ <h1 class="heading-02 text-center">
261
+ {{ $t('login.form.title') }}
262
+ </h1>
263
+ <p class="body-400 text-center">
264
+ {{ $t('login.form.subtitle') }}
265
+ </p>
266
+ <div class="flex flex-col gap-4">
267
+ <a-input-standard
268
+ v-model="form.username"
269
+ type="email"
270
+ :label="$t('login.form.labels.email')"
271
+ :error="getError('username')"
272
+ />
273
+ <a-input-password
274
+ v-model="form.password"
275
+ :label="$t('login.form.labels.password')"
276
+ :error="getError('password')"
277
+ />
278
+ <div class="flex items-center justify-between">
279
+ <div class="body-400 flex gap-2">
280
+ <a-checkbox
281
+ v-model="rememberMe"
282
+ name="remember_me"
283
+ />
284
+ <label
285
+ for="remember_me"
295
286
  class="cursor-pointer"
296
- @click="authWithSocial('mailru')"
297
- >
298
- <a-icon-mailru />
299
- </span>
287
+ >{{
288
+ $t('login.form.remember_me')
289
+ }}</label>
300
290
  </div>
301
- <div class="h-px w-full bg-deepblue-500/30" />
291
+ <button
292
+ class="link-s-400"
293
+ @click="onForgotPassword"
294
+ >
295
+ {{ $t('login.form.forget_password') }}
296
+ </button>
302
297
  </div>
303
- <a-button
304
- :loading="loading"
305
- type="submit"
306
- @click="submit"
307
- >
308
- {{ $t('actions.login') }}
309
- </a-button>
310
- <p class="body-400 text-center">
311
- {{ $t('login.form.first_time') }}
312
- </p>
313
-
314
- <a-button
315
- type="button"
316
- view="outline"
317
- class="w-full"
318
- @click="onRegister"
319
- >
320
- {{ $t('actions.register') }}
321
- </a-button>
322
-
323
- <a-button
324
- type="button"
325
- view="transparent"
326
- class="w-full"
327
- @click="toTariffs"
328
- >
329
- {{ $t('actions.toTariffs') }}
330
- </a-button>
331
-
332
- <a-alert
333
- class="max-w-screen-sm !text-[10px]"
334
- size="xs"
335
- >
336
- {{ $t('info.userAgreement') }}
337
- </a-alert>
338
298
  </div>
339
- </a-modal>
340
-
341
- <a-modal v-model="isResendModal">
342
- <resend
343
- v-if="isResendModal"
344
- @close="isResendModal = false"
345
- @resend="onResend"
346
- />
347
- </a-modal>
348
-
349
- <a-modal v-model="isConfirmationEmailModal">
350
- <a-confirmation-email
351
- v-if="isConfirmationEmailModal"
352
- @close="isConfirmationEmailModal = false"
353
- @resend="confirmationEmailResend"
354
- />
355
- </a-modal>
299
+ <div class="flex items-center gap-4">
300
+ <div class="h-px w-full bg-deepblue-500/30" />
301
+ <div class="flex shrink-0 gap-4">
302
+ <span
303
+ class="cursor-pointer"
304
+ @click="authWithSocial('google')"
305
+ >
306
+ <a-icon-google />
307
+ </span>
308
+ <span
309
+ class="cursor-pointer"
310
+ @click="authWithSocial('yandex')"
311
+ >
312
+ <a-icon-yandex />
313
+ </span>
314
+ <span
315
+ class="cursor-pointer"
316
+ @click="authWithSocial('mailru')"
317
+ >
318
+ <a-icon-mailru />
319
+ </span>
320
+ </div>
321
+ <div class="h-px w-full bg-deepblue-500/30" />
322
+ </div>
323
+ <a-button
324
+ :loading="loading"
325
+ type="submit"
326
+ @click="submit"
327
+ >
328
+ {{ $t('actions.login') }}
329
+ </a-button>
330
+ <p class="body-400 text-center">
331
+ {{ $t('login.form.first_time') }}
332
+ </p>
333
+
334
+ <a-button
335
+ type="button"
336
+ view="outline"
337
+ class="w-full"
338
+ @click="onRegister"
339
+ >
340
+ {{ $t('actions.register') }}
341
+ </a-button>
342
+
343
+ <a-button
344
+ type="button"
345
+ view="transparent"
346
+ class="w-full"
347
+ @click="toTariffs"
348
+ >
349
+ {{ $t('actions.toTariffs') }}
350
+ </a-button>
351
+
352
+ <a-alert
353
+ class="max-w-screen-sm !text-[10px]"
354
+ size="xs"
355
+ >
356
+ {{ $t('info.userAgreement') }}
357
+ </a-alert>
358
+ </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> -->
356
375
 
357
376
  <two-factor
358
377
  v-model="isTwoFactorOpen"