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

Alessey/extract lifecycle status #1830

Closed
wants to merge 2 commits into from
Closed
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
23 changes: 12 additions & 11 deletions src/core-react/nft/hooks/useLifecycleStatus.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { useCallback, useState } from 'react';
import type { LifecycleStatus, LifecycleStatusUpdate } from '../types';
import type { APIError } from '@/core/api/types';

type UseLifecycleStatusReturn = [
lifecycleStatus: LifecycleStatus,
updatelifecycleStatus: (newStatus: LifecycleStatusUpdate) => void,
type UseLifecycleStatusReturn<T extends LifecycleStatus> = [
lifecycleStatus: T,
updatelifecycleStatus: (newStatus: LifecycleStatusUpdate<T>) => void,
];

export function useLifecycleStatus(
initialState: LifecycleStatus,
): UseLifecycleStatusReturn {
export function useLifecycleStatus<T extends LifecycleStatus>(
initialState: T,
): UseLifecycleStatusReturn<T> {
const [lifecycleStatus, setLifecycleStatus] =
useState<LifecycleStatus>(initialState); // Component lifecycle
useState<T>(initialState); // Component lifecycle

// Update lifecycle status, statusData will be persisted for the full lifecycle
const updateLifecycleStatus = useCallback(
(newStatus: LifecycleStatusUpdate) => {
setLifecycleStatus((prevStatus: LifecycleStatus) => {
(newStatus: LifecycleStatusUpdate<T>) => {
setLifecycleStatus((prevStatus: T) => {
// do not persist errors
const persistedStatusData =
prevStatus.statusName === 'error'
? (({ error, code, message, ...statusData }) => statusData)(
prevStatus.statusData,
prevStatus.statusData as APIError
)
: prevStatus.statusData;
return {
Expand All @@ -29,7 +30,7 @@ export function useLifecycleStatus(
...persistedStatusData,
...newStatus.statusData,
},
} as LifecycleStatus;
} as T;
});
},
[],
Expand Down
21 changes: 11 additions & 10 deletions src/core-react/nft/providers/NFTLifecycleProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import { createContext, useContext, useEffect } from 'react';
import { useValue } from '../../internal/hooks/useValue';
import { useLifecycleStatus } from '../hooks/useLifecycleStatus';
import type {
LifecycleStatus,
NFTLifecycleContextType,
NFTLifecycleProviderReact,
} from '../types';
import type { TransactionReceipt } from 'viem';
import type { NFTError } from '@/core/api/types';

const emptyContext = {} as NFTLifecycleContextType;
const emptyContext = {} as NFTLifecycleContextType<LifecycleStatus>;

export const NFTLifecycleContext =
createContext<NFTLifecycleContextType>(emptyContext);
createContext<NFTLifecycleContextType<LifecycleStatus>>(emptyContext);

export function useNFTLifecycleContext() {
const context = useContext(NFTLifecycleContext);
Expand All @@ -21,27 +24,25 @@ export function useNFTLifecycleContext() {
return context;
}

export function NFTLifecycleProvider({
export function NFTLifecycleProvider<T extends LifecycleStatus>({
type,
onStatus,
onError,
onSuccess,
children,
}: NFTLifecycleProviderReact) {
const [lifecycleStatus, updateLifecycleStatus] = useLifecycleStatus({
statusName: 'init',
statusData: null,
}); // Component lifecycle
initialState = { statusName: 'init', statusData: null } as T,
}: NFTLifecycleProviderReact<T>) {
const [lifecycleStatus, updateLifecycleStatus] = useLifecycleStatus<T>(initialState); // Component lifecycle

// Component lifecycle emitters
useEffect(() => {
// Error
if (lifecycleStatus.statusName === 'error') {
onError?.(lifecycleStatus.statusData);
onError?.(lifecycleStatus.statusData as NFTError);
}
// Success
if (lifecycleStatus.statusName === 'success') {
onSuccess?.(lifecycleStatus.statusData?.transactionReceipts?.[0]);
onSuccess?.(lifecycleStatus.statusData?.transactionReceipts?.[0] as unknown as TransactionReceipt);
}
// Emit Status
onStatus?.(lifecycleStatus);
Expand Down
53 changes: 41 additions & 12 deletions src/core-react/nft/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ export enum LifecycleType {
MINT = 'mint',
}

export type NFTLifecycleProviderReact = {
export type NFTLifecycleProviderReact<T extends LifecycleStatus> = {
type: LifecycleType;
onError?: (error: NFTError) => void;
onStatus?: (lifecycleStatus: LifecycleStatus) => void;
onStatus?: (lifecycleStatus: T) => void;
onSuccess?: (transactionReceipt?: TransactionReceipt) => void;
children: ReactNode;
initialState?: T;
};

export type NFTLifecycleContextType = {
export type NFTLifecycleContextType<T extends LifecycleStatus> = {
type: LifecycleType;
lifecycleStatus: LifecycleStatus;
updateLifecycleStatus: (status: LifecycleStatusUpdate) => void;
lifecycleStatus: T;
updateLifecycleStatus: (status: LifecycleStatusUpdate<T>) => void;
};

/* NFT Provider */
Expand Down Expand Up @@ -120,7 +121,7 @@ export type NFTCardReact = {
tokenId: string; // Required Token ID of the NFT
useNFTData?: UseNFTData; // Optional hook to override the default useNftData hook
onError?: (error: NFTError) => void; // An optional callback function that handles errors within the provider.
onStatus?: (lifecycleStatus: LifecycleStatus) => void; // An optional callback function that exposes the component lifecycle state
onStatus?: (lifecycleStatus: LifecycleStatusView) => void; // An optional callback function that exposes the component lifecycle state
onSuccess?: (transactionReceipt?: TransactionReceipt) => void; // card will not pass transactionReceipt
};

Expand All @@ -143,7 +144,7 @@ export type NFTMintCardReact = {
useNFTData?: UseNFTData; // Optional hook to override the default useNFTData hook
buildMintTransaction?: BuildMintTransaction; // Optional function to override the default function that builds the mint transaction
onError?: (error: NFTError) => void; // An optional callback function that handles errors within the provider.
onStatus?: (lifecycleStatus: LifecycleStatus) => void; // An optional callback function that exposes the component lifecycle state
onStatus?: (lifecycleStatus: LifecycleStatusMint) => void; // An optional callback function that exposes the component lifecycle state
onSuccess?: (transactionReceipt?: TransactionReceipt) => void; // mint will pass transactionReceipt
};

Expand All @@ -153,7 +154,37 @@ export type NFTMintCardReact = {
*/
export type NFTMintCardDefaultReact = Omit<NFTMintCardReact, 'children'>;

export type LifecycleStatus =
export type LifecycleStatus = {
statusName: string;
statusData: any;
} | {
statusName: 'init' | 'error' | 'success';
statusData: any;
};

export type LifecycleStatusView =
| {
statusName: 'init';
statusData: null;
}
| {
statusName: 'error';
statusData: NFTError;
}
| {
statusName: 'mediaLoading';
statusData: {
mediaType: MediaType;
mediaUrl: string;
};
}
| {
statusName: 'success';
statusData: null
};


export type LifecycleStatusMint =
| {
statusName: 'init';
statusData: null;
Expand Down Expand Up @@ -208,8 +239,7 @@ type AllKeysInShared<T> = keyof T extends keyof LifecycleStatusDataShared
* Used to type the statuses used to update LifecycleStatus
* LifecycleStatusData is persisted across state updates allowing SharedData to be optional except for in init step
*/
export type LifecycleStatusUpdate = LifecycleStatus extends infer T
? T extends { statusName: infer N; statusData: infer D }
export type LifecycleStatusUpdate<T extends LifecycleStatus> = T extends { statusName: infer N; statusData: infer D }
? { statusName: N } & (N extends 'init' // statusData required in statusName "init"
? { statusData: D }
: AllKeysInShared<D> extends true // is statusData is LifecycleStatusDataShared, make optional
Expand All @@ -225,5 +255,4 @@ export type LifecycleStatusUpdate = LifecycleStatus extends infer T
keyof D & keyof LifecycleStatusDataShared
>;
})
: never
: never;
: never;
5 changes: 3 additions & 2 deletions src/ui/react/nft/NFTCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useTheme } from '@/core-react/internal/hooks/useTheme';
import { useNFTData as defaultUseNFTData } from '@/core-react/nft/hooks/useNFTData';
import { NFTLifecycleProvider } from '@/core-react/nft/providers/NFTLifecycleProvider';
import { NFTProvider } from '@/core-react/nft/providers/NFTProvider';
import { LifecycleType, type NFTCardReact } from '@/core-react/nft/types';
import { type LifecycleStatusView, LifecycleType, type NFTCardReact } from '@/core-react/nft/types';
import { useCallback } from 'react';
import { useAccount } from 'wagmi';
import { border, cn, color, pressable } from '../../../styles/theme';
Expand Down Expand Up @@ -40,7 +40,8 @@ export function NFTCard({

return (
<NFTErrorBoundary fallback={NFTErrorFallback}>
<NFTLifecycleProvider
<NFTLifecycleProvider<LifecycleStatusView>
initialState={{ statusName: 'init', statusData: null }}
type={LifecycleType.VIEW}
onStatus={onStatus}
onError={onError}
Expand Down
5 changes: 3 additions & 2 deletions src/ui/react/nft/NFTMintCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useTheme } from '@/core-react/internal/hooks/useTheme';
import { useMintData as defaultUseMintData } from '@/core-react/nft/hooks/useMintData';
import { NFTLifecycleProvider } from '@/core-react/nft/providers/NFTLifecycleProvider';
import { NFTProvider } from '@/core-react/nft/providers/NFTProvider';
import { LifecycleType, type NFTMintCardReact } from '@/core-react/nft/types';
import { type LifecycleStatusMint, LifecycleType, type NFTMintCardReact } from '@/core-react/nft/types';
import { buildMintTransactionData as defaultBuildMintTransaction } from '@/core/nft/utils/buildMintTransactionData';
import { background, border, cn, color } from '../../../styles/theme';
import NFTErrorBoundary from './NFTErrorBoundary';
Expand Down Expand Up @@ -33,7 +33,8 @@ export function NFTMintCard({

return (
<NFTErrorBoundary fallback={NFTErrorFallback}>
<NFTLifecycleProvider
<NFTLifecycleProvider<LifecycleStatusMint>
initialState={{ statusName: 'init', statusData: null }}
type={LifecycleType.MINT}
onStatus={onStatus}
onError={onError}
Expand Down
Loading