diff --git a/apps/web/src/pages/[...workspaceSlug]/index.tsx b/apps/web/src/pages/[...workspaceSlug]/index.tsx
new file mode 100644
index 00000000..89d04a52
--- /dev/null
+++ b/apps/web/src/pages/[...workspaceSlug]/index.tsx
@@ -0,0 +1,5 @@
+import WorkspaceSlugView from "~/views/workspaceSlug";
+
+export default function WorkspaceSlugPage() {
+ return ;
+}
diff --git a/apps/web/src/views/workspaceSlug/index.tsx b/apps/web/src/views/workspaceSlug/index.tsx
new file mode 100644
index 00000000..b1ef788f
--- /dev/null
+++ b/apps/web/src/views/workspaceSlug/index.tsx
@@ -0,0 +1,97 @@
+import Link from "next/link";
+import { useRouter } from "next/router";
+
+import { PageHead } from "~/components/PageHead";
+import PatternedBackground from "~/components/PatternedBackground";
+import { api } from "~/utils/api";
+
+export default function WorkspaceSlugPage() {
+ const router = useRouter();
+
+ const workspaceSlug = Array.isArray(router.query.workspaceSlug)
+ ? router.query.workspaceSlug[0]
+ : router.query.workspaceSlug;
+
+ const { data, isLoading } = api.workspace.bySlug.useQuery(
+ {
+ workspaceSlug: workspaceSlug ?? "",
+ },
+ { enabled: !!workspaceSlug },
+ );
+
+ const BoardsList = ({
+ isLoading,
+ boards,
+ workspaceSlug,
+ }: {
+ isLoading: boolean;
+ boards: { publicId: string; name: string }[];
+ workspaceSlug: string;
+ }) => {
+ if (isLoading)
+ return (
+
+ );
+
+ if (boards.length === 0) return <>>;
+
+ return (
+
+ {boards.map((board) => (
+
+
+
+ ))}
+
+ );
+ };
+
+ return (
+ <>
+
+
+
+
+ {data?.name}
+
+
+ The open source Trello alternative.
+
+
+ {data?.boards && workspaceSlug && (
+
+ )}
+
+
+ kan.bn
+
+
+ >
+ );
+}
diff --git a/packages/api/src/routers/workspace.ts b/packages/api/src/routers/workspace.ts
index 56a3e976..0ae2cb6f 100644
--- a/packages/api/src/routers/workspace.ts
+++ b/packages/api/src/routers/workspace.ts
@@ -64,6 +64,29 @@ export const workspaceRouter = createTRPCRouter({
code: "NOT_FOUND",
});
+ return result;
+ }),
+ bySlug: publicProcedure
+ .meta({
+ openapi: {
+ summary: "Get a workspace by slug",
+ method: "GET",
+ path: "/workspaces/slug/{workspaceSlug}",
+ description: "Retrieves a workspace by its slug",
+ tags: ["Workspaces"],
+ protect: true,
+ },
+ })
+ .input(z.object({ workspaceSlug: z.string().min(3).max(24) }))
+ .output(
+ z.custom>>(),
+ )
+ .query(async ({ ctx, input }) => {
+ const result = await workspaceRepo.getBySlugWithBoards(
+ ctx.db,
+ input.workspaceSlug,
+ );
+
return result;
}),
create: protectedProcedure
diff --git a/packages/db/src/repository/workspace.repo.ts b/packages/db/src/repository/workspace.repo.ts
index 92866ce5..5cc8a9c3 100644
--- a/packages/db/src/repository/workspace.repo.ts
+++ b/packages/db/src/repository/workspace.repo.ts
@@ -101,6 +101,32 @@ export const getByPublicIdWithMembers = async (
return data;
};
+export const getBySlugWithBoards = async (
+ db: SupabaseClient,
+ workspaceSlug: string,
+) => {
+ const { data } = await db
+ .from("workspace")
+ .select(
+ `
+ publicId,
+ name,
+ slug,
+ boards: board (
+ publicId,
+ name
+ )
+ `,
+ )
+ .eq("slug", workspaceSlug)
+ .is("deletedAt", null)
+ .is("boards.deletedAt", null)
+ .limit(1)
+ .single();
+
+ return data;
+};
+
export const getAllByUserId = async (
db: SupabaseClient,
userId: string,