diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/add-admin-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/add-admin-button.tsx index a3fa5fc306e..26039ccc0ae 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/add-admin-button.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/add-admin-button.tsx @@ -1,116 +1,155 @@ -import { - Flex, - FormControl, - Input, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - useDisclosure, -} from "@chakra-ui/react"; -import { Button } from "chakra/button"; -import { FormLabel } from "chakra/form"; -import { CirclePlusIcon } from "lucide-react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { PlusIcon } from "lucide-react"; +import { useState } from "react"; import { useForm } from "react-hook-form"; +import { toast } from "sonner"; import { isAddress } from "thirdweb"; -import { type EngineAdmin, useEngineGrantPermissions } from "@/hooks/useEngine"; -import { useTxNotifications } from "@/hooks/useTxNotifications"; +import { z } from "zod"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { useEngineGrantPermissions } from "@/hooks/useEngine"; +import { parseError } from "@/utils/errorParser"; -interface AddAdminButtonProps { - instanceUrl: string; - authToken: string; -} +const addAdminSchema = z.object({ + walletAddress: z + .string() + .refine((address) => isAddress(address), "Invalid address"), + label: z.string().optional(), +}); -export const AddAdminButton: React.FC = ({ +type AddAdminFormData = z.infer; + +export function AddAdminButton({ instanceUrl, authToken, -}) => { - const { isOpen, onOpen, onClose } = useDisclosure(); - const { mutate: grantPermissions } = useEngineGrantPermissions({ +}: { + instanceUrl: string; + authToken: string; +}) { + const [open, setOpen] = useState(false); + const grantPermissionsMutation = useEngineGrantPermissions({ authToken, instanceUrl, }); - const form = useForm({ - defaultValues: { - permissions: "ADMIN", - }, + const form = useForm({ + resolver: zodResolver(addAdminSchema), + defaultValues: {}, }); - const { onSuccess, onError } = useTxNotifications( - "Successfully added admin.", - "Failed to add admin.", - ); + const onSubmit = (data: AddAdminFormData) => { + grantPermissionsMutation.mutate( + { + ...data, + permissions: "ADMIN", + }, + { + onError: (error) => { + toast.error("Failed to add admin.", { + description: parseError(error), + }); + console.error(error); + }, + onSuccess: () => { + toast.success("Admin added successfully."); + setOpen(false); + form.reset(); + }, + }, + ); + }; return ( - <> - - - - { - if (!isAddress(data.walletAddress)) { - onError(new Error("Invalid wallet address")); - } - grantPermissions(data, { - onError: (error) => { - onError(error); - console.error(error); - }, - onSuccess: () => { - onSuccess(); - onClose(); - }, - }); - })} - > - Add Admin - - -
- - Wallet Address - - - - Label - - -
-
+ + + + + + + Add Admin + + Add a new admin to your engine instance. + + - - - - -
-
- +
+ +
+ {/* wallet address */} + ( + + Wallet Address + + + + + + )} + /> + {/* label */} + ( + + Label + + + + + + )} + /> +
+
+ + +
+
+ + + ); -}; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/admins-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/admins-table.tsx index e6f07f24397..a6b1e78eaec 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/admins-table.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/admins-table.tsx @@ -1,55 +1,64 @@ -import { - Flex, - FormControl, - Input, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - type UseDisclosureReturn, - useDisclosure, -} from "@chakra-ui/react"; +import { zodResolver } from "@hookform/resolvers/zod"; import { createColumnHelper } from "@tanstack/react-table"; -import { Button } from "chakra/button"; -import { FormLabel } from "chakra/form"; -import { Text } from "chakra/text"; import { PencilIcon, Trash2Icon } from "lucide-react"; import { useMemo, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; import type { ThirdwebClient } from "thirdweb"; +import { z } from "zod"; import { TWTable } from "@/components/blocks/TWTable"; import { WalletAddress } from "@/components/blocks/wallet-address"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; import { type EngineAdmin, useEngineGrantPermissions, useEngineRevokePermissions, } from "@/hooks/useEngine"; -import { useTxNotifications } from "@/hooks/useTxNotifications"; - -interface AdminsTableProps { - instanceUrl: string; - admins: EngineAdmin[]; - isPending: boolean; - isFetched: boolean; - authToken: string; - client: ThirdwebClient; -} +import { parseError } from "@/utils/errorParser"; const columnHelper = createColumnHelper(); -export const AdminsTable: React.FC = ({ +const editAdminSchema = z.object({ + label: z.string().optional(), +}); + +type EditAdminFormData = z.infer; + +export function AdminsTable({ instanceUrl, admins, isPending, isFetched, authToken, client, -}) => { - const editDisclosure = useDisclosure(); - const removeDisclosure = useDisclosure(); +}: { + instanceUrl: string; + admins: EngineAdmin[]; + isPending: boolean; + isFetched: boolean; + authToken: string; + client: ThirdwebClient; +}) { + const [editOpen, setEditOpen] = useState(false); + const [removeOpen, setRemoveOpen] = useState(false); const [selectedAdmin, setSelectedAdmin] = useState(); const columns = useMemo(() => { @@ -63,11 +72,7 @@ export const AdminsTable: React.FC = ({ }), columnHelper.accessor("label", { cell: (cell) => { - return ( - - {cell.getValue()} - - ); + return

{cell.getValue()}

; }, header: "Label", }), @@ -92,7 +97,7 @@ export const AdminsTable: React.FC = ({ icon: , onClick: (admin) => { setSelectedAdmin(admin); - editDisclosure.onOpen(); + setEditOpen(true); }, text: "Edit", }, @@ -101,7 +106,7 @@ export const AdminsTable: React.FC = ({ isDestructive: true, onClick: (admin) => { setSelectedAdmin(admin); - removeDisclosure.onOpen(); + setRemoveOpen(true); }, text: "Remove", }, @@ -109,174 +114,222 @@ export const AdminsTable: React.FC = ({ title="admins" /> - {selectedAdmin && editDisclosure.isOpen && ( - )} - {selectedAdmin && removeDisclosure.isOpen && ( - )} ); -}; +} -const EditModal = ({ +function EditDialog({ admin, - disclosure, instanceUrl, authToken, + open, + onOpenChange, }: { admin: EngineAdmin; - disclosure: UseDisclosureReturn; instanceUrl: string; authToken: string; -}) => { - const { mutate: updatePermissions } = useEngineGrantPermissions({ + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + const updatePermissionsMutation = useEngineGrantPermissions({ authToken, instanceUrl, }); - const { onSuccess, onError } = useTxNotifications( - "Successfully updated admin", - "Failed to update admin", - ); - - const [label, setLabel] = useState(admin.label ?? ""); + const form = useForm({ + resolver: zodResolver(editAdminSchema), + values: { + label: admin.label ?? "", + }, + }); - const onClick = () => { - updatePermissions( + const onSubmit = (data: EditAdminFormData) => { + updatePermissionsMutation.mutate( { - label, + label: data.label, permissions: admin.permissions, walletAddress: admin.walletAddress, }, { onError: (error) => { - onError(error); + toast.error("Failed to update admin.", { + description: parseError(error), + }); console.error(error); }, onSuccess: () => { - onSuccess(); - disclosure.onClose(); + toast.success("Admin updated successfully."); + onOpenChange(false); + form.reset(); }, }, ); }; return ( - - - - Update Admin - - -
- - Wallet Address - {admin.walletAddress} - - - Label - setLabel(e.target.value)} - placeholder="Enter a description for this admin" - type="text" - value={label} + + + + Update Admin + + Update the label for this admin. + + + +
+ +
+
+

Wallet Address

+

+ {admin.walletAddress} +

+
+ + ( + + Label + + + + + + )} /> - -
- +
- - - - -
-
+
+ + +
+ + + + ); -}; +} -const RemoveModal = ({ +function RemoveDialog({ admin, - disclosure, instanceUrl, authToken, + open, + onOpenChange, }: { admin: EngineAdmin; - disclosure: UseDisclosureReturn; instanceUrl: string; authToken: string; -}) => { - const { mutate: revokePermissions } = useEngineRevokePermissions({ + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + const revokePermissionsMutation = useEngineRevokePermissions({ authToken, instanceUrl, }); - const { onSuccess, onError } = useTxNotifications( - "Successfully removed admin", - "Failed to remove admin", - ); - const onClick = () => { - revokePermissions( + revokePermissionsMutation.mutate( { walletAddress: admin.walletAddress, }, { onError: (error) => { - onError(error); + toast.error("Failed to remove admin.", { + description: parseError(error), + }); console.error(error); }, onSuccess: () => { - onSuccess(); - disclosure.onClose(); + toast.success("Admin removed successfully."); + onOpenChange(false); }, }, ); }; return ( - - - - Remove Admin - - -
- Are you sure you want to remove this admin? - - Wallet Address - {admin.walletAddress} - - - Label - {admin.label ?? N/A} - + + + + Remove Admin + + Are you sure you want to remove this admin? + + + +
+
+

Wallet Address

+

+ {admin.walletAddress} +

- - -
+ +
+ - - - - +
+
+
); -}; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/engine-admins.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/engine-admins.tsx index f4fcfbc509c..1313c12f64a 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/engine-admins.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/admins/components/engine-admins.tsx @@ -1,45 +1,40 @@ "use client"; -import { Heading } from "chakra/heading"; -import { Link } from "chakra/link"; -import { Text } from "chakra/text"; import type { ThirdwebClient } from "thirdweb"; +import { UnderlineLink } from "@/components/ui/UnderlineLink"; import { useEnginePermissions } from "@/hooks/useEngine"; import { AddAdminButton } from "./add-admin-button"; import { AdminsTable } from "./admins-table"; -interface EngineAdminsProps { - instanceUrl: string; - authToken: string; - client: ThirdwebClient; -} - -export const EngineAdmins: React.FC = ({ +export function EngineAdmins({ instanceUrl, authToken, client, -}) => { +}: { + instanceUrl: string; + authToken: string; + client: ThirdwebClient; +}) { const admins = useEnginePermissions({ authToken, instanceUrl, }); return ( -
-
- Admins - - Admins are allowed to manage your Engine instance from the dashboard.{" "} - - Learn more about admins - - . - -
+
+

Admins

+

+ Admins are allowed to manage your Engine instance from the dashboard.{" "} + + Learn more about admins + + . +

+ = ({ isFetched={admins.isFetched} isPending={admins.isPending} /> - + +
+ +
); -}; +}