Refactor CdnTable and related components for improved type safety and functionality; implement character creation and editing modals; enhance form handling with autosave; update Sentry configuration for production; clean up unused files and code.

This commit is contained in:
2026-01-22 16:10:30 +07:00 Unverified
parent 0168dc5f07
commit 0dbb9396a3
18 changed files with 963 additions and 34 deletions
+7 -1
View File
@@ -1,7 +1,13 @@
import { createContext } from "react";
export type CdnFile = {
id: string;
name: string | null;
size: string;
};
interface CdnChooserContextType {
call(options?: { type?: string }): Promise<string | null>;
call(options?: { type?: string }): Promise<CdnFile | null>;
}
export const CdnChooserContext = createContext<CdnChooserContextType | null>(
+48 -15
View File
@@ -1,15 +1,22 @@
"use client";
import { Check, FileSearch2 } from "lucide-react";
import { type ComponentProps, useContext, useRef, useState } from "react";
import { CdnTable } from "@/app/(ui)/admin/cdn/table";
import {
type ComponentProps,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { b2sClient, CdnTable } from "@/app/(ui)/admin/cdn/table";
import type { cdn } from "@/lib/db/schema";
import { cn } from "@/lib/utils";
import { Button } from "../ui/button";
import { Dialog, DialogContent, DialogTitle } from "../ui/dialog";
import { Kbd } from "../ui/kbd";
import { Spinner } from "../ui/spinner";
import { listFiles } from "./api";
import { CdnChooserContext } from "./context";
import { CdnChooserContext, type CdnFile } from "./context";
export function CdnChooserProvider({
children,
@@ -20,9 +27,9 @@ export function CdnChooserProvider({
const [files, setFiles] = useState<
Omit<typeof cdn.$inferSelect, "data">[] | null
>(null);
const callback = useRef<((value: string | null) => void) | null>(null);
const callback = useRef<((value: CdnFile | null) => void) | null>(null);
async function call(options?: { type?: string }): Promise<string | null> {
async function call(options?: { type?: string }): Promise<CdnFile | null> {
setOpen(true);
const list = await listFiles().catch(() => []);
setFiles(
@@ -30,7 +37,7 @@ export function CdnChooserProvider({
? list.filter((file) => file.type.startsWith(options.type!))
: list,
);
return new Promise<string | null>((resolve) => {
return new Promise<CdnFile | null>((resolve) => {
callback.current = resolve;
}).finally(() => setOpen(false));
}
@@ -71,19 +78,39 @@ export function CdnChooser({
type,
children,
defaultValue,
value: externalValue,
...props
}: ComponentProps<"input"> & {
onChange?: (value: string) => void;
type?: string;
value?: string;
}) {
const [value, setValue] = useState<string>(`${defaultValue || ""}`);
const [meta, setMeta] = useState<CdnFile>();
const chooser = useContext(CdnChooserContext);
// biome-ignore lint/correctness/useExhaustiveDependencies: first run only
useEffect(() => {
setValue(externalValue || "");
const newVal = externalValue || defaultValue || "";
if (!newVal) {
if (meta) setMeta(undefined);
return;
}
(async () => {
const fileList = await listFiles();
const file = fileList.find((f) => f.id === newVal);
if (!file) return setValue("");
setMeta(file);
})();
}, [externalValue]);
async function choose() {
const res = await chooser?.call({ type });
if (res) {
setValue(res);
onChange?.(res);
setValue(res.id);
onChange?.(res.id);
setMeta(res);
}
}
@@ -91,15 +118,21 @@ export function CdnChooser({
<>
<Button
onClick={choose}
className={cn(
"min-w-50 justify-between",
value && "bg-emerald-900!",
className,
)}
size="sm"
className={cn("min-w-50 justify-between", className)}
// size="sm"
variant="outline"
type="button"
>
{children || "Select File"}
{children ||
(meta ? (
<span>
{meta.name} <Kbd>{b2sClient(Number(meta.size))}</Kbd>
</span>
) : value ? (
"File Selected"
) : (
"Select File"
))}
{value ? <Check /> : <FileSearch2 />}
</Button>
<input value={value} hidden {...props} readOnly />