top 500 page initial

This commit is contained in:
Kalle (Sendou) 2020-11-02 18:01:36 +02:00
parent 6a386f940d
commit eb5ccbf724
14 changed files with 336 additions and 186 deletions

View File

@ -0,0 +1,34 @@
import { ApolloError } from "@apollo/client";
import { Alert, AlertDescription, AlertIcon, Spinner } from "@chakra-ui/core";
import { useEffect, useState } from "react";
interface Props {
children: React.ReactNode;
loading: boolean;
error?: ApolloError;
}
const LoadingBoundary: React.FC<Props> = ({ children, loading, error }) => {
const [showSpinner, setShowSpinner] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setShowSpinner(true), 1000);
return () => clearTimeout(timer);
}, []);
if (loading) return showSpinner ? <Spinner size="xl" /> : null;
if (error) {
return (
<Alert status="error">
<AlertIcon />
<AlertDescription>{error.message}</AlertDescription>
</Alert>
);
}
return <>{children}</>;
};
export default LoadingBoundary;

View File

@ -8,7 +8,7 @@ const ModeImage: React.FC<ModeImageProps & ImageProps> = ({
mode,
...props
}) => {
return <Image src={`/images/modeIcons/${mode}.png`} {...props} />;
return <Image src={`/modes/${mode}.png`} {...props} />;
};
export default ModeImage;

26
components/PageHeader.tsx Normal file
View File

@ -0,0 +1,26 @@
import { Heading } from "@chakra-ui/core";
import { useMyTheme } from "lib/useMyTheme";
interface PageHeaderProps {
title: string;
}
const PageHeader: React.FC<PageHeaderProps> = ({ title }) => {
const { themeColor } = useMyTheme();
return (
<>
<Heading
className="shadow"
borderBottomColor={themeColor}
borderBottomWidth="5px"
mb="0.5em"
fontFamily="'Rubik', sans-serif"
fontWeight="bold"
>
{title}
</Heading>
</>
);
};
export default PageHeader;

View File

