feat(rubgram): add submit another and submission list modal

This commit is contained in:
2026-06-20 14:07:58 +00:00 Unverified
parent 4559f4693c
commit 4180883cfc
3 changed files with 198 additions and 27 deletions
+31
View File
@@ -178,6 +178,37 @@ export async function getEndgameConfig() {
};
}
export async function getUserSubmissions(uid: string) {
const types = await db.select().from(endgameTypes);
const typeNames = Object.fromEntries(types.map((t) => [t.id, t.display]));
const subs = await db
.select({
id: endgameSubmissions.id,
queue: endgameSubmissions.queue,
name: endgameSubmissions.name,
server: endgameSubmissions.server,
price: endgameSubmissions.price,
paid: endgameSubmissions.paid,
checked: endgameSubmissions.checked,
deleted: endgameSubmissions.deleted,
expires: endgameSubmissions.expires,
service: endgameSubmissions.service,
submitDay: endgameSubmissions.submit_day,
})
.from(endgameSubmissions)
.where(
and(eq(endgameSubmissions.user, uid), not(endgameSubmissions.deleted)),
)
.orderBy(desc(endgameSubmissions.submit_day))
.limit(20);
return subs.map((s) => ({
...s,
services: s.service.map((id) => typeNames[id] || id),
}));
}
export async function submitEndgame(formData: FormData) {
const { full, locked, limit, types, free } = await getEndgameConfig();
const name = formData.get("name") as string;
+120 -1
View File
@@ -1,12 +1,30 @@
"use client";
import { ArrowRight, Check, Download, LogIn, ScrollText } from "lucide-react";
import {
ArrowRight,
Check,
Circle,
Clock,
Download,
List,
LogIn,
Plus,
ScrollText,
} from "lucide-react";
import { useRouter } from "next/navigation";
import posthog from "posthog-js";
import { type ReactNode, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { Blocker } from "@/components/blocker";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Kbd } from "@/components/ui/kbd";
import { Label } from "@/components/ui/label";
import {
@@ -25,14 +43,17 @@ import {
cancel,
type getDiscordSession,
type getEndgameConfig,
type getUserSubmissions,
loginDiscord,
} from "./api";
import { RulesDialog } from "./rules";
export function WelcomeScreening({
session,
userSubs,
}: {
session: Awaited<ReturnType<typeof getDiscordSession>>;
userSubs: Awaited<ReturnType<typeof getUserSubmissions>>;
}) {
const [agreed, setAgreed] = useState(false);
const [updated] = shared.state("updated");
@@ -58,6 +79,13 @@ export function WelcomeScreening({
สลับบัญชี?
</button>
</span>
<div className="absolute right-0 bottom-0 m-2">
<SubmissionListModal subs={userSubs}>
<Button variant="outline" size="sm">
<List /> รายการคิว
</Button>
</SubmissionListModal>
</div>
</div>
) : (
<div className="grid gap-2">
@@ -266,3 +294,94 @@ export function ClearCookie() {
}
return "";
}
export function SubmitAnotherButton() {
const router = useRouter();
return (
<Button variant="outline" size="sm" onClick={() => router.push("?new")}>
<Plus /> ลงคิวเพิ่ม
</Button>
);
}
export function SubmissionListModal({
subs,
children,
}: {
subs: Awaited<ReturnType<typeof getUserSubmissions>>;
children: React.ReactNode;
}) {
const router = useRouter();
const serverLabels: Record<string, string> = {
as: "Asia",
eu: "Europe",
us: "America",
tw: "TW, HK, MO",
};
function select(id: string) {
try {
// biome-ignore lint/suspicious/noTsIgnore: typescript issue
// @ts-ignore
cookieStore.set("rsid", id);
} catch {
console.warn("Cannot set rubgram cookie");
}
router.refresh();
}
return (
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="max-h-[80svh] max-w-lg overflow-y-auto">
<DialogHeader>
<DialogTitle>รายการคิวของฉัน</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-2">
{subs.length === 0 && (
<p className="text-sm text-muted-foreground">ไม่มีรายการคิว</p>
)}
{subs.map((r) => (
<button
key={r.id}
type="button"
onClick={() => select(r.id)}
className="flex cursor-pointer items-start gap-3 rounded-md border p-3 text-left transition-colors hover:bg-accent"
>
<div className="flex size-12 shrink-0 items-center justify-center rounded bg-muted">
{r.paid ? (
<Check className="size-5 text-green-500" />
) : r.expires && new Date(r.expires) <= new Date() ? (
<Circle className="size-5 text-muted-foreground" />
) : (
<Clock className="size-5 text-yellow-500" />
)}
</div>
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
<div className="flex items-center gap-2">
<span className="truncate font-medium">
{r.queue}. {r.name}
</span>
</div>
<span className="truncate text-xs text-muted-foreground">
{r.services.join(", ")}
</span>
<div className="flex items-center gap-2 text-xs">
<Badge variant="outline">{serverLabels[r.server]}</Badge>
<span className="font-medium">{r.price} ฿</span>
{r.paid ? (
<span className="text-green-500">ชำระแล้ว</span>
) : r.expires && new Date(r.expires) <= new Date() ? (
<span className="text-muted-foreground">หมดเวลา</span>
) : (
<span className="text-yellow-500">รอชำระ</span>
)}
</div>
</div>
</button>
))}
</div>
</DialogContent>
</Dialog>
);
}
+47 -26
View File
@@ -3,6 +3,7 @@ import {
AlertCircle,
BookAlert,
CircleDollarSign,
List,
SendHorizonal,
X,
} from "lucide-react";
@@ -54,7 +55,7 @@ import { db } from "@/lib/db";
import { endgameSubmissions } from "@/lib/db/schema";
import { LiveButton } from "../artifact/live";
import { Watcher } from "./admin/client";
import { getDiscordSession, getEndgameConfig } from "./api";
import { getDiscordSession, getEndgameConfig, getUserSubmissions } from "./api";
import {
CancelButton,
ClearCookie,
@@ -63,6 +64,8 @@ import {
PriceEstimation,
ServiceSelector,
SlipUpload,
SubmissionListModal,
SubmitAnotherButton,
WelcomeScreening,
} from "./client";
import { EndgameFormWrapper } from "./form";
@@ -73,16 +76,25 @@ export const metadata: Metadata = {
description: "รับเล่นคอนเทนต์เอนเกมเกนชินแทนคนดู",
};
export default async function EndgamePage() {
export default async function EndgamePage({
searchParams,
}: {
searchParams: Promise<{ new?: string }>;
}) {
const sp = await searchParams;
const isNew = sp.new === "";
const cookie = await cookies();
const sid = cookie.get("rsid");
const session = await getDiscordSession();
const [q] = sid?.value
? await db
.select({
// Using endgame.submissions.queue here because
// ${endgameSubmissions.queue} does not work
queue: sql<number>`
const [q] =
!isNew && sid?.value
? await db
.select({
// Using endgame.submissions.queue here because
// ${endgameSubmissions.queue} does not work
queue: sql<number>`
${endgameSubmissions.queue} - (
select count(*)
from ${endgameSubmissions} e2
@@ -90,24 +102,25 @@ export default async function EndgamePage() {
and e2.queue < endgame.submissions.queue
)
`,
paid: endgameSubmissions.paid,
price: endgameSubmissions.price,
expires: endgameSubmissions.expires,
})
.from(endgameSubmissions)
.where(
and(
or(
eq(endgameSubmissions.id, sid.value),
and(
eq(endgameSubmissions.user, session?.uid || "placeholder"),
gt(endgameSubmissions.expires, new Date()),
paid: endgameSubmissions.paid,
price: endgameSubmissions.price,
expires: endgameSubmissions.expires,
})
.from(endgameSubmissions)
.where(
and(
or(
eq(endgameSubmissions.id, sid.value),
and(
eq(endgameSubmissions.user, session?.uid || "placeholder"),
gt(endgameSubmissions.expires, new Date()),
),
),
not(endgameSubmissions.deleted),
),
not(endgameSubmissions.deleted),
),
)
: [];
)
: [];
const userSubs = session ? await getUserSubmissions(session.uid) : [];
const { count, ...config } = await getEndgameConfig();
const [canExpire] = q
? await db
@@ -141,6 +154,14 @@ export default async function EndgamePage() {
</span>
)}
</div>
<div className="absolute right-0 bottom-0 m-2 flex gap-2">
<SubmissionListModal subs={userSubs}>
<Button variant="outline" size="sm">
<List /> รายการคิว
</Button>
</SubmissionListModal>
<SubmitAnotherButton />
</div>
</Blocker>
)
) : config.locked ? (
@@ -154,7 +175,7 @@ export default async function EndgamePage() {
</div>
</Blocker>
) : (
<WelcomeScreening {...{ session }} />
<WelcomeScreening {...{ session, userSubs }} />
)}
<CardHeader className="justify-center">
<CardTitle>
@@ -333,7 +354,7 @@ export default async function EndgamePage() {
ช่องดิสคอร์ด
</a>
</span>
{sid && !q && <ClearCookie />}
{sid && !q && !isNew && <ClearCookie />}
<Watcher />
</div>
);