Share types between backend and frontend

This commit is contained in:
Kalle (Sendou) 2021-10-25 16:16:20 +03:00
parent e7bf162ab9
commit 90408ca7f9
11 changed files with 7477 additions and 103 deletions

7419
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,10 @@
{
"name": "sendou.ink",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/server",
"packages/frontend"
"packages/frontend",
"packages/api"
]
}

1
packages/api/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./tournaments.api";

View File

@ -0,0 +1,4 @@
{
"name": "@sendou-ink/api",
"version": "1.0.0"
}

View File

@ -0,0 +1,14 @@
export interface GetTournamentByOrganizationAndName {
name: string;
desription: string | null;
startTime: Date;
checkInTime: Date;
bannerBackground: string;
bannerTextColor: string;
organizer: {
name: string;
discordInvite: string;
twitter: string | null;
nameForUrl: string;
};
}

View File

@ -1,6 +1,6 @@
import styled from "@emotion/styled";
import { ReactNode } from "react";
import { FaDiscord, FaTwitter } from "react-icons/fa";
import { useTournamentData } from "../../hooks/data/useTournamentData";
const infos = [
{
@ -91,37 +91,6 @@ const _IconButtons = styled.div`
gap: 1rem;
`;
export function InfoBanner() {
return (
<_Container>
<_TopRow>
<_DateName>
<_MonthDate>
<_Month>APR</_Month>
<_Date>23</_Date>
</_MonthDate>
<_TournamentName>In The Zone 23</_TournamentName>
</_DateName>
<_IconButtons>
<IconButton href="https://twitter.com/sendouc">
<FaTwitter />
</IconButton>
<IconButton href="https://discord.gg/sendou">
<FaDiscord />
</IconButton>
</_IconButtons>
</_TopRow>
<_BottomRow>
<_Infos>
{infos.map((info) => (
<Info key={info.title} info={info} />
))}
</_Infos>
</_BottomRow>
</_Container>
);
}
const _InfoContainer = styled.div`
font-size: var(--fonts-xs);
@ -130,15 +99,6 @@ const _InfoContainer = styled.div`
}
`;
function Info(props: { info: { title: string; value: string } }) {
return (
<_InfoContainer>
<label>{props.info.title}</label>
<div>{props.info.value}</div>
</_InfoContainer>
);
}
const _IconButton = styled.a`
display: inline-flex;
width: 2.25rem;
@ -159,6 +119,44 @@ const _IconButton = styled.a`
}
`;
function IconButton(props: { children: ReactNode; href: string }) {
return <_IconButton href={props.href}>{props.children}</_IconButton>;
export function InfoBanner() {
const { data } = useTournamentData();
// TODO: handle loading
// TODO: handle error in parent
if (!data) return null;
return (
<_Container>
<_TopRow>
<_DateName>
<_MonthDate>
<_Month>APR</_Month>
<_Date>23</_Date>
</_MonthDate>
<_TournamentName>{data.name}</_TournamentName>
</_DateName>
<_IconButtons>
{data.organizer.twitter && (
<_IconButton href={data.organizer.twitter}>
<FaTwitter />
</_IconButton>
)}
<_IconButton href={data.organizer.discordInvite}>
<FaDiscord />
</_IconButton>
</_IconButtons>
</_TopRow>
<_BottomRow>
<_Infos>
{infos.map((info) => (
<_InfoContainer>
<label>{info.title}</label>
<div>{info.value}</div>
</_InfoContainer>
))}
</_Infos>
</_BottomRow>
</_Container>
);
}

View File

@ -0,0 +1,15 @@
import useSWR from "swr";
import type { GetTournamentByOrganizationAndName } from "@sendou-ink/api";
import { useRouter } from "next/dist/client/router";
export function useTournamentData() {
const router = useRouter();
const { organization, tournament } = router.query;
const result = useSWR<GetTournamentByOrganizationAndName>(
typeof organization === "string" && typeof tournament === "string"
? `/tournaments/${organization}/${tournament}`
: null
);
return result;
}

View File

@ -1,13 +1,5 @@
import { useRouter } from "next/dist/client/router";
import useSWR from "swr";
import { InfoBanner } from "../../../components/tournament/InfoBanner";
export default function TournamentPage() {
const router = useRouter();
const { data, error } = useSWR(
router.query.organization && router.query.tournament
? `/tournaments/${router.query.organization}/${router.query.tournament}`
: null
);
return <InfoBanner />;
}

View File

@ -1,11 +1,11 @@
{
"name": "server.sendou.ink",
"name": "@sendou.ink/server",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "server.sendou.ink",
"name": "@sendou.ink/server",
"version": "1.0.0",
"dependencies": {
"@prisma/client": "^3.3.0",

View File

@ -1,14 +1,16 @@
import { App } from "@tinyhttp/app";
import { findTournamentByNameForUrl } from "../services/tournament";
import type { GetTournamentByOrganizationAndName } from "@sendou-ink/api";
const app = new App();
app.get("/:organization/:tournament", async (req, res) => {
const { organization, tournament } = req.params;
const tournamentFromDB = await findTournamentByNameForUrl({
organizationNameForUrl: organization,
tournamentNameForUrl: tournament,
});
const tournamentFromDB: GetTournamentByOrganizationAndName | undefined =
await findTournamentByNameForUrl({
organizationNameForUrl: organization,
tournamentNameForUrl: tournament,
});
if (!tournamentFromDB) return res.sendStatus(404);

View File

@ -11,8 +11,21 @@ export async function findTournamentByNameForUrl({
where: {
nameForUrl: tournamentNameForUrl.toLowerCase(),
},
include: {
organizer: true,
select: {
name: true,
desription: true,
startTime: true,
checkInTime: true,
bannerBackground: true,
bannerTextColor: true,
organizer: {
select: {
name: true,
discordInvite: true,
twitter: true,
nameForUrl: true,
},
},
},
});