adata-ui 2.0.12 → 2.0.14

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.
@@ -77,9 +77,14 @@ const disabledStyles =
77
77
  'disabled:pointer-events-none disabled:opacity-40 disabled:bg-deepblue-50 disabled:text-deepblue-200 disabled:dark:text-gray-500'
78
78
 
79
79
  const componentOptions = computed(() =>
80
- props.options.filter((item: T) =>
81
- item[props.searchKey as keyof T].toLowerCase().includes(searchValue.value.toLowerCase())
82
- )
80
+ props.options.filter((item: T) => {
81
+ if (Array.isArray(props.searchKey) && props.searchKey.length === 2) {
82
+ return item[props.searchKey[0] as keyof T].toLowerCase().includes(searchValue.value.toLowerCase()) ||
83
+ item[props.searchKey[1] as keyof T].toLowerCase().includes(searchValue.value.toLowerCase())
84
+ } else {
85
+ return item[props.searchKey as keyof T].toLowerCase().includes(searchValue.value.toLowerCase())
86
+ }
87
+ })
83
88
  )
84
89
 
85
90
  onClickOutside(wrapper, () => {
@@ -0,0 +1,356 @@
1
+ <script setup lang="ts">
2
+ import { useToggle } from '@vueuse/shared'
3
+ import * as z from 'zod'
4
+ import AConfirmationEmail from '#adata-ui/components/modals/AConfirmationEmail.vue'
5
+ import Resend from '#adata-ui/components/modals/Resend.vue'
6
+ import TwoFactor from '#adata-ui/components/modals/two-factor/two-factor.vue'
7
+
8
+ const appConfig = useAppConfig()
9
+ const landingUrl = appConfig.myLayer.landingUrl
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 localePath = useLocalePath()
16
+ const showOtpError = ref(false)
17
+ const { t, locale } = useI18n()
18
+ const route = useRoute()
19
+ const submitted = ref(false)
20
+ const accessToken = ref(null)
21
+
22
+ export interface ILoginForm {
23
+ username: string
24
+ password: string
25
+ }
26
+
27
+
28
+ const loginSchema = z.object({
29
+ username: z.string().nonempty(t('errors.required')).email(t('register.form.errors.email')),
30
+ password: z.string().nonempty(t('errors.required')),
31
+ })
32
+
33
+ const validation = computed(() => {
34
+ if (!submitted.value) return null
35
+ const result = loginSchema.safeParse(form)
36
+ return result.success ? null : result.error
37
+ })
38
+
39
+ function getError(path: string) {
40
+ return validation.value?.issues.find(issue => issue.path[0] === path)?.message
41
+ }
42
+
43
+ function savePassAndLogin(form: { username: string, password: string }) {
44
+ const { username, password } = form
45
+ const cookieOptions = {
46
+ expires: rememberMe.value ? new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) : undefined, // Срок действия 14 дней
47
+ }
48
+ useCookie('username', cookieOptions).value = username
49
+ useCookie('password', cookieOptions).value = password
50
+ }
51
+ function clearAuthCookie() {
52
+ useCookie('username').value = null
53
+ useCookie('password').value = null
54
+ }
55
+
56
+ const loading = ref(false)
57
+ const form: ILoginForm = reactive({
58
+ username: useCookie('username').value || '',
59
+ password: useCookie('password').value || '',
60
+ })
61
+ const rememberMe = ref(false)
62
+
63
+ async function submit() {
64
+ submitted.value = true
65
+ if (!validation.value) {
66
+ loading.value = true
67
+ const login = await fetch(`https://auth.adtdev.kz/api/login`, {
68
+ method: "POST",
69
+ headers: {
70
+ "Content-Type": "application/json",
71
+ lang: locale.value,
72
+ },
73
+ body: JSON.stringify({
74
+ username: form.username.trim().toLowerCase(),
75
+ password: form.password.toString()
76
+ })
77
+ })
78
+ const { data, message } = await login.json().catch(() => ({}));
79
+ if (data) accessToken.value = data?.access_token
80
+ if (login.status > 202) {
81
+ if (login.status === 422) {
82
+ $toast.error(t('error.validation'))
83
+ }
84
+ else {
85
+ $toast.error(message)
86
+ }
87
+ }
88
+ else {
89
+ rememberMe.value ? savePassAndLogin(form) : clearAuthCookie()
90
+ if (data.is_2fa_enabled) {
91
+ isTwoFactorOpen.value = true
92
+ }
93
+ else if (data.email_is_verified) {
94
+ const response = await fetch('https://auth.adtdev.kz/api/access/cookie',{
95
+ method: "GET",
96
+ credentials: 'include',
97
+ headers: {
98
+ Authorization: `Bearer ${accessToken.value}`,
99
+ lang: locale.value,
100
+ },
101
+ })
102
+ const { data: cookiesData } = await response.json()
103
+ if (cookiesData) {
104
+ const { access_token, expire_in } = cookiesData
105
+ const hostname = location.hostname.split('.').reverse()
106
+ document.cookie = `accessToken=${access_token}; max-age=${expire_in}; domain=.${hostname[1]}.${hostname[0]}; path=/`
107
+ }
108
+ $toast.success(t('login.successfully'))
109
+ if (route.query.url) {
110
+ await navigateTo(route.query.url as string, {
111
+ external: true,
112
+ })
113
+ }
114
+ else {
115
+ await navigateTo(landingUrl, {
116
+ external: true,
117
+ })
118
+ }
119
+ }
120
+ else {
121
+ isConfirmationEmailModal.value = true
122
+ }
123
+ }
124
+ loading.value = false
125
+ }
126
+ }
127
+
128
+ function authWithSocial(social: string) {
129
+ const mode = (useNuxtApp().$config.public.authApiURL as string).includes('adata') ? 'adata' : 'adtdev'
130
+ document.location.replace(`https://auth.${mode}.kz/api/login/social?source=${social}`)
131
+ }
132
+
133
+ async function onResend() {
134
+ const { data, success, message } = await $api.auth.emailResend()
135
+ if (success) {
136
+ $toast.success(message)
137
+ }
138
+ }
139
+
140
+ function onRegister() {
141
+ if (Object.keys(route.query).length) {
142
+ navigateTo(localePath({ path: `/register`, query: route.query }))
143
+ }
144
+ else {
145
+ navigateTo(localePath(`/register`))
146
+ }
147
+ }
148
+
149
+ function toTariffs() {
150
+ return navigateToLocalizedPage({ locale, projectUrl: appConfig.myLayer.landingUrl, path: '/tariffs', target: '_self' })
151
+ }
152
+
153
+ onMounted(() => {
154
+ rememberMe.value = !!useCookie('username').value && !!useCookie('password').value
155
+ })
156
+
157
+ function confirmationEmailResend() {
158
+ isConfirmationEmailModal.value = false
159
+ toggleResendModal()
160
+ }
161
+ async function handleConfirmOtp(otpCode: string) {
162
+ isLoadingOtp.value = true
163
+ const login = await fetch(`https://auth.adtdev.kz/api/login`, {
164
+ method: "POST",
165
+ headers: {
166
+ "Content-Type": "application/json",
167
+ lang: locale.value,
168
+ },
169
+ body: JSON.stringify({
170
+ 'username': form.username.trim(),
171
+ 'password': form.password.toString(),
172
+ '2fa_code': otpCode,
173
+ })
174
+ })
175
+ const { data, message } = await login.json().catch(() => ({}));
176
+
177
+ if (login.status > 202) {
178
+ if (login.status === 403) {
179
+ showOtpError.value = true
180
+ isLoadingOtp.value = false
181
+ }
182
+ }
183
+ else {
184
+
185
+ const response = await fetch('https://auth.adtdev.kz/api/access/cookie',{
186
+ method: "GET",
187
+ credentials: 'include',
188
+ headers: {
189
+ 'Authorization': `Bearer ${accessToken.value}`,
190
+ lang: locale.value,
191
+ },
192
+ })
193
+ const { data: cookiesData } = await response.json()
194
+ if (cookiesData?.access_token) {
195
+ const { access_token, expire_in } = cookiesData
196
+ const hostname = location.hostname.split('.').reverse()
197
+ document.cookie = `accessToken=${access_token}; max-age=${expire_in}; domain=.${hostname[1]}.${hostname[0]}; path=/`
198
+ }
199
+ $toast.success(t('login.successfully'))
200
+ if (route.query.url) {
201
+ await navigateTo(route.query.url as string, {
202
+ external: true,
203
+ })
204
+ }
205
+ else {
206
+ await navigateTo(landingUrl, {
207
+ external: true,
208
+ })
209
+ }
210
+
211
+ isTwoFactorOpen.value = false
212
+ }
213
+ isLoadingOtp.value = false
214
+ }
215
+
216
+ function handleEnter(e: KeyboardEvent) {
217
+ if (e.key === 'Enter' && !isTwoFactorOpen.value && !isConfirmationEmailModal.value && !isResendModal.value) {
218
+ submit()
219
+ }
220
+ }
221
+
222
+ onMounted(() => {
223
+ document.addEventListener('keyup', handleEnter)
224
+ })
225
+
226
+ onBeforeUnmount(() => {
227
+ document.removeEventListener('keyup', handleEnter)
228
+ })
229
+ </script>
230
+
231
+ <template>
232
+ <div class="flex flex-col gap-5 rounded-lg bg-white px-7 py-8 dark:bg-gray-900">
233
+ <h1 class="heading-02 text-center">
234
+ {{ $t('login.form.title') }}
235
+ </h1>
236
+ <p class="body-400 text-center">
237
+ {{ $t('login.form.subtitle') }}
238
+ </p>
239
+ <div class="flex flex-col gap-4">
240
+ <a-input-standard
241
+ v-model="form.username"
242
+ type="email"
243
+ :label="$t('login.form.labels.email')"
244
+ :error="getError('username')"
245
+ />
246
+ <a-input-password
247
+ v-model="form.password"
248
+ :label="$t('login.form.labels.password')"
249
+ :error="getError('password')"
250
+ />
251
+ <div class="flex items-center justify-between">
252
+ <div class="body-400 flex gap-2 ">
253
+ <a-checkbox
254
+ v-model="rememberMe"
255
+ name="remember_me"
256
+ />
257
+ <label
258
+ for="remember_me"
259
+ class="cursor-pointer"
260
+ >{{ $t('login.form.remember_me') }}</label>
261
+ </div>
262
+ <nuxt-link-locale
263
+ class="link-s-400"
264
+ to="/password-recovery"
265
+ >
266
+ {{ $t('login.form.forget_password') }}
267
+ </nuxt-link-locale>
268
+ </div>
269
+ </div>
270
+ <div class="flex items-center gap-4">
271
+ <div class="bg-deepblue-500/30 h-px w-full" />
272
+ <div class="flex shrink-0 gap-4">
273
+ <span
274
+ class="cursor-pointer"
275
+ @click="authWithSocial('google')"
276
+ >
277
+ <a-icon-google />
278
+ </span>
279
+ <span
280
+ class="cursor-pointer"
281
+ @click="authWithSocial('yandex')"
282
+ >
283
+ <a-icon-yandex />
284
+ </span>
285
+ <span
286
+ class="cursor-pointer"
287
+ @click="authWithSocial('mailru')"
288
+ >
289
+ <a-icon-mailru />
290
+ </span>
291
+ </div>
292
+ <div class="bg-deepblue-500/30 h-px w-full" />
293
+ </div>
294
+ <a-button
295
+ :loading="loading"
296
+ type="submit"
297
+ @click="submit"
298
+ >
299
+ {{ $t('actions.login') }}
300
+ </a-button>
301
+ <p class="body-400 text-center">
302
+ {{ $t('login.form.first_time') }}
303
+ </p>
304
+
305
+ <a-button
306
+ type="button"
307
+ view="outline"
308
+ class="w-full"
309
+ @click="onRegister"
310
+ >
311
+ {{ $t('actions.register') }}
312
+ </a-button>
313
+
314
+ <a-button
315
+ type="button"
316
+ view="transparent"
317
+ class="w-full"
318
+ @click="toTariffs"
319
+ >
320
+ {{ $t('actions.toTariffs') }}
321
+ </a-button>
322
+
323
+ <a-alert
324
+ class="max-w-screen-sm !text-[10px]"
325
+ size="xs"
326
+ >
327
+ {{ $t('info.userAgreement') }}
328
+ </a-alert>
329
+
330
+ <a-modal v-model="isResendModal">
331
+ <resend
332
+ v-if="isResendModal"
333
+ @close="isResendModal = false"
334
+ @resend="onResend"
335
+ />
336
+ </a-modal>
337
+ <a-modal v-model="isConfirmationEmailModal">
338
+ <a-confirmation-email
339
+ v-if="isConfirmationEmailModal"
340
+ @close="isConfirmationEmailModal = false"
341
+ @resend="confirmationEmailResend"
342
+ />
343
+ </a-modal>
344
+ <two-factor
345
+ v-model="isTwoFactorOpen"
346
+ v-model:error="showOtpError"
347
+ :loading="isLoadingOtp"
348
+ @confirm="handleConfirmOtp"
349
+ @close="isTwoFactorOpen = false"
350
+ />
351
+ </div>
352
+ </template>
353
+
354
+ <style scoped>
355
+
356
+ </style>
@@ -0,0 +1,230 @@
1
+ <script setup lang="ts">
2
+ import { computed, reactive, ref } from 'vue'
3
+ import { useToggle } from '@vueuse/shared'
4
+ import { PAGES } from '#adata-ui/shared/constans/pages'
5
+
6
+ import Accept from '#adata-ui/components/modals/Accept.vue'
7
+
8
+ import * as z from 'zod'
9
+
10
+ const { $toast } = useNuxtApp()
11
+ const route = useRoute()
12
+
13
+ const localePath = useLocalePath()
14
+ const { t, locale} = useI18n()
15
+ const { myLayer }: any = useAppConfig()
16
+ const redirectUrl = useCookie('redirect_url')
17
+
18
+ export interface RegistrationForm {
19
+ email: string
20
+ password: string
21
+ password_confirmation: string
22
+ }
23
+
24
+ const form = reactive<RegistrationForm>({
25
+ email: '',
26
+ password: '',
27
+ password_confirmation: '',
28
+ })
29
+
30
+ const [isAcceptModal, toggleAcceptModal] = useToggle()
31
+ const agreement = ref(false)
32
+ const loading = ref(false)
33
+
34
+ const registerSchema = z.object({
35
+ email: z.string().nonempty(t('register.form.errors.required')).email(t('register.form.errors.email')),
36
+ password: z
37
+ .string()
38
+ .nonempty(t('register.form.errors.required'))
39
+ .min(8, t('register.form.errors.low_security', { length: 8 }))
40
+ .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
41
+ password_confirmation: z
42
+ .string()
43
+ .nonempty(t('register.form.errors.required'))
44
+ .min(8, t('register.form.errors.low_security', { length: 8 }))
45
+ .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('register.form.errors.low_security')),
46
+ }).refine(
47
+ data => data.password === data.password_confirmation,
48
+ {
49
+ message: t('register.form.errors.sameAs'),
50
+ path: ['password_confirmation'],
51
+ },
52
+ )
53
+
54
+ const submitted = ref(false)
55
+
56
+ const validation = computed(() => {
57
+ if (!submitted.value) return null
58
+ const result = registerSchema.safeParse(form)
59
+ return result.success ? null : result.error
60
+ })
61
+
62
+ function getError(path: string) {
63
+ return validation.value?.issues?.find(issue => issue.path[0] === path)?.message
64
+ }
65
+
66
+ async function onSubmit() {
67
+ if (!agreement.value) return
68
+
69
+ submitted.value = true
70
+
71
+ if (validation.value) return
72
+
73
+ try {
74
+ loading.value = true
75
+ const register = await fetch(`https://auth.adtdev.kz/api/register`, {
76
+ method: "POST",
77
+ headers: {
78
+ "Content-Type": "application/json",
79
+ lang: locale.value,
80
+ },
81
+ body: JSON.stringify(form)
82
+ })
83
+ const { data, message, error } = await register.json().catch(() => ({}));
84
+ if (data) {
85
+ toggleAcceptModal()
86
+ $toast.success(message)
87
+ }
88
+ else if (error) {
89
+ $toast.error(error.data.message.email || error.data.message.password)
90
+ }
91
+ console.error(error.data)
92
+ loading.value = false
93
+ }
94
+ catch (e) {
95
+ console.error(e)
96
+ }
97
+ }
98
+
99
+ if (route.query.url) {
100
+ redirectUrl.value = route.query.url as string
101
+ }
102
+
103
+ function goBack() {
104
+ const url = new URL(redirectUrl.value)
105
+ window.location.href = `${url.protocol}//${url.hostname}`
106
+ }
107
+
108
+ // onMounted(() => {
109
+ // v$.value.$touch();
110
+ // });
111
+
112
+ async function onResend() {
113
+
114
+ const emailResend = await fetch('https://auth.adtdev.kz/api/email/resend',{
115
+ method: "GET",
116
+ credentials: 'include',
117
+ headers: {
118
+ lang: locale.value,
119
+ },
120
+ })
121
+ const { data, success, message } = await emailResend.json()
122
+ if (success) {
123
+ toggleAcceptModal()
124
+ loading.value = false
125
+ $toast.success(message)
126
+ }
127
+ }
128
+
129
+ function getUrl() {
130
+ return navigateTo(locale.value !== 'ru'
131
+ ? myLayer.landingUrl + localePath('/') + PAGES.userAgreement
132
+ : myLayer.landingUrl + PAGES.userAgreement, {
133
+ external: true,
134
+ open: {
135
+ target: "_blank",
136
+ },
137
+ })
138
+ }
139
+
140
+ function handleEnter(event: KeyboardEvent) {
141
+ if (event.key === 'Enter' && !isAcceptModal.value) {
142
+ onSubmit()
143
+ }
144
+ }
145
+
146
+ onMounted(() => {
147
+ document.addEventListener('keyup', handleEnter)
148
+ })
149
+
150
+ onBeforeUnmount(() => {
151
+ document.removeEventListener('keyup', handleEnter)
152
+ })
153
+ </script>
154
+
155
+ <template>
156
+ <form
157
+ class="flex flex-col items-stretch gap-5 rounded-lg bg-white px-4 py-8 md:px-7 dark:bg-gray-900"
158
+ novalidate
159
+ @submit.prevent="onSubmit"
160
+ >
161
+ <h2 class="text-center text-2xl font-bold">
162
+ {{ t('register.form.title') }}
163
+ </h2>
164
+ <p class="text-center text-sm">
165
+ {{ t('register.form.subtitle') }}
166
+ </p>
167
+ <a-input-standard
168
+ v-model="form.email"
169
+ :label="t('register.form.labels.email')"
170
+ :error="getError('email')"
171
+ type="email"
172
+ />
173
+
174
+ <a-input-password
175
+ v-model="form.password"
176
+ :label="t('register.form.labels.password')"
177
+ :error="getError('password')"
178
+ />
179
+
180
+ <a-input-password
181
+ v-model="form.password_confirmation"
182
+ :label="t('register.form.labels.password_confirmation')"
183
+ :error="getError('password_confirmation')"
184
+ />
185
+ <a-alert color="blue">
186
+ {{ t('register.form.alert') }}
187
+ <template #icon>
188
+ <a-icon-info-circle />
189
+ </template>
190
+ </a-alert>
191
+ <a-checkbox
192
+ v-model="agreement"
193
+ side="right"
194
+ >
195
+ <i18n-t keypath="register.form.agreement.text">
196
+ <template #link>
197
+ <nuxt-link-locale
198
+ class="text-blue-700"
199
+ @click="getUrl"
200
+ >
201
+ {{ t('register.form.agreement.link') }}
202
+ </nuxt-link-locale>
203
+ </template>
204
+ </i18n-t>
205
+ </a-checkbox>
206
+ <a-button
207
+ :disabled="!agreement"
208
+ :loading="loading"
209
+ >
210
+ {{ t('register.form.continue') }}
211
+ </a-button>
212
+ <p class="text-center text-sm">
213
+ {{ t('register.form.haveAcc') }}
214
+ </p>
215
+ <a-button
216
+ type="button"
217
+ view="outline"
218
+ :to="localePath('/')"
219
+ >
220
+ {{ t('register.form.enter') }}
221
+ </a-button>
222
+ </form>
223
+ <a-modal v-model="isAcceptModal">
224
+ <accept
225
+ v-if="isAcceptModal"
226
+ @back="goBack"
227
+ @repeated="onResend"
228
+ />
229
+ </a-modal>
230
+ </template>
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ const emit = defineEmits<{
3
+ (e: 'resend'): void
4
+ (e: 'close'): void
5
+ }>()
6
+ </script>
7
+
8
+ <template>
9
+ <div class="flex flex-col justify-center items-center gap-5">
10
+ <p class="heading-02">
11
+ {{ $t('login.modal.title') }}
12
+ </p>
13
+ <a-ill-mail />
14
+ <p class="body-400 text-center">
15
+ {{ $t('login.modal.subtitle1') }}
16
+ </p>
17
+ <p class="body-400 text-center">
18
+ {{ $t('login.modal.confirmationEmail') }}
19
+ </p>
20
+ <div class="flex flex-col gap-2 w-full">
21
+ <a-button
22
+ class="w-full"
23
+ view="outline"
24
+ @click="emit('close')"
25
+ >
26
+ {{ $t('login.modal.back') }}
27
+ </a-button>
28
+ <a-button
29
+ class="w-full"
30
+ @click="emit('resend')"
31
+ >
32
+ {{ $t('login.modal.resend') }}
33
+ </a-button>
34
+ </div>
35
+ </div>
36
+ </template>
37
+
38
+ <style scoped>
39
+
40
+ </style>
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+
3
+ defineEmits<{ (e: 'back'): void, (e: 'repeated'): void }>()
4
+
5
+ const timer = ref(60)
6
+
7
+ function runTimer() {
8
+ const intervalId = setInterval(() => {
9
+ if (!timer.value) clearInterval(intervalId)
10
+ return timer.value--
11
+ }, 1000)
12
+ }
13
+
14
+ onMounted(() => runTimer())
15
+ </script>
16
+
17
+ <template>
18
+ <section class="flex flex-col items-stretch gap-4 py-4 py-8 text-center md:px-8">
19
+ <h2 class="text-2xl font-bold">
20
+ {{ $t('register.modal.title') }}
21
+ </h2>
22
+ <span class="mx-auto">
23
+ <a-ill-mail/>
24
+ </span>
25
+ <p class="text-sm">
26
+ {{ $t('register.modal.subtitle1') }}
27
+ </p>
28
+ <p class="text-sm">
29
+ {{ $t('register.modal.subtitle2') }}
30
+ </p>
31
+ <div v-if="timer > 0" class="text-2xl font-bold">
32
+ {{ timer }} {{ $t('register.modal.seconds') }}
33
+ </div>
34
+ <button
35
+ v-else
36
+ class="text-sm text-blue-500"
37
+ @click="$emit('repeated')"
38
+ >
39
+ {{ $t('register.modal.resend') }}
40
+ </button>
41
+ <a-button @click="$emit('back')">
42
+ {{ $t('register.modal.back') }}
43
+ </a-button>
44
+ </section>
45
+ </template>