Skip to content
Open
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
4 changes: 2 additions & 2 deletions app/features/gift-cards/gift-card-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,14 @@ export default function GiftCardDetails({ cardId }: GiftCardDetailsProps) {

<div className="mt-6 grid w-72 grid-cols-2 gap-10">
<LinkWithViewTransition
to={`/receive?accountId=${card.id}`}
to={`/receive?accountId=${card.id}&redirectTo=${encodeURIComponent(`/gift-cards/${card.id}`)}`}
transition="slideUp"
applyTo="newView"
>
<Button className="w-full px-7 py-6 text-lg">Add</Button>
</LinkWithViewTransition>
<LinkWithViewTransition
to={`/send?accountId=${card.id}`}
to={`/send?accountId=${card.id}&redirectTo=${encodeURIComponent(`/gift-cards/${card.id}`)}`}
transition="slideUp"
applyTo="newView"
>
Expand Down
15 changes: 14 additions & 1 deletion app/features/receive/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import ReceiveCashu from './receive-cashu';
import ReceiveCashuToken from './receive-cashu-token';
import {
type ReceiveFlowDefinition,
ReceiveFlowProvider,
useReceiveFlowStep,
} from './receive-flow';
import ReceiveInput from './receive-input';
import { ReceiveProvider } from './receive-provider';

export { ReceiveInput, ReceiveCashuToken, ReceiveCashu, ReceiveProvider };
export {
ReceiveInput,
ReceiveCashuToken,
ReceiveCashu,
ReceiveProvider,
ReceiveFlowProvider,
useReceiveFlowStep,
};
export type { ReceiveFlowDefinition };
19 changes: 7 additions & 12 deletions app/features/receive/receive-cashu-token.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ import { Button } from '~/components/ui/button';
import { useToast } from '~/hooks/use-toast';
import { useFeatureFlag } from '~/lib/feature-flags';
import type { Currency } from '~/lib/money';
import {
LinkWithViewTransition,
useNavigateWithViewTransition,
} from '~/lib/transitions';
import { LinkWithViewTransition } from '~/lib/transitions';
import { AccountSelector } from '../accounts/account-selector';
import { GiftCardItem } from '../gift-cards/gift-card-item';
import { getGiftCardImageByUrl } from '../gift-cards/use-discover-cards';
Expand All @@ -45,6 +42,7 @@ import {
type ReceiveCashuTokenAccount,
isClaimingToSameCashuAccount,
} from './receive-cashu-token-models';
import { useReceiveFlowStep } from './receive-flow';

type Props = {
token: Token;
Expand Down Expand Up @@ -104,7 +102,7 @@ export default function ReceiveToken({
preferredReceiveAccountId,
}: Props) {
const { toast } = useToast();
const navigate = useNavigateWithViewTransition();
const { back, onSuccess } = useReceiveFlowStep('claimCashuToken');
const { claimableToken, cannotClaimReason } =
useCashuTokenWithClaimableProofs({ token });
const {
Expand Down Expand Up @@ -160,10 +158,7 @@ export default function ReceiveToken({
return result.lightningReceiveQuote.transactionId;
},
onSuccess: (transactionId) => {
navigate(`/transactions/${transactionId}?redirectTo=/`, {
transition: 'slideLeft',
applyTo: 'newView',
});
onSuccess(transactionId);
},
onError: (error) => {
console.error('Error claiming token', { cause: error });
Expand All @@ -179,9 +174,9 @@ export default function ReceiveToken({
<>
<PageHeader className="z-10">
<PageBackButton
to="/receive"
transition="slideRight"
applyTo="oldView"
to={back.to}
transition={back.transition}
applyTo={back.applyTo}
/>
<PageHeaderTitle>Receive</PageHeaderTitle>
</PageHeader>
Expand Down
25 changes: 10 additions & 15 deletions app/features/receive/receive-cashu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ import type { CashuAccount } from '~/features/accounts/account';
import { useEffectNoStrictMode } from '~/hooks/use-effect-no-strict-mode';
import { useToast } from '~/hooks/use-toast';
import type { Money } from '~/lib/money';
import {
LinkWithViewTransition,
useNavigateWithViewTransition,
} from '~/lib/transitions';
import { LinkWithViewTransition } from '~/lib/transitions';
import { getDefaultUnit } from '../shared/currencies';
import { MoneyWithConvertedAmount } from '../shared/money-with-converted-amount';
import type { CashuReceiveQuote } from './cashu-receive-quote';
import {
useCashuReceiveQuote,
useCreateCashuReceiveQuote,
} from './cashu-receive-quote-hooks';
import { useReceiveFlowStep } from './receive-flow';

type CreateQuoteProps = {
account: CashuAccount;
Expand Down Expand Up @@ -110,16 +108,13 @@ export default function ReceiveCashu({ amount, account }: Props) {
const [showOk, setShowOk] = useState(false);
const [, copyToClipboard] = useCopyToClipboard();
const { toast } = useToast();
const navigate = useNavigateWithViewTransition();
const { back, onSuccess } = useReceiveFlowStep('cashuLightningInvoice');

const { quote, errorMessage, isLoading } = useCreateQuote({
account,
amount,
onPaid: (quote) => {
navigate(`/transactions/${quote.transactionId}?redirectTo=/`, {
transition: 'fade',
applyTo: 'newView',
});
onSuccess(quote.transactionId);
},
});

Expand All @@ -141,9 +136,9 @@ export default function ReceiveCashu({ amount, account }: Props) {
<>
<PageHeader>
<ClosePageButton
to="/receive"
transition="slideDown"
applyTo="oldView"
to={back.to}
transition={back.transition}
applyTo={back.applyTo}
/>
<PageHeaderTitle>Receive Ecash</PageHeaderTitle>
</PageHeader>
Expand Down Expand Up @@ -172,9 +167,9 @@ export default function ReceiveCashu({ amount, account }: Props) {
<PageFooter className="pb-14">
<Button asChild className="w-[80px]">
<LinkWithViewTransition
to="/"
transition="slideDown"
applyTo="oldView"
to={back.to}
transition={back.transition}
applyTo={back.applyTo}
>
OK
</LinkWithViewTransition>
Expand Down
93 changes: 93 additions & 0 deletions app/features/receive/receive-flow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { type PropsWithChildren, createContext, useContext } from 'react';
import type { ApplyTo, Transition } from '~/lib/transitions';

/**
* A navigation action with destination and animation.
* The `to` is an object with pathname and search to ensure we can spread hash into it.
*/
export type NavAction = {
to: { pathname: string; search: string };
transition: Transition;
applyTo: ApplyTo;
};

/** Dynamic navigation action that requires parameters */
type DynamicNavAction<T extends unknown[]> = (...args: T) => NavAction;

/**
* The complete receive flow definition.
* Step and action names are self-documenting to describe the flow structure.
*/
export type ReceiveFlowDefinition = {
amountInput: {
close: NavAction;
next: {
cashuLightningInvoice: NavAction;
sparkLightningInvoice: NavAction;
};
actions: {
scanToken: NavAction;
claimCashuToken: DynamicNavAction<[string]>;
};
};
scanToken: {
back: NavAction;
next: {
claimCashuToken: DynamicNavAction<[string]>;
};
};
cashuLightningInvoice: {
back: NavAction;
onSuccess: (transactionId: string) => void;
};
sparkLightningInvoice: {
back: NavAction;
onSuccess: (transactionId: string) => void;
};
claimCashuToken: {
back: NavAction;
onSuccess: (transactionId: string) => void;
};
};

const ReceiveFlowContext = createContext<ReceiveFlowDefinition | null>(null);

type ReceiveFlowProviderProps = PropsWithChildren<{
flow: ReceiveFlowDefinition;
}>;

/**
* Provider that makes the receive flow navigation available to all child components.
* Should be used in the receive layout to wrap the Outlet.
*/
export const ReceiveFlowProvider = ({
flow,
children,
}: ReceiveFlowProviderProps) => {
return (
<ReceiveFlowContext.Provider value={flow}>
{children}
</ReceiveFlowContext.Provider>
);
};

/**
* Hook for components to get their step's navigation actions.
* Components think in terms of back/next/close.
*
* @example
* const { close, next, actions } = useReceiveFlowStep('amountInput');
* <ClosePageButton {...close} />
* navigate(next.cashuLightningInvoice.to, { transition: next.cashuLightningInvoice.transition });
*/
export function useReceiveFlowStep<K extends keyof ReceiveFlowDefinition>(
stepName: K,
): ReceiveFlowDefinition[K] {
const flow = useContext(ReceiveFlowContext);
if (!flow) {
throw new Error(
'useReceiveFlowStep must be used within ReceiveFlowProvider',
);
}
return flow[stepName];
}
40 changes: 22 additions & 18 deletions app/features/receive/receive-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
useNavigateWithViewTransition,
} from '~/lib/transitions';
import { useAccount, useAccounts } from '../accounts/account-hooks';
import { useReceiveFlowStep } from './receive-flow';
import { useReceiveStore } from './receive-provider';

type ConvertedMoneySwitcherProps = {
Expand Down Expand Up @@ -63,6 +64,7 @@ const ConvertedMoneySwitcher = ({
export default function ReceiveInput() {
const navigate = useNavigateWithViewTransition();
const { toast } = useToast();
const { close, next, actions } = useReceiveFlowStep('amountInput');
const { animationClass: shakeAnimationClass, start: startShakeAnimation } =
useAnimation({ name: 'shake' });

Expand Down Expand Up @@ -104,17 +106,14 @@ export default function ReceiveInput() {
setReceiveAmount(convertedValue);
}

if (receiveAccount.type === 'cashu') {
navigate('/receive/cashu', {
transition: 'slideLeft',
applyTo: 'newView',
});
} else {
navigate('/receive/spark', {
transition: 'slideLeft',
applyTo: 'newView',
});
}
const nextStep =
receiveAccount.type === 'cashu'
? next.cashuLightningInvoice
: next.sparkLightningInvoice;
navigate(nextStep.to, {
transition: nextStep.transition,
applyTo: nextStep.applyTo,
});
};

const handlePaste = async () => {
Expand All @@ -139,19 +138,24 @@ export default function ReceiveInput() {
// The hash needs to be set manually before navigating or clientLoader of the destination route won't see it
// See https://github.com/remix-run/remix/discussions/10721
window.history.replaceState(null, '', hash);
const tokenAction = actions.claimCashuToken(receiveAccountId);
navigate(
`/receive/cashu/token?selectedAccountId=${receiveAccountId}${hash}`,
{ ...tokenAction.to, hash },
{
transition: 'slideLeft',
applyTo: 'newView',
transition: tokenAction.transition,
applyTo: tokenAction.applyTo,
},
);
};

return (
<>
<PageHeader>
<ClosePageButton to="/" transition="slideDown" applyTo="oldView" />
<ClosePageButton
to={close.to}
transition={close.transition}
applyTo={close.applyTo}
/>
<PageHeaderTitle>Receive</PageHeaderTitle>
</PageHeader>

Expand Down Expand Up @@ -197,9 +201,9 @@ export default function ReceiveInput() {
</button>

<LinkWithViewTransition
to="/receive/scan"
transition="slideUp"
applyTo="newView"
to={actions.scanToken.to}
transition={actions.scanToken.transition}
applyTo={actions.scanToken.applyTo}
>
<Scan />
</LinkWithViewTransition>
Expand Down
25 changes: 10 additions & 15 deletions app/features/receive/receive-spark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ import { Button } from '~/components/ui/button';
import { useEffectNoStrictMode } from '~/hooks/use-effect-no-strict-mode';
import { useToast } from '~/hooks/use-toast';
import type { Money } from '~/lib/money';
import {
LinkWithViewTransition,
useNavigateWithViewTransition,
} from '~/lib/transitions';
import { LinkWithViewTransition } from '~/lib/transitions';
import type { SparkAccount } from '../accounts/account';
import { MoneyWithConvertedAmount } from '../shared/money-with-converted-amount';
import { useReceiveFlowStep } from './receive-flow';
import type { SparkReceiveQuote } from './spark-receive-quote';
import {
useCreateSparkReceiveQuote,
Expand Down Expand Up @@ -68,19 +66,16 @@ const useCreateQuote = ({
};

export default function ReceiveSpark({ amount, account }: Props) {
const navigate = useNavigateWithViewTransition();
const [showOk, setShowOk] = useState(false);
const [, copyToClipboard] = useCopyToClipboard();
const { toast } = useToast();
const { back, onSuccess } = useReceiveFlowStep('sparkLightningInvoice');

const { quote, errorMessage, isLoading } = useCreateQuote({
account,
amount,
onPaid: (quote) => {
navigate(`/transactions/${quote.transactionId}?redirectTo=/`, {
transition: 'fade',
applyTo: 'newView',
});
onSuccess(quote.transactionId);
},
});

Expand All @@ -98,9 +93,9 @@ export default function ReceiveSpark({ amount, account }: Props) {
<>
<PageHeader>
<ClosePageButton
to="/receive"
transition="slideDown"
applyTo="oldView"
to={back.to}
transition={back.transition}
applyTo={back.applyTo}
/>
<PageHeaderTitle>Receive</PageHeaderTitle>
</PageHeader>
Expand All @@ -118,9 +113,9 @@ export default function ReceiveSpark({ amount, account }: Props) {
<PageFooter className="pb-14">
<Button asChild className="w-[80px]">
<LinkWithViewTransition
to="/"
transition="slideDown"
applyTo="oldView"
to={back.to}
transition={back.transition}
applyTo={back.applyTo}
>
OK
</LinkWithViewTransition>
Expand Down
Loading