From 233e19276e3caf63824c2c4651f5d09285c93a64 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 17 Sep 2024 21:54:03 +0100 Subject: [PATCH] feat: update workspace name --- src/components/Input.tsx | 19 +++-- src/pages/settings/index.tsx | 2 + src/server/api/routers/workspace.ts | 16 ++++ src/server/db/repository/workspace.repo.ts | 14 ++++ .../components/UpdateWorkspaceNameForm.tsx | 79 +++++++++++++++++++ src/views/settings/index.tsx | 19 ++++- 6 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 src/views/settings/components/UpdateWorkspaceNameForm.tsx diff --git a/src/components/Input.tsx b/src/components/Input.tsx index fb23ef55..567ac3c3 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -4,13 +4,14 @@ import ContentEditable from "react-contenteditable"; interface InputProps extends React.InputHTMLAttributes { contentEditable?: boolean; value?: string; + errorMessage?: string; onChange?: ( e: React.ChangeEvent, ) => void; } const Input = forwardRef( - ({ contentEditable, value, onChange, ...props }, ref) => { + ({ contentEditable, errorMessage, value, onChange, ...props }, ref) => { if (contentEditable) { return ( ( } return ( - +
+ + {errorMessage && ( +
{errorMessage}
+ )} +
); }, ); diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx index 280e6654..a9428bbb 100644 --- a/src/pages/settings/index.tsx +++ b/src/pages/settings/index.tsx @@ -1,6 +1,7 @@ import { WorkspaceProvider } from "~/providers/workspace"; import Dashboard from "~/components/dashboard"; import SettingsView from "~/views/settings"; +import Popup from "~/components/Popup"; export default function SettingsPage() { return ( @@ -8,6 +9,7 @@ export default function SettingsPage() { + ); } diff --git a/src/server/api/routers/workspace.ts b/src/server/api/routers/workspace.ts index b5efdaeb..b2c87d37 100644 --- a/src/server/api/routers/workspace.ts +++ b/src/server/api/routers/workspace.ts @@ -55,6 +55,22 @@ export const workspaceRouter = createTRPCRouter({ code: "INTERNAL_SERVER_ERROR", }); + return result; + }), + update: protectedProcedure + .input( + z.object({ + workspacePublicId: z.string().min(12), + name: z.string().min(3).max(24), + }), + ) + .mutation(async ({ ctx, input }) => { + const result = await workspaceRepo.update( + ctx.db, + input.workspacePublicId, + input.name, + ); + return result; }), delete: protectedProcedure diff --git a/src/server/db/repository/workspace.repo.ts b/src/server/db/repository/workspace.repo.ts index 633e47f1..0af00eca 100644 --- a/src/server/db/repository/workspace.repo.ts +++ b/src/server/db/repository/workspace.repo.ts @@ -38,6 +38,20 @@ export const create = async ( return newWorkspace; }; +export const update = async ( + db: SupabaseClient, + workspacePublicId: string, + name: string, +) => { + const { data, error } = await db + .from("workspace") + .update({ name }) + .eq("publicId", workspacePublicId) + .is("deletedAt", null); + + return data; +}; + export const getByPublicId = async ( db: SupabaseClient, workspacePublicId: string, diff --git a/src/views/settings/components/UpdateWorkspaceNameForm.tsx b/src/views/settings/components/UpdateWorkspaceNameForm.tsx new file mode 100644 index 00000000..28d3e1ba --- /dev/null +++ b/src/views/settings/components/UpdateWorkspaceNameForm.tsx @@ -0,0 +1,79 @@ +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import Input from "~/components/Input"; +import Button from "~/components/Button"; +import { usePopup } from "~/providers/popup"; +import { api } from "~/utils/api"; + +const schema = z.object({ + name: z + .string() + .min(3, { message: "Workspace name must be at least 3 characters long" }) + .max(24, { message: "Workspace name cannot exceed 24 characters" }), +}); + +type FormValues = z.infer; + +const UpdateWorkspaceNameForm = ({ + workspacePublicId, + workspaceName, +}: { + workspacePublicId: string; + workspaceName: string; +}) => { + const utils = api.useUtils(); + const { showPopup } = usePopup(); + const { + register, + handleSubmit, + formState: { isDirty, errors }, + } = useForm({ + resolver: zodResolver(schema), + values: { + name: workspaceName, + }, + }); + + const updateWorkspaceName = api.workspace.update.useMutation({ + onSuccess: async () => { + try { + await utils.workspace.all.refetch(); + } catch (e) { + console.error(e); + throw e; + } + }, + onError: () => { + showPopup({ + header: "Error updating workspace name", + message: "Please try again later, or contact customer support.", + }); + }, + }); + + const onSubmit = (data: FormValues) => { + updateWorkspaceName.mutate({ + workspacePublicId, + name: data.name, + }); + }; + + return ( + <> +
+ +
+ + + ); +}; + +export default UpdateWorkspaceNameForm; diff --git a/src/views/settings/index.tsx b/src/views/settings/index.tsx index ca229c7a..9d57919f 100644 --- a/src/views/settings/index.tsx +++ b/src/views/settings/index.tsx @@ -1,10 +1,17 @@ -import { useModal } from "~/providers/modal"; +import { useForm } from "react-hook-form"; + import Modal from "~/components/modal"; import Button from "~/components/Button"; + import { DeleteWorkspaceConfirmation } from "./components/DeleteWorkspaceConfirmation"; +import UpdateWorkspaceNameForm from "./components/UpdateWorkspaceNameForm"; + +import { useModal } from "~/providers/modal"; +import { useWorkspace } from "~/providers/workspace"; export default function SettingsPage() { const { modalContentType, openModal } = useModal(); + const { workspace } = useWorkspace(); return (
@@ -14,6 +21,16 @@ export default function SettingsPage() {
+
+

+ Workspace name +

+ +
+

Delete workspace