refactored setup flow (#129)

* refactored setup flow

* next button labels
This commit is contained in:
dan13ram
2020-10-02 21:50:52 +05:30
committed by GitHub
parent d5844dd8db
commit 1c6f899dbb
13 changed files with 276 additions and 175 deletions

View File

@@ -6,7 +6,7 @@ export const MetaButton: React.FC<ButtonProps> = ({ children, ...props }) => (
colorScheme="purple"
textTransform="uppercase"
px={12}
letterSpacing="0.1rem"
letterSpacing="0.1em"
size="lg"
fontSize="sm"
{...props}

View File

@@ -0,0 +1,18 @@
import { MetaButton, MetaHeading } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { SetupContext } from 'contexts/SetupContext';
import React, { useContext } from 'react';
export const SetupAvailability: React.FC = () => {
const { onNextPress, nextButtonLabel } = useContext(SetupContext);
return (
<FlexContainer>
<MetaHeading mb={10} textAlign="center">
Availability
</MetaHeading>
<MetaButton onClick={onNextPress} mt={10}>
{nextButtonLabel}
</MetaButton>
</FlexContainer>
);
};

View File

@@ -8,7 +8,9 @@ export const SetupDone: React.FC = () => {
return (
<FlexContainer flex={1}>
<MetaHeading mb={10}>Game on!</MetaHeading>
<MetaButton onClick={() => router.push('/')}>Play</MetaButton>
<MetaButton onClick={() => router.push('/')} px={20} py={8} fontSize="xl">
Play
</MetaButton>
</FlexContainer>
);
};

View File

@@ -5,54 +5,25 @@ import AvatarImage from 'public/images/avatar.png';
import BackImage from 'public/images/Back.svg';
import SkipImage from 'public/images/Skip.svg';
import React, { useContext } from 'react';
import { options } from 'utils/setupOptions';
export const SetupHeader: React.FC = () => {
const { step, progress, setStep, numTotalSteps } = useContext(SetupContext);
const { step, screen, onNextPress, onBackPress } = useContext(SetupContext);
return (
<Grid templateColumns="0.5fr 1fr 1fr 1fr 0.5fr" gap="1rem" w="100%">
<FlexContainer
justify="flex-end"
onClick={() => {
if (step > 0) {
setStep((_step) => (_step - 1) % numTotalSteps);
}
}}
cursor="pointer"
>
<FlexContainer justify="flex-end" onClick={onBackPress} cursor="pointer">
<Image src={BackImage} h="1rem" />
</FlexContainer>
<StepProgress
title={['About You', '1. About You']}
isActive={step === 0}
isDone={step > 0}
progress={step > 0 ? 1 : progress}
/>
<StepProgress
title={[
'Portfolio',
'2. Portfolio',
'2. Portfolio',
'2. Professional Profile',
]}
isActive={step === 1}
isDone={step > 1}
progress={step > 1 ? 1 : progress}
/>
<StepProgress
title={['Play', '3. Play', '3. Start Playing']}
isDone={false}
isActive={step === 2}
progress={progress}
/>
<FlexContainer
justify="flex-end"
onClick={() => {
if ((step + 1) % numTotalSteps !== 0) {
setStep((_step) => (_step + 1) % numTotalSteps);
}
}}
cursor="pointer"
>
{options.map((option, id) => (
<StepProgress
title={option.title}
step={id}
isActive={step === id}
isDone={step > id}
screen={screen}
/>
))}
<FlexContainer justify="flex-end" onClick={onNextPress} cursor="pointer">
<Image src={SkipImage} h="1rem" />
</FlexContainer>
</Grid>
@@ -60,51 +31,58 @@ export const SetupHeader: React.FC = () => {
};
interface StepProps {
title: any[] | Record<string, any>;
title: { [any: string]: string | undefined };
isDone: boolean;
isActive: boolean;
progress: number;
step: number;
screen: number;
}
export const StepProgress: React.FC<StepProps> = ({
title,
isDone,
isActive,
progress,
}) => (
<FlexContainer pos="relative">
<ResponsiveText
w="100%"
textTransform="uppercase"
fontSize="xs"
fontFamily="mono"
fontWeight="bold"
color="offwhite"
opacity={isActive ? 1 : 0.4}
mb={4}
content={title}
/>
<Flex
bgColor="blue20"
w="100%"
h="0.5rem"
borderRadius="0.25rem"
overflow="hidden"
>
{(isActive || isDone) && (
<Box bgColor="purple.400" w={`${progress * 100}%`} />
)}
</Flex>
{isActive && (
<Image
mt={4}
pos="absolute"
w="1.5rem"
top="100%"
src={AvatarImage}
left={`${progress * 100}%`}
transform="translateX(-50%)"
step,
screen,
}) => {
const progress = isDone
? 100
: Math.floor(((screen + 1) * 100.0) / options[step].screens.length);
return (
<FlexContainer pos="relative">
<ResponsiveText
w="100%"
textTransform="uppercase"
fontSize="xs"
fontFamily="mono"
fontWeight="bold"
color="offwhite"
opacity={isActive ? 1 : 0.4}
mb={4}
content={title}
/>
)}
</FlexContainer>
);
<Flex
bgColor="blue20"
w="100%"
h="0.5rem"
borderRadius="0.25rem"
overflow="hidden"
>
{(isActive || isDone) && (
<Box bgColor="purple.400" w={`${progress}%`} />
)}
</Flex>
{isActive && (
<Image
mt={4}
pos="absolute"
w="1.5rem"
top="100%"
src={AvatarImage}
left={`${progress}%`}
transform="translateX(-50%)"
/>
)}
</FlexContainer>
);
};

View File

@@ -0,0 +1,18 @@
import { MetaButton, MetaHeading } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { SetupContext } from 'contexts/SetupContext';
import React, { useContext } from 'react';
export const SetupMemberships: React.FC = () => {
const { onNextPress, nextButtonLabel } = useContext(SetupContext);
return (
<FlexContainer>
<MetaHeading mb={10} textAlign="center">
Memberships
</MetaHeading>
<MetaButton onClick={onNextPress} mt={10}>
{nextButtonLabel}
</MetaButton>
</FlexContainer>
);
};

View File

@@ -1,19 +0,0 @@
import { MetaButton, MetaHeading } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { SetupContext } from 'contexts/SetupContext';
import React, { useContext } from 'react';
export const SetupPersonality: React.FC = () => {
const { useProgress } = useContext(SetupContext);
const numProgressSteps = 2;
const [currentProgress, onNextPress] = useProgress(numProgressSteps);
return (
<FlexContainer flex={1}>
{currentProgress === 0 && (
<MetaHeading mb={10}>Personality type</MetaHeading>
)}
{currentProgress === 1 && <MetaHeading mb={10}>Player type</MetaHeading>}
<MetaButton onClick={onNextPress}>Next Step</MetaButton>
</FlexContainer>
);
};

View File

@@ -0,0 +1,18 @@
import { MetaButton, MetaHeading } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { SetupContext } from 'contexts/SetupContext';
import React, { useContext } from 'react';
export const SetupPersonalityType: React.FC = () => {
const { onNextPress, nextButtonLabel } = useContext(SetupContext);
return (
<FlexContainer>
<MetaHeading mb={10} textAlign="center">
Personality Type
</MetaHeading>
<MetaButton onClick={onNextPress} mt={10}>
{nextButtonLabel}
</MetaButton>
</FlexContainer>
);
};

View File

@@ -0,0 +1,18 @@
import { MetaButton, MetaHeading } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { SetupContext } from 'contexts/SetupContext';
import React, { useContext } from 'react';
export const SetupPlayerType: React.FC = () => {
const { onNextPress, nextButtonLabel } = useContext(SetupContext);
return (
<FlexContainer>
<MetaHeading mb={10} textAlign="center">
Player Type
</MetaHeading>
<MetaButton onClick={onNextPress} mt={10}>
{nextButtonLabel}
</MetaButton>
</FlexContainer>
);
};

View File

@@ -1,21 +0,0 @@
import { MetaButton, MetaHeading } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { SetupContext } from 'contexts/SetupContext';
import React, { useContext } from 'react';
import { SetupSkills } from './SetupSkills';
export const SetupProfession: React.FC = () => {
const { useProgress } = useContext(SetupContext);
const numProgressSteps = 3;
const [currentProgress, onNextPress] = useProgress(numProgressSteps);
return (
<FlexContainer flex={1}>
{currentProgress === 0 && <SetupSkills />}
{currentProgress === 1 && <MetaHeading mb={10}>Availability</MetaHeading>}
{currentProgress === 2 && <MetaHeading mb={10}>Memberships</MetaHeading>}
<MetaButton onClick={onNextPress}>Next Step</MetaButton>
</FlexContainer>
);
};

View File

@@ -1,22 +1,35 @@
import { MetaHeading, SelectSearch } from '@metafam/ds';
import { MetaButton, MetaHeading, SelectSearch } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { SetupContext } from 'contexts/SetupContext';
import React, { useContext } from 'react';
import { SkillOption } from 'utils/skillHelpers';
export const SetupSkills: React.FC = () => {
const { skillsList, skills, setSkills } = useContext(SetupContext);
const {
skillsList,
skills,
setSkills,
onNextPress,
nextButtonLabel,
} = useContext(SetupContext);
return (
<FlexContainer mb={10} align="stretch">
<MetaHeading mb={10}>What are your superpowers?</MetaHeading>
<SelectSearch
isMulti
value={skills}
onChange={(value) => setSkills(value as Array<SkillOption>)}
options={skillsList}
placeholder="ADD YOUR SKILLS"
/>
<FlexContainer>
<MetaHeading mb={10} textAlign="center">
What are your superpowers?
</MetaHeading>
<FlexContainer w="100%" align="stretch">
<SelectSearch
isMulti
value={skills}
onChange={(value) => setSkills(value as Array<SkillOption>)}
options={skillsList}
placeholder="ADD YOUR SKILLS"
/>
</FlexContainer>
<MetaButton onClick={onNextPress} mt={10}>
{nextButtonLabel}
</MetaButton>
</FlexContainer>
);
};

View File

@@ -1,12 +1,13 @@
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { options } from 'utils/setupOptions';
import { CategoryOption, SkillOption } from 'utils/skillHelpers';
type SetupContextType = {
useProgress: (numProgressSteps: number) => [number, () => void];
step: number;
progress: number;
setStep: React.Dispatch<React.SetStateAction<number>>;
setProgress: React.Dispatch<React.SetStateAction<number>>;
screen: number;
onNextPress: () => void;
onBackPress: () => void;
nextButtonLabel: string;
numTotalSteps: number;
skills: Array<SkillOption>;
setSkills: React.Dispatch<React.SetStateAction<Array<SkillOption>>>;
@@ -14,11 +15,11 @@ type SetupContextType = {
};
export const SetupContext = React.createContext<SetupContextType>({
useProgress: (numProgressSteps: number) => [numProgressSteps, () => {}],
step: 0,
progress: 0,
setStep: () => undefined,
setProgress: () => undefined,
screen: 0,
onNextPress: () => undefined,
onBackPress: () => undefined,
nextButtonLabel: 'Next Step',
numTotalSteps: 0,
skills: [],
setSkills: () => undefined,
@@ -34,41 +35,58 @@ export const SetupContextProvider: React.FC<Props> = ({
skillsList,
}) => {
const [step, setStep] = useState<number>(0);
const [progress, setProgress] = useState<number>(0.5);
const numTotalSteps = 3;
const [screen, setScreen] = useState<number>(0);
const numTotalSteps = options.length;
const [nextButtonLabel, setNextButtonLabel] = useState('Next Step');
const useProgress: SetupContextType['useProgress'] = (numProgressSteps) => {
const [currentProgress, setCurrentProgress] = useState<number>(0);
useEffect(() => {
const numScreens = options[step].screens.length;
if (step >= numTotalSteps - 1) {
setNextButtonLabel(options[(step + 1) % numTotalSteps].label);
}
if (screen + 1 >= numScreens) {
setNextButtonLabel(`Next: ${options[(step + 1) % numTotalSteps].label}`);
} else {
setNextButtonLabel(
`Next: ${options[step].screens[(screen + 1) % numScreens].label}`,
);
}
}, [step, screen, setNextButtonLabel, numTotalSteps]);
useEffect(() => {
const progressValue = (currentProgress + 1) / numProgressSteps;
setProgress(progressValue);
}, [currentProgress, numProgressSteps]);
const onNextPress = useCallback(() => {
const numScreens = options[step].screens.length;
if (step >= numTotalSteps - 1 && screen >= numScreens - 1) return;
if (screen + 1 >= numScreens) {
setStep((step + 1) % numTotalSteps);
setScreen(0);
} else {
setScreen((screen + 1) % numScreens);
}
}, [step, screen, setStep, setScreen, numTotalSteps]);
const onNextPress = () => {
if ((currentProgress + 1) % numProgressSteps === 0) {
setStep((_step) => (_step + 1) % numTotalSteps);
} else {
setCurrentProgress(
(_currentProgress) => (_currentProgress + 1) % numProgressSteps,
);
}
};
return [currentProgress, onNextPress];
};
const onBackPress = useCallback(() => {
if (step <= 0 && screen <= 0) return;
const numScreens = options[step].screens.length;
if (screen <= 0) {
setStep((step - 1) % numTotalSteps);
setScreen(options[(step - 1) % numTotalSteps].screens.length - 1);
} else {
setScreen((screen - 1) % numScreens);
}
}, [step, screen, setStep, setScreen, numTotalSteps]);
const [skills, setSkills] = useState<Array<SkillOption>>([]);
return (
<SetupContext.Provider
value={{
useProgress,
step,
progress,
setStep,
setProgress,
screen,
numTotalSteps,
onNextPress,
onBackPress,
nextButtonLabel,
// data
skills,
setSkills,
skillsList,

View File

@@ -1,23 +1,21 @@
import { PageContainer } from 'components/Container';
import { SetupDone } from 'components/Setup/SetupDone';
import { FlexContainer, PageContainer } from 'components/Container';
import { SetupHeader } from 'components/Setup/SetupHeader';
import { SetupPersonality } from 'components/Setup/SetupPersonality';
import { SetupProfession } from 'components/Setup/SetupProfession';
import { SetupContext, SetupContextProvider } from 'contexts/SetupContext';
import { getSkills } from 'graphql/getSkills';
import { InferGetStaticPropsType } from 'next';
import BackgroundImage from 'public/images/profile-background.jpg';
import React, { useContext } from 'react';
import { options } from 'utils/setupOptions';
import { parseSkills } from 'utils/skillHelpers';
const ProfileSetup: React.FC = () => {
const { step, numTotalSteps } = useContext(SetupContext);
const { step, screen, numTotalSteps } = useContext(SetupContext);
return (
<PageContainer backgroundImage={`url(${BackgroundImage})`}>
{(step + 1) % numTotalSteps !== 0 && <SetupHeader />}
{step === 0 && <SetupPersonality />}
{step === 1 && <SetupProfession />}
{step === 2 && <SetupDone />}
<FlexContainer flex={1}>
{options[step].screens[screen].component}
</FlexContainer>
</PageContainer>
);
};

View File

@@ -0,0 +1,60 @@
import { SetupAvailability } from 'components/Setup/SetupAvailability';
import { SetupDone } from 'components/Setup/SetupDone';
import { SetupMemberships } from 'components/Setup/SetupMemberships';
import { SetupPersonalityType } from 'components/Setup/SetupPersonalityType';
import { SetupPlayerType } from 'components/Setup/SetupPlayerType';
import { SetupSkills } from 'components/Setup/SetupSkills';
import React from 'react';
export const options = [
{
label: 'Personality',
title: { base: 'About You', sm: '1. About You' },
screens: [
{
label: 'Personality Type',
component: <SetupPersonalityType />,
},
{
label: 'Player Type',
component: <SetupPlayerType />,
},
],
},
{
label: 'Portfolio',
title: {
base: 'Portfolio',
sm: '2. Portfolio',
lg: '2. Professional Profile',
},
screens: [
{
label: 'Skills',
component: <SetupSkills />,
},
{
label: 'Availability',
component: <SetupAvailability />,
},
{
label: 'Memberships',
component: <SetupMemberships />,
},
],
},
{
label: 'Start Playing',
title: {
base: 'Play',
sm: '3. Play',
md: '3. Start Playing',
},
screens: [
{
label: 'Done',
component: <SetupDone />,
},
],
},
];