Skip to content

Commit

Permalink
add overview and CreateSafeInfo widget
Browse files Browse the repository at this point in the history
- displays safe name and connected wallet during owner setup
- displays hints for each step + dynamic hints during owner and threshold setup
- state is pulled up into the CreateSafe component and update functions passed into the owner step
  • Loading branch information
schmanu committed Oct 28, 2022
1 parent 0b379c7 commit 518f016
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 40 deletions.
10 changes: 8 additions & 2 deletions src/components/create-safe/InfoWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, Button, Card, CardActions, CardContent, CardHeader, SvgIcon, Typography } from '@mui/material'
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import type { AlertColor } from '@mui/material'
import type { ReactElement } from 'react'

Expand Down Expand Up @@ -37,7 +37,13 @@ const InfoWidget = ({ title, steps, variant }: Props): ReactElement | null => {
}
}

if (dismissed) {
// Reset if steps change
useEffect(() => {
setActiveStep(0)
setDismissed(false)
}, [steps])

if (dismissed || steps.length === 0) {
return null
}

Expand Down
7 changes: 6 additions & 1 deletion src/components/new-safe/CardStepper/useCardStepper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactElement } from 'react'
import type { ReactElement, SetStateAction } from 'react'
import { useState } from 'react'
import { trackEvent, MODALS_CATEGORY } from '@/services/analytics'

Expand All @@ -25,6 +25,7 @@ export type TxStepperProps<TData> = {
initialData: TData
initialStep?: number
eventCategory?: string
updateActiveStep?: (step: number | SetStateAction<number>) => void
onClose: () => void
}

Expand All @@ -34,17 +35,20 @@ export const useCardStepper = <TData>({
initialStep,
eventCategory = MODALS_CATEGORY,
onClose,
updateActiveStep,
}: TxStepperProps<TData>) => {
const [activeStep, setActiveStep] = useState<number>(initialStep || 0)
const [stepData, setStepData] = useState(initialData)

const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1)
updateActiveStep && updateActiveStep((prevActiveStep) => prevActiveStep + 1)
trackEvent({ category: eventCategory, action: lastStep ? 'Submit' : 'Next' })
}

