s
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"use server";
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { uidRegex } from "@/lib/const";
|
||||
import { db } from "@/lib/db";
|
||||
import {
|
||||
characters,
|
||||
endgameSettings,
|
||||
endgameSubmissions,
|
||||
settings,
|
||||
submissions,
|
||||
} from "@/lib/db/schema";
|
||||
import { sse } from "@/lib/utils";
|
||||
import type { TypedFormData } from "./type";
|
||||
|
||||
export async function getEndgameConfig() {
|
||||
const [ngm]: (typeof endgameSettings.$inferSelect | undefined)[] = await db
|
||||
.select()
|
||||
.from(endgameSettings)
|
||||
.limit(1);
|
||||
const [glob]: (typeof settings.$inferSelect | undefined)[] = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.limit(1);
|
||||
const count = await db.$count(endgameSubmissions);
|
||||
const full = ngm?.limit !== -1 && count >= (ngm?.limit || 0);
|
||||
|
||||
return {
|
||||
locked: false,
|
||||
limit: -1,
|
||||
enka: false,
|
||||
count,
|
||||
full,
|
||||
...ngm,
|
||||
...glob,
|
||||
};
|
||||
}
|
||||
|
||||
export async function submitEndgame(
|
||||
formData: TypedFormData<{
|
||||
name: string;
|
||||
uid: string;
|
||||
character: string;
|
||||
comment: string;
|
||||
}>,
|
||||
) {
|
||||
const { full, locked, limit } = await getEndgameConfig();
|
||||
const name = formData.get("name")?.toString();
|
||||
const uid = formData.get("uid")?.toString();
|
||||
const character = formData.get("character")?.toString();
|
||||
const comment = formData.get("comment")?.toString() || "";
|
||||
if (!name || !uid || !character) return "กรุณากรอกข้อมูลให้ครบถ้วน";
|
||||
if (name.length > 32) return "ชื่อยาวเกินไป ต้องไม่เกิน 32 ตัวอักษร";
|
||||
if (!uidRegex.test(uid)) return "UID ไม่ถูกต้อง ต้องเป็นเลข 9 หลัก";
|
||||
if (comment.length > 256) return "ข้อความเพิ่มเติมยาวเกินไป ต้องไม่เกิน 512 ตัวอักษร";
|
||||
if (locked) return "ปิดรับลงทะเบียนชั่วคราว เนื่องจากมีผู้ลงจำนวนมาก";
|
||||
if (full) return `คิวลงทะเบียนเต็มแล้ว (${limit} ครั้ง)`;
|
||||
const [char] = await db
|
||||
.select()
|
||||
.from(characters)
|
||||
.where(eq(characters.name, character))
|
||||
.limit(1);
|
||||
if (!char) return "ไม่พบตัวละครที่เลือก";
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(submissions)
|
||||
.where(eq(submissions.uid, uid))
|
||||
.limit(1);
|
||||
if (existing) return "คุณลงทะเบียนไปแล้ว";
|
||||
const [queue] = await db
|
||||
.insert(submissions)
|
||||
.values({
|
||||
uid,
|
||||
name,
|
||||
comment,
|
||||
char: char.name,
|
||||
})
|
||||
.returning({ queue: endgameSubmissions.queue, id: endgameSubmissions.id });
|
||||
revalidatePath("/artifact/admin");
|
||||
sse.publish({}, { topic: "artifact-ev", event: "update" });
|
||||
return queue;
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
export function EndgameForm() {
|
||||
return <form></form>;
|
||||
}
|
||||
}
|
||||
|
||||
+169
-7
@@ -1,14 +1,176 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { BookAlert, CircleDollarSign, SendHorizonal } from "lucide-react";
|
||||
import { cookies } from "next/headers";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Suspense } from "react";
|
||||
import { PageTransition } from "@/app/transition";
|
||||
import { Blocker } from "@/components/blocker";
|
||||
import { EndgameForm } from "./form";
|
||||
import { SimpleTooltip } from "@/components/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { db } from "@/lib/db";
|
||||
import { endgameSubmissions } from "@/lib/db/schema";
|
||||
import { ClearCookie, Disclaimer } from "../artifact/client";
|
||||
import { ArtifactFormWrapper } from "../artifact/form";
|
||||
import { LiveButton } from "../artifact/live";
|
||||
import { RulesDialog } from "../artifact/rules";
|
||||
import { getEndgameConfig } from "./api";
|
||||
import banner from "#/logos/artifact.webp";
|
||||
|
||||
export default async function EndgamePage() {
|
||||
const cookie = await cookies();
|
||||
const sid = cookie.get("sid");
|
||||
const [q] = sid?.value
|
||||
? await db
|
||||
.select({ queue: endgameSubmissions.queue })
|
||||
.from(endgameSubmissions)
|
||||
.where(eq(endgameSubmissions.id, sid.value))
|
||||
: [];
|
||||
const { count, ...config } = await getEndgameConfig();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full justify-center items-center">
|
||||
<div className="rounded flex flex-col bg-card border p-2">
|
||||
<Blocker>ขณะนี้คิวเต็มแล้ว</Blocker>
|
||||
<h1>รับลงเอนเกคอนเท้น</h1>
|
||||
<EndgameForm />
|
||||
<PageTransition>
|
||||
<div className="flex flex-col justify-around items-center h-svh">
|
||||
<Card className="w-full max-w-md">
|
||||
{q ? (
|
||||
<Blocker>
|
||||
<div className="flex gap-1 flex-col items-center">
|
||||
<span className="font-bold text-3xl">คิวของคุณคือหมายเลข</span>
|
||||
<span className="font-bold text-5xl">{q.queue}</span>
|
||||
</div>
|
||||
</Blocker>
|
||||
) : config.locked ? (
|
||||
<Blocker>
|
||||
<span className="font-bold text-3xl">ยังไม่เปิดรับคิว</span>
|
||||
</Blocker>
|
||||
) : config.limit >= 0 && count >= config.limit ? (
|
||||
<Blocker>
|
||||
<div className="flex gap-1 flex-col items-center">
|
||||
<span className="font-bold text-3xl">คิวเต็มแล้ว</span>
|
||||
<span className="font-bold text-2xl">ต้องโดเนทลัดคิวแล้วล่ะ</span>
|
||||
</div>
|
||||
</Blocker>
|
||||
) : (
|
||||
<Disclaimer />
|
||||
)}
|
||||
<CardHeader className="justify-center">
|
||||
<CardTitle>
|
||||
<div className="w-[276.5px]">
|
||||
<Link href="/">
|
||||
<Image
|
||||
style={{
|
||||
transform: "translateY(-70%)",
|
||||
}}
|
||||
className="absolute z-50"
|
||||
height={137.5}
|
||||
width={276.5}
|
||||
src={banner}
|
||||
alt="เสือกไอดีชาวบ้าน"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ArtifactFormWrapper id="mainform">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">ชื่อ*</Label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="Mr.Buzz"
|
||||
autoComplete="name"
|
||||
maxLength={32}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="comment">ข้อความเพิ่มเติม</Label>
|
||||
<Textarea
|
||||
id="comment"
|
||||
name="comment"
|
||||
placeholder="เช่น Er พอไหมครับ, คริสวยยังครับ (ไม่บังคับ)"
|
||||
className="bg-card!"
|
||||
maxLength={512}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ArtifactFormWrapper>
|
||||
</CardContent>
|
||||
<CardFooter className="flex gap-2 justify-between">
|
||||
<div className="flex gap-2">
|
||||
<SimpleTooltip text="โดเนทลัดคิว ขั้นต่ำ 10 บาท">
|
||||
<Link
|
||||
href="https://tipme.in.th/536d969652666273c2fa85ad"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={
|
||||
config.limit >= 0 && count >= config.limit
|
||||
? "bg-emerald-600! border-white! animate-pulse"
|
||||
: ""
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<CircleDollarSign />
|
||||
</Button>
|
||||
</Link>
|
||||
</SimpleTooltip>
|
||||
<Tooltip>
|
||||
<RulesDialog>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="destructive" type="button">
|
||||
<BookAlert />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
</RulesDialog>
|
||||
<TooltipContent>อ่านกฏการลงคิว</TooltipContent>
|
||||
</Tooltip>
|
||||
<Suspense>
|
||||
<LiveButton />
|
||||
</Suspense>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<span className="p-1">
|
||||
{count} / {config.limit < 0 ? "∞" : config.limit}
|
||||
</span>
|
||||
<SimpleTooltip text="ส่งเลยจัฟลูกพี่">
|
||||
<Button
|
||||
type="submit"
|
||||
form="mainform"
|
||||
disabled={
|
||||
!!q ||
|
||||
config.locked ||
|
||||
(config.limit >= 0 && count >= config.limit)
|
||||
}
|
||||
>
|
||||
<SendHorizonal />
|
||||
</Button>
|
||||
</SimpleTooltip>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
{sid && !q && <ClearCookie />}
|
||||
</div>
|
||||
</div>
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
export type TypedFormDataShape = Record<
|
||||
string,
|
||||
string | Blob | File | (string | Blob | File)[]
|
||||
>;
|
||||
|
||||
export interface TypedFormData<T extends TypedFormDataShape> {
|
||||
append<K extends keyof T>(
|
||||
key: K,
|
||||
value: T[K] extends unknown[] ? T[K][number] : T[K],
|
||||
): void;
|
||||
|
||||
set<K extends keyof T>(
|
||||
key: K,
|
||||
value: T[K] extends unknown[] ? T[K][number] : T[K],
|
||||
): void;
|
||||
|
||||
get<K extends keyof T>(
|
||||
key: K,
|
||||
): T[K] extends unknown[] ? T[K][number] | null : T[K] | null;
|
||||
|
||||
getAll<K extends keyof T>(key: K): T[K] extends unknown[] ? T[K] : T[K][];
|
||||
|
||||
delete<K extends keyof T>(key: K): void;
|
||||
|
||||
has<K extends keyof T>(key: K): boolean;
|
||||
|
||||
/** underlying raw FormData */
|
||||
readonly raw: FormData;
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { defineConfig } from "drizzle-kit";
|
||||
export default defineConfig({
|
||||
out: "./drizzle",
|
||||
schema: ["./lib/db/schema.ts", "./lib/db/references.ts"],
|
||||
schemaFilter: ["public", "artifact", "tierlist"],
|
||||
schemaFilter: ["public", "artifact", "tierlist", "endgame"],
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
|
||||
+11
-4
@@ -5,6 +5,7 @@ import type { PgDatabase } from "drizzle-orm/pg-core";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { after } from "next/server";
|
||||
import type { TypedFormData } from "@/app/(ui)/endgame/type";
|
||||
import { apiAuthCheck } from "./auth";
|
||||
import { uidRegex } from "./const";
|
||||
import { db } from "./db";
|
||||
@@ -36,11 +37,17 @@ export async function getArtifactConfig() {
|
||||
}
|
||||
|
||||
export async function submitArtifact(formData: FormData) {
|
||||
const form = formData as unknown as TypedFormData<{
|
||||
name: string;
|
||||
uid: string;
|
||||
character: string;
|
||||
comment: string;
|
||||
}>;
|
||||
const config = await getArtifactConfig();
|
||||
const name = formData.get("name")?.toString();
|
||||
const uid = formData.get("uid")?.toString();
|
||||
const character = formData.get("character")?.toString();
|
||||
const comment = formData.get("comment")?.toString() || "";
|
||||
const name = form.get("name")?.toString();
|
||||
const uid = form.get("uid")?.toString();
|
||||
const character = form.get("character")?.toString();
|
||||
const comment = form.get("comment")?.toString() || "";
|
||||
if (!name || !uid || !character) return "กรุณากรอกข้อมูลให้ครบถ้วน";
|
||||
if (name.length > 32) return "ชื่อยาวเกินไป ต้องไม่เกิน 32 ตัวอักษร";
|
||||
if (!uidRegex.test(uid)) return "UID ไม่ถูกต้อง ต้องเป็นเลข 9 หลัก";
|
||||
|
||||
+1
-1
@@ -38,5 +38,5 @@ export async function apiAuthCheck() {
|
||||
|
||||
// console.log(session);
|
||||
|
||||
return session?.user;
|
||||
return session?.user.role === "admin" ? session.user : null;
|
||||
}
|
||||
|
||||
+6
-7
@@ -109,13 +109,7 @@ export const endgameSubmissions = endgame.table("submissions", {
|
||||
id: text().primaryKey().$defaultFn(uuidv7),
|
||||
name: text().notNull(),
|
||||
user: text().notNull(),
|
||||
slip: bytea().notNull(),
|
||||
char: text()
|
||||
.notNull()
|
||||
.references(() => characters.name, {
|
||||
onDelete: "cascade",
|
||||
onUpdate: "cascade",
|
||||
}),
|
||||
expires: timestamp().$defaultFn(() => new Date(Date.now() + 15 * 60 * 1000)), // 15 minutes to pay
|
||||
queue: serial(),
|
||||
checked: boolean().notNull().default(false),
|
||||
});
|
||||
@@ -244,6 +238,10 @@ export const user = pgTable("user", {
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
role: text("role"),
|
||||
banned: boolean("banned").default(false),
|
||||
banReason: text("ban_reason"),
|
||||
banExpires: timestamp("ban_expires"),
|
||||
});
|
||||
|
||||
export const session = pgTable("session", {
|
||||
@@ -259,6 +257,7 @@ export const session = pgTable("session", {
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
impersonatedBy: text("impersonated_by"),
|
||||
});
|
||||
|
||||
export const account = pgTable("account", {
|
||||
|
||||
Reference in New Issue
Block a user