added filters for /players page (#566)

* player filters for type and skills

* fetching multiple players in parallel

* removed tile fragment

* search by username or address

* better spacing

* availability filter

* timezone filter

* passing tests in ds

* submit form in search bar

* added better labels for timezone

* fixed test issue

* searching only if search >= 2 char

* meta select ds

* updated metabutton bg color

* parallel for > 50 only

* fix reset search filter
This commit is contained in:
dan13ram
2021-05-19 20:49:06 +05:30
committed by GitHub
parent cc8326361e
commit c2e3782d28
29 changed files with 686 additions and 180 deletions

View File

@@ -0,0 +1,7 @@
module.exports = {
env: {
test: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
};

View File

@@ -0,0 +1,3 @@
module.exports = {
transformIgnorePatterns: ['SelectTimezone'],
};

View File

@@ -24,18 +24,20 @@
"react": ">=16"
},
"dependencies": {
"@types/react": "17.0.5",
"@types/react-dom": "17.0.5",
"@types/react-select": "3.0.22",
"@chakra-ui/icons": "1.0.13",
"@chakra-ui/react": "1.6.1",
"@chakra-ui/theme-tools": "1.1.7",
"@emotion/react": "11.4.0",
"@emotion/styled": "11.3.0",
"framer-motion": "4.1.16",
"@types/react": "17.0.5",
"@types/react-dom": "17.0.5",
"@types/react-select": "3.0.22",
"framer-motion": "4.1.17",
"next": "10.2.0",
"react-select": "4.3.1",
"react-timezone-select": "0.9.8",
"react-timezone-select": "1.0.3",
"spacetime": "6.16.1",
"spacetime-informal": "0.6.1",
"storybook-addon-performance": "0.15.2"
},
"devDependencies": {

View File

@@ -14,6 +14,7 @@ export const MetaButton: React.FC<
letterSpacing="0.1em"
size="lg"
fontSize="sm"
bg="purple.400"
ref={ref}
{...props}
>

View File

@@ -0,0 +1,19 @@
import { Select, SelectProps } from '@chakra-ui/react';
import React from 'react';
import { DropDownIcon } from './icons/DropDownIcon';
export const MetaSelect: React.FC<SelectProps> = (props) => (
<Select
textTransform="uppercase"
maxW="48"
bg="dark"
iconColor="purple.400"
iconSize="xs"
icon={<DropDownIcon boxSize={2} />}
borderColor="purple.400"
borderWidth="2px"
borderRadius="4px"
{...props}
/>
);

View File

@@ -1,10 +1,46 @@
/* istanbul ignore file */
import React from 'react';
import { Styles } from 'react-select';
import TimezoneSelect, { TimezoneSelectProps } from 'react-timezone-select';
import { i18nTimezones } from 'react-timezone-select/dist/index.js';
import spacetime from 'spacetime';
import informal from 'spacetime-informal';
import { theme } from './theme';
export const selectStyles: Styles = {
export type TimezoneType = {
id: string;
label: string;
};
export const TimezoneOptions: TimezoneType[] = Object.entries(
i18nTimezones,
).map((zone) => {
const now = spacetime.now().goto(zone[0]);
const tz = now.timezone();
const tzStrings = informal.display(zone[0]);
let abbrev = zone[0];
if (tzStrings && tzStrings.daylight && tzStrings.standard) {
abbrev = now.isDST()
? tzStrings.daylight.abbrev
: tzStrings.standard.abbrev;
}
const min = tz.current.offset * 60;
const hr = `${(min / 60) ^ 0}:${min % 60 === 0 ? '00' : Math.abs(min % 60)}`;
const prefix = `(GMT${hr.includes('-') ? hr : `+${hr}`}) ${zone[1]}`;
const label = `${prefix} ${abbrev.length < 5 ? `(${abbrev})` : ''}`;
return {
id: zone[0],
label,
};
});
const selectStyles: Styles = {
menu: (styles) => ({
...styles,
background: theme.colors.dark,

View File

@@ -0,0 +1,13 @@
import { createIcon } from '@chakra-ui/icons';
import * as React from 'react';
export const DropDownIcon = createIcon({
displayName: 'DropDownIcon',
path: (
<path
d="M10 0H2C1.17595 0 0.705573 0.940764 1.2 1.6L5.2 6.93333C5.6 7.46667 6.4 7.46667 6.8 6.93333L10.8 1.6C11.2944 0.940764 10.824 0 10 0Z"
fill="currentColor"
/>
),
viewBox: '0 0 12 8',
});

View File

@@ -1,2 +1,3 @@
export { BrightIdIcon } from './BrightIdIcon';
export { DropDownIcon } from './DropDownIcon';
export { Icon3box } from './Icon3box';

View File

@@ -1,15 +1,20 @@
export { BoxedNextImage } from './BoxedNextImage';
export { ConfirmModal } from './ConfirmModal';
export { BrightIdIcon, Icon3box } from './icons';
export * from './icons';
export { LoadingState } from './LoadingState';
export { MetaBox } from './MetaBox';
export { MetaButton } from './MetaButton';
export { MetaHeading } from './MetaHeading';
export { MetaSelect } from './MetaSelect';
export { MetaTag } from './MetaTag';
export { MetaTile, MetaTileBody, MetaTileHeader } from './MetaTile';
export { ResponsiveText } from './ResponsiveText';
export { SelectSearch, selectStyles } from './SelectSearch';
export { SelectTimeZone } from './SelectTimeZone';
export {
SelectTimeZone,
TimezoneOptions,
TimezoneType,
} from './SelectTimeZone';
export { SVG } from './SVG';
export { theme as MetaTheme } from './theme';
export { H1, P } from './typography';

View File

@@ -9,6 +9,7 @@ export type MetaColors = ChakraTheme['colors'] & {
purpleBoxDark: string;
purpleBoxLight: string;
purpleTag: string;
purpleTag30: string;
blueLight: string;
cyanText: string;
diamond: string;
@@ -36,6 +37,7 @@ export const colors: MetaColors = {
purpleBoxDark: '#261943',
purpleBoxLight: '#392373',
purpleTag: '#40347C',
purpleTag30: 'rgba(64, 52, 124, 0.3)',
blueLight: '#A5B9F6',
cyanText: '#79F8FB',
discord: '#7289da',

View File

@@ -14,6 +14,10 @@ export const theme: Theme = extendTheme({
background: 'dark',
color: 'white',
minHeight: '100vh',
option: {
background: 'dark',
color: 'white',
},
},
},
},

View File

@@ -1,6 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"noEmit": true,

View File

@@ -0,0 +1,222 @@
import {
Input,
MetaButton,
MetaSelect,
Stack,
Text,
TimezoneOptions,
TimezoneType,
VStack,
Wrap,
WrapItem,
} from '@metafam/ds';
import {
GetPlayersQueryVariables,
PlayerFragmentFragment,
} from 'graphql/autogen/types';
import { PlayerAggregates, QueryVariableSetter } from 'lib/hooks/players';
import React, { useState } from 'react';
type Props = {
fetching: boolean;
players: PlayerFragmentFragment[];
aggregates: PlayerAggregates;
queryVariables: GetPlayersQueryVariables;
setQueryVariable: QueryVariableSetter;
};
export const PlayerFilter: React.FC<Props> = ({
fetching,
players,
aggregates,
queryVariables,
setQueryVariable,
}) => {
const [search, setSearch] = useState<string>('');
const onSearch = (e: React.ChangeEvent<HTMLFormElement>) => {
e.preventDefault();
if (search.length >= 2) {
setQueryVariable('search', `%${search}%`);
} else {
setQueryVariable('search', `%%`);
}
};
return (
<>
<form onSubmit={onSearch}>
<Stack
spacing="4"
w="100%"
maxW="2xl"
direction={{ base: 'column', md: 'row' }}
align="center"
>
<Input
background="dark"
w="100%"
type="text"
minW={{ base: 'sm', sm: 'md', md: 'lg', lg: 'xl' }}
placeholder="SEARCH PLAYERS BY USERNAME OR ETHEREUM ADDRESS"
_placeholder={{ color: 'whiteAlpha.500' }}
value={search}
onChange={(e) => setSearch(e.target.value)}
size="lg"
borderRadius="0"
borderColor="purple.400"
fontSize="md"
borderWidth="2px"
/>
<MetaButton type="submit" size="lg" isLoading={fetching} px="16">
SEARCH
</MetaButton>
</Stack>
</form>
<Wrap
justify="space-between"
w="100%"
bg="whiteAlpha.200"
style={{ backdropFilter: 'blur(7px)' }}
p="6"
borderRadius="6px"
maxW="79rem"
>
<WrapItem>
<Wrap spacing="4">
<WrapItem>
<VStack spacing="2" w="100%">
<Text
textTransform="uppercase"
color="blueLight"
w="100%"
fontSize="xs"
>
Show
</Text>
<MetaSelect
value={queryVariables.limit as number}
onChange={(e) =>
setQueryVariable('limit', Number(e.target.value))
}
minW="3rem"
>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
<option value={150}>150</option>
</MetaSelect>
</VStack>
</WrapItem>
<WrapItem>
<VStack spacing="2" w="100%">
<Text
textTransform="uppercase"
color="blueLight"
w="100%"
fontSize="xs"
>
Player Type
</Text>
<MetaSelect
value={(queryVariables.playerType as number) || ''}
onChange={(e) =>
setQueryVariable('playerType', e.target.value)
}
>
<option value="">All Types</option>
{aggregates.playerTypes &&
aggregates.playerTypes.map(({ id, title }) => (
<option key={id} value={id}>
{title}
</option>
))}
</MetaSelect>
</VStack>
</WrapItem>
<WrapItem>
<VStack spacing="2" w="100%">
<Text
textTransform="uppercase"
color="blueLight"
w="100%"
fontSize="xs"
>
Skills
</Text>
<MetaSelect
value={(queryVariables.skillCategory as string) || ''}
onChange={(e) =>
setQueryVariable('skillCategory', e.target.value)
}
>
<option value="">All Skills</option>
{aggregates.skillCategories &&
aggregates.skillCategories.map(({ name }) => (
<option key={name} value={name}>
{name}
</option>
))}
</MetaSelect>
</VStack>
</WrapItem>
<WrapItem>
<VStack spacing="2" w="100%">
<Text
textTransform="uppercase"
color="blueLight"
w="100%"
fontSize="xs"
>
Availability
</Text>
<MetaSelect
value={queryVariables.availability as number}
onChange={(e) =>
setQueryVariable('availability', e.target.value)
}
>
<option value={0}>Any h/week</option>
<option value={1}>{'> 1 h/week'}</option>
<option value={5}>{'> 5 h/week'}</option>
<option value={10}>{'> 10 h/week'}</option>
<option value={20}>{'> 20 h/week'}</option>
<option value={30}>{'> 30 h/week'}</option>
<option value={40}>{'> 40 h/week'}</option>
</MetaSelect>
</VStack>
</WrapItem>
<WrapItem>
<VStack spacing="2" w="100%">
<Text
textTransform="uppercase"
color="blueLight"
w="100%"
fontSize="xs"
>
Timezone
</Text>
<MetaSelect
value={(queryVariables.timezone as string) || ''}
onChange={(e) => setQueryVariable('timezone', e.target.value)}
>
<option value="">All timezones</option>
{TimezoneOptions.map((z: TimezoneType) => (
<option key={z.id} value={z.id}>
{z.label}
</option>
))}
</MetaSelect>
</VStack>
</WrapItem>
</Wrap>
</WrapItem>
{players && !fetching && (
<WrapItem>
<Text align="center" fontWeight="bold">
{players.length} players
</Text>
</WrapItem>
)}
</Wrap>
</>
);
};

View File

@@ -0,0 +1,23 @@
import { SimpleGrid, Text } from '@metafam/ds';
import { PlayerTile } from 'components/Player/PlayerTile';
import { PlayerFragmentFragment } from 'graphql/autogen/types';
import React from 'react';
type Props = {
players: PlayerFragmentFragment[];
};
export const PlayerList: React.FC<Props> = ({ players }) =>
players.length > 0 ? (
<SimpleGrid
columns={[1, null, 2, 3]}
spacing="8"
autoRows="minmax(35rem, auto)"
>
{players.map((p) => (
<PlayerTile key={p.username} player={p} />
))}
</SimpleGrid>
) : (
<Text>No players found</Text>
);

View File

@@ -1,20 +0,0 @@
import { SimpleGrid } from '@metafam/ds';
import { PlayerTile } from 'components/Player/PlayerTile';
import { PlayerFragmentFragment } from 'graphql/autogen/types';
import React from 'react';
type Props = {
players: PlayerFragmentFragment[];
};
export const PlayerList: React.FC<Props> = ({ players }) => (
<SimpleGrid
columns={[1, null, 2, 3]}
spacing="8"
autoRows="minmax(35rem, auto)"
>
{players.map((p) => (
<PlayerTile key={p.id} player={p} />
))}
</SimpleGrid>
);

View File

@@ -1,7 +1,7 @@
import {
Flex,
MetaButton,
Select,
MetaSelect,
Switch,
Text,
Wrap,
@@ -43,7 +43,7 @@ export const QuestFilter: React.FC<Props> = ({
<WrapItem>
<Wrap>
<WrapItem>
<Select
<MetaSelect
value={queryVariables.limit as number}
onChange={(e) =>
setQueryVariable('limit', Number(e.target.value))
@@ -52,28 +52,28 @@ export const QuestFilter: React.FC<Props> = ({
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</Select>
</MetaSelect>
</WrapItem>
<WrapItem>
<Select
<MetaSelect
value={queryVariables.order as string}
onChange={(e) => setQueryVariable('order', e.target.value)}
>
<option value={Order_By.Desc}>Newest</option>
<option value={Order_By.Asc}>Oldest</option>
</Select>
</MetaSelect>
</WrapItem>
<WrapItem>
<Select
<MetaSelect
value={queryVariables.status as string}
onChange={(e) => setQueryVariable('status', e.target.value)}
>
<option value={QuestStatus_Enum.Open}>Open</option>
<option value={QuestStatus_Enum.Closed}>Closed</option>
</Select>
</MetaSelect>
</WrapItem>
<WrapItem>
<Select
<MetaSelect
value={(queryVariables.guild_id as string) || ''}
onChange={(e) => setQueryVariable('guild_id', e.target.value)}
>
@@ -84,7 +84,7 @@ export const QuestFilter: React.FC<Props> = ({
{g.name}
</option>
))}
</Select>
</MetaSelect>
</WrapItem>
{myId && (
@@ -94,6 +94,8 @@ export const QuestFilter: React.FC<Props> = ({
size="md"
colorScheme="cyan"
variant="outline"
borderWidth="2px"
borderRadius="4px"
px={4}
onClick={() =>
setQueryVariable(
@@ -107,13 +109,6 @@ export const QuestFilter: React.FC<Props> = ({
isChecked={
myId && queryVariables.created_by_player_id === myId
}
onClick={(e) => {
setQueryVariable(
'created_by_player_id',
queryVariables.created_by_player_id ? '' : myId,
);
e.preventDefault();
}}
/>
</MetaButton>
</Flex>
@@ -123,7 +118,9 @@ export const QuestFilter: React.FC<Props> = ({
</WrapItem>
{quests && (
<WrapItem>
<Text align="center">{quests.length} quests</Text>
<Text align="center" fontWeight="bold">
{quests.length} quests
</Text>
</WrapItem>
)}
</Wrap>

View File

@@ -23,18 +23,14 @@ const errorHasResponseTimeout = (err: CombinedError): boolean =>
err.graphQLErrors.length > 0 &&
!!err.graphQLErrors.find((_err) => _err.message === 'ResponseTimeout');
const retryExchangeFunc = retryExchange({
retryIf: (error) => !!(errorHasResponseTimeout(error) || error.networkError),
});
export const client = createClient({
url: CONFIG.graphqlURL,
suspense: false,
exchanges: [
dedupExchange,
cacheExchange,
retryExchange({
retryIf: (error) =>
!!(errorHasResponseTimeout(error) || error.networkError),
}),
fetchExchange,
],
exchanges: [dedupExchange, cacheExchange, retryExchangeFunc, fetchExchange],
});
export const getSsrClient = (): [Client, ReturnType<typeof ssrExchange>] => {
@@ -43,7 +39,13 @@ export const getSsrClient = (): [Client, ReturnType<typeof ssrExchange>] => {
const ssrClient = initUrqlClient(
{
url: CONFIG.graphqlURL,
exchanges: [dedupExchange, cacheExchange, ssrCache, fetchExchange],
exchanges: [
dedupExchange,
cacheExchange,
ssrCache,
retryExchangeFunc,
fetchExchange,
],
},
false,
);

View File

@@ -1,77 +1,92 @@
import gql from 'fake-tag';
import { Client } from 'urql';
import {
GetPlayersDocument,
GetPlayersQuery,
GetPlayersQueryVariables,
GetPlayerUsernamesQuery,
GetPlayerUsernamesQueryVariables,
PlayerFragmentFragment,
} from './autogen/types';
import { client } from './client';
import { client as defaultClient } from './client';
import { PlayerFragment } from './fragments';
const playersQuery = gql`
query GetPlayers($limit: Int, $offset: Int) {
player(order_by: { total_xp: desc }, limit: $limit, offset: $offset) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
gql`
query GetPlayers(
$offset: Int
$limit: Int
$skillCategory: SkillCategory_enum
$playerType: Int
$availability: Int
$timezone: String
$search: String
) {
player(
order_by: { total_xp: desc }
offset: $offset
limit: $limit
where: {
Player_Skills: { Skill: { category: { _eq: $skillCategory } } }
playerType: { id: { _eq: $playerType } }
availability_hours: { _gte: $availability }
timezone: { _eq: $timezone }
_or: [
{ username: { _ilike: $search } }
{ ethereum_address: { _ilike: $search } }
]
}
) {
...PlayerFragment
}
}
${PlayerFragment}
`;
export const getPlayers = async (
limit = 50,
offset = 0,
): Promise<PlayerFragmentFragment[]> => {
const { data, error } = await client
.query<GetPlayersQuery, GetPlayersQueryVariables>(playersQuery, {
limit,
offset,
})
.toPromise();
if (!data) {
if (error) {
throw error;
}
return [];
}
return data.player;
export const defaultQueryVariables: GetPlayersQueryVariables = {
offset: 0,
limit: 50,
skillCategory: undefined,
playerType: undefined,
availability: 0,
timezone: undefined,
search: '%%',
};
const LIMIT = 50;
const TOTAL_PLAYERS = 150;
export type PlayersResponse = {
error: Error | undefined;
players: PlayerFragmentFragment[];
};
export const getTopPlayers = async (): Promise<PlayerFragmentFragment[]> => {
const promises: Promise<PlayerFragmentFragment[]>[] = new Array(
TOTAL_PLAYERS / LIMIT,
)
.fill(false)
.map((_, i) => getPlayers(LIMIT, i * LIMIT));
const playersArr = await Promise.all(promises);
return playersArr.reduce((_total, _players) => [..._total, ..._players], []);
export const getPlayers = async (
queryVariables = defaultQueryVariables,
client: Client = defaultClient,
): Promise<PlayersResponse> => {
const { data, error } = await client
.query<GetPlayersQuery, GetPlayersQueryVariables>(
GetPlayersDocument,
queryVariables,
)
.toPromise();
return { players: data?.player || [], error };
};
const playerUsernamesQuery = gql`
query GetPlayerUsernames($limit: Int, $offset: Int) {
player(order_by: { total_xp: desc }, limit: $limit, offset: $offset) {
query GetPlayerUsernames($limit: Int) {
player(order_by: { total_xp: desc }, limit: $limit) {
username
}
}
`;
export const getPlayerUsernames = async (
limit = 50,
offset = 0,
): Promise<string[]> => {
const { data, error } = await client
export const getPlayerUsernames = async (limit = 150): Promise<string[]> => {
const { data, error } = await defaultClient
.query<GetPlayerUsernamesQuery, GetPlayerUsernamesQueryVariables>(
playerUsernamesQuery,
{
limit,
offset,
},
)
.toPromise();
@@ -87,10 +102,47 @@ export const getPlayerUsernames = async (
return data.player.map((p) => p.username);
};
export const getTopPlayerUsernames = async (): Promise<string[]> => {
const promises: Promise<string[]>[] = new Array(TOTAL_PLAYERS / LIMIT)
export const getTopPlayerUsernames = getPlayerUsernames;
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
gql`
query GetPlayerFilters {
skill_aggregate(distinct_on: category) {
nodes {
name: category
}
}
player_type(distinct_on: id) {
id
title
}
}
`;
export const getPlayersInParallel = async (
variables: GetPlayersQueryVariables,
): Promise<PlayersResponse> => {
const limit = 50;
const total = variables?.limit as number;
if (total <= limit) {
return getPlayers(variables);
}
const len = Math.ceil(total / limit);
const variablesArr: GetPlayersQueryVariables[] = new Array<boolean>(len)
.fill(false)
.map((_, i) => getPlayerUsernames(LIMIT, i * LIMIT));
const playersArr = await Promise.all(promises);
return playersArr.reduce((_total, _players) => [..._total, ..._players], []);
.map((_, i) => ({
...variables,
offset: i * limit,
limit: i < len - 1 ? limit : total - limit * (len - 1),
}));
const promises = variablesArr.map((vars) => getPlayers(vars));
const playersRespArr = await Promise.all(promises);
return playersRespArr.reduce(
(totalRes, response) => ({
error: totalRes.error || response.error,
players: [...totalRes.players, ...response.players],
}),
{ error: undefined, players: [] },
);
};

View File

@@ -43,14 +43,6 @@ gql`
) {
...QuestFragment
}
quest_aggregate(distinct_on: guild_id) {
nodes {
guild_id
guild {
name
}
}
}
}
${QuestFragment}

View File

@@ -0,0 +1,112 @@
import {
GetPlayersQueryVariables,
PlayerFragmentFragment,
useGetPlayerFiltersQuery,
useGetPlayersQuery,
} from 'graphql/autogen/types';
import {
defaultQueryVariables,
getPlayersInParallel,
PlayersResponse,
} from 'graphql/getPlayers';
import { useCallback, useEffect, useState } from 'react';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type QueryVariableSetter = (key: string, value: any) => void;
export interface PlayerAggregates {
skillCategories: { name: string }[];
playerTypes: { id: number; title: string }[];
}
interface PlayerFilter {
players: PlayerFragmentFragment[];
fetching: boolean;
aggregates: PlayerAggregates;
queryVariables: GetPlayersQueryVariables;
setQueryVariable: QueryVariableSetter;
error?: Error;
}
const usePlayerAggregates = () => {
const [{ data }] = useGetPlayerFiltersQuery();
return {
skillCategories: data?.skill_aggregate.nodes || [],
playerTypes: data?.player_type || [],
};
};
const usePlayersSingle = (
run: boolean,
variables: GetPlayersQueryVariables,
) => {
const [{ fetching, data, error }] = useGetPlayersQuery({
variables,
pause: !run,
});
const players = data?.player || [];
return { fetching, players, error };
};
const usePlayersParallel = (
run: boolean,
variables: GetPlayersQueryVariables,
) => {
const [fetching, setFetching] = useState(true);
const [{ players, error }, setResponse] = useState<PlayersResponse>({
error: undefined,
players: [],
});
useEffect(() => {
const load = async () => {
if (run) {
setFetching(true);
const response = await getPlayersInParallel(variables);
setResponse(response);
setFetching(false);
}
};
load();
}, [run, variables]);
return { fetching, players, error };
};
const useFilteredPlayers = (variables: GetPlayersQueryVariables) => {
const runParallel = (variables.limit as number) > 50; // if limit is 150 then hasura is unable to handle in one query
const playersParallel = usePlayersParallel(runParallel, variables);
const playersSingle = usePlayersSingle(!runParallel, variables);
return runParallel ? playersParallel : playersSingle;
};
export const usePlayerFilter = (): PlayerFilter => {
const [
queryVariables,
setQueryVariables,
] = useState<GetPlayersQueryVariables>(defaultQueryVariables);
const aggregates = usePlayerAggregates();
const setQueryVariable: QueryVariableSetter = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(key: string, value: any) => {
setQueryVariables((oldQueryVariables) => ({
...oldQueryVariables,
[key]: value !== '' ? value : null,
}));
},
[],
);
const { fetching, players, error } = useFilteredPlayers(queryVariables);
return {
players,
aggregates,
fetching,
error,
queryVariables,
setQueryVariable,
};
};

View File

@@ -1,14 +1,17 @@
// eslint-disable-next-line
/* eslint-disable @typescript-eslint/no-var-requires */
const withTM = require('next-transpile-modules')(['react-timezone-select']);
const withImages = require('next-images');
module.exports = withImages({
async redirects() {
return [
{
source: '/',
destination: '/players',
permanent: false,
},
];
},
});
module.exports = withTM(
withImages({
async redirects() {
return [
{
source: '/',
destination: '/players',
permanent: false,
},
];
},
}),
);

View File

@@ -25,6 +25,7 @@
"moment": "2.29.1",
"next": "10.2.0",
"next-images": "1.7.0",
"next-transpile-modules": "7.0.0",
"next-urql": "3.1.0",
"node-fetch": "2.6.1",
"opensea-js": "1.1.11",
@@ -33,9 +34,9 @@
"react-hook-form": "6.15.5",
"react-icons": "4.2.0",
"react-qr-svg": "2.3.0",
"spacetime": "6.16.0",
"spacetime-informal": "0.6.1",
"urql": "2.0.2",
"web3modal": "1.9.3"
"web3modal": "1.9.3",
"spacetime": "6.16.1",
"spacetime-informal": "0.6.1"
}
}

View File

@@ -1,5 +1,5 @@
import { PageContainer } from 'components/Container';
import { GuildList } from 'components/GuildList';
import { GuildList } from 'components/Guild/GuildList';
import { getGuilds } from 'graphql/getGuilds';
import { InferGetStaticPropsType } from 'next';
import React from 'react';

View File

@@ -1,5 +1,5 @@
import { PageContainer } from 'components/Container';
import { PatronList } from 'components/PatronList';
import { PatronList } from 'components/Patron/PatronList';
import { getPatrons } from 'graphql/getPatrons';
import { InferGetStaticPropsType } from 'next';
import React from 'react';

View File

@@ -1,25 +1,52 @@
import { LoadingState, Text, VStack } from '@metafam/ds';
import { PageContainer } from 'components/Container';
import { PlayerList } from 'components/PlayerList';
import { getTopPlayers } from 'graphql/getPlayers';
import { PlayerFilter } from 'components/Player/PlayerFilter';
import { PlayerList } from 'components/Player/PlayerList';
import { getSsrClient } from 'graphql/client';
import { getPlayers } from 'graphql/getPlayers';
import { usePlayerFilter } from 'lib/hooks/players';
import { InferGetStaticPropsType } from 'next';
import React from 'react';
type Props = InferGetStaticPropsType<typeof getStaticProps>;
export const getStaticProps = async () => {
const players = await getTopPlayers();
const [ssrClient, ssrCache] = getSsrClient();
// This populate the cache server-side
await getPlayers(undefined, ssrClient);
return {
props: {
players,
urqlState: ssrCache.extractData(),
},
revalidate: 1,
};
};
const Players: React.FC<Props> = ({ players }) => (
<PageContainer>
<PlayerList players={players} />
</PageContainer>
);
const Players: React.FC<Props> = () => {
const {
players,
aggregates,
fetching,
error,
queryVariables,
setQueryVariable,
} = usePlayerFilter();
return (
<PageContainer>
<VStack w="100%" spacing="8">
<PlayerFilter
fetching={fetching}
aggregates={aggregates}
queryVariables={queryVariables}
setQueryVariable={setQueryVariable}
players={players || []}
/>
{error && <Text>{`Error: ${error.message}`}</Text>}
{fetching && <LoadingState />}
{players && !fetching && !error && <PlayerList players={players} />}
</VStack>
</PageContainer>
);
};
export default Players;

View File

@@ -10,15 +10,14 @@ import {
import { PageContainer } from 'components/Container';
import { QuestFilter } from 'components/Quest/QuestFilter';
import { QuestList } from 'components/Quest/QuestList';
import { getSsrClient } from 'graphql/client';
import { getQuests } from 'graphql/getQuests';
import { usePSeedBalance } from 'lib/hooks/balances';
import { useQuestFilter } from 'lib/hooks/quests';
import { InferGetStaticPropsType } from 'next';
import { useRouter } from 'next/router';
import React, { useMemo } from 'react';
import { getSsrClient } from '../graphql/client';
import { usePSeedBalance } from '../lib/hooks/balances';
import { useQuestFilter } from '../lib/hooks/quests';
import { isAllowedToCreateQuest } from '../utils/questHelpers';
import { isAllowedToCreateQuest } from 'utils/questHelpers';
type Props = InferGetStaticPropsType<typeof getStaticProps>;