@coopenomics/desktop 2.2.2 → 2.2.4

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/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.2.4](https://github.com/coopenomics/monocoop/compare/v2.2.0...v2.2.4) (2025-01-17)
7
+
8
+ **Note:** Version bump only for package @coopenomics/desktop
9
+
10
+
11
+
12
+
13
+
14
+ ## [2.2.3](https://github.com/coopenomics/monocoop/compare/v2.2.0...v2.2.3) (2025-01-16)
15
+
16
+ **Note:** Version bump only for package @coopenomics/desktop
17
+
18
+
19
+
20
+
21
+
6
22
  ## [2.2.1](https://github.com/coopenomics/monocoop/compare/v2.2.0...v2.2.1) (2025-01-14)
7
23
 
8
24
  **Note:** Version bump only for package @coopenomics/desktop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coopenomics/desktop",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "description": "A Desktop Project",
5
5
  "productName": "Desktop App",
6
6
  "author": "Alex Ant <dacom.dark.sun@gmail.com>",
@@ -20,8 +20,8 @@
20
20
  "prepublishOnly": "npm run build:lib"
21
21
  },
22
22
  "dependencies": {
23
- "@coopenomics/controller": "2.2.2",
24
- "@coopenomics/sdk": "2.2.2",
23
+ "@coopenomics/controller": "2.2.4",
24
+ "@coopenomics/sdk": "2.2.4",
25
25
  "@dicebear/collection": "^9.0.1",
26
26
  "@dicebear/core": "^9.0.1",
27
27
  "@fortawesome/fontawesome-svg-core": "^6.5.2",
@@ -45,7 +45,7 @@
45
45
  "@wharfkit/wallet-plugin-privatekey": "^1.1.0",
46
46
  "axios": "^1.2.1",
47
47
  "compression": "^1.7.4",
48
- "cooptypes": "^2.2.2",
48
+ "cooptypes": "2.2.4",
49
49
  "dompurify": "^3.1.7",
50
50
  "dotenv": "^16.4.5",
51
51
  "email-regex": "^5.0.0",
@@ -94,5 +94,5 @@
94
94
  "npm": ">= 6.13.4",
95
95
  "yarn": ">= 1.21.1"
96
96
  },
97
- "gitHead": "88352202b02fd190abd2c892290cb668ba137cb8"
97
+ "gitHead": "cbfe37dbba00574ec4f1ca354ecf5a4438d72bc1"
98
98
  }
