adata-ui 2.1.41-beta.11 → 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.
@@ -41,17 +41,17 @@ defineProps<Props>()
41
41
  />
42
42
 
43
43
  <!-- нет данных -->
44
- <span
45
- class="absolute -bottom-1.5 -right-1.5 grid size-[26px] place-items-center rounded-full border border-default bg-main text-icon-default shadow-sm"
46
- >
47
- <empty-ban class="size-3.5" />
48
- </span>
44
+ <!-- <span-->
45
+ <!-- class="absolute -bottom-1.5 -right-1.5 grid size-[26px] place-items-center rounded-full border border-default bg-main text-icon-default shadow-sm"-->
46
+ <!-- >-->
47
+ <!-- <empty-ban class="size-3.5" />-->
48
+ <!-- </span>-->
49
49
  </div>
50
50
  </div>
51
51
 
52
52
  <!-- текст -->
53
53
  <div class="flex flex-col items-center gap-2">
54
- <h3 class="font-semibold text-primary">
54
+ <h3 class=" text-primary">
55
55
  {{ title }}
56
56
  </h3>
57
57
 
@@ -397,7 +397,9 @@ function rowDisplay(item: T): unknown {
397
397
  :class="iconOffsetClass"
398
398
  />
399
399
 
400
- <slot name="endButton" />
400
+ <!-- iconOffsetClass отдаётся наружу: обёртки (AInputPasswordV2) должны
401
+ центрировать свою кнопку так же, как встроенные иконки поля -->
402
+ <slot name="endButton" :icon-offset-class="iconOffsetClass" />
401
403
  </div>
402
404
 
403
405
  <teleport to="body">
@@ -470,6 +472,24 @@ function rowDisplay(item: T): unknown {
470
472
  cursor: not-allowed;
471
473
  }
472
474
 
475
+ /* Chrome красит autofill-фоном сам <input>, а видимое поле в v2 — это обёртка
476
+ .input-v2__field, поэтому родная подсветка ложилась узкой полосой внутри поля
477
+ (в v1 фон нёс сам input, и подсветка совпадала с полем). Родной фон иначе не
478
+ убрать — прячем его бесконечной задержкой перехода, а подсвечиваем всю обёртку. */
479
+ .input-v2__input:-webkit-autofill,
480
+ .input-v2__input:-webkit-autofill:hover,
481
+ .input-v2__input:-webkit-autofill:focus,
482
+ .input-v2__input:-webkit-autofill:active {
483
+ -webkit-text-fill-color: var(--text-color-primary);
484
+ caret-color: var(--text-color-primary);
485
+ transition: background-color 600000s 0s;
486
+ }
487
+
488
+ .input-v2__field:has(.input-v2__input:-webkit-autofill),
489
+ .input-v2__field:has(.input-v2__input:autofill) {
490
+ background-color: var(--background-color-brand-subtle);
491
+ }
492
+
473
493
  .input-v2__label {
474
494
  font-size: 14px;
475
495
  line-height: 1.3;
@@ -0,0 +1,72 @@
1
+ <script lang="ts" setup>
2
+ import type { Component } from 'vue'
3
+
4
+ interface Props {
5
+ label?: string
6
+ placeholder?: string
7
+ error?: string | string[]
8
+ size?: 'sm' | 'md' | 'lg'
9
+ variant?: 'default' | 'gray' | 'blue' | 'canvas'
10
+ required?: boolean
11
+ disabled?: boolean
12
+ readonly?: boolean
13
+ loading?: boolean
14
+ startIcon?: Component
15
+ startIconClass?: string
16
+ autocomplete?: string
17
+ inputId?: string
18
+ iconEyeTestId?: string
19
+ }
20
+
21
+ withDefaults(defineProps<Props>(), {
22
+ size: 'sm',
23
+ variant: 'default',
24
+ required: false,
25
+ disabled: false,
26
+ readonly: false,
27
+ loading: false,
28
+ autocomplete: 'current-password',
29
+ })
30
+
31
+ const modelValue = defineModel<string | number>({ default: '' })
32
+
33
+ const isOpened = ref(false)
34
+ </script>
35
+
36
+ <template>
37
+ <a-input-v2
38
+ v-model="modelValue"
39
+ :type="isOpened ? 'text' : 'password'"
40
+ :label="label"
41
+ :placeholder="placeholder"
42
+ :error="error"
43
+ :size="size"
44
+ :variant="variant"
45
+ :required="required"
46
+ :disabled="disabled"
47
+ :readonly="readonly"
48
+ :loading="loading"
49
+ :start-icon="startIcon"
50
+ :start-icon-class="startIconClass"
51
+ :autocomplete="autocomplete"
52
+ :input-id="inputId"
53
+ >
54
+ <template #endButton="{ iconOffsetClass }">
55
+ <button
56
+ type="button"
57
+ tabindex="-1"
58
+ class="flex shrink-0 items-center justify-center rounded-full p-0.5 text-icon-secondary transition-colors hover:bg-badge disabled:pointer-events-none"
59
+ :class="iconOffsetClass"
60
+ :disabled="disabled"
61
+ :data-test-id="iconEyeTestId"
62
+ :aria-label="isOpened ? $t('actions.hidePassword') : $t('actions.showPassword')"
63
+ :aria-pressed="isOpened"
64
+ @mousedown.prevent
65
+ @click="isOpened = !isOpened"
66
+ >
67
+ <a-icon-eye-closed v-if="!isOpened" class="size-4" />
68
+ <a-icon-eye-opened v-else class="size-4" />
69
+ </button>
70
+ </template>
71
+ </a-input-v2>
72
+ </template>
@@ -0,0 +1,50 @@
1
+ <script lang="ts" setup>
2
+ import { PASSWORD_MIN_LENGTH, PASSWORD_RULES } from '#adata-ui/utils/passwordRules'
3
+
4
+ const props = withDefaults(defineProps<{
5
+ password?: string
6
+ requireSpecial?: boolean
7
+ }>(), {
8
+ password: '',
9
+ requireSpecial: true,
10
+ })
11
+
12
+ const { t } = useI18n()
13
+
14
+ const rules = computed(() =>
15
+ PASSWORD_RULES
16
+ .filter(rule => props.requireSpecial || rule.key !== 'special')
17
+ .map(rule => ({
18
+ key: rule.key,
19
+ ok: rule.test(props.password),
20
+ label: t(`forms.password_requirements.${rule.key}`, { length: PASSWORD_MIN_LENGTH }),
21
+ })),
22
+ )
23
+ </script>
24
+
25
+ <template>
26
+ <ul class="flex flex-col gap-2 rounded-lg bg-muted px-4 py-3">
27
+ <li
28
+ v-for="rule in rules"
29
+ :key="rule.key"
30
+ class="flex items-center gap-2"
31
+ >
32
+ <a-icon-check
33
+ v-if="rule.ok"
34
+ class="size-4 shrink-0 text-icon-success"
35
+ aria-hidden="true"
36
+ />
37
+ <span
38
+ v-else
39
+ class="flex size-4 shrink-0 items-center justify-center rounded-full bg-badge"
40
+ aria-hidden="true"
41
+ >
42
+ <span class="size-1.5 rounded-full bg-(--color-icon-secondary)" />
43
+ </span>
44
+ <span
45
+ class="text-body-md transition-colors"
46
+ :class="rule.ok ? 'text-primary' : 'text-secondary'"
47
+ >{{ rule.label }}</span>
48
+ </li>
49
+ </ul>
50
+ </template>
@@ -2,6 +2,7 @@
2
2
  import IdOtpInput from '#adata-ui/components/modals/id/IdOtpInput.vue'
3
3
  import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
4
4
  import { writeAccessTokenCookie } from '#adata-ui/composables/useAccessTokenCookie'
5
+ import { apiErrorMessage } from '#adata-ui/utils/apiErrorMessage'
5
6
 
6
7
  const { $toast } = useNuxtApp()
7
8
  const { t, locale } = useI18n()
@@ -104,10 +105,13 @@ async function onResend() {
104
105
  })
105
106
  resetOtp()
106
107
  runTimer()
107
- isResendLoading.value = false
108
108
  }
