import { Box, Button, Flex, FormControl, FormErrorMessage, MetaHeading, Spinner, StatusedSubmitButton, Text, useToast, Wrap, WrapItem, } from '@metafam/ds'; import { Maybe, Optional } from '@metafam/utils'; import { ConnectToProgress } from 'components/ConnectToProgress'; import { FlexContainer } from 'components/Container'; import { HeadComponent } from 'components/Seo'; import { useSetupFlow } from 'contexts/SetupContext'; import { CeramicError, useWeb3 } from 'lib/hooks'; import { PropsWithChildren, ReactElement, useCallback, useEffect, useState, } from 'react'; import { Control, useForm, UseFormRegisterReturn } from 'react-hook-form'; export type MaybeModalProps = { buttonLabel?: string | ReactElement; onClose?: () => void; }; export type WizardPaneProps = { field: string; title?: string | ReactElement; prompt?: string | ReactElement; buttonLabel?: string | ReactElement; onClose?: () => void; }; export type PaneProps = WizardPaneProps & { value: Optional>; fetching?: boolean; authenticating?: boolean; onSave?: ({ values, setStatus, }: { values: Record; setStatus?: (msg: string) => void; }) => Promise; }; export type WizardPaneCallbackProps = { register: ( field: string, opts: Record, ) => UseFormRegisterReturn; control: Control; loading: boolean; errored: boolean; dirty: boolean; current: T; setter: (arg: T | ((prev: Optional>) => Maybe)) => void; }; export const WizardPane = ({ field, title, prompt, buttonLabel, onClose, onSave, value: existing, fetching = false, children, }: PropsWithChildren>) => { const { onNextPress, nextButtonLabel } = useSetupFlow(); const [status, setStatus] = useState>(); const { register, control, handleSubmit, setValue, watch, formState: { errors, isValidating: validating, dirtyFields }, } = useForm(); const current = watch(field, existing); const dirty = current !== existing || dirtyFields[field]; const { connecting, connected, chainId } = useWeb3(); const toast = useToast(); useEffect(() => { setValue(field, existing); }, [existing, field, setValue]); const onSubmit = useCallback( async (values) => { try { if (!dirty) { setStatus('No Change. Skipping Save…'); await new Promise((resolve) => { setTimeout(resolve, 10); }); } else if (onSave) { setStatus('Saving…'); await onSave({ values, setStatus }); } (onClose ?? onNextPress).call(this); } catch (err) { const heading = err instanceof CeramicError ? 'Ceramic Error' : 'Error'; toast({ title: heading, description: (err as Error).message, status: 'error', isClosable: true, duration: 12000, }); setStatus(null); } }, [dirty, onClose, onNextPress, onSave, toast], ); const setter = useCallback( (val: unknown) => { let next = val; if (val instanceof Function) { next = val(current); } setValue(field, next); }, [current, field, setValue], ); if ((!connecting && !connected) || chainId !== '0x1') { return ; } return ( {title && {title}} {prompt && ( {typeof prompt === 'string' ? ( {prompt} ) : ( prompt )} )} {(!connected || fetching || validating) && ( {(() => { if (!connected) return 'Authenticating…'; if (validating) return 'Validating…'; return 'Loading Current Value…'; })()} )} {typeof children === 'function' ? children.call(null, { register, control, loading: !connected || fetching, errored: !!errors[field], dirty, current, setter, }) : children} {errors[field]?.message} {onClose && ( )} ); };