diff --git a/src/app/(dashboard)/dashboard/clients/[id]/page.tsx b/src/app/(dashboard)/dashboard/clients/[id]/page.tsx
new file mode 100644
index 0000000..a100cda
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/clients/[id]/page.tsx
@@ -0,0 +1,38 @@
+import { notFound, redirect } from "next/navigation";
+
+import { getClientById } from "@/lib/dto/client";
+import { getCurrentUser } from "@/lib/session";
+import { EditClientForm } from "@/components/clients/edit-client";
+
+interface EditClientPageProps {
+ params: {
+ id: string;
+ };
+}
+
+export default async function EditClientPage({ params }: EditClientPageProps) {
+ const user = await getCurrentUser();
+ if (!user) {
+ redirect("/sign-in");
+ }
+
+ const client = await getClientById(params.id);
+ if (!client) {
+ notFound();
+ }
+
+ // 检查是否是应用的所有者
+ if (client.userId !== user.id) {
+ redirect("/dashboard/clients");
+ }
+
+ return (
+
+ );
+}
diff --git a/src/app/(dashboard)/dashboard/clients/page.tsx b/src/app/(dashboard)/dashboard/clients/page.tsx
index c703aee..2567ffa 100644
--- a/src/app/(dashboard)/dashboard/clients/page.tsx
+++ b/src/app/(dashboard)/dashboard/clients/page.tsx
@@ -1,3 +1,4 @@
+import Link from "next/link";
import { redirect } from "next/navigation";
import { getClientsByUserId } from "@/lib/dto/client";
@@ -12,6 +13,7 @@ import {
TableRow,
} from "@/components/ui/table";
import { AddClientButton } from "@/components/clients/add-client";
+import { DeleteClientButton } from "@/components/clients/delete-client";
// 创建 Prisma 客户端实例
async function fetchClients(userId: string) {
@@ -62,12 +64,15 @@ export default async function ClientsPage() {
-
-
+
+
+
+
diff --git a/src/app/api/clients/[id]/route.ts b/src/app/api/clients/[id]/route.ts
new file mode 100644
index 0000000..4b94409
--- /dev/null
+++ b/src/app/api/clients/[id]/route.ts
@@ -0,0 +1,105 @@
+import { NextRequest } from "next/server";
+
+import { prisma } from "@/lib/prisma";
+import { getCurrentUser } from "@/lib/session";
+
+export async function PUT(
+ request: NextRequest,
+ { params }: { params: { id: string } },
+) {
+ try {
+ const user = await getCurrentUser();
+ if (!user) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ const client = await prisma.client.findUnique({
+ where: { id: params.id },
+ });
+
+ if (!client) {
+ return new Response("Not Found", { status: 404 });
+ }
+
+ if (client.userId !== user.id) {
+ return new Response("Forbidden", { status: 403 });
+ }
+
+ const formData = await request.formData();
+ const name = formData.get("name") as string;
+ const home = formData.get("home") as string;
+ const logo = formData.get("logo") as string;
+ const redirectUri = formData.get("redirectUri") as string;
+ const description = formData.get("description") as string;
+
+ // 验证必填字段
+ if (!name || !home || !logo || !redirectUri) {
+ return new Response("Missing required fields", { status: 400 });
+ }
+
+ const updatedClient = await prisma.client.update({
+ where: { id: params.id },
+ data: {
+ name,
+ home,
+ logo,
+ redirectUri,
+ description,
+ },
+ });
+
+ return Response.json(updatedClient);
+ } catch (error) {
+ console.error("Error updating client:", error);
+ return new Response("Internal Server Error", { status: 500 });
+ }
+}
+
+export async function DELETE(
+ _request: NextRequest,
+ { params }: { params: { id: string } },
+) {
+ try {
+ const user = await getCurrentUser();
+ if (!user) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+
+ const client = await prisma.client.findUnique({
+ where: { id: params.id },
+ });
+
+ if (!client) {
+ return new Response("Not Found", { status: 404 });
+ }
+
+ if (client.userId !== user.id) {
+ return new Response("Forbidden", { status: 403 });
+ }
+
+ // 删除相关的授权记录
+ await prisma.authorization.deleteMany({
+ where: { clientId: params.id },
+ });
+
+ // 删除相关的访问令牌
+ await prisma.accessToken.deleteMany({
+ where: { clientId: params.id },
+ });
+
+ // 删除相关的授权码
+ await prisma.code.deleteMany({
+ where: { clientId: params.id },
+ });
+
+ // 删除客户端
+ await prisma.client.delete({
+ where: { id: params.id },
+ });
+
+ return new Response(null, { status: 204 });
+ } catch (error) {
+ console.error("Error deleting client:", error);
+ return new Response("Internal Server Error", { status: 500 });
+ }
+}
diff --git a/src/components/clients/delete-client.tsx b/src/components/clients/delete-client.tsx
new file mode 100644
index 0000000..8f16f39
--- /dev/null
+++ b/src/components/clients/delete-client.tsx
@@ -0,0 +1,87 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+
+import { useToast } from "@/hooks/use-toast";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog";
+import { Button } from "@/components/ui/button";
+
+interface DeleteClientButtonProps {
+ clientId: string;
+ clientName: string;
+}
+
+export function DeleteClientButton({
+ clientId,
+ clientName,
+}: DeleteClientButtonProps) {
+ const [isLoading, setIsLoading] = useState(false);
+ const { toast } = useToast();
+ const router = useRouter();
+
+ async function onDelete() {
+ setIsLoading(true);
+ try {
+ const response = await fetch(`/api/clients/${clientId}`, {
+ method: "DELETE",
+ });
+
+ if (!response.ok) {
+ throw new Error("删除失败");
+ }
+
+ router.refresh();
+ toast({
+ title: "删除成功",
+ description: "应用已成功删除",
+ });
+ } catch (error) {
+ toast({
+ variant: "destructive",
+ title: "删除失败",
+ description: error instanceof Error ? error.message : "未知错误",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ 确认删除应用?
+
+ 您确定要删除应用"{clientName}"吗?此操作不可撤销,删除后将无法恢复。
+
+
+
+ 取消
+
+ 删除
+
+
+
+
+ );
+}
diff --git a/src/components/clients/edit-client.tsx b/src/components/clients/edit-client.tsx
new file mode 100644
index 0000000..3e581c6
--- /dev/null
+++ b/src/components/clients/edit-client.tsx
@@ -0,0 +1,130 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { Client } from "@prisma/client";
+
+import { useToast } from "@/hooks/use-toast";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+
+interface EditClientFormProps {
+ client: Client;
+}
+
+export function EditClientForm({ client }: EditClientFormProps) {
+ const [isLoading, setIsLoading] = useState(false);
+ const { toast } = useToast();
+ const router = useRouter();
+
+ async function onSubmit(event: React.FormEvent) {
+ event.preventDefault();
+ setIsLoading(true);
+
+ try {
+ const formData = new FormData(event.currentTarget);
+ const response = await fetch(`/api/clients/${client.id}`, {
+ method: "PUT",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ throw new Error("更新失败");
+ }
+
+ router.refresh();
+ toast({
+ title: "更新成功",
+ description: "应用信息已更新",
+ });
+ router.push("/dashboard/clients");
+ } catch (error) {
+ toast({
+ variant: "destructive",
+ title: "更新失败",
+ description: error instanceof Error ? error.message : "未知错误",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ return (
+
+
+ 编辑应用
+ 修改应用的基本信息
+
+
+
+
+
+ );
+}
diff --git a/src/lib/dto/client.ts b/src/lib/dto/client.ts
index 25f0bcf..1900a2b 100644
--- a/src/lib/dto/client.ts
+++ b/src/lib/dto/client.ts
@@ -25,3 +25,11 @@ export async function getClientsByUserId(userId: string) {
},
});
}
+
+export async function getClientById(id: string) {
+ return await prisma.client.findUnique({
+ where: {
+ id,
+ },
+ });
+}