@ -37,7 +37,7 @@ export type Profile = {
export type Query = {
__typename?: 'Query';
getUserByIdentifier?: Maybe<User>;
xRankPlacements: Array<XRankPlacement>;
getXRankPlacements: Array<XRankPlacement>;
};
@ -46,12 +46,10 @@ export type QueryGetUserByIdentifierArgs = {
};
export type QueryXRankPlacementsArgs = {
orderBy?: Maybe<Array<QueryXRankPlacementsOrderByInput>>;
first?: Maybe<Scalars['Int']>;
last?: Maybe<Scalars['Int']>;
before?: Maybe<XRankPlacementWhereUniqueInput>;
after?: Maybe<XRankPlacementWhereUniqueInput>;
export type QueryGetXRankPlacementsArgs = {
year: Scalars['Int'];
month: Scalars['Int'];
mode: RankedMode;
};
export type UpdateUserProfileInput = {
@ -119,12 +117,6 @@ export type XRankPlacementWhereUniqueInput = {
playerId_mode_month_year?: Maybe<PlayerIdModeMonthYearCompoundUniqueInput>;
};
export type QueryXRankPlacementsOrderByInput = {
ranking?: Maybe<SortOrder>;
month?: Maybe<SortOrder>;
year?: Maybe<SortOrder>;
};
export type PlayerIdModeMonthYearCompoundUniqueInput = {
playerId: Scalars['String'];
mode: RankedMode;
@ -132,11 +124,6 @@ export type PlayerIdModeMonthYearCompoundUniqueInput = {
year: Scalars['Int'];
};
export enum SortOrder {
Asc = 'asc',
Desc = 'desc'
}
export type UpdateUserProfileMutationVariables = Exact<{
profile: UpdateUserProfileInput;
}>;
@ -164,14 +151,25 @@ export type GetUserByIdentifierQuery = (
)> }
);
export type XRankPlacementsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetXRankPlacementsQueryVariables = Exact<{
month: Scalars['Int'];
year: Scalars['Int'];
mode: RankedMode;
}>;
export type XRankPlacementsQuery = (
export type GetXRankPlacementsQuery = (
{ __typename?: 'Query' }
& { xRankPlacements: Array<(
& { getXRankPlacements: Array<(
{ __typename?: 'XRankPlacement' }
& Pick<XRankPlacement, 'id' | 'playerId' | 'playerName' | 'ranking' | 'xPower' | 'weapon' | 'mode' | 'month' | 'year'>
& Pick<XRankPlacement, 'playerId' | 'playerName' | 'ranking' | 'xPower' | 'weapon' | 'mode'>
& { player: (
{ __typename?: 'Player' }
& { user?: Maybe<(
{ __typename?: 'User' }
& Pick<User, 'avatarUrl' | 'fullUsername' | 'profilePath'>
)> }
) }
)> }
);
@ -252,43 +250,50 @@ export function useGetUserByIdentifierLazyQuery(baseOptions?: Apollo.LazyQueryHo
export type GetUserByIdentifierQueryHookResult = ReturnType<typeof useGetUserByIdentifierQuery>;
export type GetUserByIdentifierLazyQueryHookResult = ReturnType<typeof useGetUserByIdentifierLazyQuery>;
export type GetUserByIdentifierQueryResult = Apollo.QueryResult<GetUserByIdentifierQuery, GetUserByIdentifierQueryVariables>;
export const XRankPlacementsDocument = gql`
query XRankPlacements {
xRankPlacements(orderBy: [{ranking: asc}, {month: desc}, {year: desc}]) {
id
export const GetXRankPlacementsDocument = gql`
query getXRankPlacements($month: Int!, $year: Int!, $mode: RankedMode!) {
getXRankPlacements(month: $month, year: $year, mode: $mode) {
playerId
playerName
ranking
xPower
weapon
mode
month
year
player {
user {
avatarUrl
fullUsername
profilePath
}
}
}
}
`;
/**
* __useXRankPlacementsQuery__
* __useGetXRankPlacementsQuery__
*
* To run a query within a React component, call `useXRankPlacementsQuery` and pass it any options that fit your needs.
* When your component renders, `useXRankPlacementsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* To run a query within a React component, call `useGetXRankPlacementsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetXRankPlacementsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useXRankPlacementsQuery({
* const { data, loading, error } = useGetXRankPlacementsQuery({
* variables: {
* month: // value for 'month'
* year: // value for 'year'
* mode: // value for 'mode'
* },
* });
*/
export function useXRankPlacementsQuery(baseOptions?: Apollo.QueryHookOptions<XRankPlacementsQuery, XRankPlacementsQueryVariables>) {
return Apollo.useQuery<XRankPlacementsQuery, XRankPlacementsQueryVariables>(XRankPlacementsDocument, baseOptions);
export function useGetXRankPlacementsQuery(baseOptions?: Apollo.QueryHookOptions<GetXRankPlacementsQuery, GetXRankPlacementsQueryVariables>) {
return Apollo.useQuery<GetXRankPlacementsQuery, GetXRankPlacementsQueryVariables>(GetXRankPlacementsDocument, baseOptions);
}
export function useXRankPlacementsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<XRankPlacementsQuery, XRankPlacementsQueryVariables>) {
return Apollo.useLazyQuery<XRankPlacementsQuery, XRankPlacementsQueryVariables>(XRankPlacementsDocument, baseOptions);
export function useGetXRankPlacementsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetXRankPlacementsQuery, GetXRankPlacementsQueryVariables>) {
return Apollo.useLazyQuery<GetXRankPlacementsQuery, GetXRankPlacementsQueryVariables>(GetXRankPlacementsDocument, baseOptions);
}
export type XRankPlacementsQueryHookResult = ReturnType<typeof useXRankPlacementsQuery>;
export type XRankPlacementsLazyQueryHookResult = ReturnType<typeof useXRankPlacementsLazyQuery>;
export type XRankPlacementsQueryResult = Apollo.QueryResult<XRankPlacementsQuery, XRankPlacementsQueryVariables>;
export type GetXRankPlacementsQueryHookResult = ReturnType<typeof useGetXRankPlacementsQuery>;
export type GetXRankPlacementsLazyQueryHookResult = ReturnType<typeof useGetXRankPlacementsLazyQuery>;
export type GetXRankPlacementsQueryResult = Apollo.QueryResult<GetXRankPlacementsQuery, GetXRankPlacementsQueryVariables>;

View File

@ -1,4 +1,4 @@
import { extendType, objectType } from "@nexus/schema";
import { arg, extendType, intArg, objectType } from "@nexus/schema";
export const XRankPlacmement = objectType({
name: "XRankPlacement",
@ -33,22 +33,20 @@ export const Player = objectType({
export const Query = extendType({
type: "Query",
definition(t) {
// TODO: limit so that you can only get max 100 results max and 50 by default
t.crud.xRankPlacements({
ordering: { ranking: true, month: true, year: true },
async resolve(
root: any,
args: any,
ctx: any,
info: any,
originalResolve: any
) {
console.log({ args: args.orderBy });
if (!args.first && !args.last) args.first = 25;
args.first = Math.min(args.first, 100);
args.last = Math.min(args.last, 100);
return originalResolve(root, args, ctx, info);
t.field("getXRankPlacements", {
type: "XRankPlacement",
nullable: false,
list: [true],
args: {
year: intArg({ nullable: false }),
month: intArg({ nullable: false }),
mode: arg({ type: "RankedMode", nullable: false }),
},
resolve: (_root, args, ctx) => {
return ctx.prisma.xRankPlacement.findMany({
where: { month: args.month, year: args.year, mode: args.mode },
orderBy: [{ ranking: "asc" }, { month: "desc" }, { year: "desc" }],
});
},
});
},

View File

@ -0,0 +1,16 @@
export const getLocalizedMonthYearString = (
month: number,
year: number,
locale: string
) => {
const dateForLocalization = new Date();
dateForLocalization.setDate(1);
dateForLocalization.setMonth(month - 1);
dateForLocalization.setFullYear(year);
const localizedString = dateForLocalization.toLocaleString(locale, {
month: "long",
year: "numeric",
});
return localizedString.charAt(0).toUpperCase() + localizedString.slice(1);
};

13
lib/getRankingString.ts Normal file
View File

@ -0,0 +1,13 @@
export const getRankingString = (ranking: number) => {
switch (ranking) {
case 1:
return "🥇";
case 2:
return "🥈";
case 3:
return "🥉";
default:
return `${ranking}`;
}
};

View File

@ -26,11 +26,6 @@ export interface NexusGenInputs {
playerId: string; // String!
year: number; // Int!
}
QueryXRankPlacementsOrderByInput: { // input type
month?: NexusGenEnums['SortOrder'] | null; // SortOrder
ranking?: NexusGenEnums['SortOrder'] | null; // SortOrder
year?: NexusGenEnums['SortOrder'] | null; // SortOrder
}
UpdateUserProfileInput: { // input type
bio?: string | null; // String
country?: string | null; // String
@ -50,7 +45,6 @@ export interface NexusGenInputs {
export interface NexusGenEnums {
RankedMode: Prisma.RankedMode
SortOrder: Prisma.SortOrder
}
export interface NexusGenScalars {
@ -72,11 +66,9 @@ export interface NexusGenRootTypes {
export interface NexusGenAllTypes extends NexusGenRootTypes {
PlayerIdModeMonthYearCompoundUniqueInput: NexusGenInputs['PlayerIdModeMonthYearCompoundUniqueInput'];
QueryXRankPlacementsOrderByInput: NexusGenInputs['QueryXRankPlacementsOrderByInput'];
UpdateUserProfileInput: NexusGenInputs['UpdateUserProfileInput'];
XRankPlacementWhereUniqueInput: NexusGenInputs['XRankPlacementWhereUniqueInput'];
RankedMode: NexusGenEnums['RankedMode'];
SortOrder: NexusGenEnums['SortOrder'];
String: NexusGenScalars['String'];
Int: NexusGenScalars['Int'];
Float: NexusGenScalars['Float'];
@ -107,7 +99,7 @@ export interface NexusGenFieldTypes {
}
Query: { // field return type
getUserByIdentifier: NexusGenRootTypes['User'] | null; // User
xRankPlacements: NexusGenRootTypes['XRankPlacement'][]; // [XRankPlacement!]!
getXRankPlacements: NexusGenRootTypes['XRankPlacement'][]; // [XRankPlacement!]!
}
User: { // field return type
avatarUrl: string | null; // String
@ -154,7 +146,7 @@ export interface NexusGenFieldTypeNames {
}
Query: { // field return type name
getUserByIdentifier: 'User'
xRankPlacements: 'XRankPlacement'
getXRankPlacements: 'XRankPlacement'
}
User: { // field return type name
avatarUrl: 'String'
@ -196,12 +188,10 @@ export interface NexusGenArgTypes {
getUserByIdentifier: { // args
identifier: string; // String!
}
xRankPlacements: { // args
after?: NexusGenInputs['XRankPlacementWhereUniqueInput'] | null; // XRankPlacementWhereUniqueInput
before?: NexusGenInputs['XRankPlacementWhereUniqueInput'] | null; // XRankPlacementWhereUniqueInput
first?: number | null; // Int
last?: number | null; // Int
orderBy?: NexusGenInputs['QueryXRankPlacementsOrderByInput'][] | null; // [QueryXRankPlacementsOrderByInput!]
getXRankPlacements: { // args
mode: NexusGenEnums['RankedMode']; // RankedMode!
month: number; // Int!
year: number; // Int!
}
}
}
@ -213,9 +203,9 @@ export interface NexusGenInheritedFields {}
export type NexusGenObjectNames = "Mutation" | "Player" | "Profile" | "Query" | "User" | "XRankPlacement";
export type NexusGenInputNames = "PlayerIdModeMonthYearCompoundUniqueInput" | "QueryXRankPlacementsOrderByInput" | "UpdateUserProfileInput" | "XRankPlacementWhereUniqueInput";
export type NexusGenInputNames = "PlayerIdModeMonthYearCompoundUniqueInput" | "UpdateUserProfileInput" | "XRankPlacementWhereUniqueInput";
export type NexusGenEnumNames = "RankedMode" | "SortOrder";
export type NexusGenEnumNames = "RankedMode";
export type NexusGenInterfaceNames = never;

134
pages/top500.tsx Normal file
View File

@ -0,0 +1,134 @@
import { Radio, RadioGroup, Select, Stack } from "@chakra-ui/core";
import { PrismaClient } from "@prisma/client";
import LoadingBoundary from "components/LoadingBoundary";
import PageHeader from "components/PageHeader";
import {
GetXRankPlacementsDocument,
GetXRankPlacementsQueryVariables,
RankedMode,
useGetXRankPlacementsQuery,
} from "generated/graphql";
import { initializeApollo } from "lib/apollo";
import { getLocalizedMonthYearString } from "lib/getLocalizedMontYearString";
import { useTranslation } from "lib/useMockT";
import { GetStaticProps } from "next";
import { useState } from "react";
import XSearch from "scenes/XSearch";
const prisma = new PrismaClient();
const getMonthOptions = (latestMonth: number, latestYear: number) => {
const monthChoices = [];
let month = 5;
let year = 2018;
while (true) {
const monthString = getLocalizedMonthYearString(month, year, "en");
monthChoices.push({ label: monthString, value: `${month},${year}` });
if (month === latestMonth && year === latestYear) break;
month++;
if (month === 13) {
month = 1;
year++;
}
}
monthChoices.reverse();
return monthChoices;
};
export const getStaticProps: GetStaticProps = async () => {
const apolloClient = initializeApollo(null, { prisma });
const mostRecentResult = await prisma.xRankPlacement.findFirst({
orderBy: [{ month: "desc" }, { year: "desc" }],
});
if (!mostRecentResult) throw Error("No X Rank Placements");
await apolloClient.query({
query: GetXRankPlacementsDocument,
variables: {
month: mostRecentResult.month,
year: mostRecentResult.year,
mode: "SZ",
},
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
monthOptions: getMonthOptions(
mostRecentResult.month,
mostRecentResult.year
),
},
};
};
const XSearchPage = ({
monthOptions,
}: {
monthOptions: { label: string; value: string }[];
}) => {
const { t } = useTranslation();
const [variables, setVariables] = useState<GetXRankPlacementsQueryVariables>({
month: Number(monthOptions[0].value.split(",")[0]),
year: Number(monthOptions[0].value.split(",")[1]),
mode: "SZ" as RankedMode,
});
const { data, loading, error } = useGetXRankPlacementsQuery({
variables,
});
return (
<>
<PageHeader title="Top 500" />
<Select
value={`${variables.month},${variables.year}`}
onChange={(e) => {
const [month, year] = e.target.value.split(",");
setVariables({
...variables,
month: Number(month),
year: Number(year),
});
}}
mt={8}
mb={4}
maxW={64}
>
{monthOptions.map((monthYear) => (
<option key={monthYear.value} value={monthYear.value}>
{monthYear.label}
</option>
))}
</Select>
<RadioGroup
value={variables.mode}
onChange={(value) =>
setVariables({ ...variables, mode: value as RankedMode })
}
mt={4}
mb={8}
>
<Stack direction="row">
<Radio value="SZ">{t("plans;splatZonesShort")}</Radio>
<Radio value="TC">{t("plans;towerControlShort")}</Radio>
<Radio value="RM">{t("plans;rainMakerShort")}</Radio>
<Radio value="CB">{t("plans;clamBlitzShort")}</Radio>
</Stack>
</RadioGroup>
<LoadingBoundary loading={loading} error={error}>
<XSearch placements={data?.getXRankPlacements!} />
</LoadingBoundary>
</>
);
};
export default XSearchPage;

View File

@ -1,35 +0,0 @@
import { PrismaClient } from "@prisma/client";
import {
useXRankPlacementsQuery,
XRankPlacementsDocument,
} from "generated/graphql";
import { initializeApollo } from "lib/apollo";
import { GetStaticProps } from "next";
import { useRouter } from "next/router";
import XSearch from "scenes/XSearch";
export const getStaticProps: GetStaticProps = async () => {
const apolloClient = initializeApollo(null, { prisma: new PrismaClient() });
await apolloClient.query({
query: XRankPlacementsDocument,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
},
};
};
const XSearchPage = () => {
const { data } = useXRankPlacementsQuery();
const router = useRouter();
// FIXME: handle fallback
const getUserByIdentifier = data!.xRankPlacements;
return <XSearch placements={getUserByIdentifier} />;
};
export default XSearchPage;

View File

@ -1,5 +1,5 @@
const { PrismaClient } = require("@prisma/client");
//import { PrismaClient } from "@prisma/client";
//const { PrismaClient } = require("@prisma/client");
import { PrismaClient } from "@prisma/client";
const fs = require("fs");
const path = require("path");
@ -26,7 +26,7 @@ const main = async () => {
await prisma.profile.deleteMany({});
await prisma.user.deleteMany({});
await prisma.user.create({
const testUser = await prisma.user.create({
data: {
username: "N-ZAP",
discriminator: "6227",
@ -86,6 +86,14 @@ const main = async () => {
create: {
playerId: "" + i,
names: [playerName],
user:
i === 0
? {
connect: {
id: testUser.id,
},
}
: undefined,
},
},
},

View File

@ -1,4 +1,4 @@
import { Button, Text } from "@chakra-ui/core";
import { Avatar, Text } from "@chakra-ui/core";
import MyHead from "components/MyHead";
import {
Table,
@ -9,19 +9,20 @@ import {
TableRow,
} from "components/Table";
import WeaponImage from "components/WeaponImage";
import { XRankPlacementsQuery } from "generated/graphql";
import { GetXRankPlacementsQuery } from "generated/graphql";
import { getRankingString } from "lib/getRankingString";
import { useTranslation } from "lib/useMockT";
import { useMyTheme } from "lib/useMyTheme";
import Image from "next/image";
import Link from "next/link";
interface Props {
placements: NonNullable<XRankPlacementsQuery["xRankPlacements"]>;
placements: NonNullable<GetXRankPlacementsQuery["getXRankPlacements"]>;
}
const XSearch: React.FC<Props> = ({ placements }) => {
const { t } = useTranslation();
const { gray, themeColor } = useMyTheme();
console.log({ placements });
const { gray } = useMyTheme();
return (
<>
<MyHead title="Top 500 Browser" />
@ -36,71 +37,40 @@ const XSearch: React.FC<Props> = ({ placements }) => {
<Table maxW="50rem">
<TableHead>
<TableRow>
<TableHeader width={4} />
<TableHeader width={4} />
<TableHeader>{t("xsearch;Name")}</TableHeader>
<TableHeader>{t("freeagents;Weapon")}</TableHeader>
<TableHeader>{t("xsearch;X Power")}</TableHeader>
<TableHeader p={1}>{t("xsearch;Placement")}</TableHeader>
<TableHeader>{t("xsearch;Mode")}</TableHeader>
<TableHeader>{t("xsearch;Month")}</TableHeader>
<TableHeader></TableHeader>
<TableHeader>{t("freeagents;Weapon")}</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{placements.map((record) => {
return (
<TableRow key={record.id}>
<TableRow key={record.playerId}>
<TableCell color={gray}>
{getRankingString(record.ranking)}
</TableCell>
<TableCell>
{/* {record.user ? (
<Flex alignItems="center">
<ChakraLink
as={Link}
color={themeColor}
to={record.user.profilePath}
>
{record.player.user && (
<Link href={record.player.user.profilePath}>
<a>
<Avatar
src={record.user.avatarUrl}
src={record.player.user.avatarUrl ?? undefined}
size="sm"
name={record.user.fullUsername}
name={record.player.user.fullUsername}
mr="0.5rem"
/>
</ChakraLink>
{record.playerName}
</Flex>
) : (
<>{record.playerName}</>
)} */}
{record.playerName}
</TableCell>
<TableCell>
<WeaponImage name={record.weapon} size={32} />
</a>
</Link>
)}
</TableCell>
<TableCell>{record.playerName}</TableCell>
<TableCell>
<Text fontWeight="bold">{record.xPower}</Text>
</TableCell>
<TableCell color={gray}>{record.ranking}</TableCell>
<TableCell>
<Image
src={`/modes/${record.mode}.png`}
alt={record.mode}
width={32}
height={32}
/>
</TableCell>
<TableCell color={gray}>
{record.month}/{record.year}
</TableCell>
<TableCell>
<Button
size="xs"
onClick={() => {
/*setFilter({ playerId: record.playerId });
setPage(1);*/
console.log(record.playerId);
}}
variant="outline"
>
{t("xsearch;ID")}
</Button>
<WeaponImage name={record.weapon} size={32} />
</TableCell>
</TableRow>
);

View File

@ -1,15 +1,17 @@
query XRankPlacements {
xRankPlacements(
orderBy: [{ ranking: asc }, { month: desc }, { year: desc }]
) {
id
query getXRankPlacements($month: Int!, $year: Int!, $mode: RankedMode!) {
getXRankPlacements(month: $month, year: $year, mode: $mode) {
playerId
playerName
ranking
xPower
weapon
mode
month
year
player {
user {
avatarUrl
fullUsername
profilePath
}
}
}
}

View File

@ -37,13 +37,7 @@ type Profile {
type Query {
getUserByIdentifier(identifier: String!): User
xRankPlacements(after: XRankPlacementWhereUniqueInput, before: XRankPlacementWhereUniqueInput, first: Int, last: Int, orderBy: [QueryXRankPlacementsOrderByInput!]): [XRankPlacement!]!
}
input QueryXRankPlacementsOrderByInput {
month: SortOrder
ranking: SortOrder
year: SortOrder
getXRankPlacements(mode: RankedMode!, month: Int!, year: Int!): [XRankPlacement!]!
}
enum RankedMode {
@ -53,11 +47,6 @@ enum RankedMode {
TC
}
enum SortOrder {
asc
desc
}
input UpdateUserProfileInput {
bio: String
country: String