@coopenomics/desktop 2025.11.9-alpha-1 → 2025.11.10-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.9-alpha-1",
3
+ "version": "2025.11.10-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.9-alpha-1",
29
- "@coopenomics/notifications": "2025.11.9-alpha-1",
30
- "@coopenomics/sdk": "2025.11.9-alpha-1",
28
+ "@coopenomics/controller": "2025.11.10-alpha-1",
29
+ "@coopenomics/notifications": "2025.11.10-alpha-1",
30
+ "@coopenomics/sdk": "2025.11.10-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.9-alpha-1",
62
+ "cooptypes": "2025.11.10-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": "8270801711836de12cce027677732785fab42dd1"
126
+ "gitHead": "634766a08edc036ccf4b08438b14251fa3a8ab73"
127
127
  }
package/src/app/App.vue CHANGED
@@ -33,11 +33,23 @@ const { showDialog } = useNotificationPermissionDialog();
33
33
  onMounted(async () => {
34
34
  try {
35
35
  console.log('systemInfo', info)
36
- // Если мы в SPA режиме (hash mode) и pathname не является главной страницей
37
- // и при этом hash пустой или указывает не на текущий pathname
38
- if (typeof window !== 'undefined' && window.location.pathname !== '/' &&
39
- (!window.location.hash || !window.location.hash.includes(window.location.pathname))) {
40
- console.log('URL needs hash correction');
36
+ // Проверяем, нужно ли корректировать URL для hash роутера
37
+ // Выполняем только в клиентском режиме с hash роутером
38
+ const isClientMode = process.env.CLIENT === 'true';
39
+ const isHashRouter = process.env.VUE_ROUTER_MODE === 'hash';
40
+ const isNotSSR = process.env.SERVER !== 'true';
41
+ const hasWindow = typeof window !== 'undefined';
42
+
43
+ const shouldCorrectHashUrl =
44
+ isClientMode &&
45
+ isHashRouter &&
46
+ isNotSSR &&
47
+ hasWindow &&
48
+ window.location.pathname !== '/' &&
49
+ (!window.location.hash || !window.location.hash.includes(window.location.pathname));
50
+
51
+ if (shouldCorrectHashUrl) {
52
+ console.log('URL needs hash correction for hash router mode');
41
53
  const newUrl = window.location.origin + '/#' + window.location.pathname + window.location.search;
42
54
  console.log('Redirecting to:', newUrl);
43
55
  window.location.replace(newUrl);
package/src/env.d.ts CHANGED
@@ -5,6 +5,8 @@ declare namespace NodeJS {
5
5
  NODE_ENV: string;
6
6
  VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
7
7
  VUE_ROUTER_BASE: string | undefined;
8
+ CLIENT?: string;
9
+ SERVER?: string;
8
10
  }
9
11
  }
10
12
 
@@ -80,14 +80,17 @@ export function useNotificationPermissionDialog() {
80
80
  const handleAllow = async () => {
81
81
  try {
82
82
  store.isPermissionDialogProcessing = true;
83
-
83
+ console.log('handleAllow');
84
84
  // Подписываемся на уведомления (это включает запрос разрешения)
85
85
  const success = await subscribe();
86
-
86
+ console.log('success', success);
87
87
  if (success) {
88
+ console.log('saveChoice granted');
88
89
  saveChoice('granted');
90
+ console.log('hideDialog');
89
91
  hideDialog();
90
92
  } else {
93
+ console.log('saveChoice denied');
91
94
  // Если подписка не удалась, но пользователь согласился,
92
95
  // все равно сохраняем выбор чтобы не показывать диалог снова
93
96
  saveChoice('denied');
@@ -95,6 +98,7 @@ export function useNotificationPermissionDialog() {
95
98
  }
96
99
  } catch (error) {
97
100
  console.error('Ошибка при разрешении уведомлений:', error);
101
+ console.log('saveChoice denied');
98
102
  saveChoice('denied');
99
103
  hideDialog();
100
104
  } finally {
@@ -0,0 +1,12 @@
1
+ import { client } from 'src/shared/api/client';
2
+ import { Queries } from '@coopenomics/sdk';
3
+
4
+ /**
5
+ * Получить подписки пользователя у провайдера
6
+ */
7
+ export async function loadProviderSubscriptions() {
8
+ const { [Queries.System.GetProviderSubscriptions.name]: subscriptions } =
9
+ await client.Query(Queries.System.GetProviderSubscriptions.query);
10
+
11
+ return subscriptions;
12
+ }
@@ -0,0 +1 @@
1
+ export * from './model';
@@ -0,0 +1,105 @@
1
+ import { ref, computed } from 'vue';
2
+ import { loadProviderSubscriptions } from '../api';
3
+ import { useSystemStore } from 'src/entities/System/model';
4
+ import { Queries } from '@coopenomics/sdk';
5
+
6
+ /**
7
+ * Специфичные данные для подписки на хостинг
8
+ */
9
+ export interface HostingSubscriptionData {
10
+ subscription_type_id: 1;
11
+ progress: number;
12
+ is_valid: boolean;
13
+ }
14
+
15
+ /**
16
+ * Специфичные данные подписки (union тип для всех типов подписок)
17
+ */
18
+ export type SubscriptionSpecificData = HostingSubscriptionData | null;
19
+
20
+ /**
21
+ * Тип подписки провайдера из GraphQL API
22
+ */
23
+ export type ProviderSubscription = Queries.System.GetProviderSubscriptions.IOutput[typeof Queries.System.GetProviderSubscriptions.name][number];
24
+
25
+ /**
26
+ * Composable для работы с подписками провайдера
27
+ */
28
+ export function useProviderSubscriptions() {
29
+ const system = useSystemStore();
30
+ const subscriptions = ref<ProviderSubscription[]>([]);
31
+ const isLoading = ref(false);
32
+ const error = ref<string | null>(null);
33
+
34
+ // Получить подписку на хостинг (id=1)
35
+ const hostingSubscription = computed(() =>
36
+ subscriptions.value.find(sub => sub.subscription_type_id === 1)
37
+ );
38
+
39
+ // Статус валидности домена для хостинг подписки (из specific_data)
40
+ const domainValid = computed(() => {
41
+ const specificData = hostingSubscription.value?.specific_data as SubscriptionSpecificData;
42
+ return specificData?.is_valid ?? null;
43
+ });
44
+
45
+ // Прогресс установки для хостинг подписки (из specific_data)
46
+ const installationProgress = computed(() => {
47
+ const specificData = hostingSubscription.value?.specific_data as SubscriptionSpecificData;
48
+ return specificData?.progress ?? null;
49
+ });
50
+
51
+ // Статус инстанса для хостинг подписки (пока оставляем как есть, может поле будет добавлено позже)
52
+ const instanceStatus = computed(() =>
53
+ hostingSubscription.value?.instance_status ?? null
54
+ );
55
+
56
+ /**
57
+ * Загрузить подписки пользователя
58
+ */
59
+ const loadSubscriptions = async () => {
60
+ // Проверяем доступность провайдера
61
+ if (!system.info.is_providered) {
62
+ error.value = 'Функционал провайдера не доступен';
63
+ return;
64
+ }
65
+
66
+ try {
67
+ isLoading.value = true;
68
+ error.value = null;
69
+ subscriptions.value = await loadProviderSubscriptions();
70
+ } catch (err: any) {
71
+ error.value = err.message || 'Ошибка загрузки подписок';
72
+ console.error('Error loading provider subscriptions:', err);
73
+ } finally {
74
+ isLoading.value = false;
75
+ }
76
+ };
77
+
78
+ /**
79
+ * Автоматическая загрузка с интервалом
80
+ */
81
+ const startAutoRefresh = (intervalMs = 60000) => { // 1 минута по умолчанию
82
+ loadSubscriptions(); // Первая загрузка
83
+
84
+ const interval = setInterval(() => {
85
+ loadSubscriptions();
86
+ }, intervalMs);
87
+
88
+ // Функция для остановки автообновления
89
+ const stop = () => clearInterval(interval);
90
+
91
+ return stop;
92
+ };
93
+
94
+ return {
95
+ subscriptions,
96
+ isLoading,
97
+ error,
98
+ hostingSubscription,
99
+ domainValid,
100
+ installationProgress,
101
+ instanceStatus,
102
+ loadSubscriptions,
103
+ startAutoRefresh,
104
+ };
105
+ }
@@ -83,7 +83,16 @@ export function useWebPushNotifications() {
83
83
  throw new Error('Push уведомления не поддерживаются');
84
84
  }
85
85
 
86
- const permission = await Notification.requestPermission();
86
+ // Добавляем timeout на случай, если пользователь не взаимодействует с диалогом
87
+ const permission = await Promise.race([
88
+ Notification.requestPermission(),
89
+ new Promise<never>((_, reject) => {
90
+ setTimeout(() => {
91
+ reject(new Error('Пользователь не ответил на запрос разрешения в течение 30 секунд'));
92
+ }, 15000);
93
+ }),
94
+ ]);
95
+
87
96
  store.support.permission = permission;
88
97
  store.support.hasPermission = permission === 'granted';
89
98
  store.support.canSubscribe =
@@ -150,7 +159,16 @@ export function useWebPushNotifications() {
150
159
  }
151
160
 
152
161
  console.log('Форсированный запрос разрешения...');
153
- const permission = await Notification.requestPermission();
162
+
163
+ // Добавляем timeout на случай, если пользователь не взаимодействует с диалогом
164
+ const permission = await Promise.race([
165
+ Notification.requestPermission(),
166
+ new Promise<never>((_, reject) => {
167
+ setTimeout(() => {
168
+ reject(new Error('Пользователь не ответил на запрос разрешения в течение 30 секунд'));
169
+ }, 30000);
170
+ }),
171
+ ]);
154
172
 
155
173
  store.support.permission = permission;
156
174
  store.support.hasPermission = permission === 'granted';
@@ -267,7 +285,7 @@ export function useWebPushNotifications() {
267
285
  // Создаем новую подписку
268
286
  const subscription = await registration.pushManager.subscribe({
269
287
  userVisibleOnly: true,
270
- applicationServerKey: applicationServerKey,
288
+ applicationServerKey: applicationServerKey as any,
271
289
  });
272
290
 
273
291
  return subscription;
@@ -1,101 +1,170 @@
1
1
  <template lang="pug">
2
- div.row.justify-center.q-pa-md
3
- div.col-md-8.col-xs-12
4
- div(v-if="is_finish == false")
5
- div(v-if="!signedDocument")
6
- div(v-if="html")
7
- DocumentHtmlReader(:html="html")
8
- q-btn(@click="sign" color="primary") подписать
9
-
10
- div(v-else)
11
- Loader(:text='`Готовим соглашение...`')
12
-
13
- div(v-else)
14
- p.text-h6 Предварительная настройка
15
- p Пожалуйста, укажите домен для установки MONO. Также, укажите суммы вступительных и минимальных паевых взносов для физических лиц, юридических лиц и индивидуальных предпринимателей:
16
-
17
- AddCooperativeForm(:document="signedDocument" @finish="finish").q-pt-md
18
- div(v-if="is_finish == true && coop")
19
-
20
- p.text-h6 Кооператив на подключении
21
- p Статус:
22
- q-badge(v-if="coop.status == 'pending'" color="orange").q-ml-sm ожидание
23
- q-badge(v-if="coop.status == 'active'" color="teal").q-ml-sm активен
24
- q-badge(v-if="coop.status == 'blocked'" color="red").q-ml-sm заблокирован
25
-
26
- q-btn(@click="reload" color="primary" size="sm").q-ml-md
27
- q-icon(name="refresh")
28
- span обновить
29
-
30
- p Пожалуйста, перешлите инструкцию ниже вашему техническому специалисту. После её выполнения, мы автоматически выполним запуск. Далее, Вам необходимо завершить установку уже на Вашем сайте следуя инструкциям, представленным там.
31
-
32
- q-card(flat bordered).q-pa-sm
33
- p.text-bold Инструкция
34
- div.flex.justify-between
35
- span {{instruction}}
36
- q-btn(size="sm" icon="fas fa-copy" flat @click="copy")
2
+ div.row.q-pa-md
3
+ div.col-md-12.col-xs-12
4
+ div(v-if="system.info.is_providered")
5
+ ConnectionAgreementStepper(
6
+ :initial-step="currentStep"
7
+ :is-finish="is_finish"
8
+ :signed-document="signedDocument"
9
+ :coop="coop"
10
+ :html="html"
11
+ :domain-valid="domainValid"
12
+ :installation-progress="installationProgress"
13
+ :instance-status="instanceStatus"
14
+ :subscriptions-loading="subscriptionsLoading"
15
+ :subscriptions-error="subscriptionsError"
16
+ @step-change="handleStepChange"
17
+ @tariff-selected="handleTariffSelected"
18
+ @tariff-deselected="handleTariffDeselected"
19
+ @continue="handleContinue"
20
+ @sign="sign"
21
+ @finish="finish"
22
+ @reload="reload"
23
+ )
24
+
25
+ div(v-else).row
26
+ //- Заглушка для недоступного провайдера
27
+ div.col-md-12.col-xs-12
28
+ ColorCard(color="blue")
29
+ .text-center.q-pa-md
30
+ q-icon(name="fas fa-info-circle" size="2rem").q-mb-sm
31
+ .text-h6.q-mb-md Информация о подключении
32
+ p Для подключения к платформе Кооперативной Экономики обратитесь в ПК ВОСХОД.
33
+ q-btn(
34
+ color="primary"
35
+ label="Перейти на сайт"
36
+ @click="openProviderWebsite"
37
+ size="md"
38
+ ).q-mt-md
37
39
 
38
40
  </template>
39
41
  <script setup lang="ts">
40
42
  import { DigitalDocument } from 'src/shared/lib/document';
41
43
  import { useSessionStore } from 'src/entities/Session';
42
- import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader';
43
- import { computed, ref } from 'vue';
44
- import { Loader } from 'src/shared/ui/Loader';
45
- import { AddCooperativeForm } from 'src/features/Union/AddCooperative';
44
+ import { useSystemStore } from 'src/entities/System/model';
45
+ import { computed, ref, onMounted, onUnmounted } from 'vue';
46
46
  import { useLoadCooperatives } from 'src/features/Union/LoadCooperatives';
47
- import { copyToClipboard } from 'quasar';
48
- import { SuccessAlert } from 'src/shared/api';
47
+ import { useProviderSubscriptions } from 'src/features/Provider';
49
48
  import { Cooperative } from 'cooptypes';
49
+ import { ConnectionAgreementStepper } from 'src/widgets/ConnectionAgreementStepper';
50
+ import { ColorCard } from 'src/shared/ui';
51
+
50
52
 
51
53
  const session = useSessionStore()
54
+ const system = useSystemStore()
52
55
  const document = ref(new DigitalDocument())
53
56
  const {loadOneCooperative} = useLoadCooperatives()
57
+ const {
58
+ domainValid,
59
+ installationProgress,
60
+ instanceStatus,
61
+ isLoading: subscriptionsLoading,
62
+ error: subscriptionsError,
63
+ startAutoRefresh
64
+ } = useProviderSubscriptions()
54
65
 
55
66
  const coop = ref()
56
- const instruction = computed(() => `Создайте A-запись домена ${coop.value?.announce} на IP-адрес: 51.250.114.13`)
57
67
 
58
68
  const html = computed(() => document.value?.data?.html)
59
69
  const signedDocument = computed(() => document.value?.signedDocument)
60
70
  const is_finish = ref(false)
61
71
 
62
- const copy = () => {
63
- copyToClipboard(instruction.value)
64
- .then(() => {
65
- SuccessAlert('Инструкция скопирована в буфер')
66
- })
67
- .catch((e) => {
68
- console.log(e)
72
+ // Управление шагом степпера
73
+ const currentStep = ref(1)
74
+
75
+ const handleStepChange = (step: number) => {
76
+ currentStep.value = step
77
+ }
78
+
79
+ const handleTariffSelected = (tariff: any) => {
80
+ // Здесь можно сохранить выбранный тариф
81
+ console.log('Selected tariff:', tariff)
82
+ }
83
+
84
+ const handleTariffDeselected = () => {
85
+ // Здесь можно обработать снятие выбора тарифа
86
+ console.log('Tariff deselected')
87
+ }
88
+
89
+ // Остановка автообновления при размонтировании компонента
90
+ let stopRefresh: (() => void) | null = null
91
+
92
+ const handleContinue = async () => {
93
+ // Если документ еще не сгенерирован, генерируем его
94
+ if (!document.value.data?.html && !coop.value) {
95
+ await document.value.generate({
96
+ registry_id: Cooperative.Registry.CoopenomicsAgreement.registry_id,
97
+ coopname: 'voskhod',
98
+ username: session.username,
69
99
  })
100
+ }
101
+ }
102
+
103
+ const openProviderWebsite = () => {
104
+ window.open('https://цифровой-кооператив.рф', '_blank')
70
105
  }
71
106
 
72
107
 
73
108
  const finish = () => {
109
+ // Эта функция имеет смысл только если провайдер доступен
110
+ if (!system.info.is_providered) return
111
+
74
112
  is_finish.value = true
75
113
  reload()
114
+
115
+ // Запускаем автообновление подписок каждую минуту
116
+ if (!stopRefresh) {
117
+ stopRefresh = startAutoRefresh(60000) // 1 минута
118
+ }
76
119
  }
77
120
 
78
121
  //todo loadCooperative by username and check status
79
122
  const reload = async() => {
80
- coop.value = await loadOneCooperative(session.username)
123
+ // Загружаем кооператив только если провайдер доступен
124
+ if (system.info.is_providered) {
125
+ coop.value = await loadOneCooperative(session.username)
126
+ }
81
127
  }
82
128
 
83
129
  const init = async () => {
130
+ // Инициализация имеет смысл только если провайдер доступен
131
+ if (!system.info.is_providered) return
132
+
84
133
  coop.value = await loadOneCooperative(session.username)
85
134
 
86
- if (!coop.value)
135
+ if (!coop.value) {
87
136
  await document.value.generate({
88
137
  registry_id: Cooperative.Registry.CoopenomicsAgreement.registry_id,
89
138
  coopname: 'voskhod',
90
139
  username: session.username,
91
140
  })
92
- else is_finish.value = true
141
+ } else {
142
+ is_finish.value = true
143
+ currentStep.value = 4 // Переходим на последний шаг если кооператив уже создан
144
+ }
93
145
  }
94
146
 
95
147
  const sign = async() => {
96
148
  await document.value.sign(session.username)
97
149
  }
98
150
 
151
+ // Lifecycle хуки
152
+ onMounted(() => {
153
+ // Если провайдер доступен - делаем полную инициализацию
154
+ if (system.info.is_providered) {
155
+ init()
156
+ }
157
+ // Если провайдер недоступен - ничего не делаем, показываем заглушку
158
+ })
159
+
160
+ onUnmounted(() => {
161
+ // Останавливаем автообновление при размонтировании компонента
162
+ if (stopRefresh) {
163
+ stopRefresh()
164
+ stopRefresh = null
165
+ }
166
+ })
167
+
99
168
  init()
100
169
 
101
170
 
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ import { computed, withDefaults } from 'vue'
3
+ import type { IStepProps } from '../model/types'
4
+ import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader'
5
+ import { Loader } from 'src/shared/ui/Loader'
6
+
7
+ const props = withDefaults(defineProps<IStepProps & {
8
+ html?: string
9
+ onSign?: () => void
10
+ onBack?: () => void
11
+ }>(), {})
12
+
13
+ const emits = defineEmits<{
14
+ back: []
15
+ sign: []
16
+ }>()
17
+
18
+ const isActive = computed(() => props.isActive)
19
+ const isDone = computed(() => props.isDone)
20
+
21
+ const handleSign = () => {
22
+ emits('sign')
23
+ }
24
+
25
+ const handleBack = () => {
26
+ emits('back')
27
+ }
28
+ </script>
29
+
30
+ <template lang="pug">
31
+ q-step(
32
+ :name="2"
33
+ title="Соглашение о подключении"
34
+ icon="description"
35
+ :done="isDone"
36
+ )
37
+ .q-pa-md
38
+ template(v-if="html")
39
+ DocumentHtmlReader(:html="html")
40
+ template(v-else)
41
+ Loader(:text='`Готовим соглашение...`')
42
+
43
+ q-stepper-navigation.q-gutter-sm(v-if="html")
44
+ q-btn(
45
+ v-if="isActive"
46
+ color="grey-6"
47
+ flat
48
+ label="Назад"
49
+ @click="handleBack"
50
+ )
51
+ q-btn(
52
+ v-if="isActive"
53
+ color="primary"
54
+ label="Подписать"
55
+ @click="handleSign"
56
+ )
57
+ </template>
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { withDefaults } from 'vue'
3
+ import type { IStepProps } from '../model/types'
4
+ import { AddCooperativeForm } from 'src/features/Union/AddCooperative'
5
+
6
+ withDefaults(defineProps<IStepProps & {
7
+ signedDocument?: any
8
+ onFinish?: () => void
9
+ onBack?: () => void
10
+ }>(), {})
11
+
12
+ const emits = defineEmits<{
13
+ back: []
14
+ finish: []
15
+ }>()
16
+
17
+ const handleFinish = () => {
18
+ emits('finish')
19
+ }
20
+
21
+ const handleBack = () => {
22
+ emits('back')
23
+ }
24
+ </script>
25
+
26
+ <template lang="pug">
27
+ q-step(
28
+ :name="3"
29
+ title="Настройка кооператива"
30
+ icon="settings"
31
+ :done="isDone"
32
+ )
33
+ .q-pa-md
34
+ p.text-h6.q-mb-md Предварительная настройка
35
+ p.q-mb-md
36
+ | Пожалуйста, укажите домен для установки Цифрового Кооператива. Также, укажите суммы вступительных и минимальных паевых взносов для физических лиц, юридических лиц и индивидуальных предпринимателей:
37
+
38
+ AddCooperativeForm(
39
+ v-if="signedDocument"
40
+ :document="signedDocument"
41
+ @finish="handleFinish"
42
+ )
43
+
44
+ q-stepper-navigation.q-gutter-sm(v-if="signedDocument")
45
+ q-btn(
46
+ color="grey-6"
47
+ flat
48
+ label="Назад"
49
+ @click="handleBack"
50
+ )
51
+ </template>
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ import { computed, withDefaults, ref } from 'vue'
3
+ import type { IStepProps } from '../model/types'
4
+ import { TariffSelector, type ITariff } from '../Tariffs'
5
+
6
+ const props = withDefaults(defineProps<IStepProps & {
7
+ onContinue?: () => void
8
+ }>(), {})
9
+
10
+ const emits = defineEmits<{
11
+ tariffSelected: [tariff: ITariff]
12
+ tariffDeselected: []
13
+ }>()
14
+
15
+ const isActive = computed(() => props.isActive)
16
+ const isDone = computed(() => props.isDone)
17
+
18
+ const selectedTariff = ref<ITariff | null>(null)
19
+
20
+ const canContinue = computed(() => selectedTariff.value !== null)
21
+
22
+ const handleTariffSelected = (tariff: ITariff) => {
23
+ selectedTariff.value = tariff
24
+ emits('tariffSelected', tariff)
25
+ }
26
+
27
+ const handleTariffDeselected = () => {
28
+ selectedTariff.value = null
29
+ emits('tariffDeselected')
30
+ }
31
+
32
+ const handleContinue = () => {
33
+ if (canContinue.value && props.onContinue) {
34
+ props.onContinue()
35
+ }
36
+ }
37
+ </script>
38
+
39
+ <template lang="pug">
40
+ q-step(
41
+ :name="1"
42
+ title="Выберите тариф"
43
+ icon="info"
44
+ :done="isDone"
45
+ )
46
+ .q-pa-md
47
+
48
+ TariffSelector(
49
+ :disabled="!isActive"
50
+ @tariff-selected="handleTariffSelected"
51
+ @tariff-deselected="handleTariffDeselected"
52
+ )
53
+
54
+ q-stepper-navigation.q-gutter-sm
55
+ q-btn(
56
+ v-if="isActive"
57
+ color="primary"
58
+ :disable="!canContinue"
59
+ label="Продолжить"
60
+ @click="handleContinue"
61
+ )
62
+ </template>