diff --git a/apps/dashboard/src/actions/change-team-action.ts b/apps/dashboard/src/actions/change-team-action.ts index 783421b22..26920a3ec 100644 --- a/apps/dashboard/src/actions/change-team-action.ts +++ b/apps/dashboard/src/actions/change-team-action.ts @@ -7,11 +7,14 @@ import { redirect } from "next/navigation"; import { action } from "./safe-action"; import { changeTeamSchema } from "./schema"; -export const changeTeamAction = action(changeTeamSchema, async ({ teamId }) => { - const supabase = createClient(); - const user = await updateUser(supabase, { team_id: teamId }); +export const changeTeamAction = action( + changeTeamSchema, + async ({ teamId, redirectTo }) => { + const supabase = createClient(); + const user = await updateUser(supabase, { team_id: teamId }); - revalidateTag(`user_${user.data.id}`); + revalidateTag(`user_${user.data.id}`); - redirect("/"); -}); + redirect(redirectTo); + } +); diff --git a/apps/dashboard/src/actions/create-team-action.ts b/apps/dashboard/src/actions/create-team-action.ts index bfe403f29..df54c2720 100644 --- a/apps/dashboard/src/actions/create-team-action.ts +++ b/apps/dashboard/src/actions/create-team-action.ts @@ -3,16 +3,24 @@ import { createTeam, updateUser } from "@midday/supabase/mutations"; import { createClient } from "@midday/supabase/server"; import { revalidateTag } from "next/cache"; +import { redirect } from "next/navigation"; import { action } from "./safe-action"; import { createTeamSchema } from "./schema"; -export const createTeamAction = action(createTeamSchema, async ({ name }) => { - const supabase = createClient(); - const { team_id } = await createTeam(supabase, { name }); - const user = await updateUser(supabase, { team_id }); +export const createTeamAction = action( + createTeamSchema, + async ({ name, redirectTo }) => { + const supabase = createClient(); + const { team_id } = await createTeam(supabase, { name }); + const user = await updateUser(supabase, { team_id }); - revalidateTag(`user_${user.data.id}`); - revalidateTag(`teams_${user.data.id}`); + revalidateTag(`user_${user.data.id}`); + revalidateTag(`teams_${user.data.id}`); - return team_id; -}); + if (redirectTo) { + redirect(redirectTo); + } + + return team_id; + } +); diff --git a/apps/dashboard/src/actions/invite-team-members-action.ts b/apps/dashboard/src/actions/invite-team-members-action.ts index b334362c1..9a8f6f488 100644 --- a/apps/dashboard/src/actions/invite-team-members-action.ts +++ b/apps/dashboard/src/actions/invite-team-members-action.ts @@ -6,9 +6,9 @@ import { getI18n } from "@midday/email/locales"; import { getUser } from "@midday/supabase/cached-queries"; import { createClient } from "@midday/supabase/server"; import { renderAsync } from "@react-email/components"; -import { revalidatePath } from "next/cache"; import { revalidateTag } from "next/cache"; import { headers } from "next/headers"; +import { redirect } from "next/navigation"; import { Resend } from "resend"; import { action } from "./safe-action"; import { inviteTeamMembersSchema } from "./schema"; @@ -17,7 +17,7 @@ const resend = new Resend(env.RESEND_API_KEY); export const inviteTeamMembersAction = action( inviteTeamMembersSchema, - async ({ invites }) => { + async ({ invites, redirectTo }) => { const supabase = createClient(); const user = await getUser(); @@ -69,5 +69,9 @@ export const inviteTeamMembersAction = action( const htmlEmails = await Promise.all(emails); await resend.batch.send(htmlEmails); + + if (redirectTo) { + redirect(redirectTo); + } } ); diff --git a/apps/dashboard/src/actions/leave-team-action.ts b/apps/dashboard/src/actions/leave-team-action.ts index 4a01f74b5..c6c4bc766 100644 --- a/apps/dashboard/src/actions/leave-team-action.ts +++ b/apps/dashboard/src/actions/leave-team-action.ts @@ -4,12 +4,13 @@ import { getTeamMembers, getUser } from "@midday/supabase/cached-queries"; import { leaveTeam } from "@midday/supabase/mutations"; import { createClient } from "@midday/supabase/server"; import { revalidateTag } from "next/cache"; +import { redirect } from "next/navigation"; import { action } from "./safe-action"; import { leaveTeamSchema } from "./schema"; export const leaveTeamAction = action( leaveTeamSchema, - async ({ teamId, role }) => { + async ({ teamId, role, redirectTo }) => { const supabase = createClient(); const user = await getUser(); const { data: teamMembersData } = await getTeamMembers(); @@ -31,6 +32,10 @@ export const leaveTeamAction = action( revalidateTag(`user_${user.data.id}`); revalidateTag(`teams_${user.data.id}`); + if (redirectTo) { + redirect(redirectTo); + } + return data; } ); diff --git a/apps/dashboard/src/actions/schema.ts b/apps/dashboard/src/actions/schema.ts index 94bbf8389..1f34dd7b0 100644 --- a/apps/dashboard/src/actions/schema.ts +++ b/apps/dashboard/src/actions/schema.ts @@ -141,10 +141,12 @@ export const updaterMenuSchema = z.array( export const changeTeamSchema = z.object({ teamId: z.string(), + redirectTo: z.string(), }); export const createTeamSchema = z.object({ name: z.string(), + redirectTo: z.string().optional(), }); export const changeUserRoleSchema = z.object({ @@ -160,6 +162,7 @@ export const deleteTeamMemberSchema = z.object({ export const leaveTeamSchema = z.object({ teamId: z.string(), + redirectTo: z.string().optional(), role: z.enum(["owner", "member"]), }); @@ -174,6 +177,7 @@ export const inviteTeamMembersSchema = z.object({ role: z.enum(["owner", "member"]), }) ), + redirectTo: z.string().optional(), }); export type InviteTeamMembersFormValues = z.infer< diff --git a/apps/dashboard/src/app/[locale]/@dashboard/(root)/layout.tsx b/apps/dashboard/src/app/[locale]/@dashboard/(root)/layout.tsx index bf892cb0e..940d088e1 100644 --- a/apps/dashboard/src/app/[locale]/@dashboard/(root)/layout.tsx +++ b/apps/dashboard/src/app/[locale]/@dashboard/(root)/layout.tsx @@ -4,10 +4,21 @@ import { ConnectBankModal } from "@/components/modals/connect-bank-modal"; import { SelectAccountModal } from "@/components/modals/select-account-modal"; import { Sidebar } from "@/components/sidebar"; import { getCountryCode } from "@midday/location"; +import { getUser } from "@midday/supabase/cached-queries"; +import { redirect } from "next/navigation"; -export default function Layout({ children }: { children: React.ReactNode }) { +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const user = await getUser(); const countryCode = getCountryCode(); + if (!user?.data?.team) { + redirect("/teams"); + } + return (
diff --git a/apps/dashboard/src/app/[locale]/@teams/create/page.tsx b/apps/dashboard/src/app/[locale]/@dashboard/teams/create/page.tsx similarity index 72% rename from apps/dashboard/src/app/[locale]/@teams/create/page.tsx rename to apps/dashboard/src/app/[locale]/@dashboard/teams/create/page.tsx index b8330d0d9..cd2586cda 100644 --- a/apps/dashboard/src/app/[locale]/@teams/create/page.tsx +++ b/apps/dashboard/src/app/[locale]/@dashboard/teams/create/page.tsx @@ -1,7 +1,5 @@ -import { getTeams } from "@midday/supabase/cached-queries"; -import { Button } from "@midday/ui/button"; +import { CreateTeamForm } from "@/components/create-team-form"; import { Icons } from "@midday/ui/icons"; -import { Input } from "@midday/ui/input"; import Link from "next/link"; export default async function CreateTeam() { @@ -30,14 +28,7 @@ export default async function CreateTeam() {

- - - + diff --git a/apps/dashboard/src/app/[locale]/@teams/invite/[code]/page.tsx b/apps/dashboard/src/app/[locale]/@dashboard/teams/invite/[code]/page.tsx similarity index 100% rename from apps/dashboard/src/app/[locale]/@teams/invite/[code]/page.tsx rename to apps/dashboard/src/app/[locale]/@dashboard/teams/invite/[code]/page.tsx diff --git a/apps/dashboard/src/app/[locale]/@teams/invite/page.tsx b/apps/dashboard/src/app/[locale]/@dashboard/teams/invite/page.tsx similarity index 50% rename from apps/dashboard/src/app/[locale]/@teams/invite/page.tsx rename to apps/dashboard/src/app/[locale]/@dashboard/teams/invite/page.tsx index f72bf76cd..18e4479bd 100644 --- a/apps/dashboard/src/app/[locale]/@teams/invite/page.tsx +++ b/apps/dashboard/src/app/[locale]/@dashboard/teams/invite/page.tsx @@ -1,7 +1,5 @@ -import { getTeams } from "@midday/supabase/cached-queries"; -import { Button } from "@midday/ui/button"; +import { InviteForm } from "@/components/invite-form"; import { Icons } from "@midday/ui/icons"; -import { Input } from "@midday/ui/input"; import Link from "next/link"; export default async function InviteMembers() { @@ -25,26 +23,7 @@ export default async function InviteMembers() {

Invite new members by email address

-
- - - -
- -
- - -
+ diff --git a/apps/dashboard/src/app/[locale]/@teams/(select)/page.tsx b/apps/dashboard/src/app/[locale]/@dashboard/teams/page.tsx similarity index 92% rename from apps/dashboard/src/app/[locale]/@teams/(select)/page.tsx rename to apps/dashboard/src/app/[locale]/@dashboard/teams/page.tsx index b949d6a85..aa9ff0f22 100644 --- a/apps/dashboard/src/app/[locale]/@teams/(select)/page.tsx +++ b/apps/dashboard/src/app/[locale]/@dashboard/teams/page.tsx @@ -8,7 +8,7 @@ export default async function Teams() { const { data } = await getTeams(); if (!data.length > 0) { - redirect("/create"); + redirect("/teams/create"); } return ( @@ -32,7 +32,7 @@ export default async function Teams() {
- + Create team
diff --git a/apps/dashboard/src/app/[locale]/layout.tsx b/apps/dashboard/src/app/[locale]/layout.tsx index c42f27b67..8ecdd67c6 100644 --- a/apps/dashboard/src/app/[locale]/layout.tsx +++ b/apps/dashboard/src/app/[locale]/layout.tsx @@ -1,24 +1,20 @@ -import { getUser } from "@midday/supabase/cached-queries"; +import { createClient } from "@midday/supabase/server"; import { Providers } from "./providers"; export default async function Layout({ dashboard, login, - teams, params: { locale }, }: { dashboard: React.ReactNode; login: React.ReactNode; - teams: React.ReactNode; params: { locale: string }; }) { - let content = login; + const supabase = createClient(); - const user = await getUser(); + const { + data: { session }, + } = await supabase.auth.getSession(); - if (user?.data) { - content = user?.data.team ? dashboard : teams; - } - - return {content}; + return {session ? dashboard : login}; } diff --git a/apps/dashboard/src/components/create-team-form.tsx b/apps/dashboard/src/components/create-team-form.tsx new file mode 100644 index 000000000..06c6defe0 --- /dev/null +++ b/apps/dashboard/src/components/create-team-form.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { createTeamAction } from "@/actions/create-team-action"; +import { Button } from "@midday/ui/button"; +import { Input } from "@midday/ui/input"; +import { Loader2 } from "lucide-react"; +import { useAction } from "next-safe-action/hook"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +export function CreateTeamForm() { + const [name, setName] = useState(""); + const router = useRouter(); + const createTeam = useAction(createTeamAction); + + return ( + <> + setName(evt.target.value)} + /> + + + + ); +} diff --git a/apps/dashboard/src/components/delete-team.tsx b/apps/dashboard/src/components/delete-team.tsx index 97752e1f1..076ae4ba8 100644 --- a/apps/dashboard/src/components/delete-team.tsx +++ b/apps/dashboard/src/components/delete-team.tsx @@ -27,7 +27,7 @@ import { useRouter } from "next/navigation"; export function DeleteTeam({ teamId }) { const router = useRouter(); const deleteTeam = useAction(deleteTeamAction, { - onSuccess: () => router.push("/"), + onSuccess: () => router.push("/teams"), }); return ( diff --git a/apps/dashboard/src/components/invite-form.tsx b/apps/dashboard/src/components/invite-form.tsx new file mode 100644 index 000000000..1fe809a2c --- /dev/null +++ b/apps/dashboard/src/components/invite-form.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { inviteTeamMembersAction } from "@/actions/invite-team-members-action"; +import { + InviteTeamMembersFormValues, + inviteTeamMembersSchema, +} from "@/actions/schema"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Button } from "@midday/ui/button"; +import { Form, FormControl, FormField, FormItem } from "@midday/ui/form"; +import { Icons } from "@midday/ui/icons"; +import { Input } from "@midday/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@midday/ui/select"; +import { useToast } from "@midday/ui/use-toast"; +import { Loader2 } from "lucide-react"; +import { useAction } from "next-safe-action/hook"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useFieldArray, useForm } from "react-hook-form"; + +export function InviteForm() { + const router = useRouter(); + const { toast } = useToast(); + + const inviteMembers = useAction(inviteTeamMembersAction, { + onError: () => { + toast({ + duration: 3500, + variant: "error", + title: "Something went wrong pleaase try again.", + }); + }, + }); + + const form = useForm({ + resolver: zodResolver(inviteTeamMembersSchema), + defaultValues: { + invites: [ + { + email: "", + role: "member", + }, + ], + }, + }); + + const onSubmit = form.handleSubmit((data) => { + inviteMembers.execute({ + // Remove invites without email (last appended invite validation) + invites: data.invites.filter((invite) => invite.email !== undefined), + redirectTo: "/", + }); + }); + + const { fields, append } = useFieldArray({ + name: "invites", + control: form.control, + }); + + return ( +
+ + {fields.map((field, index) => ( +
+ ( + + + + + + )} + /> + + ( + + + + )} + /> +
+ ))} + + + +
+
+ {Object.values(form.formState.errors).length > 0 && ( + + Please complete the fields above. + + )} +
+ +
+ + + + + +
+
+
+ + ); +} diff --git a/apps/dashboard/src/components/modals/create-team-modal.tsx b/apps/dashboard/src/components/modals/create-team-modal.tsx index d1ef26835..c941d79e2 100644 --- a/apps/dashboard/src/components/modals/create-team-modal.tsx +++ b/apps/dashboard/src/components/modals/create-team-modal.tsx @@ -12,9 +12,11 @@ import { Loader2 } from "lucide-react"; import { useAction } from "next-safe-action/hook"; import { useState } from "react"; -export function CreateTeamModal() { +export function CreateTeamModal({ onOpenChange }) { const [name, setName] = useState(""); - const createTeam = useAction(createTeamAction); + const createTeam = useAction(createTeamAction, { + onSuccess: () => onOpenChange(false), + }); return ( diff --git a/apps/dashboard/src/components/tables/select-team/table.tsx b/apps/dashboard/src/components/tables/select-team/table.tsx index 3d9e0f813..752f2f41a 100644 --- a/apps/dashboard/src/components/tables/select-team/table.tsx +++ b/apps/dashboard/src/components/tables/select-team/table.tsx @@ -34,7 +34,12 @@ export function SelectTeamTable({ data }) {
diff --git a/apps/dashboard/src/components/tables/teams/table.tsx b/apps/dashboard/src/components/tables/teams/table.tsx index 757e83210..a9c48c2c1 100644 --- a/apps/dashboard/src/components/tables/teams/table.tsx +++ b/apps/dashboard/src/components/tables/teams/table.tsx @@ -42,7 +42,6 @@ import { import { MoreHorizontal } from "lucide-react"; import { Loader2 } from "lucide-react"; import { useAction } from "next-safe-action/hook"; -import { useRouter } from "next/navigation"; import * as React from "react"; export type Payment = { @@ -84,18 +83,11 @@ export const columns: ColumnDef[] = [ { id: "actions", cell: ({ row }) => { - const router = useRouter(); const { toast } = useToast(); - const manageTeam = useAction(changeTeamAction, { - onSuccess: () => router.push("/settings"), - }); - - const viewTeam = useAction(changeTeamAction, { - onSuccess: () => router.push("/"), - }); + const manageTeam = useAction(changeTeamAction); + const viewTeam = useAction(changeTeamAction); const leaveTeam = useAction(leaveTeamAction, { - onSuccess: () => router.push("/teams"), onError: () => { toast({ duration: 3500, @@ -136,7 +128,12 @@ export const columns: ColumnDef[] = [
@@ -144,7 +141,10 @@ export const columns: ColumnDef[] = [ - + {teams.map(({ team }) => { return ( changeTeam.execute({ teamId: team.id })} + onClick={() => + changeTeam.execute({ teamId: team.id, redirectTo: "/" }) + } >