@@ -24,6 +24,17 @@ export const manifest = {
24
24
  path: '/:coopname/user',
25
25
  name: 'home',
26
26
  children: [
27
+ {
28
+ meta: {
29
+ title: 'Подключение',
30
+ icon: 'fas fa-link',
31
+ roles: ['user'],
32
+ conditions: 'isCoop === true && coopname === "voskhod"',
33
+ },
34
+ path: '/:coopname/connect',
35
+ name: 'connect',
36
+ component: markRaw(ConnectionPage),
37
+ },
27
38
  {
28
39
  meta: {
29
40
  title: 'Карта пайщика',
@@ -141,17 +152,7 @@ export const manifest = {
141
152
  component: markRaw(UnionPageListOfCooperatives),
142
153
 
143
154
  },
144
- {
145
- // meta: {
146
- // title: 'Подключения',
147
- // icon: 'fas fa-link',
148
- // roles: ['chairman', 'member'],
149
- // },
150
- path: '/:coopname/connect',
151
- name: 'connect',
152
- component: markRaw(ConnectionPage),
153
155
 
154
- },
155
156
  {
156
157
  path: '/:coopname/contacts',
157
158
  name: 'contacts',
@@ -16,6 +16,7 @@ interface RouteMeta {
16
16
  title: string;
17
17
  icon: string;
18
18
  roles: string[];
19
+ conditions: string;
19
20
  }
20
21
 
21
22
  export interface IRoute {
package/src/env.d.ts CHANGED
@@ -21,6 +21,7 @@ declare module 'vue-router' {
21
21
  // Расширяем интерфейс RouteMeta, добавляя новые свойства
22
22
  interface RouteMeta {
23
23
  roles?: string[];
24
+ conditions?: string;
24
25
  agreements?: string[]
25
26
  title: string
26
27
  icon: string
@@ -5,6 +5,7 @@ import {client} from 'src/shared/api/client'
5
5
  import { useCurrentUserStore } from 'src/entities/User'
6
6
  import { useSystemStore } from 'src/entities/System/model'
7
7
  import { useRegistratorStore } from 'src/entities/Registrator'
8
+ import type { ITokens } from 'src/shared/lib/types/user'
8
9
 
9
10
  export function useLoginUser() {
10
11
  const globalStore = useGlobalStore()
@@ -12,10 +13,23 @@ export function useLoginUser() {
12
13
  const system = useSystemStore()
13
14
 
14
15
  async function login(email: string, wif: string): Promise<void> {
15
- const auth = await api.loginUser(email, wif)
16
+ const auth = await api.loginUser(email, wif);
17
+ const { tokens, account } = await client.login(email, wif);
16
18
 
17
- await globalStore.setWif(auth.user.username, wif)
18
- await globalStore.setTokens(auth.tokens)
19
+ // Создаём объект tokens с правильными типами
20
+ const adaptedTokens: ITokens = {
21
+ access: {
22
+ token: tokens.access.token,
23
+ expires: new Date(tokens.access.expires as string),
24
+ },
25
+ refresh: {
26
+ token: tokens.refresh.token,
27
+ expires: new Date(tokens.refresh.expires as string),
28
+ },
29
+ };
30
+
31
+ await globalStore.setWif(account.username, wif);
32
+ await globalStore.setTokens(adaptedTokens);
19
33
 
20
34
  const session = useSessionStore()
21
35
  await session.init()
@@ -34,6 +34,8 @@ import { ref, computed } from 'vue'
34
34
  import { useCreateUser } from 'src/features/User/CreateUser'
35
35
  import { Notify, copyToClipboard } from 'quasar'
36
36
  import { useRegistratorStore } from 'src/entities/Registrator'
37
+ import { Classes } from '@coopenomics/sdk'
38
+
37
39
  const store = useRegistratorStore()
38
40
 
39
41
  import { FailAlert } from 'src/shared/api'
@@ -43,7 +45,7 @@ const i_save = ref(false)
43
45
  const account = ref(store.state.account)
44
46
 
45
47
  if (!account.value.private_key || !account.value.public_key || !account.value.username)
46
- account.value = api.generateAccount()
48
+ account.value = new Classes.Account()
47
49
 
48
50
  const email = computed(() => store.state.email)
49
51
  const userData = computed(() => store.state.userData)
@@ -1,189 +1,153 @@
1
- <template lang='pug'>
2
- div
3
- q-step(:name='store.steps.SignStatement', title='Подпишите заявление на вступление', :done='store.isStepDone("SignStatement")')
4
- div(v-if='onSign')
5
- Loader(:text='loadingText')
6
- div(v-else)
7
- .bg-grey-2(v-if='!onSign' ref='around', style='border: 0.1px solid grey; min-height: 300px;')
8
- canvas(
9
- ref='canvas',
10
- @touchstart='startDrawing',
11
- @touchmove='draw',
12
- @touchend='endDrawing',
13
- @mousedown='startDrawing',
14
- @mousemove='draw',
15
- @mouseup='endDrawing'
1
+ <template lang="pug">
2
+ div
3
+ q-step(
4
+ :name="store.steps.SignStatement"
5
+ title="Подпишите заявление на вступление"
6
+ :done="store.isStepDone('SignStatement')"
7
+ )
8
+ div(v-if="onSign")
9
+ Loader(:text="loadingText")
10
+
11
+ div(v-else)
12
+ // Контейнер, внутри которого класс Canvas создаёт <canvas>
13
+ .bg-grey-2(
14
+ v-if="!onSign"
15
+ ref="around"
16
+ style="border: 0.1px solid grey; min-height: 300px;"
16
17
  )
17
- p.text-center.full-width Оставьте собственноручную подпись в рамке
18
- .q-mt-lg.q-mb-lg
19
- q-btn.col-md-4.col-xs-12(flat, @click='store.prev()')
20
- i.fa.fa-arrow-left
21
- span.q-ml-md назад
22
- q-btn.col-md-4.col-xs-12(flat, @click='clearCanvas')
23
- span.q-ml-md очистить
24
- q-btn.col-md-4.col-xs-12(color='primary', label='Продолжить', @click='setSignature')
25
-
26
- </template>
27
- <script lang="ts" setup>
28
- import { ref, watch, onBeforeMount, nextTick, onMounted } from 'vue'
29
- import { useCreateUser } from 'src/features/User/CreateUser'
30
- import { Notify } from 'quasar'
31
- import { FailAlert } from 'src/shared/api';
32
- import { Loader } from 'src/shared/ui/Loader';
33
-
34
- import { useRegistratorStore } from 'src/entities/Registrator'
35
- const store = useRegistratorStore()
36
-
37
- const createUser = useCreateUser()
38
-
39
- const around = ref()
40
- const onSign = ref(false)
41
- const loadingText = ref('')
42
-
43
- const canvas = ref<HTMLCanvasElement | null>(null)
44
- const drawing = ref<boolean>(false)
45
- let context: CanvasRenderingContext2D | null = null
46
- let lastX = 0
47
- let lastY = 0
48
-
49
- const windowWidth = ref<number>()
50
- const windowHeight = ref<number>()
51
-
52
- const setSignature = async (): Promise<void> => {
53
- if (!context || !canvas.value) {
54
- Notify.create({
55
- message: 'Пожалуйста, оставьте собственноручную подпись в окне',
56
- color: 'negative',
57
- })
58
- return
18
+ p.text-center.full-width Оставьте собственноручную подпись в рамке
19
+ .q-mt-lg.q-mb-lg
20
+ q-btn.col-md-4.col-xs-12(flat @click="store.prev()")
21
+ i.fa.fa-arrow-left
22
+ span.q-ml-md назад
23
+ q-btn.col-md-4.col-xs-12(flat @click="clearCanvas")
24
+ span.q-ml-md очистить
25
+ q-btn.col-md-4.col-xs-12(color="primary" label="Продолжить" @click="setSignature")
26
+ </template>
27
+
28
+ <script lang="ts" setup>
29
+ import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
30
+ import { Notify } from 'quasar'
31
+ import { FailAlert } from 'src/shared/api'
32
+ import { Loader } from 'src/shared/ui/Loader'
33
+ import { useRegistratorStore } from 'src/entities/Registrator'
34
+ import { useCreateUser } from 'src/features/User/CreateUser'
35
+
36
+ // Импортируем класс
37
+ import { Classes } from '@coopenomics/sdk'
38
+
39
+ const store = useRegistratorStore()
40
+ const createUser = useCreateUser()
41
+
42
+ const around = ref<HTMLElement | null>(null)
43
+ const onSign = ref(false)
44
+ const loadingText = ref('')
45
+
46
+ // Экземпляр Canvas
47
+ let canvasClass: Classes.Canvas | null = null
48
+
49
+ /**
50
+ * Инициализация Canvas с задержкой
51
+ */
52
+ const initCanvas = () => {
53
+
54
+ // Ждём 200ms, чтобы Quasar завершил рендеринг
55
+ setTimeout(() => {
56
+ if (around.value)
57
+ canvasClass = new Classes.Canvas(around.value, {
58
+ lineWidth: 5,
59
+ strokeStyle: '#000',
60
+ })
61
+ }, 200)
59
62
  }
60
63
 
64
+ /**
65
+ * Следим за текущим шагом
66
+ */
67
+ watch(
68
+ () => store.state.step,
69
+ (newStep) => {
70
+ if (newStep === store.steps.SignStatement) {
71
+ nextTick(() => initCanvas())
72
+ }
73
+ }
74
+ )
75
+
76
+ /**
77
+ * Если при загрузке уже SignStatement — инициализируем
78
+ */
79
+ onMounted(() => {
80
+ if (store.state.step === store.steps.SignStatement) {
81
+ nextTick(() => initCanvas())
82
+ }
83
+ })
61
84
 
62
- const imgData = context.getImageData(0, 0, canvas.value.width, canvas.value.height).data
63
- const isEmpty = !imgData.some((channel) => channel !== 0)
64
- const sign = canvas.value.toDataURL('image/png')
85
+ /**
86
+ * При размонтировании снимаем слушатели
87
+ */
88
+ onBeforeUnmount(() => {
89
+ canvasClass?.destroy()
90
+ })
65
91
 
66
- if (!sign || isEmpty) {
67
- FailAlert('Пожалуйста, оставьте собственноручную подпись в окне')
68
- return
92
+ /**
93
+ * Очистка холста
94
+ */
95
+ const clearCanvas = () => {
96
+ canvasClass?.clearCanvas()
69
97
  }
70
98
 
71
- try {
72
- onSign.value = true
73
- store.state.signature = sign
74
-
75
- loadingText.value = 'Подписываем положение о ЦПП "Цифровой Кошелёк"'
76
-
77
- await createUser.signWalletAgreement()
78
-
79
- loadingText.value = 'Подписываем соглашение о политике конфиденциальности'
80
-
81
- await createUser.signPrivacyAgreement()
82
-
83
- loadingText.value = 'Подписываем соглашение о порядке и правилах использования ЭЦП'
84
-
85
- await createUser.signSignatureAgreement()
86
-
87
- loadingText.value = 'Подписываем пользовательское соглашение'
88
-
89
- await createUser.signUserAgreement()
90
-
91
- loadingText.value = 'Подписываем заявление'
92
-
93
- await createUser.signStatement()
94
-
95
- //посылаем
96
- await createUser.sendStatementAndAgreements()
97
- loadingText.value = ''
98
-
99
- onSign.value = false
100
- store.next()
101
- } catch (e: any) {
102
- onSign.value = false
103
- FailAlert(e.message)
104
- }
99
+ /**
100
+ * Получение подписи + проверка пустоты
101
+ */
102
+ const setSignature = async () => {
103
+ if (!canvasClass) {
104
+ Notify.create({
105
+ message: 'Пожалуйста, оставьте собственноручную подпись в окне',
106
+ color: 'negative',
107
+ })
108
+ return
109
+ }
105
110
 
111
+ const sign = canvasClass.getSignature()
112
+ // Проверим, есть ли хоть один ненулевой пиксель
113
+ const ctx = canvasClass.ctx
114
+ const { width, height } = canvasClass.canvas
115
+ const data = ctx.getImageData(0, 0, width, height).data
116
+ const isEmpty = !data.some((channel) => channel !== 0)
106
117
 
107
- }
118
+ if (!sign || isEmpty) {
119
+ FailAlert('Пожалуйста, оставьте собственноручную подпись в окне')
120
+ return
121
+ }
108
122
 
109
- const init = (): void => {
110
- prepareCanvas()
111
- }
123
+ try {
124
+ onSign.value = true
125
+ store.state.signature = sign
112
126
 
113
- watch(() => store.state.step, (newStep) => {
114
- if (newStep == store.steps.SignStatement) {
115
- init()
116
- }
117
- })
127
+ loadingText.value = 'Подписываем положение о ЦПП "Цифровой Кошелёк"'
128
+ await createUser.signWalletAgreement()
118
129
 
119
- window.addEventListener('resize', () => {
120
- prepareCanvas()
121
- })
130
+ loadingText.value = 'Подписываем соглашение о политике конфиденциальности'
131
+ await createUser.signPrivacyAgreement()
122
132
 
123
- onBeforeMount(() => {
124
- window.removeEventListener('resize', prepareCanvas)
125
- })
133
+ loadingText.value = 'Подписываем соглашение о порядке и правилах использования ЭЦП'
134
+ await createUser.signSignatureAgreement()
126
135
 
127
- onMounted(() => {
128
- init()
129
- })
136
+ loadingText.value = 'Подписываем пользовательское соглашение'
137
+ await createUser.signUserAgreement()
130
138
 
131
- const prepareCanvas = (): void => {
132
- nextTick(() => {
133
- windowWidth.value = around.value?.offsetWidth || 0
134
- windowHeight.value = around.value?.offsetHeight || 0
139
+ loadingText.value = 'Подписываем заявление'
140
+ await createUser.signStatement()
135
141
 
136
- context = canvas.value?.getContext('2d') ?? null
142
+ // Отправка
143
+ await createUser.sendStatementAndAgreements()
137
144
 
138
- if (context) {
139
- if (canvas.value) {
140
- canvas.value.width = windowWidth.value || 0
141
- canvas.value.height = windowHeight.value || 0
142
- }
143
- context.strokeStyle = 'grey'
144
- context.lineWidth = 5
145
- context.lineJoin = 'round'
146
- context.lineCap = 'round'
147
- context.strokeStyle = '#000'
145
+ loadingText.value = ''
146
+ onSign.value = false
147
+ store.next()
148
+ } catch (err: any) {
149
+ onSign.value = false
150
+ FailAlert(err.message)
148
151
  }
149
- })
150
- }
151
-
152
- const clearCanvas = (): void => {
153
- context?.clearRect(0, 0, canvas.value?.width || 0, canvas.value?.height || 0)
154
- }
155
-
156
- const startDrawing = (e: MouseEvent | TouchEvent): void => {
157
- e.preventDefault()
158
- drawing.value = true
159
- const rect = canvas.value?.getBoundingClientRect()
160
- lastX =
161
- e instanceof MouseEvent
162
- ? e.clientX - (rect?.left || 0)
163
- : e.touches[0].clientX - (rect?.left || 0)
164
- lastY =
165
- e instanceof MouseEvent ? e.clientY - (rect?.top || 0) : e.touches[0].clientY - (rect?.top || 0)
166
- }
167
-
168
- const endDrawing = (): void => {
169
- drawing.value = false
170
- }
171
-
172
- const draw = (e: MouseEvent | TouchEvent): void => {
173
- e.preventDefault()
174
- if (!drawing.value) return
175
- context?.beginPath()
176
- context?.moveTo(lastX, lastY)
177
- const rect = canvas.value?.getBoundingClientRect()
178
- const x =
179
- e instanceof MouseEvent
180
- ? e.clientX - (rect?.left || 0)
181
- : e.touches[0].clientX - (rect?.left || 0)
182
- const y =
183
- e instanceof MouseEvent ? e.clientY - (rect?.top || 0) : e.touches[0].clientY - (rect?.top || 0)
184
- context?.lineTo(x, y)
185
- context?.stroke()
186
- lastX = x
187
- lastY = y
188
- }
189
- </script>
152
+ }
153
+ </script>
@@ -25,7 +25,7 @@ q-list(
25
25
  import { useRoute, useRouter } from 'vue-router';
26
26
  import { useDesktopStore } from 'src/entities/Desktop/model';
27
27
  import { type IRoute } from 'src/entities/Desktop/model/types';
28
- import { COOPNAME } from 'src/shared/config';
28
+ import { COOPNAME } from 'src/shared/config';
29
29
 
30
30
  const desktop = useDesktopStore()
31
31
  const routes = ref<IRoute[]>([])
@@ -33,11 +33,41 @@ import { COOPNAME } from 'src/shared/config';
33
33
  const router = useRouter()
34
34
  const user = useCurrentUserStore()
35
35
 
36
+ const evaluateCondition = (condition: string, context: Record<string, any>): boolean => {
37
+ try {
38
+ const func = new Function(...Object.keys(context), `return ${condition};`);
39
+ return func(...Object.values(context));
40
+ } catch (error) {
41
+ console.error('Error evaluating condition:', error);
42
+ return false;
43
+ }
44
+ };
45
+
36
46
  const init = () => {
47
+ const isCoop = user.userAccount?.type === 'organization' &&
48
+ user.userAccount?.private_data &&
49
+ 'type' in user.userAccount.private_data &&
50
+ user.userAccount.private_data.type === 'coop';
51
+
37
52
  const userRole = user.userAccount?.role || 'user';
53
+
54
+ const context = {
55
+ isCoop,
56
+ userRole,
57
+ userAccount: user.userAccount,
58
+ coopname: COOPNAME,
59
+ // любые другие свойства, которые нужно использовать в условиях
60
+ };
61
+
38
62
  routes.value = (desktop.getSecondLevel(route) as unknown as IRoute[]).filter(
39
- (route) => route.meta?.roles?.includes(userRole) || route.meta?.roles?.length === 0
40
- );
63
+ (route) => {
64
+ const rolesMatch = route.meta?.roles?.includes(userRole) || route.meta?.roles?.length === 0;
65
+ const conditionMatch = route.meta?.conditions
66
+ ? evaluateCondition(route.meta.conditions, context)
67
+ : true; // Если условия нет, пропускаем проверку
68
+ return rolesMatch && conditionMatch;
69
+ }
70
+ );
41
71
  }
42
72
 
43
73
  // Функция проверки активного маршрута
@@ -9,7 +9,7 @@ q-btn-dropdown(flat :size="isMobile ? 'sm' : 'md'" :dense="isMobile" stretch ico
9
9
  q-icon(name="fa-solid fa-wrench").q-mr-sm
10
10
  span.font10px НАСТРОЙКИ ПАЙЩИКА
11
11
 
12
- q-item(v-if="loggedIn && isChairman" flat clickable v-close-popup @click="open('members')")
12
+ q-item(v-if="loggedIn && (isChairman || isMember)" flat clickable v-close-popup @click="open('members')")
13
13
  q-item-section
14
14
  q-item-label
15
15
  q-icon(name="fa-solid fa-hammer").q-mr-sm