-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d036ab9
commit 62a32bb
Showing
11 changed files
with
963 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
...b/src/app/[orgShortCode]/settings/org/users/teams/[teamId]/_components/add-new-member.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { Button } from '@/src/components/shadcn-ui/button'; | ||
import { api } from '@/src/lib/trpc'; | ||
import { useGlobalStore } from '@/src/providers/global-store-provider'; | ||
import { type TypeId } from '@u22n/utils/typeid'; | ||
import { | ||
Select, | ||
SelectContent, | ||
SelectItem, | ||
SelectTrigger, | ||
SelectValue | ||
} from '@/src/components/shadcn-ui/select'; | ||
import { useState } from 'react'; | ||
import { toast } from 'sonner'; | ||
|
||
type Props = { | ||
teamId: TypeId<'teams'>; | ||
existingMembers: TypeId<'orgMembers'>[]; | ||
complete: () => Promise<void>; | ||
}; | ||
|
||
export function AddNewMember({ teamId, existingMembers, complete }: Props) { | ||
const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode); | ||
const { data: allMembers, isLoading } = | ||
api.org.users.members.getOrgMembers.useQuery({ | ||
orgShortCode | ||
}); | ||
const { mutateAsync: addNewMember, isLoading: isAdding } = | ||
api.org.users.teams.addOrgMemberToTeam.useMutation({ | ||
onError: (error) => { | ||
toast.error(error.message); | ||
} | ||
}); | ||
const [selectedMember, setSelectedMember] = useState(''); | ||
|
||
return isLoading ? ( | ||
<div className="font-bold">Loading...</div> | ||
) : ( | ||
<div className="flex w-fit flex-col gap-2"> | ||
<div className="font-bold">Add a new Member</div> | ||
<Select | ||
value={selectedMember} | ||
onValueChange={setSelectedMember}> | ||
<SelectTrigger> | ||
<SelectValue placeholder="Select a Member" /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
{allMembers?.members | ||
?.filter((m) => !existingMembers.includes(m.publicId)) | ||
.map((m) => ( | ||
<SelectItem | ||
key={m.publicId} | ||
value={m.publicId}> | ||
{`${m.profile.firstName} ${m.profile.lastName}`} | ||
</SelectItem> | ||
))} | ||
</SelectContent> | ||
</Select> | ||
<Button | ||
className="w-fit" | ||
disabled={!selectedMember || isAdding} | ||
// loading={isAdding} | ||
onClick={async () => { | ||
await addNewMember({ | ||
orgShortCode, | ||
teamPublicId: teamId, | ||
orgMemberPublicId: selectedMember | ||
}); | ||
await complete(); | ||
}}> | ||
{isAdding ? 'Adding...' : 'Add Member'} | ||
</Button> | ||
</div> | ||
); | ||
} |
70 changes: 70 additions & 0 deletions
70
apps/web/src/app/[orgShortCode]/settings/org/users/teams/[teamId]/_components/columns.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
'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'; | ||
|
||
type Member = | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
(RouterOutputs['org']['users']['teams']['getTeam']['team'] & {})['members'][number]; | ||
|
||
const columnHelper = createColumnHelper<Member>(); | ||
|
||
export const columns: ColumnDef<Member>[] = [ | ||
columnHelper.display({ | ||
id: 'name', | ||
header: 'Name', | ||
cell: ({ row }) => { | ||
if (!row.original.orgMemberProfile) return null; | ||
const { publicId, avatarTimestamp, firstName, lastName } = | ||
row.original.orgMemberProfile; | ||
|
||
const avatarUrl = generateAvatarUrl({ | ||
avatarTimestamp, | ||
publicId, | ||
size: 'lg' | ||
}); | ||
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} ${lastName}`} | ||
/> | ||
<AvatarFallback>{initials}</AvatarFallback> | ||
</Avatar> | ||
<span>{`${firstName} ${lastName}`}</span> | ||
</div> | ||
); | ||
} | ||
}), | ||
columnHelper.display({ | ||
id: 'username', | ||
header: 'Username', | ||
cell: ({ row }) => { | ||
return ( | ||
<div className="flex h-full items-center"> | ||
@{row.original.orgMemberProfile?.handle ?? null} | ||
</div> | ||
); | ||
} | ||
}), | ||
columnHelper.display({ | ||
id: 'title', | ||
header: 'Title', | ||
cell: ({ row }) => { | ||
return ( | ||
<div className="flex h-full items-center"> | ||
{row.original.orgMemberProfile?.title ?? null} | ||
</div> | ||
); | ||
} | ||
}) | ||
]; |
83 changes: 83 additions & 0 deletions
83
...eb/src/app/[orgShortCode]/settings/org/users/teams/[teamId]/_components/member-editor.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* This Component is ready but the backend is yet to implemented, so we are currently using a simpler one member at a time setup until the backend is ready | ||
*/ | ||
|
||
import { Button } from '@/src/components/shadcn-ui/button'; | ||
import { MultiSelect } from '@/src/components/shared/multiselect'; | ||
import { api } from '@/src/lib/trpc'; | ||
import { useGlobalStore } from '@/src/providers/global-store-provider'; | ||
import { type TypeId } from '@u22n/utils/typeid'; | ||
import { useState, useEffect } from 'react'; | ||
|
||
type Props = { | ||
teamId: TypeId<'teams'>; | ||
existingMembers: TypeId<'orgMembers'>[]; | ||
complete: () => void; | ||
}; | ||
|
||
export function EditMemberList({ teamId, existingMembers, complete }: Props) { | ||
const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode); | ||
const { data: allMembers, isLoading } = | ||
api.org.users.members.getOrgMembers.useQuery({ | ||
orgShortCode | ||
}); | ||
const { mutateAsync: saveList } = | ||
api.org.users.teams.updateTeamMembers.useMutation(); | ||
|
||
const [selectedMembers, setSelectedMembers] = useState<string[]>([]); | ||
|
||
useEffect(() => { | ||
if (!allMembers) return; | ||
setSelectedMembers( | ||
allMembers.members | ||
?.filter((member) => | ||
existingMembers.some( | ||
(existingMember) => existingMember === member.publicId | ||
) | ||
) | ||
.map((m) => m.publicId) ?? [] | ||
); | ||
}, [allMembers, existingMembers]); | ||
|
||
return isLoading ? ( | ||
<div className="font-bold">Loading...</div> | ||
) : ( | ||
<div className="flex flex-col gap-2"> | ||
<div className="font-bold">Edit Members List</div> | ||
<MultiSelect | ||
items={ | ||
allMembers?.members?.map((m) => ({ | ||
name: `${m.profile.firstName} ${m.profile.lastName}`, | ||
value: m.publicId, | ||
keywords: [m.profile.handle ?? '', m.profile.title ?? ''] | ||
})) ?? [] | ||
} | ||
ItemRenderer={(item) => <div>{item.name}</div>} | ||
values={selectedMembers} | ||
setValues={setSelectedMembers} | ||
TriggerRenderer={(props) => ( | ||
<div className="flex flex-wrap gap-1"> | ||
{props.items.map((item, i, all) => ( | ||
<span key={item.value}> | ||
{item.name} | ||
{i !== all.length - 1 && ', '} | ||
</span> | ||
))} | ||
</div> | ||
)} | ||
/> | ||
<Button | ||
className="w-fit" | ||
onClick={async () => { | ||
await saveList({ | ||
orgShortCode, | ||
teamPublicId: teamId, | ||
orgMemberPublicIds: selectedMembers | ||
}); | ||
complete(); | ||
}}> | ||
Save | ||
</Button> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.