From 39fa230d31cb61987711dd9a90b37c06896c763d Mon Sep 17 00:00:00 2001 From: Oliver Bryan <04oliverbryan@gmail.com> Date: Mon, 29 Dec 2025 04:48:04 +0000 Subject: [PATCH] create organisation dialog form --- .../src/components/create-organisation.tsx | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 packages/frontend/src/components/create-organisation.tsx diff --git a/packages/frontend/src/components/create-organisation.tsx b/packages/frontend/src/components/create-organisation.tsx new file mode 100644 index 0000000..5dd6bc5 --- /dev/null +++ b/packages/frontend/src/components/create-organisation.tsx @@ -0,0 +1,223 @@ +import { type FormEvent, useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { cn, getAuthHeaders } from "@/lib/utils"; + +const slugify = (value: string) => + value + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+/, "") + .replace(/-+$/, "") + .replace(/-{2,}/g, "-"); + +export function CreateOrganisation() { + const serverURL = import.meta.env.VITE_SERVER_URL?.trim() || "http://localhost:3000"; + + const userId = JSON.parse(localStorage.getItem("user") || "{}").id as number | undefined; + + const [open, setOpen] = useState(false); + const [name, setName] = useState(""); + const [slug, setSlug] = useState(""); + const [description, setDescription] = useState(""); + + const [nameTouched, setNameTouched] = useState(false); + const [slugTouched, setSlugTouched] = useState(false); + const [slugManuallyEdited, setSlugManuallyEdited] = useState(false); + const [submitAttempted, setSubmitAttempted] = useState(false); + + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + const nameInvalid = useMemo( + () => ((nameTouched || submitAttempted) && name.trim() === "" ? "Cannot be empty" : ""), + [nameTouched, submitAttempted, name], + ); + const slugInvalid = useMemo( + () => ((slugTouched || submitAttempted) && slug.trim() === "" ? "Cannot be empty" : ""), + [slugTouched, submitAttempted, slug], + ); + + const reset = () => { + setName(""); + setSlug(""); + setDescription(""); + + setNameTouched(false); + setSlugTouched(false); + setSlugManuallyEdited(false); + setSubmitAttempted(false); + + setSubmitting(false); + setError(null); + }; + + const onOpenChange = (nextOpen: boolean) => { + setOpen(nextOpen); + if (!nextOpen) { + reset(); + } + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setError(null); + setSubmitAttempted(true); + + if (name.trim() === "" || slug.trim() === "") { + return; + } + + if (!userId) { + setError("you must be logged in to create an organisation"); + return; + } + + setSubmitting(true); + try { + const url = new URL(`${serverURL}/organisation/create`); + url.searchParams.set("name", name.trim()); + url.searchParams.set("slug", slug.trim()); + url.searchParams.set("userId", `${userId}`); + if (description.trim() !== "") { + url.searchParams.set("description", description.trim()); + } + + const res = await fetch(url.toString(), { + headers: getAuthHeaders(), + }); + + if (!res.ok) { + const message = await res.text(); + setError(message || `failed to create organisation (${res.status})`); + setSubmitting(false); + return; + } + + setOpen(false); + window.location.reload(); + } catch (err) { + console.error(err); + setError("failed to create organisation"); + setSubmitting(false); + } + }; + + return ( + + + + + + + + Create Organisation + {/* Enter the details for the new organisation. */} + + +
+
+
+ + { + const nextName = e.target.value; + setName(nextName); + + if (!slugManuallyEdited) { + setSlug(slugify(nextName)); + } + }} + onBlur={() => setNameTouched(true)} + aria-invalid={nameInvalid !== ""} + placeholder="Demo Organisation" + required + /> +
+ {nameInvalid !== "" ? ( + + ) : ( + + )} +
+
+ +
+ + { + setSlug(slugify(e.target.value)); + setSlugManuallyEdited(true); + }} + onBlur={() => setSlugTouched(true)} + aria-invalid={slugInvalid !== ""} + placeholder="demo-organisation" + required + /> +
+ {slugInvalid !== "" ? ( + + ) : ( + + )} +
+
+ +
+ + setDescription(e.target.value)} + placeholder="What is this organisation for?" + /> +
+ +
+ {error ? ( + + ) : ( + + )} +
+ +
+ + + + +
+
+
+ + {/* */} + {/* */} +
+
+ ); +}