@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 +6 -6
- package/src/entities/ConnectionAgreement/api/index.ts +3 -8
- package/src/entities/ConnectionAgreement/model/store.ts +11 -2
- package/src/features/Provider/api/index.ts +2 -3
- package/src/features/Provider/model/index.ts +15 -1
- package/src/pages/Union/ConnectionAgreement/ConnectionAgreementPage.vue +10 -11
- package/src/shared/api/errors.ts +46 -0
- package/src/shared/lib/utils/formatToAsset.ts +2 -2
- package/src/shared/ui/BaseDocument/BaseDocument.vue +0 -6
- package/src/widgets/ConnectionDashboard/ui/AxonWallet.vue +77 -20
- package/src/widgets/ConnectionDashboard/ui/ConnectionDashboard.vue +1 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coopenomics/desktop",
|
|
3
|
-
"version": "2025.11.
|
|
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.
|
|
29
|
-
"@coopenomics/notifications": "2025.11.
|
|
30
|
-
"@coopenomics/sdk": "2025.11.
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
+
isBadGateway.value = true
|
|
154
|
+
|
|
155
|
+
currentInstanceError.value = extractGraphQLErrorMessages(error)
|
|
149
156
|
// НЕ очищаем старые данные при ошибке - они остаются актуальными из localStorage
|
|
150
157
|
// currentInstance.value остается как есть
|
|
151
|
-
console.
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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);
|
package/src/shared/api/errors.ts
CHANGED
|
@@ -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(
|
|
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
|
}
|
|
@@ -29,39 +29,60 @@
|
|
|
29
29
|
|
|
30
30
|
// Диалог пополнения
|
|
31
31
|
q-dialog(v-model="showDepositDialog", @hide="clear")
|
|
32
|
-
ModalBase(title="Пополнение кошелька AXON")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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>
|