itube-specs 0.0.759 → 0.0.761
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/composables/use-meta.ts +15 -7
- package/composables/use-model-filter-chips.ts +50 -46
- package/package.json +1 -1
- package/components/auth/s-auth-icon.vue +0 -20
- package/components/auth/s-auth-login.vue +0 -120
- package/components/auth/s-auth-popup.vue +0 -69
- package/components/auth/s-auth-recovery.vue +0 -104
- package/components/auth/s-auth-register.vue +0 -176
- package/components/cards/s-video-mini-card.vue +0 -62
- package/components/grids/s-grid-categories.vue +0 -20
- package/components/grids/s-grid-channels.vue +0 -23
- package/components/grids/s-grid-models.vue +0 -25
- package/components/grids/s-grid-playlists.vue +0 -21
- package/components/grids/s-grid-videos.vue +0 -75
- package/components/page-components/s-breadcrumbs.vue +0 -44
- package/components/page-components/s-expand-row.vue +0 -113
- package/components/page-components/s-filter-button.vue +0 -41
- package/components/page-components/s-filter-chips.vue +0 -34
- package/components/page-components/s-filter-page.vue +0 -185
- package/components/page-components/s-filter-popup.vue +0 -155
- package/components/page-components/s-filter-slider.vue +0 -145
- package/components/page-components/s-filter-videos-chips.vue +0 -105
- package/components/page-components/s-filter.vue +0 -357
- package/components/page-components/s-footer-models.vue +0 -28
- package/components/page-components/s-info-grid.vue +0 -89
- package/components/page-components/s-info-socials.vue +0 -33
- package/components/page-components/s-like.vue +0 -121
- package/components/page-components/s-model-filters.vue +0 -235
- package/components/page-components/s-navigation-links.vue +0 -63
- package/components/page-components/s-pagination.vue +0 -214
- package/components/page-components/s-report.vue +0 -267
- package/components/page-components/s-section-title.vue +0 -35
- package/components/page-components/s-share.vue +0 -122
- package/components/playlist/s-playlist-add.vue +0 -299
- package/components/playlist/s-playlist-delete-video.vue +0 -79
- package/components/playlist/s-playlist-edit.vue +0 -215
- package/components/playlist/s-playlist-input.vue +0 -88
- package/components/playlist/s-playlist-like.vue.unused +0 -96
- package/components/playlist/s-playlist-new.vue.unused +0 -57
- package/components/playlist/s-playlist-private-toggle.vue +0 -20
- package/components/ui/s-avatar.vue +0 -33
- package/components/ui/s-button.vue +0 -51
- package/components/ui/s-checkbox.vue +0 -57
- package/components/ui/s-chips.vue +0 -142
- package/components/ui/s-count.vue +0 -17
- package/components/ui/s-dropdown.vue +0 -152
- package/components/ui/s-icon.vue +0 -19
- package/components/ui/s-img.vue +0 -59
- package/components/ui/s-input.vue +0 -181
- package/components/ui/s-label.vue +0 -20
- package/components/ui/s-link.vue +0 -46
- package/components/ui/s-notification.vue +0 -72
- package/components/ui/s-popup.vue +0 -119
- package/components/ui/s-radio.vue +0 -56
- package/components/ui/s-select.vue +0 -105
- package/components/ui/s-slider.vue +0 -67
- package/components/ui/s-snackbar.vue +0 -27
- package/components/ui/s-timestamp.vue +0 -59
- package/components/ui/s-toggle.vue +0 -30
- package/components/ui/s-tooltip.vue +0 -57
- package/components/video/s-video-autoplay.vue +0 -29
package/composables/use-meta.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { IChipsItem } from '../types';
|
|
|
14
14
|
* @param thirdText - третий подставляемый текст
|
|
15
15
|
* @param fourthText - четвёртый подставляемый текст
|
|
16
16
|
*/
|
|
17
|
-
export function useMeta(t, page: string, brandName: string, sortOptions?: IChipsItem[], text?: Ref<string>, secondText?: Ref<string>, thirdText?: Ref<string>, fourthText?: Ref<string>) {
|
|
17
|
+
export function useMeta(t: (key: string, params?: Record<string, unknown>) => string, page: string, brandName: string, sortOptions?: IChipsItem[], text?: Ref<string>, secondText?: Ref<string>, thirdText?: Ref<string>, fourthText?: Ref<string>) {
|
|
18
18
|
const route = useRoute();
|
|
19
19
|
|
|
20
20
|
const sortType = computed(() => {
|
|
@@ -48,12 +48,20 @@ export function useMeta(t, page: string, brandName: string, sortOptions?: IChips
|
|
|
48
48
|
const metaTitle = computed(() => getPath('title'));
|
|
49
49
|
const h1 = computed(() => getPath('h1'));
|
|
50
50
|
|
|
51
|
-
const meta = computed(() =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
const meta = computed(() => {
|
|
52
|
+
const description = getPath('meta_description');
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
title: metaTitle.value,
|
|
56
|
+
meta: [
|
|
57
|
+
{ name: 'description', content: description },
|
|
58
|
+
{ property: 'og:title', content: metaTitle.value },
|
|
59
|
+
{ property: 'og:description', content: description },
|
|
60
|
+
{ name: 'twitter:title', content: metaTitle.value },
|
|
61
|
+
{ name: 'twitter:description', content: description },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
});
|
|
57
65
|
|
|
58
66
|
return {
|
|
59
67
|
meta,
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import type { IChipsItem, IModelFilter, IModelFilterOptions } from '../types'
|
|
2
|
-
import type { LocationQuery } from '#vue-router'
|
|
3
|
-
import { getMonth } from '../runtime'
|
|
1
|
+
import type { IChipsItem, IModelFilter, IModelFilterOptions } from '../types';
|
|
2
|
+
import type { LocationQuery } from '#vue-router';
|
|
3
|
+
import { getMonth } from '../runtime';
|
|
4
4
|
import type { Ref } from 'vue';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Формирует вычисляемый список чипсов из активных filter_* параметров в query маршрута.
|
|
8
|
-
* @param filters - список схем фильтров модели
|
|
9
|
-
* @param route - текущий маршрут с query
|
|
10
|
-
* @param t - функция перевода (i18n)
|
|
11
|
-
*/
|
|
12
6
|
export function useFilterChipsItems(
|
|
13
7
|
filters: Ref<IModelFilter[]>,
|
|
14
8
|
route: { query: LocationQuery },
|
|
@@ -21,52 +15,58 @@ export function useFilterChipsItems(
|
|
|
21
15
|
item
|
|
22
16
|
.replace('filter_', '')
|
|
23
17
|
.replace(/_/g, ' ')
|
|
24
|
-
)
|
|
18
|
+
);
|
|
25
19
|
|
|
26
20
|
const groups = Object.values(
|
|
27
21
|
queryItems.reduce((acc: Record<string, string[]>, str: string) => {
|
|
28
|
-
const parts = str.split(' ')
|
|
29
|
-
const last = parts.at(-1)
|
|
22
|
+
const parts = str.split(' ');
|
|
23
|
+
const last = parts.at(-1);
|
|
30
24
|
|
|
31
25
|
const key =
|
|
32
26
|
last === 'from' || last === 'to'
|
|
33
27
|
? parts.slice(0, -1).join(' ')
|
|
34
|
-
: str
|
|
28
|
+
: str;
|
|
35
29
|
|
|
36
|
-
acc[key] ??= []
|
|
30
|
+
acc[key] ??= [];
|
|
37
31
|
|
|
38
|
-
if (last === 'from') acc[key].unshift(str)
|
|
39
|
-
else acc[key].push(str)
|
|
32
|
+
if (last === 'from') acc[key].unshift(str);
|
|
33
|
+
else acc[key].push(str);
|
|
40
34
|
|
|
41
|
-
return acc
|
|
35
|
+
return acc;
|
|
42
36
|
}, {})
|
|
43
|
-
)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const findFilter = (items: string[]) => {
|
|
40
|
+
const key = items[0].replace(' from', '').replace(' to', '');
|
|
41
|
+
return filters.value.find(f => f.name.replace(/_/g, ' ') === key);
|
|
42
|
+
};
|
|
44
43
|
|
|
45
44
|
const getValue = (item: string, index: number) => {
|
|
46
45
|
const filter = filters.value.find(filter =>
|
|
47
46
|
filter.name ===
|
|
48
47
|
item
|
|
49
|
-
.replace(/ /g,
|
|
48
|
+
.replace(/ /g,
|
|
49
|
+
'_')
|
|
50
50
|
.replace('_from', '')
|
|
51
51
|
.replace('_to', '')
|
|
52
|
-
)
|
|
52
|
+
);
|
|
53
53
|
|
|
54
54
|
const defaultValue =
|
|
55
55
|
index === 0
|
|
56
56
|
? filter?.options[0]
|
|
57
|
-
: filter?.options.at(-1)
|
|
57
|
+
: filter?.options.at(-1);
|
|
58
58
|
|
|
59
|
-
const value = route.query[`filter_${item.replace(/ /g, '_')}`]
|
|
59
|
+
const value = route.query[`filter_${item.replace(/ /g, '_')}`];
|
|
60
60
|
|
|
61
61
|
if (item.includes('month')) {
|
|
62
62
|
return getMonth(
|
|
63
63
|
t,
|
|
64
64
|
Number(value ?? defaultValue?.name) - 1
|
|
65
|
-
)
|
|
65
|
+
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
if (!isNaN(Number(value))) {
|
|
69
|
-
return value
|
|
69
|
+
return value;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
return (
|
|
@@ -75,31 +75,35 @@ export function useFilterChipsItems(
|
|
|
75
75
|
option.name === value
|
|
76
76
|
)?.title ??
|
|
77
77
|
defaultValue?.title
|
|
78
|
-
)
|
|
79
|
-
}
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
80
|
|
|
81
81
|
const getValueText = (items: string[]) =>
|
|
82
|
-
[...new Set(items.map(getValue))].join(' - ')
|
|
83
|
-
|
|
84
|
-
const chipsTitle = (items: string[]) => {
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
82
|
+
[...new Set(items.map(getValue).filter(v => v !== undefined && v !== null && v !== ''))].join(' - ');
|
|
83
|
+
|
|
84
|
+
const chipsTitle = (items: string[], filter: IModelFilter) => {
|
|
85
|
+
const valueText = getValueText(items);
|
|
86
|
+
const text = `${filter.title}: ${valueText}`;
|
|
87
|
+
return text.length > 30 ? valueText : text;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return groups
|
|
91
|
+
.map(item => {
|
|
92
|
+
const filter = findFilter(item);
|
|
93
|
+
if (!filter) return null;
|
|
94
|
+
const valueText = getValueText(item);
|
|
95
|
+
if (!valueText) return null;
|
|
96
|
+
return {
|
|
97
|
+
title: chipsTitle(item, filter),
|
|
98
|
+
value: item.map(
|
|
99
|
+
sub => `filter_${sub.replace(/ /g, '_')}`
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
})
|
|
103
|
+
.filter((c): c is IChipsItem => c !== null);
|
|
104
|
+
});
|
|
101
105
|
|
|
102
106
|
return {
|
|
103
107
|
chipsItems,
|
|
104
|
-
}
|
|
108
|
+
};
|
|
105
109
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="s-auth-icon">
|
|
3
|
-
<SIcon
|
|
4
|
-
class="s-auth-icon__icon"
|
|
5
|
-
:name="icon"
|
|
6
|
-
size="24"
|
|
7
|
-
/>
|
|
8
|
-
</div>
|
|
9
|
-
</template>
|
|
10
|
-
|
|
11
|
-
<script setup lang="ts">
|
|
12
|
-
const props = withDefaults(defineProps<{
|
|
13
|
-
icon?: string
|
|
14
|
-
}>(), {
|
|
15
|
-
icon: 'user-filled'
|
|
16
|
-
})
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<style scoped lang="scss">
|
|
20
|
-
</style>
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="s-auth-login"
|
|
4
|
-
:class="{'_loading': loading}"
|
|
5
|
-
>
|
|
6
|
-
|
|
7
|
-
<SAuthIcon class="s-auth-login__icon"/>
|
|
8
|
-
|
|
9
|
-
<SInput
|
|
10
|
-
v-model="form.username"
|
|
11
|
-
:label="t('auth.username')"
|
|
12
|
-
:placeholder="t('auth.username')"
|
|
13
|
-
hide-label
|
|
14
|
-
name="login-name"
|
|
15
|
-
:error="error.username"
|
|
16
|
-
@update:error="(val: boolean) => error.username = val"
|
|
17
|
-
/>
|
|
18
|
-
<SInput
|
|
19
|
-
v-model="form.password_hash"
|
|
20
|
-
:label="t('auth.password')"
|
|
21
|
-
:placeholder="t('auth.password')"
|
|
22
|
-
hide-label
|
|
23
|
-
type="password"
|
|
24
|
-
:error="error.password_hash"
|
|
25
|
-
@update:error="(val: boolean) => error.password_hash = val"
|
|
26
|
-
/>
|
|
27
|
-
|
|
28
|
-
<div class="s-auth-login__buttons">
|
|
29
|
-
<SButton
|
|
30
|
-
class="s-auth-login__login"
|
|
31
|
-
wide
|
|
32
|
-
size="l"
|
|
33
|
-
:disabled="loading"
|
|
34
|
-
theme="primary"
|
|
35
|
-
@click="login"
|
|
36
|
-
>{{ t('auth.log_in') }}
|
|
37
|
-
</SButton>
|
|
38
|
-
<button
|
|
39
|
-
class="s-auth-login__forgot"
|
|
40
|
-
type="button"
|
|
41
|
-
@click="onForgotClick"
|
|
42
|
-
>
|
|
43
|
-
{{ t('auth.forgot') }}
|
|
44
|
-
</button>
|
|
45
|
-
|
|
46
|
-
<p class="s-auth-login__back">
|
|
47
|
-
{{ t('register_text') }}
|
|
48
|
-
<button
|
|
49
|
-
class="s-auth-login__back-button"
|
|
50
|
-
type="button"
|
|
51
|
-
@click="onCreateClick"
|
|
52
|
-
>
|
|
53
|
-
{{ t('auth.sign_up') }}
|
|
54
|
-
</button>
|
|
55
|
-
</p>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</template>
|
|
59
|
-
|
|
60
|
-
<script setup lang="ts">
|
|
61
|
-
import { validatePassword, validateUsername } from '../../runtime';
|
|
62
|
-
import { AuthorizationApiService } from '~/services/api/authorization.service';
|
|
63
|
-
|
|
64
|
-
const { t } = useI18n();
|
|
65
|
-
|
|
66
|
-
const form = ref({
|
|
67
|
-
username: '',
|
|
68
|
-
password_hash: '',
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const error = ref({
|
|
72
|
-
username: false,
|
|
73
|
-
password_hash: false,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const emit = defineEmits<{
|
|
77
|
-
(eventName: 'forgot'): void
|
|
78
|
-
(eventName: 'create'): void
|
|
79
|
-
}>();
|
|
80
|
-
|
|
81
|
-
function onForgotClick() {
|
|
82
|
-
emit('forgot');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function onCreateClick() {
|
|
86
|
-
emit('create');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const loading = ref(false);
|
|
90
|
-
|
|
91
|
-
const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
|
|
92
|
-
|
|
93
|
-
async function login() {
|
|
94
|
-
resetSnackbar();
|
|
95
|
-
error.value.username = false;
|
|
96
|
-
error.value.password_hash = false;
|
|
97
|
-
|
|
98
|
-
if (!validateUsername(form.value.username)) {
|
|
99
|
-
error.value.username = true;
|
|
100
|
-
showError('error_username');
|
|
101
|
-
}
|
|
102
|
-
if (!validatePassword(form.value.password_hash)) {
|
|
103
|
-
error.value.password_hash = true;
|
|
104
|
-
showError('error_password');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!error.value.username && !error.value.password_hash) {
|
|
108
|
-
try {
|
|
109
|
-
loading.value = true;
|
|
110
|
-
await useUser(AuthorizationApiService).login(form.value);
|
|
111
|
-
useAuthPopup().closeAuthPopup();
|
|
112
|
-
showSuccess('Welcome back!');
|
|
113
|
-
} catch (error) {
|
|
114
|
-
setErrorState(error);
|
|
115
|
-
} finally {
|
|
116
|
-
loading.value = false;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
</script>
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-if="isAuthPopupOpen" class="s-auth-popup__decorate"/>
|
|
3
|
-
<transition mode="out-in">
|
|
4
|
-
<SPopup
|
|
5
|
-
v-if="currentStep === EAuthSteps.Registration && isAuthPopupOpen"
|
|
6
|
-
v-model="isAuthPopupOpen"
|
|
7
|
-
transparent-backdrop
|
|
8
|
-
:back="isBackShow"
|
|
9
|
-
class="s-auth-popup"
|
|
10
|
-
@back="currentStep -= 1"
|
|
11
|
-
>
|
|
12
|
-
<template #title>{{ $t('auth.create_new') }}</template>
|
|
13
|
-
<SAuthRegister
|
|
14
|
-
:additional-text="additionalText"
|
|
15
|
-
@login="onLoginClick"
|
|
16
|
-
/>
|
|
17
|
-
</SPopup>
|
|
18
|
-
</transition>
|
|
19
|
-
|
|
20
|
-
<transition mode="out-in">
|
|
21
|
-
<SPopup
|
|
22
|
-
v-if="currentStep === EAuthSteps.Login && isAuthPopupOpen"
|
|
23
|
-
v-model="isAuthPopupOpen"
|
|
24
|
-
transparent-backdrop
|
|
25
|
-
:back="isBackShow"
|
|
26
|
-
class="s-auth-popup"
|
|
27
|
-
@back="currentStep -= 1"
|
|
28
|
-
>
|
|
29
|
-
<template #title>{{ $t('auth.welcome_back') }}</template>
|
|
30
|
-
<SAuthLogin @forgot="onRecoveryClick" @create="currentStep -= 1"/>
|
|
31
|
-
</SPopup>
|
|
32
|
-
</transition>
|
|
33
|
-
|
|
34
|
-
<transition mode="out-in">
|
|
35
|
-
<SPopup
|
|
36
|
-
v-if="currentStep === EAuthSteps.Recovery && isAuthPopupOpen"
|
|
37
|
-
v-model="isAuthPopupOpen"
|
|
38
|
-
transparent-backdrop
|
|
39
|
-
:back="isBackShow"
|
|
40
|
-
class="s-auth-popup"
|
|
41
|
-
@back="currentStep -= 1"
|
|
42
|
-
>
|
|
43
|
-
<template #title>{{ $t('auth.recovery_title')}}</template>
|
|
44
|
-
<SAuthRecovery @login="onLoginClick"/>
|
|
45
|
-
</SPopup>
|
|
46
|
-
</transition>
|
|
47
|
-
</template>
|
|
48
|
-
|
|
49
|
-
<script setup lang="ts">
|
|
50
|
-
import { EAuthSteps } from '../../runtime';
|
|
51
|
-
|
|
52
|
-
const { isAuthPopupOpen, currentStep, additionalText } = useAuthPopup()
|
|
53
|
-
|
|
54
|
-
const backShow = ref(false)
|
|
55
|
-
|
|
56
|
-
function onLoginClick(): void {
|
|
57
|
-
currentStep.value = EAuthSteps.Login
|
|
58
|
-
backShow.value = true
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function onRecoveryClick(): void {
|
|
62
|
-
currentStep.value = EAuthSteps.Recovery
|
|
63
|
-
backShow.value = true
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const isBackShow = computed(() => {
|
|
67
|
-
return currentStep.value !== EAuthSteps.Registration && backShow.value
|
|
68
|
-
})
|
|
69
|
-
</script>
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="s-auth-recovery"
|
|
4
|
-
:class="{'_loading': loading}"
|
|
5
|
-
>
|
|
6
|
-
|
|
7
|
-
<SAuthIcon
|
|
8
|
-
class="s-auth-recovery__icon"
|
|
9
|
-
icon="envelope"
|
|
10
|
-
/>
|
|
11
|
-
|
|
12
|
-
<p class="s-auth-recovery__text">{{ t('auth.recovery_text') }}</p>
|
|
13
|
-
<SInput
|
|
14
|
-
v-model="form.email"
|
|
15
|
-
:label="$t('email')"
|
|
16
|
-
:placeholder="$t('email')"
|
|
17
|
-
hide-label
|
|
18
|
-
inputmode="email"
|
|
19
|
-
autocomplete="email"
|
|
20
|
-
:error="error.email"
|
|
21
|
-
@update:error="(val: boolean) => error.email = val"
|
|
22
|
-
/>
|
|
23
|
-
|
|
24
|
-
<SButton
|
|
25
|
-
class="s-auth-recovery__button"
|
|
26
|
-
wide
|
|
27
|
-
size="l"
|
|
28
|
-
:disabled="loading"
|
|
29
|
-
theme="primary"
|
|
30
|
-
@click="submit"
|
|
31
|
-
>{{ t('auth.get_recovery') }}
|
|
32
|
-
</SButton>
|
|
33
|
-
<button
|
|
34
|
-
class="s-auth-recovery__login"
|
|
35
|
-
type="button"
|
|
36
|
-
@click="onLoginClick"
|
|
37
|
-
>{{ t('auth.back_to_login') }}
|
|
38
|
-
</button>
|
|
39
|
-
</div>
|
|
40
|
-
</template>
|
|
41
|
-
|
|
42
|
-
<script setup lang="ts">
|
|
43
|
-
import type { IPasswordForm } from '../../types';
|
|
44
|
-
import { validateEmail } from '../../runtime';
|
|
45
|
-
import { AuthorizationApiService } from '~/services/api/authorization.service';
|
|
46
|
-
|
|
47
|
-
const { initRecaptcha, getRecaptchaToken } = useRecaptcha();
|
|
48
|
-
|
|
49
|
-
const { t } = useI18n();
|
|
50
|
-
|
|
51
|
-
const form = ref({
|
|
52
|
-
email: '',
|
|
53
|
-
token: '',
|
|
54
|
-
} as IPasswordForm)
|
|
55
|
-
|
|
56
|
-
const error = ref({
|
|
57
|
-
email: false,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const emit = defineEmits<{
|
|
61
|
-
(eventName: 'login'): void
|
|
62
|
-
}>()
|
|
63
|
-
|
|
64
|
-
function onLoginClick() {
|
|
65
|
-
emit('login')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const loading = ref(false)
|
|
69
|
-
|
|
70
|
-
const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
|
|
71
|
-
|
|
72
|
-
// Загружаем reCAPTCHA при открытии формы (onMounted) — даёт Google время собрать данные о поведении
|
|
73
|
-
onMounted(async () => {
|
|
74
|
-
try {
|
|
75
|
-
await initRecaptcha();
|
|
76
|
-
console.log('reCAPTCHA готов для этой формы');
|
|
77
|
-
} catch (err) {
|
|
78
|
-
console.error('Ошибка инициализации reCAPTCHA:', err);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
async function submit() {
|
|
83
|
-
resetSnackbar()
|
|
84
|
-
error.value.email = false;
|
|
85
|
-
|
|
86
|
-
if (!validateEmail(form.value.email)) {
|
|
87
|
-
error.value.email = true;
|
|
88
|
-
showError('error_email');
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
loading.value = true;
|
|
94
|
-
form.value.token = await getRecaptchaToken('password_reset');
|
|
95
|
-
await useUser(AuthorizationApiService).password(form.value);
|
|
96
|
-
useAuthPopup().closeAuthPopup();
|
|
97
|
-
showSuccess('Check your email');
|
|
98
|
-
} catch (error) {
|
|
99
|
-
setErrorState(error);
|
|
100
|
-
} finally {
|
|
101
|
-
loading.value = false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
</script>
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ClientOnly>
|
|
3
|
-
<div
|
|
4
|
-
class="s-auth-register"
|
|
5
|
-
:class="{'_loading': loading}"
|
|
6
|
-
>
|
|
7
|
-
<SAuthIcon class="s-auth-register__icon" />
|
|
8
|
-
<p
|
|
9
|
-
v-if="additionalText"
|
|
10
|
-
class="s-auth-register__text"
|
|
11
|
-
>{{ t(additionalText) }}
|
|
12
|
-
</p>
|
|
13
|
-
<SInput
|
|
14
|
-
v-model="form.email"
|
|
15
|
-
:placeholder="t('email')"
|
|
16
|
-
:label="t('email')"
|
|
17
|
-
hide-label
|
|
18
|
-
inputmode="email"
|
|
19
|
-
autocomplete="email"
|
|
20
|
-
:error="error.email"
|
|
21
|
-
@update:error="(val: boolean) => error.email = val"
|
|
22
|
-
/>
|
|
23
|
-
<SInput
|
|
24
|
-
v-model="form.username"
|
|
25
|
-
:label="t('login')"
|
|
26
|
-
:placeholder="t('login')"
|
|
27
|
-
hide-label
|
|
28
|
-
name="login-name"
|
|
29
|
-
:error="error.username"
|
|
30
|
-
@update:error="(val: boolean) => error.username = val"
|
|
31
|
-
/>
|
|
32
|
-
<SInput
|
|
33
|
-
v-model="form.password_hash"
|
|
34
|
-
:label="t('auth.password')"
|
|
35
|
-
:placeholder="t('auth.password')"
|
|
36
|
-
hide-label
|
|
37
|
-
type="password"
|
|
38
|
-
:error="error.password_hash"
|
|
39
|
-
autocomplete="new-password"
|
|
40
|
-
@update:error="(val: boolean) => error.password_hash = val"
|
|
41
|
-
/>
|
|
42
|
-
|
|
43
|
-
<div class="s-auth-register__buttons">
|
|
44
|
-
<SButton
|
|
45
|
-
:disabled="loading"
|
|
46
|
-
wide
|
|
47
|
-
size="l"
|
|
48
|
-
theme="primary"
|
|
49
|
-
@click="submit"
|
|
50
|
-
>{{ t('auth.sign_up') }}
|
|
51
|
-
</SButton>
|
|
52
|
-
<p class="s-auth-register__login">
|
|
53
|
-
{{ t('login_text') }}
|
|
54
|
-
<button
|
|
55
|
-
class="s-auth-register__login-button"
|
|
56
|
-
type="button"
|
|
57
|
-
@click="onLoginClick">
|
|
58
|
-
{{ t('auth.log_in') }}
|
|
59
|
-
</button>
|
|
60
|
-
</p>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<p class="s-auth-register__agreement">{{ t('auth.agree_begin') }}
|
|
64
|
-
<NuxtLink
|
|
65
|
-
class="s-auth-register__agreement-link"
|
|
66
|
-
to="/terms"
|
|
67
|
-
target="_blank"
|
|
68
|
-
>{{ t('auth.agree_terms') }}
|
|
69
|
-
</NuxtLink>
|
|
70
|
-
{{ t('auth.agree_and') }}
|
|
71
|
-
<NuxtLink
|
|
72
|
-
class="s-auth-register__agreement-link"
|
|
73
|
-
to="/policy"
|
|
74
|
-
target="_blank"
|
|
75
|
-
>{{ t('auth.agree_policy') }}
|
|
76
|
-
</NuxtLink>
|
|
77
|
-
</p>
|
|
78
|
-
</div>
|
|
79
|
-
</ClientOnly>
|
|
80
|
-
</template>
|
|
81
|
-
|
|
82
|
-
<script setup lang="ts">
|
|
83
|
-
import type { IRegistrateForm } from '../../types';
|
|
84
|
-
import { validateEmail, validatePassword, validateUsername } from '../../runtime';
|
|
85
|
-
import { AuthorizationApiService } from '~/services/api/authorization.service';
|
|
86
|
-
|
|
87
|
-
const { initRecaptcha, getRecaptchaToken } = useRecaptcha();
|
|
88
|
-
|
|
89
|
-
defineProps<{
|
|
90
|
-
additionalText?: string;
|
|
91
|
-
}>()
|
|
92
|
-
|
|
93
|
-
const { t } = useI18n();
|
|
94
|
-
|
|
95
|
-
const form = ref({
|
|
96
|
-
username: '',
|
|
97
|
-
email: '',
|
|
98
|
-
password_hash: '',
|
|
99
|
-
token: '',
|
|
100
|
-
} as IRegistrateForm)
|
|
101
|
-
|
|
102
|
-
const error = ref({
|
|
103
|
-
username: false,
|
|
104
|
-
email: false,
|
|
105
|
-
password_hash: false,
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
const emit = defineEmits<{
|
|
109
|
-
(eventName: 'login'): void
|
|
110
|
-
}>()
|
|
111
|
-
|
|
112
|
-
function onLoginClick() {
|
|
113
|
-
emit('login')
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const loading = ref(false);
|
|
117
|
-
|
|
118
|
-
const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
|
|
119
|
-
// Загружаем reCAPTCHA при открытии формы (onMounted) — даёт Google время собрать данные о поведении
|
|
120
|
-
onMounted(async () => {
|
|
121
|
-
try {
|
|
122
|
-
await initRecaptcha();
|
|
123
|
-
console.log('reCAPTCHA готов для этой формы');
|
|
124
|
-
} catch (err) {
|
|
125
|
-
console.error('Ошибка инициализации reCAPTCHA:', err);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
function validateForm(): boolean {
|
|
130
|
-
const { username, email, password_hash } = form.value;
|
|
131
|
-
|
|
132
|
-
const errors = {
|
|
133
|
-
username: !validateUsername(username),
|
|
134
|
-
email: !validateEmail(email),
|
|
135
|
-
password_hash: !validatePassword(password_hash),
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
if (!validateEmail(email)) showError('error_email');
|
|
139
|
-
if (!validatePassword(password_hash)) showError('error_password');
|
|
140
|
-
if (!validateUsername(username)) showError('error_username');
|
|
141
|
-
|
|
142
|
-
error.value = errors;
|
|
143
|
-
|
|
144
|
-
return !Object.values(errors).some(Boolean);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function submit() {
|
|
148
|
-
resetSnackbar();
|
|
149
|
-
if (!validateForm()) return;
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
loading.value = true;
|
|
153
|
-
|
|
154
|
-
// Уникальное действие для каждой формы
|
|
155
|
-
// здесь 'register', в других формах — 'password_reset' или 'contact_form'
|
|
156
|
-
form.value.token = await getRecaptchaToken('register');
|
|
157
|
-
|
|
158
|
-
await useUser(AuthorizationApiService).register(form.value);
|
|
159
|
-
|
|
160
|
-
// Создаем сразу Favorites плейлист, возможно костыль убрать когда будет нормальная логика с беком
|
|
161
|
-
// const { FAVORITES_KEY } = useFavorites();
|
|
162
|
-
// await PlaylistsApiService.postPlaylist(FAVORITES_KEY, EPlaylistType.Private);
|
|
163
|
-
|
|
164
|
-
useAuthPopup().closeAuthPopup();
|
|
165
|
-
showSuccess('Registration success');
|
|
166
|
-
} catch (err) {
|
|
167
|
-
setErrorState(err);
|
|
168
|
-
console.error('Ошибка reCAPTCHA или регистрации:', err);
|
|
169
|
-
} finally {
|
|
170
|
-
loading.value = false;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
</script>
|
|
174
|
-
|
|
175
|
-
<style scoped>
|
|
176
|
-
</style>
|