world champs signup
Basic landing page to sign up for the "world champs"
This commit is contained in:
@@ -37,6 +37,7 @@ const config = {
|
|||||||
"warn",
|
"warn",
|
||||||
{ vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" },
|
{ vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" },
|
||||||
],
|
],
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
4
@types/nextjs-routes.d.ts
vendored
4
@types/nextjs-routes.d.ts
vendored
@@ -17,7 +17,9 @@ declare module "nextjs-routes" {
|
|||||||
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
|
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
|
||||||
| DynamicRoute<"/experiments/[id]", { "id": string }>
|
| DynamicRoute<"/experiments/[id]", { "id": string }>
|
||||||
| StaticRoute<"/experiments">
|
| StaticRoute<"/experiments">
|
||||||
| StaticRoute<"/">;
|
| StaticRoute<"/">
|
||||||
|
| StaticRoute<"/world-champs">
|
||||||
|
| StaticRoute<"/world-champs/signup">;
|
||||||
|
|
||||||
interface StaticRoute<Pathname> {
|
interface StaticRoute<Pathname> {
|
||||||
pathname: Pathname;
|
pathname: Pathname;
|
||||||
|
|||||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -1,4 +1,4 @@
|
|||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.1'
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "WorldChampEntrant" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"userId" UUID NOT NULL,
|
||||||
|
"approved" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "WorldChampEntrant_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "WorldChampEntrant_userId_key" ON "WorldChampEntrant"("userId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "WorldChampEntrant" ADD CONSTRAINT "WorldChampEntrant_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -112,24 +112,24 @@ model ScenarioVariantCell {
|
|||||||
model ModelResponse {
|
model ModelResponse {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
inputHash String
|
inputHash String
|
||||||
requestedAt DateTime?
|
requestedAt DateTime?
|
||||||
receivedAt DateTime?
|
receivedAt DateTime?
|
||||||
output Json?
|
output Json?
|
||||||
cost Float?
|
cost Float?
|
||||||
promptTokens Int?
|
promptTokens Int?
|
||||||
completionTokens Int?
|
completionTokens Int?
|
||||||
statusCode Int?
|
statusCode Int?
|
||||||
errorMessage String?
|
errorMessage String?
|
||||||
retryTime DateTime?
|
retryTime DateTime?
|
||||||
outdated Boolean @default(false)
|
outdated Boolean @default(false)
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
scenarioVariantCellId String @db.Uuid
|
scenarioVariantCellId String @db.Uuid
|
||||||
scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade)
|
scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade)
|
||||||
outputEvaluations OutputEvaluation[]
|
outputEvaluations OutputEvaluation[]
|
||||||
|
|
||||||
@@index([inputHash])
|
@@index([inputHash])
|
||||||
}
|
}
|
||||||
@@ -150,8 +150,8 @@ model Evaluation {
|
|||||||
experimentId String @db.Uuid
|
experimentId String @db.Uuid
|
||||||
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
outputEvaluations OutputEvaluation[]
|
outputEvaluations OutputEvaluation[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ model OutputEvaluation {
|
|||||||
result Float
|
result Float
|
||||||
details String?
|
details String?
|
||||||
|
|
||||||
modelResponseId String @db.Uuid
|
modelResponseId String @db.Uuid
|
||||||
modelResponse ModelResponse @relation(fields: [modelResponseId], references: [id], onDelete: Cascade)
|
modelResponse ModelResponse @relation(fields: [modelResponseId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
evaluationId String @db.Uuid
|
evaluationId String @db.Uuid
|
||||||
@@ -179,8 +179,8 @@ model Organization {
|
|||||||
personalOrgUserId String? @unique @db.Uuid
|
personalOrgUserId String? @unique @db.Uuid
|
||||||
PersonalOrgUser User? @relation(fields: [personalOrgUserId], references: [id], onDelete: Cascade)
|
PersonalOrgUser User? @relation(fields: [personalOrgUserId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
organizationUsers OrganizationUser[]
|
organizationUsers OrganizationUser[]
|
||||||
experiments Experiment[]
|
experiments Experiment[]
|
||||||
}
|
}
|
||||||
@@ -208,6 +208,20 @@ model OrganizationUser {
|
|||||||
@@unique([organizationId, userId])
|
@@unique([organizationId, userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model WorldChampEntrant {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
|
||||||
|
userId String @db.Uuid
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
approved Boolean @default(false)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@unique([userId])
|
||||||
|
}
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
userId String @db.Uuid
|
userId String @db.Uuid
|
||||||
@@ -245,6 +259,7 @@ model User {
|
|||||||
sessions Session[]
|
sessions Session[]
|
||||||
organizationUsers OrganizationUser[]
|
organizationUsers OrganizationUser[]
|
||||||
organizations Organization[]
|
organizations Organization[]
|
||||||
|
worldChampEntrant WorldChampEntrant?
|
||||||
}
|
}
|
||||||
|
|
||||||
model VerificationToken {
|
model VerificationToken {
|
||||||
|
|||||||
@@ -84,7 +84,11 @@ const NavSidebar = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
{user ? <UserMenu user={user} /> : <Divider />}
|
{user ? (
|
||||||
|
<UserMenu user={user} borderColor={"gray.200"} borderTopWidth={1} borderBottomWidth={1} />
|
||||||
|
) : (
|
||||||
|
<Divider />
|
||||||
|
)}
|
||||||
<VStack spacing={0} align="center">
|
<VStack spacing={0} align="center">
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/openpipe/openpipe"
|
href="https://github.com/openpipe/openpipe"
|
||||||
|
|||||||
@@ -8,12 +8,16 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
Link,
|
Link,
|
||||||
|
useColorMode,
|
||||||
|
type StackProps,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { type Session } from "next-auth";
|
import { type Session } from "next-auth";
|
||||||
import { signOut } from "next-auth/react";
|
import { signOut } from "next-auth/react";
|
||||||
import { BsBoxArrowRight, BsChevronRight, BsPersonCircle } from "react-icons/bs";
|
import { BsBoxArrowRight, BsChevronRight, BsPersonCircle } from "react-icons/bs";
|
||||||
|
|
||||||
export default function UserMenu({ user }: { user: Session }) {
|
export default function UserMenu({ user, ...rest }: { user: Session } & StackProps) {
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
const profileImage = user.user.image ? (
|
const profileImage = user.user.image ? (
|
||||||
<Image src={user.user.image} alt="profile picture" boxSize={8} borderRadius="50%" />
|
<Image src={user.user.image} alt="profile picture" boxSize={8} borderRadius="50%" />
|
||||||
) : (
|
) : (
|
||||||
@@ -29,12 +33,10 @@ export default function UserMenu({ user }: { user: Session }) {
|
|||||||
px={3}
|
px={3}
|
||||||
spacing={3}
|
spacing={3}
|
||||||
py={2}
|
py={2}
|
||||||
borderColor={"gray.200"}
|
{...rest}
|
||||||
borderTopWidth={1}
|
|
||||||
borderBottomWidth={1}
|
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
_hover={{
|
_hover={{
|
||||||
bgColor: "gray.200",
|
bgColor: colorMode === "light" ? "gray.200" : "gray.700",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{profileImage}
|
{profileImage}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const replicate = new Replicate({
|
|||||||
const modelIds: Record<ReplicateLlama2Input["model"], string> = {
|
const modelIds: Record<ReplicateLlama2Input["model"], string> = {
|
||||||
"7b-chat": "5ec5fdadd80ace49f5a2b2178cceeb9f2f77c493b85b1131002c26e6b2b13184",
|
"7b-chat": "5ec5fdadd80ace49f5a2b2178cceeb9f2f77c493b85b1131002c26e6b2b13184",
|
||||||
"13b-chat": "6b4da803a2382c08868c5af10a523892f38e2de1aafb2ee55b020d9efef2fdb8",
|
"13b-chat": "6b4da803a2382c08868c5af10a523892f38e2de1aafb2ee55b020d9efef2fdb8",
|
||||||
"70b-chat": "2d19859030ff705a87c746f7e96eea03aefb71f166725aee39692f1476566d48",
|
"70b-chat": "2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1",
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getCompletion(
|
export async function getCompletion(
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const MyApp: AppType<{ session: Session | null }> = ({
|
|||||||
<meta
|
<meta
|
||||||
name="og:description"
|
name="og:description"
|
||||||
content="OpenPipe is a powerful playground for quickly optimizing performance, cost, and speed across models."
|
content="OpenPipe is a powerful playground for quickly optimizing performance, cost, and speed across models."
|
||||||
|
key="description"
|
||||||
/>
|
/>
|
||||||
<meta name="og:image" content="/og.png" key="og-image" />
|
<meta name="og:image" content="/og.png" key="og-image" />
|
||||||
<meta property="og:image:height" content="630" />
|
<meta property="og:image:height" content="630" />
|
||||||
|
|||||||
15
src/pages/world-champs/index.tsx
Normal file
15
src/pages/world-champs/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { type GetServerSideProps } from "next";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
export const getServerSideProps: GetServerSideProps = async () => {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/world-champs/signup",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function WorldChamps() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
201
src/pages/world-champs/signup.tsx
Normal file
201
src/pages/world-champs/signup.tsx
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
type BoxProps,
|
||||||
|
Button,
|
||||||
|
DarkMode,
|
||||||
|
GlobalStyle,
|
||||||
|
HStack,
|
||||||
|
Heading,
|
||||||
|
Icon,
|
||||||
|
Link,
|
||||||
|
Table,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Text,
|
||||||
|
type TextProps,
|
||||||
|
Th,
|
||||||
|
Tr,
|
||||||
|
VStack,
|
||||||
|
useInterval,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { signIn, useSession } from "next-auth/react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import { BsGithub } from "react-icons/bs";
|
||||||
|
import UserMenu from "~/components/nav/UserMenu";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
import dayjs from "~/utils/dayjs";
|
||||||
|
import { useHandledAsyncCallback } from "~/utils/hooks";
|
||||||
|
|
||||||
|
// Shows how long until the competition starts. Refreshes every second
|
||||||
|
function CountdownTimer(props: { date: Date } & TextProps) {
|
||||||
|
const [now, setNow] = useState(dayjs(0));
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
setNow(dayjs());
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
const { date, ...rest } = props;
|
||||||
|
|
||||||
|
const kickoff = dayjs(props.date);
|
||||||
|
const diff = kickoff.diff(now, "second");
|
||||||
|
const days = Math.floor(diff / 86400);
|
||||||
|
const hours = Math.floor((diff % 86400) / 3600);
|
||||||
|
const minutes = Math.floor((diff % 3600) / 60);
|
||||||
|
const seconds = Math.floor(diff % 60);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text {...rest}>
|
||||||
|
<Text as="span" fontWeight="bold">
|
||||||
|
Kickoff in
|
||||||
|
</Text>{" "}
|
||||||
|
{days}d {hours}h {minutes}m {seconds}s
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApplicationStatus(props: BoxProps) {
|
||||||
|
const user = useSession().data;
|
||||||
|
const entrant = api.worldChamps.userStatus.useQuery().data;
|
||||||
|
const applyMutation = api.worldChamps.apply.useMutation();
|
||||||
|
|
||||||
|
const utils = api.useContext();
|
||||||
|
|
||||||
|
const [onSignIn] = useHandledAsyncCallback(async () => {
|
||||||
|
await signIn("github");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [onApply] = useHandledAsyncCallback(async () => {
|
||||||
|
await applyMutation.mutateAsync();
|
||||||
|
await utils.worldChamps.userStatus.invalidate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const Wrapper = useCallback(
|
||||||
|
(wrapperProps: BoxProps) => (
|
||||||
|
<Box {...props} {...wrapperProps} minH="120px" alignItems="center" justifyItems="center" />
|
||||||
|
),
|
||||||
|
[props],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (user === null) {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Button onClick={onSignIn} colorScheme="orange" leftIcon={<Icon as={BsGithub} />}>
|
||||||
|
Connect GitHub to apply
|
||||||
|
</Button>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
} else if (user) {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<HStack spacing={8}>
|
||||||
|
<UserMenu user={user} borderRadius={2} borderColor={"gray.700"} borderWidth={1} pr={6} />
|
||||||
|
<Box flex={1}>
|
||||||
|
{entrant?.approved ? (
|
||||||
|
<Text fontSize="sm">
|
||||||
|
You're accepted! We'll send you more details before August 14th.
|
||||||
|
</Text>
|
||||||
|
) : entrant ? (
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Application submitted successfully! We'll notify you by email before August 14th.
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Button onClick={onApply} colorScheme="orange">
|
||||||
|
Apply to compete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</HStack>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Wrapper />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Signup() {
|
||||||
|
return (
|
||||||
|
<DarkMode>
|
||||||
|
<GlobalStyle />
|
||||||
|
|
||||||
|
<Head>
|
||||||
|
<title>🏆 Prompt Engineering World Championships</title>
|
||||||
|
<meta property="og:title" content="🏆 Prompt Engineering World Championships" key="title" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Think you have what it takes to be the best? Compete with the world's top prompt engineers and see where you rank!"
|
||||||
|
key="description"
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<Box bgColor="gray.900" color="gray.200" minH="100vh" w="full">
|
||||||
|
<VStack mx="auto" py={24} maxW="2xl" align="start" fontSize="lg">
|
||||||
|
<Heading size="lg">🏆 Prompt Engineering World Championships</Heading>
|
||||||
|
<CountdownTimer
|
||||||
|
date={new Date("2023-08-14T00:00:00Z")}
|
||||||
|
fontSize="2xl"
|
||||||
|
alignSelf="center"
|
||||||
|
color="gray.500"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ApplicationStatus py={8} alignSelf="center" />
|
||||||
|
|
||||||
|
<Text fontSize="lg">
|
||||||
|
Think you have what it takes to be the best? Compete with the world's top prompt
|
||||||
|
engineers and see where you rank!
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Heading size="lg" pt={12} alignSelf="left">
|
||||||
|
Event Details
|
||||||
|
</Heading>
|
||||||
|
<Table variant="simple">
|
||||||
|
<Tbody>
|
||||||
|
<Tr>
|
||||||
|
<Th>Kickoff</Th>
|
||||||
|
<Td>August 14</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Th>Prize</Th>
|
||||||
|
<Td>$15,000 grand prize + smaller category prizes.</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Th>Events</Th>
|
||||||
|
<Td>
|
||||||
|
Optimize prompts for multiple tasks selected from academic benchmarks and
|
||||||
|
real-world applications.
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Th>Models</Th>
|
||||||
|
<Td>Separate "weight classes" for GPT 3.5, Claude Instant, and Llama 2.</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Th>Qualifications</Th>
|
||||||
|
<Td>Open to entrants with any level of experience.</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Th>Certificates</Th>
|
||||||
|
<Td>Certificate of mastery for all qualifying participants.</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Th>Cost</Th>
|
||||||
|
<Td>
|
||||||
|
<strong>Free</strong>. We'll cover your inference budget.
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Th>Questions?</Th>
|
||||||
|
<Td>
|
||||||
|
<Link href="mailto:world-champs@openpipe.ai" textDecor="underline">
|
||||||
|
Email us
|
||||||
|
</Link>{" "}
|
||||||
|
with any follow-up questions!
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
</DarkMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { scenariosRouter } from "./routers/scenarios.router";
|
|||||||
import { scenarioVariantCellsRouter } from "./routers/scenarioVariantCells.router";
|
import { scenarioVariantCellsRouter } from "./routers/scenarioVariantCells.router";
|
||||||
import { templateVarsRouter } from "./routers/templateVariables.router";
|
import { templateVarsRouter } from "./routers/templateVariables.router";
|
||||||
import { evaluationsRouter } from "./routers/evaluations.router";
|
import { evaluationsRouter } from "./routers/evaluations.router";
|
||||||
|
import { worldChampsRouter } from "./routers/worldChamps.router";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
@@ -18,6 +19,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
scenarioVariantCells: scenarioVariantCellsRouter,
|
scenarioVariantCells: scenarioVariantCellsRouter,
|
||||||
templateVars: templateVarsRouter,
|
templateVars: templateVarsRouter,
|
||||||
evaluations: evaluationsRouter,
|
evaluations: evaluationsRouter,
|
||||||
|
worldChamps: worldChampsRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
36
src/server/api/routers/worldChamps.router.ts
Normal file
36
src/server/api/routers/worldChamps.router.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/api/trpc";
|
||||||
|
import { prisma } from "~/server/db";
|
||||||
|
import { requireNothing } from "~/utils/accessControl";
|
||||||
|
|
||||||
|
export const worldChampsRouter = createTRPCRouter({
|
||||||
|
userStatus: publicProcedure.query(async ({ input, ctx }) => {
|
||||||
|
const userId = ctx.session?.user.id;
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.worldChampEntrant.findUnique({
|
||||||
|
where: { userId },
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
apply: protectedProcedure.mutation(async ({ ctx }) => {
|
||||||
|
const userId = ctx.session.user.id;
|
||||||
|
requireNothing(ctx);
|
||||||
|
|
||||||
|
const existingEntrant = await prisma.worldChampEntrant.findUnique({
|
||||||
|
where: { userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingEntrant) {
|
||||||
|
return existingEntrant;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.worldChampEntrant.create({
|
||||||
|
data: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -7,3 +7,5 @@ dayjs.extend(relativeTime);
|
|||||||
|
|
||||||
export const formatTimePast = (date: Date) =>
|
export const formatTimePast = (date: Date) =>
|
||||||
dayjs.duration(dayjs(date).diff(dayjs())).humanize(true);
|
dayjs.duration(dayjs(date).diff(dayjs())).humanize(true);
|
||||||
|
|
||||||
|
export default dayjs;
|
||||||
|
|||||||
Reference in New Issue
Block a user