Skip to content

Commit

Permalink
feat: adding pending invited users screen
Browse files Browse the repository at this point in the history
  • Loading branch information
aacevski committed Feb 11, 2025
1 parent 118a4b5 commit 138dc70
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { and, eq, isNull } from "drizzle-orm";
import db from "../../database";
import { userTable, workspaceUserTable } from "../../database/schema";

async function getPendingWorkspaceUsers({
workspaceId,
}: {
workspaceId: string;
}) {
const pendingInvites = await db
.select({
id: workspaceUserTable.id,
email: workspaceUserTable.userEmail,
role: workspaceUserTable.role,
invitedAt: workspaceUserTable.joinedAt,
})
.from(workspaceUserTable)
.leftJoin(userTable, eq(workspaceUserTable.userEmail, userTable.email))
.where(
and(
eq(workspaceUserTable.workspaceId, workspaceId),
isNull(userTable.email),
),
);

return pendingInvites;
}

export default getPendingWorkspaceUsers;
9 changes: 8 additions & 1 deletion apps/api/src/workspace-user/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import Elysia, { t } from "elysia";
import getPendingWorkspaceUsers from "./controllers/get-pending-workspace-users";
import getWorkspaceUsers from "./controllers/get-workspace-users";
import inviteWorkspaceUser from "./controllers/invite-workspace-user";