const handleBack = (data?: Partial<TData>) => {
setActiveStep((prevActiveStep) => prevActiveStep - 1)
updateActiveStep && updateActiveStep((prevActiveStep) => prevActiveStep - 1)
trackEvent({ category: eventCategory, action: firstStep ? 'Cancel' : 'Back' })

if (data) {
Expand All @@ -54,6 +58,7 @@ export const useCardStepper = <TData>({

const setStep = (step: number) => {
setActiveStep(step)
updateActiveStep && updateActiveStep(step)
}

const firstStep = activeStep === 0
Expand Down
100 changes: 81 additions & 19 deletions src/components/new-safe/CreateSafe/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import WalletInfo from '@/components/common/WalletInfo'
import { useCurrentChain } from '@/hooks/useChains'
import useWallet from '@/hooks/wallets/useWallet'
import OverviewWidget from '../OverviewWidget'
import type { NamedAddress } from '@/components/create-safe/types'
Expand All @@ -9,10 +7,14 @@ import useAddressBook from '@/hooks/useAddressBook'
import CreateSafeStep2 from '../steps/Step2'
import { CardStepper } from '../CardStepper'
import Grid from '@mui/material/Grid'
import type { AlertColor } from '@mui/material'
import { Card, CardContent, Typography } from '@mui/material'
import { useRouter } from 'next/router'
import { AppRoutes } from '@/config/routes'
import { CREATE_SAFE_CATEGORY } from '@/services/analytics'
import type { CreateSafeInfoItem } from '../CreateSafeInfos'
import CreateSafeInfos from '../CreateSafeInfos'
import { useMemo, useState } from 'react'

export type NewSafeFormData = {
name: string
Expand All @@ -21,19 +23,56 @@ export type NewSafeFormData = {
mobileOwners: NamedAddress[]
}

export const CreateSafeSteps: TxStepperProps<NewSafeFormData>['steps'] = [
{
title: 'Select network and name Safe',
subtitle: 'Select the network on which to create your Safe',
render: (data, onSubmit, onBack) => <CreateSafeStep1 onSubmit={onSubmit} onBack={onBack} data={data} />,
const staticHints: Record<number, { title: string; variant: AlertColor; steps: { title: string; text: string }[] }> = {
0: {
title: 'Safe Creation',
variant: 'info',
steps: [
{
title: 'Network fee',
text: 'Deploying your Safe contract requires the payment of the associated network fee with your connected wallet. An estmation will be provided in the last step.',
},
],
},
{
title: 'Owners and confirmations',
subtitle:
'Here you can add owners to your Safe and determine how many owners need to confirm before making a successful transaction',
render: (data, onSubmit, onBack) => <CreateSafeStep2 onSubmit={onSubmit} onBack={onBack} data={data} />,
1: {
title: 'Safe Creation',
variant: 'info',
steps: [
{
title: 'Flat hierarchy',
text: 'Every owner has the same rights within the Safe and can propose, sign and execute transactions.',
},
{
title: 'Managing Owners',
text: 'You can always change the amount of owners and required confirmations in your Safe at a later stage after the creation.',
},
{
title: 'Safe Setup',
text: 'Not sure how many owners and confirmations you need for your Safe? Learn more about setting up your Safe.',
},
],
},
]
2: {
title: 'Safe Creation',
variant: 'info',
steps: [
{
title: 'Wait for the creation',
text: 'Depending on the network congestion, it can take some time until the transaction is successfully included on the network and picked up by our services.',
},
],
},
3: {
title: 'Safe Usage',
variant: 'success',
steps: [
{
title: 'Connect your Safe',
text: 'In our Safe App section you can connect your Safe to over 70 dApps directly or use Wallet Connect to interact with any application.',
},
],
},
}

const CreateSafe = () => {
const router = useRouter()
Expand All @@ -45,6 +84,30 @@ const CreateSafe = () => {
address: wallet?.address || '',
}

const [safeName, setSafeName] = useState('')
const [dynamicHint, setDynamicHint] = useState<CreateSafeInfoItem>()
const [activeStep, setActiveStep] = useState(0)

const CreateSafeSteps: TxStepperProps<NewSafeFormData>['steps'] = [
{
title: 'Select network and name Safe',
subtitle: 'Select the network on which to create your Safe',
render: (data, onSubmit, onBack) => (
<CreateSafeStep1 setSafeName={setSafeName} onSubmit={onSubmit} onBack={onBack} data={data} />
),
},
{
title: 'Owners and confirmations',
subtitle:
'Here you can add owners to your Safe and determine how many owners need to confirm before making a successful transaction',
render: (data, onSubmit, onBack) => (
<CreateSafeStep2 setDynamicHint={setDynamicHint} onSubmit={onSubmit} onBack={onBack} data={data} />
),
},
]

const staticHint = useMemo(() => staticHints[activeStep], [activeStep])

const initialData: NewSafeFormData = {
name: '',
mobileOwners: [] as NamedAddress[],
Expand All @@ -56,11 +119,6 @@ const CreateSafe = () => {
router.push(AppRoutes.welcome)
}

const chain = useCurrentChain()
const rows = [
...(wallet && chain ? [{ title: 'Wallet', component: <WalletInfo wallet={wallet} chain={chain} /> }] : []),
]

// TODO: Improve layout when other widget/responsive design is ready
return (
<Grid container spacing={3}>
Expand All @@ -79,6 +137,7 @@ const CreateSafe = () => {
onClose={onClose}
steps={CreateSafeSteps}
eventCategory={CREATE_SAFE_CATEGORY}
updateActiveStep={setActiveStep}
/>
) : (
<Card>
Expand All @@ -91,7 +150,10 @@ const CreateSafe = () => {
)}
</Grid>
<Grid item xs={12} md={4}>
{wallet?.address && <OverviewWidget rows={rows} />}
<Grid container spacing={3}>
{wallet?.address && activeStep < 2 && <OverviewWidget safeName={safeName} />}
{wallet?.address && <CreateSafeInfos staticHint={staticHint} dynamicHint={dynamicHint} />}
</Grid>
</Grid>
<Grid item xs={1} />
</Grid>
Expand Down
34 changes: 34 additions & 0 deletions src/components/new-safe/CreateSafeInfos/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import InfoWidget from '@/components/create-safe/InfoWidget'
import { Grid } from '@mui/material'
import type { AlertColor } from '@mui/material'

export type CreateSafeInfoItem = {
title: string
variant: AlertColor
steps: { title: string; text: string }[]
}

const CreateSafeInfos = ({
staticHint,
dynamicHint,
}: {
staticHint: CreateSafeInfoItem
dynamicHint?: CreateSafeInfoItem
}) => {
return (
<Grid item>
<Grid container direction="column" gap={3}>
<Grid item>
<InfoWidget title={staticHint.title} variant={staticHint.variant} steps={staticHint.steps} />
</Grid>
{dynamicHint && (
<Grid item>
<InfoWidget title={dynamicHint.title} variant={dynamicHint.variant} steps={dynamicHint.steps} />
</Grid>
)}
</Grid>
</Grid>
)
}

export default CreateSafeInfos
38 changes: 25 additions & 13 deletions src/components/new-safe/OverviewWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import { Card, Typography } from '@mui/material'
import WalletInfo from '@/components/common/WalletInfo'
import { useCurrentChain } from '@/hooks/useChains'
import useWallet from '@/hooks/wallets/useWallet'
import { Card, Grid, Typography } from '@mui/material'
import type { ReactElement } from 'react'

import css from './styles.module.css'

const LOGO_DIMENSIONS = '22px'

const OverviewWidget = ({ rows }: { rows?: { title: string; component: ReactElement }[] }): ReactElement => {
const OverviewWidget = ({ safeName }: { safeName: string }): ReactElement | null => {
const wallet = useWallet()
const chain = useCurrentChain()
const rows = [
...(wallet && chain ? [{ title: 'Wallet', component: <WalletInfo wallet={wallet} chain={chain} /> }] : []),
...(safeName !== '' ? [{ title: 'Name', component: <Typography>{safeName}</Typography> }] : []),
]

return (
<Card className={css.card}>
<div className={css.header}>
<img src="/images/logo-no-text.svg" alt="Safe logo" width={LOGO_DIMENSIONS} />
<Typography variant="h4">Your Safe preview</Typography>
</div>
{rows?.map((row) => (
<div key={row.title} className={css.row}>
<Typography variant="body2">{row.title}</Typography>
{row.component}
<Grid item xs={12}>
<Card className={css.card}>
<div className={css.header}>
<img src="/images/logo-no-text.svg" alt="Safe logo" width={LOGO_DIMENSIONS} />
<Typography variant="h4">Your Safe preview</Typography>
</div>
))}
</Card>
{rows?.map((row) => (
<div key={row.title} className={css.row}>
<Typography variant="body2">{row.title}</Typography>
{row.component}
</div>
))}
</Card>
</Grid>
)
}

Expand Down
10 changes: 8 additions & 2 deletions src/components/new-safe/steps/Step1/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ function CreateSafeStep1({
data,
onSubmit,
onBack,
}: Pick<StepRenderProps<NewSafeFormData>, 'onSubmit' | 'data' | 'onBack'>) {
setSafeName,
}: Pick<StepRenderProps<NewSafeFormData>, 'onSubmit' | 'data' | 'onBack'> & { setSafeName: (name: string) => void }) {
const fallbackName = useMnemonicSafeName()

const {
Expand All @@ -48,8 +49,13 @@ function CreateSafeStep1({
},
})

const onFormSubmit = (data: Partial<NewSafeFormData>) => {
setSafeName(data.name ?? '')
onSubmit(data)
}

return (
<form onSubmit={handleSubmit(onSubmit)} id={STEP_1_FORM_ID} className={css.form}>
<form onSubmit={handleSubmit(onFormSubmit)} id={STEP_1_FORM_ID} className={css.form}>
<Grid container spacing={3}>
<Grid item xs={6}>
<Box className={css.select}>
Expand Down
12 changes: 9 additions & 3 deletions src/components/new-safe/steps/Step2/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { OwnerRow } from './OwnerRow'
import type { NamedAddress } from '@/components/create-safe/types'
import type { StepRenderProps } from '../../CardStepper/useCardStepper'
import type { NewSafeFormData } from '../../CreateSafe'
import type { CreateSafeInfoItem } from '../../CreateSafeInfos'
import { useSafeSetupHints } from './useSafeSetupHints'

type CreateSafeStep2Form = {
export type CreateSafeStep2Form = {
owners: NamedAddress[]
mobileOwners: NamedAddress[]
threshold: number
Expand All @@ -27,7 +29,10 @@ const CreateSafeStep2 = ({
onSubmit,
onBack,
data,
}: Pick<StepRenderProps<NewSafeFormData>, 'onSubmit' | 'data' | 'onBack'>): ReactElement => {
setDynamicHint,
}: Pick<StepRenderProps<NewSafeFormData>, 'onSubmit' | 'data' | 'onBack'> & {
setDynamicHint: (hints: CreateSafeInfoItem | undefined) => void
}): ReactElement => {
const formMethods = useForm<CreateSafeStep2Form>({
mode: 'all',
defaultValues: {
Expand All @@ -40,7 +45,6 @@ const CreateSafeStep2 = ({
const { register, handleSubmit, control, watch } = formMethods

const allFormData = watch()
const currentThreshold = watch(CreateSafeStep2Fields.threshold)

const { fields: ownerFields, append: appendOwner, remove: removeOwner } = useFieldArray({ control, name: 'owners' })

Expand All @@ -52,6 +56,8 @@ const CreateSafeStep2 = ({

const allOwners = [...ownerFields, ...mobileOwnerFields]

useSafeSetupHints(allFormData.threshold, allOwners.length, setDynamicHint)

const handleBack = () => {
onBack(allFormData)
}
Expand Down
Loading

0 comments on commit 518f016

Please sign in to comment.