109
109
  catch (error) {
110
- $toast.error(error.data)
110
+ $toast.error(apiErrorMessage(error, t('error.validation')))
111
+ }
112
+ finally {
113
+ // без finally сбой resend оставлял кнопку заблокированной навсегда
114
+ isResendLoading.value = false
111
115
  }
112
116
  }
113
117
 
@@ -121,25 +125,14 @@ function runTimer() {
121
125
  }, 1000)
122
126
  }
123
127
 
124
- function handleEnter(e: KeyboardEvent) {
125
- if (e.key === 'Enter') {
126
- onConfirm()
127
- }
128
- }
129
-
130
128
  onMounted(() => {
131
129
  runTimer()
132
- document.addEventListener('keyup', handleEnter)
133
- })
134
-
135
- onBeforeUnmount(() => {
136
- document.removeEventListener('keyup', handleEnter)
137
130
  })
138
131
  </script>
139
132
 
140
133
  <template>
141
- <div class="flex flex-col items-center gap-4 text-center">
142
- <h2 class="text-h2">
134
+ <div class="flex flex-col items-center gap-4 p-6 text-center">
135
+ <h2 class="text-h2 text-primary">
143
136
  {{ t('modals.id.confirmAccount.title') }}
144
137
  </h2>
145
138
 
