profile setup flow clean up (#167)

* fixed update token on address change

* fetching already set profile data in setup flow

* select skills colors

* showing more data on player tile

* rename variables
This commit is contained in:
dan13ram
2020-10-23 22:33:31 +05:30
committed by GitHub
parent 424359993c
commit bf5fa29f8a
17 changed files with 373 additions and 204 deletions

View File

@@ -44,7 +44,7 @@ export const PlayerFeatures: React.FC<Props> = ({ player }) => {
? `${player.availability_hours} hr / week`
: 'N/A'
}
color="whiteAlpha.500"
color={player.availability_hours ? undefined : 'whiteAlpha.500'}
/>
</Wrap>
</Container>

View File

@@ -0,0 +1,122 @@
import {
Avatar,
Heading,
HStack,
MetaTag,
Text,
VStack,
Wrap,
} from '@metafam/ds';
import { MetaLink } from 'components/Link';
import { PlayerContacts } from 'components/Player/PlayerContacts';
import { PlayerFragmentFragment } from 'graphql/autogen/types';
import { SkillColors } from 'graphql/types';
import React from 'react';
import { getPlayerImage, getPlayerName } from 'utils/playerHelpers';
type Props = {
player: PlayerFragmentFragment;
};
const SHOW_MEMBERSHIPS = 4;
const SHOW_SKILLS = 4;
export const PlayerTile: React.FC<Props> = ({ player }) => (
<VStack
key={player.id}
bg="whiteAlpha.200"
style={{ backdropFilter: 'blur(7px)' }}
rounded="lg"
py="6"
px="4"
spacing="6"
w="100%"
>
<MetaLink
as={`/player/${player.username}`}
href="player/[username]"
key={player.id}
>
<VStack>
<Avatar
size="xl"
src={getPlayerImage(player)}
name={getPlayerName(player)}
/>
<Heading size="xs">{getPlayerName(player)}</Heading>
</VStack>
</MetaLink>
<Wrap>
{player.playerType?.title ? (
<MetaTag size="md">{player.playerType?.title.toUpperCase()}</MetaTag>
) : null}
{player.rank && (
<MetaTag
backgroundColor={player.rank?.toLowerCase()}
size="md"
color="blackAlpha.600"
>
{player.rank}
</MetaTag>
)}
<MetaTag size="md">XP: {Math.floor(player.totalXp)}</MetaTag>
</Wrap>
{player.Player_Skills.length ? (
<VStack spacing={2}>
<Text fontFamily="mono" fontSize="sm" color="blueLight">
SKILLS
</Text>
<Wrap justify="center">
{player.Player_Skills.slice(0, SHOW_SKILLS).map(({ Skill }) => (
<MetaTag
key={Skill.id}
size="md"
fontWeight="normal"
backgroundColor={SkillColors[Skill.category]}
>
{Skill.name}
</MetaTag>
))}
{player.Player_Skills.length > SHOW_SKILLS && (
<MetaTag size="md" fontWeight="normal">
{`+${player.Player_Skills.length - SHOW_SKILLS}`}
</MetaTag>
)}
</Wrap>
</VStack>
) : null}
{player.daohausMemberships.length ? (
<VStack spacing={2}>
<Text fontFamily="mono" fontSize="sm" color="blueLight">
MEMBER OF
</Text>
<Wrap justify="center">
{player.daohausMemberships
.slice(0, SHOW_MEMBERSHIPS)
.map((member) => (
<MetaTag key={member.id} size="md" fontWeight="normal">
{member.moloch.title}
</MetaTag>
))}
{player.daohausMemberships.length > SHOW_MEMBERSHIPS && (
<MetaTag size="md" fontWeight="normal">
{`+${player.daohausMemberships.length - SHOW_MEMBERSHIPS}`}
</MetaTag>
)}
</Wrap>
</VStack>
) : null}
{player.Accounts.length ? (
<VStack spacing={2}>
<Text fontFamily="mono" fontSize="sm" color="blueLight">
CONTACT
</Text>
<HStack mt="2">
<PlayerContacts player={player} />
</HStack>
</VStack>
) : null}
</VStack>
);

View File

@@ -1,18 +1,7 @@
import {
Avatar,
Heading,
HStack,
MetaTag,
SimpleGrid,
Text,
VStack,
Wrap,
} from '@metafam/ds';
import { MetaLink } from 'components/Link';
import { PlayerContacts } from 'components/Player/PlayerContacts';
import { SimpleGrid } from '@metafam/ds';
import { PlayerTile } from 'components/Player/PlayerTile';
import { PlayerFragmentFragment } from 'graphql/autogen/types';
import React from 'react';
import { getPlayerImage, getPlayerName } from 'utils/playerHelpers';
type Props = {
players: PlayerFragmentFragment[];
@@ -21,62 +10,7 @@ type Props = {
export const PlayerList: React.FC<Props> = ({ players }) => (
<SimpleGrid columns={[1, null, 2, 3]} spacing="8">
{players.map((p) => (
<VStack
key={p.id}
bg="whiteAlpha.200"
style={{ backdropFilter: 'blur(7px)' }}
rounded="lg"
py="6"
px="4"
spacing="6"
>
<MetaLink
as={`/player/${p.username}`}
href="player/[username]"
key={p.id}
>
<VStack>
<Avatar size="xl" src={getPlayerImage(p)} name={getPlayerName(p)} />
<Heading size="xs">{getPlayerName(p)}</Heading>
</VStack>
</MetaLink>
<Wrap>
<MetaTag
backgroundColor={p.rank?.toLowerCase()}
size="md"
color="blackAlpha.600"
>
{p.rank}
</MetaTag>
<MetaTag size="md">XP: {Math.floor(p.totalXp)}</MetaTag>
</Wrap>
{p.daohausMemberships.length ? (
<VStack spacing={2}>
<Text fontFamily="mono" fontSize="sm" color="blueLight">
MEMBER OF
</Text>
<Wrap justify="center">
{p.daohausMemberships.map((member) => (
<MetaTag key={member.id} size="md" fontWeight="normal">
{member.moloch.title}
</MetaTag>
))}
</Wrap>
</VStack>
) : null}
{p.Accounts.length ? (
<VStack spacing={2}>
<Text fontFamily="mono" fontSize="sm" color="blueLight">
CONTACT
</Text>
<HStack mt="2">
<PlayerContacts player={p} />
</HStack>
</VStack>
) : null}
</VStack>
<PlayerTile key={p.id} player={p} />
))}
</SimpleGrid>
);

View File

@@ -10,11 +10,10 @@ import {
} from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { useSetupFlow } from 'contexts/SetupContext';
import { useUpdatePlayerSkillsMutation } from 'graphql/autogen/types';
import { useUser } from 'lib/hooks';
import React, { useEffect, useState } from 'react';
import { useUpdatePlayerSkillsMutation } from '../../graphql/autogen/types';
import { useUser } from '../../lib/hooks';
export const SetupAvailability: React.FC = () => {
const {
onNextPress,

View File

@@ -1,16 +1,33 @@
import { MetaButton, MetaHeading } from '@metafam/ds';
import { MetaButton, MetaHeading, Stack } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { PlayerTile } from 'components/Player/PlayerTile';
import { useUser } from 'lib/hooks';
import { useRouter } from 'next/router';
import React from 'react';
export const SetupDone: React.FC = () => {
const router = useRouter();
const { user } = useUser({ redirectTo: '/' });
return (
<FlexContainer flex={1}>
<MetaHeading mb={10}>Game on!</MetaHeading>
<MetaButton onClick={() => router.push('/')} px={20} py={8} fontSize="xl">
Play
</MetaButton>
<Stack
spacing={8}
direction={{ base: 'column', md: 'row' }}
justify="center"
align="center"
>
{user?.player && <PlayerTile player={user.player} />}
<MetaButton
onClick={() => router.push('/')}
px={20}
py={8}
fontSize="xl"
>
Play
</MetaButton>
</Stack>
</FlexContainer>
);
};

View File

@@ -7,11 +7,10 @@ import {
} from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { useSetupFlow } from 'contexts/SetupContext';
import { useUpdateAboutYouMutation } from 'graphql/autogen/types';
import { useUser } from 'lib/hooks';
import React from 'react';
import { useUpdateAboutYouMutation } from '../../graphql/autogen/types';
import { useUser } from '../../lib/hooks';
export const SetupPlayerType: React.FC = () => {
const {
onNextPress,
@@ -29,23 +28,28 @@ export const SetupPlayerType: React.FC = () => {
const handleNextPress = async () => {
if (!user) return;
const { error } = await updateAboutYou({
playerId: user.id,
input: {
enneagram: personalityType?.name,
playerTypeId: playerType?.id,
},
});
if (error) {
console.warn(error);
toast({
title: 'Error',
description: 'Unable to update Player Account. The octo is sad 😢',
status: 'error',
isClosable: true,
if (
user.player?.EnneagramType?.name !== personalityType?.name ||
user.player?.playerType?.id !== playerType?.id
) {
const { error } = await updateAboutYou({
playerId: user.id,
input: {
enneagram: personalityType?.name,
playerTypeId: playerType?.id,
},
});
return;
if (error) {
console.warn(error);
toast({
title: 'Error',
description: 'Unable to update Player Account. The octo is sad 😢',
status: 'error',
isClosable: true,
});
return;
}
}
onNextPress();

View File

@@ -2,10 +2,45 @@ import BackgroundImage from 'assets/profile-background.jpg';
import { FlexContainer, PageContainer } from 'components/Container';
import { SetupHeader } from 'components/Setup/SetupHeader';
import { useSetupFlow } from 'contexts/SetupContext';
import React from 'react';
import { PersonalityTypes } from 'graphql/types';
import { useUser } from 'lib/hooks';
import React, { useEffect } from 'react';
export const SetupProfile: React.FC = () => {
const { step, screen, numTotalSteps, options } = useSetupFlow();
const {
step,
screen,
numTotalSteps,
options,
setPersonalityType,
setPlayerType,
setAvailability,
setSkills,
} = useSetupFlow();
const { user } = useUser({ redirectTo: '/' });
useEffect(() => {
if (user?.player) {
if (user.player.availability_hours) {
setAvailability(user.player.availability_hours.toString());
}
if (user.player.EnneagramType) {
setPersonalityType(PersonalityTypes[user.player.EnneagramType.name]);
}
if (user.player.playerType) {
setPlayerType(user.player.playerType);
}
if (user.player.Player_Skills && user.player.Player_Skills.length > 0) {
setSkills(
user.player.Player_Skills.map((s) => ({
value: s.Skill.id,
label: s.Skill.name,
...s.Skill,
})),
);
}
}
}, [user, setAvailability, setPersonalityType, setPlayerType, setSkills]);
return (
<PageContainer backgroundImage={`url(${BackgroundImage})`}>
{(step + 1) % numTotalSteps !== 0 && <SetupHeader />}

View File

@@ -1,6 +1,14 @@
import { MetaButton, MetaHeading, SelectSearch } from '@metafam/ds';
import {
MetaButton,
MetaHeading,
MetaTheme,
SelectSearch,
selectStyles,
} from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { useSetupFlow } from 'contexts/SetupContext';
import { SkillCategory_Enum } from 'graphql/autogen/types';
import { SkillColors } from 'graphql/types';
import React from 'react';
import { SkillOption } from 'utils/skillHelpers';
@@ -13,6 +21,28 @@ export const SetupSkills: React.FC = () => {
nextButtonLabel,
} = useSetupFlow();
const styles: typeof selectStyles = {
...selectStyles,
multiValue: (s, { data }) => ({
...s,
background: SkillColors[data.category as SkillCategory_Enum],
color: MetaTheme.colors.white,
}),
multiValueLabel: (s, { data }) => ({
...s,
background: SkillColors[data.category as SkillCategory_Enum],
color: MetaTheme.colors.white,
}),
groupHeading: (s, { children }) => {
return {
...s,
...(selectStyles.groupHeading &&
selectStyles.groupHeading(s, { children })),
background: SkillColors[children as SkillCategory_Enum],
};
},
};
return (
<FlexContainer>
<MetaHeading mb={10} mt={-64} textAlign="center">
@@ -21,6 +51,7 @@ export const SetupSkills: React.FC = () => {
<FlexContainer w="100%" align="stretch">
<SelectSearch
isMulti
styles={styles}
value={skills}
onChange={(value) => setSkills(value as Array<SkillOption>)}
options={skillsList}

View File

@@ -56,15 +56,18 @@ export const Web3ContextProvider: React.FC = ({ children }) => {
const modalProvider = await web3Modal.connect();
const ethersProvider = new providers.Web3Provider(modalProvider);
let token = getTokenFromStore();
const ethAddress = await ethersProvider.getSigner().getAddress();
setAddress(ethAddress);
if (!token) {
let token = getTokenFromStore();
if (
!token ||
did.getSignerAddress(token)?.toLowerCase() !== ethAddress.toLowerCase()
) {
token = await did.createToken(ethersProvider);
}
setTokenInStore(token);
setAddress(await ethersProvider.getSigner().getAddress());
setProvider(ethersProvider);
setAuthToken(token);
setIsConnected(true);

View File

@@ -1,6 +1,5 @@
import gql from 'fake-tag';
import { PlayerFragment } from './fragments';
import { PlayerFragment } from 'graphql/fragments';
export const GetMeQuery = gql`
query GetMe {

View File

@@ -1,81 +1,3 @@
import AchieverImage from 'assets/achiever.png';
import ChallengerImage from 'assets/challenger.png';
import EnthusiastImage from 'assets/enthusiast.png';
import HelperImage from 'assets/helper.png';
import IndividualistImage from 'assets/individualist.png';
import InvestigatorImage from 'assets/investigator.png';
import LoyalistImage from 'assets/loyalist.png';
import PeacemakerImage from 'assets/peacemaker.png';
import ReformerImage from 'assets/reformer.png';
import { PersonalityType } from 'graphql/types';
import { PersonalityTypes } from 'graphql/types';
import { EnneagramType_Enum } from './autogen/types';
const personalityTypes: Array<PersonalityType> = [
{
id: '1',
name: EnneagramType_Enum.Reformer,
label: 'The Reformer',
description: 'Principled, Purposeful, Self-Controlled, and Perfectionistic',
image: ReformerImage,
},
{
id: '2',
name: EnneagramType_Enum.Helper,
label: 'The Helper',
description: 'Demonstrative, Generous, People-Pleasing, and Possessive',
image: HelperImage,
},
{
id: '3',
name: EnneagramType_Enum.Achiever,
label: 'The Achiever',
description: 'Adaptive, Excelling, Driven, and Image-Conscious',
image: AchieverImage,
},
{
id: '4',
name: EnneagramType_Enum.Individualist,
label: 'The Individualist',
description: 'Expressive, Dramatic, Self-Absorbed, and Temperamental',
image: IndividualistImage,
},
{
id: '5',
name: EnneagramType_Enum.Investigator,
label: 'The Investigator',
description: 'Perceptive, Innovative, Secretive, and Isolated',
image: InvestigatorImage,
},
{
id: '6',
name: EnneagramType_Enum.Loyalist,
label: 'The Loyalist',
description: 'Engaging, Responsible, Anxious, and Suspicious',
image: LoyalistImage,
},
{
id: '7',
name: EnneagramType_Enum.Enthusiast,
label: 'The Enthusiast',
description: 'Spontaneous, Versatile, Distractible, and Scattered',
image: EnthusiastImage,
},
{
id: '8',
name: EnneagramType_Enum.Challenger,
label: 'The Challenger',
description: 'Self-Confident, Decisive, Willful, and Confrontational',
image: ChallengerImage,
},
{
id: '9',
name: EnneagramType_Enum.Peacemaker,
label: 'The Peacemaker',
description: 'Receptive, Reassuring, Agreeable, and Complacent',
image: PeacemakerImage,
},
];
// TODO: fetch data from backend instead
export const getPersonalityTypes = () => personalityTypes;
export const getPersonalityTypes = () => Object.values(PersonalityTypes);

View File

@@ -1,4 +1,19 @@
import { EnneagramType_Enum, Member, Moloch } from 'graphql/autogen/types';
import { MetaTheme } from '@metafam/ds';
import AchieverImage from 'assets/achiever.png';
import ChallengerImage from 'assets/challenger.png';
import EnthusiastImage from 'assets/enthusiast.png';
import HelperImage from 'assets/helper.png';
import IndividualistImage from 'assets/individualist.png';
import InvestigatorImage from 'assets/investigator.png';
import LoyalistImage from 'assets/loyalist.png';
import PeacemakerImage from 'assets/peacemaker.png';
import ReformerImage from 'assets/reformer.png';
import {
EnneagramType_Enum,
Member,
Moloch,
SkillCategory_Enum,
} from 'graphql/autogen/types';
export type Skill = {
id: string;
@@ -17,3 +32,80 @@ export type PersonalityType = {
export type Membership = Pick<Member, 'id'> & {
moloch: Pick<Moloch, 'id' | 'title' | 'version'>;
};
export const PersonalityTypes: {
[any: string]: PersonalityType;
} = {
[EnneagramType_Enum.Reformer]: {
id: '1',
name: EnneagramType_Enum.Reformer,
label: 'The Reformer',
description: 'Principled, Purposeful, Self-Controlled, and Perfectionistic',
image: ReformerImage,
},
[EnneagramType_Enum.Helper]: {
id: '2',
name: EnneagramType_Enum.Helper,
label: 'The Helper',
description: 'Demonstrative, Generous, People-Pleasing, and Possessive',
image: HelperImage,
},
[EnneagramType_Enum.Achiever]: {
id: '3',
name: EnneagramType_Enum.Achiever,
label: 'The Achiever',
description: 'Adaptive, Excelling, Driven, and Image-Conscious',
image: AchieverImage,
},
[EnneagramType_Enum.Individualist]: {
id: '4',
name: EnneagramType_Enum.Individualist,
label: 'The Individualist',
description: 'Expressive, Dramatic, Self-Absorbed, and Temperamental',
image: IndividualistImage,
},
[EnneagramType_Enum.Investigator]: {
id: '5',
name: EnneagramType_Enum.Investigator,
label: 'The Investigator',
description: 'Perceptive, Innovative, Secretive, and Isolated',
image: InvestigatorImage,
},
[EnneagramType_Enum.Loyalist]: {
id: '6',
name: EnneagramType_Enum.Loyalist,
label: 'The Loyalist',
description: 'Engaging, Responsible, Anxious, and Suspicious',
image: LoyalistImage,
},
[EnneagramType_Enum.Enthusiast]: {
id: '7',
name: EnneagramType_Enum.Enthusiast,
label: 'The Enthusiast',
description: 'Spontaneous, Versatile, Distractible, and Scattered',
image: EnthusiastImage,
},
[EnneagramType_Enum.Challenger]: {
id: '8',
name: EnneagramType_Enum.Challenger,
label: 'The Challenger',
description: 'Self-Confident, Decisive, Willful, and Confrontational',
image: ChallengerImage,
},
[EnneagramType_Enum.Peacemaker]: {
id: '9',
name: EnneagramType_Enum.Peacemaker,
label: 'The Peacemaker',
description: 'Receptive, Reassuring, Agreeable, and Complacent',
image: PeacemakerImage,
},
};
export const SkillColors: Record<SkillCategory_Enum, string> = {
[SkillCategory_Enum.Community]: MetaTheme.colors.green['700'],
[SkillCategory_Enum.Design]: MetaTheme.colors.pink['700'],
[SkillCategory_Enum.Dev]: MetaTheme.colors.cyan['700'],
[SkillCategory_Enum.Engineering]: MetaTheme.colors.blue['700'],
[SkillCategory_Enum.Technologies]: MetaTheme.colors.gray['600'],
[SkillCategory_Enum.Strategy]: MetaTheme.colors.yellow['700'],
};

View File

@@ -1,9 +1,8 @@
import { Web3Context } from 'contexts/Web3Context';
import { useGetMeQuery } from 'graphql/autogen/types';
import { useRouter } from 'next/router';
import { useContext, useEffect } from 'react';
import { Web3Context } from '../contexts/Web3Context';
import { useGetMeQuery } from '../graphql/autogen/types';
export const useWeb3 = () => useContext(Web3Context);
type UseUserOpts = {

View File

@@ -1,16 +1,20 @@
import {
Container,
HStack,
Image,
MetaBox,
MetaTag,
MetaTheme,
P,
SimpleGrid,
Text,
Wrap,
} from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { PlayerFeatures } from 'components/Player/PlayerFeatures';
import { PlayerHero } from 'components/Player/PlayerHero';
import { getPlayer } from 'graphql/getPlayer';
import { getPlayers } from 'graphql/getPlayers';
import { PersonalityTypes, SkillColors } from 'graphql/types';
import {
GetStaticPaths,
GetStaticPropsContext,
@@ -20,19 +24,8 @@ import Error from 'next/error';
import React from 'react';
import { getPlayerDescription } from 'utils/playerHelpers';
import { SkillCategory_Enum } from '../../graphql/autogen/types';
type Props = InferGetStaticPropsType<typeof getStaticProps>;
const SkillColors: Record<SkillCategory_Enum, string> = {
[SkillCategory_Enum.Community]: MetaTheme.colors.green['700'],
[SkillCategory_Enum.Design]: MetaTheme.colors.pink['700'],
[SkillCategory_Enum.Dev]: MetaTheme.colors.cyan['700'],
[SkillCategory_Enum.Engineering]: MetaTheme.colors.blue['700'],
[SkillCategory_Enum.Technologies]: MetaTheme.colors.gray['600'],
[SkillCategory_Enum.Strategy]: MetaTheme.colors.yellow['700'],
};
const PlayerPage: React.FC<Props> = ({ player }) => {
if (!player) {
return <Error statusCode={404} />;
@@ -46,6 +39,24 @@ const PlayerPage: React.FC<Props> = ({ player }) => {
<SimpleGrid columns={[1, 1, 2, 3]} spacing="8" pt="12">
<MetaBox title="About me">
<P>{getPlayerDescription(player)}</P>
{player.EnneagramType ? (
<HStack p={6} spacing={4}>
<Image
w="4rem"
src={PersonalityTypes[player.EnneagramType.name].image}
alt={player.EnneagramType.name}
style={{ mixBlendMode: 'color-dodge' }}
/>
<FlexContainer align="stretch">
<Text color="white" fontWeight="bold">
{player.EnneagramType.name}
</Text>
<Text color="blueLight">
{player.EnneagramType.description}
</Text>
</FlexContainer>
</HStack>
) : null}
</MetaBox>
<MetaBox title="Skills">
<Wrap>