@coopenomics/desktop 2025.11.19-alpha-3 → 2025.11.20-alpha-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coopenomics/desktop",
3
- "version": "2025.11.19-alpha-3",
3
+ "version": "2025.11.20-alpha-2",
4
4
  "description": "A Desktop Project",
5
5
  "productName": "Desktop App",
6
6
  "author": "Alex Ant <dacom.dark.sun@gmail.com>",
@@ -25,9 +25,9 @@
25
25
  "start": "node -r ./alias-resolver.js dist/ssr/index.js"
26
26
  },
27
27
  "dependencies": {
28
- "@coopenomics/controller": "2025.11.19-alpha-3",
29
- "@coopenomics/notifications": "2025.11.19-alpha-3",
30
- "@coopenomics/sdk": "2025.11.19-alpha-3",
28
+ "@coopenomics/controller": "2025.11.20-alpha-2",
29
+ "@coopenomics/notifications": "2025.11.20-alpha-2",
30
+ "@coopenomics/sdk": "2025.11.20-alpha-2",
31
31
  "@dicebear/collection": "^9.0.1",
32
32
  "@dicebear/core": "^9.0.1",
33
33
  "@editorjs/code": "^2.9.3",
@@ -59,7 +59,7 @@
59
59
  "@wharfkit/wallet-plugin-privatekey": "^1.1.0",
60
60
  "axios": "^1.2.1",
61
61
  "compression": "^1.7.4",
62
- "cooptypes": "2025.11.19-alpha-3",
62
+ "cooptypes": "2025.11.20-alpha-2",
63
63
  "dompurify": "^3.1.7",
64
64
  "dotenv": "^16.4.5",
65
65
  "email-regex": "^5.0.0",
@@ -123,5 +123,5 @@
123
123
  "npm": ">= 6.13.4",
124
124
  "yarn": ">= 1.21.1"
125
125
  },
126
- "gitHead": "2cf5291831617389fdcf1564bc06f26c2cd24177"
126
+ "gitHead": "23f8af43ee12cd32edeeac0284db46354492bf0c"
127
127
  }
