@coopenomics/desktop 2025.11.20-alpha-2 → 2025.11.24-alpha-1

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 (66) hide show
  1. package/extensions/coopgram/README.md +65 -0
  2. package/extensions/coopgram/entities/CoopgramChat/api/index.ts +19 -0
  3. package/extensions/coopgram/entities/CoopgramChat/index.ts +2 -0
  4. package/extensions/coopgram/entities/CoopgramChat/model/index.ts +2 -0
  5. package/extensions/coopgram/entities/CoopgramChat/model/store.ts +58 -0
  6. package/extensions/coopgram/entities/CoopgramChat/model/types.ts +3 -0
  7. package/extensions/coopgram/entities/index.ts +1 -0
  8. package/extensions/coopgram/index.ts +2 -0
  9. package/extensions/coopgram/install.ts +41 -0
  10. package/extensions/coopgram/pages/CoopgramPage/features/CreateMatrixAccount/index.ts +2 -0
  11. package/extensions/coopgram/pages/CoopgramPage/features/CreateMatrixAccount/model/useCreateMatrixAccount.ts +38 -0
  12. package/extensions/coopgram/pages/CoopgramPage/features/CreateMatrixAccount/ui/CreateMatrixAccountButton.vue +59 -0
  13. package/extensions/coopgram/pages/CoopgramPage/index.ts +1 -0
  14. package/extensions/coopgram/pages/CoopgramPage/ui/CoopgramPage.vue +123 -0
  15. package/extensions/coopgram/pages/CoopgramPage/ui/index.ts +1 -0
  16. package/extensions/coopgram/pages/index.ts +1 -0
  17. package/extensions/coopgram/widgets/MatrixRegistration/index.ts +3 -0
  18. package/extensions/coopgram/widgets/MatrixRegistration/model/types.ts +4 -0
  19. package/extensions/coopgram/widgets/MatrixRegistration/model/useMatrixRegistration.ts +102 -0
  20. package/extensions/coopgram/widgets/MatrixRegistration/ui/MatrixRegistration.vue +339 -0
  21. package/extensions/powerup/POWERUP_TEXTS_SUMMARY.md +121 -0
  22. package/extensions/powerup/USER_GUIDE.md +240 -0
  23. package/extensions/powerup/entities/index.ts +2 -0
  24. package/extensions/powerup/features/index.ts +2 -0
  25. package/extensions/powerup/index.ts +2 -0
  26. package/extensions/powerup/install.ts +53 -12
  27. package/extensions/powerup/pages/LogsPage/index.ts +1 -0
  28. package/extensions/powerup/pages/LogsPage/ui/LogsPage.vue +17 -0
  29. package/extensions/powerup/pages/LogsPage/ui/index.ts +1 -0
  30. package/extensions/powerup/pages/MonitorPage/index.ts +1 -0
  31. package/extensions/powerup/pages/MonitorPage/ui/MonitorPage.vue +62 -0
  32. package/extensions/powerup/pages/MonitorPage/ui/index.ts +1 -0
  33. package/extensions/powerup/pages/SettingsPage/index.ts +1 -0
  34. package/extensions/powerup/pages/SettingsPage/ui/SettingsPage.vue +126 -0
  35. package/extensions/powerup/pages/SettingsPage/ui/index.ts +1 -0
  36. package/extensions/powerup/pages/index.ts +4 -0
  37. package/extensions/powerup/widgets/AxonWalletCard/index.ts +1 -0
  38. package/extensions/powerup/widgets/AxonWalletCard/ui/AxonWalletCard.vue +173 -0
  39. package/extensions/powerup/widgets/CpuResourceWidget/index.ts +1 -0
  40. package/extensions/powerup/widgets/CpuResourceWidget/ui/CpuResourceWidget.vue +307 -0
  41. package/extensions/powerup/widgets/NetResourceWidget/index.ts +1 -0
  42. package/extensions/powerup/widgets/NetResourceWidget/ui/NetResourceWidget.vue +308 -0
  43. package/extensions/powerup/widgets/PowerupLogItem/index.ts +1 -0
  44. package/extensions/powerup/widgets/PowerupLogItem/ui/PowerupLogItem.vue +124 -0
  45. package/extensions/powerup/widgets/PowerupLogItem/ui/index.ts +1 -0
  46. package/extensions/powerup/widgets/RamResourceWidget/index.ts +1 -0
  47. package/extensions/powerup/widgets/RamResourceWidget/ui/RamResourceWidget.vue +290 -0
  48. package/extensions/powerup/widgets/ResourceInfoWidget/index.ts +1 -0
  49. package/extensions/powerup/widgets/ResourceInfoWidget/ui/ResourceInfoWidget.vue +484 -0
  50. package/extensions/powerup/widgets/index.ts +7 -0
  51. package/package.json +6 -6
  52. package/src/entities/Extension/api/index.ts +15 -1
  53. package/src/entities/Extension/model/store.ts +13 -1
  54. package/src/entities/Session/model/store.ts +1 -0
  55. package/src/pages/Union/ConnectionAgreement/ConnectionAgreementPage.vue +1 -1
  56. package/src/shared/api/errors.ts +10 -1
  57. package/src/shared/lib/utils/pluralizeHours.ts +30 -3
  58. package/src/shared/ui/AxonWalletDisplay/index.ts +1 -0
  59. package/src/shared/ui/AxonWalletDisplay/ui/AxonWalletDisplay.vue +82 -0
  60. package/src/shared/ui/ZodForm/ZodForm.vue +129 -14
  61. package/src/shared/ui/index.ts +2 -0
  62. package/src/widgets/ConnectionDashboard/ui/SubscriptionsCard.vue +12 -36
  63. package/src/widgets/ExtensionLogsList/index.ts +1 -0
  64. package/src/widgets/ExtensionLogsList/ui/ExtensionLogsList.vue +169 -0
  65. package/src/widgets/ExtensionLogsList/ui/index.ts +1 -0
  66. package/src/widgets/ExtensionSettings/ui/ExtensionSettings.vue +43 -7
