From 7996a8e373f7bcc1b191525b1b7660b8dbf20641 Mon Sep 17 00:00:00 2001 From: KMKoushik Date: Tue, 30 Apr 2024 21:31:54 +1000 Subject: [PATCH] Add create team page --- apps/web/src/app/(dashboard)/layout.tsx | 11 +++ apps/web/src/app/create-team/page.tsx | 92 +++++++++++++++++++++++++ apps/web/src/app/page.tsx | 37 +++------- apps/web/src/server/api/root.ts | 2 + apps/web/src/server/api/routers/team.ts | 50 ++++++++++++++ packages/ui/src/spinner.tsx | 50 ++++++++++++++ 6 files changed, 215 insertions(+), 27 deletions(-) create mode 100644 apps/web/src/app/create-team/page.tsx create mode 100644 apps/web/src/server/api/routers/team.ts create mode 100644 packages/ui/src/spinner.tsx diff --git a/apps/web/src/app/(dashboard)/layout.tsx b/apps/web/src/app/(dashboard)/layout.tsx index 4ddbd61..f98916f 100644 --- a/apps/web/src/app/(dashboard)/layout.tsx +++ b/apps/web/src/app/(dashboard)/layout.tsx @@ -31,6 +31,7 @@ import { Sheet, SheetContent, SheetTrigger } from "@unsend/ui/src/sheet"; import { NextAuthProvider } from "~/providers/next-auth"; import { getServerAuthSession } from "~/server/auth"; import { NavButton } from "./nav-button"; +import { db } from "~/server/db"; export const metadata = { title: "Unsend", @@ -53,6 +54,16 @@ export default async function AuthenticatedDashboardLayout({ redirect("/wait-list"); } + const teamUser = await db.teamUser.findFirst({ + where: { + userId: session.user.id, + }, + }); + + if (!teamUser) { + redirect("/create-team"); + } + return (
diff --git a/apps/web/src/app/create-team/page.tsx b/apps/web/src/app/create-team/page.tsx new file mode 100644 index 0000000..17e1721 --- /dev/null +++ b/apps/web/src/app/create-team/page.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { Button } from "@unsend/ui/src/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormMessage, +} from "@unsend/ui/src/form"; +import { Input } from "@unsend/ui/src/input"; +import { Spinner } from "@unsend/ui/src/spinner"; +import { api } from "~/trpc/react"; +import { useRouter } from "next/navigation"; + +const FormSchema = z.object({ + name: z.string().min(2, { + message: "Team name must be at least 2 characters.", + }), +}); + +export default function CreateTeam() { + const createTeam = api.team.createTeam.useMutation(); + + const router = useRouter(); + + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + name: "", + }, + }); + + function onSubmit(data: z.infer) { + createTeam.mutate(data, { + onSuccess: () => { + router.replace("/dashboard"); + }, + }); + } + + return ( +
+
+
+

Create Team

+
+
+ + ( + + + + + {formState.errors.name ? ( + + ) : ( + + Request admin to join existing team + + )} + + )} + /> + + + +
+
+ ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 63d8ad1..faa42fb 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,33 +1,16 @@ -import Link from "next/link"; - import { getServerAuthSession } from "~/server/auth"; -import { Button } from "@unsend/ui/src/button"; -import { SendHorizonal } from "lucide-react"; +import { redirect } from "next/navigation"; export default async function Home() { const session = await getServerAuthSession(); - return ( -
-

- - Send emails in minutes. Completely open source -

-
- {session?.user ? ( - - ) : ( - - )} -
-
- ); + if (!session?.user) { + redirect("/login"); + } + + if (!session.user.isBetaUser) { + redirect("/wait-list"); + } else { + redirect("/dashboard"); + } } diff --git a/apps/web/src/server/api/root.ts b/apps/web/src/server/api/root.ts index 81d2142..4ea876d 100644 --- a/apps/web/src/server/api/root.ts +++ b/apps/web/src/server/api/root.ts @@ -2,6 +2,7 @@ import { domainRouter } from "~/server/api/routers/domain"; import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc"; import { apiRouter } from "./routers/api"; import { emailRouter } from "./routers/email"; +import { teamRouter } from "./routers/team"; /** * This is the primary router for your server. @@ -12,6 +13,7 @@ export const appRouter = createTRPCRouter({ domain: domainRouter, apiKey: apiRouter, email: emailRouter, + team: teamRouter, }); // export type definition of API diff --git a/apps/web/src/server/api/routers/team.ts b/apps/web/src/server/api/routers/team.ts new file mode 100644 index 0000000..5ab274b --- /dev/null +++ b/apps/web/src/server/api/routers/team.ts @@ -0,0 +1,50 @@ +import { z } from "zod"; + +import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; + +export const teamRouter = createTRPCRouter({ + createTeam: protectedProcedure + .input(z.object({ name: z.string() })) + .mutation(async ({ ctx, input }) => { + const teams = await ctx.db.team.findMany({ + where: { + teamUsers: { + some: { + userId: ctx.session.user.id, + }, + }, + }, + }); + + if (teams) { + console.log("User already has a team"); + return; + } + + return ctx.db.team.create({ + data: { + name: input.name, + teamUsers: { + create: { + userId: ctx.session.user.id, + role: "ADMIN", + }, + }, + }, + }); + }), + + getTeams: protectedProcedure.query(async ({ ctx }) => { + const teams = await ctx.db.team.findMany({ + where: { + teamUsers: { + some: { + userId: ctx.session.user.id, + }, + }, + }, + }); + + return teams; + }), +}); diff --git a/packages/ui/src/spinner.tsx b/packages/ui/src/spinner.tsx new file mode 100644 index 0000000..d06396b --- /dev/null +++ b/packages/ui/src/spinner.tsx @@ -0,0 +1,50 @@ +import React from "react"; + +export const Spinner: React.FC> = (props) => { + return ( + + + + + + + + + + + + + + + + + + ); +}; + +export default Spinner;