feat(artifact): refresh button

This commit is contained in:
2026-04-09 07:59:37 +07:00 Unverified
parent 10efbb4a9f
commit 37b31c782c
3 changed files with 61 additions and 25 deletions
+32 -8
View File
@@ -5,6 +5,7 @@ import {
CopyCheck, CopyCheck,
ImageIcon, ImageIcon,
OctagonAlert, OctagonAlert,
RefreshCwIcon,
ScanSearch, ScanSearch,
SquircleDashed, SquircleDashed,
} from "lucide-react"; } from "lucide-react";
@@ -13,7 +14,8 @@ import { useEffect, useState } from "react";
import Enka from "#/enka_logo.png"; import Enka from "#/enka_logo.png";
import { SimpleTooltip } from "@/components/tooltip"; import { SimpleTooltip } from "@/components/tooltip";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { getCardStatus } from "@/lib/api"; import { Spinner } from "@/components/ui/spinner";
import { getCardStatus, revalidateCard } from "@/lib/api";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) { export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
@@ -21,6 +23,14 @@ export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [useWeb, setUseWeb] = useState(false); const [useWeb, setUseWeb] = useState(false);
const [isCached, setIsCached] = useState(true); const [isCached, setIsCached] = useState(true);
const [t, setT] = useState(0);
async function refresh() {
setReady(false);
setIsCached(false);
if (!useWeb) await revalidateCard(sub);
setT(Date.now());
}
// biome-ignore lint/correctness/useExhaustiveDependencies: for update // biome-ignore lint/correctness/useExhaustiveDependencies: for update
useEffect(() => { useEffect(() => {
@@ -31,7 +41,7 @@ export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
setIsCached(s.cached); setIsCached(s.cached);
if (!s.cached) setError(s.error); if (!s.cached) setError(s.error);
}); });
}, [sub, uid]); }, [sub, uid, t]);
return ( return (
<div className="h-full w-full relative"> <div className="h-full w-full relative">
{!ready && !error && (isCached || useWeb) && ( {!ready && !error && (isCached || useWeb) && (
@@ -68,13 +78,16 @@ export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
</div> </div>
)} )}
<div
className={cn(
"absolute left-1 z-45 opacity-70 md:opacity-50 hover:opacity-100 flex gap-1",
useWeb ? "-bottom-19" : "bottom-1",
)}
>
<SimpleTooltip text={useWeb ? "สลับเป็นรูปภาพ" : "สลับเป็น Enka"}> <SimpleTooltip text={useWeb ? "สลับเป็นรูปภาพ" : "สลับเป็น Enka"}>
<Button <Button
variant="outline" variant="outline"
className={cn( className="bg-[#222a]! backdrop-blur-sm"
"absolute left-1 z-45 bg-[#222a]! backdrop-blur-sm opacity-70 md:opacity-50 hover:opacity-100",
useWeb ? "-bottom-19" : "bottom-1",
)}
size="icon" size="icon"
onClick={() => { onClick={() => {
setReady(false); setReady(false);
@@ -89,9 +102,20 @@ export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
)} )}
</Button> </Button>
</SimpleTooltip> </SimpleTooltip>
<SimpleTooltip text="รีเฟรช">
<Button
variant="ghost"
size="icon"
onClick={refresh}
disabled={!error && !ready}
>
{!error && !ready ? <Spinner /> : <RefreshCwIcon />}
</Button>
</SimpleTooltip>
</div>
{useWeb ? ( {useWeb ? (
<iframe <iframe
src={`https://enka.network/u/${uid}`} src={`https://enka.network/u/${uid}?t=${t}`}
className={cn( className={cn(
"w-full border-0 bg-card -mt-20 h-[calc(100%+5rem)]", "w-full border-0 bg-card -mt-20 h-[calc(100%+5rem)]",
!ready && "grayscale blur-md brightness-50 pointer-events-none", !ready && "grayscale blur-md brightness-50 pointer-events-none",
@@ -108,7 +132,7 @@ export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
)} )}
> >
<Image <Image
src={`/api/card/${sub}`} src={`/api/card/${sub}?t=${t}`}
alt={`Enka card for ${sub}`} alt={`Enka card for ${sub}`}
fill fill
objectFit="contain" objectFit="contain"
+6
View File
@@ -229,6 +229,12 @@ export async function random() {
else throw "ไม่พบผู้ลงทะเบียนที่ยังไม่ตรวจสอบ"; else throw "ไม่พบผู้ลงทะเบียนที่ยังไม่ตรวจสอบ";
} }
export async function revalidateCard(sub: string) {
if (!(await adminCheck())) throw "Unauthorized";
await db.delete(cards).where(eq(cards.submission, sub));
revalidatePath(`/api/card/${sub}`);
}
export async function tlState( export async function tlState(
data: Partial<typeof tierlistStates.$inferInsert>, data: Partial<typeof tierlistStates.$inferInsert>,
) { ) {
+6
View File
@@ -27,6 +27,12 @@ const nextConfig: NextConfig = withBundleAnalyzer({
hostname: "cards.enka.network", hostname: "cards.enka.network",
}, },
], ],
localPatterns: [
{ pathname: "/api/card/*" },
{
pathname: "/cdn/*",
},
],
minimumCacheTTL: 86400, minimumCacheTTL: 86400,
}, },
experimental: { experimental: {