Skip to content

Commit

Permalink
Save donation flow inside sessionStorage (#1533)
Browse files Browse the repository at this point in the history
* one-time-donation: Save donation steps inside sessionStorage
Saves the selected values from formik inside sessionStorage. The values are saved until either step 3 of donation has been reached, or until the browser tab has been closed.

* FormikStepper.tsx: Clear session only on successful donation
  • Loading branch information
sashko9807 authored Aug 2, 2023
1 parent 553b732 commit fe4fd8a
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 8 deletions.
17 changes: 12 additions & 5 deletions src/components/client/one-time-donation/FormikStepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ export type GenericFormProps<T> = PropsWithChildren<FormikConfig<T>>

export function FormikStepper({ children, ...props }: GenericFormProps<OneTimeDonation>) {
const childrenArray = React.Children.toArray(children) as React.ReactElement<FormikStepProps>[]
const { step, setStep } = useContext(StepsContext)
const { step, setStep, updateDonationSession, clearDonationSession } = useContext(StepsContext)
const router = useRouter()
const mobile = useMediaQuery('(max-width:568px)')
useEffect(() => {
router.query.success === 'false' || router.query.success === 'true' ? setStep(3) : null
if (router.query.success === 'true' || router.query.success === 'false') {
setStep(3)
router.query.success === 'true' && clearDonationSession()
}
}, [router.query.success])
const currentChild = childrenArray[step]
const { data: session } = useSession()
Expand All @@ -59,6 +62,7 @@ export function FormikStepper({ children, ...props }: GenericFormProps<OneTimeDo

return true
}

const { t } = useTranslation('one-time-donation')
const hideNextButton = useCallback(
(isAnonymous: boolean) => {
Expand All @@ -67,13 +71,15 @@ export function FormikStepper({ children, ...props }: GenericFormProps<OneTimeDo
}
return false
},
[step],
[step, isLogged()],
)

return (
<Formik
{...props}
validationSchema={currentChild.props.validationSchema}
onSubmit={async (values, helpers) => {
updateDonationSession(values, step + 1)
if (isLastStep()) {
values.isAnonymous = isLogged() === false ? true : values.isAnonymous ?? !isLogged()
await props.onSubmit(values, helpers)
Expand All @@ -84,7 +90,7 @@ export function FormikStepper({ children, ...props }: GenericFormProps<OneTimeDo
}}
validateOnMount
validateOnBlur>
{({ isSubmitting, handleSubmit, isValid, values: { isAnonymous } }) => (
{({ isSubmitting, handleSubmit, isValid, values }) => (
<Form
onSubmit={handleSubmit}
style={{
Expand Down Expand Up @@ -117,14 +123,15 @@ export function FormikStepper({ children, ...props }: GenericFormProps<OneTimeDo
color="error"
size="large"
onClick={() => {
updateDonationSession(values, step - 1)
setStep((s) => s - 1)
}}>
{t('btns.back')}
</Button>
</Grid>
<Grid item xs={12} md={6}>
<LoadingButton
disabled={!isValid || isSubmitting || hideNextButton(isAnonymous)}
disabled={!isValid || isSubmitting || hideNextButton(values.isAnonymous)}
fullWidth
type="submit"
variant="contained"
Expand Down
11 changes: 8 additions & 3 deletions src/components/client/one-time-donation/Steps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Fail from './steps/Fail'
import { FormikStep, FormikStepper } from './FormikStepper'
import { validateFirst, validateSecond, validateThird } from './helpers/validation-schema'
import { StepsContext } from './helpers/stepperContext'
import { useDonationStepSession } from './helpers/donateSession'

const initialValues: OneTimeDonation = {
message: '',
Expand Down Expand Up @@ -60,6 +61,8 @@ export default function DonationStepper({ onStepChange }: DonationStepperProps)
const mutation = useDonationSession()
const { data: session } = useSession()
const { data: { user: person } = { user: null } } = useCurrentPerson()
const [donateSession, { updateDonationSession, clearDonationSession }] =
useDonationStepSession(slug)
if (isLoading || !data) return <CenteredSpinner size="2rem" />
const { campaign } = data

Expand Down Expand Up @@ -152,18 +155,20 @@ export default function DonationStepper({ onStepChange }: DonationStepperProps)
validate: null,
},
]
const [step, setStep] = React.useState(0)

const [step, setStep] = React.useState(donateSession?.step ?? 0)

React.useEffect(() => {
onStepChange()
}, [step])

return (
<StepsContext.Provider value={{ step, setStep, campaign }}>
<StepsContext.Provider
value={{ step, setStep, campaign, updateDonationSession, clearDonationSession }}>
{isLoading ? (
<CircularProgress color="primary" />
) : (
<FormikStepper onSubmit={onSubmit} initialValues={initialValues}>
<FormikStepper onSubmit={onSubmit} initialValues={donateSession?.values ?? initialValues}>
{steps.map(({ label, component, validate }) => (
<FormikStep key={label} label={t(`step-labels.${label}`)} validationSchema={validate}>
{component}
Expand Down
65 changes: 65 additions & 0 deletions src/components/client/one-time-donation/helpers/donateSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { OneTimeDonation } from 'gql/donations'
import { useState } from 'react'

const PREFIX = 'donation-session-'

type DonationSession = {
values: OneTimeDonation
step: number
}

function createSessionObject(
campaignSlug: string,
values: OneTimeDonation,
step: number,
): DonationSession {
const donationSession = sessionStorage.getItem(PREFIX + campaignSlug)
//Session already exists do nothing
if (donationSession) return JSON.parse(donationSession)
const sessionObj = { values, step }
sessionStorage.setItem(PREFIX + campaignSlug, JSON.stringify(sessionObj))
return sessionObj
}

function updateSessionObject(
campaignSlug: string,
values: OneTimeDonation,
step: number,
): DonationSession {
const donationSession = sessionStorage.getItem(PREFIX + campaignSlug)
if (!donationSession) {
// if session does not exist create new one.
return createSessionObject(campaignSlug, values, step)
}

const newSessionObj = { values, step }
sessionStorage.setItem(PREFIX + campaignSlug, JSON.stringify(newSessionObj))
return newSessionObj
}

function getSessionObject(campaignSlug: string): DonationSession | null {
const donationSession = sessionStorage.getItem(PREFIX + campaignSlug)
if (!donationSession) return null
return JSON.parse(donationSession)
}

function destroySessionObject(campaignSlug: string): void {
sessionStorage.removeItem(PREFIX + campaignSlug)
}

export function useDonationStepSession(campaignSlug: string) {
const sessionObj = getSessionObject(campaignSlug)
const [donationSession, setDonationSession] = useState<DonationSession | null>(sessionObj)

const updateDonationSession = (value: OneTimeDonation, step: number) => {
const newSessionObj = updateSessionObject(campaignSlug, value, step)
setDonationSession(newSessionObj)
}

const clearDonationSession = () => {
destroySessionObject(campaignSlug)
setDonationSession(null)
}

return [donationSession, { updateDonationSession, clearDonationSession }] as const
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { createContext } from 'react'
import { CampaignResponse } from 'gql/campaigns'
import { OneTimeDonation } from 'gql/donations'

type Steps = {
step: number
setStep: React.Dispatch<React.SetStateAction<number>>
campaign: CampaignResponse
updateDonationSession: (value: OneTimeDonation, step: number) => void
clearDonationSession: () => void
}
export const StepsContext = createContext<Steps>({} as Steps)

0 comments on commit fe4fd8a

Please sign in to comment.