const workspaceUser = new Elysia({ prefix: "/workspace-user" })
.get("/list/:workspaceId", async ({ params: { workspaceId } }) => {
const workspaceUsersInWorkspace = await getWorkspaceUsers({ workspaceId });
console.log({ workspaceUsersInWorkspace });

return workspaceUsersInWorkspace;
})
.get("/pending/list/:workspaceId", async ({ params: { workspaceId } }) => {
const pendingWorkspaceUsersInWorkspace = await getPendingWorkspaceUsers({
workspaceId,
});

return pendingWorkspaceUsersInWorkspace;
})
.post(
"/:workspaceId/invite",
async ({ body }) => {
Expand Down
43 changes: 4 additions & 39 deletions apps/web/src/components/team/invite-team-member-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import useInviteWorkspaceUser from "@/hooks/mutations/workspace-user/use-invite-workspace-user";
import useGetWorkspaces from "@/hooks/queries/workspace/use-get-workspaces";
import { Route } from "@/routes/dashboard/workspace/$workspaceId/team";
import { zodResolver } from "@hookform/resolvers/zod";
import * as Dialog from "@radix-ui/react-dialog";
import { Building2, X } from "lucide-react";
import { X } from "lucide-react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "../ui/button";
Expand All @@ -15,7 +15,6 @@ import {
FormMessage,
} from "../ui/form";
import { Input } from "../ui/input";
import { Select } from "../ui/select";

type Props = {
open: boolean;
Expand All @@ -24,24 +23,22 @@ type Props = {

const teamMemberSchema = z.object({
userEmail: z.string().email(),
workspaceId: z.string(),
});

type TeamMemberFormValues = z.infer<typeof teamMemberSchema>;

function InviteTeamMemberModal({ open, onClose }: Props) {
const { data: workspaces } = useGetWorkspaces();
const { mutateAsync } = useInviteWorkspaceUser();
const { workspaceId } = Route.useParams();

const form = useForm<TeamMemberFormValues>({
resolver: zodResolver(teamMemberSchema),
defaultValues: {
userEmail: "",
workspaceId: "",
},
});

const onSubmit = async ({ userEmail, workspaceId }: TeamMemberFormValues) => {
const onSubmit = async ({ userEmail }: TeamMemberFormValues) => {
await mutateAsync({ userEmail, workspaceId });

form.reset();
Expand Down Expand Up @@ -87,38 +84,6 @@ function InviteTeamMemberModal({ open, onClose }: Props) {
)}
/>
</div>

<FormField
control={form.control}
name="workspaceId"
render={({ field }) => (
<FormItem>
<FormLabel className="block text-sm font-medium text-zinc-900 dark:text-zinc-300 mb-1">
Workspace
</FormLabel>
<FormControl>
<Select
{...field}
options={[
{
value: "",
label: "Select a workspace",
icon: (
<Building2 className="w-4 h-4 text-zinc-400" />
),
},
...(workspaces ?? []).map((workspace) => ({
value: workspace.id,
label: workspace.name,
})),
]}
placeholder="Select workspace"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<div className="flex justify-end gap-2 mt-6">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { api } from "@kaneo/libs";

async function getPendingWorkspaceUsers({
workspaceId,
}: { workspaceId: string }) {
const response = await api["workspace-user"].pending
.list({ workspaceId })
.get();

if (response.error) {
throw new Error(response.error.value.message);
}

return response.data;
}

export default getPendingWorkspaceUsers;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import getPendingWorkspaceUsers from "@/fetchers/workspace-user/get-pending-workspace-users";
import { useQuery } from "@tanstack/react-query";

function useGetPendingWorkspaceUsers({ workspaceId }: { workspaceId: string }) {
return useQuery({
queryKey: ["pending-workspace-users", workspaceId],
queryFn: () => getPendingWorkspaceUsers({ workspaceId }),
});
}

export default useGetPendingWorkspaceUsers;
113 changes: 107 additions & 6 deletions apps/web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { Route as AuthSignUpImport } from './routes/auth/sign-up'
import { Route as AuthSignInImport } from './routes/auth/sign-in'
import { Route as DashboardWorkspaceWorkspaceIdImport } from './routes/dashboard/workspace/$workspaceId'
import { Route as DashboardWorkspaceWorkspaceIdTeamImport } from './routes/dashboard/workspace/$workspaceId/team'
import { Route as DashboardWorkspaceWorkspaceIdTeamRolesImport } from './routes/dashboard/workspace/$workspaceId/team/roles'
import { Route as DashboardWorkspaceWorkspaceIdTeamMembersImport } from './routes/dashboard/workspace/$workspaceId/team/members'
import { Route as DashboardWorkspaceWorkspaceIdTeamInvitationsImport } from './routes/dashboard/workspace/$workspaceId/team/invitations'
import { Route as DashboardWorkspaceWorkspaceIdProjectProjectIdImport } from './routes/dashboard/workspace/$workspaceId/project/$projectId'

// Create/Update Routes
Expand Down Expand Up @@ -59,6 +62,27 @@ const DashboardWorkspaceWorkspaceIdTeamRoute =
getParentRoute: () => DashboardWorkspaceWorkspaceIdRoute,
} as any)

const DashboardWorkspaceWorkspaceIdTeamRolesRoute =
DashboardWorkspaceWorkspaceIdTeamRolesImport.update({
id: '/roles',
path: '/roles',
getParentRoute: () => DashboardWorkspaceWorkspaceIdTeamRoute,
} as any)

const DashboardWorkspaceWorkspaceIdTeamMembersRoute =
DashboardWorkspaceWorkspaceIdTeamMembersImport.update({
id: '/members',
path: '/members',
getParentRoute: () => DashboardWorkspaceWorkspaceIdTeamRoute,
} as any)

const DashboardWorkspaceWorkspaceIdTeamInvitationsRoute =
DashboardWorkspaceWorkspaceIdTeamInvitationsImport.update({
id: '/invitations',
path: '/invitations',
getParentRoute: () => DashboardWorkspaceWorkspaceIdTeamRoute,
} as any)

const DashboardWorkspaceWorkspaceIdProjectProjectIdRoute =
DashboardWorkspaceWorkspaceIdProjectProjectIdImport.update({
id: '/project/$projectId',
Expand Down Expand Up @@ -119,20 +143,62 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardWorkspaceWorkspaceIdProjectProjectIdImport
parentRoute: typeof DashboardWorkspaceWorkspaceIdImport
}
'/dashboard/workspace/$workspaceId/team/invitations': {
id: '/dashboard/workspace/$workspaceId/team/invitations'
path: '/invitations'
fullPath: '/dashboard/workspace/$workspaceId/team/invitations'
preLoaderRoute: typeof DashboardWorkspaceWorkspaceIdTeamInvitationsImport
parentRoute: typeof DashboardWorkspaceWorkspaceIdTeamImport
}
'/dashboard/workspace/$workspaceId/team/members': {
id: '/dashboard/workspace/$workspaceId/team/members'
path: '/members'
fullPath: '/dashboard/workspace/$workspaceId/team/members'
preLoaderRoute: typeof DashboardWorkspaceWorkspaceIdTeamMembersImport
parentRoute: typeof DashboardWorkspaceWorkspaceIdTeamImport
}
'/dashboard/workspace/$workspaceId/team/roles': {
id: '/dashboard/workspace/$workspaceId/team/roles'
path: '/roles'
fullPath: '/dashboard/workspace/$workspaceId/team/roles'
preLoaderRoute: typeof DashboardWorkspaceWorkspaceIdTeamRolesImport
parentRoute: typeof DashboardWorkspaceWorkspaceIdTeamImport
}
}
}

// Create and export the route tree

interface DashboardWorkspaceWorkspaceIdTeamRouteChildren {
DashboardWorkspaceWorkspaceIdTeamInvitationsRoute: typeof DashboardWorkspaceWorkspaceIdTeamInvitationsRoute
DashboardWorkspaceWorkspaceIdTeamMembersRoute: typeof DashboardWorkspaceWorkspaceIdTeamMembersRoute
DashboardWorkspaceWorkspaceIdTeamRolesRoute: typeof DashboardWorkspaceWorkspaceIdTeamRolesRoute
}

const DashboardWorkspaceWorkspaceIdTeamRouteChildren: DashboardWorkspaceWorkspaceIdTeamRouteChildren =
{
DashboardWorkspaceWorkspaceIdTeamInvitationsRoute:
DashboardWorkspaceWorkspaceIdTeamInvitationsRoute,
DashboardWorkspaceWorkspaceIdTeamMembersRoute:
DashboardWorkspaceWorkspaceIdTeamMembersRoute,
DashboardWorkspaceWorkspaceIdTeamRolesRoute:
DashboardWorkspaceWorkspaceIdTeamRolesRoute,
}

const DashboardWorkspaceWorkspaceIdTeamRouteWithChildren =
DashboardWorkspaceWorkspaceIdTeamRoute._addFileChildren(
DashboardWorkspaceWorkspaceIdTeamRouteChildren,
)

interface DashboardWorkspaceWorkspaceIdRouteChildren {
DashboardWorkspaceWorkspaceIdTeamRoute: typeof DashboardWorkspaceWorkspaceIdTeamRoute
DashboardWorkspaceWorkspaceIdTeamRoute: typeof DashboardWorkspaceWorkspaceIdTeamRouteWithChildren
DashboardWorkspaceWorkspaceIdProjectProjectIdRoute: typeof DashboardWorkspaceWorkspaceIdProjectProjectIdRoute
}

const DashboardWorkspaceWorkspaceIdRouteChildren: DashboardWorkspaceWorkspaceIdRouteChildren =
{
DashboardWorkspaceWorkspaceIdTeamRoute:
DashboardWorkspaceWorkspaceIdTeamRoute,
DashboardWorkspaceWorkspaceIdTeamRouteWithChildren,
DashboardWorkspaceWorkspaceIdProjectProjectIdRoute:
DashboardWorkspaceWorkspaceIdProjectProjectIdRoute,
}
Expand Down Expand Up @@ -161,8 +227,11 @@ export interface FileRoutesByFullPath {
'/auth/sign-in': typeof AuthSignInRoute
'/auth/sign-up': typeof AuthSignUpRoute
'/dashboard/workspace/$workspaceId': typeof DashboardWorkspaceWorkspaceIdRouteWithChildren
'/dashboard/workspace/$workspaceId/team': typeof DashboardWorkspaceWorkspaceIdTeamRoute
'/dashboard/workspace/$workspaceId/team': typeof DashboardWorkspaceWorkspaceIdTeamRouteWithChildren
'/dashboard/workspace/$workspaceId/project/$projectId': typeof DashboardWorkspaceWorkspaceIdProjectProjectIdRoute
'/dashboard/workspace/$workspaceId/team/invitations': typeof DashboardWorkspaceWorkspaceIdTeamInvitationsRoute
'/dashboard/workspace/$workspaceId/team/members': typeof DashboardWorkspaceWorkspaceIdTeamMembersRoute
'/dashboard/workspace/$workspaceId/team/roles': typeof DashboardWorkspaceWorkspaceIdTeamRolesRoute
}

export interface FileRoutesByTo {
Expand All @@ -171,8 +240,11 @@ export interface FileRoutesByTo {
'/auth/sign-in': typeof AuthSignInRoute
'/auth/sign-up': typeof AuthSignUpRoute
'/dashboard/workspace/$workspaceId': typeof DashboardWorkspaceWorkspaceIdRouteWithChildren
'/dashboard/workspace/$workspaceId/team': typeof DashboardWorkspaceWorkspaceIdTeamRoute
'/dashboard/workspace/$workspaceId/team': typeof DashboardWorkspaceWorkspaceIdTeamRouteWithChildren
'/dashboard/workspace/$workspaceId/project/$projectId': typeof DashboardWorkspaceWorkspaceIdProjectProjectIdRoute
'/dashboard/workspace/$workspaceId/team/invitations': typeof DashboardWorkspaceWorkspaceIdTeamInvitationsRoute
'/dashboard/workspace/$workspaceId/team/members': typeof DashboardWorkspaceWorkspaceIdTeamMembersRoute
'/dashboard/workspace/$workspaceId/team/roles': typeof DashboardWorkspaceWorkspaceIdTeamRolesRoute
}

export interface FileRoutesById {
Expand All @@ -182,8 +254,11 @@ export interface FileRoutesById {
'/auth/sign-in': typeof AuthSignInRoute
'/auth/sign-up': typeof AuthSignUpRoute
'/dashboard/workspace/$workspaceId': typeof DashboardWorkspaceWorkspaceIdRouteWithChildren
'/dashboard/workspace/$workspaceId/team': typeof DashboardWorkspaceWorkspaceIdTeamRoute
'/dashboard/workspace/$workspaceId/team': typeof DashboardWorkspaceWorkspaceIdTeamRouteWithChildren
'/dashboard/workspace/$workspaceId/project/$projectId': typeof DashboardWorkspaceWorkspaceIdProjectProjectIdRoute
'/dashboard/workspace/$workspaceId/team/invitations': typeof DashboardWorkspaceWorkspaceIdTeamInvitationsRoute
'/dashboard/workspace/$workspaceId/team/members': typeof DashboardWorkspaceWorkspaceIdTeamMembersRoute
'/dashboard/workspace/$workspaceId/team/roles': typeof DashboardWorkspaceWorkspaceIdTeamRolesRoute
}

export interface FileRouteTypes {
Expand All @@ -196,6 +271,9 @@ export interface FileRouteTypes {
| '/dashboard/workspace/$workspaceId'
| '/dashboard/workspace/$workspaceId/team'
| '/dashboard/workspace/$workspaceId/project/$projectId'
| '/dashboard/workspace/$workspaceId/team/invitations'
| '/dashboard/workspace/$workspaceId/team/members'
| '/dashboard/workspace/$workspaceId/team/roles'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
Expand All @@ -205,6 +283,9 @@ export interface FileRouteTypes {
| '/dashboard/workspace/$workspaceId'
| '/dashboard/workspace/$workspaceId/team'
| '/dashboard/workspace/$workspaceId/project/$projectId'
| '/dashboard/workspace/$workspaceId/team/invitations'
| '/dashboard/workspace/$workspaceId/team/members'
| '/dashboard/workspace/$workspaceId/team/roles'
id:
| '__root__'
| '/'
Expand All @@ -214,6 +295,9 @@ export interface FileRouteTypes {
| '/dashboard/workspace/$workspaceId'
| '/dashboard/workspace/$workspaceId/team'
| '/dashboard/workspace/$workspaceId/project/$projectId'
| '/dashboard/workspace/$workspaceId/team/invitations'
| '/dashboard/workspace/$workspaceId/team/members'
| '/dashboard/workspace/$workspaceId/team/roles'
fileRoutesById: FileRoutesById
}

Expand Down Expand Up @@ -272,11 +356,28 @@ export const routeTree = rootRoute
},
"/dashboard/workspace/$workspaceId/team": {
"filePath": "dashboard/workspace/$workspaceId/team.tsx",
"parent": "/dashboard/workspace/$workspaceId"
"parent": "/dashboard/workspace/$workspaceId",
"children": [
"/dashboard/workspace/$workspaceId/team/invitations",
"/dashboard/workspace/$workspaceId/team/members",
"/dashboard/workspace/$workspaceId/team/roles"
]
},
"/dashboard/workspace/$workspaceId/project/$projectId": {
"filePath": "dashboard/workspace/$workspaceId/project/$projectId.tsx",
"parent": "/dashboard/workspace/$workspaceId"
},
"/dashboard/workspace/$workspaceId/team/invitations": {
"filePath": "dashboard/workspace/$workspaceId/team/invitations.tsx",
"parent": "/dashboard/workspace/$workspaceId/team"
},
"/dashboard/workspace/$workspaceId/team/members": {
"filePath": "dashboard/workspace/$workspaceId/team/members.tsx",
"parent": "/dashboard/workspace/$workspaceId/team"
},
"/dashboard/workspace/$workspaceId/team/roles": {
"filePath": "dashboard/workspace/$workspaceId/team/roles.tsx",
"parent": "/dashboard/workspace/$workspaceId/team"
}
}
}
Expand Down
Loading

0 comments on commit 138dc70

Please sign in to comment.