@@ -10,15 +10,10 @@ export type CurrentInstance = Queries.System.GetCurrentInstance.IOutput[typeof Q
10
10
  * Получить текущий инстанс авторизованного пользователя
11
11
  */
12
12
  export async function getCurrentInstance(): Promise<CurrentInstance | null> {
13
- try {
14
- const { [Queries.System.GetCurrentInstance.name]: instance } =
15
- await client.Query(Queries.System.GetCurrentInstance.query);
13
+ const { [Queries.System.GetCurrentInstance.name]: instance } =
14
+ await client.Query(Queries.System.GetCurrentInstance.query);
16
15
 
17
- return instance;
18
- } catch (error) {
19
- console.warn('Инстанс не получен. Продолжаем работу без него:', error);
20
- return null;
21
- }
16
+ return instance;
22
17
  }
23
18
 
24
19
  export const api = {
@@ -4,6 +4,7 @@ import { DigitalDocument } from 'src/shared/lib/document'
4
4
  import { useSessionStore } from 'src/entities/Session'
5
5
  import { useLoadCooperatives } from 'src/features/Union/LoadCooperatives'
6
6
  import { getCurrentInstance, type CurrentInstance } from '../api'
7
+ import { extractGraphQLErrorMessages } from 'src/shared/api/errors'
7
8
  import type { ITariff, IConnectionAgreementState, ICooperativeFormData } from './types'
8
9
 
9
10
 
@@ -19,6 +20,7 @@ export const useConnectionAgreementStore = defineStore(namespace, () => {
19
20
  const currentInstance = ref<CurrentInstance | null>(null)
20
21
  const currentInstanceLoading = ref<boolean>(false)
21
22
  const currentInstanceError = ref<string | null>(null)
23
+ const isBadGateway = ref<boolean>(false)
22
24
  const coop = ref<any>(null)
23
25
  const formData = ref<ICooperativeFormData>({
24
26
  announce: '',
@@ -137,6 +139,9 @@ export const useConnectionAgreementStore = defineStore(namespace, () => {
137
139
  currentInstanceError.value = null
138
140
  const freshInstance = await getCurrentInstance()
139
141
 
142
+ // При успешной загрузке сбрасываем флаг Bad Gateway
143
+ isBadGateway.value = false
144
+
140
145
  // Обновляем данные только если получили свежие данные
141
146
  // При ошибке оставляем старые данные в currentInstance (они могут быть из localStorage)
142
147
  if (freshInstance !== null) {
@@ -145,10 +150,12 @@ export const useConnectionAgreementStore = defineStore(namespace, () => {
145
150
 
146
151
  console.log('Текущий инстанс загружен:', currentInstance.value)
147
152
  } catch (error: any) {
148
- currentInstanceError.value = error.message || 'Ошибка загрузки инстанса'
153
+ isBadGateway.value = true
154
+
155
+ currentInstanceError.value = extractGraphQLErrorMessages(error)
149
156
  // НЕ очищаем старые данные при ошибке - они остаются актуальными из localStorage
150
157
  // currentInstance.value остается как есть
151
- console.error('Ошибка при загрузке текущего инстанса:', error)
158
+ console.warn('Ошибка при загрузке текущего инстанса:', error)
152
159
  } finally {
153
160
  currentInstanceLoading.value = false
154
161
  }
@@ -176,6 +183,7 @@ export const useConnectionAgreementStore = defineStore(namespace, () => {
176
183
  currentInstance.value = null
177
184
  currentInstanceLoading.value = false
178
185
  currentInstanceError.value = null
186
+ isBadGateway.value = false
179
187
  coop.value = null
180
188
  formData.value = {
181
189
  announce: '',
@@ -220,6 +228,7 @@ export const useConnectionAgreementStore = defineStore(namespace, () => {
220
228
  currentInstance,
221
229
  currentInstanceLoading,
222
230
  currentInstanceError,
231
+ isBadGateway,
223
232
  coop,
224
233
  formData,
225
234
 
@@ -31,13 +31,12 @@ export async function generateConvertToAxonStatement(data: Mutations.Provider.Ge
31
31
  /**
32
32
  * Обрабатывает подписанный документ конвертации в AXON
33
33
  */
34
- export async function processConvertToAxonStatement(signedDocument: Mutations.Provider.ProcessConvertToAxonStatement.IInput['signedDocument'], convertAmount: string) {
34
+ export async function processConvertToAxonStatement(data: Mutations.Provider.ProcessConvertToAxonStatement.IInput['data']) {
35
35
  const { [Mutations.Provider.ProcessConvertToAxonStatement.name]: result } = await client.Mutation(
36
36
  Mutations.Provider.ProcessConvertToAxonStatement.mutation,
37
37
  {
38
38
  variables: {
39
- signedDocument,
40
- convertAmount
39
+ data
41
40
  }
42
41
  }
43
42
  );
@@ -1,6 +1,8 @@
1
1
  import { ref, computed } from 'vue';
2
2
  import { loadProviderSubscriptions, generateConvertToAxonStatement, processConvertToAxonStatement } from '../api';
3
3
  import { useSystemStore } from 'src/entities/System/model';
4
+ import { useSessionStore } from 'src/entities/Session';
5
+ import { useAccountStore } from 'src/entities/Account/model';
4
6
  import { Queries, Mutations } from '@coopenomics/sdk';
5
7
  import { useSignDocument } from 'src/shared/lib/document/model/entity';
6
8
  import { SuccessAlert, FailAlert } from 'src/shared/api';
@@ -152,7 +154,19 @@ export function useProviderAxonConvert() {
152
154
 
153
155
 
154
156
  // Обрабатываем подписанный документ
155
- await processConvertToAxonStatement(signedDocument, params.convertAmount);
157
+ await processConvertToAxonStatement({
158
+ signedDocument,
159
+ convertAmount: params.convertAmount,
160
+ username: params.username
161
+ });
162
+
163
+ // Обновляем баланс AXON после успешной конвертации
164
+ const session = useSessionStore();
165
+ const accountStore = useAccountStore();
166
+ const updatedAccount = await accountStore.getAccount(params.username);
167
+ if (updatedAccount) {
168
+ session.setCurrentUserAccount(updatedAccount);
169
+ }
156
170
 
157
171
  SuccessAlert('Конвертация успешно выполнена');
158
172
  return true;
@@ -1,11 +1,14 @@
1
1
  <template lang="pug">
2
2
  div.row.q-pa-md
3
3
  div.col-md-12.col-xs-12
4
- // Лоадер пока идет загрузка данных
5
- WindowLoader(v-if="isLoading", text="Загрузка данных подключения...")
6
-
7
- // Основной контент после загрузки
8
- div(v-else)
4
+ // Лоадер пока идет загрузка данных или технические работы у провайдера
5
+ WindowLoader(
6
+ v-if="isLoading || connectionAgreement.isBadGateway",
7
+ :text="connectionAgreement.isBadGateway ? провайдера идут технические работы...' : 'Загрузка данных подключения...'"
8
+ )
9
+
10
+ // Основной контент после загрузки (не показываем при технических работах у провайдера)
11
+ div(v-else-if="!connectionAgreement.isBadGateway")
9
12
  div(v-if="system.info.is_providered")
10
13
  //- Показываем дашборд если установка завершена и мы на основной странице
11
14
  div(v-if="isInstallationCompleted && !isOnCompletionRoute").relative
@@ -108,15 +111,11 @@ const init = async () => {
108
111
  console.log('SYSTEM.info.is_unioned', system.info.is_unioned, connectionAgreement.isInitialized);
109
112
 
110
113
  // Запускаем автообновление инстанса каждые 30 секунд (включает начальную загрузку)
111
- // Не ждем завершения первой загрузки, чтобы избежать зависания при недоступности бэкенда
112
- connectionAgreement.startInstanceAutoRefresh(30000).then((stop) => {
114
+ // Ждем завершения первой загрузки, чтобы корректно определить состояние isBadGateway
115
+ await connectionAgreement.startInstanceAutoRefresh(30000).then((stop) => {
113
116
  stopInstanceRefresh = stop;
114
117
  });
115
118
 
116
- // Даем небольшую паузу для того, чтобы данные могли загрузиться из кэша или быстро
117
- // Но не ждем обязательно завершения
118
- await new Promise(resolve => setTimeout(resolve, 100));
119
-
120
119
  // Инициализируем persistent store если он еще не инициализирован
121
120
  if (!connectionAgreement.isInitialized) {
122
121
  connectionAgreement.setInitialized(true);
@@ -39,3 +39,49 @@ export function extractGraphQLErrorMessages(error: unknown): string {
39
39
  // Обработка в случае, если ошибка — одиночная
40
40
  return (error as any).message || 'Unknown error';
41
41
  }
42
+
43
+ /**
44
+ * Проверяет, содержит ли ошибка GraphQL указанные параметры
45
+ * @param error - ошибка GraphQL (может быть массивом или объектом)
46
+ * @param options - параметры для проверки
47
+ * @param options.code - код ошибки (например, 500)
48
+ * @param options.message - текст сообщения ошибки
49
+ * @returns true если ошибка соответствует критериям
50
+ */
51
+ export function isGraphQLError(
52
+ error: unknown,
53
+ options: { code?: number; message?: string }
54
+ ): boolean {
55
+ if (!error || typeof error !== 'object') return false;
56
+
57
+ // Получаем массив ошибок для проверки
58
+ let errorsToCheck: any[] = [];
59
+
60
+ if (Array.isArray(error)) {
61
+ errorsToCheck = error;
62
+ } else {
63
+ // Проверяем поле errors (Apollo Client)
64
+ const errors = (error as any).errors;
65
+ if (Array.isArray(errors)) {
66
+ errorsToCheck = errors;
67
+ } else {
68
+ // Одиночная ошибка
69
+ errorsToCheck = [error];
70
+ }
71
+ }
72
+
73
+ // Проверяем каждую ошибку
74
+ return errorsToCheck.some((err: any) => {
75
+ // Проверяем код, если указан
76
+ if (options.code !== undefined && err?.extensions?.code !== options.code) {
77
+ return false;
78
+ }
79
+
80
+ // Проверяем сообщение, если указано
81
+ if (options.message !== undefined && err?.message !== options.message) {
82
+ return false;
83
+ }
84
+
85
+ return true;
86
+ });
87
+ }
@@ -1,4 +1,4 @@
1
- export function formatToAsset(amount: string | number, currency: string): string {
2
- const formattedAmount = (typeof amount === 'number' ? amount : parseFloat(amount)).toFixed(4);
1
+ export function formatToAsset(amount: string | number, currency: string, precision=4): string {
2
+ const formattedAmount = (typeof amount === 'number' ? amount : parseFloat(amount)).toFixed(precision);
3
3
  return `${formattedAmount} ${currency}`;
4
4
  }
@@ -197,12 +197,6 @@ const shadowStyles = computed(
197
197
  white-space: pre-wrap;
198
198
  }
199
199
 
200
- p {
201
- margin: 0 !important;
202
- font-size: 14px;
203
- white-space: pre-wrap;
204
- }
205
-
206
200
  table {
207
201
  width: 100%;
208
202
  border-collapse: collapse;
@@ -29,39 +29,60 @@
29
29
 
30
30
  // Диалог пополнения
31
31
  q-dialog(v-model="showDepositDialog", @hide="clear")
32
- ModalBase(title="Пополнение кошелька AXON")
33
- Form.q-pa-sm(
34
- :handler-submit="handlerSubmit",
35
- :is-submitting="isSubmitting",
36
- button-cancel-txt="Отменить",
37
- button-submit-txt="Пополнить",
38
- @cancel="clear"
39
- )
40
- q-input(
41
- v-model="depositAmount",
42
- standout="bg-teal text-white",
43
- placeholder="Введите сумму в RUB",
44
- type="number",
45
- :min="0",
46
- :step="10",
47
- :hint="depositHint",
48
- :rules="[(val) => val > 0 || 'Сумма должна быть положительной']"
32
+ ModalBase(title="Пополнение кошелька AXON", :style="dialogStyle")
33
+ .q-pa-sm
34
+ // Текущий баланс
35
+ .current-balance-section.q-mb-md
36
+ .text-body2.text-grey-7.q-mb-xs
37
+ | Текущий баланс: {{ formattedRubBalance }}.
38
+ | Для оплаты AXON используется паевой взнос на вашем кошельке.
39
+ | При недостатке средств на балансе совершите паевой взнос:
40
+ q-btn(
41
+ flat,
42
+ dense,
43
+ no-caps,
44
+ color="primary",
45
+ label="перейти в кошелек",
46
+ @click="goToWallet"
47
+ )
48
+
49
+ Form(
50
+ :handler-submit="handlerSubmit",
51
+ :is-submitting="isSubmitting",
52
+ button-cancel-txt="Отменить",
53
+ button-submit-txt="Пополнить",
54
+ @cancel="clear"
49
55
  )
50
- template(#append)
51
- span.text-overline RUB
56
+ q-input(
57
+ v-model="depositAmount",
58
+ standout="bg-teal text-white",
59
+ placeholder="Введите сумму в RUB",
60
+ type="number",
61
+ :min="0",
62
+ :step="10",
63
+ :hint="depositHint",
64
+ :rules="[(val) => val > 0 || 'Сумма должна быть положительной']"
65
+ )
66
+ template(#append)
67
+ span.text-overline RUB
52
68
  </template>
53
69
 
54
70
  <script setup lang="ts">
55
71
  import { computed, ref } from 'vue';
72
+ import { useRouter } from 'vue-router';
56
73
  import { useSessionStore } from 'src/entities/Session';
74
+ import { useWalletStore } from 'src/entities/Wallet';
57
75
  import { ColorCard } from 'src/shared/ui';
58
76
  import { Form } from 'src/shared/ui/Form';
59
77
  import { ModalBase } from 'src/shared/ui/ModalBase';
60
78
  import { formatAsset2Digits } from 'src/shared/lib/utils/formatAsset2Digits';
79
+ import { formatToAsset } from 'src/shared/lib/utils/formatToAsset';
61
80
  import { useProviderAxonConvert, AXON_GOVERN_RATE } from 'src/features/Provider/model';
62
81
  import { useSystemStore } from 'src/entities/System/model';
63
82
 
83
+ const router = useRouter();
64
84
  const session = useSessionStore();
85
+ const walletStore = useWalletStore();
65
86
  const system = useSystemStore();
66
87
  const { convertToAxon } = useProviderAxonConvert();
67
88
 
@@ -76,6 +97,12 @@ const formattedBalance = computed(() => {
76
97
  return formatAsset2Digits(`${balance} AXON`);
77
98
  });
78
99
 
100
+ // Форматированный баланс RUB из MicroWallet
101
+ const formattedRubBalance = computed(() => {
102
+ const available = walletStore.program_wallets[0]?.available || '0';
103
+ return formatAsset2Digits(`${available} ${system.info.symbols.root_govern_symbol}`);
104
+ });
105
+
79
106
  // Подсказка с расчетом AXON
80
107
  const depositHint = computed(() => {
81
108
  if (!depositAmount.value || parseFloat(depositAmount.value) <= 0) {
@@ -87,6 +114,13 @@ const depositHint = computed(() => {
87
114
  return `Будет зачислено: ${formatAsset2Digits(`${axonAmount} AXON`)} (курс: 1 AXON = ${AXON_GOVERN_RATE} RUB)`;
88
115
  });
89
116
 
117
+ // Стиль диалога - адаптивная ширина
118
+ const dialogStyle = computed(() => {
119
+ return {
120
+ maxWidth: 'min(400px, 90vw)'
121
+ };
122
+ });
123
+
90
124
  // Закрыть диалог
91
125
  const clear = () => {
92
126
  showDepositDialog.value = false;
@@ -94,13 +128,18 @@ const clear = () => {
94
128
  isSubmitting.value = false;
95
129
  };
96
130
 
131
+ // Переход на страницу кошелька
132
+ const goToWallet = () => {
133
+ router.push({ name: 'wallet' });
134
+ };
135
+
97
136
  // Обработчик пополнения (конвертация RUB в AXON)
98
137
  const handlerSubmit = async () => {
99
138
  isSubmitting.value = true;
100
139
  try {
101
140
  // Выполняем конвертацию RUB в AXON
102
141
  const success = await convertToAxon({
103
- convertAmount: depositAmount.value,
142
+ convertAmount: formatToAsset(depositAmount.value, system.info.symbols.root_govern_symbol, system.info.symbols.root_govern_precision),
104
143
  username: session.username || '',
105
144
  coopname: system.info.coopname || ''
106
145
  });
@@ -180,5 +219,23 @@ const handlerSubmit = async () => {
180
219
  }
181
220
  }
182
221
  }
222
+
223
+ .current-balance-section {
224
+ padding: 12px;
225
+ background: rgba(255, 255, 255, 0.05);
226
+ border-radius: 8px;
227
+ border: 1px solid rgba(255, 255, 255, 0.1);
228
+
229
+ .text-body2 {
230
+ font-weight: 600;
231
+ }
232
+ }
233
+
234
+ .contribution-info {
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 8px;
238
+ flex-wrap: wrap;
239
+ }
183
240
  }
184
241
  </style>
@@ -9,13 +9,10 @@ div.connection-dashboard
9
9
  //- Карточка домена
10
10
  .col-12.col-md-6
11
11
  DomainCard
12
-
13
- //- Карточка подписок
14
- .row.justify-center
15
- .col-12.col-md-6
16
12
  SubscriptionsCard
17
13
 
18
14
 
15
+
19
16
  </template>
20
17
 
21
18
  <script setup lang="ts">