styling for player tiles (#173)

* update username flow

* skills/memberships not shown if non-existent

* better errors

* styling for player tiles

* cover image for player tile
This commit is contained in:
dan13ram
2020-11-06 22:28:00 +05:30
committed by GitHub
parent bf5fa29f8a
commit d7068373b2
18 changed files with 317 additions and 136 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -17,17 +17,23 @@ export const PlayerFeatures: React.FC<Props> = ({ player }) => {
title="XP"
value={Math.floor(player.totalXp).toString()}
/>
<Divider orientation="vertical" color="whiteAlpha.400" />
<PlayerFeature title="Rank">
<MetaTag
backgroundColor={player.rank?.toLowerCase()}
size="md"
color="blackAlpha.600"
>
{player.rank}
</MetaTag>
</PlayerFeature>
<Divider orientation="vertical" color="whiteAlpha.400" />
{player.rank && (
<Divider orientation="vertical" color="whiteAlpha.400" />
)}
{player.rank && (
<PlayerFeature title="Rank">
<MetaTag
backgroundColor={player.rank?.toLowerCase()}
size="md"
color="blackAlpha.600"
>
{player.rank}
</MetaTag>
</PlayerFeature>
)}
{player.box_profile?.location && (
<Divider orientation="vertical" color="whiteAlpha.400" />
)}
{player.box_profile?.location && (
<PlayerFeature
title="Location"

View File

@@ -1,5 +1,7 @@
import {
Avatar,
Box,
Flex,
Heading,
HStack,
MetaTag,
@@ -12,7 +14,11 @@ 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';
import {
getPlayerCoverImage,
getPlayerImage,
getPlayerName,
} from 'utils/playerHelpers';
type Props = {
player: PlayerFragmentFragment;
@@ -22,101 +28,129 @@ const SHOW_MEMBERSHIPS = 4;
const SHOW_SKILLS = 4;
export const PlayerTile: React.FC<Props> = ({ player }) => (
<VStack
<Flex
direction="column"
key={player.id}
bg="whiteAlpha.200"
style={{ backdropFilter: 'blur(7px)' }}
rounded="lg"
py="6"
px="4"
spacing="6"
p="6"
maxW="30rem"
w="100%"
align="stretch"
position="relative"
overflow="hidden"
justify="space-between"
>
<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>
<Box
bgImage={`url(${getPlayerCoverImage(player)})`}
bgSize="cover"
bgPosition="center"
position="absolute"
top="0"
left="0"
w="100%"
h="4.5rem"
/>
<VStack w="100%" spacing="6" align="stretch" mb={6} position="relative">
<MetaLink
as={`/player/${player.username}`}
href="/player/[username]"
key={player.id}
>
<VStack>
<Avatar
size="xl"
src={getPlayerImage(player)}
name={getPlayerName(player)}
/>
<Heading size="xs" color="white">
{getPlayerName(player)}
</Heading>
</VStack>
</MetaLink>
<Wrap w="100%" justify="center">
{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.box_profile?.description ? (
<VStack spacing={2} align="stretch">
<Text fontFamily="mono" fontSize="sm" color="blueLight">
ABOUT
</Text>
<Text fontSize="sm">{player.box_profile.description}</Text>
</VStack>
) : 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}
</VStack>
<VStack w="100%" spacing="6" align="stretch">
{player.Player_Skills.length ? (
<VStack spacing={2} align="stretch">
<Text fontFamily="mono" fontSize="sm" color="blueLight">
SKILLS
</Text>
<Wrap>
{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.daohausMemberships.length > SHOW_MEMBERSHIPS && (
<MetaTag size="md" fontWeight="normal">
{`+${player.daohausMemberships.length - SHOW_MEMBERSHIPS}`}
</MetaTag>
)}
</Wrap>
</VStack>
) : null}
{player.Player_Skills.length > SHOW_SKILLS && (
<MetaTag size="md" fontWeight="normal">
{`+${player.Player_Skills.length - SHOW_SKILLS}`}
</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>
{player.daohausMemberships.length ? (
<VStack spacing={2} align="stretch">
<Text fontFamily="mono" fontSize="sm" color="blueLight">
MEMBER OF
</Text>
<Wrap>
{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} align="stretch">
<Text fontFamily="mono" fontSize="sm" color="blueLight">
CONTACT
</Text>
<HStack mt="2">
<PlayerContacts player={player} />
</HStack>
</VStack>
) : null}
</VStack>
</Flex>
);

View File

@@ -85,7 +85,7 @@ export const SetupAvailability: React.FC = () => {
<MetaButton
onClick={handleNextPress}
mt={10}
isDisabled={!availability}
isDisabled={invalid}
isLoading={updateSkillsRes.fetching}
loadingText="Saving"
>

View File

@@ -1,6 +1,6 @@
import { Box, Flex, Grid, Image, ResponsiveText } from '@metafam/ds';
import AvatarImage from 'assets/avatar.png';
import BackImage from 'assets/Back.svg';
import LogoImage from 'assets/logo.png';
import SkipImage from 'assets/Skip.svg';
import { FlexContainer } from 'components/Container';
import { useSetupFlow } from 'contexts/SetupContext';
@@ -80,7 +80,7 @@ export const StepProgress: React.FC<StepProps> = ({
pos="absolute"
w="1.5rem"
top="100%"
src={AvatarImage}
src={LogoImage}
left={`${progress}%`}
transform="translateX(-50%)"
alt="Avatar"

View File

@@ -55,7 +55,8 @@ export const SetupPersonalityType: React.FC = () => {
}
>
<Image
w="4rem"
w="100%"
maxW="4rem"
src={p.image}
alt={p.name}
style={{ mixBlendMode: 'color-dodge' }}

View File

@@ -80,6 +80,7 @@ export const SetupPlayerType: React.FC = () => {
cursor="pointer"
onClick={() => setPlayerType(p)}
align="stretch"
justify="flex-start"
border="2px"
borderColor={
playerType && playerType.id === p.id

View File

@@ -3,7 +3,7 @@ import { FlexContainer, PageContainer } from 'components/Container';
import { SetupHeader } from 'components/Setup/SetupHeader';
import { useSetupFlow } from 'contexts/SetupContext';
import { PersonalityTypes } from 'graphql/types';
import { useUser } from 'lib/hooks';
import { useUser, useWeb3 } from 'lib/hooks';
import React, { useEffect } from 'react';
export const SetupProfile: React.FC = () => {
@@ -12,24 +12,42 @@ export const SetupProfile: React.FC = () => {
screen,
numTotalSteps,
options,
username,
setUsername,
personalityType,
setPersonalityType,
playerType,
setPlayerType,
availability,
setAvailability,
skills,
setSkills,
} = useSetupFlow();
const { user } = useUser({ redirectTo: '/' });
const { address } = useWeb3();
useEffect(() => {
if (user?.player) {
if (user.player.availability_hours) {
if (
user.player.username &&
user.player.username.toLowerCase() !== address?.toLowerCase() &&
!username
) {
setUsername(user.player.username);
}
if (user.player.availability_hours && !availability) {
setAvailability(user.player.availability_hours.toString());
}
if (user.player.EnneagramType) {
if (user.player.EnneagramType && !personalityType) {
setPersonalityType(PersonalityTypes[user.player.EnneagramType.name]);
}
if (user.player.playerType) {
if (user.player.playerType && !playerType) {
setPlayerType(user.player.playerType);
}
if (user.player.Player_Skills && user.player.Player_Skills.length > 0) {
if (
user.player.Player_Skills &&
user.player.Player_Skills.length > 0 &&
skills.length === 0
) {
setSkills(
user.player.Player_Skills.map((s) => ({
value: s.Skill.id,
@@ -39,7 +57,20 @@ export const SetupProfile: React.FC = () => {
);
}
}
}, [user, setAvailability, setPersonalityType, setPlayerType, setSkills]);
}, [
user,
address,
username,
setUsername,
personalityType,
setPersonalityType,
playerType,
setPlayerType,
availability,
setAvailability,
skills,
setSkills,
]);
return (
<PageContainer backgroundImage={`url(${BackgroundImage})`}>

View File

@@ -48,7 +48,7 @@ export const SetupSkills: React.FC = () => {
<MetaHeading mb={10} mt={-64} textAlign="center">
What are your superpowers?
</MetaHeading>
<FlexContainer w="100%" align="stretch">
<FlexContainer w="100%" align="stretch" maxW="50rem">
<SelectSearch
isMulti
styles={styles}

View File

@@ -0,0 +1,77 @@
import { Input, MetaButton, MetaHeading, useToast } from '@metafam/ds';
import { FlexContainer } from 'components/Container';
import { useSetupFlow } from 'contexts/SetupContext';
import { useUpdatePlayerUsernameMutation } from 'graphql/autogen/types';
import { useUser } from 'lib/hooks';
import React, { useEffect, useState } from 'react';
const USERNAME_REGEX = /^[a-zA-Z0-9](_(?!(\.|_))|\.(?!(_|\.))|[a-zA-Z0-9]){2,18}[a-zA-Z0-9]$/;
export const SetupUsername: React.FC = () => {
const {
onNextPress,
nextButtonLabel,
username,
setUsername,
} = useSetupFlow();
const [invalid, setInvalid] = useState(false);
const { user } = useUser({ redirectTo: '/' });
const toast = useToast();
useEffect(() => {
setInvalid(!USERNAME_REGEX.test(username));
}, [username]);
const [updateUsernameRes, updateUsername] = useUpdatePlayerUsernameMutation();
const handleNextPress = async () => {
if (!user) return;
const { error } = await updateUsername({
username,
});
if (error) {
console.warn(error);
const errorDescription = error.message.includes('Uniqueness violation')
? 'Username already taken 😢'
: 'The octo is sad 😢';
toast({
title: 'Error',
description: `Unable to update Player Username. ${errorDescription}`,
status: 'error',
isClosable: true,
});
return;
}
onNextPress();
};
return (
<FlexContainer>
<MetaHeading mb={10} textAlign="center">
What do we call you?
</MetaHeading>
<Input
background="dark"
placeholder="USERNAME"
value={username}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setUsername(e.target.value)
}
isInvalid={invalid}
/>
<MetaButton
onClick={handleNextPress}
mt={10}
isDisabled={invalid}
isLoading={updateUsernameRes.fetching}
loadingText="Saving"
>
{nextButtonLabel}
</MetaButton>
</FlexContainer>
);
};

View File

@@ -25,6 +25,8 @@ type SetupContextType = {
skillsList: Array<CategoryOption>;
personalityTypes: Array<PersonalityType>;
playerTypes: Array<PlayerType>;
username: string;
setUsername: React.Dispatch<React.SetStateAction<string>>;
skills: Array<SkillOption>;
setSkills: React.Dispatch<React.SetStateAction<Array<SkillOption>>>;
personalityType: PersonalityType | undefined;
@@ -52,6 +54,8 @@ export const SetupContext = React.createContext<SetupContextType>({
skillsList: [],
personalityTypes: [],
playerTypes: [],
username: '',
setUsername: () => undefined,
skills: [],
setSkills: () => undefined,
personalityType: undefined,
@@ -119,6 +123,7 @@ export const SetupContextProvider: React.FC<Props> = ({
}
}, [options, step, screen, setStep, setScreen, numTotalSteps]);
const [username, setUsername] = useState<string>('');
const [skills, setSkills] = useState<Array<SkillOption>>([]);
const [personalityType, setPersonalityType] = useState<PersonalityType>();
const [playerType, setPlayerType] = useState<PlayerType>();
@@ -141,6 +146,9 @@ export const SetupContextProvider: React.FC<Props> = ({
skillsList,
personalityTypes,
playerTypes,
// username
username,
setUsername,
// skills
skills,
setSkills,

View File

@@ -0,0 +1,12 @@
import gql from 'fake-tag';
export const UpdateUsernameMutation = gql`
mutation UpdatePlayerUsername($username: String!) {
update_Player(_set: { username: $username }, where: {}) {
returning {
id
username
}
}
}
`;

View File

@@ -40,9 +40,10 @@ const PlayerPage: React.FC<Props> = ({ player }) => {
<MetaBox title="About me">
<P>{getPlayerDescription(player)}</P>
{player.EnneagramType ? (
<HStack p={6} spacing={4}>
<HStack py={6} spacing={4}>
<Image
w="4rem"
w="100%"
maxW="4rem"
src={PersonalityTypes[player.EnneagramType.name].image}
alt={player.EnneagramType.name}
style={{ mixBlendMode: 'color-dodge' }}
@@ -58,29 +59,33 @@ const PlayerPage: React.FC<Props> = ({ player }) => {
</HStack>
) : null}
</MetaBox>
<MetaBox title="Skills">
<Wrap>
{player.Player_Skills.map(({ Skill }) => (
<MetaTag
key={Skill.id}
size="md"
fontWeight="normal"
backgroundColor={SkillColors[Skill.category]}
>
{Skill.name}
</MetaTag>
))}
</Wrap>
</MetaBox>
<MetaBox title="Memberships">
<Wrap>
{player.daohausMemberships.map((member) => (
<MetaTag key={member.id} size="md" fontWeight="normal">
{member.moloch.title}
</MetaTag>
))}
</Wrap>
</MetaBox>
{player.Player_Skills.length ? (
<MetaBox title="Skills">
<Wrap>
{player.Player_Skills.map(({ Skill }) => (
<MetaTag
key={Skill.id}
size="md"
fontWeight="normal"
backgroundColor={SkillColors[Skill.category]}
>
{Skill.name}
</MetaTag>
))}
</Wrap>
</MetaBox>
) : null}
{player.daohausMemberships.length ? (
<MetaBox title="Memberships">
<Wrap>
{player.daohausMemberships.map((member) => (
<MetaTag key={member.id} size="md" fontWeight="normal">
{member.moloch.title}
</MetaTag>
))}
</Wrap>
</MetaBox>
) : null}
</SimpleGrid>
</Container>
</>

View File

@@ -1,4 +1,4 @@
import BackgroundImage from 'assets/login-background.jpg';
import BackgroundImage from 'assets/tile-background.jpg';
import { PlayerFragmentFragment } from '../graphql/autogen/types';

View File

@@ -4,6 +4,7 @@ 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 { SetupUsername } from 'components/Setup/SetupUsername';
import React from 'react';
export const options = [
@@ -11,6 +12,10 @@ export const options = [
label: 'About You',
title: { base: 'About You', sm: '1. About You' },
screens: [
{
label: 'Username',
component: <SetupUsername />,
},
{
label: 'Personality Type',
component: <SetupPersonalityType />,