diff --git a/app/dashboard/applications/[id]/page.tsx b/app/dashboard/applications/[id]/page.tsx new file mode 100644 index 0000000..c3bd6c9 --- /dev/null +++ b/app/dashboard/applications/[id]/page.tsx @@ -0,0 +1,364 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { trpc } from "@/client/utils/trpc/trpc-client"; +import { toast } from "sonner"; +import { + LoadingSpinner, + LoadingPage, +} from "@/client/components/LoadingSpinner"; +import { CopyButton } from "@/client/components/CopyButton"; +import { Modal } from "@/client/components/Modal"; +import { PageHeader } from "@/client/components/PageHeader"; +import { validateDomains } from "@/shared/validators/domains"; + +const inputStyles = + "mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-base py-2.5 px-3"; +const textareaStyles = inputStyles; + +export default function ApplicationDetailsPage({ + params, +}: { + params: { id: string }; +}) { + const router = useRouter(); + const utils = trpc.useUtils(); + const { data: app, isLoading } = trpc.getApplication.useQuery({ + id: params.id, + }); + const [isEditing, setIsEditing] = useState(false); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [editForm, setEditForm] = useState({ + name: "", + description: "", + domains: [] as string[], + }); + const [newDomain, setNewDomain] = useState(""); + + const updateAppMutation = trpc.updateApplication.useMutation({ + onSuccess: () => { + toast.success("Application updated successfully"); + setIsEditing(false); + utils.getApplication.invalidate({ id: params.id }); + }, + onError: (error) => { + toast.error(error.message || "Failed to update application"); + }, + }); + + const deleteAppMutation = trpc.deleteApplication.useMutation({ + onSuccess: () => { + toast.success("Application deleted successfully"); + router.push("/dashboard?tab=applications"); + utils.listApplications.invalidate(); + }, + onError: (error) => { + toast.error(error.message || "Failed to delete application"); + }, + }); + + if (isLoading) { + return ; + } + + if (!app) { + return ( +
+ +
+ ); + } + + const handleEdit = () => { + setEditForm({ + name: app.name, + description: app.description || "", + domains: app.domains, + }); + setIsEditing(true); + }; + + const handleUpdate = async (e: React.FormEvent) => { + e.preventDefault(); + await updateAppMutation.mutateAsync({ + id: app.id, + name: editForm.name, + description: editForm.description, + domains: editForm.domains, + }); + }; + + const handleDelete = async () => { + await deleteAppMutation.mutateAsync({ id: app.id }); + }; + + const handleAddDomain = () => { + if (newDomain && !editForm.domains.includes(newDomain)) { + const updatedDomains = [...editForm.domains, newDomain]; + const validationResult = validateDomains(updatedDomains); + + if (!validationResult.valid) { + toast.error(validationResult.error); + return; + } + + setEditForm((prev) => ({ + ...prev, + domains: updatedDomains, + })); + setNewDomain(""); + } + }; + + const handleRemoveDomain = (domain: string) => { + setEditForm((prev) => ({ + ...prev, + domains: prev.domains.filter((d) => d !== domain), + })); + }; + + const headerActions = ( + <> + + + + ); + + return ( +
+
+ +
+ +
+
+
+ {/* Application Info */} + {!isEditing && ( +
+
+

+ Application ID +

+
+ +
+
+ + {app.description && ( +
+

+ Description +

+

{app.description}

+
+ )} + +
+

+ Client ID +

+
+ {app.clientId ? ( + + ) : ( + + No Client ID available + + )} +
+
+ +
+

Domains

+ {app.domains.length > 0 ? ( +
+ {app.domains.map((domain) => ( + + {domain} + + ))} +
+ ) : ( + + No domains added + + )} +
+
+ )} + + {/* Edit Form */} + {isEditing && ( +
+
+ + + setEditForm({ ...editForm, name: e.target.value }) + } + className={inputStyles} + required + /> +
+ +
+ +