From f067a01e47f2788ebba4a621987d2d961dd58ba3 Mon Sep 17 00:00:00 2001 From: Derock Date: Sat, 24 Feb 2024 22:18:43 +0100 Subject: [PATCH] feat: domain slide --- .../[serviceId]/domains/DomainsList.tsx | 15 ++-- .../[serviceId]/domains/[domainId]/layout.tsx | 54 +++++++++++++ .../domains/[domainId]/loading.tsx | 9 +++ .../[serviceId]/domains/[domainId]/page.tsx | 78 +++++++++---------- .../domains/_components/DomainEntry.tsx | 56 +++++-------- .../service/[serviceId]/domains/layout.tsx | 17 +++- src/components/FormInput.tsx | 2 +- src/components/ui/sheet.tsx | 68 ++++++++-------- 8 files changed, 177 insertions(+), 122 deletions(-) create mode 100644 src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/layout.tsx create mode 100644 src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/loading.tsx diff --git a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/DomainsList.tsx b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/DomainsList.tsx index b343cce..cea699a 100644 --- a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/DomainsList.tsx +++ b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/DomainsList.tsx @@ -1,9 +1,9 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { motion } from "framer-motion"; +import { AnimatePresence, motion } from "framer-motion"; import { PlusIcon } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, type ReactNode } from "react"; import { useFieldArray, useForm } from "react-hook-form"; import { uuidv7 } from "uuidv7"; import { z } from "zod"; @@ -37,8 +37,10 @@ export type DomainsListForm = z.infer; export default function DomainsList({ defaultData, + children, }: { defaultData: RouterOutputs["projects"]["services"]["get"]; + children: ReactNode; }) { const service = useService(undefined, defaultData); const updateDomain = api.projects.services.updateDomain.useMutation(); @@ -65,11 +67,6 @@ export default function DomainsList({ ); }, [form, service.data?.domains]); - console.log( - "Rendering fields with ids: ", - domainsForm.fields.map((field) => field.id), - ); - return (
@@ -164,6 +159,8 @@ export default function DomainsList({ {/* */} {/* */} + + {children} ); diff --git a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/layout.tsx b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/layout.tsx new file mode 100644 index 0000000..2c8d91d --- /dev/null +++ b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/layout.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { usePathname, useRouter } from "next/navigation"; +import { useEffect, useState, type ReactNode } from "react"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "~/components/ui/sheet"; + +export default function DomainSlideLayout({ + children, +}: { + children: ReactNode; +}) { + const router = useRouter(); + const pathname = usePathname(); + + // need to keep state separately just so DOM elements dont get unloaded and break close animation + const [open, setOpen] = useState(true); + + // re-open on new nav + useEffect(() => { + setOpen(true); + }, [pathname]); + + return ( + { + if (!open) { + setOpen(false); + router.push( + new URL(pathname + "/../", window.location.href).pathname, + ); + } + }} + > + + + Edit Domain + + Make advanced changes to the reverse-proxy configuration for this + domain. Hit the save button on the main page to apply changes. + Reloading will cause you to lose unsaved edits. + + + {children} + + + ); +} diff --git a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/loading.tsx b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/loading.tsx new file mode 100644 index 0000000..54b103e --- /dev/null +++ b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/loading.tsx @@ -0,0 +1,9 @@ +import { CgSpinner } from "react-icons/cg"; + +export default function DomainSlideLoading() { + return ( +
+ +
+ ); +} diff --git a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/page.tsx b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/page.tsx index bdcc2e9..2576ca9 100644 --- a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/page.tsx +++ b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/[domainId]/page.tsx @@ -1,46 +1,42 @@ -import { Button } from "~/components/ui/button"; -import { Input } from "~/components/ui/input"; -import { Label } from "~/components/ui/label"; -import { - Sheet, - SheetClose, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, -} from "~/components/ui/sheet"; +"use client"; + +import { useSelectedLayoutSegment } from "next/navigation"; +import { useMemo } from "react"; +import { useFormContext } from "react-hook-form"; +import { Switch } from "~/components/ui/switch"; +import { SimpleFormField } from "~/hooks/forms"; +import { type DomainsListForm } from "../DomainsList"; export default function DomainPage() { + const form = useFormContext(); + const domainId = useSelectedLayoutSegment(); + + const index = useMemo( + () => + form.getValues("domains").findIndex( + (domain) => + domain.domainId === domainId || + // @ts-expect-error this exists + domain.id === domainId, + ), + [form, domainId], + ); + return ( - - - - Edit profile - - Make changes to your profile here. Click save when you're done. - - -
-
- - -
-
- - -
-
- - - - - -
-
+
+ + Automatically redirects all HTTP requests to HTTPS. Recommended by + default. Redirects will go to the same page using the same domain + and all query paramters will be preserved. + + } + name={`domains.${index}.forceSSL`} + render={({ field }) => } + /> +
); } diff --git a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/_components/DomainEntry.tsx b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/_components/DomainEntry.tsx index 3d8fbb3..30b5609 100644 --- a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/_components/DomainEntry.tsx +++ b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/_components/DomainEntry.tsx @@ -1,39 +1,35 @@ "use client"; -import { AnimatePresence, motion } from "framer-motion"; +import { motion } from "framer-motion"; import { ArrowRight, CogIcon, TrashIcon } from "lucide-react"; -import { forwardRef, useState } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { forwardRef } from "react"; import { useFormContext, type UseFieldArrayReturn } from "react-hook-form"; import { Button } from "~/components/ui/button"; import { Card } from "~/components/ui/card"; import { Switch } from "~/components/ui/switch"; import { SimpleFormField } from "~/hooks/forms"; +import { useProject } from "../../../../_context/ProjectContext"; import { type DomainsListForm } from "../DomainsList"; -type FieldData = { - id: string; // internal ID for react-form-hook - domainId?: string; - domain: string; - internalPort: number; - https: boolean; - forceSSL: boolean; -}; - const DomainEntry = forwardRef< HTMLDivElement, { - field: FieldData; + field: DomainsListForm["domains"][number] & { + id: string; // react-hook-form adds this + }; index: number; domains: UseFieldArrayReturn; } >(({ field, index, domains }, ref) => { const form = useFormContext(); - const [isOpen, setIsOpen] = useState(false); + const router = useRouter(); + const pathname = usePathname(); + const project = useProject(); return ( { - setIsOpen(!isOpen); + const domains = pathname.trim().endsWith("/domains") + ? pathname.trim() + : pathname.replace(/\/[A-z0-9]*?$/, ``); + + console.log(`${domains}/${field.domainId ?? field.id}`); + router.push( + `${project.servicePath}/domains/${ + field.domainId ?? field.id + }`, + ); }} /> @@ -95,27 +100,6 @@ const DomainEntry = forwardRef< /> - - - {isOpen && ( - -

Advanced Settings

- - ( -
- -
- )} - /> - - {/* TODO: allow custom SSL certificates */} -
- )} -
); diff --git a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/layout.tsx b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/layout.tsx index f4ce767..4ba136d 100644 --- a/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/layout.tsx +++ b/src/app/(dashboard)/project/[projectId]/service/[serviceId]/domains/layout.tsx @@ -15,5 +15,20 @@ export default async function DomainsLayout(props: { serviceId: props.params.serviceId, }); - return ; + return ( + <> +
+
+

+ By exposing this service, it will be added to a global network for the + reverse proxy and thus will be able to communicate with other services + from different projects that also have an exposed domain. To avoid + this, consider setting up a separate proxy (traefik, freenginx, etc) + for this project and expose that instead. +

+
+ + {props.children} + + ); } diff --git a/src/components/FormInput.tsx b/src/components/FormInput.tsx index 06b5f59..918f4c1 100644 --- a/src/components/FormInput.tsx +++ b/src/components/FormInput.tsx @@ -1,4 +1,4 @@ -import { GetInputProps, UseFormReturnType } from "@mantine/form/lib/types"; +import { type UseFormReturnType } from "@mantine/form/lib/types"; import { Label } from "./ui/label"; import { Required } from "./ui/required"; diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index 7e3f6cd..7e1cfe1 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -1,19 +1,19 @@ -"use client" +"use client"; -import * as React from "react" -import * as SheetPrimitive from "@radix-ui/react-dialog" -import { Cross2Icon } from "@radix-ui/react-icons" -import { cva, type VariantProps } from "class-variance-authority" +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "~/utils/utils.ts" +import { cn } from "~/utils/utils"; -const Sheet = SheetPrimitive.Root +const Sheet = SheetPrimitive.Root; -const SheetTrigger = SheetPrimitive.Trigger +const SheetTrigger = SheetPrimitive.Trigger; -const SheetClose = SheetPrimitive.Close +const SheetClose = SheetPrimitive.Close; -const SheetPortal = SheetPrimitive.Portal +const SheetPortal = SheetPrimitive.Portal; const SheetOverlay = React.forwardRef< React.ElementRef, @@ -22,13 +22,13 @@ const SheetOverlay = React.forwardRef< -)) -SheetOverlay.displayName = SheetPrimitive.Overlay.displayName +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", @@ -46,8 +46,8 @@ const sheetVariants = cva( defaultVariants: { side: "right", }, - } -) + }, +); interface SheetContentProps extends React.ComponentPropsWithoutRef, @@ -71,8 +71,8 @@ const SheetContent = React.forwardRef< -)) -SheetContent.displayName = SheetPrimitive.Content.displayName +)); +SheetContent.displayName = SheetPrimitive.Content.displayName; const SheetHeader = ({ className, @@ -81,12 +81,12 @@ const SheetHeader = ({
-) -SheetHeader.displayName = "SheetHeader" +); +SheetHeader.displayName = "SheetHeader"; const SheetFooter = ({ className, @@ -95,12 +95,12 @@ const SheetFooter = ({
-) -SheetFooter.displayName = "SheetFooter" +); +SheetFooter.displayName = "SheetFooter"; const SheetTitle = React.forwardRef< React.ElementRef, @@ -111,8 +111,8 @@ const SheetTitle = React.forwardRef< className={cn("text-lg font-semibold text-foreground", className)} {...props} /> -)) -SheetTitle.displayName = SheetPrimitive.Title.displayName +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; const SheetDescription = React.forwardRef< React.ElementRef, @@ -123,18 +123,18 @@ const SheetDescription = React.forwardRef< className={cn("text-sm text-muted-foreground", className)} {...props} /> -)) -SheetDescription.displayName = SheetPrimitive.Description.displayName +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; export { Sheet, - SheetPortal, - SheetOverlay, - SheetTrigger, SheetClose, SheetContent, - SheetHeader, - SheetFooter, - SheetTitle, SheetDescription, -} + SheetFooter, + SheetHeader, + SheetOverlay, + SheetPortal, + SheetTitle, + SheetTrigger, +};