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

[Create Safe] add overview and CreateSafeInfo widget #1002

Merged
merged 18 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from 10 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
122 changes: 65 additions & 57 deletions src/components/create-safe/InfoWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,92 @@
import { Box, Button, Card, CardActions, CardContent, CardHeader, SvgIcon, Typography } from '@mui/material'
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
import { useState } from 'react'
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Card,
CardContent,
CardHeader,
IconButton,
SvgIcon,
Typography,
} from '@mui/material'
import type { AlertColor } from '@mui/material'
import type { ReactElement } from 'react'

import LightbulbIcon from '@/public/images/common/lightbulb.svg'

import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import css from './styles.module.css'

type Props = {
type InfoWidgetProps = {
title: string
steps: { title: string; text: string }[]
variant: AlertColor
startExpanded?: boolean
}

const InfoWidget = ({ title, steps, variant }: Props): ReactElement | null => {
const [activeStep, setActiveStep] = useState(0)
const [dismissed, setDismissed] = useState(false)

const isFirst = activeStep === 0
const isLast = activeStep === steps.length - 1

const InfoWidget = ({ title, steps, variant, startExpanded = false }: InfoWidgetProps): ReactElement | null => {
const isMultiStep = steps.length > 1

const onPrev = () => {
if (!isFirst) {
setActiveStep((prev) => prev - 1)
}
}

const onNext = () => {
if (isLast) {
setDismissed(true)
} else {
setActiveStep((prev) => prev + 1)
}
}

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

return (
<Card sx={{ backgroundColor: ({ palette }) => palette[variant]?.background }}>
<CardHeader
className={css.header}
sx={{ paddingBottom: '0px' }}
title={
<Box className={css.headerWrapper}>
<Typography
variant="caption"
className={css.title}
sx={{
backgroundColor: ({ palette }) => palette[variant]?.main,
}}
>
<SvgIcon component={LightbulbIcon} inheritViewBox fontSize="inherit" className={css.lightbulb} />
{title}
</Typography>
{isMultiStep && (
<>
<Box className={css.headerWrapper}>
<Typography
variant="caption"
className={css.title}
sx={{
backgroundColor: ({ palette }) => palette[variant]?.main,
}}
>
<SvgIcon component={LightbulbIcon} inheritViewBox fontSize="inherit" className={css.lightbulb} />
{title}
</Typography>
<Typography variant="caption" className={css.count}>
{activeStep + 1} of {steps.length}
{steps.length} tip{isMultiStep && 's'}
</Typography>
iamacook marked this conversation as resolved.
Show resolved Hide resolved
)}
</Box>
</Box>
</>
}
/>
<CardContent>
<Typography variant="h5">{steps[activeStep].title}</Typography>
<Typography variant="body2">{steps[activeStep].text}</Typography>
<CardContent sx={{ padding: '0', '&.MuiCardContent-root': { paddingBottom: '0px' } }}>
usame-algan marked this conversation as resolved.
Show resolved Hide resolved
{steps.map(({ title, text }) => {
return (
<Accordion
key={title.replace(' ', '-')}
iamacook marked this conversation as resolved.
Show resolved Hide resolved
className={css.tipAccordion}
sx={{
'&:hover > .MuiAccordionSummary-root': {
background: 'inherit',
},
'&.Mui-expanded > .MuiAccordionSummary-root': {
background: 'inherit',
fontWeight: 'bold',
},
}}
iamacook marked this conversation as resolved.
Show resolved Hide resolved
defaultExpanded={startExpanded}
>
<AccordionSummary
expandIcon={
<IconButton sx={{ '&:hover': { background: ({ palette }) => palette[variant]?.light } }}>
<ExpandMoreIcon sx={{ color: ({ palette }) => palette[variant]?.main }} />
</IconButton>
}
>
{title}
</AccordionSummary>
<AccordionDetails sx={{ paddingTop: '0px' }}>
<Typography variant="body2">{text}</Typography>
</AccordionDetails>
</Accordion>
)
})}
</CardContent>
<CardActions className={css.actions}>
{isMultiStep && !isFirst && (
<Button variant="contained" size="small" onClick={onPrev} startIcon={<ChevronLeftIcon />}>
Previous
</Button>
)}
<Button variant="outlined" size="small" onClick={onNext}>
Got it
</Button>
</CardActions>
</Card>
)
}
Expand Down
14 changes: 7 additions & 7 deletions src/components/create-safe/InfoWidget/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.header {
padding-bottom: 0px;
}

.headerWrapper {
display: flex;
justify-content: space-between;
Expand All @@ -24,7 +20,11 @@
color: var(--color-text-secondary);
}

.actions {
display: flex;
justify-content: flex-end;
.content {
padding: 0;
}

.tipAccordion {
background-color: inherit;
border: none;
}
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
iamacook marked this conversation as resolved.
Show resolved Hide resolved
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
109 changes: 86 additions & 23 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 @@ -14,6 +12,10 @@ import { useRouter } from 'next/router'
import { AppRoutes } from '@/config/routes'
import { CREATE_SAFE_CATEGORY } from '@/services/analytics'
import CreateSafeStep3 from '@/components/new-safe/steps/Step3'
import type { AlertColor } from '@mui/material'
import type { CreateSafeInfoItem } from '../CreateSafeInfos'
import CreateSafeInfos from '../CreateSafeInfos'
import { useMemo, useState } from 'react'

export type NewSafeFormData = {
name: string
Expand All @@ -22,24 +24,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.',
iamacook marked this conversation as resolved.
Show resolved Hide resolved
},
],
},
{
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.',
iamacook marked this conversation as resolved.
Show resolved Hide resolved
},
{
title: 'Safe Setup',
text: 'Not sure how many owners and confirmations you need for your Safe? Learn more about setting up your Safe.',
iamacook marked this conversation as resolved.
Show resolved Hide resolved
},
],
},
{
title: 'Review',
subtitle: `You're about to create a new Safe and will have to confirm a transaction with your currently connected wallet.`,
render: (data, onSubmit, onBack) => <CreateSafeStep3 onSubmit={onSubmit} onBack={onBack} data={data} />,
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.',
iamacook marked this conversation as resolved.
Show resolved Hide resolved
},
],
},
]
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.',
iamacook marked this conversation as resolved.
Show resolved Hide resolved
},
],
},
}

