mirror of
https://github.com/MetaFam/TheGame.git
synced 2026-02-10 05:55:23 -05:00
Setup URQL with own backend and create basic player list page
This commit is contained in:
7
packages/@types/fake-tag/index.d.ts
vendored
Normal file
7
packages/@types/fake-tag/index.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
declare module 'fake-tag' {
|
||||
function gql(
|
||||
literals: TemplateStringsArray,
|
||||
...placeholders: string[]
|
||||
): string;
|
||||
export = gql;
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
"get-schema": "env-cmd -f ../../.env -x get-graphql-schema -h x-hasura-admin-secret=\\$HASURA_GRAPHQL_ADMIN_SECRET http://localhost:8080/v1/graphql > schema.graphql",
|
||||
"update-schema": "yarn get-schema && yarn generate",
|
||||
"generate": "graphql-codegen --config=graphql-codegen-gql.yaml && graphql-codegen --config=graphql-codegen-typescript.yaml",
|
||||
"typecheck": "yarn generate"
|
||||
"prepare": "yarn generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"graphql-tag": "^2.10.4"
|
||||
|
||||
@@ -5,6 +5,7 @@ export const MetaTag: React.FC<TagProps> = ({ children, ...props }) => (
|
||||
<Tag
|
||||
fontFamily="body"
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
backgroundColor="purpleTag"
|
||||
color="white"
|
||||
{...props}
|
||||
|
||||
@@ -21,6 +21,7 @@ export {
|
||||
Spinner,
|
||||
Stack,
|
||||
Text,
|
||||
Container,
|
||||
useTheme,
|
||||
useToast,
|
||||
} from '@chakra-ui/core';
|
||||
|
||||
@@ -9,6 +9,12 @@ interface MetaColors {
|
||||
purpleTag: string;
|
||||
blueLight: string;
|
||||
cyanText: string;
|
||||
dark60: string;
|
||||
diamond: string;
|
||||
platinum: string;
|
||||
gold: string;
|
||||
silver: string;
|
||||
bronze: string;
|
||||
}
|
||||
|
||||
interface MetaTheme {
|
||||
@@ -53,8 +59,14 @@ export const theme: Theme = {
|
||||
800: '#150747',
|
||||
900: '#07021d',
|
||||
},
|
||||
diamond: '#40e8ec',
|
||||
platinum: '#81b6e3',
|
||||
gold: '#d0a757',
|
||||
silver: '#b0b0b0',
|
||||
bronze: '#a97142',
|
||||
offwhite: '#F6F8F9',
|
||||
blue02: 'rgba(79, 105, 205, 0.2)',
|
||||
dark60: 'rgba(0,0,0, 0.6)',
|
||||
dark: '#1B0D2A',
|
||||
purpleBoxDark: '#261943',
|
||||
purpleBoxLight: '#392373',
|
||||
|
||||
17
packages/web/codegen.yml
Normal file
17
packages/web/codegen.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
overwrite: true
|
||||
require:
|
||||
- ts-node/register
|
||||
generates:
|
||||
./graphql/autogen/types.tsx:
|
||||
schema: '../codegen/schema.graphql'
|
||||
documents:
|
||||
- ./graphql/**/(!(*.d)).ts
|
||||
plugins:
|
||||
- typescript
|
||||
- typescript-operations
|
||||
- typescript-urql
|
||||
- add:
|
||||
content: '/* eslint-disable */'
|
||||
config:
|
||||
gqlImport: fake-tag
|
||||
skipTypename: true
|
||||
@@ -1,29 +1,24 @@
|
||||
import { Flex } from '@metafam/ds';
|
||||
import React from 'react';
|
||||
|
||||
type Props = React.ComponentProps<typeof Flex>
|
||||
type Props = React.ComponentProps<typeof Flex>;
|
||||
|
||||
export const PageContainer: React.FC<Props> = ({
|
||||
children,
|
||||
...props
|
||||
}) => (
|
||||
export const PageContainer: React.FC<Props> = ({ children, ...props }) => (
|
||||
<Flex
|
||||
bgSize="cover"
|
||||
w="100vw"
|
||||
w="100%"
|
||||
h="100vh"
|
||||
p={12}
|
||||
direction="column"
|
||||
align="center"
|
||||
backgroundSize="cover"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export const FlexContainer: React.FC<Props> = ({
|
||||
children,
|
||||
...props
|
||||
}) => (
|
||||
export const FlexContainer: React.FC<Props> = ({ children, ...props }) => (
|
||||
<Flex align="center" justify="center" direction="column" {...props}>
|
||||
{children}
|
||||
</Flex>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createClient } from 'urql';
|
||||
|
||||
import { CONFIG } from '../config';
|
||||
|
||||
export const client = createClient({
|
||||
url: 'https://graphql-pokemon.now.sh/',
|
||||
url: CONFIG.graphqlURL,
|
||||
});
|
||||
|
||||
20
packages/web/graphql/fragments.ts
Normal file
20
packages/web/graphql/fragments.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import gql from 'fake-tag';
|
||||
|
||||
export const PlayerFragment = gql`
|
||||
fragment PlayerFragment on Player {
|
||||
id
|
||||
username
|
||||
totalXp
|
||||
rank
|
||||
ethereum_address
|
||||
box_profile {
|
||||
description
|
||||
emoji
|
||||
ethereumAddress
|
||||
imageUrl
|
||||
job
|
||||
location
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
23
packages/web/graphql/getPlayer.ts
Normal file
23
packages/web/graphql/getPlayer.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import gql from 'fake-tag';
|
||||
|
||||
import { GetPlayerQuery, GetPlayerQueryVariables } from './autogen/types';
|
||||
import { client } from './client';
|
||||
import { PlayerFragment } from './fragments';
|
||||
|
||||
const playerQuery = gql`
|
||||
query GetPlayer($username: String!) {
|
||||
Player(where: { username: { _eq: $username } }) {
|
||||
...PlayerFragment
|
||||
}
|
||||
}
|
||||
${PlayerFragment}
|
||||
`;
|
||||
|
||||
export const getPlayer = async (username: string | undefined) => {
|
||||
if (!username) return null;
|
||||
const { data } = await client
|
||||
.query<GetPlayerQuery, GetPlayerQueryVariables>(playerQuery, { username })
|
||||
.toPromise();
|
||||
|
||||
return data?.Player[0];
|
||||
};
|
||||
32
packages/web/graphql/getPlayers.ts
Normal file
32
packages/web/graphql/getPlayers.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import gql from 'fake-tag';
|
||||
|
||||
import { GetPlayersQuery, GetPlayersQueryVariables } from './autogen/types';
|
||||
import { client } from './client';
|
||||
import { PlayerFragment } from './fragments';
|
||||
|
||||
const playersQuery = gql`
|
||||
query GetPlayers($limit: Int) {
|
||||
Player(order_by: { totalXp: desc }, limit: $limit) {
|
||||
...PlayerFragment
|
||||
}
|
||||
}
|
||||
${PlayerFragment}
|
||||
`;
|
||||
|
||||
export const getPlayers = async (limit = 50) => {
|
||||
const { data, error } = await client
|
||||
.query<GetPlayersQuery, GetPlayersQueryVariables>(playersQuery, { limit })
|
||||
.toPromise();
|
||||
|
||||
if (!data) {
|
||||
if (error) {
|
||||
throw new Error(
|
||||
`${error.message}${JSON.stringify(error.graphQLErrors, null, 2)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.Player;
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Pokemon } from '../types/pokemon';
|
||||
import { client } from './client';
|
||||
|
||||
const pokemonQuery = `
|
||||
query firstTwentyPokemons($name: String!) {
|
||||
pokemon(name: $name) {
|
||||
name
|
||||
image
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const getPokemon = async (
|
||||
name: string | undefined,
|
||||
): Promise<Pokemon | null> => {
|
||||
if (!name) return null;
|
||||
const {
|
||||
data: { pokemon },
|
||||
} = await client.query(pokemonQuery, { name }).toPromise();
|
||||
|
||||
return pokemon;
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Pokemon } from '../types/pokemon';
|
||||
import { client } from './client';
|
||||
|
||||
const firstTwentyPokemonsQuery = `
|
||||
query firstTwentyPokemons {
|
||||
pokemons(first: 20) {
|
||||
image
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const getPokemons = async (): Promise<Array<Pokemon>> => {
|
||||
const {
|
||||
data: { pokemons },
|
||||
} = await client.query(firstTwentyPokemonsQuery).toPromise();
|
||||
|
||||
return pokemons.map((pokemon: Pokemon) => ({
|
||||
...pokemon,
|
||||
name: pokemon.name.toLowerCase(),
|
||||
}));
|
||||
};
|
||||
@@ -7,10 +7,13 @@
|
||||
"build": "next build && next export",
|
||||
"start": "next start",
|
||||
"typecheck": "tsc",
|
||||
"precommit": "yarn lint-staged"
|
||||
"precommit": "yarn lint-staged",
|
||||
"generate": "graphql-codegen --config=codegen.yml",
|
||||
"prepare": "yarn generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@metafam/ds": "0.1.0",
|
||||
"fake-tag": "2.0.0",
|
||||
"graphql": "^15.0.0",
|
||||
"isomorphic-unfetch": "^3.0.0",
|
||||
"next": "latest",
|
||||
|
||||
@@ -1,34 +1,60 @@
|
||||
import { Box, Heading, Image, SimpleGrid } from '@metafam/ds';
|
||||
import { Avatar, Box, Flex, Heading, MetaTag, Stack } from '@metafam/ds';
|
||||
import { PageContainer } from 'components/Container';
|
||||
import { MetaLink } from 'components/Link';
|
||||
import { getPokemons } from 'graphql/getPokemons';
|
||||
import { getPlayers } from 'graphql/getPlayers';
|
||||
import { InferGetStaticPropsType } from 'next';
|
||||
import React from 'react';
|
||||
|
||||
import BackgroundImage from '../public/images/login-background.jpg';
|
||||
import { getPlayerImage, getPlayerName } from '../utils/playerHelpers';
|
||||
|
||||
type Props = InferGetStaticPropsType<typeof getStaticProps>;
|
||||
|
||||
export const getStaticProps = async () => {
|
||||
const pokemon = await getPokemons();
|
||||
const players = await getPlayers();
|
||||
return {
|
||||
props: {
|
||||
pokemon,
|
||||
players,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const Home: React.FC<Props> = ({ pokemon }) => (
|
||||
<SimpleGrid columns={{ sm: 2, lg: 3 }} spacing={6}>
|
||||
{pokemon.map((p, index) => (
|
||||
<MetaLink
|
||||
as={`/pokemon/${p.name}`}
|
||||
href="pokemon/[name]"
|
||||
key={index.toString()}
|
||||
>
|
||||
<Box key={p.name}>
|
||||
<Heading style={{ textTransform: 'capitalize' }}>{p.name}</Heading>
|
||||
<Image src={p.image} alt={p.name} />
|
||||
</Box>
|
||||
</MetaLink>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
const Home: React.FC<Props> = ({ players }) => (
|
||||
<PageContainer backgroundImage={`url(${BackgroundImage})`}>
|
||||
<Stack direction="column" spacing="8">
|
||||
{players.map((p) => (
|
||||
<MetaLink
|
||||
as={`/player/${p.username}`}
|
||||
href="player/[username]"
|
||||
key={p.id}
|
||||
flex={1}
|
||||
>
|
||||
<Flex key={p.id} dir="row" align="center">
|
||||
<Avatar
|
||||
size="lg"
|
||||
src={getPlayerImage(p)}
|
||||
name={getPlayerName(p)}
|
||||
mr="6"
|
||||
/>
|
||||
<Box>
|
||||
<Heading size="xs">{getPlayerName(p)}</Heading>
|
||||
<Box mt="4">
|
||||
<MetaTag
|
||||
backgroundColor={p.rank?.toLowerCase()}
|
||||
mr="3"
|
||||
size="md"
|
||||
color="dark60"
|
||||
>
|
||||
{p.rank}
|
||||
</MetaTag>
|
||||
<MetaTag size="md">XP: {Math.floor(p.totalXp)}</MetaTag>
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</MetaLink>
|
||||
))}
|
||||
</Stack>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
export default Home;
|
||||
|
||||
@@ -11,7 +11,10 @@ import MetaGameImage from '../public/images/metagame.png';
|
||||
const Login: React.FC = () => {
|
||||
const [step, setStep] = useState(0);
|
||||
return (
|
||||
<PageContainer backgroundImage={`url(${BackgroundImage})`}>
|
||||
<PageContainer
|
||||
backgroundImage={`url(${BackgroundImage})`}
|
||||
backgroundSize="cover"
|
||||
>
|
||||
<SimpleGrid
|
||||
columns={3}
|
||||
alignItems="center"
|
||||
|
||||
62
packages/web/pages/player/[username].tsx
Normal file
62
packages/web/pages/player/[username].tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Avatar, Box, Heading } from '@metafam/ds';
|
||||
import {
|
||||
GetStaticPaths,
|
||||
GetStaticPropsContext,
|
||||
InferGetStaticPropsType,
|
||||
} from 'next';
|
||||
import Error from 'next/error';
|
||||
import React from 'react';
|
||||
|
||||
import { PageContainer } from '../../components/Container';
|
||||
import { getPlayer } from '../../graphql/getPlayer';
|
||||
import { getPlayers } from '../../graphql/getPlayers';
|
||||
import { getPlayerImage, getPlayerName } from '../../utils/playerHelpers';
|
||||
|
||||
type Props = InferGetStaticPropsType<typeof getStaticProps>;
|
||||
|
||||
const PlayerPage: React.FC<Props> = ({ player }) => {
|
||||
if (!player) {
|
||||
return <Error statusCode={404} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Box>
|
||||
<Heading size="md" textAlign="center">
|
||||
{player.username}
|
||||
</Heading>
|
||||
<Avatar
|
||||
size="xl"
|
||||
src={getPlayerImage(player)}
|
||||
name={getPlayerName(player)}
|
||||
/>
|
||||
</Box>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayerPage;
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const players = await getPlayers();
|
||||
|
||||
return {
|
||||
paths: players.map(({ username }) => ({
|
||||
params: { username },
|
||||
})),
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticProps = async (
|
||||
context: GetStaticPropsContext<{ username: string }>,
|
||||
) => {
|
||||
const username = context.params?.username;
|
||||
const player = await getPlayer(username);
|
||||
|
||||
return {
|
||||
props: {
|
||||
player,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Box, Flex, Heading, Image } from '@metafam/ds';
|
||||
import { GetStaticPaths, GetStaticProps } from 'next';
|
||||
import Error from 'next/error';
|
||||
|
||||
import { getPokemon } from '../../graphql/getPokemon';
|
||||
import { getPokemons } from '../../graphql/getPokemons';
|
||||
import { Pokemon } from '../../types/pokemon';
|
||||
|
||||
type Props = {
|
||||
pokemon: Pokemon | null;
|
||||
};
|
||||
|
||||
const PokemonPage: React.FC<Props> = ({ pokemon }) => {
|
||||
if (!pokemon) {
|
||||
return <Error statusCode={404} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex align="center" justify="center">
|
||||
<Box>
|
||||
<Heading textAlign="center">{pokemon.name}</Heading>
|
||||
<Image src={pokemon.image} alt={pokemon.name} />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default PokemonPage;
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const pokemons = await getPokemons();
|
||||
|
||||
return {
|
||||
paths: pokemons.map(({ name }) => ({
|
||||
params: { name },
|
||||
})),
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props, { name: string }> = async (
|
||||
context,
|
||||
) => {
|
||||
const name = context.params?.name;
|
||||
const pokemon = await getPokemon(name);
|
||||
|
||||
return {
|
||||
props: {
|
||||
pokemon,
|
||||
},
|
||||
};
|
||||
};
|
||||
8
packages/web/utils/playerHelpers.ts
Normal file
8
packages/web/utils/playerHelpers.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { PlayerFragmentFragment } from '../graphql/autogen/types';
|
||||
|
||||
export const getPlayerImage = (player: PlayerFragmentFragment): string =>
|
||||
player.box_profile?.imageUrl ||
|
||||
`https://avatars.dicebear.com/api/jdenticon/${player.username}.svg`;
|
||||
|
||||
export const getPlayerName = (player: PlayerFragmentFragment): string =>
|
||||
player.box_profile?.name || player.username;
|
||||
Reference in New Issue
Block a user