From 56fdaa1361cb8db99c616d92d2cacc4e02a52638 Mon Sep 17 00:00:00 2001 From: Sergey Zhuravlev Date: Wed, 12 Feb 2025 14:25:06 +0100 Subject: [PATCH 1/6] feat: save --- .eslintrc.cjs | 1 + eslint/rules/no-relative-import-from-root.cjs | 4 +- .../aggregates/basket-operations/model.ts | 50 +++++++++++++++++-- .../app-shell/components/Navigation.tsx | 12 ++--- src/renderer/features/app-shell/index.ts | 3 +- .../features/basket-navigation/index.tsx | 44 +++++++++------- .../components/BasketOperations.tsx | 31 ++++++------ .../basket-operations/model/select.ts | 44 ---------------- .../notifications-navigation/index.ts | 18 ------- .../notifications-navigation/index.tsx | 18 +++++++ .../features/settings-navigation/index.ts | 18 ------- .../features/settings-navigation/index.tsx | 18 +++++++ 12 files changed, 132 insertions(+), 129 deletions(-) delete mode 100644 src/renderer/features/basket-operations/model/select.ts delete mode 100644 src/renderer/features/notifications-navigation/index.ts create mode 100644 src/renderer/features/notifications-navigation/index.tsx delete mode 100644 src/renderer/features/settings-navigation/index.ts create mode 100644 src/renderer/features/settings-navigation/index.tsx diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1bb0d19b5c..296c09227c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -408,6 +408,7 @@ module.exports = { // TODO error '@typescript-eslint/consistent-type-assertions': ['off', { assertionStyle: 'never' }], + '@typescript-eslint/no-unused-expressions': 'off', '@typescript-eslint/no-explicit-any': 'warn', 'no-restricted-syntax': [ diff --git a/eslint/rules/no-relative-import-from-root.cjs b/eslint/rules/no-relative-import-from-root.cjs index b60db76b4e..70914572a7 100644 --- a/eslint/rules/no-relative-import-from-root.cjs +++ b/eslint/rules/no-relative-import-from-root.cjs @@ -52,7 +52,7 @@ module.exports = { if (possibleRoot === absoluteRoot) { return context.report({ node, - message: `Relative import through root is forbidden.`, + message: `Relative imports through root are forbidden.`, }); } @@ -68,7 +68,7 @@ module.exports = { if (sourcePackage !== requestedPackage) { return context.report({ node, - message: `Relative to another package is forbidden.\n${sourcePackage}\n${requestedPackage}`, + message: `Relative imports to another package are forbidden.\n${sourcePackage}\n${requestedPackage}`, }); } }, diff --git a/src/renderer/aggregates/basket-operations/model.ts b/src/renderer/aggregates/basket-operations/model.ts index 7b6266285d..3fc2b291b2 100644 --- a/src/renderer/aggregates/basket-operations/model.ts +++ b/src/renderer/aggregates/basket-operations/model.ts @@ -1,10 +1,13 @@ -import { createEffect, createStore, sample } from 'effector'; +import { combine, createEffect, createEvent, createStore, sample } from 'effector'; +import { uniq } from 'lodash'; import { readonly } from 'patronum'; import { storageService } from '@/shared/api/storage'; -import { type BasketTransaction } from '@/shared/core'; +import { type BasketTransaction, type ID } from '@/shared/core'; -const $basketTransactions = createStore([]); +// list + +const $list = createStore([]); const populateFx = createEffect(() => storageService.basketTransactions.readAll()); @@ -24,7 +27,7 @@ const removeTransactionsFx = createEffect((transactions: BasketTransaction[]): P sample({ clock: populateFx.doneData, - target: $basketTransactions, + target: $list, }); sample({ @@ -42,11 +45,48 @@ sample({ target: populateFx, }); +// select + +const $selectedIds = createStore([]); +const $selected = combine($list, $selectedIds, (list, ids) => { + return list.filter(record => ids.includes(record.id)); +}); + +const select = createEvent(); +const toggle = createEvent(); +const deselect = createEvent(); + +sample({ + clock: select, + source: $selectedIds, + fn: (selected, toAdd) => uniq(selected.concat(toAdd)), + target: $selectedIds, +}); + +sample({ + clock: toggle, + source: $selectedIds, + fn: (selected, id) => (selected.includes(id) ? selected.filter(x => x !== id) : uniq(selected.concat(id))), + target: $selectedIds, +}); + +sample({ + clock: deselect, + source: $selectedIds, + fn: (selected, toRemove) => selected.filter(s => !toRemove.includes(s)), + target: $selectedIds, +}); + export const basketOperations = { - $list: readonly($basketTransactions), + $list: readonly($list), + $selected: readonly($selected), populate: populateFx, addTransactions: addTransactionsFx, updateTransactions: updateTransactionsFx, removeTransactions: removeTransactionsFx, + + select, + toggle, + deselect, }; diff --git a/src/renderer/features/app-shell/components/Navigation.tsx b/src/renderer/features/app-shell/components/Navigation.tsx index a39609b0bc..f610180824 100644 --- a/src/renderer/features/app-shell/components/Navigation.tsx +++ b/src/renderer/features/app-shell/components/Navigation.tsx @@ -1,6 +1,6 @@ import { memo } from 'react'; -import { createPipeline, usePipeline } from '@/shared/di'; +import { createPipeline, createSlot, usePipeline, useSlot } from '@/shared/di'; import { NavItem, type Props as NavItemProps } from './NavItem'; @@ -9,11 +9,11 @@ export const navigationTopLinksPipeline = createPipeline({ return items.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); }, }); -export const navigationBottomLinksPipeline = createPipeline(); +export const navigationBottomLinksSlot = createSlot(); export const Navigation = memo(() => { const upperItems = usePipeline(navigationTopLinksPipeline, []); - const lowerItems = usePipeline(navigationBottomLinksPipeline, []); + const lowerItems = useSlot(navigationBottomLinksSlot); return ( ); diff --git a/src/renderer/features/app-shell/index.ts b/src/renderer/features/app-shell/index.ts index 0f397c49f9..d016c25823 100644 --- a/src/renderer/features/app-shell/index.ts +++ b/src/renderer/features/app-shell/index.ts @@ -1,3 +1,4 @@ +export { NavItem } from './components/NavItem'; export { AppShell } from './components/AppShell'; -export { navigationTopLinksPipeline, navigationBottomLinksPipeline } from './components/Navigation'; +export { navigationTopLinksPipeline, navigationBottomLinksSlot } from './components/Navigation'; export { navigationHeaderSlot } from './components/AppShell'; diff --git a/src/renderer/features/basket-navigation/index.tsx b/src/renderer/features/basket-navigation/index.tsx index acf9f2a1c2..1518b2daa6 100644 --- a/src/renderer/features/basket-navigation/index.tsx +++ b/src/renderer/features/basket-navigation/index.tsx @@ -2,11 +2,12 @@ import { useUnit } from 'effector-react'; import { $features } from '@/shared/config/features'; import { createFeature } from '@/shared/feature'; +import { useI18n } from '@/shared/i18n'; import { Paths } from '@/shared/routes'; import { BodyText } from '@/shared/ui'; -import { walletModel } from '@/entities/wallet'; import { basketOperations } from '@/aggregates/basket-operations'; -import { navigationBottomLinksPipeline } from '@/features/app-shell'; +import { walletSelect } from '@/aggregates/wallet-select'; +import { NavItem, navigationBottomLinksSlot } from '@/features/app-shell'; import { basketUtils } from '@/features/operations/OperationsConfirm/lib/basket-utils'; export const basketNavigationFeature = createFeature({ @@ -14,23 +15,28 @@ export const basketNavigationFeature = createFeature({ enable: $features.map(({ basket }) => basket), }); -basketNavigationFeature.inject(navigationBottomLinksPipeline, (items) => { - const wallet = useUnit(walletModel.$activeWallet); - const basket = useUnit(basketOperations.$list); +basketNavigationFeature.inject(navigationBottomLinksSlot, { + order: 0, + render() { + const { t } = useI18n(); + const wallet = useUnit(walletSelect.$selectedWallet); + const basket = useUnit(basketOperations.$list); - if (!wallet || !basketUtils.isBasketAvailable(wallet)) { - return items; - } + if (!wallet || !basketUtils.isBasketAvailable(wallet)) { + return null; + } - return items.concat({ - order: 0, - icon: 'operations', - title: 'navigation.basketLabel', - link: Paths.BASKET, - badge: ( - - {basket.filter((tx) => tx.initiatorWallet === wallet?.id).length || ''} - - ), - }); + return ( + + {basket.filter((tx) => tx.initiatorWallet === wallet?.id).length || ''} + + } + > + ); + }, }); diff --git a/src/renderer/features/basket-operations/components/BasketOperations.tsx b/src/renderer/features/basket-operations/components/BasketOperations.tsx index 130010a221..b6174a4c56 100644 --- a/src/renderer/features/basket-operations/components/BasketOperations.tsx +++ b/src/renderer/features/basket-operations/components/BasketOperations.tsx @@ -1,11 +1,11 @@ -import { useGate, useUnit } from 'effector-react'; +import { useUnit } from 'effector-react'; import { type BasketTransaction } from '@/shared/core'; import { Slot, createSlot } from '@/shared/di'; import { useI18n } from '@/shared/i18n'; import { Button, FootnoteText } from '@/shared/ui'; import { Checkbox } from '@/shared/ui-kit'; -import { selectOperations } from '../model/select'; +import { basketOperations } from '@/aggregates/basket-operations'; import { signOperations } from '../model/sign'; import { EmptyBasket } from './EmptyBasket'; @@ -21,11 +21,10 @@ export const operationTitleSlot = createSlot<{ operation: BasketTransaction }>() export const BasketOperations = ({ operations }: Props) => { const { t } = useI18n(); - useGate(selectOperations.flow); - const selectedTxs = useUnit(selectOperations.$selectedTxs); + const selected = useUnit(basketOperations.$selected); - const isSignAvailable = selectedTxs.length > 0; + const isSignAvailable = selected.length > 0; return ( <> @@ -33,12 +32,16 @@ export const BasketOperations = ({ operations }: Props) => {
0 && operations.length === selectedTxs.length} - semiChecked={selectedTxs.length > 0 && operations.length !== selectedTxs.length} - onChange={() => selectOperations.selectTxs(operations)} + checked={operations.length > 0 && operations.length === selected.length} + semiChecked={selected.length > 0 && operations.length !== selected.length} + onChange={(value) => { + value + ? basketOperations.select(operations.map((x) => x.id)) + : basketOperations.deselect(operations.map((x) => x.id)); + }} > - {t('basket.selectedStatus', { count: operations.length, selected: selectedTxs.length })} + {t('basket.selectedStatus', { count: operations.length, selected: selected.length })}
@@ -47,9 +50,9 @@ export const BasketOperations = ({ operations }: Props) => { size="sm" className="w-[125px]" disabled={!isSignAvailable} - onClick={() => signOperations.events.flowStarted({ transactions: selectedTxs, feeMap: {} })} + onClick={() => signOperations.events.flowStarted({ transactions: selected, feeMap: {} })} > - {t(selectedTxs.length === 0 ? 'basket.emptySignButton' : 'basket.signButton')} + {t(selected.length === 0 ? 'basket.emptySignButton' : 'basket.signButton')}
@@ -62,12 +65,12 @@ export const BasketOperations = ({ operations }: Props) => {
  • { e.preventDefault(); e.stopPropagation(); - selectOperations.selectTx(operation); + basketOperations.toggle(operation.id); }} />
    @@ -88,7 +91,7 @@ export const BasketOperations = ({ operations }: Props) => { {operations.length === 0 && } - {selectedTxs.length > 1 ? : } + {selected.length > 1 ? : } ); }; diff --git a/src/renderer/features/basket-operations/model/select.ts b/src/renderer/features/basket-operations/model/select.ts deleted file mode 100644 index 03d51227a1..0000000000 --- a/src/renderer/features/basket-operations/model/select.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { createEvent, createStore, sample } from 'effector'; -import { createGate } from 'effector-react'; - -import { type BasketTransaction } from '@/shared/core'; -import { removeFromCollection } from '@/shared/lib/utils'; - -const flow = createGate(); - -const selectTx = createEvent(); -const selectTxs = createEvent(); -const filterTxs = createEvent(); - -const $selectedTxs = createStore([]); - -sample({ - clock: selectTx, - source: $selectedTxs, - fn: (selectedTxs, tx) => (selectedTxs.includes(tx) ? removeFromCollection(selectedTxs, tx) : [...selectedTxs, tx]), - target: $selectedTxs, -}); - -sample({ - clock: selectTxs, - source: $selectedTxs, - fn: (selectedTxs, txs) => (selectedTxs.length > 0 ? [] : [...txs]), - target: $selectedTxs, -}); - -sample({ - clock: flow.state, - source: $selectedTxs, - fn: (selectedTxs, txs) => selectedTxs.filter((s) => txs.some((tx) => tx.id === s.id)), - target: $selectedTxs, -}); - -export const selectOperations = { - flow, - - $selectedTxs, - - selectTx, - selectTxs, - filterTxs, -}; diff --git a/src/renderer/features/notifications-navigation/index.ts b/src/renderer/features/notifications-navigation/index.ts deleted file mode 100644 index 5592d4d91c..0000000000 --- a/src/renderer/features/notifications-navigation/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { $features } from '@/shared/config/features'; -import { createFeature } from '@/shared/feature'; -import { Paths } from '@/shared/routes'; -import { navigationBottomLinksPipeline } from '@/features/app-shell'; - -export const notificationsNavigationFeature = createFeature({ - name: 'notifications/navigation', - enable: $features.map(({ notifications }) => notifications), -}); - -notificationsNavigationFeature.inject(navigationBottomLinksPipeline, (items) => { - return items.concat({ - order: 1, - icon: 'notification', - title: 'navigation.notificationsLabel', - link: Paths.NOTIFICATIONS, - }); -}); diff --git a/src/renderer/features/notifications-navigation/index.tsx b/src/renderer/features/notifications-navigation/index.tsx new file mode 100644 index 0000000000..bfe9bc32c6 --- /dev/null +++ b/src/renderer/features/notifications-navigation/index.tsx @@ -0,0 +1,18 @@ +import { $features } from '@/shared/config/features'; +import { createFeature } from '@/shared/feature'; +import { useI18n } from '@/shared/i18n'; +import { Paths } from '@/shared/routes'; +import { NavItem, navigationBottomLinksSlot } from '@/features/app-shell'; + +export const notificationsNavigationFeature = createFeature({ + name: 'notifications/navigation', + enable: $features.map(({ notifications }) => notifications), +}); + +notificationsNavigationFeature.inject(navigationBottomLinksSlot, { + order: 1, + render() { + const { t } = useI18n(); + return ; + }, +}); diff --git a/src/renderer/features/settings-navigation/index.ts b/src/renderer/features/settings-navigation/index.ts deleted file mode 100644 index b1c7eb1421..0000000000 --- a/src/renderer/features/settings-navigation/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { $features } from '@/shared/config/features'; -import { createFeature } from '@/shared/feature'; -import { Paths } from '@/shared/routes'; -import { navigationBottomLinksPipeline } from '@/features/app-shell'; - -export const settingsNavigationFeature = createFeature({ - name: 'settings/navigation', - enable: $features.map(({ settings }) => settings), -}); - -settingsNavigationFeature.inject(navigationBottomLinksPipeline, (items) => { - return items.concat({ - order: 2, - icon: 'settings', - title: 'navigation.settingsLabel', - link: Paths.SETTINGS, - }); -}); diff --git a/src/renderer/features/settings-navigation/index.tsx b/src/renderer/features/settings-navigation/index.tsx new file mode 100644 index 0000000000..69d4afbdc6 --- /dev/null +++ b/src/renderer/features/settings-navigation/index.tsx @@ -0,0 +1,18 @@ +import { $features } from '@/shared/config/features'; +import { createFeature } from '@/shared/feature'; +import { useI18n } from '@/shared/i18n'; +import { Paths } from '@/shared/routes'; +import { NavItem, navigationBottomLinksSlot } from '@/features/app-shell'; + +export const settingsNavigationFeature = createFeature({ + name: 'settings/navigation', + enable: $features.map(({ settings }) => settings), +}); + +settingsNavigationFeature.inject(navigationBottomLinksSlot, { + order: 2, + render() { + const { t } = useI18n(); + return ; + }, +}); From 41fc18ccf9adc5ca37dbea8a5cf68d7f1ebfc4f4 Mon Sep 17 00:00:00 2001 From: Sergey Zhuravlev Date: Wed, 12 Feb 2025 14:28:02 +0100 Subject: [PATCH 2/6] feat: save --- eslint/rules/enforce-di-naming-convention.cjs | 4 +++- eslint/rules/no-relative-import-from-root.cjs | 5 ++--- eslint/rules/no-self-import.cjs | 9 ++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/eslint/rules/enforce-di-naming-convention.cjs b/eslint/rules/enforce-di-naming-convention.cjs index 3d2ebfb3e7..5eeff53f44 100644 --- a/eslint/rules/enforce-di-naming-convention.cjs +++ b/eslint/rules/enforce-di-naming-convention.cjs @@ -15,6 +15,8 @@ const IDENTIFIERS_SUFFIXES = { createAnyOf: 'AnyOf', }; +const DEFAULT_IMPORT_SOURCES = [/@\/shared\/di/]; + const fixName = (name, suffix) => camelCase(name.replace(new RegExp(suffix, 'gi'), '').replace(/\$/g, '') + suffix); /** @@ -57,7 +59,7 @@ module.exports = { ...IDENTIFIERS_SUFFIXES, ...(settings.identifierCreators || {}), }; - const importSources = [/@\/shared\/di/, ...(settings.importSources || [])]; + const importSources = [...DEFAULT_IMPORT_SOURCES, ...(settings.importSources || [])]; return { VariableDeclarator(node) { diff --git a/eslint/rules/no-relative-import-from-root.cjs b/eslint/rules/no-relative-import-from-root.cjs index 70914572a7..01dbad6b22 100644 --- a/eslint/rules/no-relative-import-from-root.cjs +++ b/eslint/rules/no-relative-import-from-root.cjs @@ -35,12 +35,11 @@ module.exports = { return { ImportDeclaration(node) { - const { source } = node; - if (!isLiteral(source)) { + if (!isLiteral(node.source)) { return; } - const requestPath = source.value.toString(); + const requestPath = node.source.value.toString(); // Not relative import to parent if (!requestPath.startsWith('../')) { return; diff --git a/eslint/rules/no-self-import.cjs b/eslint/rules/no-self-import.cjs index a8550a8eb1..edde72ca26 100644 --- a/eslint/rules/no-self-import.cjs +++ b/eslint/rules/no-self-import.cjs @@ -75,12 +75,11 @@ module.exports = { return; } - const { source } = node; - if (!isLiteral(source)) { + if (!isLiteral(node.source)) { return; } - const requestPath = source.value.toString(); + const requestPath = node.source.value.toString(); // Child import if (requestPath.startsWith('./')) { @@ -132,9 +131,9 @@ module.exports = { { desc: `Replace with relative path ${replacedPath}`, fix(fixer) { - const stringQ = source.raw.charAt(0); + const stringQ = node.source.raw.charAt(0); - return fixer.replaceText(source, `${stringQ}${replacedPath}${stringQ}`); + return fixer.replaceText(node.source, `${stringQ}${replacedPath}${stringQ}`); }, }, ], From 196e3c6934f773aeefb8ef835795bfd0c854f208 Mon Sep 17 00:00:00 2001 From: Sergey Zhuravlev Date: Wed, 12 Feb 2025 15:31:18 +0100 Subject: [PATCH 3/6] feat: wip --- .../features/basket-navigation/index.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/renderer/features/basket-navigation/index.tsx b/src/renderer/features/basket-navigation/index.tsx index 1518b2daa6..ba2e64a8dd 100644 --- a/src/renderer/features/basket-navigation/index.tsx +++ b/src/renderer/features/basket-navigation/index.tsx @@ -5,10 +5,10 @@ import { createFeature } from '@/shared/feature'; import { useI18n } from '@/shared/i18n'; import { Paths } from '@/shared/routes'; import { BodyText } from '@/shared/ui'; +import { basketUtils } from '@/entities/basket'; import { basketOperations } from '@/aggregates/basket-operations'; import { walletSelect } from '@/aggregates/wallet-select'; import { NavItem, navigationBottomLinksSlot } from '@/features/app-shell'; -import { basketUtils } from '@/features/operations/OperationsConfirm/lib/basket-utils'; export const basketNavigationFeature = createFeature({ name: 'basket/navigation', @@ -19,23 +19,23 @@ basketNavigationFeature.inject(navigationBottomLinksSlot, { order: 0, render() { const { t } = useI18n(); - const wallet = useUnit(walletSelect.$selectedWallet); + const accounts = useUnit(walletSelect.$selectedAccounts); const basket = useUnit(basketOperations.$list); - if (!wallet || !basketUtils.isBasketAvailable(wallet)) { + const isAvailable = accounts.some(basketUtils.isBasketAvailableForAccount); + + if (!isAvailable) { return null; } + const availableOperations = basket.filter((tx) => accounts.some((a) => a.walletId === tx.initiatorWallet)); + return ( - {basket.filter((tx) => tx.initiatorWallet === wallet?.id).length || ''} - - } + badge={{availableOperations.length || ''}} > ); }, From 0c90eccd94c379cc4049be948064e378146835a9 Mon Sep 17 00:00:00 2001 From: Sergey Zhuravlev Date: Wed, 12 Feb 2025 16:17:48 +0100 Subject: [PATCH 4/6] feat: wip --- .../chain/ui/OperationTitle/OperationTitle.tsx | 2 +- .../components/SignOperation.tsx | 4 +++- .../components/SignOperations.tsx | 6 ++++-- .../features/basket-operations/index.ts | 2 ++ .../fellowship-basket-operation/index.tsx | 15 ++++++++++++--- .../model/confirm.ts | 2 +- .../model/feature.ts | 6 ++++++ .../fellowship-tasks/components/Basket.tsx | 18 ++++++++++++++---- .../fellowship-tasks/components/Tasks.tsx | 6 ++++-- .../FellowshipVoting/ui/Confirmation.tsx | 2 +- .../lib/createTransactionConfirmStore.ts | 4 ++-- src/renderer/shared/effector/createFlow.ts | 2 ++ 12 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/renderer/entities/chain/ui/OperationTitle/OperationTitle.tsx b/src/renderer/entities/chain/ui/OperationTitle/OperationTitle.tsx index 1cdc758eb6..5c1cffc83c 100644 --- a/src/renderer/entities/chain/ui/OperationTitle/OperationTitle.tsx +++ b/src/renderer/entities/chain/ui/OperationTitle/OperationTitle.tsx @@ -10,7 +10,7 @@ type Props = { }; export const OperationTitle = ({ title, chainId, className }: Props) => ( -
    +
    {title} { const isModalOpen = useUnit(signOperations.$isModalOpen); const wallet = useUnit(walletSelect.$selectedWallet); - const operation = transactions[0]; + const operation = transactions.at(0); + + if (!operation) return null; if (signOperationsUtils.isSubmitStep(step)) { return ; diff --git a/src/renderer/features/basket-operations/components/SignOperations.tsx b/src/renderer/features/basket-operations/components/SignOperations.tsx index a534ebcb55..5dfc2d60eb 100644 --- a/src/renderer/features/basket-operations/components/SignOperations.tsx +++ b/src/renderer/features/basket-operations/components/SignOperations.tsx @@ -4,7 +4,7 @@ import { type BasketTransaction, WalletType } from '@/shared/core'; import { Slot, createSlot } from '@/shared/di'; import { useI18n } from '@/shared/i18n'; import { HeaderTitleText } from '@/shared/ui'; -import { Modal } from '@/shared/ui-kit'; +import { Box, Modal } from '@/shared/ui-kit'; import { SignButton } from '@/entities/operations'; import { OperationSign, OperationSubmit } from '@/features/operations'; import { ConfirmSlider } from '@/features/operations/OperationsConfirm'; @@ -41,7 +41,9 @@ export const SignOperations = () => { > {transactions.map((t) => ( - + + + ))} diff --git a/src/renderer/features/basket-operations/index.ts b/src/renderer/features/basket-operations/index.ts index 678b8e2822..4713689ed0 100644 --- a/src/renderer/features/basket-operations/index.ts +++ b/src/renderer/features/basket-operations/index.ts @@ -1,4 +1,6 @@ +export { SignOperations } from './components/SignOperations'; export { confirmDetailsSlot, confirmTitleSlot } from './components/SignOperations'; export { BasketOperations, operationTitleSlot } from './components/BasketOperations'; export { basketOperationsFeature } from './model/feature'; export { validate } from './model/validate'; +export { signOperations } from './model/sign'; diff --git a/src/renderer/features/fellowship-basket-operation/index.tsx b/src/renderer/features/fellowship-basket-operation/index.tsx index 7cb38342e8..4aa348b84f 100644 --- a/src/renderer/features/fellowship-basket-operation/index.tsx +++ b/src/renderer/features/fellowship-basket-operation/index.tsx @@ -2,6 +2,7 @@ import { useGate, useUnit } from 'effector-react'; import { type Transaction, TransactionType } from '@/shared/core'; import { useI18n } from '@/shared/i18n'; +import { nullable } from '@/shared/lib/utils'; import { type IconNames } from '@/shared/ui'; import { OperationTitle } from '@/entities/chain'; import { basketOperationsService } from '@/aggregates/basket-operations'; @@ -75,7 +76,7 @@ fellowshipBasketOperationFeature.inject(operationTitleSlot, ({ operation }) => { if (title && icon) { return ( { fellowshipBasketOperationFeature.inject(confirmTitleSlot, ({ operation }) => { const { t } = useI18n(); + const input = useUnit(fellowshipBasketOperationFeature.input); + + if (nullable(input)) return null; + const transaction = basketOperationsService.getCoreTx(operation); + const chain = input.chains[transaction.chainId]; + if (nullable(chain)) return null; + const asset = chain.assets.at(0); + if (nullable(asset)) return null; const title = getModalTitle(transaction); if (title) { return ( ); diff --git a/src/renderer/features/fellowship-basket-operation/model/confirm.ts b/src/renderer/features/fellowship-basket-operation/model/confirm.ts index b51725c116..e882f1261b 100644 --- a/src/renderer/features/fellowship-basket-operation/model/confirm.ts +++ b/src/renderer/features/fellowship-basket-operation/model/confirm.ts @@ -97,7 +97,7 @@ sample({ sample({ clock: prepareCollectiveVoteDataFx.doneData, fn: data => [data], - target: fellowshipVotingConfirmModel.events.fillConfirm, + target: fellowshipVotingConfirmModel.events.addConfirms, }); export const confirm = { diff --git a/src/renderer/features/fellowship-basket-operation/model/feature.ts b/src/renderer/features/fellowship-basket-operation/model/feature.ts index 257098684e..0f5653089f 100644 --- a/src/renderer/features/fellowship-basket-operation/model/feature.ts +++ b/src/renderer/features/fellowship-basket-operation/model/feature.ts @@ -1,5 +1,11 @@ +import { combine } from 'effector'; + import { createFeature } from '@/shared/feature'; +import { networkModel } from '@/entities/network'; + +const $input = combine({ chains: networkModel.$chains }); export const fellowshipBasketOperationFeature = createFeature({ name: 'fellowship/basket-operations', + input: $input, }); diff --git a/src/renderer/features/fellowship-tasks/components/Basket.tsx b/src/renderer/features/fellowship-tasks/components/Basket.tsx index 17c1154b1e..4141e1c2db 100644 --- a/src/renderer/features/fellowship-tasks/components/Basket.tsx +++ b/src/renderer/features/fellowship-tasks/components/Basket.tsx @@ -5,19 +5,29 @@ import { useI18n } from '@/shared/i18n'; import { nullable } from '@/shared/lib/utils'; import { Button } from '@/shared/ui'; import { basketUtils } from '@/entities/basket'; +import { basketOperations } from '@/aggregates/basket-operations'; +import { SignOperations, signOperations } from '@/features/basket-operations'; import { fellowshipTasksFeature } from '../model/feature'; import { tasks } from '../model/tasks'; export const Basket = memo(() => { const { t } = useI18n(); const input = useUnit(fellowshipTasksFeature.input); - const basketOperations = useUnit(tasks.$basketOperations); + const transactions = useUnit(tasks.$basketOperations); if (nullable(input?.account) || !basketUtils.isBasketAvailableForAccount(input.account)) return null; + const openSigning = () => { + basketOperations.select(transactions.map(x => x.id)); + signOperations.events.flowStarted({ transactions, feeMap: {} }); + }; + return ( - + <> + + + ); }); diff --git a/src/renderer/features/fellowship-tasks/components/Tasks.tsx b/src/renderer/features/fellowship-tasks/components/Tasks.tsx index 414ac47770..d60012d856 100644 --- a/src/renderer/features/fellowship-tasks/components/Tasks.tsx +++ b/src/renderer/features/fellowship-tasks/components/Tasks.tsx @@ -3,7 +3,7 @@ import { memo, useState } from 'react'; import { useI18n } from '@/shared/i18n'; import { nullable } from '@/shared/lib/utils'; -import { FootnoteText, Icon, SmallTitleText } from '@/shared/ui'; +import { FootnoteText, Icon, Loader, SmallTitleText } from '@/shared/ui'; import { Box } from '@/shared/ui-kit'; import { fellowshipTasksFeature } from '../model/feature'; import { tasks } from '../model/tasks'; @@ -19,7 +19,9 @@ export const Tasks = memo(() => { if (nullable(input)) { return ( -
    +
    + +
    ); } diff --git a/src/renderer/features/operations/OperationsConfirm/FellowshipVoting/ui/Confirmation.tsx b/src/renderer/features/operations/OperationsConfirm/FellowshipVoting/ui/Confirmation.tsx index 7aca4820a0..b7d8890d65 100644 --- a/src/renderer/features/operations/OperationsConfirm/FellowshipVoting/ui/Confirmation.tsx +++ b/src/renderer/features/operations/OperationsConfirm/FellowshipVoting/ui/Confirmation.tsx @@ -39,7 +39,7 @@ export const Confirmation = ({ id, secondaryActionButton, hideSignButton, onGoBa } return ( -
    +
    ({ type ConfirmMap = Record>; const fillConfirm = createEvent(); - const addConfirms = createEvent(); + const addConfirms = createEvent(); const replaceWithConfirm = createEvent(); const resetConfirm = createEvent(); @@ -113,7 +113,7 @@ export const createTransactionConfirmStore = ({ sample({ clock: addConfirms, source: $store, - fn: (store, input) => [...store, input], + fn: (store, input) => store.concat(input), target: $store, }); diff --git a/src/renderer/shared/effector/createFlow.ts b/src/renderer/shared/effector/createFlow.ts index e6905d766b..554dfe6c4c 100644 --- a/src/renderer/shared/effector/createFlow.ts +++ b/src/renderer/shared/effector/createFlow.ts @@ -21,6 +21,8 @@ export const createFlow = (defaultState: Props): Flow => { const close = createEvent(); const shut = createEvent(); + // debug($stack, $state); + sample({ clock: open, source: $stack, From 28a631ddc789e3d975e4819a13c77f246b747b37 Mon Sep 17 00:00:00 2001 From: Sergey Zhuravlev Date: Wed, 12 Feb 2025 16:32:40 +0100 Subject: [PATCH 5/6] feat: wip --- .../components/ProfileCard.tsx | 5 ++-- .../features/fellowship-voting/index.tsx | 3 ++- .../fellowship-voting/model/fellowship.ts | 2 +- .../fellowship-voting/model/votingStatus.ts | 8 +++---- .../FellowshipVoting/ui/Confirmation.tsx | 23 +++++++++++-------- src/renderer/shared/effector/createFlow.ts | 2 -- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/renderer/features/fellowship-profile/components/ProfileCard.tsx b/src/renderer/features/fellowship-profile/components/ProfileCard.tsx index a9433fc958..8725ea3e6a 100644 --- a/src/renderer/features/fellowship-profile/components/ProfileCard.tsx +++ b/src/renderer/features/fellowship-profile/components/ProfileCard.tsx @@ -30,11 +30,10 @@ export const ProfileCard = memo(() => { return ( - +
    ); }); diff --git a/src/renderer/features/fellowship-voting/index.tsx b/src/renderer/features/fellowship-voting/index.tsx index a073284ee4..5b3d6f727d 100644 --- a/src/renderer/features/fellowship-voting/index.tsx +++ b/src/renderer/features/fellowship-voting/index.tsx @@ -13,10 +13,11 @@ import { VotingConfirmation } from './components/VotingConfirmation'; import { VotingModal } from './components/VotingModal'; import { WalletVotingInfo } from './components/WalletVotingInfo'; import { fellowshipVotingFeature } from './model/feature'; +import { fellowship } from './model/fellowship'; import { voting } from './model/voting'; import { votingStatus } from './model/votingStatus'; -export { fellowshipVotingFeature, VotingConfirmation, votingStatus }; +export { fellowshipVotingFeature, VotingConfirmation, votingStatus, fellowship }; fellowshipVotingFeature.inject(taskVotingActionSlot, ({ referendumId }) => { useFlow(votingStatus.flow, { referendumId }); diff --git a/src/renderer/features/fellowship-voting/model/fellowship.ts b/src/renderer/features/fellowship-voting/model/fellowship.ts index 2de5bf5d4e..bb10d8d8f5 100644 --- a/src/renderer/features/fellowship-voting/model/fellowship.ts +++ b/src/renderer/features/fellowship-voting/model/fellowship.ts @@ -15,6 +15,6 @@ const $store = combine($fellowshipStore, fellowshipVotingFeature.state, (fellows return fellowshipStore[state.data.chainId] ?? null; }); -export const fellowshipModel = { +export const fellowship = { $store, }; diff --git a/src/renderer/features/fellowship-voting/model/votingStatus.ts b/src/renderer/features/fellowship-voting/model/votingStatus.ts index b7e227a986..b031f8c42a 100644 --- a/src/renderer/features/fellowship-voting/model/votingStatus.ts +++ b/src/renderer/features/fellowship-voting/model/votingStatus.ts @@ -8,14 +8,14 @@ import { referendum, referendumService, trackService, voting } from '@/domains/c import { accountsService } from '@/domains/network'; import { fellowshipVotingFeature } from './feature'; -import { fellowshipModel } from './fellowship'; +import { fellowship } from './fellowship'; const flow = createFlow<{ referendumId: ReferendumId | null }>({ referendumId: null }); const $referendumId = flow.state.map(({ referendumId }) => referendumId); -const $referendums = fellowshipModel.$store.map(store => store?.referendums ?? []); -const $maxRank = fellowshipModel.$store.map(x => x?.maxRank ?? 0); -const $voting = fellowshipModel.$store.map(x => x?.voting ?? []); +const $referendums = fellowship.$store.map(store => store?.referendums ?? []); +const $maxRank = fellowship.$store.map(x => x?.maxRank ?? 0); +const $voting = fellowship.$store.map(x => x?.voting ?? []); const $currentMember = fellowshipVotingFeature.input.map(input => input?.member ?? null); const $votingAccount = fellowshipVotingFeature.input.map(input => input?.account ?? null); diff --git a/src/renderer/features/operations/OperationsConfirm/FellowshipVoting/ui/Confirmation.tsx b/src/renderer/features/operations/OperationsConfirm/FellowshipVoting/ui/Confirmation.tsx index b7d8890d65..5a3d146024 100644 --- a/src/renderer/features/operations/OperationsConfirm/FellowshipVoting/ui/Confirmation.tsx +++ b/src/renderer/features/operations/OperationsConfirm/FellowshipVoting/ui/Confirmation.tsx @@ -1,14 +1,12 @@ import { useStoreMap, useUnit } from 'effector-react'; import { type ReactNode } from 'react'; -import { useFlow } from '@/shared/effector'; import { useI18n } from '@/shared/i18n'; import { nullable } from '@/shared/lib/utils'; -import { referendaPallet } from '@/shared/pallet/referenda'; import { Button } from '@/shared/ui'; import { referendumService } from '@/domains/collectives'; import { SignButton } from '@/entities/operations'; -import { VotingConfirmation, votingStatus } from '@/features/fellowship-voting'; +import { VotingConfirmation, fellowship, votingStatus } from '@/features/fellowship-voting'; import { confirmModel } from '../model/confirm-model'; type Props = { @@ -27,26 +25,33 @@ export const Confirmation = ({ id, secondaryActionButton, hideSignButton, onGoBa fn: (value, [id]) => (id ? value[id] : null) ?? null, }); - useFlow(votingStatus.flow, { - referendumId: confirm?.meta?.poll ? referendaPallet.helpers.toReferendumId(parseInt(confirm?.meta?.poll)) : null, + // What a mess, we should find a solution for rendering multiple confirms + const votingReferendum = useStoreMap({ + store: fellowship.$store, + keys: [confirm?.meta], + fn: (store, [meta]) => { + if (!meta) return null; + const list = store?.referendums ?? []; + + return list.find((r) => r.id === parseInt(meta.poll)) ?? null; + }, }); - const referendum = useUnit(votingStatus.$referendum); const maxRank = useUnit(votingStatus.$maxRank); - if (nullable(confirm) || nullable(referendum) || referendumService.isCompleted(referendum)) { + if (nullable(confirm) || nullable(votingReferendum) || referendumService.isCompleted(votingReferendum)) { return null; } return ( -
    +
    (defaultState: Props): Flow => { const close = createEvent(); const shut = createEvent(); - // debug($stack, $state); - sample({ clock: open, source: $stack, From 25532e563ba60fdafd651ae7b278827c7f18a501 Mon Sep 17 00:00:00 2001 From: Sergey Zhuravlev Date: Wed, 12 Feb 2025 16:40:36 +0100 Subject: [PATCH 6/6] feat: wip --- src/renderer/features/fellowship-basket-operation/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/features/fellowship-basket-operation/index.tsx b/src/renderer/features/fellowship-basket-operation/index.tsx index 4aa348b84f..147c276297 100644 --- a/src/renderer/features/fellowship-basket-operation/index.tsx +++ b/src/renderer/features/fellowship-basket-operation/index.tsx @@ -76,7 +76,7 @@ fellowshipBasketOperationFeature.inject(operationTitleSlot, ({ operation }) => { if (title && icon) { return ( { return ( );