~ minimum code to add color chooser 🔦
3
packages/design-system/src/SVG.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { chakra } from '@chakra-ui/react';
|
||||
|
||||
export const SVG = chakra('svg');
|
||||
@@ -9,6 +9,7 @@ export { MetaTile, MetaTileBody, MetaTileHeader } from './MetaTile';
|
||||
export { ResponsiveText } from './ResponsiveText';
|
||||
export { SelectSearch, selectStyles } from './SelectSearch';
|
||||
export { SelectTimeZone } from './SelectTimeZone';
|
||||
export { SVG } from './SVG'
|
||||
export { theme as MetaTheme } from './theme';
|
||||
export { H1, P } from './typography';
|
||||
export { EmailIcon } from '@chakra-ui/icons';
|
||||
@@ -20,6 +21,7 @@ export {
|
||||
ButtonGroup,
|
||||
ButtonProps,
|
||||
Center,
|
||||
ChakraProps,
|
||||
ChakraProvider,
|
||||
Container,
|
||||
CSSReset,
|
||||
|
||||
15
packages/web/assets/colors/Ambition.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 141.91 137.27" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient1365" x2="1" gradientTransform="matrix(0 49.54 49.54 0 119.75 520.38)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#020121" offset="0"/>
|
||||
<stop stop-color="#2f3e57" offset=".69367"/>
|
||||
<stop stop-color="#2f3e57" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(-20.653 -9.4842)">
|
||||
<g transform="matrix(2.7709 0 0 -2.7709 -240.2 1588.7)">
|
||||
<path d="m118.35 569.3-20.208-22.702c-1.084-1.212-0.224-3.223 1.402-3.223h12.945l-17.785-19.634c-1.256-1.405-0.259-3.365 1.625-3.365h46.848c1.883 0 2.881 1.96 1.625 3.365l-17.786 19.634h12.946c1.625 0 2.486 2.024 1.402 3.236l-20.208 22.664c-0.38 0.425-0.904 0.64-1.426 0.64-0.507 0-1.011-0.203-1.38-0.615" fill="url(#linearGradient1365)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 922 B |
15
packages/web/assets/colors/Balance.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 131.71 131.7" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient1387" x2="1" gradientTransform="matrix(0 -51.25 -51.25 0 620.06 570.38)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9cce62" offset="0"/>
|
||||
<stop stop-color="#009b5b" offset=".98876"/>
|
||||
<stop stop-color="#009b5b" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(-19.625 -6.5215)">
|
||||
<g transform="matrix(2.5699 0 0 -2.5699 -1508 1472.3)">
|
||||
<path d="m594.44 544.76c0-14.129 11.496-25.625 25.625-25.625 14.13 0 25.626 11.496 25.626 25.625 0 14.13-11.496 25.624-25.626 25.624-14.129 0-25.625-11.494-25.625-25.624m3.622 0c0 12.133 9.87 22.004 22.003 22.004s22.004-9.871 22.004-22.004-9.871-22.004-22.004-22.004-22.003 9.871-22.003 22.004m8.701 0c0-7.347 5.955-13.303 13.303-13.303 7.347 0 13.302 5.956 13.302 13.303 0 7.346-5.955 13.303-13.302 13.303-7.348 0-13.303-5.957-13.303-13.303" fill="url(#linearGradient1387)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
15
packages/web/assets/colors/Chaos.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 114.02 136.76" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient1005" x2="1" gradientTransform="matrix(0 -49.659 -49.659 0 369.88 421.96)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#f15236" offset="0"/>
|
||||
<stop stop-color="#94136e" offset=".91424"/>
|
||||
<stop stop-color="#94136e" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(-19.777 -8.3514)">
|
||||
<g transform="matrix(2.7539 0 0 -2.7539 -941.82 1170.4)">
|
||||
<path d="m362.63 421.88c2.821-3.011 3.426-9.684 1.671-12.411s-4.115-2.372-5.27-0.152c-1.121 2.151 0.665 5.857 0.665 5.857-10.873-9.792-10.513-22.177-10.513-22.177 0-11.432 9.268-20.701 20.701-20.701 11.434 0 20.702 9.269 20.702 20.701 0 9.513-6.058 16.705-6.058 16.705 1.549-3.357-1.026-7.03-4.644-0.864-1.548 2.637-2.383 5.601-5.517 8.766-3.644 3.681-7.527 4.352-10.162 4.352-0.594 0-1.125-0.034-1.575-0.076" fill="url(#linearGradient1005)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
23
packages/web/assets/colors/Justice.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 133.06 137.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient1049" x2="1" gradientTransform="matrix(0 -48.66 -48.66 0 873.93 421.04)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#a4aec4" offset="0"/>
|
||||
<stop stop-color="#dca8a9" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g transform="translate(-15.651 -3.0238)">
|
||||
<g transform="matrix(2.8298 0 0 -2.8298 -2390.8 1194.5)">
|
||||
<path d="m868.89 420.34c-1.987-0.58-3.794-1.494-5.074-3.216-0.093-0.124-0.277-0.204-0.434-0.252-2.363-0.712-4.61-1.663-6.614-3.132-0.115-0.084-0.259-0.136-0.396-0.177-1.277-0.38-2.554-0.759-3.833-1.128-0.279-0.08-0.44-0.217-0.535-0.512-0.955-2.95-1.482-5.973-1.573-9.069-0.069-2.313 0.125-4.611 0.581-6.878 0.044-0.219 0.09-0.437 0.139-0.654 0.414-1.863 0.992-3.691 1.738-5.447 0.703-1.756 1.664-3.44 2.718-5.007 0.435-0.647 0.895-1.279 1.378-1.892 0.776-0.987 1.613-1.927 2.504-2.811 1.372-1.359 2.879-2.58 4.491-3.642 1.974-1.3 4.047-2.414 6.324-3.103 1.117-0.339 2.236-0.678 3.358-1.004 0.16-0.046 0.349-0.047 0.511-0.01 4.83 1.079 9.11 3.251 12.842 6.499 1.624 1.414 3.082 2.986 4.37 4.712 1.836 2.46 3.267 5.142 4.283 8.037 1.357 3.867 1.942 7.85 1.726 11.952-0.15 2.854-0.674 5.63-1.542 8.348-0.077 0.242-0.198 0.381-0.439 0.453-0.741 0.221-1.482 0.44-2.214 0.687-0.873 0.296-1.843 0.329-2.59 0.973-0.232 0.199-0.495 0.365-0.758 0.519-1.69 0.982-3.482 1.726-5.354 2.286-0.2 0.06-0.384 0.221-0.539 0.372-0.422 0.405-0.789 0.873-1.241 1.237-1.461 1.183-3.184 1.818-5 2.173-1.294 0.252-2.587 0.386-3.876 0.386-1.661 0-3.315-0.222-4.951-0.7" fill="url(#linearGradient1049)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
25
packages/web/assets/colors/Wisdom.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 136.2 130.09" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient1439" x2="1" gradientTransform="matrix(0 -49.577 -49.577 0 1118.7 569.96)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#09b9f2" offset="0"/>
|
||||
<stop stop-color="#09b9f2" offset=".080645"/>
|
||||
<stop stop-color="#09b9f2" offset=".2827"/>
|
||||
<stop stop-color="#04468b" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g transform="translate(-19.169 -4.0977)">
|
||||
<g transform="matrix(2.624 0 0 -2.624 -2848.2 1499.6)">
|
||||
<path d="m1116.8 568.76-6.352-12.871c-0.314-0.636-0.92-1.076-1.621-1.177l-14.205-2.065c-1.765-0.256-2.47-2.426-1.192-3.672l10.278-10.019c0.507-0.494 0.739-1.207 0.619-1.905l-2.426-14.146c-0.302-1.759 1.544-3.101 3.124-2.27l12.704 6.679c0.626 0.33 1.376 0.33 2.003 0l12.705-6.679c1.579-0.831 3.425 0.511 3.124 2.27l-2.427 14.146c-0.119 0.698 0.112 1.411 0.619 1.905l10.279 10.019c1.277 1.246 0.572 3.416-1.194 3.672l-14.204 2.065c-0.701 0.101-1.307 0.541-1.621 1.177l-6.352 12.871c-0.395 0.8-1.163 1.2-1.931 1.2-0.767 0-1.535-0.4-1.93-1.2" fill="url(#linearGradient1439)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 236 KiB |
137
packages/web/components/Player/ColorBar.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
/* eslint no-bitwise: "off" */
|
||||
|
||||
import { Box, ChakraProps, Flex, SVG } from '@metafam/ds';
|
||||
import { FlexContainer } from 'components/Container';
|
||||
import {
|
||||
colors,
|
||||
getPersonalityInfo,
|
||||
images,
|
||||
} from 'graphql/getPersonalityInfo';
|
||||
import { PersonalityOption } from 'graphql/types';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
// This is just verbose, so I am pulling it out to
|
||||
// save space in the main template
|
||||
const maskImageStyle = (
|
||||
({ url }: { url: string }): Record<string, string> => ({
|
||||
maskImage: `url(${url})`,
|
||||
maskSize: 'contain',
|
||||
maskPosition: 'center',
|
||||
maskRepeat: 'no-repeat',
|
||||
WebkitMaskImage: `url(${url})`,
|
||||
WebkitMaskSize: 'contain',
|
||||
WebkitMaskPosition: 'center',
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
})
|
||||
);
|
||||
|
||||
/* The color bar is below the attribute selection screen,
|
||||
* and shows an equally proportioned set of colors with
|
||||
* monochrome icons above them and a term for the
|
||||
* combination below.
|
||||
*/
|
||||
export const ColorBar = (
|
||||
(
|
||||
{ mask = 0, ...props }:
|
||||
ChakraProps & { mask: number | undefined },
|
||||
): JSX.Element => {
|
||||
const [parts, setParts] = (
|
||||
useState<Array<PersonalityOption>>([])
|
||||
);
|
||||
const [types, setTypes] = (
|
||||
useState<Record<number, PersonalityOption>>({})
|
||||
);
|
||||
const load = async () => {
|
||||
const { parts: ps, types: ts } = (
|
||||
await getPersonalityInfo()
|
||||
)
|
||||
setParts(ps)
|
||||
setTypes(ts)
|
||||
}
|
||||
|
||||
useEffect(() => { load() }, []);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction='column'
|
||||
maxW='100%'
|
||||
className="color-bar"
|
||||
{...props}
|
||||
>
|
||||
<Flex maxW='100%' minH='1.5rem' mb='1rem'>
|
||||
{parts.map((part) => {
|
||||
const set = ((mask & part.mask) !== 0)
|
||||
|
||||
return (
|
||||
!set // if the bit isn't set
|
||||
? ( null ) // return null for map to work
|
||||
: (
|
||||
<Flex
|
||||
key={part.mask}
|
||||
grow={1} justify='center'
|
||||
opacity={0.75}
|
||||
>
|
||||
<Box
|
||||
bgColor='white'
|
||||
h={6} w={6}
|
||||
title={part.name}
|
||||
style={maskImageStyle({ url: images[part.mask] })}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
<Flex
|
||||
minH='calc(1.5rem + 4px)' maxW='100%'
|
||||
border='2px' borderRadius={3}
|
||||
>
|
||||
{parts.map((part) => (
|
||||
((mask & part.mask) === 0)
|
||||
? ( null )
|
||||
: (
|
||||
<Flex
|
||||
key={part.mask}
|
||||
grow={1}
|
||||
h='1.5rem'
|
||||
>
|
||||
<SVG
|
||||
viewBox='0 0 100 100'
|
||||
preserveAspectRatio='none'
|
||||
w='100%'
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="shading"
|
||||
gradientTransform="rotate(90)"
|
||||
>
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor="black" stopOpacity={0.5}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor="white" stopOpacity={0.25}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect
|
||||
width='100%' height='100%'
|
||||
fill={colors[part.mask]}
|
||||
/>
|
||||
<rect
|
||||
width='100%' height='100%'
|
||||
fill='url(#shading)'
|
||||
/>
|
||||
</SVG>
|
||||
</Flex>
|
||||
)
|
||||
))}
|
||||
</Flex>
|
||||
<FlexContainer mt={1}>
|
||||
<q>{types?.[mask]?.name}</q>
|
||||
</FlexContainer>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
);
|
||||
@@ -1,56 +1,73 @@
|
||||
import { Avatar, Box, HStack, Image, Text, VStack } from '@metafam/ds';
|
||||
import {
|
||||
Avatar, Box, Flex, HStack, Link, Text, VStack,
|
||||
} from '@metafam/ds';
|
||||
import { PlayerFragmentFragment } from 'graphql/autogen/types';
|
||||
import React from 'react';
|
||||
import { getPersonalityInfo } from 'graphql/getPersonalityInfo';
|
||||
import { PersonalityOption } from 'graphql/types';
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
getPlayerDescription,
|
||||
getPlayerImage,
|
||||
getPlayerName,
|
||||
} from 'utils/playerHelpers';
|
||||
|
||||
import { PersonalityTypes } from '../../../graphql/types';
|
||||
import { FlexContainer } from '../../Container';
|
||||
import { ProfileSection } from '../../ProfileSection';
|
||||
import { ColorBar } from '../ColorBar';
|
||||
import { PlayerContacts } from '../PlayerContacts';
|
||||
import { PlayerBrightId } from './PlayerBrightId';
|
||||
import { PlayerCollab } from './PlayerCollab';
|
||||
|
||||
const BIO_LENGTH = 240;
|
||||
const MAX_BIO_LENGTH = 240;
|
||||
|
||||
type Props = { player: PlayerFragmentFragment };
|
||||
export const PlayerHero: React.FC<Props> = ({ player }) => {
|
||||
const description = getPlayerDescription(player);
|
||||
const [show, setShow] = React.useState(
|
||||
getPlayerDescription(player).length < BIO_LENGTH,
|
||||
description.length <= MAX_BIO_LENGTH,
|
||||
);
|
||||
const [types, setTypes] = React.useState<{
|
||||
[any: string]: PersonalityOption;
|
||||
}>();
|
||||
const mask = player?.ColorAspect?.mask;
|
||||
const type = mask && types?.[mask];
|
||||
|
||||
const loadTypes = async () => {
|
||||
const { types: list } = await getPersonalityInfo();
|
||||
setTypes(list);
|
||||
};
|
||||
useEffect(() => { loadTypes(); }, []);
|
||||
|
||||
return (
|
||||
<ProfileSection>
|
||||
<VStack spacing={8}>
|
||||
<Avatar
|
||||
w={{ base: '32', md: '56' }}
|
||||
h={{ base: '32', md: '56' }}
|
||||
w={{ base: 32, md: 56 }}
|
||||
h={{ base: 32, md: 56 }}
|
||||
src={getPlayerImage(player)}
|
||||
name={getPlayerName(player)}
|
||||
/>
|
||||
<Box textAlign="center">
|
||||
<Text fontSize="xl" fontFamily="heading" mb="1">
|
||||
<Text fontSize="xl" fontFamily="heading" mb={1}>
|
||||
{getPlayerName(player)}
|
||||
</Text>
|
||||
<PlayerBrightId player={player} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text>
|
||||
{`${getPlayerDescription(player).substring(
|
||||
0,
|
||||
show ? getPlayerDescription(player).length : BIO_LENGTH,
|
||||
)}${show ? '' : '...'} `}
|
||||
{getPlayerDescription(player).length > BIO_LENGTH && (
|
||||
{show
|
||||
? description
|
||||
: `${description.substring(0, MAX_BIO_LENGTH - 9)}…`
|
||||
}
|
||||
{description.length > MAX_BIO_LENGTH && (
|
||||
<Text
|
||||
as="span"
|
||||
fontFamily="body"
|
||||
fontSize="xs"
|
||||
color="cyanText"
|
||||
cursor="pointer"
|
||||
onClick={() => setShow(!show)}
|
||||
onClick={() => setShow(s => !s)}
|
||||
pl={1}
|
||||
>
|
||||
Read {show ? 'less' : 'more'}
|
||||
</Text>
|
||||
@@ -58,38 +75,50 @@ export const PlayerHero: React.FC<Props> = ({ player }) => {
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<HStack mt="2">
|
||||
<HStack mt={2}>
|
||||
<PlayerContacts player={player} />
|
||||
</HStack>
|
||||
<Box w="100%">
|
||||
<PlayerCollab player={player} />
|
||||
</Box>
|
||||
{player.EnneagramType && (
|
||||
<HStack spacing={4}>
|
||||
<Image
|
||||
w="100%"
|
||||
maxW="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>
|
||||
)}
|
||||
{player.playerType?.title ? (
|
||||
<FlexContainer align="stretch">
|
||||
<Text color="white" fontWeight="bold">
|
||||
{player.playerType.title.toUpperCase()}
|
||||
{type && types && (
|
||||
<Flex direction="column" id="color" mb={0} w="100%">
|
||||
<Text
|
||||
fontSize="xs" color="blueLight"
|
||||
casing="uppercase"
|
||||
mb={3}
|
||||
textAlign="left"
|
||||
>
|
||||
Color Disposition
|
||||
</Text>
|
||||
<Link
|
||||
isExternal
|
||||
href={`//dysbulic.github.io/5-color-radar/#/combos/${type.mask.toString(2)}`}
|
||||
maxH='6rem'
|
||||
>
|
||||
<Flex justify="center">
|
||||
<ColorBar mask={type.mask}/>
|
||||
</Flex>
|
||||
</Link>
|
||||
<Text color="blueLight" mt={4} style={{ textIndent: 16 }}>
|
||||
{type.description}
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
{player.playerType?.title && (
|
||||
<FlexContainer align="stretch">
|
||||
<Text
|
||||
color="white" fontWeight="bold"
|
||||
casing="uppercase"
|
||||
>
|
||||
{player.playerType.title}
|
||||
</Text>
|
||||
<Text color="blueLight" style={{ textIndent: 16 }}>
|
||||
{player.playerType.description}
|
||||
</Text>
|
||||
<Text color="blueLight">{player.playerType.description}</Text>
|
||||
</FlexContainer>
|
||||
) : null}
|
||||
)}
|
||||
</VStack>
|
||||
</ProfileSection>
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1,56 +1,63 @@
|
||||
/* eslint no-bitwise: "off" */
|
||||
|
||||
import {
|
||||
HStack,
|
||||
Button,
|
||||
Flex,
|
||||
Image,
|
||||
MetaButton,
|
||||
MetaHeading,
|
||||
SimpleGrid,
|
||||
Text,
|
||||
useToast,
|
||||
} from '@metafam/ds';
|
||||
import { FlexContainer } from 'components/Container';
|
||||
import { MetaLink } from 'components/Link';
|
||||
import { ColorBar } from 'components/Player/ColorBar';
|
||||
import { useSetupFlow } from 'contexts/SetupContext';
|
||||
import { useUpdateAboutYouMutation } from 'graphql/autogen/types';
|
||||
import { PersonalityType } from 'graphql/types';
|
||||
import { images as BaseImages } from 'graphql/getPersonalityInfo';
|
||||
import { PersonalityOption } from 'graphql/types';
|
||||
import { useUser } from 'lib/hooks';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
export type SetupPersonalityTypeProps = {
|
||||
personalityTypeChoices: Array<PersonalityType>;
|
||||
personalityType: PersonalityType | undefined;
|
||||
setPersonalityType: React.Dispatch<
|
||||
React.SetStateAction<PersonalityType | undefined>
|
||||
// keyed on a bitmask of the format 0bWUBRG
|
||||
personalityTypes: { [x: number]: PersonalityOption };
|
||||
colorMask: number | undefined;
|
||||
setColorMask: React.Dispatch<
|
||||
React.SetStateAction<number | undefined>
|
||||
>;
|
||||
};
|
||||
|
||||
export const SetupPersonalityType: React.FC<SetupPersonalityTypeProps> = ({
|
||||
personalityTypeChoices,
|
||||
personalityType,
|
||||
setPersonalityType,
|
||||
export const SetupPersonalityType: (
|
||||
React.FC<SetupPersonalityTypeProps>
|
||||
) = ({
|
||||
personalityTypes, colorMask, setColorMask,
|
||||
}) => {
|
||||
const { onNextPress, nextButtonLabel } = useSetupFlow();
|
||||
const { user } = useUser({ redirectTo: '/' });
|
||||
const toast = useToast();
|
||||
const [updateAboutYouRes, updateAboutYou] = (
|
||||
useUpdateAboutYouMutation()
|
||||
);
|
||||
|
||||
const [updateAboutYouRes, updateAboutYou] = useUpdateAboutYouMutation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleNextPress = async () => {
|
||||
const handleNextPress = useCallback(async () => {
|
||||
if (!user) return;
|
||||
|
||||
setLoading(true);
|
||||
if (user.player?.EnneagramType?.name !== personalityType?.name) {
|
||||
if (user.player?.ColorAspect?.mask !== colorMask) {
|
||||
const { error } = await updateAboutYou({
|
||||
playerId: user.id,
|
||||
input: {
|
||||
enneagram: personalityType?.name,
|
||||
color_mask: colorMask,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.warn(error); // eslint-disable-line no-console
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Unable to update personality type. The octo is sad 😢',
|
||||
description: (
|
||||
'Unable to update personality type. The octo is sad. 😢'
|
||||
),
|
||||
status: 'error',
|
||||
isClosable: true,
|
||||
});
|
||||
@@ -60,64 +67,130 @@ export const SetupPersonalityType: React.FC<SetupPersonalityTypeProps> = ({
|
||||
}
|
||||
|
||||
onNextPress();
|
||||
}, [colorMask, onNextPress, toast, updateAboutYou, user]);
|
||||
|
||||
// mask should always only have at most a single bit set
|
||||
const toggleMaskElement = (mask = 0): void => {
|
||||
setColorMask((current = 0) => {
|
||||
if ((mask & current) > 0) { // if the bit in mask is set
|
||||
return current & ~mask; // unset it
|
||||
}
|
||||
return current | mask; // otherwise set it
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<FlexContainer>
|
||||
<MetaHeading mb={5} textAlign="center">
|
||||
Personality Type
|
||||
</MetaHeading>
|
||||
<Text mb={10}>
|
||||
{`Please select your personality type below. Not sure what type you are? `}
|
||||
<MetaLink href="https://enneagramtest.net/" isExternal>
|
||||
Take a quick test.
|
||||
</MetaLink>
|
||||
</Text>
|
||||
<SimpleGrid columns={[1, null, 2, 3]} spacing="8">
|
||||
{personalityTypeChoices.map((p: PersonalityType) => (
|
||||
<HStack
|
||||
key={p.id}
|
||||
p={6}
|
||||
spacing={4}
|
||||
bgColor={
|
||||
personalityType && personalityType.id === p.id
|
||||
? 'purpleBoxDark'
|
||||
: 'purpleBoxLight'
|
||||
}
|
||||
borderRadius="0.5rem"
|
||||
_hover={{ bgColor: 'purpleBoxDark' }}
|
||||
transition="background 0.25s"
|
||||
cursor="pointer"
|
||||
onClick={() => setPersonalityType(p)}
|
||||
border="2px"
|
||||
borderColor={
|
||||
personalityType && personalityType.id === p.id
|
||||
? 'purple.400'
|
||||
: 'transparent'
|
||||
}
|
||||
<FlexContainer maxW='100%'>
|
||||
<Flex direction='column'>
|
||||
<MetaHeading mb={5} textAlign="center">
|
||||
Person­ality Type
|
||||
</MetaHeading>
|
||||
<Text mb={10}>
|
||||
Please select your personality components below.
|
||||
Not sure what type you are?
|
||||
<Text as="span"> Take </Text>
|
||||
<MetaLink
|
||||
href="//dysbulic.github.io/5-color-radar/#/explore/"
|
||||
isExternal
|
||||
>
|
||||
<Image
|
||||
w="100%"
|
||||
maxW="4rem"
|
||||
src={p.image}
|
||||
alt={p.name}
|
||||
style={{ mixBlendMode: 'color-dodge' }}
|
||||
/>
|
||||
<FlexContainer align="stretch">
|
||||
<Text color="white" fontWeight="bold">
|
||||
{p.name}
|
||||
</Text>
|
||||
<Text color="blueLight">{p.description}</Text>
|
||||
</FlexContainer>
|
||||
</HStack>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
a quick exam
|
||||
</MetaLink>
|
||||
<Text as="span"> or </Text>
|
||||
<MetaLink
|
||||
href="//dysbulic.github.io/5-color-radar/#/test/"
|
||||
isExternal
|
||||
>
|
||||
a longer quiz
|
||||
</MetaLink>
|
||||
.
|
||||
</Text>
|
||||
</Flex>
|
||||
<FlexContainer
|
||||
grow={1} spacing={8} maxW='70rem'
|
||||
direction='row' wrap='wrap'
|
||||
id="colors"
|
||||
>
|
||||
{Object.entries(BaseImages)
|
||||
.reverse().map(
|
||||
([orig, image], idx) => {
|
||||
const option = personalityTypes[parseInt(orig, 10)]
|
||||
const { mask = 0 } = (option ?? {})
|
||||
const selected = (((colorMask ?? 0) & mask) > 0)
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={mask}
|
||||
display="flex"
|
||||
direction="row"
|
||||
p={6} m={2} h="auto" spacing={4}
|
||||
borderRadius={8}
|
||||
cursor="pointer"
|
||||
onClick={() => toggleMaskElement(mask)}
|
||||
autoFocus={idx === 0} // Doesn't work
|
||||
ref={input => {
|
||||
if (idx === 0 && !input?.getAttribute('focused-once')) {
|
||||
input?.focus()
|
||||
input?.setAttribute('focused-once', 'true')
|
||||
}
|
||||
}}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleNextPress()
|
||||
e.preventDefault()
|
||||
}
|
||||
}}
|
||||
transition="background 0.25s, filter 0.5s"
|
||||
bgColor={
|
||||
selected ? 'purpleBoxDark' : 'purpleBoxLight'
|
||||
}
|
||||
_hover={{
|
||||
filter: 'hue-rotate(25deg)',
|
||||
}}
|
||||
_focus={{
|
||||
borderColor: '#FFFFFF55',
|
||||
outline: 'none',
|
||||
}}
|
||||
_active={{ bg: (
|
||||
selected ? 'purpleBoxDark' : 'purpleBoxLight'
|
||||
) }}
|
||||
borderWidth={2}
|
||||
borderColor={
|
||||
selected ? 'purple.400' : 'transparent'
|
||||
}
|
||||
>
|
||||
<Image
|
||||
w="100%" maxW={16} h={16}
|
||||
src={image} alt={option.name}
|
||||
filter="drop-shadow(0px 0px 3px black)"
|
||||
/>
|
||||
<FlexContainer align="stretch" ml={2}>
|
||||
<Text
|
||||
color="white"
|
||||
casing="uppercase" textAlign="left"
|
||||
>
|
||||
{option.name}
|
||||
</Text>
|
||||
<Text
|
||||
color="blueLight" fontWeight="normal"
|
||||
whiteSpace="initial" textAlign="left"
|
||||
>
|
||||
{option.description}
|
||||
</Text>
|
||||
</FlexContainer>
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
</FlexContainer>
|
||||
|
||||
<ColorBar mask={colorMask} w="min(100vw, 30rem)"/>
|
||||
|
||||
<MetaButton
|
||||
onClick={handleNextPress}
|
||||
mt={10}
|
||||
isDisabled={!personalityType}
|
||||
isLoading={updateAboutYouRes.fetching || loading}
|
||||
isDisabled={colorMask === undefined}
|
||||
isLoading={updateAboutYouRes.fetching}
|
||||
loadingText="Saving"
|
||||
>
|
||||
{nextButtonLabel}
|
||||
|
||||
@@ -9,9 +9,10 @@ export const PlayerFragment = gql`
|
||||
ethereum_address
|
||||
availability_hours
|
||||
timezone
|
||||
EnneagramType {
|
||||
description
|
||||
ColorAspect {
|
||||
name
|
||||
description
|
||||
mask
|
||||
}
|
||||
playerType {
|
||||
description
|
||||
|
||||
73
packages/web/graphql/getPersonalityInfo.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import AmbitionAltImg from 'assets/colors/Ambition.svg';
|
||||
import BalanceAltImg from 'assets/colors/Balance.svg';
|
||||
import ChaosAltImg from 'assets/colors/Chaos.svg';
|
||||
import JusticeAltImg from 'assets/colors/Justice.svg';
|
||||
import WisdomAltImg from 'assets/colors/Wisdom.svg';
|
||||
import gql from 'fake-tag';
|
||||
import { isPow2 } from 'utils/mathHelper';
|
||||
|
||||
import { ColorAspect } from './autogen/types';
|
||||
import { client } from './client';
|
||||
import { PersonalityOption } from './types';
|
||||
|
||||
const AspectsQuery = gql`
|
||||
query GetAspects {
|
||||
ColorAspect {
|
||||
mask
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const images: {
|
||||
[x: number]: string
|
||||
} = {
|
||||
0b10000: JusticeAltImg,
|
||||
0b01000: WisdomAltImg,
|
||||
0b00100: AmbitionAltImg,
|
||||
0b00010: ChaosAltImg,
|
||||
0b00001: BalanceAltImg,
|
||||
};
|
||||
|
||||
export const colors: {
|
||||
[x: number]: string
|
||||
} = {
|
||||
0b10000: '#c4aab4',
|
||||
0b01000: '#0273b2',
|
||||
0b00100: '#141a36',
|
||||
0b00010: '#b72d5b',
|
||||
0b00001: '#36ae60',
|
||||
};
|
||||
|
||||
export const getPersonalityInfo = async (): Promise<{
|
||||
parts: Array<PersonalityOption>;
|
||||
types: { [any: string]: PersonalityOption };
|
||||
}> => {
|
||||
const { data, error } = await (
|
||||
client
|
||||
.query(AspectsQuery)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
if (error) throw error;
|
||||
if (!data) throw new Error("data isn't set");
|
||||
|
||||
const parts: Array<PersonalityOption> = [];
|
||||
const types: { [x: number]: PersonalityOption } = {};
|
||||
data.ColorAspect.forEach((aspect: ColorAspect) => {
|
||||
const option = {
|
||||
name: aspect.name,
|
||||
description: aspect.description,
|
||||
mask: aspect.mask,
|
||||
};
|
||||
types[aspect.mask] = option;
|
||||
|
||||
// pure colors are powers of 2 (only 1 bit set)
|
||||
if (isPow2(aspect.mask)) {
|
||||
parts.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
return { parts, types };
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import { PersonalityTypes } from 'graphql/types';
|
||||
|
||||
export const getPersonalityTypes = () => Object.values(PersonalityTypes);
|
||||
@@ -1,15 +1,5 @@
|
||||
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,
|
||||
Me,
|
||||
Member,
|
||||
Moloch,
|
||||
@@ -27,12 +17,10 @@ export type Skill = {
|
||||
category: string;
|
||||
};
|
||||
|
||||
export type PersonalityType = {
|
||||
id: string;
|
||||
name: EnneagramType_Enum;
|
||||
label: string;
|
||||
description: string;
|
||||
image: string;
|
||||
export type PersonalityOption = {
|
||||
mask: number;
|
||||
name: string;
|
||||
description?: string | null | undefined;
|
||||
};
|
||||
|
||||
export type Membership = Pick<Member, 'id'> & {
|
||||
@@ -46,74 +34,6 @@ export type MeType =
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
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'],
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { SetupPersonalityType } from 'components/Setup/SetupPersonalityType';
|
||||
import { SetupProfile } from 'components/Setup/SetupProfile';
|
||||
import { SetupContextProvider } from 'contexts/SetupContext';
|
||||
import { getPersonalityTypes } from 'graphql/getPersonalityTypes';
|
||||
import { PersonalityType, PersonalityTypes } from 'graphql/types';
|
||||
import { getPersonalityInfo } from 'graphql/getPersonalityInfo';
|
||||
import { useUser } from 'lib/hooks';
|
||||
import { InferGetStaticPropsType } from 'next';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
export const getStaticProps = async () => {
|
||||
const personalityTypeChoices = await getPersonalityTypes();
|
||||
const { types: personalityTypes, parts: personalityParts } = (
|
||||
await getPersonalityInfo()
|
||||
);
|
||||
|
||||
return {
|
||||
props: {
|
||||
personalityTypeChoices,
|
||||
personalityParts,
|
||||
personalityTypes,
|
||||
hideAppDrawer: true,
|
||||
},
|
||||
};
|
||||
@@ -22,24 +24,32 @@ type Props = InferGetStaticPropsType<typeof getStaticProps>;
|
||||
|
||||
const PersonalityTypeSetup: React.FC<Props> = (props) => {
|
||||
|
||||
const { personalityTypeChoices } = props;
|
||||
const [personalityType, setPersonalityType] = useState<PersonalityType>();
|
||||
const { personalityTypes } = props;
|
||||
const { user } = useUser({ redirectTo: '/' });
|
||||
const [colorMask, setColorMask] = (
|
||||
useState<number | undefined>(user?.player?.ColorAspect?.mask)
|
||||
);
|
||||
|
||||
if (user?.player) {
|
||||
const { player } = user;
|
||||
if (player.EnneagramType && !personalityType) {
|
||||
setPersonalityType(PersonalityTypes[player.EnneagramType.name]);
|
||||
const load = () => {
|
||||
const { player } = user ?? {};
|
||||
if (player) {
|
||||
if (colorMask === undefined && player.ColorAspect !== null) {
|
||||
setColorMask(player.ColorAspect?.mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
useEffect(load, [user, colorMask]);
|
||||
|
||||
return (
|
||||
<SetupContextProvider>
|
||||
<SetupProfile>
|
||||
<SetupPersonalityType
|
||||
personalityTypeChoices={personalityTypeChoices}
|
||||
personalityType={personalityType}
|
||||
setPersonalityType={setPersonalityType} />
|
||||
<SetupPersonalityType
|
||||
{...{
|
||||
personalityTypes,
|
||||
colorMask,
|
||||
setColorMask,
|
||||
}}
|
||||
/>
|
||||
</SetupProfile>
|
||||
</SetupContextProvider>
|
||||
);
|
||||
|
||||
5
packages/web/utils/mathHelper.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// true if the number is a power of 2
|
||||
export const isPow2 = (int: number): boolean => (
|
||||
// eslint-disable-next-line no-bitwise
|
||||
int > 0 && (int & (int - 1)) === 0
|
||||
);
|
||||