Skip to content

Commit 573b802

Browse files
Moustafa-Koterbadilaouid
authored andcommitted
feature/LIVE-20724 [LLD][UI] Memo screen
wip Fix width on the wip wip
1 parent cf8643c commit 573b802

File tree

14 files changed

+1505
-4
lines changed

14 files changed

+1505
-4
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import React, { useMemo, type ComponentType, type ReactNode, type CSSProperties } from "react";
2+
import { useFlowWizardNavigation } from "./hooks/useFlowWizardNavigation";
3+
import type {
4+
FlowStep,
5+
FlowConfig,
6+
StepRegistry,
7+
StepRenderer,
8+
FlowNavigationDirection,
9+
AnimationConfig,
10+
FlowWizardContextValue,
11+
FlowStepConfig,
12+
} from "./types";
13+
14+
const DEFAULT_ANIMATION_CONFIG: AnimationConfig = {
15+
forward: "animate-fade-in",
16+
backward: "animate-fade-out",
17+
};
18+
19+
type FlowWizardOrchestratorProps<
20+
TStep extends FlowStep,
21+
TContextValue,
22+
TStepConfig extends FlowStepConfig<TStep> = FlowStepConfig<TStep>,
23+
> = Readonly<{
24+
flowConfig: FlowConfig<TStep, TStepConfig>;
25+
stepRegistry: StepRegistry<TStep>;
26+
contextValue: TContextValue;
27+
ContextProvider: ComponentType<{
28+
value: FlowWizardContextValue<TStep, TContextValue, TStepConfig>;
29+
children: ReactNode;
30+
}>;
31+
animationConfig?: AnimationConfig;
32+
getContainerStyle?: (stepConfig: TStepConfig) => CSSProperties | undefined;
33+
children?: ReactNode;
34+
}>;
35+
36+
// Returns the transition class for the current direction; keeps UI concerns isolated here.
37+
function getAnimationClass(
38+
direction: FlowNavigationDirection,
39+
config: AnimationConfig,
40+
): string | undefined {
41+
return direction === "FORWARD" ? config.forward : config.backward;
42+
}
43+
44+
/**
45+
* FlowWizardOrchestrator
46+
*
47+
* Generic runner for multi-step flows:
48+
* - drives navigation (forward/back/jump) via useFlowWizardNavigation
49+
* - injects navigation & step metadata into the provided ContextProvider
50+
* - renders the current step with optional enter animations
51+
* - remains UI-agnostic: only needs a step registry and a flow config
52+
*/
53+
export function FlowWizardOrchestrator<
54+
TStep extends FlowStep,
55+
TContextValue,
56+
TStepConfig extends FlowStepConfig<TStep> = FlowStepConfig<TStep>,
57+
>({
58+
flowConfig,
59+
stepRegistry,
60+
contextValue,
61+
ContextProvider,
62+
animationConfig = DEFAULT_ANIMATION_CONFIG,
63+
getContainerStyle,
64+
children,
65+
}: FlowWizardOrchestratorProps<TStep, TContextValue, TStepConfig>) {
66+
const { state, actions, currentStepConfig } = useFlowWizardNavigation<TStep, TStepConfig>({
67+
flowConfig,
68+
});
69+
70+
const enhancedContextValue = useMemo<FlowWizardContextValue<TStep, TContextValue, TStepConfig>>(
71+
() => ({
72+
...contextValue,
73+
navigation: actions,
74+
currentStep: state.currentStep,
75+
direction: state.direction,
76+
currentStepConfig,
77+
}),
78+
[contextValue, actions, state.currentStep, state.direction, currentStepConfig],
79+
);
80+
81+
const StepComponent = useMemo<StepRenderer | null>(() => {
82+
const renderer = stepRegistry[state.currentStep];
83+
return renderer ?? null;
84+
}, [state.currentStep, stepRegistry]);
85+
86+
const hasNavigated = state.stepHistory.length > 0 || state.direction === "BACKWARD";
87+
const animationClass = hasNavigated
88+
? getAnimationClass(state.direction, animationConfig)
89+
: undefined;
90+
91+
const containerStyle = getContainerStyle ? getContainerStyle(currentStepConfig) : undefined;
92+
return (
93+
<ContextProvider value={enhancedContextValue}>
94+
<div className="flex flex-col" style={containerStyle}>
95+
{children}
96+
{StepComponent && (
97+
<div key={state.currentStep} className={`min-h-0 flex-1 ${animationClass ?? ""}`}>
98+
<StepComponent />
99+
</div>
100+
)}
101+
</div>
102+
</ContextProvider>
103+
);
104+
}
105+
106+
// Need to use it to create the step registry typesafe
107+
export function createStepRegistry<TStep extends FlowStep>(
108+
registry: StepRegistry<TStep>,
109+
): StepRegistry<TStep> {
110+
return registry;
111+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { useMemo, type ReactNode } from "react";
2+
import { FlowWizardOrchestrator } from "../FlowWizard/FlowWizardOrchestrator";
3+
import type { StepRegistry, AnimationConfig, FlowWizardContextValue } from "../FlowWizard/types";
4+
import { SendFlowProvider } from "./context/SendFlowContext";
5+
import { useSendFlowBusinessLogic } from "./hooks/useSendFlowState";
6+
import { SEND_FLOW_CONFIG } from "./constants";
7+
import { SEND_FLOW_STEP } from "./types";
8+
import type {
9+
SendFlowStep,
10+
SendFlowInitParams,
11+
SendFlowBusinessContext,
12+
SendStepConfig,
13+
} from "./types";
14+
15+
type SendFlowStepRegistry = StepRegistry<SendFlowStep>;
16+
type SendFlowWizardContext = FlowWizardContextValue<
17+
SendFlowStep,
18+
SendFlowBusinessContext,
19+
SendStepConfig
20+
>;
21+
22+
type SendFlowOrchestratorProps = Readonly<{
23+
initParams?: SendFlowInitParams;
24+
onClose: () => void;
25+
stepRegistry: SendFlowStepRegistry;
26+
animationConfig?: AnimationConfig;
27+
children?: ReactNode;
28+
}>;
29+
30+
type SendFlowProviderWrapperProps = Readonly<{
31+
value: SendFlowWizardContext;
32+
children: ReactNode;
33+
}>;
34+
35+
// Adapter that injects the Send context into the generic FlowWizard orchestrator
36+
function SendFlowProviderWrapper({ value, children }: SendFlowProviderWrapperProps) {
37+
return <SendFlowProvider value={value}>{children}</SendFlowProvider>;
38+
}
39+
40+
export function SendFlowOrchestrator({
41+
initParams,
42+
onClose,
43+
stepRegistry,
44+
animationConfig,
45+
children,
46+
}: SendFlowOrchestratorProps) {
47+
const skipAccountSelection = Boolean(initParams?.account) || Boolean(initParams?.fromMAD);
48+
const businessContext = useSendFlowBusinessLogic({ initParams, onClose });
49+
const flowConfig = useMemo(
50+
() => ({
51+
...SEND_FLOW_CONFIG,
52+
initialStep: skipAccountSelection
53+
? SEND_FLOW_STEP.RECIPIENT
54+
: SEND_FLOW_STEP.ACCOUNT_SELECTION,
55+
}),
56+
[skipAccountSelection],
57+
);
58+
59+
// Bridge the generic wizard runner with Send-specific business state and config
60+
return (
61+
<FlowWizardOrchestrator<SendFlowStep, SendFlowBusinessContext, SendStepConfig>
62+
flowConfig={flowConfig}
63+
stepRegistry={stepRegistry}
64+
contextValue={businessContext}
65+
ContextProvider={SendFlowProviderWrapper}
66+
animationConfig={animationConfig}
67+
getContainerStyle={stepConfig =>
68+
stepConfig.sizeDialog ? { height: `${stepConfig.sizeDialog}px` } : { height: "612px" }
69+
}
70+
>
71+
{children}
72+
</FlowWizardOrchestrator>
73+
);
74+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React, { useCallback } from "react";
2+
import { createPortal } from "react-dom";
3+
import { Dialog, DialogContent, DialogBody } from "@ledgerhq/lumen-ui-react";
4+
import { DomainServiceProvider } from "@ledgerhq/domain-service/hooks/index";
5+
import { useDispatch, useSelector } from "react-redux";
6+
import { SendWorkflow } from ".";
7+
import { closeSendFlowDialog, sendFlowStateSelector } from "~/renderer/reducers/sendFlow";
8+
import { setMemoTagInfoBoxDisplay } from "~/renderer/actions/UI";
9+
import Snow, { isSnowTime } from "~/renderer/extra/Snow";
10+
11+
export function SendFlowRoot() {
12+
const dispatch = useDispatch();
13+
const { isOpen, data } = useSelector(sendFlowStateSelector);
14+
15+
const handleClose = useCallback(() => {
16+
dispatch(
17+
setMemoTagInfoBoxDisplay({
18+
isMemoTagBoxVisible: false,
19+
forceAutoFocusOnMemoField: false,
20+
}),
21+
);
22+
data?.onClose?.();
23+
dispatch(closeSendFlowDialog());
24+
}, [data, dispatch]);
25+
26+
const handleDialogOpenChange = useCallback(
27+
(open: boolean) => {
28+
if (!open) {
29+
handleClose();
30+
}
31+
},
32+
[handleClose],
33+
);
34+
35+
if (!isOpen) return null;
36+
37+
return (
38+
<>
39+
{isSnowTime() && isOpen
40+
? createPortal(
41+
<div
42+
style={{
43+
position: "fixed",
44+
top: 0,
45+
left: 0,
46+
width: "100vw",
47+
height: "100vh",
48+
pointerEvents: "none",
49+
zIndex: 99,
50+
overflow: "hidden",
51+
}}
52+
>
53+
<Snow numFlakes={30} />
54+
</div>,
55+
document.body,
56+
)
57+
: null}
58+
<Dialog open={isOpen} onOpenChange={handleDialogOpenChange}>
59+
<DialogContent>
60+
<DialogBody
61+
className="shrink-0 px-0 text-base"
62+
style={{ scrollbarWidth: "none" as const }}
63+
>
64+
<DomainServiceProvider>
65+
<SendWorkflow onClose={handleClose} params={data?.params} />
66+
</DomainServiceProvider>
67+
</DialogBody>
68+
</DialogContent>
69+
</Dialog>
70+
</>
71+
);
72+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { useCallback, useMemo } from "react";
2+
import useBridgeTransaction from "@ledgerhq/live-common/bridge/useBridgeTransaction";
3+
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
4+
import { applyMemoToTransaction } from "@ledgerhq/live-common/bridge/descriptor";
5+
import type { Account, AccountLike } from "@ledgerhq/types-live";
6+
import type { Transaction } from "@ledgerhq/live-common/generated/types";
7+
import type { SendFlowTransactionState, SendFlowTransactionActions, RecipientData } from "../types";
8+
9+
type UseSendFlowTransactionParams = Readonly<{
10+
account: AccountLike | null;
11+
parentAccount: Account | null;
12+
}>;
13+
14+
type UseSendFlowTransactionResult = Readonly<{
15+
state: SendFlowTransactionState;
16+
actions: SendFlowTransactionActions;
17+
}>;
18+
19+
export function useSendFlowTransaction({
20+
account,
21+
parentAccount,
22+
}: UseSendFlowTransactionParams): UseSendFlowTransactionResult {
23+
const {
24+
transaction,
25+
setTransaction: bridgeSetTransaction,
26+
updateTransaction: bridgeUpdateTransaction,
27+
status,
28+
bridgeError,
29+
bridgePending,
30+
setAccount,
31+
} = useBridgeTransaction(() => {
32+
if (!account) return {};
33+
return { account, parentAccount: parentAccount ?? undefined };
34+
});
35+
36+
const setTransaction = useCallback(
37+
(tx: Transaction) => bridgeSetTransaction(tx),
38+
[bridgeSetTransaction],
39+
);
40+
41+
const updateTransaction = useCallback(
42+
(updater: (tx: Transaction) => Transaction) => bridgeUpdateTransaction(updater),
43+
[bridgeUpdateTransaction],
44+
);
45+
46+
const setRecipient = useCallback(
47+
(recipient: RecipientData) => {
48+
if (!account || !transaction) return;
49+
50+
const bridge = getAccountBridge(account, parentAccount);
51+
const updates: Partial<Transaction> = {};
52+
53+
if (recipient !== undefined) {
54+
Object.assign(updates, { recipient: recipient.address });
55+
}
56+
57+
if (recipient.memo !== undefined) {
58+
Object.assign(
59+
updates,
60+
applyMemoToTransaction(
61+
transaction.family,
62+
recipient.memo.value,
63+
recipient.memo.type,
64+
transaction,
65+
),
66+
);
67+
}
68+
69+
if (recipient.destinationTag !== undefined) {
70+
const parsedTag = Number(recipient.destinationTag.trim());
71+
if (Number.isFinite(parsedTag)) {
72+
Object.assign(
73+
updates,
74+
applyMemoToTransaction(transaction.family, parsedTag, undefined, transaction),
75+
);
76+
}
77+
}
78+
79+
if (Object.keys(updates).length > 0) {
80+
bridgeSetTransaction(bridge.updateTransaction(transaction, updates));
81+
}
82+
},
83+
[account, parentAccount, transaction, bridgeSetTransaction],
84+
);
85+
86+
const setAccountForTransaction = useCallback(
87+
(newAccount: AccountLike, newParentAccount?: Account | null) => {
88+
setAccount(newAccount, newParentAccount ?? undefined);
89+
},
90+
[setAccount],
91+
);
92+
93+
const state: SendFlowTransactionState = useMemo(
94+
() => ({
95+
transaction: transaction ?? null,
96+
status,
97+
bridgeError: bridgeError ?? null,
98+
bridgePending,
99+
}),
100+
[transaction, status, bridgeError, bridgePending],
101+
);
102+
103+
const actions: SendFlowTransactionActions = useMemo(
104+
() => ({
105+
setTransaction,
106+
updateTransaction,
107+
setRecipient,
108+
setAccount: setAccountForTransaction,
109+
}),
110+
[setTransaction, updateTransaction, setRecipient, setAccountForTransaction],
111+
);
112+
113+
return { state, actions };
114+
}

0 commit comments

Comments
 (0)