feat: enhance CdnChooser with truncation and improve FormRow layout
- Added truncation class to the CdnChooser component for better UI. - Updated FormRow to include flex-wrap and items-end for improved layout. - Introduced a new overrides.ts file to generate IDs with a rot13 encoding for tierlist versions. - Created a new page for creating tierlist versions with form handling and validation. - Implemented a DatePicker component for selecting dates with a calendar UI.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
|
||||
export function genId({ name, prefix }: { name: string; prefix: string }) {
|
||||
console.log(name, prefix)
|
||||
const generated = name.toLowerCase().replace(/[^a-z0-9]+/g, "") + (name.length ? rot13(prefix) : "");
|
||||
return { id: generated };
|
||||
}
|
||||
|
||||
function rot13(str: string) {
|
||||
const shift = 13;
|
||||
let res = "";
|
||||
for (const c of str) res += String.fromCharCode(((c.toLowerCase().charCodeAt(0) - 97 + shift) % 26) + 97)
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { and, desc, eq, isNotNull, sql } from "drizzle-orm";
|
||||
import { ArrowRight, PlusIcon } from "lucide-react";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import type { TypedFormData } from "@/app/(ui)/rubgram/type";
|
||||
import { CdnChooser } from "@/components/chooser";
|
||||
import { DatePicker } from "@/components/date";
|
||||
import {
|
||||
FormAction,
|
||||
FormInput,
|
||||
FormProvider,
|
||||
FormRow,
|
||||
} from "@/components/form";
|
||||
import { ModalBase } from "@/components/modal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DialogFooter } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Kbd, KbdGroup } from "@/components/ui/kbd";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { actionLog } from "@/lib/api";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { tierlistTypes, tierlistVersions, versions } from "@/lib/db/schema";
|
||||
import { genId } from "./overrides";
|
||||
|
||||
export default async function TlVersionCreatePage({
|
||||
params,
|
||||
}: PageProps<"/admin/tl/ver/[type]/create">) {
|
||||
if (!(await adminCheck())) redirect("/login");
|
||||
const { type: typeId } = await params;
|
||||
const [[{ maxOrder }], [{ disclaimer }], [type], versionList] =
|
||||
await Promise.all([
|
||||
db
|
||||
.select({ maxOrder: sql<number>`MAX(${tierlistVersions.order})` })
|
||||
.from(tierlistVersions)
|
||||
.where(eq(tierlistVersions.type, typeId)),
|
||||
db
|
||||
.select({ disclaimer: tierlistVersions.disclaimer })
|
||||
.from(tierlistVersions)
|
||||
.where(
|
||||
and(
|
||||
eq(tierlistVersions.type, typeId),
|
||||
isNotNull(tierlistVersions.disclaimer),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(tierlistVersions.id))
|
||||
.limit(1),
|
||||
db.select().from(tierlistTypes).where(eq(tierlistTypes.id, typeId)),
|
||||
db.select().from(versions).orderBy(desc(versions.id)),
|
||||
]);
|
||||
if (!type) notFound();
|
||||
|
||||
async function submit(
|
||||
form: TypedFormData<{
|
||||
name: string;
|
||||
id: string;
|
||||
order: string;
|
||||
from: string;
|
||||
image: string;
|
||||
disclaimer: string;
|
||||
}>,
|
||||
) {
|
||||
"use server";
|
||||
if (!(await adminCheck())) redirect("/login");
|
||||
|
||||
for (const field of ["name", "id", "order"] as const) {
|
||||
if (!form.get(field)) {
|
||||
return { error: `Field "${field}" is required.` };
|
||||
}
|
||||
}
|
||||
|
||||
let data: typeof tierlistVersions.$inferInsert;
|
||||
|
||||
try {
|
||||
data = {
|
||||
id: form.get("id")!,
|
||||
name: form.get("name")!,
|
||||
image: form.get("image")!,
|
||||
disclaimer: form.get("disclaimer")!,
|
||||
from: form.get("from")!,
|
||||
deprecates: "",
|
||||
order: parseInt(form.get("order")!, 10),
|
||||
type: typeId,
|
||||
};
|
||||
await db.insert(tierlistVersions).values(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
const err = e as Error & { cause: { detail: string; message: string } };
|
||||
return {
|
||||
error:
|
||||
err?.cause?.detail || err?.cause?.message || "Failed to create type.",
|
||||
};
|
||||
}
|
||||
|
||||
await actionLog(`Created tierlist version ${typeId}/${data.id}`, data);
|
||||
|
||||
revalidatePath("/admin/tl/ver");
|
||||
return { toast: "Version created successfully.", close: true };
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalBase title={`Create ${type.name} Tierlist`}>
|
||||
<FormProvider id={`tl-${typeId}-create`} onSubmit={submit}>
|
||||
<FormInput name="prefix">
|
||||
<input defaultValue={typeId} className="hidden" />
|
||||
</FormInput>
|
||||
<FormRow>
|
||||
<FormInput name="name" override={genId} label="Name">
|
||||
<Input placeholder="6.3a" autoFocus />
|
||||
</FormInput>
|
||||
<FormInput name="id" label="ID" subLabel="auto-generated">
|
||||
<Input placeholder="63a" />
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormInput name="order" label="Order">
|
||||
<Input placeholder="1" defaultValue={maxOrder + 10} />
|
||||
</FormInput>
|
||||
<FormInput name="deprecate" label="Deprecation Date">
|
||||
<DatePicker />
|
||||
</FormInput>
|
||||
<FormRow>
|
||||
<FormInput name="from" label="Version">
|
||||
<Select>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select a version" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{versionList.map((version) => (
|
||||
<SelectItem key={version.id} value={version.id}>
|
||||
{version.name}{" "}
|
||||
<KbdGroup>
|
||||
<Kbd>{version.id}</Kbd>
|
||||
{version.from && (
|
||||
<Kbd>
|
||||
<ArrowRight />
|
||||
{version.from}
|
||||
</Kbd>
|
||||
)}
|
||||
</KbdGroup>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormInput>
|
||||
<Button asChild size="icon">
|
||||
<Link href="/admin/ver/create">
|
||||
<PlusIcon />
|
||||
</Link>
|
||||
</Button>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<FormInput name="image" label="Image" subLabel="optional">
|
||||
<CdnChooser />
|
||||
</FormInput>
|
||||
<FormInput
|
||||
name="disclaimer"
|
||||
label="Disclaimer"
|
||||
subLabel={disclaimer ? "auto-selected" : "optional"}
|
||||
>
|
||||
<CdnChooser defaultValue={disclaimer || undefined} />
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<DialogFooter>
|
||||
<FormAction type="clear" asChild>
|
||||
<Button variant="outline">Clear</Button>
|
||||
</FormAction>
|
||||
<Button asChild>
|
||||
<FormAction
|
||||
type="submit"
|
||||
loading={
|
||||
<>
|
||||
<Spinner />
|
||||
Creating...
|
||||
</>
|
||||
}
|
||||
>
|
||||
<PlusIcon />
|
||||
Create
|
||||
</FormAction>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { tierlistTypes } from "@/lib/db/schema";
|
||||
|
||||
export default async function CharacterCreatePage({
|
||||
export default async function TlTypeEditPage({
|
||||
params,
|
||||
}: PageProps<"/admin/tl/ver/[type]/edit">) {
|
||||
if (!(await adminCheck())) redirect("/login");
|
||||
|
||||
@@ -16,7 +16,7 @@ import { db } from "@/lib/db";
|
||||
import { tierlistTypes } from "@/lib/db/schema";
|
||||
import { genId } from "./overrides";
|
||||
|
||||
export default async function CharacterCreatePage() {
|
||||
export default async function TlTypeCreatePage() {
|
||||
const [{ maxOrder }] = await db
|
||||
.select({ maxOrder: sql<number>`MAX(${tierlistTypes.order})` })
|
||||
.from(tierlistTypes);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function TierlistManagerPage() {
|
||||
<div className="md:opacity-0 group-hover/type:opacity-100 transition-opacity flex gap-1">
|
||||
<SimpleTooltip text="Create version">
|
||||
<Button asChild>
|
||||
<Link href={`/admin/tl/${t.id}/create`}>
|
||||
<Link href={`/admin/tl/ver/${t.id}/create`}>
|
||||
<PlusIcon />
|
||||
<span className="md:hidden">Create</span>
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user