From 36bbb412dd7106da605a947bb72cfefa1d65054e Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Mon, 11 Nov 2024 00:00:16 -0500 Subject: [PATCH] refactor resources --- .../components/CreateResource.tsx | 241 -------------- .../components/CustomDomainInput.tsx | 2 +- .../GeneralForm.tsx => general/page.tsx} | 2 +- .../resources/[resourceId]/layout.tsx | 44 +-- .../settings/resources/[resourceId]/page.tsx | 27 +- .../components/CreateResourceForm.tsx | 306 ++++++++++++++++++ .../resources/components/ResourcesTable.tsx | 197 +++++------ 7 files changed, 441 insertions(+), 378 deletions(-) delete mode 100644 src/app/[orgId]/settings/resources/[resourceId]/components/CreateResource.tsx rename src/app/[orgId]/settings/resources/[resourceId]/{components/GeneralForm.tsx => general/page.tsx} (99%) create mode 100644 src/app/[orgId]/settings/resources/components/CreateResourceForm.tsx diff --git a/src/app/[orgId]/settings/resources/[resourceId]/components/CreateResource.tsx b/src/app/[orgId]/settings/resources/[resourceId]/components/CreateResource.tsx deleted file mode 100644 index eaba41c9..00000000 --- a/src/app/[orgId]/settings/resources/[resourceId]/components/CreateResource.tsx +++ /dev/null @@ -1,241 +0,0 @@ -"use client"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { - CaretSortIcon, - CheckIcon, -} from "@radix-ui/react-icons"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { cn } from "@/lib/utils"; -import { toast } from "@/hooks/useToast"; -import { Button} from "@/components/ui/button"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import React, { useState, useEffect } from "react"; -import { api } from "@/api"; -import { useParams } from "next/navigation"; -import { useRouter } from "next/navigation"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { ListSitesResponse } from "@server/routers/site"; -import { AxiosResponse } from "axios"; -import CustomDomainInput from "./CustomDomainInput"; - -const method = [ - { label: "Wireguard", value: "wg" }, - { label: "Newt", value: "newt" }, -] as const; - -const accountFormSchema = z.object({ - subdomain: z - .string() - .min(2, { - message: "Name must be at least 2 characters.", - }) - .max(30, { - message: "Name must not be longer than 30 characters.", - }), - name: z.string(), - siteId: z.number(), -}); - -type AccountFormValues = z.infer; - -const defaultValues: Partial = { - subdomain: "someanimalherefromapi", - name: "My Resource", -}; - -export function CreateResourceForm() { - const params = useParams(); - const orgId = params.orgId; - const router = useRouter(); - - const [sites, setSites] = useState([]); - const [domainSuffix, setDomainSuffix] = useState(".example.com"); - - const form = useForm({ - resolver: zodResolver(accountFormSchema), - defaultValues, - }); - - useEffect(() => { - if (typeof window !== "undefined") { - const fetchSites = async () => { - const res = await api.get>( - `/org/${orgId}/sites/` - ); - setSites(res.data.data.sites); - }; - fetchSites(); - } - }, []); - - async function onSubmit(data: AccountFormValues) { - console.log(data); - - const res = await api - .put(`/org/${orgId}/site/${data.siteId}/resource/`, { - name: data.name, - subdomain: data.subdomain, - // subdomain: data.subdomain, - }) - .catch((e) => { - toast({ - title: "Error creating resource...", - }); - }); - - if (res && res.status === 201) { - const niceId = res.data.data.niceId; - // navigate to the resource page - router.push(`/${orgId}/settings/resources/${niceId}`); - } - } - - return ( - <> -
- - ( - - Name - - - - - This is the name that will be displayed for - this resource. - - - - )} - /> - ( - - Subdomain - - {/* */} - - - - This is the fully qualified domain name that - will be used to access the resource. - - - - )} - /> - ( - - Site - - - - - - - - - - - - No site found. - - - {sites.map((site) => ( - { - form.setValue( - "siteId", - site.siteId - ); - }} - > - - {site.name} - - ))} - - - - - - - This is the site that will be used in the - dashboard. - - - - )} - /> - - - - - - ); -} diff --git a/src/app/[orgId]/settings/resources/[resourceId]/components/CustomDomainInput.tsx b/src/app/[orgId]/settings/resources/[resourceId]/components/CustomDomainInput.tsx index b7b9c2ac..c21e3fae 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/components/CustomDomainInput.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/components/CustomDomainInput.tsx @@ -29,7 +29,7 @@ export default function CustomDomainInput( }; return ( -
+
; -export function GeneralForm() { +export default function GeneralForm() { const params = useParams(); const orgId = params.orgId; const { resource, updateResource } = useResourceContext(); diff --git a/src/app/[orgId]/settings/resources/[resourceId]/layout.tsx b/src/app/[orgId]/settings/resources/[resourceId]/layout.tsx index c7ae77d1..b08b3cb9 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/layout.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/layout.tsx @@ -5,6 +5,8 @@ import { AxiosResponse } from "axios"; import { redirect } from "next/navigation"; import { authCookieHeader } from "@app/api/cookies"; import { SidebarSettings } from "@app/components/SidebarSettings"; +import Link from "next/link"; +import { ArrowLeft } from "lucide-react"; interface ResourceLayoutProps { children: React.ReactNode; @@ -18,22 +20,20 @@ export default async function ResourceLayout(props: ResourceLayoutProps) { let resource = null; - if (params.resourceId !== "create") { - try { - const res = await internal.get>( - `/resource/${params.resourceId}`, - await authCookieHeader() - ); - resource = res.data.data; - } catch { - redirect(`/${params.orgId}/settings/resources`); - } + try { + const res = await internal.get>( + `/resource/${params.resourceId}`, + await authCookieHeader() + ); + resource = res.data.data; + } catch { + redirect(`/${params.orgId}/settings/resources`); } const sidebarNavItems = [ { title: "General", - href: `/{orgId}/settings/resources/resourceId`, + href: `/{orgId}/settings/resources/{resourceId}/general`, }, { title: "Targets", @@ -41,27 +41,31 @@ export default async function ResourceLayout(props: ResourceLayoutProps) { }, ]; - const isCreate = params.resourceId === "create"; - return ( <> +
+ +
+ All Resources +
+ +
+

- {isCreate ? "New Resource" : resource?.name + " Settings"} + {resource?.name + " Settings"}

- {isCreate - ? "Create a new resource" - : "Configure the settings on your resource: " + - resource?.name || ""} - . + Configure the settings on your resource

{children} diff --git a/src/app/[orgId]/settings/resources/[resourceId]/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/page.tsx index 9598ffcb..1751d779 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/page.tsx @@ -1,29 +1,10 @@ -import React from "react"; -import { Separator } from "@/components/ui/separator"; -import { CreateResourceForm } from "./components/CreateResource"; -import { GeneralForm } from "./components/GeneralForm"; +import { redirect } from "next/navigation"; export default async function ResourcePage(props: { - params: Promise<{ resourceId: number | string }>; + params: Promise<{ resourceId: number | string; orgId: string }>; }) { const params = await props.params; - const isCreate = params.resourceId === "create"; - - return ( -
-
-

- {isCreate ? "Create Resource" : "General"} -

-

- {isCreate - ? "Create a new resource" - : "Edit basic resource settings"} -

-
- - - {isCreate ? : } -
+ redirect( + `/${params.orgId}/settings/resources/${params.resourceId}/general` ); } diff --git a/src/app/[orgId]/settings/resources/components/CreateResourceForm.tsx b/src/app/[orgId]/settings/resources/components/CreateResourceForm.tsx new file mode 100644 index 00000000..93cca75f --- /dev/null +++ b/src/app/[orgId]/settings/resources/components/CreateResourceForm.tsx @@ -0,0 +1,306 @@ +"use client"; + +import api from "@app/api"; +import { Button, buttonVariants } from "@app/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@app/components/ui/form"; +import { Input } from "@app/components/ui/input"; +import { useToast } from "@app/hooks/useToast"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle, +} from "@app/components/Credenza"; +import { useParams, useRouter } from "next/navigation"; +import { ListSitesResponse } from "@server/routers/site"; +import { cn } from "@app/lib/utils"; +import { CheckIcon } from "lucide-react"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@app/components/ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@app/components/ui/command"; +import { CaretSortIcon } from "@radix-ui/react-icons"; +import CustomDomainInput from "../[resourceId]/components/CustomDomainInput"; +import { Axios, AxiosResponse } from "axios"; +import { Resource } from "@server/db/schema"; + +const accountFormSchema = z.object({ + subdomain: z + .string() + .min(2, { + message: "Name must be at least 2 characters.", + }) + .max(30, { + message: "Name must not be longer than 30 characters.", + }), + name: z.string(), + siteId: z.number(), +}); + +type AccountFormValues = z.infer; + +const defaultValues: Partial = { + subdomain: "someanimalherefromapi", + name: "My Resource", +}; + +type CreateResourceFormProps = { + open: boolean; + setOpen: (open: boolean) => void; +}; + +export default function CreateResourceForm({ + open, + setOpen, +}: CreateResourceFormProps) { + const { toast } = useToast(); + + const [loading, setLoading] = useState(false); + const params = useParams(); + + const orgId = params.orgId; + const router = useRouter(); + + const [sites, setSites] = useState([]); + const [domainSuffix, setDomainSuffix] = useState(".example.com"); + + const form = useForm({ + resolver: zodResolver(accountFormSchema), + defaultValues, + }); + + useEffect(() => { + if (!open) { + return; + } + + const fetchSites = async () => { + const res = await api.get>( + `/org/${orgId}/sites/` + ); + setSites(res.data.data.sites); + }; + + fetchSites(); + }, [open]); + + async function onSubmit(data: AccountFormValues) { + console.log(data); + + const res = await api + .put>( + `/org/${orgId}/site/${data.siteId}/resource/`, + { + name: data.name, + subdomain: data.subdomain, + // subdomain: data.subdomain, + } + ) + .catch((e) => { + toast({ + title: "Error creating resource", + }); + }); + + if (res && res.status === 201) { + const id = res.data.data.resourceId; + // navigate to the resource page + router.push(`/${orgId}/settings/resources/${id}`); + } + } + + return ( + <> + { + setOpen(val); + setLoading(false); + + // reset all values + form.reset(); + }} + > + + + Create Resource + + Create a new resource to proxy requests to your app + + + +
+ + ( + + Name + + + + + This is the name that will be + displayed for this resource. + + + + )} + /> + ( + + Subdomain + + + + + This is the fully qualified + domain name that will be used to + access the resource. + + + + )} + /> + ( + + Site + + + + + + + + + + + + No site found. + + + {sites.map( + (site) => ( + { + form.setValue( + "siteId", + site.siteId + ); + }} + > + + { + site.name + } + + ) + )} + + + + + + + This is the site that will be + used in the dashboard. + + + + )} + /> + + +
+ + + + + + +
+
+ + ); +} diff --git a/src/app/[orgId]/settings/resources/components/ResourcesTable.tsx b/src/app/[orgId]/settings/resources/components/ResourcesTable.tsx index 63d2ec3e..f07d1e54 100644 --- a/src/app/[orgId]/settings/resources/components/ResourcesTable.tsx +++ b/src/app/[orgId]/settings/resources/components/ResourcesTable.tsx @@ -13,6 +13,8 @@ import { ArrowUpDown, MoreHorizontal } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import api from "@app/api"; +import CreateResourceForm from "./CreateResourceForm"; +import { useState } from "react"; export type ResourceRow = { id: number; @@ -22,91 +24,6 @@ export type ResourceRow = { site: string; }; -export const columns: ColumnDef[] = [ - { - accessorKey: "name", - header: ({ column }) => { - return ( - - ); - }, - }, - { - accessorKey: "site", - header: ({ column }) => { - return ( - - ); - }, - }, - { - accessorKey: "domain", - header: "Domain", - }, - { - id: "actions", - cell: ({ row }) => { - const router = useRouter(); - - const resourceRow = row.original; - - const deleteResource = (resourceId: number) => { - api.delete(`/resource/${resourceId}`) - .catch((e) => { - console.error("Error deleting resource", e); - }) - .then(() => { - router.refresh(); - }); - }; - - return ( - - - - - - - - View settings - - - - - - - - ); - }, - }, -]; - type ResourcesTableProps = { resources: ResourceRow[]; orgId: string; @@ -115,13 +32,109 @@ type ResourcesTableProps = { export default function SitesTable({ resources, orgId }: ResourcesTableProps) { const router = useRouter(); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + + const columns: ColumnDef[] = [ + { + accessorKey: "name", + header: ({ column }) => { + return ( + + ); + }, + }, + { + accessorKey: "site", + header: ({ column }) => { + return ( + + ); + }, + }, + { + accessorKey: "domain", + header: "Domain", + }, + { + id: "actions", + cell: ({ row }) => { + const router = useRouter(); + + const resourceRow = row.original; + + const deleteResource = (resourceId: number) => { + api.delete(`/resource/${resourceId}`) + .catch((e) => { + console.error("Error deleting resource", e); + }) + .then(() => { + router.refresh(); + }); + }; + + return ( + + + + + + + + View settings + + + + + + + + ); + }, + }, + ]; + return ( - { - router.push(`/${orgId}/settings/resources/create`); - }} - /> + <> + + + { + setIsCreateModalOpen(true); + }} + /> + ); }