diff --git a/components/builds/BuildCard.tsx b/components/builds/BuildCard.tsx new file mode 100644 index 000000000..aa05f2451 --- /dev/null +++ b/components/builds/BuildCard.tsx @@ -0,0 +1,220 @@ +import { + Box, + BoxProps, + Flex, + IconButton, + Popover, + PopoverBody, + PopoverContent, + PopoverTrigger, +} from "@chakra-ui/core"; +import { t, Trans } from "@lingui/macro"; +import WeaponImage from "components/common/WeaponImage"; +import { getEmojiFlag } from "countries-list"; +import { Unpacked } from "lib/types"; +import { useMyTheme } from "lib/useMyTheme"; +import Image from "next/image"; +import Link from "next/link"; +import { GetBuildsByWeaponData } from "prisma/queries/getBuildsByWeapon"; +import { useState } from "react"; +import { FiBarChart2, FiInfo, FiTarget } from "react-icons/fi"; +import Gears from "./Gears"; +//import ViewAP from "./ViewAP"; +import ViewSlots from "./ViewSlots"; + +interface BuildCardProps { + build: Unpacked; + canModify?: boolean; + //setBuildBeingEdited?: (build: Build) => void; + otherBuildCount?: number; + onShowAllByUser?: () => void; +} + +const BuildCard: React.FC = ({ + build, + canModify, + //setBuildBeingEdited, + otherBuildCount, + onShowAllByUser, + ...props +}) => { + const [apView, setApView] = useState(false); + const [showStats, setShowStats] = useState(false); + const { themeColor, secondaryBgColor, gray } = useMyTheme(); + + const username = build.user.username; + + return ( + <> + {/* {showStats && ( + setShowStats(false)} /> + )} */} + + + + + + + {build.top500 && ( + x rank top 500 logo + )} + {build.jpn && getEmojiFlag("jp")} + + + + {build.updatedAt.toLocaleDateString()} + + {build.user && ( + + •{" "} + + + {build.user.username}#{build.user.discriminator} + + + + )} + + {build.title && ( + + {build.title} + + )} + + setApView(!apView)} + aria-label="Set build card view" + fontSize="20px" + icon={} + mr="0.5em" + /> + setShowStats(!showStats)} + aria-label="Show build stats view" + fontSize="20px" + icon={} + mr="0.5em" + /> + {build.description && ( + + + } + /> + + + + {build.description} + + + + )} + {/* {canModify && ( + setBuildBeingEdited(build)) + } + aria-label="Show description" + fontSize="20px" + icon={} + ml="0.5em" + /> + )} */} + + + + + + {/* {apView ? : } */} + + + + {otherBuildCount && ( + + + Show all {otherBuildCount} builds by {username} + + + )} + + + + + ); +}; + +export default BuildCard; diff --git a/components/builds/Gears.tsx b/components/builds/Gears.tsx new file mode 100644 index 000000000..a40f264e0 --- /dev/null +++ b/components/builds/Gears.tsx @@ -0,0 +1,30 @@ +import { Box, Flex } from "@chakra-ui/core"; +import GearImage from "components/common/GearImage"; +import { Unpacked } from "lib/types"; +import { GetBuildsByWeaponData } from "prisma/queries/getBuildsByWeapon"; +import React from "react"; + +interface GearsProps { + build: Unpacked; +} + +const Gears: React.FC = ({ build }) => { + if (!build.headGear && !build.clothingGear && !build.shoesGear) { + return ; + } + return ( + + + {build.headGear && } + + + {build.clothingGear && } + + + {build.shoesGear && } + + + ); +}; + +export default Gears; diff --git a/components/builds/ViewAP.tsx b/components/builds/ViewAP.tsx new file mode 100644 index 000000000..29c9bb458 --- /dev/null +++ b/components/builds/ViewAP.tsx @@ -0,0 +1,110 @@ +// This while is bit of a mess from TS PoV - might be worth while to do it better later + +import { Box, Flex } from "@chakra-ui/core"; +import React, { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import MyThemeContext from "../../themeContext"; +import { Ability, Build } from "../../types"; +import { mainOnlyAbilities } from "../../utils/lists"; +import DividingBox from "../common/DividingBox"; +import AbilityIcon from "./AbilityIcon"; + +interface ViewAPProps { + build: Build; +} + +const ViewAP: React.FC = ({ build }) => { + const { t } = useTranslation(); + const { grayWithShade } = useContext(MyThemeContext); + const abilityArrays: Ability[][] = [ + build.headgear, + build.clothing, + build.shoes, + ]; + + const abilityToPoints: Partial> = {}; + abilityArrays.forEach((arr) => + arr.forEach((ability, index) => { + let abilityPoints = index === 0 ? 10 : 3; + if (mainOnlyAbilities.indexOf(ability as any) !== -1) abilityPoints = 999; + abilityToPoints[ability] = abilityToPoints.hasOwnProperty(ability) + ? (abilityToPoints[ability] as any) + abilityPoints + : abilityPoints; + }) + ); + + const pointsToAbilities: Record = {}; + (Object.keys(abilityToPoints) as Array).forEach( + (ability: Ability) => { + const points = abilityToPoints[ability]; + + pointsToAbilities.hasOwnProperty(points as any) + ? pointsToAbilities[points as any].push(ability) + : (pointsToAbilities[points as any] = [ability]); + } + ); + + const APArrays = (Object.keys(pointsToAbilities) as Array< + keyof typeof pointsToAbilities + >) + .map((points) => [points, pointsToAbilities[points as any]]) + .sort((a1, a2) => parseInt(a2[0] as string) - parseInt(a1[0] as string)); + + let indexToPrintAPAt = APArrays[0][0] === "999" ? 1 : 0; + + return ( + + {APArrays.map((arr, index) => { + return ( + + {arr[0] !== "999" ? ( + + + {arr[0] as string} + {indexToPrintAPAt === index ? ( + <> +
+ {t("analyzer;abilityPointShort")} + + ) : null} +
+
+ ) : ( + + )} + {(arr[1] as Array).map((ability, abilityIndex) => ( + 5 + ? `-${(arr[1].length - 5) * 5}px` + : undefined + } + > + + + ))} +
+ ); + })} +
+ ); +}; + +export default ViewAP; diff --git a/components/builds/ViewSlots.tsx b/components/builds/ViewSlots.tsx new file mode 100644 index 000000000..f57060d3b --- /dev/null +++ b/components/builds/ViewSlots.tsx @@ -0,0 +1,83 @@ +import { Box, BoxProps, Flex } from "@chakra-ui/core"; +import AbilityIcon from "components/common/AbilityIcon"; +import { Unpacked } from "lib/types"; +import { GetBuildsByWeaponData } from "prisma/queries/getBuildsByWeapon"; + +interface ViewSlotsProps { + build: Unpacked; + onAbilityClick?: (gear: "HEAD" | "CLOTHING" | "SHOES", index: number) => void; +} + +// FIXME: fix any +const defaultAbilityRow = ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"] as any[]; + +const ViewSlots: React.FC = ({ + build, + onAbilityClick, + ...props +}) => { + return ( + + + {(build.headAbilities ?? defaultAbilityRow).map((ability, index) => ( + onAbilityClick("HEAD", index) : undefined + } + cursor={onAbilityClick ? "pointer" : undefined} + > + + + ))} + + + {(build.clothingAbilities ?? defaultAbilityRow).map( + (ability, index) => ( + onAbilityClick("CLOTHING", index) + : undefined + } + cursor={onAbilityClick ? "pointer" : undefined} + > + + + ) + )} + + + {(build.shoesAbilities ?? defaultAbilityRow).map((ability, index) => ( + onAbilityClick("SHOES", index) : undefined + } + cursor={onAbilityClick ? "pointer" : undefined} + > + + + ))} + + + ); +}; + +export default ViewSlots; diff --git a/components/layout/index.tsx b/components/layout/index.tsx index 4eb199225..8f8a6a5b9 100644 --- a/components/layout/index.tsx +++ b/components/layout/index.tsx @@ -24,7 +24,20 @@ const Layout = ({ Component, pageProps }: AppProps) => { - fetch(resource, init).then((res) => res.json()), + fetch(resource, init).then(async (res) => { + let data = await res.json(); + + if (Array.isArray(data)) { + data.map((entry) => { + if (!entry.updatedAt) return entry; + entry.updatedAt = new Date(entry.updatedAt); + + return entry; + }); + } + + return data; + }), revalidateOnFocus: false, revalidateOnReconnect: false, onError: (error) => { diff --git a/lib/types.ts b/lib/types.ts index a76bf5a68..c9746dd55 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -5,3 +5,5 @@ export type Unwrap = T extends Promise : T extends (...args: any) => infer U ? U : T; + +export type Unpacked = T extends (infer U)[] ? U : T; diff --git a/pages/builds/[[...slug]].tsx b/pages/builds/[[...slug]].tsx index abb2ecf3a..50de04a07 100644 --- a/pages/builds/[[...slug]].tsx +++ b/pages/builds/[[...slug]].tsx @@ -1,14 +1,16 @@ import { t } from "@lingui/macro"; +import BuildCard from "components/builds/BuildCard"; import Breadcrumbs from "components/common/Breadcrumbs"; import WeaponSelector from "components/common/WeaponSelector"; import { weaponToCode } from "lib/lists/weaponCodes"; +import { GetBuildsByWeaponData } from "prisma/queries/getBuildsByWeapon"; import { useState } from "react"; import useSWR from "swr"; const BuildsPage = () => { const [weapon, setWeapon] = useState(""); - const { data } = useSWR(() => { + const { data = [] } = useSWR(() => { if (!weapon) return null; const key = weapon as keyof typeof weaponToCode; @@ -21,6 +23,9 @@ const BuildsPage = () => { <> + {data.map((build) => ( + + ))} ); }; diff --git a/prisma/migrations/20201111140528-init/README.md b/prisma/migrations/20201111140528-init/README.md new file mode 100644 index 000000000..f3a284a2c --- /dev/null +++ b/prisma/migrations/20201111140528-init/README.md @@ -0,0 +1,72 @@ +# Migration `20201111140528-init` + +This migration has been generated by Kalle (Sendou) at 11/11/2020, 4:05:28 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +ALTER TABLE "Build" DROP COLUMN "mainAbilities", +DROP COLUMN "subAbilities", +ADD COLUMN "headAbilities" "Ability"[], +ADD COLUMN "clothingAbilities" "Ability"[], +ADD COLUMN "shoesAbilities" "Ability"[] +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20201109140409-init..20201111140528-init +--- datamodel.dml ++++ datamodel.dml +@@ -1,8 +1,8 @@ + datasource db { + provider = "postgresql" + // FIXME: should use same .env system as Next.JS +- url = "***" ++ url = "***" + } + generator client { + provider = "prisma-client-js" +@@ -93,23 +93,24 @@ + BRU + } + model Build { +- id Int @id @default(autoincrement()) +- userId Int +- weapon String +- title String? +- description String? +- mainAbilities Ability[] +- subAbilities Ability[] +- abilityPoints Json +- headGear String? +- clothingGear String? +- shoesGear String? +- top500 Boolean +- jpn Boolean +- updatedAt DateTime @updatedAt +- user User @relation(fields: [userId], references: [id]) ++ id Int @id @default(autoincrement()) ++ userId Int ++ weapon String ++ title String? ++ description String? ++ abilityPoints Json ++ headGear String? ++ headAbilities Ability[] ++ clothingGear String? ++ clothingAbilities Ability[] ++ shoesGear String? ++ shoesAbilities Ability[] ++ top500 Boolean ++ jpn Boolean ++ updatedAt DateTime @updatedAt ++ user User @relation(fields: [userId], references: [id]) + @@index(weapon) + @@index(userId) + @@index(abilityPoints) +``` + + diff --git a/prisma/migrations/20201111140528-init/schema.prisma b/prisma/migrations/20201111140528-init/schema.prisma new file mode 100644 index 000000000..0a9dcfb80 --- /dev/null +++ b/prisma/migrations/20201111140528-init/schema.prisma @@ -0,0 +1,117 @@ +datasource db { + provider = "postgresql" + // FIXME: should use same .env system as Next.JS + url = "***" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id @default(autoincrement()) + username String + discriminator String + discordId String @unique + discordAvatar String? + profile Profile? + player Player? + Build Build[] +} + +model Profile { + twitterName String? + twitchName String? + youtubeId String? + country String? + sensMotion Float? + sensStick Float? + bio String? + weaponPool String[] + customUrlPath String? @unique + user User @relation(fields: [userId], references: [id]) + userId Int @unique + +} + +enum RankedMode { + SZ + TC + RM + CB +} + +model XRankPlacement { + id Int @id @default(autoincrement()) + switchAccountId String + player Player @relation(fields: [switchAccountId], references: [switchAccountId]) + playerName String + ranking Int + xPower Float + weapon String + mode RankedMode + month Int + year Int + + @@unique([switchAccountId, mode, month, year]) +} + +model Player { + switchAccountId String @unique + userId Int? @unique + name String + placements XRankPlacement[] + user User? @relation(fields: [userId], references: [id]) +} + +enum Ability { + CB + LDE + OG + T + H + NS + TI + RP + AD + DR + SJ + OS + BDU + REC + RES + ISM + ISS + MPU + QR + QSJ + RSU + SSU + SCU + SPU + SS + BRU +} + +model Build { + id Int @id @default(autoincrement()) + userId Int + weapon String + title String? + description String? + abilityPoints Json + headGear String? + headAbilities Ability[] + clothingGear String? + clothingAbilities Ability[] + shoesGear String? + shoesAbilities Ability[] + top500 Boolean + jpn Boolean + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) + + @@index(weapon) + @@index(userId) + @@index(abilityPoints) +} diff --git a/prisma/migrations/20201111140528-init/steps.json b/prisma/migrations/20201111140528-init/steps.json new file mode 100644 index 000000000..7fa379f69 --- /dev/null +++ b/prisma/migrations/20201111140528-init/steps.json @@ -0,0 +1,36 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateField", + "model": "Build", + "field": "headAbilities", + "type": "Ability", + "arity": "List" + }, + { + "tag": "CreateField", + "model": "Build", + "field": "clothingAbilities", + "type": "Ability", + "arity": "List" + }, + { + "tag": "CreateField", + "model": "Build", + "field": "shoesAbilities", + "type": "Ability", + "arity": "List" + }, + { + "tag": "DeleteField", + "model": "Build", + "field": "mainAbilities" + }, + { + "tag": "DeleteField", + "model": "Build", + "field": "subAbilities" + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/migrate.lock b/prisma/migrations/migrate.lock index 2645d37ac..078275aec 100644 --- a/prisma/migrations/migrate.lock +++ b/prisma/migrations/migrate.lock @@ -2,4 +2,5 @@ 20201103144752-init 20201109002900-init -20201109140409-init \ No newline at end of file +20201109140409-init +20201111140528-init \ No newline at end of file diff --git a/prisma/queries/getBuildsByWeapon.ts b/prisma/queries/getBuildsByWeapon.ts index d2e06381c..34a16b336 100644 --- a/prisma/queries/getBuildsByWeapon.ts +++ b/prisma/queries/getBuildsByWeapon.ts @@ -15,5 +15,14 @@ export const getBuildsByWeapon = async ({ return prisma.build.findMany({ where: { weapon }, orderBy: [{ top500: "desc" }, { jpn: "desc" }, { updatedAt: "desc" }], + include: { + user: { + select: { + username: true, + discriminator: true, + discordId: true, + }, + }, + }, }); }; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a49fbca8e..a9f6e64c2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -94,21 +94,22 @@ enum Ability { } model Build { - id Int @id @default(autoincrement()) - userId Int - weapon String - title String? - description String? - mainAbilities Ability[] - subAbilities Ability[] - abilityPoints Json - headGear String? - clothingGear String? - shoesGear String? - top500 Boolean - jpn Boolean - updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id]) + id Int @id @default(autoincrement()) + userId Int + weapon String + title String? + description String? + abilityPoints Json + headGear String? + headAbilities Ability[] + clothingGear String? + clothingAbilities Ability[] + shoesGear String? + shoesAbilities Ability[] + top500 Boolean + jpn Boolean + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) @@index(weapon) @@index(userId) diff --git a/prisma/seed.ts b/prisma/seed.ts index 988aed796..64154c2f3 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -118,8 +118,9 @@ const main = async () => { }, }, weapon: "Splattershot Jr.", - mainAbilities: ["SS", "SS", "SS"], - subAbilities: ["SS", "SS", "SS", "SS", "SS", "SS", "SS", "SS", "SS"], + headAbilities: ["SS", "SS", "SS", "SS"], + clothingAbilities: ["SS", "SS", "SS", "SS"], + shoesAbilities: ["SS", "SS", "SS", "SS"], title: "Amazing test build", description: "Just testing.", top500: true,