@@ -147,21 +140,23 @@ onBeforeUnmount(() => {
147
140
  <a-icon-hand-with-phone-light class="size-32 dark:hidden" />
148
141
  <a-icon-hand-with-phone-dark class="hidden size-32 dark:block" />
149
142
 
150
- <div class="text-body-md">
143
+ <div class="text-body-md text-secondary">
151
144
  <p class="mb-1">
152
145
  {{ t('modals.id.resetPasswordOtp.content') }}
153
146
  </p>
154
147
 
155
- <div v-if="timer > 0" class="text-h2">
148
+ <div v-if="timer > 0" class="text-h2 text-primary">
156
149
  {{ timer }} {{ t('modals.id.resetPasswordOtp.seconds') }}
157
150
  </div>
158
151
  <button
159
152
  v-else
160
- class="text-brand"
153
+ type="button"
154
+ class="mx-auto flex items-center gap-2 text-body-md text-link transition-colors hover:text-link-hover"
161
155
  :disabled="isResendLoading"
162
156
  @click="onResend"
163
157
  >
164
- {{ t('actions.resend') }}
158
+ <a-icon-loader-circle v-if="isResendLoading" class="size-4 animate-spin" />
159
+ <span>{{ t('actions.resend') }}</span>
165
160
  </button>
166
161
  </div>
167
162
 
@@ -171,12 +166,14 @@ onBeforeUnmount(() => {
171
166
  @on-completed="onConfirm"
172
167
  />
173
168
 
174
- <a-button
175
- block
169
+ <a-button-v2
176
170
  view="outline"
171
+ color="gray"
172
+ size="lg"
173
+ block
177
174
  @click="onClose"
178
175
  >
179
176
  {{ t('actions.close') }}
180
- </a-button>
177
+ </a-button-v2>
181
178
  </div>
182
179
  </template>
@@ -3,39 +3,42 @@ import { useUrls } from '#adata-ui/composables/useUrls'
3
3
 
4
4
  const { t } = useI18n()
5
5
  const { confirmSuccessfulModal } = useIdModals()
6
- const regex = /^\/counterparty\/main\/company\/\d+\/basic-info$/;
6
+ const regex = /^\/counterparty\/main\/company\/\d+\/basic-info$/
7
7
  const { pk } = useUrls()
8
8
 
9
9
  function onClose() {
10
10
  confirmSuccessfulModal.value = false
11
11
 
12
- const path = window.location.pathname;
12
+ const path = window.location.pathname
13
13
 
14
14
  if (regex.test(path)) {
15
- navigateTo(`${pk}/${path.split("/")[4]}`, { external: true })
16
- } else {
15
+ navigateTo(`${pk}/${path.split('/')[4]}`, { external: true })
16
+ }
17
+ else {
17
18
  window.location.reload()
18
19
  }
19
20
  }
20
21
  </script>
21
22
 
22
23
  <template>
23
- <div class="flex flex-col gap-4 text-center">
24
- <h2 class="text-h2">
24
+ <div class="flex flex-col gap-4 p-6 text-center">
25
+ <h2 class="text-h2 text-primary">
25
26
  {{ t('modals.id.confirmSuccessful.title') }}
26
27
  </h2>
27
- <span class="mx-auto size-[100px] rounded-full bg-muted">
28
+ <div class="mx-auto flex size-24 items-center justify-center rounded-full bg-muted">
28
29
  <a-ill-ok />
29
- </span>
30
- <p class="text-body-md">
30
+ </div>
31
+ <p class="text-body-md text-secondary">
31
32
  {{ t('modals.id.confirmSuccessful.content') }}
32
33
  </p>
33
- <a-button view="outline" @click="onClose">
34
+ <a-button-v2
35
+ size="lg"
36
+ block
37
+ view="outline"
38
+ color="gray"
39
+ @click="onClose"
40
+ >
34
41
  {{ t('actions.close') }}
35
- </a-button>
42
+ </a-button-v2>
36
43
  </div>
37
44
  </template>
38
-
39
- <style scoped>
40
-
41
- </style>
@@ -1,9 +1,15 @@
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
+ import { useUrls } from '#adata-ui/composables/useUrls'
5
+ import AIconArrowUpRight from '#adata-ui/icons/arrow/arrow-up-right.vue'
6
+ import AIconGoogle from '#adata-ui/icons/google.vue'
7
+ import AIconLock from '#adata-ui/icons/lock.vue'
8
+ import AIconMail from '#adata-ui/icons/mail.vue'
9
+ import AIconMailru from '#adata-ui/icons/mailru.vue'
10
+ import AIconYandex from '#adata-ui/icons/yandex.vue'
4
11
  import { navigateToLocalizedPage } from '#adata-ui/utils/localizedNavigation'
5
12
  import * as z from 'zod'
6
- import { useUrls } from '#adata-ui/composables/useUrls'
7
13
 
8
14
  const { $toast } = useNuxtApp()
9
15
  const { commonAuth } = useAppConfig()
@@ -25,11 +31,28 @@ const authApiURL = commonAuth.authApiURL
25
31
  const submitted = ref(false)
26
32
  const accessToken = ref(null)
27
33
 
34
+ const uid = useId()
35
+ const emailFieldId = `login-email-${uid}`
36
+ const passwordFieldId = `login-password-${uid}`
37
+
38
+ const socials = [
39
+ { id: 'google', label: 'Google', icon: AIconGoogle },
40
+ { id: 'yandex', label: 'Yandex', icon: AIconYandex },
41
+ { id: 'mailru', label: 'Mail.ru', icon: AIconMailru },
42
+ ]
43
+
28
44
  export interface ILoginForm {
29
45
  username: string
30
46
  password: string
31
47
  }
32
48
 
49
+ const loading = ref(false)
50
+ const form: ILoginForm = reactive({
51
+ username: useCookie('username', { domain: COOKIE_DOMAIN }).value || '',
52
+ password: useCookie('password', { domain: COOKIE_DOMAIN }).value || '',
53
+ })
54
+ const rememberMe = ref(false)
55
+
33
56
  const loginSchema = z.object({
34
57
  username: z.string().nonempty(t('error.required')).email(t('error.email')),
35
58
  password: z.string().nonempty(t('error.required')),
@@ -61,13 +84,6 @@ function clearAuthCookie() {
61
84
  useCookie('password', options).value = null
62
85
  }
63
86
 
64
- const loading = ref(false)
65
- const form: ILoginForm = reactive({
66
- username: useCookie('username', { domain: COOKIE_DOMAIN }).value || '',
67
- password: useCookie('password', { domain: COOKIE_DOMAIN }).value || '',
68
- })
69
- const rememberMe = ref(false)
70
-
71
87
  async function submit() {
72
88
  submitted.value = true
73
89
  if (!validation.value) {
@@ -146,7 +162,8 @@ async function submit() {
146
162
  const companyId = match[1]
147
163
 
148
164
  navigateTo(`${pk}/company/${companyId}`, { external: true })
149
- }else if (redirectUrl) {
165
+ }
166
+ else if (redirectUrl) {
150
167
  navigateTo(redirectUrl as string, { external: true })
151
168
  }
152
169
  else {
@@ -173,7 +190,7 @@ async function submit() {
173
190
  }
174
191
 
175
192
  function authWithSocial(social: string) {
176
- const url = new URL(`https://auth.${mode}.kz/api/login/social`)
193
+ const url = new URL(`${removeTrailingSlash(authApiURL)}/login/social`)
177
194
 
178
195
  url.searchParams.set('source', social)
179
196
  url.searchParams.set('redirect_url', window.location.origin)
@@ -201,63 +218,74 @@ onMounted(() => {
201
218
  rememberMe.value = !!useCookie('username', options).value && !!useCookie('password', options).value
202
219
  })
203
220
 
204
- function handleEnter(e: KeyboardEvent) {
205
- if (e.key === 'Enter') {
206
- submit()
207
- }
208
- }
209
-
210
221
  function onForgotPassword() {
211
222
  loginModal.value = false
212
223
  recoveryModal.value = true
213
224
  }
214
-
215
- onMounted(() => {
216
- document.addEventListener('keyup', handleEnter)
217
- })
218
-
219
- onBeforeUnmount(() => {
220
- document.removeEventListener('keyup', handleEnter)
221
- })
222
225
  </script>
223
226
 
224
227
  <template>
225
- <div class="flex flex-col gap-5">
226
- <h1 class="text-h2 text-center" data-test-id="login-form-title">
227
- {{ t('modals.id.login.title') }}
228
- </h1>
229
- <p class="text-body-md text-center" data-test-id="login-form-subtitle">
230
- {{ t('modals.id.login.subtitle') }}
231
- </p>
228
+ <!-- Enter отправляет форму нативно: документного keyup-слушателя здесь быть не должно,
229
+ иначе один Enter даёт два запроса -->
230
+ <form
231
+ class="flex flex-col gap-6 p-6"
232
+ novalidate
233
+ @submit.prevent="submit"
234
+ >
235
+ <div class="flex flex-col gap-1">
236
+ <h1 class="text-center text-h2 text-primary" data-test-id="login-form-title">
237
+ {{ t('modals.id.login.title') }}
238
+ </h1>
239
+ <p class="text-center text-body-md text-secondary" data-test-id="login-form-subtitle">
240
+ {{ t('modals.id.login.subtitle') }}
241
+ </p>
242
+ </div>
243
+
232
244
  <div class="flex flex-col gap-4">
233
- <a-input-standard
234
- v-model="form.username"
235
- type="email"
236
- :data-test-id="'login-form-email-input'"
237
- :label="t('modals.id.login.labels.email')"
238
- :error="getError('username')"
239
- />
240
- <a-input-password
241
- v-model="form.password"
242
- :data-test-id="'login-form-password-input'"
243
- :label="t('modals.id.login.labels.password')"
244
- :error="getError('password')"
245
- />
246
- <div class="flex items-center justify-between">
247
- <div class="text-body-md flex gap-2" data-test-id="login-form-remember-me-checkbox">
248
- <a-checkbox
249
- v-model="rememberMe"
250
- name="remember_me"
251
- />
252
- <label
253
- for="remember_me"
254
- class="cursor-pointer"
255
- >{{
256
- t('modals.id.login.remember_me')
257
- }}</label>
258
- </div>
245
+ <div class="flex flex-col gap-1.5">
246
+ <label :for="emailFieldId" class="text-body-md-strong text-primary">
247
+ {{ t('modals.id.login.labels.email') }}
248
+ </label>
249
+ <a-input-v2
250
+ v-model="form.username"
251
+ :input-id="emailFieldId"
252
+ type="email"
253
+ size="lg"
254
+ autocomplete="email"
255
+ :start-icon="AIconMail"
256
+ :placeholder="t('modals.id.login.placeholders.email')"
257
+ :error="getError('username')"
258
+ data-test-id="login-form-email-input"
259
+ />
260
+ </div>
261
+
262
+ <div class="flex flex-col gap-1.5">
263
+ <label :for="passwordFieldId" class="text-body-md-strong text-primary">
264
+ {{ t('modals.id.login.labels.password') }}
265
+ </label>
266
+ <a-input-password-v2
267
+ v-model="form.password"
268
+ :input-id="passwordFieldId"
269
+ size="lg"
270
+ :start-icon="AIconLock"
271
+ :placeholder="t('modals.id.login.placeholders.password')"
272
+ :error="getError('password')"
273
+ data-test-id="login-form-password-input"
274
+ icon-eye-test-id="login-form-password-eye"
275
+ />
276
+ </div>
277
+
278
+ <div class="flex items-center justify-between gap-3">
279
+ <a-checkbox-v2
280
+ v-model="rememberMe"
281
+ side="right"
282
+ name="remember_me"
283
+ :label="t('modals.id.login.remember_me')"
284
+ data-test-id="login-form-remember-me-checkbox"
285
+ />
259
286
  <button
260
- class="text-body-md text-link"
287
+ type="button"
288
+ class="text-body-md text-link transition-colors hover:text-link-hover"
261
289
  data-test-id="login-form-forgot-password-button"
262
290
  @click="onForgotPassword"
263
291
  >
@@ -265,72 +293,75 @@ onBeforeUnmount(() => {
265
293
  </button>
266
294
  </div>
267
295
  </div>
268
- <div class="flex items-center gap-4">
269
- <div class="h-px w-full bg-(--border-color-default)" />
270
- <div class="flex shrink-0 gap-4">
271
- <span
272
- class="cursor-pointer"
273
- data-test-id="login-form-google-button"
274
- @click="authWithSocial('google')"
275
- >
276
- <a-icon-google />
277
- </span>
278
- <span
279
- class="cursor-pointer"
280
- data-test-id="login-form-yandex-button"
281
- @click="authWithSocial('yandex')"
282
- >
283
- <a-icon-yandex />
284
- </span>
285
- <span
286
- class="cursor-pointer"
287
- data-test-id="login-form-mailru-button"
288
- @click="authWithSocial('mailru')"
289
- >
290
- <a-icon-mailru />
291
- </span>
292
- </div>
293
- <div class="h-px w-full bg-(--border-color-default)" />
294
- </div>
295
- <a-button
296
- :loading="loading"
296
+
297
+ <a-button-v2
297
298
  type="submit"
299
+ size="lg"
300
+ block
301
+ :loading="loading"
298
302
  data-test-id="login-form-login-button"
299
- @click="submit"
300
303
  >
301
304
  {{ t('actions.login') }}
302
- </a-button>
303
- <p class="text-body-md text-center">
304
- {{ t('modals.id.login.first_time') }}
305
- </p>
306
-
307
- <a-button
308
- type="button"
309
- view="outline"
310
- class="w-full"
311
- data-test-id="login-form-register-button"
312
- @click="onRegister"
313
- >
314
- {{ t('actions.register') }}
315
- </a-button>
316
-
317
- <a-button
318
- type="button"
319
- view="transparent"
320
- class="w-full"
321
- data-test-id="login-form-tariffs-button"
322
- @click="toTariffs"
323
- >
324
- {{ t('actions.toTariffs') }}
325
- </a-button>
305
+ </a-button-v2>
306
+
307
+ <div class="flex items-center gap-3">
308
+ <span class="h-px flex-1 bg-(--border-color-default)" />
309
+ <span class="shrink-0 text-body-sm text-secondary">
310
+ {{ t('modals.id.login.social_divider') }}
311
+ </span>
312
+ <span class="h-px flex-1 bg-(--border-color-default)" />
313
+ </div>
326
314
 
327
- <a-alert
328
- class="max-w-screen-sm !text-body-sm"
329
- size="xs"
330
- >
331
- {{ t('info.userAgreement') }}
332
- </a-alert>
333
- </div>
315
+ <div class="grid grid-cols-3 gap-2">
316
+ <a-button-v2
317
+ v-for="social in socials"
318
+ :key="social.id"
319
+ view="outline"
320
+ color="gray"
321
+ block
322
+ :icon="social.icon"
323
+ :data-test-id="`login-form-${social.id}-button`"
324
+ @click="authWithSocial(social.id)"
325
+ >
326
+ {{ social.label }}
327
+ </a-button-v2>
328
+ </div>
329
+
330
+ <div class="flex flex-col gap-4">
331
+ <span class="h-px w-full bg-(--border-color-default)" />
332
+ <p class="text-center text-body-md text-secondary">
333
+ {{ t('modals.id.login.first_time') }}
334
+ </p>
335
+ <a-button-v2
336
+ view="outline"
337
+ color="gray"
338
+ size="lg"
339
+ block
340
+ data-test-id="login-form-register-button"
341
+ @click="onRegister"
342
+ >
343
+ {{ t('actions.register') }}
344
+ </a-button-v2>
345
+ <a-button-v2
346
+ view="ghost"
347
+ color="blue"
348
+ block
349
+ icon-right
350
+ :icon="AIconArrowUpRight"
351
+ data-test-id="login-form-tariffs-button"
352
+ @click="toTariffs"
353
+ >
354
+ {{ t('actions.toTariffs') }}
355
+ </a-button-v2>
356
+ </div>
357
+
358
+ <div class="flex items-start gap-3 rounded-lg border-l-4 border-brand bg-brand-subtle px-4 py-3">
359
+ <a-icon-shield-check class="mt-0.5 size-5 shrink-0 text-icon-brand" />
360
+ <p class="text-body-sm text-primary">
361
+ {{ t('info.userAgreement') }}
362
+ </p>
363
+ </div>
364
+ </form>
334
365
  </template>
335
366
 
336
367
  <style scoped></style>