adata-ui 2.1.0 → 2.1.2

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.
Files changed (307) hide show
  1. package/.editorconfig +12 -12
  2. package/.nuxtrc +1 -1
  3. package/.playground/app.config.ts +5 -5
  4. package/README.md +75 -75
  5. package/app.config.ts +4 -3
  6. package/app.vue +1 -0
  7. package/assets/styles/index.scss +104 -0
  8. package/assets/styles/modules/_base.scss +5 -0
  9. package/assets/styles/modules/_typography.scss +152 -0
  10. package/components/elements/README.md +1 -1
  11. package/components/elements/accordion/AAccordion.vue +13 -12
  12. package/components/elements/curve-block/ACurveBlock.vue +94 -19
  13. package/components/elements/feature-description/AFeatureDescription.vue +4 -4
  14. package/components/elements/illustrations/noAccess.vue +11 -2
  15. package/components/elements/photos-animation/APhotosAnimation.vue +3 -1
  16. package/components/elements/select/ASelect.vue +65 -9
  17. package/components/features/lang-switcher/lang-switcher.vue +8 -1
  18. package/components/features/payment/banner/BasicPlusLimitBanner.vue +104 -0
  19. package/components/features/payment/banner/PaymentBanner.vue +108 -0
  20. package/components/features/payment/process/PaymentKaspiQrSidePanel.vue +158 -0
  21. package/components/features/payment/process/PaymentKaspiRedirectSidePanel.vue +112 -0
  22. package/components/features/payment/process/PaymentMethodSidePanel.vue +121 -0
  23. package/components/features/payment/process/PaymentProcess.vue +138 -0
  24. package/components/features/payment/process/PaymentTopUpSidePanel.vue +130 -0
  25. package/components/features/pk-mobile-services/APkMobileServices.vue +21 -2
  26. package/components/forms/README.md +1 -1
  27. package/components/forms/checkbox/ACheckbox.vue +0 -2
  28. package/components/forms/radio-button/ARadioButton.vue +21 -13
  29. package/components/forms/request-demo/ARequestDemo.vue +21 -7
  30. package/components/modals/AConfirmationEmail.vue +40 -0
  31. package/components/modals/AnotherDeviceModal.vue +3 -2
  32. package/components/modals/ConnectingTariffModal.vue +0 -1
  33. package/components/modals/ContentNavigationModal.vue +148 -75
  34. package/components/modals/Insufficient-funds-modal.vue +5 -2
  35. package/components/modals/LimitReachedModal.vue +4 -3
  36. package/components/modals/ReportBugModal.vue +3 -3
  37. package/components/modals/Resend.vue +82 -0
  38. package/components/modals/SubmitApplicationModal.vue +4 -0
  39. package/components/modals/id/AuthModal.vue +78 -0
  40. package/components/modals/id/IdAutoLogoutModal.vue +45 -0
  41. package/components/modals/id/IdBanner.vue +58 -0
  42. package/components/modals/id/IdConfirmAccountOtpModal.vue +186 -0
  43. package/components/modals/id/IdConfirmSuccessfulModal.vue +41 -0
  44. package/components/modals/id/IdLoginModal.vue +316 -0
  45. package/components/modals/id/IdModals.vue +114 -0
  46. package/components/modals/id/IdNewPasswordModal.vue +129 -0
  47. package/components/modals/id/IdOtpInput.vue +155 -0
  48. package/components/modals/id/IdPasswordSuccessfulModal.vue +25 -0
  49. package/components/modals/id/IdRecoveryModal.vue +117 -0
  50. package/components/modals/id/IdRegistrationModal.vue +215 -0
  51. package/components/modals/id/IdResetPasswordOtpModal.vue +158 -0
  52. package/components/modals/id/IdTwoFactorModal.vue +130 -0
  53. package/components/navigation/README.md +1 -1
  54. package/components/navigation/bottom-navigation/ABottomNavigation.vue +34 -29
  55. package/components/navigation/footer/AFooter.vue +210 -57
  56. package/components/navigation/footer/ui/{new-footer-accordion.vue → a-footer-accordion.vue} +2 -4
  57. package/components/navigation/header/AHeader.vue +59 -51
  58. package/components/navigation/header/AlmatyContacts.vue +16 -14
  59. package/components/navigation/header/AstanaContacts.vue +13 -11
  60. package/components/navigation/header/CardGallery.vue +5 -4
  61. package/components/navigation/header/HeaderLink.vue +16 -8
  62. package/components/navigation/header/NavList.vue +21 -5
  63. package/components/navigation/header/ProductMenu.vue +8 -78
  64. package/components/navigation/header/ProfileMenu.vue +10 -5
  65. package/components/navigation/header/TopHeader.vue +2 -2
  66. package/components/navigation/mobile-navigation/AMobileNavigation.vue +3 -3
  67. package/components/navigation/pill-tabs/APillTabs.vue +18 -4
  68. package/components/navigation/pill-tabs/types.ts +1 -0
  69. package/components/navigation/side-navigation/ASideNavigation.vue +23 -21
  70. package/components/overlays/README.md +1 -1
  71. package/components/overlays/side-panel/ASidePanel.vue +439 -0
  72. package/components/overlays/tooltip/ATooltip.vue +149 -154
  73. package/components/utils/removeTrailingSlash.ts +7 -0
  74. package/composables/useBuyTariffs.ts +91 -0
  75. package/composables/useHeaderNavigationLinks.ts +174 -297
  76. package/composables/useIdModals.ts +36 -0
  77. package/composables/usePayment.ts +74 -0
  78. package/composables/useUrls.ts +21 -0
  79. package/eslint.config.mjs +45 -0
  80. package/icons/adata-logo.vue +1 -1
  81. package/icons/ai-assistant.vue +13 -0
  82. package/icons/akimat.vue +20 -0
  83. package/icons/arrow/arrow-bottom-left-on-square.vue +1 -1
  84. package/icons/arrow/arrow-circle-reset.vue +1 -1
  85. package/icons/arrow/arrow-corner.vue +1 -1
  86. package/icons/arrow/arrow-graph-down.vue +1 -1
  87. package/icons/arrow/arrow-graph-up.vue +1 -1
  88. package/icons/arrow/arrow-square-down.vue +1 -1
  89. package/icons/arrow/arrow-top-right-on-square.vue +1 -1
  90. package/icons/arrow-currency-gray.vue +1 -1
  91. package/icons/arrow-currency-green.vue +1 -1
  92. package/icons/arrow-currency-red.vue +1 -1
  93. package/icons/avatar.vue +1 -1
  94. package/icons/bank.vue +5 -0
  95. package/icons/block.vue +1 -1
  96. package/icons/bookmark/bookmark-filled.vue +1 -1
  97. package/icons/bookmark/bookmark.vue +1 -1
  98. package/icons/browsers/browser-duck.vue +1 -1
  99. package/icons/browsers/browser-google.vue +7 -1
  100. package/icons/browsers/browser-yandex.vue +1 -1
  101. package/icons/building-vector.vue +1 -1
  102. package/icons/calculator.vue +1 -1
  103. package/icons/calendar.vue +1 -1
  104. package/icons/car.vue +1 -1
  105. package/icons/chart-bar.vue +5 -0
  106. package/icons/chart-pie.vue +16 -0
  107. package/icons/check/check-circle.vue +1 -1
  108. package/icons/check/check.vue +1 -1
  109. package/icons/check/checkmark-circle.vue +1 -1
  110. package/icons/check-sb.vue +7 -0
  111. package/icons/checkbox/checkbox-active.vue +1 -1
  112. package/icons/checkbox/checkbox-empty.vue +1 -1
  113. package/icons/checkbox/checkbox-intermediate.vue +1 -1
  114. package/icons/chevron/chevron-down.vue +1 -1
  115. package/icons/chevron/chevron-left.vue +1 -1
  116. package/icons/chevron/chevron-right.vue +1 -1
  117. package/icons/chevron/chevron-up.vue +1 -1
  118. package/icons/chevron/double-chevron-right.vue +1 -1
  119. package/icons/clipboard-text.vue +1 -1
  120. package/icons/clock.vue +1 -1
  121. package/icons/company/company-bazis.vue +3 -3
  122. package/icons/company/company-bereke.vue +13 -13
  123. package/icons/company/company-bigroup.vue +5 -5
  124. package/icons/company/company-erg.vue +6 -6
  125. package/icons/company/company-forte.vue +11 -11
  126. package/icons/company/company-halyk.vue +4 -4
  127. package/icons/company/company-jusan.vue +20 -3
  128. package/icons/company/company-kaspi.vue +3 -3
  129. package/icons/company/company-mycar.vue +2 -2
  130. package/icons/company/company-samruk.vue +9 -9
  131. package/icons/company-egov-small.vue +1 -1
  132. package/icons/company.vue +1 -1
  133. package/icons/copy.vue +1 -1
  134. package/icons/currency/currency-dollar.vue +1 -1
  135. package/icons/currency/currency-down.vue +1 -1
  136. package/icons/currency/currency-eur.vue +1 -1
  137. package/icons/currency/currency-rub.vue +1 -1
  138. package/icons/currency/currency-tenge.vue +9 -0
  139. package/icons/currency/currency-usd.vue +1 -1
  140. package/icons/currency/currency-yuan.vue +1 -1
  141. package/icons/delete.vue +1 -1
  142. package/icons/document.vue +1 -1
  143. package/icons/download.vue +1 -1
  144. package/icons/edit.vue +1 -1
  145. package/icons/education.vue +1 -1
  146. package/icons/egov-small.vue +1 -1
  147. package/icons/excel-icon.vue +14 -0
  148. package/icons/expand-window.vue +1 -1
  149. package/icons/eye-closed.vue +1 -1
  150. package/icons/eye-open.vue +1 -1
  151. package/icons/eye-opened.vue +1 -1
  152. package/icons/file/file.vue +1 -1
  153. package/icons/filter.vue +1 -1
  154. package/icons/flag.vue +1 -1
  155. package/icons/gift.vue +1 -1
  156. package/icons/globe.vue +1 -1
  157. package/icons/google.vue +41 -0
  158. package/icons/hand/hand-thumb-up-filled.vue +1 -1
  159. package/icons/hand/hand-thumb-up.vue +1 -1
  160. package/icons/hand-with-phone/hand-with-phone-dark.vue +1 -1
  161. package/icons/hand-with-phone/hand-with-phone-light.vue +1 -1
  162. package/icons/handshake.vue +1 -1
  163. package/icons/hcheck.vue +1 -1
  164. package/icons/hdocument.vue +1 -1
  165. package/icons/history.vue +1 -1
  166. package/icons/horizontal-more.vue +1 -1
  167. package/icons/hot-line.vue +6 -0
  168. package/icons/hummer.vue +1 -1
  169. package/icons/info/info-circle.vue +1 -1
  170. package/icons/invoice.vue +1 -1
  171. package/icons/kaspi-qr.vue +13 -0
  172. package/icons/link-chain.vue +1 -1
  173. package/icons/link.vue +1 -1
  174. package/icons/linkedin.vue +24 -24
  175. package/icons/loader-circle.vue +1 -1
  176. package/icons/location.vue +1 -1
  177. package/icons/lock.vue +1 -1
  178. package/icons/logo.vue +1 -1
  179. package/icons/logout.vue +1 -1
  180. package/icons/magnify/magnifying-glass-minus.vue +1 -1
  181. package/icons/magnify/magnifying-glass-plus.vue +1 -1
  182. package/icons/magnify/magnifying-glass.vue +1 -1
  183. package/icons/mail.vue +1 -1
  184. package/icons/mailru.vue +34 -0
  185. package/icons/main-filter.vue +1 -1
  186. package/icons/map/map-pin-filled.vue +1 -1
  187. package/icons/map/map-pin-rect.vue +1 -1
  188. package/icons/map/map-pin.vue +1 -1
  189. package/icons/map-marker-start.vue +1 -1
  190. package/icons/medal.vue +1 -1
  191. package/icons/menu-filled.vue +1 -1
  192. package/icons/menu.vue +1 -1
  193. package/icons/message/message.vue +1 -1
  194. package/icons/minus/minus-circle.vue +1 -1
  195. package/icons/money.vue +1 -1
  196. package/icons/monitoring.vue +1 -1
  197. package/icons/moon.vue +1 -1
  198. package/icons/more.vue +1 -1
  199. package/icons/notification.vue +1 -1
  200. package/icons/paperclip.vue +1 -1
  201. package/icons/payment/payment-card.vue +1 -1
  202. package/icons/payment/payment-kaspi.vue +1 -1
  203. package/icons/person-vector.vue +1 -1
  204. package/icons/person-with-briefcase.vue +1 -1
  205. package/icons/phone-filled.vue +1 -1
  206. package/icons/phone.vue +1 -1
  207. package/icons/plus/plus-circle.vue +1 -1
  208. package/icons/plus/plus.vue +1 -1
  209. package/icons/profile.vue +1 -1
  210. package/icons/radio/radio-check.vue +1 -1
  211. package/icons/radio/radio-empty.vue +1 -1
  212. package/icons/receipt.vue +1 -1
  213. package/icons/sanctions.vue +8 -0
  214. package/icons/scales/scale.vue +1 -1
  215. package/icons/scales/scales.vue +1 -1
  216. package/icons/scales/standing-scales.vue +1 -1
  217. package/icons/search.vue +1 -1
  218. package/icons/share/share-alt.vue +1 -1
  219. package/icons/share/share.vue +1 -1
  220. package/icons/socials/face-book.vue +1 -1
  221. package/icons/socials/instagram.vue +1 -1
  222. package/icons/socials/telegram.vue +1 -1
  223. package/icons/socials/tik-tok.vue +1 -1
  224. package/icons/socials/youtube.vue +1 -1
  225. package/icons/sort/sort-asc.vue +1 -1
  226. package/icons/sort/sort-desc.vue +1 -1
  227. package/icons/splitting-arrows.vue +1 -1
  228. package/icons/star/star-filled.vue +1 -1
  229. package/icons/star/star-half-filled.vue +1 -1
  230. package/icons/star/star.vue +1 -1
  231. package/icons/sun.vue +14 -14
  232. package/icons/sviazi.vue +1 -1
  233. package/icons/tag.vue +1 -1
  234. package/icons/tasks.vue +10 -0
  235. package/icons/tender-search.vue +1 -1
  236. package/icons/toasts/check-circle-toast.vue +1 -1
  237. package/icons/toasts/warning-triangle-toast.vue +1 -1
  238. package/icons/trash.vue +1 -1
  239. package/icons/triangle.vue +1 -1
  240. package/icons/truck.vue +1 -1
  241. package/icons/user.vue +1 -1
  242. package/icons/users-focus.vue +1 -1
  243. package/icons/users.vue +1 -1
  244. package/icons/warning/warning-circle.vue +1 -1
  245. package/icons/warning/warning-triangle-filled.vue +1 -1
  246. package/icons/warning/warning-triangle.vue +1 -1
  247. package/icons/whatsapp.vue +1 -1
  248. package/icons/work-bag.vue +1 -1
  249. package/icons/work-case.vue +9 -0
  250. package/icons/work-search.vue +1 -1
  251. package/icons/work.vue +1 -1
  252. package/icons/x-mark.vue +1 -1
  253. package/icons/yandex.vue +28 -0
  254. package/illustrations/address-location.vue +1 -1
  255. package/illustrations/ball-with-chain.vue +1 -1
  256. package/illustrations/bill.vue +1 -1
  257. package/illustrations/buildings.vue +1 -1
  258. package/illustrations/calendar.vue +1 -1
  259. package/illustrations/chains.vue +1 -1
  260. package/illustrations/coin-percent.vue +1 -1
  261. package/illustrations/coins-stack.vue +1 -1
  262. package/illustrations/delete-dark.vue +1 -1
  263. package/illustrations/delete.vue +1 -1
  264. package/illustrations/doc-with-stamp.vue +1 -1
  265. package/illustrations/document.vue +1 -1
  266. package/illustrations/door.vue +1 -1
  267. package/illustrations/empty-box.vue +1 -1
  268. package/illustrations/empty-wallet.vue +1 -1
  269. package/illustrations/graph-in-coin.vue +1 -1
  270. package/illustrations/hammer.vue +1 -1
  271. package/illustrations/hand-cash.vue +1 -1
  272. package/illustrations/info.vue +1 -1
  273. package/illustrations/mail.vue +1 -1
  274. package/illustrations/ok.vue +1 -1
  275. package/illustrations/people-group.vue +1 -1
  276. package/illustrations/person-with-phone.vue +1 -1
  277. package/illustrations/person.vue +1 -1
  278. package/illustrations/phone-check.vue +1 -1
  279. package/illustrations/phone-payment-method.vue +1 -1
  280. package/illustrations/stop-hand.vue +1 -1
  281. package/illustrations/stop-sign.vue +1 -1
  282. package/illustrations/suit.vue +1 -1
  283. package/illustrations/suitcase.vue +1 -1
  284. package/illustrations/terminal-dark.vue +1 -1
  285. package/illustrations/terminal.vue +1 -1
  286. package/illustrations/trash-can.vue +1 -1
  287. package/illustrations/turn-on-tariff.vue +1 -1
  288. package/illustrations/two-persons.vue +1 -1
  289. package/lang/en.ts +475 -270
  290. package/lang/kk.ts +476 -271
  291. package/lang/ru.ts +315 -107
  292. package/layouts/default.vue +13 -13
  293. package/nuxt.config.ts +42 -14
  294. package/package.json +69 -53
  295. package/public/kaspi/logo.svg +4 -0
  296. package/shared/constans/pages.ts +17 -2
  297. package/tailwind.config.ts +163 -0
  298. package/vitest.config.ts +14 -0
  299. package/.eslintrc.cjs +0 -4
  300. package/.playground/app.vue +0 -9
  301. package/.playground/pages/index.vue +0 -13
  302. package/.prettierignore +0 -24
  303. package/.prettierrc +0 -10
  304. package/assets/styles/index.css +0 -226
  305. package/components/modals/AuthModal.vue +0 -50
  306. package/components/navigation/footer/NewFooter.vue +0 -276
  307. package/components/navigation/footer/ui/footer-acccordion.vue +0 -119
