frontend: rebuild bookmark page with drag-and-drop, search, and theme system

- bookmark management with dnd-kit reordering, bulk edit, search,
  category filter/rename, and meta auto-fetch
- migrate /bookmarks → /dashboard/bookmarks under (main) layout
- homepage redesign with category grid, /-key search, dock tooltips
- theme toggle + use-theme, sonner toasts, alert-dialog/skeleton,
  visual refresh of auth pages

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
root
2026-05-02 22:53:17 +00:00
parent 832512469a
commit 694b02e848
26 changed files with 2377 additions and 561 deletions

View File

@@ -1,7 +1,6 @@
"use client";
import { Suspense, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { Suspense } from "react";
import {
Dialog,
DialogContent,
@@ -10,41 +9,42 @@ import {
} from "@/components/ui/dialog";
import { LoginForm } from "./login-form";
export const metadata: Metadata = {
title: "登录",
};
const hasKeycloak = !!process.env.AUTH_KEYCLOAK_ISSUER;
function LoginPageContent() {
const router = useRouter();
const searchParams = useSearchParams();
const callbackUrl = searchParams.get("callbackUrl");
const error = searchParams.get("error");
export default async function LoginPage({
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const sp = await searchParams;
const callbackUrl = typeof sp.callbackUrl === "string" ? sp.callbackUrl : null;
const error = typeof sp.error === "string" ? sp.error : null;
useEffect(() => {
// If not an OAuth callback, redirect to home with login modal open
if (!callbackUrl && !error) {
router.replace("/?login=1");
}
}, [callbackUrl, error, router]);
if (!callbackUrl && !error) {
redirect("/?login=1");
}
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 p-4">
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-background via-background to-muted/40 p-4">
<Dialog defaultOpen>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="text-center"></DialogTitle>
</DialogHeader>
<Suspense fallback={<div>...</div>}>
<LoginForm hasKeycloak={hasKeycloak} />
{error && (
<p className="rounded-md border border-destructive/30 bg-destructive/10 p-2 text-center text-xs text-destructive">
,
</p>
)}
<Suspense fallback={<div className="text-sm text-muted-foreground">...</div>}>
<LoginForm hasKeycloak={hasKeycloak} callbackUrl={callbackUrl ?? undefined} />
</Suspense>
</DialogContent>
</Dialog>
</div>
);
}
export default function LoginPage() {
return (
<Suspense fallback={<div className="flex min-h-screen items-center justify-center">...</div>}>
<LoginPageContent />
</Suspense>
);
}