diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/general/_components/CopyToClipboardButton.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/general/_components/CopyToClipboardButton.tsx new file mode 100644 index 000000000..f61db548a --- /dev/null +++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/general/_components/CopyToClipboardButton.tsx @@ -0,0 +1,43 @@ +"use client"; + +import * as React from "react"; + +import { Button } from "@openstatus/ui"; +import type { ButtonProps } from "@openstatus/ui"; + +import { Icons } from "@/components/icons"; +import { copyToClipboard } from "@/lib/utils"; + +export function CopyToClipboardButton({ + children, + onClick, + ...props +}: ButtonProps) { + const [hasCopied, setHasCopied] = React.useState(false); + + React.useEffect(() => { + if (hasCopied) { + setTimeout(() => { + setHasCopied(false); + }, 2000); + } + }, [hasCopied]); + + return ( + + ); +} diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/general/page.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/general/page.tsx index ab24a0900..930658d8c 100644 --- a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/general/page.tsx +++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/general/page.tsx @@ -1,12 +1,27 @@ +import { Separator } from "@openstatus/ui"; + +import { WorkspaceForm } from "@/components/forms/workspace-form"; import { api } from "@/trpc/server"; +import { CopyToClipboardButton } from "./_components/CopyToClipboardButton"; export default async function GeneralPage() { const data = await api.workspace.getWorkspace.query(); return ( -
- Your workspace slug is:{" "} - {data.slug}. +
+ + +
+

Workspace Slug

+

+ The unique identifier for your workspace. +

+
+ + {data.slug} + +
+
); } diff --git a/apps/web/src/components/forms/workspace-form.tsx b/apps/web/src/components/forms/workspace-form.tsx new file mode 100644 index 000000000..0e4da2ddf --- /dev/null +++ b/apps/web/src/components/forms/workspace-form.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useTransition } from "react"; +import { useRouter } from "next/navigation"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { insertWorkspaceSchema } from "@openstatus/db/src/schema"; +import { + Button, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, +} from "@openstatus/ui"; + +import { useToastAction } from "@/hooks/use-toast-action"; +import { api } from "@/trpc/client"; +import { LoadingAnimation } from "../loading-animation"; + +// or insertWorkspaceSchema.pick({ name: true }) and updating name to not be nullable +const schema = z.object({ + name: z.string().min(3, "workspace names must contain at least 3 characters"), +}); +type Schema = z.infer; + +export function WorkspaceForm({ defaultValues }: { defaultValues: Schema }) { + const form = useForm({ + resolver: zodResolver(schema), + defaultValues, + }); + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const { toast } = useToastAction(); + + async function onSubmit(data: Schema) { + startTransition(async () => { + try { + await api.workspace.updateWorkspace.mutate(data); + toast("saved"); + router.refresh(); + } catch { + toast("error"); + } + }); + } + + return ( +
+ + ( + + Name + + + + The name of your workspace. + + + )} + /> +
+ +
+ + + ); +} diff --git a/packages/api/src/router/workspace.ts b/packages/api/src/router/workspace.ts index 58facb3fa..226b63889 100644 --- a/packages/api/src/router/workspace.ts +++ b/packages/api/src/router/workspace.ts @@ -47,14 +47,6 @@ export const workspaceRouter = createTRPCRouter({ }), getWorkspaceUsers: protectedProcedure.query(async (opts) => { - // const result = await opts.ctx.db - // .select() - // .from(usersToWorkspaces) - // .leftJoin(user, eq(user.id, usersToWorkspaces.userId)) - // .where(eq(usersToWorkspaces.workspaceId, opts.ctx.workspace.id)) - // .all(); - // return result.map(({ user }) => user); - const result = await opts.ctx.db.query.usersToWorkspaces.findMany({ with: { user: true, @@ -64,6 +56,15 @@ export const workspaceRouter = createTRPCRouter({ return result; }), + updateWorkspace: protectedProcedure + .input(z.object({ name: z.string() })) + .mutation(async (opts) => { + return await opts.ctx.db + .update(workspace) + .set({ name: opts.input.name }) + .where(eq(workspace.id, opts.ctx.workspace.id)); + }), + removeWorkspaceUser: protectedProcedure .input(z.object({ id: z.number() })) .mutation(async (opts) => {