itube-specs 0.0.747 → 0.0.749

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.
@@ -88,7 +88,7 @@ function onCreateClick() {
88
88
 
89
89
  const loading = ref(false);
90
90
 
91
- const { setErrorState, snackbarText, snackbarTheme, showErrorSnack, resetSnackbar } = useSnackbar();
91
+ const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
92
92
 
93
93
  async function login() {
94
94
  resetSnackbar();
@@ -97,11 +97,11 @@ async function login() {
97
97
 
98
98
  if (!validateUsername(form.value.username)) {
99
99
  error.value.username = true;
100
- showErrorSnack('error_username');
100
+ showError('error_username');
101
101
  }
102
102
  if (!validatePassword(form.value.password_hash)) {
103
103
  error.value.password_hash = true;
104
- showErrorSnack('error_password');
104
+ showError('error_password');
105
105
  }
106
106
 
107
107
  if (!error.value.username && !error.value.password_hash) {
@@ -109,8 +109,7 @@ async function login() {
109
109
  loading.value = true;
110
110
  await useUser(AuthorizationApiService).login(form.value);
111
111
  useAuthPopup().closeAuthPopup();
112
- snackbarTheme.value = 'success';
113
- snackbarText.value = 'Welcome back!';
112
+ showSuccess('Welcome back!');
114
113
  } catch (error) {
115
114
  setErrorState(error);
116
115
  } finally {
@@ -67,7 +67,7 @@ function onLoginClick() {
67
67
 
68
68
  const loading = ref(false)
69
69
 
70
- const { setErrorState, snackbarTheme, snackbarText, showErrorSnack, resetSnackbar } = useSnackbar();
70
+ const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
71
71
 
72
72
  // Загружаем reCAPTCHA при открытии формы (onMounted) — даёт Google время собрать данные о поведении
73
73
  onMounted(async () => {
@@ -85,7 +85,7 @@ async function submit() {
85
85
 
86
86
  if (!validateEmail(form.value.email)) {
87
87
  error.value.email = true;
88
- showErrorSnack('error_email');
88
+ showError('error_email');
89
89
  return;
90
90
  }
91
91
 
@@ -94,8 +94,7 @@ async function submit() {
94
94
  form.value.token = await getRecaptchaToken('password_reset');
95
95
  await useUser(AuthorizationApiService).password(form.value);
96
96
  useAuthPopup().closeAuthPopup();
97
- snackbarTheme.value = 'success';
98
- snackbarText.value = 'Check your email';
97
+ showSuccess('Check your email');
99
98
  } catch (error) {
100
99
  setErrorState(error);
101
100
  } finally {
@@ -115,7 +115,7 @@ function onLoginClick() {
115
115
 
116
116
  const loading = ref(false);
117
117
 
118
- const { setErrorState, snackbarText, snackbarTheme, showErrorSnack, resetSnackbar } = useSnackbar();
118
+ const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
119
119
  // Загружаем reCAPTCHA при открытии формы (onMounted) — даёт Google время собрать данные о поведении
120
120
  onMounted(async () => {
121
121
  try {
@@ -135,9 +135,9 @@ function validateForm(): boolean {
135
135
  password_hash: !validatePassword(password_hash),
136
136
  };
137
137
 
138
- if (!validateEmail(email)) showErrorSnack('error_email');
139
- if (!validatePassword(password_hash)) showErrorSnack('error_password');
140
- if (!validateUsername(username)) showErrorSnack('error_username');
138
+ if (!validateEmail(email)) showError('error_email');
139
+ if (!validatePassword(password_hash)) showError('error_password');
140
+ if (!validateUsername(username)) showError('error_username');
141
141
 
142
142
  error.value = errors;
143
143
 
@@ -162,8 +162,7 @@ async function submit() {
162
162
  // await PlaylistsApiService.postPlaylist(FAVORITES_KEY, EPlaylistType.Private);
163
163
 
164
164
  useAuthPopup().closeAuthPopup();
165
- snackbarTheme.value = 'success';
166
- snackbarText.value = 'Registration success';
165
+ showSuccess('Registration success');
167
166
  } catch (err) {
168
167
  setErrorState(err);
169
168
  console.error('Ошибка reCAPTCHA или регистрации:', err);
@@ -179,7 +179,7 @@ const loading = ref(false);
179
179
  const activeCategory = computed(() => reportFormsScheme.find((subItem) => subItem.subject === activeStep.value))
180
180
  const requiredFields = computed(() => activeCategory.value?.items?.filter(item => item.required).map(item => item.value));
181
181
 
182
- const { snackbarText, snackbarTheme, showErrorSnack, resetSnackbar } = useSnackbar();
182
+ const { showSuccess, showError, resetSnackbar } = useSnackbar();
183
183
 
184
184
  function resetForm() {
185
185
  Object.keys(form.value).forEach((key) => {
@@ -224,15 +224,15 @@ async function submit() {
224
224
 
225
225
  if (item === 'from' && !validateEmail(val as string)) {
226
226
  error.value[ item ] = true;
227
- showErrorSnack('error_email');
227
+ showError('error_email');
228
228
  } else if (item === 'phone' && !validatePhone(val as string)) {
229
229
  error.value[ item ] = !validatePhone(val as string);
230
- showErrorSnack('error_phone');
230
+ showError('error_phone');
231
231
  } else if (['confirmTrue', 'confirmOwner'].includes(item)) {
232
232
  error.value[ item ] = !val;
233
233
  } else {
234
234
  error.value[ item ] = val === undefined || val === null || String(val).trim() === '';
235
- if (error.value[ item ]) showErrorSnack('error_text');
235
+ if (error.value[ item ]) showError('error_text');
236
236
  }
237
237
 
238
238
  if (error.value[ item ]) valid = false;
@@ -256,12 +256,10 @@ async function submit() {
256
256
  })
257
257
  closeReportPopup();
258
258
  resetForm();
259
- snackbarTheme.value = 'success';
260
- snackbarText.value = 'email_send';
259
+ showSuccess('email_send');
261
260
  } catch (error) {
262
261
  console.log('error send email', error);
263
- snackbarText.value = 'email_error';
264
- snackbarTheme.value = 'error';
262
+ showError('email_error');
265
263
  } finally {
266
264
  loading.value = false
267
265
  }
@@ -247,7 +247,7 @@ const postVideoForm = computed(() => ({
247
247
  duration: videoCard.value?.duration ?? 0,
248
248
  }));
249
249
 
250
- const { setErrorState, snackbarText, snackbarTheme } = useSnackbar();
250
+ const { setErrorState, showSuccess, showError } = useSnackbar();
251
251
 
252
252
  async function onSaveClick() {
253
253
  let newPlaylist: IPlaylistShort | undefined;
@@ -262,8 +262,7 @@ async function onSaveClick() {
262
262
 
263
263
  if (nameExists) {
264
264
  nameError.value = true;
265
- snackbarTheme.value = 'error';
266
- snackbarText.value = 'playlist_name_exists';
265
+ showError('playlist_name_exists');
267
266
  return;
268
267
  }
269
268
 
@@ -280,8 +279,7 @@ async function onSaveClick() {
280
279
  newPlaylist,
281
280
  })
282
281
  closePopup();
283
- snackbarTheme.value = 'success';
284
- snackbarText.value = 'video_added';
282
+ showSuccess('video_added');
285
283
  } catch (error) {
286
284
  setErrorState(error)
287
285
  console.log(error, 'error add video to playlist')
@@ -44,7 +44,7 @@
44
44
  import { EPlaylistStep } from '../../runtime';
45
45
 
46
46
  const { isPlaylistEditOpen, closePlaylistEdit, currentStep, selectedPlaylist, deletedVideo } = usePlaylistEdit();
47
- const { snackbarText, snackbarTheme } = useSnackbar();
47
+ const { showSuccess } = useSnackbar();
48
48
 
49
49
  const props = defineProps<{
50
50
  loadingDeleteVideo: boolean
@@ -68,8 +68,7 @@ async function onBinClick() {
68
68
  playlistId: playlistId.value,
69
69
  deletedVideoId: deletedVideo.value?.id || ''
70
70
  })
71
- snackbarTheme.value = 'success';
72
- snackbarText.value = 'video_removed';
71
+ showSuccess('video_removed');
73
72
  closePlaylistEdit();
74
73
  } catch {
75
74
  console.log('error delete')
@@ -144,7 +144,7 @@ const inputValue = ref('');
144
144
  const toggleValue = ref(false);
145
145
 
146
146
  const { isPlaylistEditOpen, closePlaylistEdit, currentStep, selectedPlaylist, openPlaylistEdit } = usePlaylistEdit();
147
- const { snackbarText, snackbarTheme } = useSnackbar();
147
+ const { showSuccess } = useSnackbar();
148
148
 
149
149
  const route = useRoute();
150
150
  const id = computed(() => route.params[ 'playlistId' ] ? String(route.params[ 'playlistId' ]) : selectedPlaylist.value?.id);
@@ -165,8 +165,7 @@ const playlistsData = useState<IPlaylistData>(`data-${EAsyncData.PlaylistById}-$
165
165
  async function onDeleteClick() {
166
166
  try {
167
167
  emit('delete-playlist', id.value || '')
168
- snackbarTheme.value = 'success';
169
- snackbarText.value = 'playlist_deleted';
168
+ showSuccess('playlist_deleted');
170
169
  closePlaylistEdit();
171
170
  } catch {
172
171
  console.log('error delete')
@@ -188,8 +187,7 @@ async function onSaveClick() {
188
187
  playlistsData.value.playlistType = playlistType;
189
188
  }
190
189
  closePlaylistEdit();
191
- snackbarTheme.value= 'success';
192
- snackbarText.value = 'changes_soon';
190
+ showSuccess('changes_soon');
193
191
  } catch {
194
192
  console.log('error save')
195
193
  }
@@ -24,7 +24,7 @@
24
24
  <script setup lang="ts">
25
25
  import { convertString } from '../../runtime';
26
26
 
27
- const { snackbarText, snackbarIcon, snackbarTheme, snackbarTimer, resetSnackbar } = useSnackbar();
27
+ const { snackbarText, snackbarIcon, snackbarTheme, resetSnackbar } = useSnackbar();
28
28
 
29
29
  const {t, te} = useI18n();
30
30
 
@@ -35,16 +35,4 @@ function close() {
35
35
  const convertSnackKey = computed(() =>
36
36
  convertString().toSnakeCase(snackbarText.value)
37
37
  );
38
-
39
- let timerId: ReturnType<typeof setTimeout>
40
-
41
- onMounted(() => {
42
- timerId = setTimeout(() => {
43
- close()
44
- }, snackbarTimer.value)
45
- })
46
-
47
- onBeforeUnmount(() => {
48
- clearTimeout(timerId)
49
- })
50
38
  </script>
@@ -15,47 +15,68 @@ describe('useSnackbar', () => {
15
15
  });
16
16
 
17
17
  it('дефолтное состояние', () => {
18
- const { snackbarTheme, snackbarText, snackbarIcon, snackbarTimer } = useSnackbar();
18
+ const { snackbarTheme, snackbarText, snackbarIcon } = useSnackbar();
19
19
  expect(snackbarTheme.value).toBe('success');
20
20
  expect(snackbarText.value).toBe('');
21
21
  expect(snackbarIcon.value).toBe('check');
22
- expect(snackbarTimer.value).toBe(2000);
23
22
  });
24
23
 
25
- it('setErrorState устанавливает ошибку', () => {
26
- const { setErrorState, snackbarTheme, snackbarIcon, snackbarTimer, snackbarText } = useSnackbar();
27
- setErrorState({ message: 'Something failed' });
28
- expect(snackbarTheme.value).toBe('error');
29
- expect(snackbarIcon.value).toBe('close');
30
- expect(snackbarTimer.value).toBe(4000);
31
- expect(snackbarText.value).toBe('Something failed');
24
+ it('showSuccess показывает и авто-сбрасывает через 2000мс', () => {
25
+ const { showSuccess, snackbarText, snackbarTheme, snackbarIcon } = useSnackbar();
26
+ showSuccess('Done');
27
+ expect(snackbarText.value).toBe('Done');
28
+ expect(snackbarTheme.value).toBe('success');
29
+ expect(snackbarIcon.value).toBe('check');
30
+
31
+ vi.advanceTimersByTime(1999);
32
+ expect(snackbarText.value).toBe('Done');
33
+ vi.advanceTimersByTime(1);
34
+ expect(snackbarText.value).toBe('');
32
35
  });
33
36
 
34
- it('showErrorSnack показывает и авто-сбрасывает', () => {
35
- const { showErrorSnack, snackbarText, snackbarTheme } = useSnackbar();
36
- showErrorSnack('Error!');
37
+ it('showError показывает и авто-сбрасывает через 4000мс', () => {
38
+ const { showError, snackbarText, snackbarTheme, snackbarIcon } = useSnackbar();
39
+ showError('Error!');
37
40
  expect(snackbarText.value).toBe('Error!');
38
41
  expect(snackbarTheme.value).toBe('error');
42
+ expect(snackbarIcon.value).toBe('close');
39
43
 
40
- vi.advanceTimersByTime(4000);
44
+ vi.advanceTimersByTime(3999);
45
+ expect(snackbarText.value).toBe('Error!');
46
+ vi.advanceTimersByTime(1);
41
47
  expect(snackbarText.value).toBe('');
42
48
  expect(snackbarTheme.value).toBe('success');
43
49
  });
44
50
 
45
- it('повторный showErrorSnack сбрасывает предыдущий таймер', () => {
46
- const { showErrorSnack, snackbarText } = useSnackbar();
47
- showErrorSnack('First');
51
+ it('setErrorState нормализует ошибку и показывает её', () => {
52
+ const { setErrorState, snackbarTheme, snackbarIcon, snackbarText } = useSnackbar();
53
+ setErrorState({ message: 'Something failed' });
54
+ expect(snackbarTheme.value).toBe('error');
55
+ expect(snackbarIcon.value).toBe('close');
56
+ expect(snackbarText.value).toBe('Something failed');
57
+ });
58
+
59
+ it('повторный показ сбрасывает предыдущий таймер', () => {
60
+ const { showError, snackbarText } = useSnackbar();
61
+ showError('First');
48
62
  vi.advanceTimersByTime(2000);
49
- showErrorSnack('Second');
63
+ showError('Second');
50
64
  expect(snackbarText.value).toBe('Second');
51
65
 
52
66
  vi.advanceTimersByTime(4000);
53
67
  expect(snackbarText.value).toBe('');
54
68
  });
55
69
 
70
+ it('showErrorSnack остаётся алиасом showError', () => {
71
+ const { showErrorSnack, snackbarText, snackbarTheme } = useSnackbar();
72
+ showErrorSnack('Legacy');
73
+ expect(snackbarText.value).toBe('Legacy');
74
+ expect(snackbarTheme.value).toBe('error');
75
+ });
76
+
56
77
  it('resetSnackbar сбрасывает всё', () => {
57
- const { showErrorSnack, resetSnackbar, snackbarText, snackbarTheme, snackbarIcon } = useSnackbar();
58
- showErrorSnack('Error');
78
+ const { showError, resetSnackbar, snackbarText, snackbarTheme, snackbarIcon } = useSnackbar();
79
+ showError('Error');
59
80
  resetSnackbar();
60
81
  expect(snackbarText.value).toBe('');
61
82
  expect(snackbarTheme.value).toBe('success');
@@ -10,7 +10,7 @@ export const useFetchRelatedVideos = async (
10
10
  const lang = useLang() as ELanguage;
11
11
  const route = useRoute();
12
12
  const mobile = !!useState<boolean>('isMobile').value;
13
- const perPage = mobile ? 7 : 14;
13
+ const perPage = mobile ? 6 : 14;
14
14
  const slug = useSlug();
15
15
  const key = EAsyncData.RelatedVideos;
16
16
  const stateKey = computed(() => `data-${key}-${slug.value}-${JSON.stringify(route.query)}-${lang}`);
@@ -3,46 +3,57 @@ import { parseApiError } from '../runtime';
3
3
  const SUCCESS_TIME = 2000;
4
4
  const ERROR_TIME = 4000;
5
5
 
6
- let _timeoutClosure: null | ReturnType<typeof setTimeout> = null;
6
+ // Один таймер на логическое уведомление. Снекбар физически переезжает между
7
+ // диалогом (s-popup) и общим макетом (default.vue), поэтому таймер не может жить
8
+ // в компоненте — при перемонтировании он сбрасывался бы заново. Идентификатор
9
+ // setTimeout создаётся только в браузере, на сервере он не заводится.
10
+ let dismissTimer: null | ReturnType<typeof setTimeout> = null;
7
11
 
8
- /** Управляет состоянием snackbar-уведомления: показ ошибки, сброс, тема, иконка и таймер. */
12
+ /** Управляет состоянием snackbar-уведомления: показ успеха и ошибки, сброс, тема, иконка и таймер автозакрытия. */
9
13
  export const useSnackbar = () => {
10
14
  const snackbarIcon = useState('snackbar-icon', () => 'check');
11
15
  const snackbarText = useState('snackbar-text', () => '');
12
16
  const snackbarButtonText = useState('snackbar-button-text', () => '');
13
17
  const isSnackBarInPopup = useState('snackbar-in-popup', () => false);
14
18
  const snackbarTheme = useState<'success' | 'error' | 'default'>('snackbar-theme', () => 'success');
15
- const snackbarTimer = useState('snackbar-timer', () => SUCCESS_TIME);
16
19
 
17
- const setErrorState = (error: any) => {
18
- snackbarText.value = parseApiError(error);
19
- snackbarTheme.value = 'error';
20
- snackbarIcon.value = 'close';
21
- snackbarTimer.value = ERROR_TIME;
22
- };
20
+ function clearTimer() {
21
+ if (dismissTimer) {
22
+ clearTimeout(dismissTimer);
23
+ dismissTimer = null;
24
+ }
25
+ }
23
26
 
24
- function showErrorSnack(message: string) {
25
- if (_timeoutClosure) {
26
- clearTimeout(_timeoutClosure);
27
+ function scheduleDismiss(ms: number) {
28
+ clearTimer();
29
+ if (import.meta.client) {
30
+ dismissTimer = setTimeout(resetSnackbar, ms);
27
31
  }
32
+ }
28
33
 
34
+ function showSuccess(message: string) {
35
+ snackbarTheme.value = 'success';
36
+ snackbarIcon.value = 'check';
37
+ snackbarText.value = message;
38
+ scheduleDismiss(SUCCESS_TIME);
39
+ }
40
+
41
+ function showError(message: string) {
29
42
  snackbarTheme.value = 'error';
30
43
  snackbarIcon.value = 'close';
31
44
  snackbarText.value = message;
32
- snackbarTimer.value = ERROR_TIME;
45
+ scheduleDismiss(ERROR_TIME);
46
+ }
33
47
 
34
- _timeoutClosure = setTimeout(resetSnackbar, snackbarTimer.value);
48
+ function setErrorState(error: any) {
49
+ showError(parseApiError(error));
35
50
  }
36
51
 
37
52
  function resetSnackbar() {
38
- if (_timeoutClosure) {
39
- clearTimeout(_timeoutClosure);
40
- _timeoutClosure = null;
41
- }
53
+ clearTimer();
42
54
  snackbarIcon.value = 'check';
43
55
  snackbarText.value = '';
44
56
  snackbarTheme.value = 'success';
45
- snackbarTimer.value = SUCCESS_TIME;
46
57
  }
47
58
 
48
59
  return {
@@ -50,10 +61,11 @@ export const useSnackbar = () => {
50
61
  snackbarText,
51
62
  snackbarButtonText,
52
63
  snackbarTheme,
53
- snackbarTimer,
54
- setErrorState,
55
- showErrorSnack,
56
64
  isSnackBarInPopup,
65
+ showSuccess,
66
+ showError,
67
+ setErrorState,
57
68
  resetSnackbar,
69
+ showErrorSnack: showError,
58
70
  };
59
71
  };
@@ -23,7 +23,7 @@ function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K
23
23
  * @param apiService - сервис для API-запросов
24
24
  */
25
25
  export const useUser = (apiService) => {
26
- const { snackbarText, snackbarTheme, showErrorSnack } = useSnackbar();
26
+ const { showSuccess, showError } = useSnackbar();
27
27
  const register = async (form: IRegistrateForm) => {
28
28
  const response = await apiService.register(form);
29
29
  if (response) {
@@ -72,10 +72,9 @@ export const useUser = (apiService) => {
72
72
  const changePassword = async (form: IChangePasswordForm) => {
73
73
  try {
74
74
  await apiService.changePassword(form);
75
- snackbarTheme.value = 'success';
76
- snackbarText.value = 'Password changed';
75
+ showSuccess('Password changed');
77
76
  } catch (err) {
78
- showErrorSnack('Error while changing password');
77
+ showError('Error while changing password');
79
78
  console.log(err, 'error change password');
80
79
  }
81
80
  };
@@ -83,10 +82,9 @@ export const useUser = (apiService) => {
83
82
  const changeEmail = async (form: IChangeEmail) => {
84
83
  try {
85
84
  await apiService.changeEmail(form);
86
- snackbarTheme.value = 'success';
87
- snackbarText.value = 'email_changed';
85
+ showSuccess('email_changed');
88
86
  } catch (err) {
89
- showErrorSnack('Error while changing email');
87
+ showError('Error while changing email');
90
88
  console.log(err, 'error change email');
91
89
  }
92
90
  };
@@ -95,11 +93,10 @@ export const useUser = (apiService) => {
95
93
  try {
96
94
  const {generateLink} = useGenerateLink();
97
95
  await apiService.recoveryPassword(form);
98
- snackbarTheme.value = 'success';
99
- snackbarText.value = 'Password recovered';
96
+ showSuccess('Password recovered');
100
97
  await useRouter().push(generateLink('/profile'));
101
98
  } catch (err) {
102
- showErrorSnack('Error while recovery password');
99
+ showError('Error while recovery password');
103
100
  console.log(err, 'error change password');
104
101
  }
105
102
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.747",
4
+ "version": "0.0.749",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {