Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: basket #3211

Merged
merged 9 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/renderer/aggregates/basket-operations/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { readonly } from 'patronum';

import { storageService } from '@/shared/api/storage';
import { type BasketTransaction, type ID } from '@/shared/core';
import { walletSelect } from '@/aggregates/wallet-select';
// eslint-disable-next-line boundaries/element-types
import { ExtrinsicResult, submitModel } from '@/features/operations/OperationSubmit';

Expand Down Expand Up @@ -81,6 +82,12 @@ sample({

// sync

sample({
clock: walletSelect.$selectedAccounts,
source: $selectedIds,
target: deselect,
});

sample({
clock: submitModel.output.formSubmitted,
source: $list,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { type BasketTransaction } from '@/shared/core';
import { Slot, createSlot } from '@/shared/di';
import { useI18n } from '@/shared/i18n';
import { Checkbox } from '@/shared/ui-kit';
import { ChainTitle } from '@/entities/chain';
import { validation } from '../model/validation';

import { BasketOperationStatus } from './BasketOperationStatus';
Expand Down Expand Up @@ -42,7 +41,7 @@ export const BasketItem = ({ transaction, selected, onSelect, onClick }: Props)
return (
<li
key={transaction.id}
className="grid h-[52px] grid-cols-[40px,380px,153px,124px,auto] items-stretch rounded-md bg-block-background-default"
className="grid h-[52px] grid-cols-[40px,533px,124px,auto] items-stretch rounded-md bg-block-background-default"
>
<div className="flex items-center justify-center px-3">
<Checkbox checked={selected} disabled={disabled} onClick={() => onSelect(transaction)} />
Expand All @@ -59,8 +58,6 @@ export const BasketItem = ({ transaction, selected, onSelect, onClick }: Props)
<Slot id={operationTitleSlot} props={{ transaction }} />
</div>

<ChainTitle chainId={transaction.coreTx.chainId} className="ps-2" />

<div className="flex items-center justify-center px-2">
<BasketOperationStatus
validating={pendingValidation}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ import { validation } from '../model/validation';
import { BasketFilter } from './BasketFilter';
import { BasketItem } from './BasketItem';
import { EmptyBasket } from './EmptyBasket';
import { SignOperationModal } from './SignOperationModal';
import { SignOperationsModal } from './SignOperationsModal';
import { SignTransactionModal } from './SignTransactionModal';
import { SignTransactionsModal } from './SignTransactionsModal';

export const BasketList = () => {
const { t } = useI18n();

const operations = useUnit(list.$filtered);
const selected = useUnit(basketOperations.$selected);
const transactionsToSign = useUnit(signOperations.$transactions);
const pendingValidations = useUnit(validation.$pending);
const refreshPending = useUnit(validation.validateAll.pending);

const isSignAvailable = selected.length > 0;
const isSignAvailable = selected.length > 0 && selected.every(operation => !pendingValidations[operation.id]);

const openSignModal = (transaction: BasketTransaction) => {
signOperations.startFlow({ transactions: [transaction] });
Expand Down Expand Up @@ -94,7 +95,7 @@ export const BasketList = () => {

{operations.length === 0 && <EmptyBasket />}

{transactionsToSign.length > 1 ? <SignOperationsModal /> : <SignOperationModal />}
{transactionsToSign.length > 1 ? <SignTransactionsModal /> : <SignTransactionModal />}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
import { useUnit } from 'effector-react';
import { useStoreMap, useUnit } from 'effector-react';

import { Slot } from '@/shared/di';
import { nonNullable } from '@/shared/lib/utils';
import { Loader } from '@/shared/ui';
import { Modal } from '@/shared/ui-kit';
import { SignButton } from '@/entities/operations';
import { walletSelect } from '@/aggregates/wallet-select';
import { OperationSign, OperationSubmit } from '@/features/operations';
import { signOperations } from '../model/sign';
import { validation } from '../model/validation';
import { signOperationsUtils } from '../service/sign-operations-utils';
import { Step } from '../types';

import { basketTransactionConfirmDetailsSlot, basketTransactionConfirmTitleSlot } from './SignOperationsModal';
import { basketTransactionConfirmDetailsSlot, basketTransactionConfirmTitleSlot } from './SignTransactionsModal';

export const SignOperationModal = () => {
export const SignTransactionModal = () => {
const transactions = useUnit(signOperations.$transactions);
const step = useUnit(signOperations.$step);
const isModalOpen = useUnit(signOperations.$isModalOpen);
const wallet = useUnit(walletSelect.$selectedWallet);

const transaction = transactions.at(0);

const validationResult = useStoreMap({
store: validation.$validatingResults,
keys: [transaction?.id],
fn: (results, [id]) => (nonNullable(id) ? (results[id] ?? null) : null),
});

const pending = useStoreMap({
store: validation.$pending,
keys: [transaction?.id],
fn: (results, [id]) => (nonNullable(id) ? (results[id] ?? true) : true),
});

if (!transaction) return null;

if (signOperationsUtils.isSubmitStep(step)) {
return <OperationSubmit isOpen={isModalOpen} onClose={() => signOperations.finishFlow()} />;
}

const canSign = validationResult && validationResult.length === 0;

return (
<Modal size="md" isOpen={isModalOpen} onToggle={() => signOperations.finishFlow()}>
<Modal.Title close>
Expand All @@ -34,17 +51,19 @@ export const SignOperationModal = () => {
{signOperationsUtils.isConfirmStep(step) && (
<div>
<Slot id={basketTransactionConfirmDetailsSlot} props={{ transaction }} />

<Modal.Footer>
<SignButton isDefault type={wallet?.type} onClick={signOperations.txsConfirmed} />
</Modal.Footer>
</div>
)}

{signOperationsUtils.isSignStep(step) && (
<OperationSign onGoBack={() => signOperations.changeStep(Step.CONFIRM)} />
)}
</Modal.Content>
{signOperationsUtils.isConfirmStep(step) && (
<Modal.Footer>
{pending && <Loader color="primary" size={24} />}
<SignButton isDefault type={wallet?.type} disabled={!canSign} onClick={signOperations.confirm} />
</Modal.Footer>
)}
</Modal>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,41 @@ import { useUnit } from 'effector-react';
import { type BasketTransaction, WalletType } from '@/shared/core';
import { Slot, createSlot } from '@/shared/di';
import { useI18n } from '@/shared/i18n';
import { HeaderTitleText } from '@/shared/ui';
import { HeaderTitleText, Loader } from '@/shared/ui';
import { Box, Modal } from '@/shared/ui-kit';
import { SignButton } from '@/entities/operations';
import { OperationSign, OperationSubmit } from '@/features/operations';
import { ConfirmSlider } from '@/features/operations/OperationsConfirm';
import { signOperations } from '../model/sign';
import { validation } from '../model/validation';
import { signOperationsUtils } from '../service/sign-operations-utils';
import { Step } from '../types';

export const basketTransactionConfirmTitleSlot = createSlot<{ transaction: BasketTransaction }>();
export const basketTransactionConfirmDetailsSlot = createSlot<{ transaction: BasketTransaction }>();

export const SignOperationsModal = () => {
export const SignTransactionsModal = () => {
const { t } = useI18n();

const transactions = useUnit(signOperations.$transactions);
const step = useUnit(signOperations.$step);
const isModalOpen = useUnit(signOperations.$isModalOpen);
const validationResults = useUnit(validation.$validatingResults);
const validationPending = useUnit(validation.$pending);

if (signOperationsUtils.isSubmitStep(step)) {
return <OperationSubmit isOpen={isModalOpen} onClose={() => signOperations.finishFlow()} />;
}

const pending = transactions.some(transaction => {
return validationPending[transaction.id] ?? true;
});

const canSign = transactions.every(transaction => {
const v = validationResults[transaction.id];
return v && v.length === 0;
});

return (
<Modal size="fit" isOpen={isModalOpen} onToggle={() => signOperations.finishFlow()}>
<Modal.Title close>
Expand All @@ -35,7 +47,17 @@ export const SignOperationsModal = () => {
{signOperationsUtils.isConfirmStep(step) && (
<ConfirmSlider
count={transactions.length}
footer={<SignButton isDefault type={WalletType.POLKADOT_VAULT} onClick={signOperations.txsConfirmed} />}
footer={
<Box direction="row" verticalAlign="center" gap={4}>
{pending && <Loader color="primary" size={24} />}
<SignButton
isDefault
type={WalletType.POLKADOT_VAULT}
disabled={!canSign}
onClick={signOperations.confirm}
/>
</Box>
}
>
{transactions.map(t => (
<ConfirmSlider.Item key={t.id}>
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/features/basket-operations/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export { SignOperationsModal } from './components/SignOperationsModal';
export { SignTransactionsModal } from './components/SignTransactionsModal';
export {
basketTransactionConfirmDetailsSlot,
basketTransactionConfirmTitleSlot,
} from './components/SignOperationsModal';
} from './components/SignTransactionsModal';
export { BasketList } from './components/BasketList';
export { operationTitleSlot } from './components/BasketItem';
export { basketOperationsFeature } from './model/feature';
Expand Down
14 changes: 11 additions & 3 deletions src/renderer/features/basket-operations/model/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import { ExtrinsicResult, submitModel } from '@/features/operations/OperationSub
import { signOperationsUtils } from '../service/sign-operations-utils';
import { Step } from '../types';

import { validation } from './validation';

const startFlow = createEvent<{ transactions: BasketTransaction[] }>();
const finishFlow = createEvent();
const changeStep = createEvent<Step>();
const txsConfirmed = createEvent();
const confirm = createEvent();

const $step = restore(changeStep, Step.NONE).reset(finishFlow);
const $transactions = createStore<BasketTransaction[]>([]).reset(finishFlow);
Expand All @@ -34,7 +36,13 @@ sample({
});

sample({
clock: txsConfirmed,
clock: startFlow,
fn: ({ transactions }) => transactions.map(transaction => ({ transaction })),
target: validation.validateTransactions,
});

sample({
clock: confirm,
source: {
transactions: $transactions,
chains: networkModel.$chains,
Expand Down Expand Up @@ -146,6 +154,6 @@ export const signOperations = {

startFlow,
finishFlow,
txsConfirmed,
changeStep,
confirm,
};
17 changes: 10 additions & 7 deletions src/renderer/features/basket-operations/model/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,16 @@ const validateFeeFx = attach({
},
});

const validateFx = createEffect(async ({ transaction, signerOptions }: ValidationParams) => {
const validateTransactionFx = createEffect(async ({ transaction, signerOptions }: ValidationParams) => {
return validateFeeFx({ transaction, signerOptions }).then(r => {
return validationAsyncPipeline.apply(r, { transaction, signerOptions });
});
});

const validateTransactionsFx = series(validateTransactionFx, { parallel: true, skipErrors: true });

sample({
clock: validateFx,
clock: validateTransactionFx,
source: $pending,
fn(pending, { transaction }) {
return produce(pending, draft => {
Expand All @@ -92,7 +94,7 @@ sample({
});

sample({
clock: validateFx.finally,
clock: validateTransactionFx.finally,
source: $pending,
fn(pending, { params }) {
return produce(pending, draft => {
Expand All @@ -103,7 +105,7 @@ sample({
});

sample({
clock: validateFx.finally,
clock: validateTransactionFx.finally,
source: $validatingResults,
fn(results, res) {
if (res.status === 'done') {
Expand Down Expand Up @@ -134,7 +136,7 @@ sample({
return { transaction, signerOptions: undefined };
});
},
target: series(validateFx),
target: validateTransactionsFx,
});

const validateAllFx = attach({
Expand All @@ -145,13 +147,14 @@ const validateAllFx = attach({
return { transaction, signerOptions: undefined };
});
},
effect: series(validateFx),
effect: validateTransactionsFx,
});

export const validation = {
validationAsyncPipeline,
$validatingResults,
$pending,
validateAll: validateAllFx,
validateTransaction: validateFx,
validateTransaction: validateTransactionFx,
validateTransactions: validateTransactionsFx,
};
9 changes: 7 additions & 2 deletions src/renderer/features/fellowship-basket/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useI18n } from '@/shared/i18n';
import { nullable } from '@/shared/lib/utils';
import { type IconNames } from '@/shared/ui';
import { salaryService, votingService } from '@/domains/collectives';
import { OperationTitle } from '@/entities/chain';
import { ChainTitle, OperationTitle } from '@/entities/chain';
import { TransactionTitle } from '@/entities/transaction';
import { basketOperationsService } from '@/aggregates/basket-operations';
import { basketSDK } from '@/sdk/basket';
Expand Down Expand Up @@ -71,7 +71,12 @@ basketSDK(fellowshipBasketFeature, {
const icon = getIcon(tx);

if (title && icon) {
return <TransactionTitle className="flex-1 overflow-hidden" title={t(title)} icon={icon} />;
return (
<>
<TransactionTitle className="flex-1 overflow-hidden" title={t(title)} icon={icon} />
<ChainTitle chainId={transaction.coreTx.chainId} />
</>
);
}

return null;
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/features/fellowship-tasks/components/Basket.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useI18n } from '@/shared/i18n';
import { nullable } from '@/shared/lib/utils';
import { Button } from '@/shared/ui';
import { basketUtils } from '@/entities/basket';
import { SignOperationsModal, signOperations } from '@/features/basket-operations';
import { SignTransactionsModal, signOperations } from '@/features/basket-operations';
import { fellowshipTasksFeature } from '../model/feature';
import { tasks } from '../model/tasks';

Expand All @@ -25,7 +25,7 @@ export const Basket = memo(() => {
<Button size="sm" pallet="secondary" disabled={transactions.length === 0} onClick={openSigning}>
{t('fellowship.tasks.reviewBasket', { count: transactions.length })}
</Button>
<SignOperationsModal />
<SignTransactionsModal />
</>
);
});
9 changes: 7 additions & 2 deletions src/renderer/features/governance-basket/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useGate } from 'effector-react';
import { type Transaction, TransactionType } from '@/shared/core';
import { useI18n } from '@/shared/i18n';
import { type IconNames } from '@/shared/ui';
import { OperationTitle } from '@/entities/chain';
import { ChainTitle, OperationTitle } from '@/entities/chain';
import { TransactionTitle } from '@/entities/transaction';
import { basketOperationsService } from '@/aggregates/basket-operations';
import { basketSDK } from '@/sdk/basket';
Expand Down Expand Up @@ -78,7 +78,12 @@ basketSDK(governanceBasketFeature, {
const icon = getOperationIcon(tx);

if (title && icon) {
return <TransactionTitle className="flex-1 overflow-hidden" title={t(title)} icon={icon} />;
return (
<>
<TransactionTitle className="flex-1 overflow-hidden" title={t(title)} icon={icon} />
<ChainTitle chainId={transaction.coreTx.chainId} />
</>
);
}

return null;
Expand Down
Loading
Loading