@coopenomics/desktop 2025.11.15-alpha-1 → 2025.11.16-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coopenomics/desktop",
3
- "version": "2025.11.15-alpha-1",
3
+ "version": "2025.11.16-alpha-1",
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.15-alpha-1",
29
- "@coopenomics/notifications": "2025.11.15-alpha-1",
30
- "@coopenomics/sdk": "2025.11.15-alpha-1",
28
+ "@coopenomics/controller": "2025.11.16-alpha-1",
29
+ "@coopenomics/notifications": "2025.11.16-alpha-1",
30
+ "@coopenomics/sdk": "2025.11.16-alpha-1",
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.15-alpha-1",
62
+ "cooptypes": "2025.11.16-alpha-1",
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": "3671c05573f13ccdfc23fc90c43dd5e7a7b8fd1d"
126
+ "gitHead": "8ed8af1b9ce9fbbfc01c9221cdd46230e6f56662"
127
127
  }
@@ -236,3 +236,8 @@ a:visited {
236
236
  /* .body--light{
237
237
  background-color:rgb(251, 250, 250);
238
238
  } */
239
+
240
+
241
+ .q-stepper--vertical .q-stepper__step-inner {
242
+ padding: 0 0px 31px 35px !important;
243
+ }
@@ -16,7 +16,7 @@ export async function getCurrentInstance(): Promise<CurrentInstance | null> {
16
16
 
17
17
  return instance;
18
18
  } catch (error) {
19
- console.error('Ошибка при получении текущего инстанса:', error);
19
+ console.warn('Инстанс не получен. Продолжаем работу без него:', error);
20
20
  return null;
21
21
  }
22
22
  }
@@ -5,42 +5,51 @@ div
5
5
  q-card-section
6
6
  div.text-h6 Наименование
7
7
  div
8
- q-badge.q-mr-sm.q-mb-md ОПФ
9
- | - организационно-правовая форма
8
+ q-badge.q-mr-sm.q-mb-md ОПФ+
9
+ | - основа наименования (ОПФ + цель деятельности)
10
+ .text-caption.text-grey Например: "Потребительский Кооператив Социального Комплекса" или просто "Потребительский Кооператив"
11
+
10
12
  q-input(
11
13
  autofocus
12
14
  v-model="installStore.vars.name"
13
- label="Наименование организации (без ОПФ)"
15
+ label="Собственное название организации"
14
16
  placeholder="Ромашка"
15
17
  standout="bg-teal text-white"
18
+ :rules="[val => notEmpty(val)]"
16
19
  )
20
+ .text-caption.text-grey Название без кавычек, которое будет добавляться к основе
17
21
 
18
22
  q-input.q-mt-md(
19
23
  v-model="installStore.vars.full_abbr"
20
- label="Полная ОПФ в именительном падеже"
21
- placeholder="Потребительский Кооператив"
24
+ label="Основа наименования в именительном падеже"
25
+ placeholder="Потребительский Кооператив Социального Комплекса"
22
26
  standout="bg-teal text-white"
27
+ :rules="[val => notEmpty(val)]"
23
28
  )
29
+ .text-caption.text-grey Вся часть до названия. Если уточнения нет - просто ОПФ
24
30
 
25
31
  q-input.q-mt-md(
26
32
  v-model="installStore.vars.full_abbr_genitive"
27
- label="Полная ОПФ в родительном падеже"
28
- placeholder="Потребительского Кооператива"
33
+ label="Основа наименования в родительном падеже"
34
+ placeholder="Потребительского Кооператива Социального Комплекса"
29
35
  standout="bg-teal text-white"
36
+ :rules="[val => notEmpty(val)]"
30
37
  )
31
38
 
32
39
  q-input.q-mt-md(
33
40
  v-model="installStore.vars.full_abbr_dative"
34
- label="Полная ОПФ в дательном падеже"
35
- placeholder="Потребительскому Кооперативу"
41
+ label="Основа наименования в дательном падеже"
42
+ placeholder="Потребительскому Кооперативу Социального Комплекса"
36
43
  standout="bg-teal text-white"
44
+ :rules="[val => notEmpty(val)]"
37
45
  )
38
46
 
39
47
  q-input.q-mt-md(
40
48
  v-model="installStore.vars.short_abbr"
41
- label="Краткая аббривиатура ОПФ"
49
+ label="Краткая аббревиатура ОПФ"
42
50
  placeholder="ПК"
43
51
  standout="bg-teal text-white"
52
+ :rules="[val => notEmpty(val)]"
44
53
  )
45
54
 
46
55
  q-card(flat).q-mb-md
@@ -53,6 +62,7 @@ div
53
62
  placeholder="https://example.com/statute.pdf"
54
63
  type="url"
55
64
  standout="bg-teal text-white"
65
+ :rules="[val => notEmpty(val)]"
56
66
  )
57
67
 
58
68
  q-card(flat).q-mb-md
@@ -90,6 +100,7 @@ div
90
100
  label="Email по вопросам конфиденциальности"
91
101
  type="email"
92
102
  standout="bg-teal text-white"