@@ -0,0 +1,316 @@
1
+ <script setup lang="ts">
2
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
3
+ import { navigateToLocalizedPage } from '#adata-ui/utils/localizedNavigation'
4
+ import * as z from 'zod'
5
+ import { useUrls } from '#adata-ui/composables/useUrls'
6
+
7
+ const { $toast } = useNuxtApp()
8
+ const { commonAuth } = useAppConfig()
9
+ const { t, locale } = useI18n()
10
+ const { pk, landing } = useUrls()
11
+ const route = useRoute()
12
+
13
+ const regex = /^\/counterparty\/main\/company\/\d+\/basic-info$/;
14
+
15
+ const { loginModal, registrationModal, recoveryModal, confirmAccountOtpModal, twoFactorModal, intermediateState } = useIdModals()
16
+
17
+ const authApiURL = commonAuth.authApiURL
18
+
19
+ const submitted = ref(false)
20
+ const accessToken = ref(null)
21
+
22
+ export interface ILoginForm {
23
+ username: string
24
+ password: string
25
+ }
26
+
27
+ const loginSchema = z.object({
28
+ username: z.string().nonempty(t('error.required')).email(t('error.email')),
29
+ password: z.string().nonempty(t('error.required')),
30
+ })
31
+
32
+ const validation = computed(() => {
33
+ if (!submitted.value) return null
34
+ const result = loginSchema.safeParse(form)
35
+ return result.success ? null : result.error
36
+ })
37
+
38
+ function getError(path: string) {
39
+ return validation.value?.issues.find(issue => issue.path[0] === path)?.message
40
+ }
41
+
42
+ function savePassAndLogin(form: { username: string, password: string }) {
43
+ const { username, password } = form
44
+ const cookieOptions = {
45
+ expires: rememberMe.value ? new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) : undefined, // Срок действия 14 дней
46
+ }
47
+ useCookie('username', cookieOptions).value = username
48
+ useCookie('password', cookieOptions).value = password
49
+ }
50
+ function clearAuthCookie() {
51
+ useCookie('username').value = null
52
+ useCookie('password').value = null
53
+ }
54
+
55
+ const loading = ref(false)
56
+ const form: ILoginForm = reactive({
57
+ username: useCookie('username').value || '',
58
+ password: useCookie('password').value || '',
59
+ })
60
+ const rememberMe = ref(false)
61
+
62
+ async function submit() {
63
+ submitted.value = true
64
+ if (!validation.value) {
65
+ loading.value = true
66
+ const login = await fetch(`${removeTrailingSlash(authApiURL)}/login`, {
67
+ method: 'POST',
68
+ credentials: 'include',
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
+
80
+ intermediateState.value.email = form.username
81
+ intermediateState.value.password = form.password
82
+
83
+ if (data) accessToken.value = data?.access_token
84
+ if (login.status > 202) {
85
+ if (login.status === 422) {
86
+ $toast.error(t('error.validation'))
87
+ }
88
+ if (login.status === 206) {
89
+ confirmAccountOtpModal.value = true
90
+
91
+ $fetch(`${removeTrailingSlash(authApiURL)}/email/resend-otp`, {
92
+ method: 'GET',
93
+ credentials: 'include',
94
+ headers: {
95
+ lang: locale.value,
96
+ },
97
+ params: {
98
+ email: intermediateState.value.email,
99
+ },
100
+ }).catch(() => {})
101
+ }
102
+ else {
103
+ $toast.error(message)
104
+ }
105
+ }
106
+ else {
107
+ rememberMe.value ? savePassAndLogin(form) : clearAuthCookie()
108
+ if (data.is_2fa_enabled) {
109
+ loginModal.value = false
110
+ twoFactorModal.value = true
111
+ }
112
+ else if (data.email_is_verified) {
113
+ const response = await fetch(`${removeTrailingSlash(authApiURL)}/access/cookie`, {
114
+ method: 'GET',
115
+ credentials: 'include',
116
+ headers: {
117
+ Authorization: `Bearer ${accessToken.value}`,
118
+ lang: locale.value,
119
+ },
120
+ })
121
+ const { data: cookiesData } = await response.json()
122
+ if (cookiesData) {
123
+ const { access_token, expire_in } = cookiesData
124
+ const hostname = location.hostname.split('.').reverse()
125
+ useCookie('autoLogout').value = true
126
+
127
+ useCookie('accessToken', {
128
+ maxAge: expire_in,
129
+ domain: `.${hostname[1]}.${hostname[0]}`,
130
+ path: '/',
131
+ secure: true,
132
+ }).value = access_token
133
+ }
134
+ $toast.success(t('reuse.successfully'))
135
+
136
+ loginModal.value = false
137
+ const path = window.location.pathname;
138
+ const redirectUrl = route.query.redirect
139
+
140
+ if (regex.test(path)) {
141
+ navigateTo(`${pk}/company/${path.split("/")[4]}`, { external: true })
142
+ }else if (redirectUrl) {
143
+ navigateTo(redirectUrl as string, { external: true })
144
+ }
145
+ else {
146
+ window.location.reload()
147
+ }
148
+ }
149
+ else {
150
+ confirmAccountOtpModal.value = true
151
+
152
+ $fetch(`${removeTrailingSlash(authApiURL)}/email/resend-otp`, {
153
+ method: 'GET',
154
+ credentials: 'include',
155
+ headers: {
156
+ lang: locale.value,
157
+ },
158
+ params: {
159
+ email: intermediateState.value.email,
160
+ },
161
+ }).catch(() => {})
162
+ }
163
+ }
164
+ loading.value = false
165
+ }
166
+ }
167
+
168
+ function authWithSocial(social: string) {
169
+ const mode = (useNuxtApp().$config.public.authApiURL as string).includes('adata')
170
+ ? 'adata'
171
+ : 'adtdev'
172
+ document.location.replace(`https://auth.${mode}.kz/api/login/social?source=${social}`)
173
+ }
174
+
175
+ function onRegister() {
176
+ loginModal.value = false
177
+ registrationModal.value = true
178
+ }
179
+
180
+ function toTariffs() {
181
+ return navigateToLocalizedPage({
182
+ locale,
183
+ projectUrl: landing,
184
+ path: '/tariffs',
185
+ target: '_self',
186
+ })
187
+ }
188
+
189
+ onMounted(() => {
190
+ rememberMe.value = !!useCookie('username').value && !!useCookie('password').value
191
+ })
192
+
193
+ function handleEnter(e: KeyboardEvent) {
194
+ if (e.key === 'Enter') {
195
+ submit()
196
+ }
197
+ }
198
+
199
+ function onForgotPassword() {
200
+ loginModal.value = false
201
+ recoveryModal.value = true
202
+ }
203
+
204
+ onMounted(() => {
205
+ document.addEventListener('keyup', handleEnter)
206
+ })
207
+
208
+ onBeforeUnmount(() => {
209
+ document.removeEventListener('keyup', handleEnter)
210
+ })
211
+ </script>
212
+
213
+ <template>
214
+ <div class="flex flex-col gap-5">
215
+ <h1 class="heading-02 text-center">
216
+ {{ t('modals.id.login.title') }}
217
+ </h1>
218
+ <p class="body-400 text-center">
219
+ {{ t('modals.id.login.subtitle') }}
220
+ </p>
221
+ <div class="flex flex-col gap-4">
222
+ <a-input-standard
223
+ v-model="form.username"
224
+ type="email"
225
+ :label="t('modals.id.login.labels.email')"
226
+ :error="getError('username')"
227
+ />
228
+ <a-input-password
229
+ v-model="form.password"
230
+ :label="t('modals.id.login.labels.password')"
231
+ :error="getError('password')"
232
+ />
233
+ <div class="flex items-center justify-between">
234
+ <div class="body-400 flex gap-2">
235
+ <a-checkbox
236
+ v-model="rememberMe"
237
+ name="remember_me"
238
+ />
239
+ <label
240
+ for="remember_me"
241
+ class="cursor-pointer"
242
+ >{{
243
+ t('modals.id.login.remember_me')
244
+ }}</label>
245
+ </div>
246
+ <button
247
+ class="link-s-400"
248
+ @click="onForgotPassword"
249
+ >
250
+ {{ t('modals.id.login.forget_password') }}
251
+ </button>
252
+ </div>
253
+ </div>
254
+ <div class="flex items-center gap-4">
255
+ <div class="h-px w-full bg-deepblue-500/30" />
256
+ <div class="flex shrink-0 gap-4">
257
+ <span
258
+ class="cursor-pointer"
259
+ @click="authWithSocial('google')"
260
+ >
261
+ <a-icon-google />
262
+ </span>
263
+ <span
264
+ class="cursor-pointer"
265
+ @click="authWithSocial('yandex')"
266
+ >
267
+ <a-icon-yandex />
268
+ </span>
269
+ <span
270
+ class="cursor-pointer"
271
+ @click="authWithSocial('mailru')"
272
+ >
273
+ <a-icon-mailru />
274
+ </span>
275
+ </div>
276
+ <div class="h-px w-full bg-deepblue-500/30" />
277
+ </div>
278
+ <a-button
279
+ :loading="loading"
280
+ type="submit"
281
+ @click="submit"
282
+ >
283
+ {{ t('actions.login') }}
284
+ </a-button>
285
+ <p class="body-400 text-center">
286
+ {{ t('modals.id.login.first_time') }}
287
+ </p>
288
+
289
+ <a-button
290
+ type="button"
291
+ view="outline"
292
+ class="w-full"
293
+ @click="onRegister"
294
+ >
295
+ {{ t('actions.register') }}
296
+ </a-button>
297
+
298
+ <a-button
299
+ type="button"
300
+ view="transparent"
301
+ class="w-full"
302
+ @click="toTariffs"
303
+ >
304
+ {{ t('actions.toTariffs') }}
305
+ </a-button>
306
+
307
+ <a-alert
308
+ class="max-w-screen-sm !text-[10px]"
309
+ size="xs"
310
+ >
311
+ {{ t('info.userAgreement') }}
312
+ </a-alert>
313
+ </div>
314
+ </template>
315
+
316
+ <style scoped></style>
@@ -0,0 +1,114 @@
1
+ <script setup lang="ts">
2
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
3
+ import IdConfirmAccountOtpModal from '#adata-ui/components/modals/id/IdConfirmAccountOtpModal.vue'
4
+ import IdConfirmSuccessfulModal from '#adata-ui/components/modals/id/IdConfirmSuccessfulModal.vue'
5
+ import IdLoginModal from '#adata-ui/components/modals/id/IdLoginModal.vue'
6
+ import IdNewPasswordModal from '#adata-ui/components/modals/id/IdNewPasswordModal.vue'
7
+ import IdPasswordSuccessfulModal from '#adata-ui/components/modals/id/IdPasswordSuccessfulModal.vue'
8
+ import IdRecoveryModal from '#adata-ui/components/modals/id/IdRecoveryModal.vue'
9
+ import IdRegistrationModal from '#adata-ui/components/modals/id/IdRegistrationModal.vue'
10
+ import IdResetPasswordOtpModal from '#adata-ui/components/modals/id/IdResetPasswordOtpModal.vue'
11
+ import IdTwoFactorModal from '#adata-ui/components/modals/id/IdTwoFactorModal.vue'
12
+ import IdAutoLogoutModal from '#adata-ui/components/modals/id/IdAutoLogoutModal.vue'
13
+
14
+ const emit = defineEmits<{
15
+ (e: 'closed', value: boolean): void
16
+ }>()
17
+
18
+ const {
19
+ loginModal,
20
+ registrationModal,
21
+ confirmAccountOtpModal,
22
+ recoveryModal,
23
+ resetPasswordOtpModal,
24
+ newPasswordModal,
25
+ passwordSuccessfulModal,
26
+ confirmSuccessfulModal,
27
+ twoFactorModal,
28
+ autoLogoutModal
29
+ } = useIdModals()
30
+
31
+ const { locale } = useI18n()
32
+ const { commonAuth } = useAppConfig()
33
+ const authApiURL = commonAuth.authApiURL
34
+
35
+ const accessToken = useCookie('accessToken')
36
+ const isAuthenticated = computed(() => !!accessToken.value)
37
+
38
+ onMounted(async () => {
39
+ const response = await fetch(`${removeTrailingSlash(authApiURL)}/access/cookie`, {
40
+ method: 'GET',
41
+ credentials: 'include',
42
+ headers: {
43
+ lang: locale.value,
44
+ },
45
+ })
46
+ const { data: cookiesData } = await response.json()
47
+ if (cookiesData?.access_token && !accessToken.value) {
48
+ const { access_token, expire_in } = cookiesData
49
+ const hostname = location.hostname.split('.').reverse()
50
+ useCookie('autoLogout').value = true
51
+
52
+ useCookie('accessToken', {
53
+ maxAge: expire_in,
54
+ domain: `.${hostname[1]}.${hostname[0]}`,
55
+ path: '/',
56
+ secure: true,
57
+ }).value = access_token
58
+
59
+ window.location.reload()
60
+ }
61
+ })
62
+
63
+ watch(isAuthenticated, (val) => {
64
+ const autoLogout = useCookie('autoLogout')
65
+
66
+ if (!val && autoLogout.value) {
67
+ autoLogoutModal.value = true
68
+ autoLogout.value = null
69
+ }
70
+ })
71
+
72
+ const autoLoginClose = () => {
73
+ emit('closed', true)
74
+ autoLogoutModal.value = false
75
+ }
76
+ </script>
77
+
78
+ <template>
79
+ <a-modal v-model="loginModal">
80
+ <id-login-modal v-if="loginModal" />
81
+ </a-modal>
82
+ <a-modal v-model="twoFactorModal">
83
+ <id-two-factor-modal v-if="twoFactorModal" />
84
+ </a-modal>
85
+
86
+ <a-modal v-model="registrationModal">
87
+ <id-registration-modal v-if="registrationModal" />
88
+ </a-modal>
89
+ <a-modal v-model="confirmAccountOtpModal" prevent-close>
90
+ <id-confirm-account-otp-modal v-if="confirmAccountOtpModal" />
91
+ </a-modal>
92
+ <a-modal v-model="confirmSuccessfulModal" prevent-close>
93
+ <id-confirm-successful-modal v-if="confirmSuccessfulModal" />
94
+ </a-modal>
95
+
96
+ <a-modal v-model="recoveryModal">
97
+ <id-recovery-modal v-if="recoveryModal" />
98
+ </a-modal>
99
+ <a-modal v-model="resetPasswordOtpModal" prevent-close>
100
+ <id-reset-password-otp-modal v-if="resetPasswordOtpModal" />
101
+ </a-modal>
102
+ <a-modal v-model="newPasswordModal" prevent-close>
103
+ <id-new-password-modal v-if="newPasswordModal" />
104
+ </a-modal>
105
+ <a-modal v-model="passwordSuccessfulModal" prevent-close>
106
+ <id-password-successful-modal v-if="passwordSuccessfulModal" />
107
+ </a-modal>
108
+
109
+ <a-modal v-model="autoLogoutModal" prevent-close>
110
+ <id-auto-logout-modal v-if="autoLogoutModal" @closed="autoLoginClose" />
111
+ </a-modal>
112
+ </template>
113
+
114
+ <style scoped></style>
@@ -0,0 +1,129 @@
1
+ <script setup lang="ts">
2
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
3
+ import * as z from 'zod'
4
+
5
+ const { $toast } = useNuxtApp()
6
+ const { commonAuth } = useAppConfig()
7
+ const { t, locale } = useI18n()
8
+
9
+ const authApiURL = commonAuth.authApiURL
10
+ const { newPasswordModal, passwordSuccessfulModal, intermediateState } = useIdModals()
11
+
12
+ const form = reactive({
13
+ password: '',
14
+ password_confirmation: '',
15
+ })
16
+ const loading = ref(false)
17
+
18
+ const resetSchema = z.object({
19
+ password: z
20
+ .string()
21
+ .nonempty(t('error.required'))
22
+ .min(8, t('modals.id.register.errors.low_security', { length: 8 }))
23
+ .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
24
+ password_confirmation: z
25
+ .string()
26
+ .nonempty(t('error.required'))
27
+ .min(8, t('modals.id.register.errors.low_security', { length: 8 }))
28
+ .regex(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, t('modals.id.register.errors.low_security')),
29
+ }).refine(
30
+ data => data.password === data.password_confirmation,
31
+ {
32
+ message: t('modals.id.register.errors.sameAs'),
33
+ path: ['password_confirmation'],
34
+ },
35
+ )
36
+
37
+ const submitted = ref(false)
38
+
39
+ const validation = computed(() => {
40
+ if (!submitted.value) return null
41
+ const result = resetSchema.safeParse(form)
42
+ return result.success ? null : result.error
43
+ })
44
+
45
+ function getError(path: string) {
46
+ return validation.value?.issues?.find(issue => issue.path[0] === path)?.message
47
+ }
48
+
49
+ async function onSubmit() {
50
+ try {
51
+ submitted.value = true
52
+ if (validation.value) return
53
+ loading.value = true
54
+
55
+ const response = await $fetch(`${removeTrailingSlash(authApiURL)}/password/reset-otp`, {
56
+ method: 'POST',
57
+ credentials: 'include',
58
+ headers: {
59
+ lang: locale.value,
60
+ },
61
+ body: {
62
+ token: intermediateState.value.token,
63
+ email: intermediateState.value.email,
64
+ ...form,
65
+ },
66
+ })
67
+ if (response?.success) {
68
+ newPasswordModal.value = false
69
+ passwordSuccessfulModal.value = true
70
+ $toast.success(response.message)
71
+ }
72
+ }
73
+ catch (error) {
74
+ $toast.error(error?.data.message.password as string)
75
+ }
76
+ finally {
77
+ loading.value = false
78
+ }
79
+ }
80
+ </script>
81
+
82
+ <template>
83
+ <form
84
+ class="flex flex-col items-stretch gap-5"
85
+ novalidate
86
+ @submit.prevent="onSubmit"
87
+ >
88
+ <h2 class="text-center text-2xl font-bold">
89
+ {{ t('modals.id.newPassword.title') }}
90
+ </h2>
91
+ <p class="text-center text-sm">
92
+ {{ t('modals.id.newPassword.content') }}
93
+ </p>
94
+
95
+ <a-input-password
96
+ v-model="form.password"
97
+ :label="t('modals.id.newPassword.placeholder.1')"
98
+ :error="getError('password')"
99
+ />
100
+
101
+ <a-alert color="blue">
102
+ {{ t('modals.id.newPassword.alert') }}
103
+ </a-alert>
104
+
105
+ <a-input-password
106
+ v-model="form.password_confirmation"
107
+ :label="t('modals.id.newPassword.placeholder.2')"
108
+ :error="getError('password_confirmation')"
109
+ />
110
+
111
+ <a-button
112
+ :loading="loading"
113
+ >
114
+ {{ t('actions.set') }}
115
+ </a-button>
116
+
117
+ <p class="text-center text-sm">
118
+ {{ t('reuse.or') }}
119
+ </p>
120
+
121
+ <a-button
122
+ type="button"
123
+ view="outline"
124
+ @click="newPasswordModal = false"
125
+ >
126
+ {{ t('actions.cancel') }}
127
+ </a-button>
128
+ </form>
129
+ </template>
@@ -0,0 +1,155 @@
1
+ <script lang="ts" setup>
2
+ const emit = defineEmits<{
3
+ (e: 'onCompleted', value: string): void
4
+ }>()
5
+
6
+ const length = 6 // Длина OTP
7
+ const showError = defineModel('error')
8
+ const otp = defineModel()
9
+ const otpRefs = ref([])
10
+ const { t } = useI18n()
11
+
12
+ function checkCompletion() {
13
+ if (otp.value.every(val => val !== '')) {
14
+ emit('onCompleted', otp.value.join(''))
15
+ }
16
+ }
17
+
18
+ function onInput(event, index) {
19
+ const value = event.target.value
20
+ showError.value = false
21
+
22
+ // Разрешить только цифры
23
+ if (/\D/.test(value)) {
24
+ otp.value[index] = ''
25
+ return
26
+ }
27
+
28
+ otp.value[index] = value
29
+ checkCompletion()
30
+
31
+ // Перемещаем фокус на следующий элемент, если введен символ
32
+ if (value && index < length - 1) {
33
+ // setTimeout(() => otpRefs.value[index + 1]?.focus(), 0)
34
+ otpRefs.value[index + 1]?.focus()
35
+ }
36
+ }
37
+
38
+ function onBackspace(index, event) {
39
+ showError.value = false
40
+ if (otp.value[index] || index > 0) {
41
+ if (otp.value[index] !== '') {
42
+ otp.value[index] = ''
43
+ return
44
+ }
45
+ otp.value[index] = ''
46
+ if (index > 0) {
47
+ otp.value[index - 1] = ''
48
+ setTimeout(() => {
49
+ const prevInput = otpRefs.value[index - 1]
50
+ prevInput?.focus()
51
+ prevInput?.setSelectionRange(0, 1)
52
+ }, 0)
53
+ }
54
+ event.preventDefault()
55
+ }
56
+ }
57
+
58
+ function onPaste(event) {
59
+ showError.value = false
60
+ const pastedData = event.clipboardData.getData('text').slice(0, length)
61
+
62
+ if (/\D/.test(pastedData)) {
63
+ event.preventDefault()
64
+ return
65
+ }
66
+
67
+ // Заполняем ячейки значениями из буфера обмена
68
+ pastedData.split('').forEach((char, index) => {
69
+ if (index < length) {
70
+ otp.value[index] = char
71
+ }
72
+ })
73
+ checkCompletion()
74
+
75
+ // Ставим фокус на следующий незаполненный элемент
76
+ const firstEmptyIndex = otp.value.findIndex(val => val === '')
77
+ if (firstEmptyIndex !== -1) {
78
+ otpRefs.value[firstEmptyIndex]?.focus()
79
+ }
80
+ else {
81
+ otpRefs.value[length - 1]?.focus()
82
+ }
83
+
84
+ event.preventDefault()
85
+ }
86
+
87
+ // Фокус на первый пустой инпут при фокусе если все пустые
88
+ function autoTarget() {
89
+ for (let i = 0; i < length; i++) {
90
+ if (otp.value[i].length) {
91
+ return
92
+ }
93
+ }
94
+ otpRefs.value[0]?.focus()
95
+ }
96
+
97
+ // макс сайз = 1 и если в ячейке есть цифра, она перезаписывается
98
+ function inputValid(index: number, event: InputEvent) {
99
+ if (otp.value[index]) {
100
+ event.target.value = ''
101
+ }
102
+ }
103
+
104
+ function moveFocus(index: number, to: string) {
105
+ if (to === 'left') {
106
+ if (index > 0) {
107
+ otpRefs.value[index - 1]?.focus()
108
+ }
109
+ }
110
+ else if (to === 'right') {
111
+ if (index < length - 1) {
112
+ otpRefs.value[index + 1]?.focus()
113
+ }
114
+ }
115
+ }
116
+ </script>
117
+
118
+ <template>
119
+ <div>
120
+ <div class="mb-1 flex gap-2">
121
+ <input
122
+ v-for="(digit, index) in otp"
123
+ :key="index"
124
+ :ref="el => otpRefs[index] = el"
125
+ v-model="otp[index]"
126
+ type="text"
127
+ maxlength="1"
128
+ class="size-10 rounded bg-deepblue-900/5 text-center text-lg caret-transparent focus:outline-none dark:bg-[#E3E5E80D]"
129
+ :class="{
130
+ 'border border-[#E74135] bg-[#E741351A] dark:border-[#F47E75] dark:bg-[#F47E751A]': showError,
131
+ 'focus:border focus:border-blue-700 dark:focus:border-blue-500': !showError,
132
+ }"
133
+ @input="onInput($event, index)"
134
+ @keydown.backspace="onBackspace(index, $event)"
135
+ @keydown.enter.prevent
136
+ @paste="onPaste"
137
+ @keydown.left="moveFocus(index, 'left')"
138
+ @keydown.right="moveFocus(index, 'right')"
139
+ @focus="autoTarget"
140
+ @beforeinput="inputValid(index, $event)"
141
+ >
142
+ </div>
143
+ <a-alert
144
+ v-show="showError"
145
+ color="red"
146
+ size="sm"
147
+ icon-type="triangle"
148
+ >
149
+ {{ t('error.otp') }}
150
+ </a-alert>
151
+ </div>
152
+ </template>
153
+
154
+ <style scoped>
155
+ </style>