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 (
+
+
+ );
+}
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) => {