const CreateSafe = () => {
const router = useRouter()
Expand All @@ -51,6 +85,35 @@ 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',
iamacook marked this conversation as resolved.
Show resolved Hide resolved
render: (data, onSubmit, onBack) => (
<CreateSafeStep2 setDynamicHint={setDynamicHint} onSubmit={onSubmit} onBack={onBack} data={data} />
),
},
{
title: 'Review',
subtitle: `You're about to create a new Safe and will have to confirm a transaction with your currently connected wallet.`,
iamacook marked this conversation as resolved.
Show resolved Hide resolved
render: (data, onSubmit, onBack) => <CreateSafeStep3 onSubmit={onSubmit} onBack={onBack} data={data} />,
},
]

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

const initialData: NewSafeFormData = {
name: '',
mobileOwners: [] as NamedAddress[],
Expand All @@ -62,11 +125,7 @@ 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 (
<Container>
<Grid container columnSpacing={3} justifyContent="center" mt={[2, null, 7]}>
Expand All @@ -82,6 +141,7 @@ const CreateSafe = () => {
onClose={onClose}
steps={CreateSafeSteps}
eventCategory={CREATE_SAFE_CATEGORY}
updateActiveStep={setActiveStep}
/>
) : (
<Card>
Expand All @@ -95,7 +155,10 @@ const CreateSafe = () => {
</Grid>

<Grid item xs={12} md={4} mb={[3, null, 0]} order={[0, null, 1]}>
{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>
</Container>
Expand Down
Loading