Add create team page

This commit is contained in:
KMKoushik
2024-04-30 21:31:54 +10:00
parent b352210185
commit 7996a8e373
6 changed files with 215 additions and 27 deletions

View File

@@ -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 (
<NextAuthProvider session={session}>
<div className="flex min-h-screen w-full h-full">

View File

@@ -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<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
name: "",
},
});
function onSubmit(data: z.infer<typeof FormSchema>) {
createTeam.mutate(data, {
onSuccess: () => {
router.replace("/dashboard");
},
});
}
return (
<div className="flex items-center justify-center min-h-screen ">
<div className=" w-[300px] flex flex-col gap-8">
<div>
<h1 className="text-2xl font-semibold text-center">Create Team</h1>
</div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className=" flex flex-col gap-8 w-full"
>
<FormField
control={form.control}
name="name"
render={({ field, formState }) => (
<FormItem>
<FormControl>
<Input
placeholder="Team name"
className="w-full"
{...field}
/>
</FormControl>
{formState.errors.name ? (
<FormMessage />
) : (
<FormDescription>
Request admin to join existing team
</FormDescription>
)}
</FormItem>
)}
/>
<Button type="submit" disabled={createTeam.isPending}>
{createTeam.isPending ? (
<Spinner className="w-5 h-5" />
) : (
"Create"
)}
</Button>
</form>
</Form>
</div>
</div>
);
}

View File

@@ -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 (
<main className="h-screen">
<h1 className="text-center text-4xl mt-20 flex gap-4 justify-center items-center">
<SendHorizonal />
Send emails in minutes. Completely open source
</h1>
<div className="flex justify-center mt-10">
{session?.user ? (
<Button className="mx-auto">
<Link href="/dashboard" className="mx-auto">
Send email
</Link>
</Button>
) : (
<Button className="mx-auto">
<Link href="api/auth/signin" className="mx-auto">
Signin
</Link>
</Button>
)}
</div>
</main>
);
if (!session?.user) {
redirect("/login");
}
if (!session.user.isBetaUser) {
redirect("/wait-list");
} else {
redirect("/dashboard");
}
}

View File

@@ -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

View File

@@ -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;
}),
});

View File

@@ -0,0 +1,50 @@
import React from "react";
export const Spinner: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
return (
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 2400 2400"
xmlSpace="preserve"
{...props}
>
<g
strokeWidth="200"
strokeLinecap="round"
className="stroke-primary-foreground"
fill="none"
id="spinner"
>
<line x1="1200" y1="600" x2="1200" y2="100" />
<line opacity="0.5" x1="1200" y1="2300" x2="1200" y2="1800" />
<line opacity="0.917" x1="900" y1="680.4" x2="650" y2="247.4" />
<line opacity="0.417" x1="1750" y1="2152.6" x2="1500" y2="1719.6" />
<line opacity="0.833" x1="680.4" y1="900" x2="247.4" y2="650" />
<line opacity="0.333" x1="2152.6" y1="1750" x2="1719.6" y2="1500" />
<line opacity="0.75" x1="600" y1="1200" x2="100" y2="1200" />
<line opacity="0.25" x1="2300" y1="1200" x2="1800" y2="1200" />
<line opacity="0.667" x1="680.4" y1="1500" x2="247.4" y2="1750" />
<line opacity="0.167" x1="2152.6" y1="650" x2="1719.6" y2="900" />
<line opacity="0.583" x1="900" y1="1719.6" x2="650" y2="2152.6" />
<line opacity="0.083" x1="1750" y1="247.4" x2="1500" y2="680.4" />
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
keyTimes="0;0.08333;0.16667;0.25;0.33333;0.41667;0.5;0.58333;0.66667;0.75;0.83333;0.91667"
values="0 1199 1199;30 1199 1199;60 1199 1199;90 1199 1199;120 1199 1199;150 1199 1199;180 1199 1199;210 1199 1199;240 1199 1199;270 1199 1199;300 1199 1199;330 1199 1199"
dur="0.83333s"
begin="0s"
repeatCount="indefinite"
calcMode="discrete"
/>
</g>
</svg>
);
};
export default Spinner;