@@ -0,0 +1,65 @@
1
+ # Coopgram Extension
2
+
3
+ Расширение для интеграции Matrix чата в кооперативную систему.
4
+
5
+ ## Описание
6
+
7
+ Coopgram предоставляет встроенный Matrix чат для пользователей кооператива. Расширение:
8
+
9
+ - Получает временный токен аутентификации через GraphQL API
10
+ - Отображает Matrix клиент (Element Web) в iframe
11
+ - Автоматически аутентифицирует пользователя с полученным токеном
12
+
13
+ ## Структура
14
+
15
+ ```
16
+ coopgram/
17
+ ├── install.ts # Конфигурация рабочего стола
18
+ ├── entities/
19
+ │ └── CoopgramChat/ # Entity для работы с Matrix токенами
20
+ │ ├── api/ # GraphQL запросы через SDK
21
+ │ └── model/ # Store и типы данных из SDK
22
+ ├── pages/
23
+ │ └── CoopgramPage/ # Главная страница с iframe
24
+ ├── shared/ # Общие компоненты
25
+ └── widgets/ # Виджеты
26
+ ```
27
+
28
+ ## SDK интеграция
29
+
30
+ Расширение использует типизированные запросы из SDK:
31
+
32
+ - **Query**: `Queries.Coopgram.GetToken`
33
+ - **Selector**: `coopgramTokenSelector`
34
+ - **Типы**: Автоматически генерируются из GraphQL схемы
35
+
36
+ ## Работа с iframe URL
37
+
38
+ 1. При загрузке страницы вызывается `coopgramStore.loadToken()`
39
+ 2. Entity отправляет типизированный GraphQL запрос через SDK: `Queries.Coopgram.GetToken`
40
+ 3. Бэкенд проверяет/создает Matrix пользователя и генерирует токен
41
+ 4. Бэкенд формирует полную iframe URL с токеном аутентификации
42
+ 5. Store сохраняет полученную ссылку в состоянии (типизировано через SDK)
43
+ 6. Страница загружает iframe с готовой ссылкой
44
+ 7. Пользователь автоматически входит в Matrix чат
45
+
46
+ ## Конфигурация
47
+
48
+ - **MATRIX_CLIENT_URL**: URL Element Web клиента
49
+ - **workspace**: 'coopgram'
50
+ - **defaultRoute**: 'chat'
51
+
52
+ ## Архитектура
53
+
54
+ Расширение построено по принципам Feature-Sliced Design (FSD):
55
+
56
+ - **Entities**: `CoopgramChat` - бизнес-сущность для работы с Matrix токенами
57
+ - **Pages**: `CoopgramPage` - UI страница без бизнес-логики
58
+ - **Store**: Pinia store для управления состоянием токена
59
+ - **API**: Функции для GraphQL запросов
60
+
61
+ ## Зависимости
62
+
63
+ - GraphQL API с resolver `getToken`
64
+ - Matrix Synapse сервер
65
+ - Element Web клиент
@@ -0,0 +1,19 @@
1
+ import { client } from 'src/shared/api/client';
2
+ import { Queries } from '@coopenomics/sdk';
3
+ import type { ICoopgramAccountStatus } from '../model/types';
4
+
5
+ async function getAccountStatus(): Promise<ICoopgramAccountStatus> {
6
+ const { [Queries.Coopgram.GetAccountStatus.name]: output } = await client.Query(
7
+ Queries.Coopgram.GetAccountStatus.query,
8
+ );
9
+
10
+ return {
11
+ hasAccount: output.hasAccount,
12
+ matrixUsername: output.matrixUsername || undefined,
13
+ iframeUrl: output.iframeUrl || undefined,
14
+ };
15
+ }
16
+
17
+ export const api = {
18
+ getAccountStatus,
19
+ };
@@ -0,0 +1,2 @@
1
+ export * from './api';
2
+ export * from './model';
@@ -0,0 +1,2 @@
1
+ export * from './store';
2
+ export * from './types';
@@ -0,0 +1,58 @@
1
+ import { defineStore } from 'pinia';
2
+ import { ref, Ref } from 'vue';
3
+ import { api } from '../api';
4
+ import type { ICoopgramAccountStatus } from './types';
5
+
6
+ const namespace = 'coopgramChatStore';
7
+
8
+ interface ICoopgramChatStore {
9
+ accountStatus: Ref<ICoopgramAccountStatus | null>;
10
+ isLoading: Ref<boolean>;
11
+ error: Ref<string | null>;
12
+ loadAccountStatus: () => Promise<ICoopgramAccountStatus | null>;
13
+ clearAccountStatus: () => void;
14
+ clearError: () => void;
15
+ }
16
+
17
+ export const useCoopgramChatStore = defineStore(
18
+ namespace,
19
+ (): ICoopgramChatStore => {
20
+ const accountStatus = ref<ICoopgramAccountStatus | null>(null);
21
+ const isLoading = ref(false);
22
+ const error = ref<string | null>(null);
23
+
24
+ const loadAccountStatus = async (): Promise<ICoopgramAccountStatus | null> => {
25
+ isLoading.value = true;
26
+ error.value = null;
27
+
28
+ try {
29
+ const status = await api.getAccountStatus();
30
+ accountStatus.value = status;
31
+ return status;
32
+ } catch (err) {
33
+ console.error('Failed to load Coopgram account status:', err);
34
+ error.value = 'Не удалось получить статус аккаунта. Попробуйте обновить страницу.';
35
+ return null;
36
+ } finally {
37
+ isLoading.value = false;
38
+ }
39
+ };
40
+
41
+ const clearAccountStatus = () => {
42
+ accountStatus.value = null;
43
+ };
44
+
45
+ const clearError = () => {
46
+ error.value = null;
47
+ };
48
+
49
+ return {
50
+ accountStatus,
51
+ isLoading,
52
+ error,
53
+ loadAccountStatus,
54
+ clearAccountStatus,
55
+ clearError,
56
+ };
57
+ },
58
+ );
@@ -0,0 +1,3 @@
1
+ import type { Queries } from '@coopenomics/sdk';
2
+
3
+ export type ICoopgramAccountStatus = Queries.Coopgram.GetAccountStatus.IOutput[typeof Queries.Coopgram.GetAccountStatus.name];
@@ -0,0 +1 @@
1
+ export * from './CoopgramChat';
@@ -0,0 +1,2 @@
1
+ // Экспорт главной функции install
2
+ export { default } from './install';
@@ -0,0 +1,41 @@
1
+ import { markRaw } from 'vue';
2
+ import { CoopgramPage } from './pages/CoopgramPage';
3
+ import { agreementsBase } from 'src/shared/lib/consts/workspaces';
4
+ import type { IWorkspaceConfig } from 'src/shared/lib/types/workspace';
5
+
6
+ export default async function (): Promise<IWorkspaceConfig[]> {
7
+ return [{
8
+ workspace: 'coopgram',
9
+ extension_name: 'coopgram',
10
+ title: 'Коопграм',
11
+ icon: 'fa-solid fa-comments',
12
+ defaultRoute: 'chat', // Маршрут по умолчанию для рабочего стола чата
13
+ routes: [
14
+ {
15
+ meta: {
16
+ title: 'Кооперативный мессенджер',
17
+ icon: 'fa-solid fa-comments',
18
+ roles: ['user', 'chairman', 'member'],
19
+ },
20
+ path: '/:coopname/coopgram',
21
+ name: 'coopgram',
22
+ component: markRaw(CoopgramPage),
23
+ children: [
24
+ {
25
+ path: 'chat',
26
+ name: 'coopgram-chat',
27
+ component: markRaw(CoopgramPage),
28
+ meta: {
29
+ title: 'Кооперативный мессенджер',
30
+ icon: 'fa-solid fa-comments',
31
+ roles: ['user', 'chairman', 'member'],
32
+ agreements: agreementsBase,
33
+ requiresAuth: true,
34
+ },
35
+ children: [],
36
+ },
37
+ ],
38
+ },
39
+ ],
40
+ }];
41
+ }
@@ -0,0 +1,2 @@
1
+ export { default as CreateMatrixAccountButton } from './ui/CreateMatrixAccountButton.vue';
2
+ export { useCreateMatrixAccount } from './model/useCreateMatrixAccount';
@@ -0,0 +1,38 @@
1
+ import { ref } from 'vue';
2
+ import { client } from 'src/shared/api/client';
3
+ import { Mutations } from '@coopenomics/sdk';
4
+
5
+ export function useCreateMatrixAccount() {
6
+ const isLoading = ref(false);
7
+ const error = ref<string | null>(null);
8
+
9
+ const createAccount = async (username: string, password: string): Promise<boolean> => {
10
+ isLoading.value = true;
11
+ error.value = null;
12
+
13
+ try {
14
+ const { [Mutations.Coopgram.CreateAccount.name]: result } = await client.Mutation(
15
+ Mutations.Coopgram.CreateAccount.mutation,
16
+ {
17
+ variables: {
18
+ data: { username, password },
19
+ },
20
+ }
21
+ );
22
+
23
+ return result;
24
+ } catch (err: any) {
25
+ console.error('Failed to create Matrix account:', err);
26
+ error.value = err?.message || 'Не удалось создать аккаунт Matrix';
27
+ return false;
28
+ } finally {
29
+ isLoading.value = false;
30
+ }
31
+ };
32
+
33
+ return {
34
+ createAccount,
35
+ isLoading,
36
+ error,
37
+ };
38
+ }
@@ -0,0 +1,59 @@
1
+ <template lang="pug">
2
+ button.create-account-button(
3
+ @click="handleCreateAccount",
4
+ :disabled="isLoading || !password",
5
+ type="button"
6
+ )
7
+ span(v-if="isLoading") Создание аккаунта...
8
+ span(v-else) Создать аккаунт Matrix
9
+ </template>
10
+
11
+ <script lang="ts" setup>
12
+ import { useCreateMatrixAccount } from '../model/useCreateMatrixAccount';
13
+
14
+ const props = defineProps<{
15
+ username: string;
16
+ password: string;
17
+ }>();
18
+
19
+ const emit = defineEmits<{
20
+ success: [];
21
+ error: [message: string];
22
+ }>();
23
+
24
+ const { createAccount, isLoading, error } = useCreateMatrixAccount();
25
+
26
+ const handleCreateAccount = async () => {
27
+ const success = await createAccount(props.username, props.password);
28
+
29
+ if (success) {
30
+ emit('success');
31
+ } else {
32
+ emit('error', error.value || 'Не удалось создать аккаунт');
33
+ }
34
+ };
35
+ </script>
36
+
37
+ <style scoped>
38
+ .create-account-button {
39
+ padding: 0.875rem 2rem;
40
+ background-color: #28a745;
41
+ color: white;
42
+ border: none;
43
+ border-radius: 6px;
44
+ font-size: 1rem;
45
+ font-weight: 500;
46
+ cursor: pointer;
47
+ transition: background-color 0.2s;
48
+ width: 100%;
49
+ }
50
+
51
+ .create-account-button:hover:not(:disabled) {
52
+ background-color: #218838;
53
+ }
54
+
55
+ .create-account-button:disabled {
56
+ background-color: #6c757d;
57
+ cursor: not-allowed;
58
+ }
59
+ </style>
@@ -0,0 +1 @@
1
+ export * from './ui'
@@ -0,0 +1,123 @@
1
+ <template lang="pug">
2
+ div
3
+ // Лоадер пока получаем статус аккаунта
4
+ WindowLoader(v-if="coopgramStore.isLoading", text="Проверка статуса аккаунта...")
5
+
6
+ // Сообщение об ошибке
7
+ div(v-else-if="coopgramStore.error", class="error-message")
8
+ p {{ coopgramStore.error }}
9
+ button(@click="retryLoadStatus", class="retry-button") Повторить попытку
10
+
11
+ // Виджет регистрации Matrix аккаунта
12
+ MatrixRegistration(
13
+ v-else-if="coopgramStore.accountStatus && !coopgramStore.accountStatus.hasAccount",
14
+ @account-created="handleAccountCreated"
15
+ )
16
+
17
+ // Лоадер пока загружается iframe Matrix клиента
18
+ WindowLoader(
19
+ v-else-if="coopgramStore.accountStatus?.iframeUrl && isIframeLoading",
20
+ text="Загрузка Matrix клиента..."
21
+ )
22
+
23
+ // Iframe с Matrix клиентом после полной загрузки
24
+ iframe(
25
+ v-else-if="coopgramStore.accountStatus?.iframeUrl",
26
+ v-show="!isIframeLoading",
27
+ :src="coopgramStore.accountStatus.iframeUrl",
28
+ class="matrix-iframe",
29
+ frameborder="0",
30
+ width="100%",
31
+ :style="{ height: 'calc(100vh - 56px)' }"
32
+ @load="onIframeLoaded"
33
+ )
34
+ </template>
35
+
36
+ <script lang="ts" setup>
37
+ import { ref, onMounted, watch } from 'vue';
38
+ import { WindowLoader } from 'src/shared/ui/Loader';
39
+ import { useCoopgramChatStore } from '../../../entities/CoopgramChat/model';
40
+ import { MatrixRegistration } from '../../../widgets/MatrixRegistration';
41
+
42
+ const coopgramStore = useCoopgramChatStore();
43
+ const isIframeLoading = ref(true);
44
+ let iframeLoadTimeout: number | null = null;
45
+
46
+ // Сбрасываем состояние загрузки iframe при изменении URL
47
+ watch(() => coopgramStore.accountStatus?.iframeUrl, () => {
48
+ if (coopgramStore.accountStatus?.iframeUrl) {
49
+ isIframeLoading.value = true;
50
+
51
+ // Очищаем предыдущий таймаут
52
+ if (iframeLoadTimeout) {
53
+ clearTimeout(iframeLoadTimeout);
54
+ }
55
+
56
+ // Устанавливаем таймаут на 5 секунд как fallback
57
+ iframeLoadTimeout = window.setTimeout(() => {
58
+ isIframeLoading.value = false;
59
+ }, 5000);
60
+ }
61
+ });
62
+
63
+ function onIframeLoaded() {
64
+ // Очищаем таймаут, если загрузка произошла раньше
65
+ if (iframeLoadTimeout) {
66
+ clearTimeout(iframeLoadTimeout);
67
+ iframeLoadTimeout = null;
68
+ }
69
+ isIframeLoading.value = false;
70
+ }
71
+
72
+ async function retryLoadStatus() {
73
+ coopgramStore.clearError();
74
+ await coopgramStore.loadAccountStatus();
75
+ }
76
+
77
+ async function handleAccountCreated() {
78
+ // После успешного создания аккаунта перезагружаем статус
79
+ await coopgramStore.loadAccountStatus();
80
+ }
81
+
82
+ onMounted(async () => {
83
+ await coopgramStore.loadAccountStatus();
84
+ });
85
+ </script>
86
+
87
+ <style scoped>
88
+ .matrix-iframe {
89
+ width: 100%;
90
+ border: none;
91
+ display: block;
92
+ }
93
+
94
+ .error-message {
95
+ display: flex;
96
+ flex-direction: column;
97
+ align-items: center;
98
+ justify-content: center;
99
+ height: calc(100vh - 56px);
100
+ text-align: center;
101
+ padding: 2rem;
102
+ }
103
+
104
+ .error-message p {
105
+ margin-bottom: 1rem;
106
+ color: #dc3545;
107
+ font-size: 1.1rem;
108
+ }
109
+
110
+ .retry-button {
111
+ padding: 0.5rem 1rem;
112
+ background-color: #007bff;
113
+ color: white;
114
+ border: none;
115
+ border-radius: 0.25rem;
116
+ cursor: pointer;
117
+ font-size: 1rem;
118
+ }
119
+
120
+ .retry-button:hover {
121
+ background-color: #0056b3;
122
+ }
123
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CoopgramPage } from './CoopgramPage.vue';
@@ -0,0 +1 @@
1
+ export { CoopgramPage } from './CoopgramPage';
@@ -0,0 +1,3 @@
1
+ export { default as MatrixRegistration } from './ui/MatrixRegistration.vue';
2
+ export { useMatrixRegistration } from './model/useMatrixRegistration';
3
+ export type { MatrixRegistrationForm } from './model/types';
@@ -0,0 +1,4 @@
1
+ export interface MatrixRegistrationForm {
2
+ password: string;
3
+ confirmPassword: string;
4
+ }
@@ -0,0 +1,102 @@
1
+ import { ref, computed } from 'vue';
2
+ import { client } from 'src/shared/api/client';
3
+ import { Queries } from '@coopenomics/sdk';
4
+
5
+ export function useMatrixRegistration() {
6
+ const username = ref('');
7
+ const password = ref('');
8
+ const confirmPassword = ref('');
9
+ const usernameAvailable = ref<boolean | null>(null);
10
+ const checkingUsername = ref(false);
11
+
12
+ const isSubmitting = ref(false);
13
+ const error = ref<string | null>(null);
14
+
15
+ const validateForm = (): boolean => {
16
+ if (!username.value || !password.value || !confirmPassword.value) {
17
+ error.value = 'Заполните все поля';
18
+ return false;
19
+ }
20
+
21
+ if (usernameAvailable.value === false) {
22
+ error.value = 'Пользователь с таким именем уже существует';
23
+ return false;
24
+ }
25
+
26
+ if (password.value !== confirmPassword.value) {
27
+ error.value = 'Пароли не совпадают';
28
+ return false;
29
+ }
30
+
31
+ error.value = null;
32
+ return true;
33
+ };
34
+
35
+ const resetForm = () => {
36
+ username.value = '';
37
+ password.value = '';
38
+ confirmPassword.value = '';
39
+ usernameAvailable.value = null;
40
+ error.value = null;
41
+ };
42
+
43
+ // Асинхронная проверка доступности username
44
+ const checkUsernameAvailability = async (value: string): Promise<boolean> => {
45
+ if (!value || value.length < 3) return false;
46
+
47
+ checkingUsername.value = true;
48
+ try {
49
+ const { [Queries.Coopgram.CheckUsernameAvailability.name]: result } = await client.Query(
50
+ Queries.Coopgram.CheckUsernameAvailability.query,
51
+ {
52
+ variables: {
53
+ data: { username: value },
54
+ },
55
+ }
56
+ );
57
+ usernameAvailable.value = result;
58
+ return result;
59
+ } catch (err) {
60
+ console.error('Failed to check username availability:', err);
61
+ usernameAvailable.value = false; // В случае ошибки считаем недоступным
62
+ return false;
63
+ } finally {
64
+ checkingUsername.value = false;
65
+ }
66
+ };
67
+
68
+ // Правило валидации для username
69
+ const validateUsernameAsync = async (val: string): Promise<string | boolean> => {
70
+ if (!val) {
71
+ usernameAvailable.value = null;
72
+ return 'Введите имя пользователя';
73
+ }
74
+
75
+ if (val.length < 3) {
76
+ usernameAvailable.value = null;
77
+ return 'Имя пользователя должно содержать минимум 3 символа';
78
+ }
79
+
80
+ if (!/^[a-zA-Z0-9_-]+$/.test(val)) {
81
+ usernameAvailable.value = null;
82
+ return 'Имя пользователя может содержать только буквы, цифры, подчеркивания и дефисы';
83
+ }
84
+
85
+ const available = await checkUsernameAvailability(val);
86
+ return available || 'Пользователь с таким именем уже существует';
87
+ };
88
+
89
+ return {
90
+ username,
91
+ password,
92
+ confirmPassword,
93
+ usernameAvailable: computed(() => usernameAvailable.value),
94
+ checkingUsername: computed(() => checkingUsername.value),
95
+ isSubmitting,
96
+ error,
97
+ validateForm,
98
+ resetForm,
99
+ checkUsernameAvailability,
100
+ validateUsernameAsync,
101
+ };
102
+ }