103
+ :rules="[val => notEmpty(val)]"
93
104
  )
94
105
 
95
106
  //- q-input.q-mt-md(
@@ -111,12 +122,14 @@ div
111
122
  label="Номер протокола"
112
123
  :placeholder="getCurrentProtocolNumber"
113
124
  standout="bg-teal text-white"
125
+ :rules="[val => notEmpty(val)]"
114
126
  )
115
127
  q-input.q-mt-sm(
116
128
  v-model="installStore.vars.wallet_agreement.protocol_day_month_year"
117
129
  label="Дата протокола"
118
130
  :placeholder="getCurrentProtocolDate"
119
131
  standout="bg-teal text-white"
132
+ :rules="[val => notEmpty(val)]"
120
133
  )
121
134
 
122
135
  div.q-mt-md
@@ -126,12 +139,14 @@ div
126
139
  label="Номер протокола"
127
140
  :placeholder="getCurrentProtocolNumber"
128
141
  standout="bg-teal text-white"
142
+ :rules="[val => notEmpty(val)]"
129
143
  )
130
144
  q-input.q-mt-sm(
131
145
  v-model="installStore.vars.signature_agreement.protocol_day_month_year"
132
146
  label="Дата протокола"
133
147
  :placeholder="getCurrentProtocolDate"
134
148
  standout="bg-teal text-white"
149
+ :rules="[val => notEmpty(val)]"
135
150
  )
136
151
 
137
152
  div.q-mt-md
@@ -141,12 +156,14 @@ div
141
156
  label="Номер протокола"
142
157
  :placeholder="getCurrentProtocolNumber"
143
158
  standout="bg-teal text-white"
159
+ :rules="[val => notEmpty(val)]"
144
160
  )
145
161
  q-input.q-mt-sm(
146
162
  v-model="installStore.vars.privacy_agreement.protocol_day_month_year"
147
163
  label="Дата протокола"
148
164
  :placeholder="getCurrentProtocolDate"
149
165
  standout="bg-teal text-white"
166
+ :rules="[val => notEmpty(val)]"
150
167
  )
151
168
 
152
169
  div.q-mt-md
@@ -156,12 +173,14 @@ div
156
173
  label="Номер протокола"
157
174
  :placeholder="getCurrentProtocolNumber"
158
175
  standout="bg-teal text-white"
176
+ :rules="[val => notEmpty(val)]"
159
177
  )
160
178
  q-input.q-mt-sm(
161
179
  v-model="installStore.vars.user_agreement.protocol_day_month_year"
162
180
  label="Дата протокола"
163
181
  :placeholder="getCurrentProtocolDate"
164
182
  standout="bg-teal text-white"
183
+ :rules="[val => notEmpty(val)]"
165
184
  )
166
185
 
167
186
  div.q-mt-md
@@ -171,12 +190,14 @@ div
171
190
  label="Номер протокола"
172
191
  :placeholder="getCurrentProtocolNumber"
173
192
  standout="bg-teal text-white"
193
+ :rules="[val => notEmpty(val)]"
174
194
  )
175
195
  q-input.q-mt-sm(
176
196
  v-model="installStore.vars.participant_application.protocol_day_month_year"
177
197
  label="Дата протокола"
178
198
  :placeholder="getCurrentProtocolDate"
179
199
  standout="bg-teal text-white"
200
+ :rules="[val => notEmpty(val)]"
180
201
  )
181
202
 
182
203
  div.flex.justify-between.q-mt-md
@@ -191,6 +212,7 @@ import { useInstallCooperativeStore } from 'src/entities/Installer/model';
191
212
  import { useSystemStore } from 'src/entities/System/model';
192
213
  import { FailAlert, SuccessAlert } from 'src/shared/api';
193
214
  import { useInstallCooperative } from '../../model';
215
+ import { notEmpty } from 'src/shared/lib/utils';
194
216
  import { ref, onMounted, computed } from 'vue';
195
217
 
196
218
  const installStore = useInstallCooperativeStore()
