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.
- package/components/elements/empty-box/empty-box-V2.vue +6 -6
- package/components/forms/input/AInputV2.vue +21 -1
- package/components/forms/input/password/AInputPasswordV2.vue +72 -0
- package/components/forms/password/APasswordRequirements.vue +50 -0
- package/components/modals/id/IdConfirmAccountOtpModal.vue +19 -22
- package/components/modals/id/IdConfirmSuccessfulModal.vue +18 -15
- package/components/modals/id/IdLoginModal.vue +150 -119
- package/components/modals/id/IdModals.vue +66 -24
- package/components/modals/id/IdNewPasswordModal.vue +93 -52
- package/components/modals/id/IdOtpInput.vue +14 -14
- package/components/modals/id/IdPasswordSuccessfulModal.vue +11 -11
- package/components/modals/id/IdRecoveryModal.vue +57 -42
- package/components/modals/id/IdRegistrationModal.vue +136 -94
- package/components/modals/id/IdResetPasswordOtpModal.vue +19 -30
- package/components/modals/id/IdTwoFactorModal.vue +14 -29
- package/components/navigation/header/ProductMenu.vue +2 -50
- package/composables/useHeaderNavigationLinks.ts +16 -0
- package/icons/file/file-scan.vue +5 -0
- package/icons/google.vue +10 -32
- package/icons/mailru.vue +12 -27
- package/icons/yandex.vue +10 -20
- package/lang/en.ts +38 -8
- package/lang/kk.ts +38 -8
- package/lang/ru.ts +38 -8
- package/package.json +1 -1
- package/utils/apiErrorMessage.ts +22 -0
- package/utils/passwordRules.ts +19 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
|
|
3
|
+
import { useUrls } from '#adata-ui/composables/useUrls'
|
|
4
|
+
import AIconLock from '#adata-ui/icons/lock.vue'
|
|
5
|
+
import AIconMail from '#adata-ui/icons/mail.vue'
|
|
3
6
|
import { PAGES } from '#adata-ui/shared/constans/pages'
|
|
7
|
+
import { apiErrorMessage } from '#adata-ui/utils/apiErrorMessage'
|
|
8
|
+
import { PASSWORD_MIN_LENGTH, PASSWORD_RULES } from '#adata-ui/utils/passwordRules'
|
|
4
9
|
import * as z from 'zod'
|
|
5
|
-
import { useUrls } from '#adata-ui/composables/useUrls'
|
|
6
10
|
|
|
7
11
|
const { $toast } = useNuxtApp()
|
|
8
12
|
const route = useRoute()
|
|
@@ -30,18 +34,28 @@ const form = reactive<RegistrationForm>({
|
|
|
30
34
|
const agreement = ref(false)
|
|
31
35
|
const loading = ref(false)
|
|
32
36
|
|
|
37
|
+
const uid = useId()
|
|
38
|
+
const emailFieldId = `register-email-${uid}`
|
|
39
|
+
const passwordFieldId = `register-password-${uid}`
|
|
40
|
+
const confirmationFieldId = `register-password-confirmation-${uid}`
|
|
41
|
+
const agreementLabelId = `register-agreement-${uid}`
|
|
42
|
+
|
|
43
|
+
/* Правила берутся из PASSWORD_RULES — того же источника, что и чеклист
|
|
44
|
+
<a-password-requirements>, поэтому валидация и подсказки не расходятся. */
|
|
45
|
+
function passwordField() {
|
|
46
|
+
return PASSWORD_RULES.reduce(
|
|
47
|
+
(schema: z.ZodType<string>, rule) => schema.refine(
|
|
48
|
+
rule.test,
|
|
49
|
+
t(`modals.id.register.errors.${rule.key}`, { length: PASSWORD_MIN_LENGTH }),
|
|
50
|
+
),
|
|
51
|
+
z.string().nonempty(t('error.required')) as z.ZodType<string>,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
33
55
|
const registerSchema = z.object({
|
|
34
56
|
email: z.string().nonempty(t('error.required')).email(t('error.email')),
|
|
35
|
-
password:
|
|
36
|
-
|
|
37
|
-
.nonempty(t('error.required'))
|
|
38
|
-
.min(8, t('modals.id.register.errors.low_security', { length: 8 }))
|
|
39
|
-
.regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
|
|
40
|
-
password_confirmation: z
|
|
41
|
-
.string()
|
|
42
|
-
.nonempty(t('error.required'))
|
|
43
|
-
.min(8, t('modals.id.register.errors.low_security', { length: 8 }))
|
|
44
|
-
.regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
|
|
57
|
+
password: passwordField(),
|
|
58
|
+
password_confirmation: passwordField(),
|
|
45
59
|
}).refine(
|
|
46
60
|
data => data.password === data.password_confirmation,
|
|
47
61
|
{
|
|
@@ -71,7 +85,7 @@ async function onSubmit() {
|
|
|
71
85
|
|
|
72
86
|
try {
|
|
73
87
|
loading.value = true
|
|
74
|
-
const { success
|
|
88
|
+
const { success } = await $fetch(`${removeTrailingSlash(authApiURL)}/v2/register`, {
|
|
75
89
|
method: 'POST',
|
|
76
90
|
credentials: 'include',
|
|
77
91
|
headers: {
|
|
@@ -88,7 +102,7 @@ async function onSubmit() {
|
|
|
88
102
|
}
|
|
89
103
|
}
|
|
90
104
|
catch (error) {
|
|
91
|
-
$toast.error(error
|
|
105
|
+
$toast.error(apiErrorMessage(error, t('error.validation')))
|
|
92
106
|
}
|
|
93
107
|
finally {
|
|
94
108
|
loading.value = false
|
|
@@ -99,10 +113,6 @@ if (route.query.url) {
|
|
|
99
113
|
redirectUrl.value = route.query.url as string
|
|
100
114
|
}
|
|
101
115
|
|
|
102
|
-
// onMounted(() => {
|
|
103
|
-
// v$.value.$touch();
|
|
104
|
-
// });
|
|
105
|
-
|
|
106
116
|
function getUrl() {
|
|
107
117
|
return navigateTo(locale.value !== 'ru'
|
|
108
118
|
? landing + localePath('/') + PAGES.userAgreement
|
|
@@ -114,12 +124,6 @@ function getUrl() {
|
|
|
114
124
|
})
|
|
115
125
|
}
|
|
116
126
|
|
|
117
|
-
function handleEnter(event: KeyboardEvent) {
|
|
118
|
-
if (event.key === 'Enter') {
|
|
119
|
-
onSubmit()
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
127
|
function onLogin() {
|
|
124
128
|
registrationModal.value = false
|
|
125
129
|
loginModal.value = true
|
|
@@ -133,83 +137,121 @@ watch(registrationModal, (value) => {
|
|
|
133
137
|
agreement.value = false
|
|
134
138
|
}
|
|
135
139
|
})
|
|
136
|
-
|
|
137
|
-
onMounted(() => {
|
|
138
|
-
document.addEventListener('keyup', handleEnter)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
onBeforeUnmount(() => {
|
|
142
|
-
document.removeEventListener('keyup', handleEnter)
|
|
143
|
-
})
|
|
144
140
|
</script>
|
|
145
141
|
|
|
146
142
|
<template>
|
|
143
|
+
<!-- Enter отправляет форму нативно: документного keyup-слушателя здесь быть не должно,
|
|
144
|
+
иначе один Enter даёт два запроса -->
|
|
147
145
|
<form
|
|
148
|
-
class="flex flex-col
|
|
146
|
+
class="flex flex-col gap-6 p-6"
|
|
149
147
|
novalidate
|
|
150
148
|
@submit.prevent="onSubmit"
|
|
151
149
|
>
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
150
|
+
<div class="flex flex-col gap-1">
|
|
151
|
+
<h2 class="text-center text-h2 text-primary">
|
|
152
|
+
{{ t('modals.id.register.title') }}
|
|
153
|
+
</h2>
|
|
154
|
+
<p class="text-center text-body-md text-secondary">
|
|
155
|
+
{{ t('modals.id.register.subtitle') }}
|
|
156
|
+
</p>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="flex flex-col gap-4">
|
|
160
|
+
<div class="flex flex-col gap-1.5">
|
|
161
|
+
<label :for="emailFieldId" class="text-body-md-strong text-primary">
|
|
162
|
+
{{ t('modals.id.register.labels.email') }}
|
|
163
|
+
</label>
|
|
164
|
+
<a-input-v2
|
|
165
|
+
v-model="form.email"
|
|
166
|
+
:input-id="emailFieldId"
|
|
167
|
+
type="email"
|
|
168
|
+
size="lg"
|
|
169
|
+
autocomplete="email"
|
|
170
|
+
:start-icon="AIconMail"
|
|
171
|
+
:placeholder="t('modals.id.register.placeholders.email')"
|
|
172
|
+
:error="getError('email')"
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="flex flex-col gap-1.5">
|
|
177
|
+
<label :for="passwordFieldId" class="text-body-md-strong text-primary">
|
|
178
|
+
{{ t('modals.id.register.labels.password') }}
|
|
179
|
+
</label>
|
|
180
|
+
<a-input-password-v2
|
|
181
|
+
v-model="form.password"
|
|
182
|
+
:input-id="passwordFieldId"
|
|
183
|
+
size="lg"
|
|
184
|
+
autocomplete="new-password"
|
|
185
|
+
:start-icon="AIconLock"
|
|
186
|
+
:placeholder="t('modals.id.register.placeholders.password')"
|
|
187
|
+
:error="getError('password')"
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<a-password-requirements :password="form.password" />
|
|
192
|
+
|
|
193
|
+
<div class="flex flex-col gap-1.5">
|
|
194
|
+
<label :for="confirmationFieldId" class="text-body-md-strong text-primary">
|
|
195
|
+
{{ t('modals.id.register.labels.password_confirmation') }}
|
|
196
|
+
</label>
|
|
197
|
+
<a-input-password-v2
|
|
198
|
+
v-model="form.password_confirmation"
|
|
199
|
+
:input-id="confirmationFieldId"
|
|
200
|
+
size="lg"
|
|
201
|
+
autocomplete="new-password"
|
|
202
|
+
:start-icon="AIconLock"
|
|
203
|
+
:placeholder="t('modals.id.register.placeholders.password_confirmation')"
|
|
204
|
+
:error="getError('password_confirmation')"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<!-- Чекбокс и текст рядом, а не вложенно: корень ACheckboxV2 — <button>, а ссылка
|
|
209
|
+
внутри кнопки невалидна; подпись привязана через aria-labelledby -->
|
|
210
|
+
<div class="flex items-start gap-2">
|
|
211
|
+
<a-checkbox-v2
|
|
212
|
+
v-model="agreement"
|
|
213
|
+
name="confirm-agreement"
|
|
214
|
+
:aria-labelledby="agreementLabelId"
|
|
215
|
+
/>
|
|
216
|
+
<p :id="agreementLabelId" class="text-body-md text-primary">
|
|
217
|
+
<i18n-t keypath="modals.id.register.agreement.text">
|
|
218
|
+
<template #link>
|
|
219
|
+
<nuxt-link-locale
|
|
220
|
+
class="text-link underline transition-colors hover:text-link-hover"
|
|
221
|
+
@click="getUrl"
|
|
222
|
+
>
|
|
223
|
+
{{ t('modals.id.register.agreement.link') }}
|
|
224
|
+
</nuxt-link-locale>
|
|
225
|
+
</template>
|
|
226
|
+
</i18n-t>
|
|
227
|
+
</p>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<a-button-v2
|
|
231
|
+
type="submit"
|
|
232
|
+
size="lg"
|
|
233
|
+
block
|
|
234
|
+
:disabled="!agreement"
|
|
235
|
+
:loading="loading"
|
|
236
|
+
>
|
|
237
|
+
{{ t('modals.id.register.continue') }}
|
|
238
|
+
</a-button-v2>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="flex flex-col gap-4">
|
|
242
|
+
<span class="h-px w-full bg-(--border-color-default)" />
|
|
243
|
+
<p class="text-center text-body-md text-secondary">
|
|
244
|
+
{{ t('modals.id.register.haveAcc') }}
|
|
245
|
+
</p>
|
|
246
|
+
<a-button-v2
|
|
247
|
+
view="outline"
|
|
248
|
+
color="gray"
|
|
249
|
+
size="lg"
|
|
250
|
+
block
|
|
251
|
+
@click="onLogin"
|
|
252
|
+
>
|
|
253
|
+
{{ t('modals.id.register.enter') }}
|
|
254
|
+
</a-button-v2>
|
|
255
|
+
</div>
|
|
214
256
|
</form>
|
|
215
257
|
</template>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
|
|
3
2
|
import IdOtpInput from '#adata-ui/components/modals/id/IdOtpInput.vue'
|
|
3
|
+
import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
|
|
4
|
+
import { apiErrorMessage } from '#adata-ui/utils/apiErrorMessage'
|
|
4
5
|
|
|
5
6
|
const { $toast } = useNuxtApp()
|
|
6
7
|
const { t, locale } = useI18n()
|
|
@@ -73,16 +74,13 @@ async function onResend() {
|
|
|
73
74
|
})
|
|
74
75
|
resetOtp()
|
|
75
76
|
runTimer()
|
|
76
|
-
isResendLoading.value = false
|
|
77
77
|
}
|
|
78
78
|
catch (error) {
|
|
79
|
-
$toast.error(error.
|
|
79
|
+
$toast.error(apiErrorMessage(error, t('error.validation')))
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (e.key === 'Enter') {
|
|
85
|
-
onConfirm()
|
|
81
|
+
finally {
|
|
82
|
+
// без finally сбой resend оставлял кнопку заблокированной навсегда
|
|
83
|
+
isResendLoading.value = false
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -98,17 +96,12 @@ function runTimer() {
|
|
|
98
96
|
|
|
99
97
|
onMounted(() => {
|
|
100
98
|
runTimer()
|
|
101
|
-
document.addEventListener('keyup', handleEnter)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
onBeforeUnmount(() => {
|
|
105
|
-
document.removeEventListener('keyup', handleEnter)
|
|
106
99
|
})
|
|
107
100
|
</script>
|
|
108
101
|
|
|
109
102
|
<template>
|
|
110
|
-
<div class="flex flex-col items-center gap-4 text-center">
|
|
111
|
-
<h2 class="text-h2">
|
|
103
|
+
<div class="flex flex-col items-center gap-4 p-6 text-center">
|
|
104
|
+
<h2 class="text-h2 text-primary">
|
|
112
105
|
{{ t('modals.id.resetPasswordOtp.title') }}
|
|
113
106
|
</h2>
|
|
114
107
|
|
|
@@ -116,24 +109,22 @@ onBeforeUnmount(() => {
|
|
|
116
109
|
<a-icon-hand-with-phone-light class="size-32 dark:hidden" />
|
|
117
110
|
<a-icon-hand-with-phone-dark class="hidden size-32 dark:block" />
|
|
118
111
|
|
|
119
|
-
<div class="text-body-md">
|
|
112
|
+
<div class="text-body-md text-secondary">
|
|
120
113
|
<p class="mb-1">
|
|
121
114
|
{{ t('modals.id.resetPasswordOtp.content') }}
|
|
122
115
|
</p>
|
|
123
116
|
|
|
124
|
-
<div v-if="timer > 0" class="text-h2">
|
|
117
|
+
<div v-if="timer > 0" class="text-h2 text-primary">
|
|
125
118
|
{{ timer }} {{ t('modals.id.resetPasswordOtp.seconds') }}
|
|
126
119
|
</div>
|
|
127
120
|
<button
|
|
128
121
|
v-else
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
type="button"
|
|
123
|
+
class="mx-auto flex items-center gap-2 text-body-md text-link transition-colors hover:text-link-hover"
|
|
131
124
|
:disabled="isResendLoading"
|
|
125
|
+
@click="onResend"
|
|
132
126
|
>
|
|
133
|
-
<a-icon-loader-circle
|
|
134
|
-
v-if="isResendLoading"
|
|
135
|
-
class="animate-spin"
|
|
136
|
-
></a-icon-loader-circle>
|
|
127
|
+
<a-icon-loader-circle v-if="isResendLoading" class="size-4 animate-spin" />
|
|
137
128
|
<span>{{ t('actions.resend') }}</span>
|
|
138
129
|
</button>
|
|
139
130
|
</div>
|
|
@@ -144,16 +135,14 @@ onBeforeUnmount(() => {
|
|
|
144
135
|
@on-completed="onConfirm"
|
|
145
136
|
/>
|
|
146
137
|
|
|
147
|
-
<a-button
|
|
148
|
-
block
|
|
138
|
+
<a-button-v2
|
|
149
139
|
view="outline"
|
|
140
|
+
color="gray"
|
|
141
|
+
size="lg"
|
|
142
|
+
block
|
|
150
143
|
@click="onClose"
|
|
151
144
|
>
|
|
152
145
|
{{ t('actions.close') }}
|
|
153
|
-
</a-button>
|
|
146
|
+
</a-button-v2>
|
|
154
147
|
</div>
|
|
155
148
|
</template>
|
|
156
|
-
|
|
157
|
-
<style scoped>
|
|
158
|
-
|
|
159
|
-
</style>
|
|
@@ -12,7 +12,7 @@ const { pk } = useUrls()
|
|
|
12
12
|
const { twoFactorModal, intermediateState } = useIdModals()
|
|
13
13
|
|
|
14
14
|
const authApiURL = commonAuth.authApiURL
|
|
15
|
-
const regex = /^\/counterparty\/main\/company\/\d+\/basic-info
|
|
15
|
+
const regex = /^\/counterparty\/main\/company\/\d+\/basic-info$/
|
|
16
16
|
|
|
17
17
|
const otp = ref(['', '', '', '', '', ''])
|
|
18
18
|
const otpFormatted = computed(() => {
|
|
@@ -62,11 +62,12 @@ async function onConfirm() {
|
|
|
62
62
|
$toast.success(t('login.successfully'))
|
|
63
63
|
twoFactorModal.value = false
|
|
64
64
|
|
|
65
|
-
const path = window.location.pathname
|
|
65
|
+
const path = window.location.pathname
|
|
66
66
|
|
|
67
67
|
if (regex.test(path)) {
|
|
68
|
-
navigateTo(`${pk}/company/${path.split(
|
|
69
|
-
}
|
|
68
|
+
navigateTo(`${pk}/company/${path.split('/')[4]}`, { external: true })
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
70
71
|
window.location.reload()
|
|
71
72
|
}
|
|
72
73
|
}
|
|
@@ -78,27 +79,13 @@ function onClose() {
|
|
|
78
79
|
showError.value = false
|
|
79
80
|
twoFactorModal.value = false
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
-
function handleEnter(e: KeyboardEvent) {
|
|
83
|
-
if (e.key === 'Enter') {
|
|
84
|
-
onConfirm()
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
onMounted(() => {
|
|
89
|
-
document.addEventListener('keyup', handleEnter)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
onBeforeUnmount(() => {
|
|
93
|
-
document.removeEventListener('keyup', handleEnter)
|
|
94
|
-
})
|
|
95
82
|
</script>
|
|
96
83
|
|
|
97
84
|
<template>
|
|
98
|
-
<div class="flex flex-col items-center gap-4">
|
|
99
|
-
<
|
|
85
|
+
<div class="flex flex-col items-center gap-4 p-6">
|
|
86
|
+
<h2 class="text-center text-h2 text-primary">
|
|
100
87
|
{{ t('modals.id.twoFactor.title') }}
|
|
101
|
-
</
|
|
88
|
+
</h2>
|
|
102
89
|
<!-- осознанно константный: пара light/dark-ассетов, тема переключается dark:-классами -->
|
|
103
90
|
<a-icon-hand-with-phone-light class="size-32 dark:hidden" />
|
|
104
91
|
<a-icon-hand-with-phone-dark class="hidden size-32 dark:block" />
|
|
@@ -107,20 +94,18 @@ onBeforeUnmount(() => {
|
|
|
107
94
|
v-model:error="showError"
|
|
108
95
|
@on-completed="onConfirm"
|
|
109
96
|
/>
|
|
110
|
-
<p class="text-body-md text-
|
|
97
|
+
<p class="text-center text-body-md text-secondary">
|
|
111
98
|
{{ t('modals.id.twoFactor.code') }}
|
|
112
99
|
</p>
|
|
113
100
|
|
|
114
|
-
<a-button
|
|
115
|
-
class="w-full"
|
|
101
|
+
<a-button-v2
|
|
116
102
|
view="outline"
|
|
103
|
+
color="gray"
|
|
104
|
+
size="lg"
|
|
105
|
+
block
|
|
117
106
|
@click="onClose"
|
|
118
107
|
>
|
|
119
108
|
{{ t('actions.close') }}
|
|
120
|
-
</a-button>
|
|
109
|
+
</a-button-v2>
|
|
121
110
|
</div>
|
|
122
111
|
</template>
|
|
123
|
-
|
|
124
|
-
<style scoped>
|
|
125
|
-
|
|
126
|
-
</style>
|
|
@@ -2,20 +2,14 @@
|
|
|
2
2
|
import NavList from '#adata-ui/components/navigation/header/NavList.vue'
|
|
3
3
|
import { useActiveNavigation } from '#adata-ui/composables/useActiveNavigation'
|
|
4
4
|
import { useHeaderNavigationLinks } from '#adata-ui/composables/useHeaderNavigationLinks'
|
|
5
|
-
import { useUrls } from '#adata-ui/composables/useUrls'
|
|
6
|
-
import AIconArrowRight from '#adata-ui/icons/arrow/arrow-right.vue'
|
|
7
|
-
import AIconDownloadRounded from '#adata-ui/icons/download-rounded.vue'
|
|
8
|
-
import { PAGES } from '#adata-ui/shared/constans/pages'
|
|
9
5
|
|
|
10
6
|
defineProps<{ url?: string }>()
|
|
11
7
|
defineEmits(['outerClick', 'mouseOver'])
|
|
12
8
|
|
|
13
|
-
const urls = useUrls()
|
|
14
|
-
const { t, locale } = useI18n()
|
|
15
9
|
const modules = useHeaderNavigationLinks()
|
|
16
10
|
const { isActiveModule } = useActiveNavigation()
|
|
17
11
|
|
|
18
|
-
const COLUMN_KEYS = [['edo'], ['pk'], ['compliance', 'fines'], ['tenders', 'analytics'], ['fea', 'work']]
|
|
12
|
+
const COLUMN_KEYS = [['edo', 'export'], ['pk'], ['compliance', 'fines'], ['tenders', 'analytics'], ['fea', 'work']]
|
|
19
13
|
const catalogColumns = computed(() =>
|
|
20
14
|
COLUMN_KEYS.map(keys =>
|
|
21
15
|
keys
|
|
@@ -23,14 +17,6 @@ const catalogColumns = computed(() =>
|
|
|
23
17
|
.filter((module): module is NonNullable<typeof module> => module !== undefined),
|
|
24
18
|
),
|
|
25
19
|
)
|
|
26
|
-
|
|
27
|
-
function handleExportClick() {
|
|
28
|
-
navigateToLocalizedPage({
|
|
29
|
-
locale: locale.value,
|
|
30
|
-
projectUrl: urls.pk,
|
|
31
|
-
path: PAGES.pk.unload,
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
20
|
</script>
|
|
35
21
|
|
|
36
22
|
<template>
|
|
@@ -57,41 +43,7 @@ function handleExportClick() {
|
|
|
57
43
|
:icon="module.icon"
|
|
58
44
|
:badge="module.is_new"
|
|
59
45
|
:data-test-id="module.data_attribute"
|
|
60
|
-
|
|
61
|
-
<template
|
|
62
|
-
v-if="module.key === 'edo'"
|
|
63
|
-
#bottom
|
|
64
|
-
>
|
|
65
|
-
<!-- осознанно константный: белый текст на брендовой solid-карточке bg-brand -->
|
|
66
|
-
<div class="mt-4 flex flex-col gap-4 rounded-lg bg-brand p-4 normal-case text-white">
|
|
67
|
-
<div class="flex items-center gap-3">
|
|
68
|
-
<span class="rounded-[10px] bg-main p-[10px]">
|
|
69
|
-
<a-icon-download-rounded class="size-5 text-brand" />
|
|
70
|
-
</span>
|
|
71
|
-
<span class="font-semibold text-white">
|
|
72
|
-
{{ t('header.products.exportCard.title') }}
|
|
73
|
-
</span>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<div class="text-body-sm text-white">
|
|
77
|
-
{{ t('header.products.exportCard.subTitle') }}
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<a-button-v2
|
|
81
|
-
view="soft"
|
|
82
|
-
class="!h-9 w-fit !bg-main text-brand"
|
|
83
|
-
size="sm"
|
|
84
|
-
:icon="AIconArrowRight"
|
|
85
|
-
icon-right
|
|
86
|
-
@click="handleExportClick"
|
|
87
|
-
>
|
|
88
|
-
<span class="text-body-md-strong">
|
|
89
|
-
{{ t('header.products.exportCard.receive') }}
|
|
90
|
-
</span>
|
|
91
|
-
</a-button-v2>
|
|
92
|
-
</div>
|
|
93
|
-
</template>
|
|
94
|
-
</nav-list>
|
|
46
|
+
/>
|
|
95
47
|
</div>
|
|
96
48
|
</div>
|
|
97
49
|
</div>
|
|
@@ -14,6 +14,7 @@ import CheckSb from '#adata-ui/icons/check-sb.vue'
|
|
|
14
14
|
import IconCheckCircle from '#adata-ui/icons/check/check-circle.vue'
|
|
15
15
|
import IconCompany from '#adata-ui/icons/company.vue'
|
|
16
16
|
import IconDollar from '#adata-ui/icons/currency/currency-dollar.vue'
|
|
17
|
+
import AIconDownloadRounded from '#adata-ui/icons/download-rounded.vue'
|
|
17
18
|
import IconFilePen from '#adata-ui/icons/file-pen.vue'
|
|
18
19
|
import IconFileStack from '#adata-ui/icons/file-stack.vue'
|
|
19
20
|
import IconGitCompare from '#adata-ui/icons/git-compare.vue'
|
|
@@ -305,6 +306,21 @@ export function useHeaderNavigationLinks() {
|
|
|
305
306
|
},
|
|
306
307
|
],
|
|
307
308
|
},
|
|
309
|
+
{
|
|
310
|
+
key: 'export',
|
|
311
|
+
name: t('header.products.export.label'),
|
|
312
|
+
link: buildLocalizedUrl(locale, urls.pk, PAGES.pk.unload),
|
|
313
|
+
icon: AIconDownloadRounded,
|
|
314
|
+
data_attribute: 'navigation-menu-export-block',
|
|
315
|
+
items: [
|
|
316
|
+
{
|
|
317
|
+
title: t('header.products.export.items.clients.title'),
|
|
318
|
+
subtitle: t('header.products.export.items.clients.subtitle'),
|
|
319
|
+
icon: IconSearch,
|
|
320
|
+
to: buildLocalizedUrl(locale, urls.pk, PAGES.pk.unload),
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
},
|
|
308
324
|
{
|
|
309
325
|
key: 'compliance',
|
|
310
326
|
is_new: true,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M13.6 7.8V6.6M13.6 6.6C13.6003 6.41007 13.563 6.22195 13.4903 6.04648C13.4176 5.87101 13.3109 5.71166 13.1764 5.5776L11.0236 3.4248C10.8895 3.2899 10.73 3.1829 10.5543 3.10999C10.3786 3.03707 10.1902 2.99969 10 3M13.6 6.6H10.6C10.4409 6.6 10.2883 6.53679 10.1757 6.42427C10.0632 6.31174 10 6.15913 10 6V3M10 3H5.2C4.88174 3 4.57652 3.12643 4.35147 3.35147C4.12643 3.57652 4 3.88174 4 4.2V13.8C4 14.1183 4.12643 14.4235 4.35147 14.6485C4.57652 14.8736 4.88174 15 5.2 15H7.81M11.2 10.2C10.8817 10.2 10.5765 10.3264 10.3515 10.5515C10.1264 10.7765 10 11.0817 10 11.4M11.2 15C10.8817 15 10.5765 14.8736 10.3515 14.6485C10.1264 14.4235 10 14.1183 10 13.8M13.6 10.2C13.9183 10.2 14.2235 10.3264 14.4485 10.5515C14.6736 10.7765 14.8 11.0817 14.8 11.4M13.6 15C13.9183 15 14.2235 14.8736 14.4485 14.6485C14.6736 14.4235 14.8 14.1183 14.8 13.8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
</svg>
|
|
5
|
+
</template>
|
package/icons/google.vue
CHANGED
|
@@ -1,41 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
<script lang="ts" setup>
|
|
4
2
|
</script>
|
|
5
3
|
|
|
6
4
|
<template>
|
|
5
|
+
<!-- осознанно константные: фирменные цвета логотипа Google -->
|
|
7
6
|
<svg
|
|
8
|
-
width="
|
|
9
|
-
height="
|
|
10
|
-
viewBox="0 0
|
|
11
|
-
fill="none"
|
|
7
|
+
width="24"
|
|
8
|
+
height="24"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
12
10
|
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
focusable="false"
|
|
13
13
|
>
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
height="31"
|
|
19
|
-
rx="5.5"
|
|
20
|
-
stroke="#9DA3AC"
|
|
21
|
-
/>
|
|
22
|
-
<path
|
|
23
|
-
d="M26.501 16.2332C26.501 15.3699 26.4296 14.7399 26.2748 14.0865H16.2153V17.9832H22.12C22.001 18.9515 21.3582 20.4099 19.9296 21.3898L19.9096 21.5203L23.0902 23.935L23.3106 23.9565C25.3343 22.1249 26.501 19.4298 26.501 16.2332Z"
|
|
24
|
-
fill="#4285F4"
|
|
25
|
-
/>
|
|
26
|
-
<path
|
|
27
|
-
d="M16.2147 26.5C19.1075 26.5 21.5361 25.5666 23.3099 23.9566L19.929 21.3898C19.0242 22.0082 17.8099 22.4399 16.2147 22.4399C13.3814 22.4399 10.9767 20.6082 10.1195 18.0765L9.99382 18.087L6.68656 20.5954L6.64331 20.7132C8.40519 24.1432 12.0242 26.5 16.2147 26.5Z"
|
|
28
|
-
fill="#34A853"
|
|
29
|
-
/>
|
|
30
|
-
<path
|
|
31
|
-
d="M10.12 18.0766C9.89379 17.4233 9.76289 16.7232 9.76289 15.9999C9.76289 15.2765 9.89379 14.5766 10.1081 13.9232L10.1021 13.7841L6.75337 11.2355L6.64381 11.2865C5.91765 12.7099 5.50098 14.3083 5.50098 15.9999C5.50098 17.6916 5.91765 19.2899 6.64381 20.7132L10.12 18.0766Z"
|
|
32
|
-
fill="#FBBC05"
|
|
33
|
-
/>
|
|
34
|
-
<path
|
|
35
|
-
d="M16.2148 9.55997C18.2267 9.55997 19.5838 10.4116 20.3576 11.1233L23.3814 8.23C21.5243 6.53834 19.1076 5.5 16.2148 5.5C12.0243 5.5 8.4052 7.85665 6.64331 11.2866L10.1076 13.9233C10.9767 11.3917 13.3815 9.55997 16.2148 9.55997Z"
|
|
36
|
-
fill="#EB4335"
|
|
37
|
-
/>
|
|
14
|
+
<path fill="#4285F4" d="M23.52 12.27c0-.85-.08-1.67-.22-2.45H12v4.64h6.46a5.52 5.52 0 0 1-2.4 3.62v3.01h3.88c2.27-2.09 3.58-5.17 3.58-8.82Z" />
|
|
15
|
+
<path fill="#34A853" d="M12 24c3.24 0 5.96-1.08 7.94-2.91l-3.88-3.01c-1.08.72-2.45 1.15-4.06 1.15-3.12 0-5.77-2.11-6.71-4.95H1.28v3.1A12 12 0 0 0 12 24Z" />
|
|
16
|
+
<path fill="#FBBC05" d="M5.29 14.28a7.2 7.2 0 0 1 0-4.56v-3.1H1.28a12 12 0 0 0 0 10.76l4.01-3.1Z" />
|
|
17
|
+
<path fill="#EA4335" d="M12 4.77c1.76 0 3.35.61 4.6 1.8l3.44-3.44C17.95 1.19 15.24 0 12 0A12 12 0 0 0 1.28 6.62l4.01 3.1C6.23 6.88 8.88 4.77 12 4.77Z" />
|
|
38
18
|
</svg>
|
|
39
19
|
</template>
|
|
40
|
-
|
|
41
|
-
<style scoped></style>
|