Skip to content

Commit

Permalink
feat: Members list and Invite list, new shadcn components (no styles)
Browse files Browse the repository at this point in the history
  • Loading branch information
BlankParticle committed May 25, 2024
1 parent 5393289 commit 9ffc66b
Show file tree
Hide file tree
Showing 24 changed files with 1,775 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const invitesRouter = router({
// Insert teamMemberships - save ID
if (teamsInput) {
for (const teamPublicId of teamsInput.teamsPublicIds) {
await addOrgMemberToTeamHandler({
await addOrgMemberToTeamHandler(db, {
orgId: org.id,
teamPublicId: teamPublicId,
orgMemberPublicId: orgMemberPublicId,
Expand Down
27 changes: 15 additions & 12 deletions apps/platform/trpc/routers/orgRouter/users/teamsHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TRPCError } from '@trpc/server';
import { db } from '@u22n/database';
import type { DBType } from '@u22n/database';
import { and, eq } from '@u22n/database/orm';
import {
convoParticipantTeamMembers,
Expand All @@ -10,17 +10,20 @@ import {
} from '@u22n/database/schema';
import { typeIdGenerator, type TypeId } from '@u22n/utils/typeid';

export async function addOrgMemberToTeamHandler({
orgId,
orgMemberId,
orgMemberPublicId,
teamPublicId
}: {
orgId: number;
orgMemberId: number;
orgMemberPublicId: TypeId<'orgMembers'>;
teamPublicId: TypeId<'teams'>;
}) {
export async function addOrgMemberToTeamHandler(
db: DBType,
{
orgId,
orgMemberId,
orgMemberPublicId,
teamPublicId
}: {
orgId: number;
orgMemberId: number;
orgMemberPublicId: TypeId<'orgMembers'>;
teamPublicId: TypeId<'teams'>;
}
) {
const orgMember = await db.query.orgMembers.findFirst({
columns: {
id: true,
Expand Down
4 changes: 2 additions & 2 deletions apps/platform/trpc/routers/orgRouter/users/teamsRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const teamsRouter = router({
message: 'Account or Organization is not defined'
});
}
const { org } = ctx;
const { org, db } = ctx;
const { teamPublicId, orgMemberPublicId } = input;

const isAdmin = await isAccountAdminOfOrg(org);
Expand All @@ -189,7 +189,7 @@ export const teamsRouter = router({
});
}

const newTeamMemberPublicId = await addOrgMemberToTeamHandler({
const newTeamMemberPublicId = await addOrgMemberToTeamHandler(db, {
orgId: org.id,
teamPublicId: teamPublicId,
orgMemberPublicId: orgMemberPublicId,
Expand Down
7 changes: 7 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@
"@calcom/embed-react": "^1.5.0",
"@phosphor-icons/react": "^2.1.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/themes": "^3.0.2",
"@simplewebauthn/browser": "^10.0.0",
Expand Down
6 changes: 4 additions & 2 deletions apps/web/src/app/[orgShortCode]/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client';
import { Flex } from '@radix-ui/themes';
import { Flex, ScrollArea } from '@radix-ui/themes';
import SettingsSidebar from './_components/settings-sidebar';

export default function Layout({
Expand All @@ -8,7 +8,9 @@ export default function Layout({
return (
<Flex className="h-full w-full">
<SettingsSidebar />
<Flex className="flex-1">{children}</Flex>
<Flex className="flex-1">
<ScrollArea className="h-full">{children}</ScrollArea>
</Flex>
</Flex>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
'use client';

import { createColumnHelper, type ColumnDef } from '@tanstack/react-table';
import type { RouterOutputs } from '@/src/lib/trpc';
import { generateAvatarUrl, getInitials } from '@/src/lib/utils';
import {
Avatar,
AvatarFallback,
AvatarImage
} from '@/src/components/shadcn-ui/avatar';
import { Badge } from '@/src/components/shadcn-ui/badge';
import { format } from 'date-fns';
import { ScrollArea } from '@radix-ui/themes';
import { Tooltip } from '@radix-ui/themes';
import CopyButton from '@/src/components/copy-button';
import { env } from 'next-runtime-env';

const WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');

type Member =
RouterOutputs['org']['users']['invites']['viewInvites']['invites'][number];

const columnHelper = createColumnHelper<Member>();

export const columns: ColumnDef<Member>[] = [
columnHelper.display({
id: 'status',
header: 'Status',
cell: ({ row }) => (
<div className="flex h-full w-full items-center">
<Badge className="uppercase">
{row.original.acceptedAt ? 'Used' : 'Pending'}
</Badge>
</div>
)
}),
columnHelper.display({
id: 'user',
header: 'User',
cell: ({ row }) => {
const { publicId, avatarTimestamp, firstName, lastName } =
row.original.orgMember?.profile ?? {};

const avatarUrl =
avatarTimestamp && publicId
? generateAvatarUrl({
avatarTimestamp,
publicId,
size: 'lg'
})
: null;
const initials = getInitials(`${firstName} ${lastName}`);
return (
<div className="flex items-center gap-2">
<Avatar className="h-8 w-8">
<AvatarImage
src={avatarUrl ?? undefined}
alt={firstName ?? ''}
/>
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
<span>
{firstName} {lastName}
</span>
</div>
);
}
}),
columnHelper.display({
id: 'invite-code',
header: 'Invite Code',
cell: ({ row }) => {
const inviteCode = row.original.inviteToken;
return inviteCode ? (
<div className="flex w-fit items-center justify-between gap-2">
<ScrollArea
scrollbars="horizontal"
className="w-32"
type="hover">
<span>{inviteCode}</span>
</ScrollArea>
<CopyButton
text={inviteCode}
size={12}
/>
</div>
) : null;
}
}),
columnHelper.display({
id: 'invite-link',
header: 'Invite Link',
cell: ({ row }) => {
const inviteCode = row.original.inviteToken;
return inviteCode ? (
<div className="flex w-fit items-center justify-between gap-2">
<ScrollArea
scrollbars="horizontal"
className="w-32"
type="hover">
<span>{`${WEBAPP_URL}/join/invite/${inviteCode}`}</span>
</ScrollArea>
<CopyButton
text={`${WEBAPP_URL}/join/invite/${inviteCode}`}
size={12}
/>
</div>
) : null;
}
}),
columnHelper.display({
id: 'email',
header: 'Email',
cell: ({ row }) => {
const email = row.original.email;
return <div className="flex h-full items-center">{email}</div>;
}
}),
columnHelper.display({
id: 'role',
header: 'Role',
cell: ({ row }) => {
const role = row.original.role;
return (
<div className="flex h-full items-center">
<Badge className="uppercase">{role}</Badge>
</div>
);
}
}),
columnHelper.display({
id: 'admin',
header: 'Admin',
cell: ({ row }) => {
const { publicId, avatarTimestamp, firstName, lastName } =
row.original.invitedByOrgMember.profile;

const avatarUrl =
avatarTimestamp && publicId
? generateAvatarUrl({
avatarTimestamp,
publicId,
size: 'lg'
})
: null;
const initials = getInitials(`${firstName} ${lastName}`);
return (
<div className="flex items-center justify-center gap-2">
<Tooltip content={`${firstName} ${lastName}`}>
<Avatar className="h-8 w-8">
<AvatarImage
src={avatarUrl ?? undefined}
alt={firstName ?? ''}
/>
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
</Tooltip>
</div>
);
}
}),
columnHelper.display({
id: 'expiry',
header: 'Expiry',
cell: ({ row }) => {
const expiry = row.original.expiresAt;
return expiry ? (
<div className="flex h-full items-center">
{format(expiry, 'eee, do MMM yyyy')}
</div>
) : null;
}
})
];
Loading

0 comments on commit 9ffc66b

Please sign in to comment.