@@ -78,31 +78,26 @@ export function useNotificationPermissionDialog() {
78
78
 
79
79
  // Обработка согласия пользователя
80
80
  const handleAllow = async () => {
81
+ // Немедленно закрываем диалог и сохраняем выбор пользователя
82
+ saveChoice('granted');
83
+ hideDialog();
84
+
85
+ // Выполняем подписку в фоне
81
86
  try {
82
- store.isPermissionDialogProcessing = true;
83
- console.log('handleAllow');
84
- // Подписываемся на уведомления (это включает запрос разрешения)
87
+ console.log('handleAllow - начинаем фоновую подписку');
85
88
  const success = await subscribe();
86
89
  console.log('success', success);
87
- if (success) {
88
- console.log('saveChoice granted');
89
- saveChoice('granted');
90
- console.log('hideDialog');
91
- hideDialog();
92
- } else {
93
- console.log('saveChoice denied');
94
- // Если подписка не удалась, но пользователь согласился,
95
- // все равно сохраняем выбор чтобы не показывать диалог снова
90
+ if (!success) {
91
+ console.log('saveChoice denied - подписка не удалась');
92
+ // Если подписка не удалась, меняем выбор на denied
93
+ // чтобы диалог мог появиться снова при необходимости
96
94
  saveChoice('denied');
97
- hideDialog();
98
95
  }
99
96
  } catch (error) {
100
97
  console.error('Ошибка при разрешении уведомлений:', error);
101
- console.log('saveChoice denied');
98
+ console.log('saveChoice denied - ошибка подписки');
99
+ // При ошибке тоже меняем на denied
102
100
  saveChoice('denied');
103
- hideDialog();
104
- } finally {
105
- store.isPermissionDialogProcessing = false;
106
101
  }
107
102
  };
108
103
 
@@ -1,7 +1,6 @@
1
1
  <template lang="pug">
2
2
  q-dialog(
3
3
  v-model='isDialogVisible',
4
- persistent,
5
4
  no-esc-dismiss,
6
5
  no-backdrop-dismiss,
7
6
  transition-show='fade',
@@ -1,23 +1,87 @@
1
1
  <template lang="pug">
2
2
  div
3
- Form(:handler-submit="saveData" :showCancel="false" :button-cancel-txt="'Отменить'" :button-submit-txt="'Продолжить'" @cancel="clear").q-gutter-md
4
- q-input(standout="bg-teal text-white" hint="domovoy.com или coop.domovoy.com" label="Домен или поддомен для запуска" v-model="formData.announce" :rules="[val => notEmpty(val), val => isDomain(val)]")
3
+ Form(:handler-submit="saveData" :showCancel="true" :button-cancel-txt="'Назад'" :button-submit-txt="'Продолжить'" @cancel="handleBack").q-gutter-md
5
4
 
6
- q-input(standout="bg-teal text-white" placeholder="100" label="Вступительный взнос для физлиц и ИП" v-model="formData.initial" type="number" :min="0" :rules="[val => notEmpty(val)]")
7
- template(#append)
8
- span.text-overline RUB
5
+ //- Домен кооператива
6
+ .form-section.q-mb-lg
7
+ .section-header.q-mb-md
8
+ .section-title
9
+ q-icon(name="domain" size="20px" color="primary").q-mr-sm
10
+ span.text-subtitle1.text-weight-medium Домен кооператива
11
+ .section-description.text-body2.q-mt-sm
12
+ | Укажите домен или поддомен, на котором будет работать ваш кооператив
9
13
 
10
- q-input(standout="bg-teal text-white" label="Минимальный паевый взнос для физлиц и ИП" placeholder="300" v-model="formData.minimum" type="number" :min="0" :rules="[val => notEmpty(val)]")
11
- template(#append)
12
- span.text-overline RUB
14
+ q-input.form-input(
15
+ standout="bg-teal text-white"
16
+ hint="domovoy.com или coop.domovoy.com"
17
+ label="Домен или поддомен для запуска"
18
+ v-model="formData.announce"
19
+ :rules="[val => notEmpty(val), val => isDomain(val)]"
20
+ )
13
21
 
14
- q-input(standout="bg-teal text-white" placeholder="1000" label="Вступительный взнос для организаций" v-model="formData.org_initial" type="number" :min="0" :rules="[val => notEmpty(val)]")
15
- template(#append)
16
- span.text-overline RUB
22
+ //- Финансовые параметры
23
+ .form-section.q-mb-lg
24
+ .section-header.q-mb-md
25
+ .section-title
26
+ q-icon(name="account_balance_wallet" size="20px" color="secondary").q-mr-sm
27
+ span.text-subtitle1.text-weight-medium Финансовые параметры
28
+ .section-description.text-body2.q-mt-sm
29
+ | Введите вступительные и минимальные паевые взносы пайщиков. Эти параметры будут должны быть указаны в уставных документах кооператива.
17
30
 
18
- q-input(standout="bg-teal text-white" placeholder="3000" label="Минимальный паевый взнос для организаций" v-model="formData.org_minimum" type="number" :min="0" :rules="[val => notEmpty(val)]")
19
- template(#append)
20
- span.text-overline RUB
31
+ .financial-grid
32
+ .grid-section.q-mb-md
33
+ .subsection-title.text-body1.text-weight-medium.q-mb-sm Для физических лиц и ИП
34
+ .input-row.q-mb-sm
35
+ q-input.form-input(
36
+ standout="bg-teal text-white"
37
+ placeholder="100"
38
+ label="Вступительный взнос"
39
+ v-model="formData.initial"
40
+ type="number"
41
+ :min="0"
42
+ :rules="[val => notEmpty(val)]"
43
+ )
44
+ template(#append)
45
+ span.text-overline RUB
46
+ .input-row
47
+ q-input.form-input(
48
+ standout="bg-teal text-white"
49
+ label="Минимальный паевый взнос"
50
+ placeholder="300"
51
+ v-model="formData.minimum"
52
+ type="number"
53
+ :min="0"
54
+ :rules="[val => notEmpty(val)]"
55
+ )
56
+ template(#append)
57
+ span.text-overline RUB
58
+
59
+ .grid-section
60
+ .subsection-title.text-body1.text-weight-medium.q-mb-sm Для организаций
61
+ .input-row.q-mb-sm
62
+ q-input.form-input(
63
+ standout="bg-teal text-white"
64
+ placeholder="1000"
65
+ label="Вступительный взнос"
66
+ v-model="formData.org_initial"
67
+ type="number"
68
+ :min="0"
69
+ :rules="[val => notEmpty(val)]"
70
+ )
71
+ template(#append)
72
+ span.text-overline RUB
73
+ .input-row
74
+ q-input.form-input(
75
+ standout="bg-teal text-white"
76
+ placeholder="3000"
77
+ label="Минимальный паевый взнос"
78
+ v-model="formData.org_minimum"
79
+ type="number"
80
+ :min="0"
81
+ :rules="[val => notEmpty(val)]"
82
+ )
83
+ template(#append)
84
+ span.text-overline RUB
21
85
  </template>
22
86
  <script lang="ts" setup>
23
87
  import { ref, watch } from 'vue'
@@ -27,7 +91,7 @@ import { useConnectionAgreementStore } from 'src/entities/ConnectionAgreement';
27
91
  import type { ICooperativeFormData } from 'src/entities/ConnectionAgreement/model/types';
28
92
 
29
93
 
30
- const emit = defineEmits(['continue'])
94
+ const emit = defineEmits(['continue', 'back'])
31
95
 
32
96
  const connectionAgreement = useConnectionAgreementStore()
33
97
 
@@ -56,8 +120,8 @@ const syncFormData = () => {
56
120
  connectionAgreement.setFormData(formData.value)
57
121
  }
58
122
 
59
- const clear = () => {
60
- emit('continue')
123
+ const handleBack = () => {
124
+ emit('back')
61
125
  }
62
126
 
63
127
  const saveData = async () => {
@@ -67,3 +131,110 @@ const saveData = async () => {
67
131
  emit('continue', formData.value)
68
132
  }
69
133
  </script>
134
+
135
+ <style scoped>
136
+ .form-section {
137
+ padding: 1.5rem;
138
+ border-radius: 16px;
139
+ border: 1px solid rgba(0, 0, 0, 0.05);
140
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
141
+ }
142
+
143
+ .section-header {
144
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
145
+ padding-bottom: 1rem;
146
+ display: block;
147
+ }
148
+
149
+ .section-title {
150
+ display: flex;
151
+ align-items: center;
152
+ margin-bottom: 0.25rem;
153
+ }
154
+
155
+ .section-description {
156
+ line-height: 1.5;
157
+ }
158
+
159
+ .financial-grid {
160
+ display: grid;
161
+ grid-template-columns: 1fr 1fr;
162
+ gap: 2rem;
163
+ }
164
+
165
+ .grid-section {
166
+ padding: 1rem;
167
+ border-radius: 12px;
168
+ border: 1px solid rgba(0, 0, 0, 0.03);
169
+ }
170
+
171
+ .subsection-title {
172
+ color: var(--q-primary);
173
+ border-bottom: 2px solid var(--q-primary);
174
+ padding-bottom: 0.5rem;
175
+ margin-bottom: 1rem;
176
+ }
177
+
178
+ .input-row {
179
+ margin-bottom: 1rem;
180
+ }
181
+
182
+ .input-row:last-child {
183
+ margin-bottom: 0;
184
+ }
185
+
186
+ .form-input {
187
+ margin-bottom: 0;
188
+ }
189
+
190
+ /* Разделитель между секциями */
191
+ .form-section + .form-section {
192
+ margin-top: 2rem;
193
+ }
194
+
195
+ /* Адаптивность */
196
+ @media (max-width: 768px) {
197
+ .financial-grid {
198
+ grid-template-columns: 1fr;
199
+ gap: 1.5rem;
200
+ }
201
+
202
+ .form-section {
203
+ padding: 1.25rem;
204
+ }
205
+ }
206
+
207
+ @media (max-width: 480px) {
208
+ .form-section {
209
+ padding: 1rem;
210
+ }
211
+
212
+ .section-title {
213
+ flex-direction: column;
214
+ gap: 0.5rem;
215
+ text-align: center;
216
+ }
217
+
218
+ .section-header {
219
+ text-align: center;
220
+ display: block !important;
221
+ }
222
+
223
+ .section-description {
224
+ display: block !important;
225
+ width: 100% !important;
226
+ }
227
+
228
+ .financial-grid {
229
+ gap: 1rem;
230
+ }
231
+
232
+ .grid-section {
233
+ padding: 0.75rem;
234
+ }
235
+
236
+ .subsection-title {
237
+ font-size: 1rem;
238
+ }
239
+ }
240
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- div.row.q-pa-md
2
+ div.row
3
3
  div.col-md-12.col-xs-12
4
4
  // Лоадер пока идет загрузка данных
5
5
  WindowLoader(v-if="isLoading", text="Загрузка данных подключения...")
@@ -104,13 +104,13 @@ const init = async () => {
104
104
  isLoading.value = false;
105
105
  return;
106
106
  }
107
-
107
+ console.log('SYSTEM.info.is_unioned', system.info.is_unioned, connectionAgreement.isInitialized);
108
108
  // Инициализируем persistent store если он еще не инициализирован
109
109
  if (!connectionAgreement.isInitialized) {
110
110
  connectionAgreement.setInitialized(true);
111
111
 
112
112
  // Устанавливаем начальный шаг в зависимости от членства в союзе
113
- if (!system.info.is_unioned) {
113
+ if (system.info.is_unioned) {
114
114
  // Если кооператив не является членом союза, начинаем с нулевого шага
115
115
  connectionAgreement.setCurrentStep(0);
116
116
  } else {
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, withDefaults } from 'vue'
2
+ import { computed, ref, withDefaults } from 'vue'
3
3
  import type { IStepProps } from '../model/types'
4
4
  import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader'
5
5
  import { Loader } from 'src/shared/ui/Loader'
@@ -15,8 +15,23 @@ const connectionAgreement = useConnectionAgreementStore()
15
15
 
16
16
  const isActive = computed(() => props.isActive)
17
17
  const isDone = computed(() => props.isDone)
18
+ const isSigning = ref(false)
19
+
20
+ // Проверяем, готов ли документ для подписания
21
+ const isDocumentReady = computed(() => {
22
+ return connectionAgreement.document && connectionAgreement.document.sign
23
+ })
18
24
 
19
25
  const handleSign = async () => {
26
+ if (isSigning.value) return // Предотвращаем повторные клики
27
+
28
+ // Дополнительная проверка готовности документа
29
+ if (!isDocumentReady.value) {
30
+ console.warn('⚠️ Документ еще не готов для подписания')
31
+ return
32
+ }
33
+
34
+ isSigning.value = true
20
35
  try {
21
36
  console.log('📝 AgreementStep: Подписываем документ')
22
37
  await connectionAgreement.signDocument()
@@ -67,14 +82,17 @@ const handleSign = async () => {
67
82
  } catch (error) {
68
83
  console.error('❌ Ошибка при подписании или отправке в блокчейн:', error)
69
84
  throw error
85
+ } finally {
86
+ isSigning.value = false
70
87
  }
71
88
  }
72
89
 
73
90
  const handleBack = () => {
74
- // Специальная логика для возврата - очищаем подписанный документ
91
+ // Специальная логика для возврата - очищаем документы для показа Loader при повторном переходе
75
92
  console.log(`⬅️ AgreementStep: Возврат с шага ${connectionAgreement.currentStep}`)
76
93
 
77
- // Очищаем подписанный документ без генерации нового
94
+ // Очищаем документы, чтобы при повторном переходе вперед показался Loader
95
+ connectionAgreement.setDocument(null)
78
96
  connectionAgreement.setSignedDocument(null)
79
97
 
80
98
  if (connectionAgreement.currentStep > 1) {
@@ -104,9 +122,12 @@ q-step(
104
122
  label="Назад"
105
123
  @click="handleBack"
106
124
  )
125
+
107
126
  q-btn(
108
127
  v-if="isActive"
109
128
  color="primary"
129
+ :loading="isSigning"
130
+ :disable="!isDocumentReady"
110
131
  label="Подписать"
111
132
  @click="handleSign"
112
133
  )
@@ -39,16 +39,51 @@ q-step(
39
39
  icon="settings"
40
40
  :done="isDone"
41
41
  )
42
- .q-pa-md
42
+ .form-step-container.q-pa-md
43
+
44
+ //- Заголовок шага
45
+ .step-header.q-mb-xl
46
+ .text-h6.form-title Настройка параметров кооператива
47
+ .subtitle.text-body2.text-grey-7.q-mt-sm
48
+ | Заполните основные параметры для запуска вашего Цифрового Кооператива
49
+
50
+ //- Форма ввода данных
43
51
  CooperativeDataForm(
44
52
  @continue="handleContinue"
45
- )
46
-
47
- q-stepper-navigation.q-gutter-sm
48
- q-btn(
49
- color="grey-6"
50
- flat
51
- label="Назад"
52
- @click="handleBack"
53
+ @back="handleBack"
53
54
  )
54
55
  </template>
56
+
57
+ <style scoped>
58
+ .form-step-container {
59
+ max-width: 900px;
60
+ margin: 0 auto;
61
+ position: relative;
62
+ }
63
+
64
+ .step-header {
65
+ text-align: center;
66
+ margin-bottom: 2rem;
67
+ }
68
+
69
+ .form-title {
70
+ font-weight: 700;
71
+ letter-spacing: -0.5px;
72
+ background: linear-gradient(135deg, var(--q-primary) 0%, rgba(25, 118, 210, 0.8) 100%);
73
+ -webkit-background-clip: text;
74
+ -webkit-text-fill-color: transparent;
75
+ background-clip: text;
76
+ text-shadow: 0 2px 4px rgba(25, 118, 210, 0.3);
77
+ }
78
+
79
+ /* Адаптивность */
80
+ @media (max-width: 480px) {
81
+ .form-step-container {
82
+ padding: 1rem;
83
+ }
84
+
85
+ .step-header {
86
+ margin-bottom: 1.5rem;
87
+ }
88
+ }
89
+ </style>
@@ -3,12 +3,14 @@ import { computed, withDefaults } from 'vue'
3
3
  import type { IStepProps } from '../model/types'
4
4
  import { TariffSelector, type ITariff } from '../Tariffs'
5
5
  import { useConnectionAgreementStore } from 'src/entities/ConnectionAgreement'
6
+ import { useSystemStore } from 'src/entities/System/model'
6
7
 
7
8
  const props = withDefaults(defineProps<IStepProps & {
8
9
  selectedTariff?: ITariff | null
9
10
  }>(), {})
10
11
 
11
12
  const connectionAgreement = useConnectionAgreementStore()
13
+ const system = useSystemStore()
12
14
 
13
15
  const isActive = computed(() => props.isActive)
14
16
  const isDone = computed(() => props.isDone)
@@ -28,6 +30,12 @@ const handleTariffDeselected = () => {
28
30
  selectedTariff.value = null
29
31
  }
30
32
 
33
+ const handleBack = () => {
34
+ if (system.info.is_unioned) {
35
+ connectionAgreement.setCurrentStep(0)
36
+ }
37
+ }
38
+
31
39
  const handleContinue = () => {
32
40
  if (canContinue.value && connectionAgreement.currentStep < 5) {
33
41
  connectionAgreement.setCurrentStep(connectionAgreement.currentStep + 1)
@@ -52,6 +60,13 @@ q-step(
52
60
  )
53
61
 
54
62
  q-stepper-navigation.q-gutter-sm
63
+ q-btn(
64
+ v-if="isActive && system.info.is_unioned"
65
+ color="grey-6"
66
+ flat
67
+ label="Назад"
68
+ @click="handleBack"
69
+ )
55
70
  q-btn(
56
71
  v-if="isActive"
57
72
  color="primary"
@@ -35,25 +35,58 @@ q-step(
35
35
  icon="group"
36
36
  :done="isDone"
37
37
  )
38
- .q-pa-md
39
- .text-h6.q-mb-md Для подключения к Кооперативной Экономике кооператив должен быть членом союза
40
-
41
- .q-mb-md
42
- | Для вступления в союз перейдите по ссылке:
43
- br
44
- q-btn(
45
- v-if="unionLink"
46
- flat
47
- color="primary"
48
- :label="unionLink"
49
- @click="openUnionLink"
50
- no-caps
51
- class="q-mt-sm"
52
- )
53
- span(v-else).text-grey-6 Ссылка недоступна
54
-
55
- .q-mb-lg После вступления в союз, пожалуйста, продолжайте процесс подключения.
38
+ .union-membership-container.q-pa-md
56
39
 
40
+ //- Основная информационная карточка
41
+ .membership-info-card.q-mb-xl
42
+ .card-header
43
+ .text-h6.membership-title Членство в Союзе Потребительских Обществ Русь
44
+ .subtitle.text-body2.text-grey-7.q-mt-sm
45
+ | Необходимый шаг для полноценной работы на платформе Кооперативной Экономики
46
+
47
+ //- Что будет сделано
48
+ .membership-benefits.q-mb-lg
49
+ .benefit-section
50
+ .section-title
51
+ q-icon(name="check_circle" size="20px" color="positive").q-mr-sm
52
+ span.text-subtitle1.text-weight-medium Ваш кооператив будет подключен к платформе
53
+
54
+ .benefit-description.text-body2.text-grey-8.q-mt-sm.q-ml-lg
55
+ | Согласно выбранному тарифу членских взносов ваш Цифровой Кооператив получит доступ ко всем сервисам платформы Кооперативной Экономики
56
+
57
+ .benefit-section.q-mt-md
58
+ .section-title
59
+ q-icon(name="security" size="20px" color="primary").q-mr-sm
60
+ span.text-subtitle1.text-weight-medium Документооборот в порядке
61
+
62
+ .benefit-description.text-body2.text-grey-8.q-mt-sm.q-ml-lg
63
+ | Членство в союзе обеспечивает юридическую чистоту всех операций и соответствие требованиям кооперативного законодательства
64
+
65
+ //- Требование членства
66
+ .membership-requirement.q-mt-lg
67
+ .requirement-card
68
+ .requirement-header
69
+ q-icon(name="info" size="24px" color="warning").q-mr-sm
70
+ .text-subtitle1.text-weight-medium Необходимость вступления в союз
71
+
72
+ .requirement-description.text-body2.q-mt-sm
73
+ | Для работы на платформе и обеспечения базового документооборота ваш кооператив должен быть членом Союза Потребительских Обществ Русь. Это обязательное условие для всех участников Кооперативной Экономики.
74
+
75
+ .union-link-section.q-mt-md
76
+ .text-body2.q-mb-sm Для вступления в союз перейдите по официальной ссылке:
77
+ q-btn.union-link-btn(
78
+ v-if="unionLink"
79
+ flat
80
+ color="primary"
81
+ :label="unionLink"
82
+ @click="openUnionLink"
83
+ no-caps
84
+ class="q-mt-sm"
85
+ )
86
+ q-icon(name="open_in_new" size="16px").q-ml-xs
87
+ span(v-else).text-grey-6.q-mt-sm Ссылка временно недоступна
88
+
89
+ //- Навигация
57
90
  q-stepper-navigation.q-gutter-sm
58
91
  q-btn(
59
92
  v-if="isActive"
@@ -62,3 +95,215 @@ q-step(
62
95
  @click="handleContinue"
63
96
  )
64
97
  </template>
98
+
99
+ <style scoped>
100
+ .union-membership-container {
101
+ max-width: 900px;
102
+ margin: 0 auto;
103
+ position: relative;
104
+ }
105
+
106
+ /* Основная информационная карточка */
107
+ .membership-info-card {
108
+ border-radius: 20px;
109
+ padding: 2.5rem;
110
+ border: 1px solid rgba(0, 0, 0, 0.05);
111
+ box-shadow:
112
+ 0 8px 32px rgba(0, 0, 0, 0.08),
113
+ 0 2px 8px rgba(0, 0, 0, 0.04);
114
+ position: relative;
115
+ overflow: hidden;
116
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 0.95) 100%);
117
+ }
118
+
119
+ .membership-info-card::before {
120
+ content: '';
121
+ position: absolute;
122
+ top: 0;
123
+ left: 0;
124
+ right: 0;
125
+ height: 4px;
126
+ background: linear-gradient(90deg, var(--q-primary) 0%, var(--q-secondary) 50%, var(--q-accent) 100%);
127
+ background-size: 200% 100%;
128
+ animation: shimmer 4s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
129
+ }
130
+
131
+ @keyframes shimmer {
132
+ 0% { background-position: 100% 0; }
133
+ 100% { background-position: -100% 0; }
134
+ }
135
+
136
+ .card-header {
137
+ text-align: center;
138
+ margin-bottom: 2rem;
139
+ }
140
+
141
+ .membership-title {
142
+ font-weight: 700;
143
+ letter-spacing: -0.5px;
144
+ background: linear-gradient(135deg, var(--q-primary) 0%, rgba(25, 118, 210, 0.8) 100%);
145
+ -webkit-background-clip: text;
146
+ -webkit-text-fill-color: transparent;
147
+ background-clip: text;
148
+ text-shadow: 0 2px 4px rgba(25, 118, 210, 0.3);
149
+ }
150
+
151
+ /* Секции преимуществ */
152
+ .membership-benefits {
153
+ display: flex;
154
+ flex-direction: column;
155
+ gap: 1rem;
156
+ }
157
+
158
+ .benefit-section {
159
+ padding: 1.5rem;
160
+ border-radius: 16px;
161
+ background: rgba(25, 118, 210, 0.02);
162
+ border: 1px solid rgba(25, 118, 210, 0.1);
163
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
164
+ }
165
+
166
+ .benefit-section:hover {
167
+ transform: translateY(-2px);
168
+ box-shadow: 0 8px 25px rgba(25, 118, 210, 0.1);
169
+ background: rgba(25, 118, 210, 0.05);
170
+ }
171
+
172
+ .section-title {
173
+ display: flex;
174
+ align-items: center;
175
+ margin-bottom: 0.5rem;
176
+ }
177
+
178
+ .benefit-description {
179
+ line-height: 1.6;
180
+ padding-left: 24px;
181
+ position: relative;
182
+ }
183
+
184
+ .benefit-description::before {
185
+ content: '';
186
+ position: absolute;
187
+ left: 0;
188
+ top: 0;
189
+ bottom: 0;
190
+ width: 2px;
191
+ background: linear-gradient(180deg, var(--q-primary) 0%, rgba(25, 118, 210, 0.3) 100%);
192
+ border-radius: 1px;
193
+ }
194
+
195
+ /* Требование членства */
196
+ .membership-requirement {
197
+ border-top: 1px solid rgba(0, 0, 0, 0.08);
198
+ padding-top: 2rem;
199
+ }
200
+
201
+ .requirement-card {
202
+ padding: 2rem;
203
+ border-radius: 16px;
204
+ background: linear-gradient(135deg, rgba(255, 152, 0, 0.02) 0%, rgba(255, 193, 7, 0.02) 100%);
205
+ border: 1px solid rgba(255, 152, 0, 0.1);
206
+ position: relative;
207
+ }
208
+
209
+ .requirement-card::before {
210
+ content: '';
211
+ position: absolute;
212
+ top: 0;
213
+ left: 0;
214
+ right: 0;
215
+ height: 3px;
216
+ background: linear-gradient(90deg, #FF9800 0%, #FFC107 50%, #FF9800 100%);
217
+ border-radius: 16px 16px 0 0;
218
+ }
219
+
220
+ .requirement-header {
221
+ display: flex;
222
+ align-items: center;
223
+ margin-bottom: 1rem;
224
+ }
225
+
226
+ .requirement-description {
227
+ line-height: 1.6;
228
+ color: #555;
229
+ }
230
+
231
+ /* Секция ссылки на союз */
232
+ .union-link-section {
233
+ padding: 1rem;
234
+ border-radius: 12px;
235
+ background: rgba(255, 255, 255, 0.5);
236
+ border: 1px solid rgba(0, 0, 0, 0.05);
237
+ }
238
+
239
+ .union-link-btn {
240
+ width: 100%;
241
+ justify-content: center;
242
+ padding: 0.75rem 1rem;
243
+ border-radius: 12px;
244
+ font-weight: 500;
245
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
246
+ box-shadow: 0 2px 8px rgba(25, 118, 210, 0.1);
247
+ }
248
+
249
+ .union-link-btn:hover {
250
+ transform: translateY(-1px);
251
+ box-shadow: 0 4px 16px rgba(25, 118, 210, 0.2);
252
+ }
253
+
254
+ /* Адаптивность */
255
+ @media (max-width: 768px) {
256
+ .membership-info-card {
257
+ padding: 2rem;
258
+ }
259
+
260
+ .benefit-section {
261
+ padding: 1.25rem;
262
+ }
263
+
264
+ .requirement-card {
265
+ padding: 1.5rem;
266
+ }
267
+
268
+ .benefit-description {
269
+ padding-left: 20px;
270
+ }
271
+
272
+ .benefit-description::before {
273
+ width: 1.5px;
274
+ }
275
+ }
276
+
277
+ @media (max-width: 480px) {
278
+ .union-membership-container {
279
+ padding: 1rem;
280
+ }
281
+
282
+ .membership-info-card {
283
+ padding: 1.5rem;
284
+ }
285
+
286
+ .benefit-section {
287
+ padding: 1rem;
288
+ }
289
+
290
+ .requirement-card {
291
+ padding: 1.25rem;
292
+ }
293
+
294
+ .section-title {
295
+ flex-direction: column;
296
+ gap: 0.5rem;
297
+ text-align: center;
298
+ }
299
+
300
+ .benefit-description {
301
+ padding-left: 0;
302
+ text-align: center;
303
+ }
304
+
305
+ .benefit-description::before {
306
+ display: none;
307
+ }
308
+ }
309
+ </style>
@@ -97,7 +97,6 @@ div.tariff-card-container
97
97
  min-height: 360px; /* Уменьшенная минимальная высота */
98
98
  padding: 24px;
99
99
  border-radius: 16px;
100
- background: white;
101
100
  border: 2px solid #f0f0f0;
102
101
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
103
102
  cursor: pointer;
@@ -220,7 +219,6 @@ div.tariff-card-container
220
219
  letter-spacing: -1px;
221
220
  background: linear-gradient(135deg, var(--q-accent) 0%, rgba(25, 118, 210, 0.8) 100%);
222
221
  -webkit-background-clip: text;
223
- -webkit-text-fill-color: transparent;
224
222
  background-clip: text;
225
223
  line-height: 1;
226
224
  text-shadow: 0 2px 4px rgba(25, 118, 210, 0.3);
@@ -29,7 +29,7 @@ q-dialog.cmdk-dialog(
29
29
 
30
30
  // Список результатов
31
31
  div.cmdk-content(ref="contentRef")
32
- transition-group.cmdk-results(
32
+ transition-group(
33
33
  name="cmdk-fade",
34
34
  tag="div"
35
35
  )
@@ -177,10 +177,6 @@ onUnmounted(() => {
177
177
  }
178
178
  }
179
179
 
180
- .cmdk-results {
181
- padding: 8px 0;
182
- }
183
-
184
180
  .cmdk-group {
185
181
  &:not(:last-child) {
186
182
  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
@@ -174,7 +174,7 @@ if (!shouldRegisterSW) {
174
174
  // Показываем уведомление пользователю о доступном обновлении
175
175
  if ('Notification' in window && Notification.permission === 'granted') {
176
176
  new Notification('Обновление доступно', {
177
- body: 'Новая версия приложения готова к установке',
177
+ body: 'Выпущена новая версия приложения. Нажмите на уведомление, чтобы обновить приложение.',
178
178
  icon: '/icons/icon-192x192.png',
179
179
  });
180
180
  }
@@ -193,7 +193,7 @@ if (!shouldRegisterSW) {
193
193
  if ('Notification' in window) {
194
194
  if (Notification.permission === 'granted') {
195
195
  new Notification('Обновление доступно', {
196
- body: 'Новая версия приложения готова. Нажмите для обновления.',
196
+ body: 'Выпущена новая версия приложения. Нажмите на уведомление, чтобы обновить приложение.',
197
197
  icon: '/icons/icon-192x192.png',
198
198
  requireInteraction: true,
199
199
  }).onclick = () => {
@@ -203,7 +203,7 @@ if (!shouldRegisterSW) {
203
203
  Notification.requestPermission().then((permission) => {
204
204
  if (permission === 'granted') {
205
205
  new Notification('Обновление доступно', {
206
- body: 'Новая версия приложения готова. Нажмите для обновления.',
206
+ body: 'Выпущена новая версия приложения. Нажмите на уведомление, чтобы обновить приложение.',
207
207
  icon: '/icons/icon-192x192.png',
208
208
  requireInteraction: true,
209
209
  }).onclick = () => {