Team Member listing and Progress Reminder (#4264)
* wip: team member listing * wip: no progress alert * wip: mail icon * feat: Send progress reminder * fix: guard clause * chore: resend invitepull/4269/head
parent
543d3b47ce
commit
fc8ce296be
5 changed files with 329 additions and 96 deletions
@ -0,0 +1,23 @@ |
||||
interface MailIconProps { |
||||
className?: string; |
||||
} |
||||
export function MailIcon(props: MailIconProps) { |
||||
const { className } = props; |
||||
return ( |
||||
<svg |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
width="24" |
||||
height="24" |
||||
viewBox="0 0 24 24" |
||||
fill="none" |
||||
stroke="currentColor" |
||||
stroke-width="2" |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
className={className} |
||||
> |
||||
<rect width="20" height="16" x="2" y="4" rx="2" /> |
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,132 @@ |
||||
import { MailIcon } from '../ReactIcons/MailIcon'; |
||||
import { MemberActionDropdown } from './MemberActionDropdown'; |
||||
import { MemberRoleBadge } from './RoleBadge'; |
||||
import type { TeamMemberItem } from './TeamMembersPage'; |
||||
|
||||
type TeamMemberProps = { |
||||
member: TeamMemberItem; |
||||
userId: string; |
||||
index: number; |
||||
teamId: string; |
||||
canManageCurrentTeam: boolean; |
||||
handleDeleteMember: () => void; |
||||
onUpdateMember: () => void; |
||||
handleSendReminder: () => void; |
||||
onResendInvite: () => void; |
||||
}; |
||||
|
||||
export function TeamMemberItem(props: TeamMemberProps) { |
||||
const { |
||||
member, |
||||
index, |
||||
onResendInvite, |
||||
onUpdateMember, |
||||
canManageCurrentTeam, |
||||
userId, |
||||
handleDeleteMember, |
||||
handleSendReminder, |
||||
} = props; |
||||
|
||||
const showNoProgress = |
||||
member.progress.length === 0 && member.status === 'joined'; |
||||
const showReminder = |
||||
member.progress.length === 0 && |
||||
member.status === 'joined' && |
||||
!(member.userId === userId); |
||||
|
||||
return ( |
||||
<div |
||||
className={`flex items-center justify-between gap-2 p-3 ${ |
||||
index === 0 ? '' : 'border-t' |
||||
}`}
|
||||
> |
||||
<div className="flex items-center gap-3"> |
||||
<img |
||||
src={ |
||||
member.avatar |
||||
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${member.avatar}` |
||||
: '/images/default-avatar.png' |
||||
} |
||||
alt={member.name || ''} |
||||
className="hidden h-10 w-10 rounded-full sm:block" |
||||
/> |
||||
<div> |
||||
<div className="mb-1 flex items-center gap-2 sm:hidden"> |
||||
<MemberRoleBadge role={member.role} /> |
||||
{showReminder && ( |
||||
<SendProgressReminder handleSendReminder={handleSendReminder} /> |
||||
)} |
||||
</div> |
||||
<div className="flex items-center"> |
||||
<h3 className="inline-grid grid-cols-[auto_auto_auto] items-center font-medium"> |
||||
<span className="truncate">{member.name}</span> |
||||
{showNoProgress && ( |
||||
<span className="ml-2 rounded-full bg-gray-600 px-2 py-0.5 text-xs font-normal text-white sm:inline"> |
||||
No Progress |
||||
</span> |
||||
)} |
||||
{member.userId === userId && ( |
||||
<span className="ml-2 hidden text-xs font-normal text-blue-500 sm:inline"> |
||||
You |
||||
</span> |
||||
)} |
||||
</h3> |
||||
<div className="ml-2 flex items-center gap-0.5"> |
||||
{member.status === 'invited' && ( |
||||
<span className="rounded-full bg-yellow-100 px-2 py-0.5 text-xs text-yellow-700"> |
||||
Invited |
||||
</span> |
||||
)} |
||||
{member.status === 'rejected' && ( |
||||
<span className="rounded-full bg-red-100 px-2 py-0.5 text-xs text-red-700"> |
||||
Rejected |
||||
</span> |
||||
)} |
||||
</div> |
||||
</div> |
||||
<p className="truncate text-sm text-gray-500"> |
||||
{member.invitedEmail} |
||||
</p> |
||||
</div> |
||||
</div> |
||||
|
||||
<div className="flex shrink-0 items-center text-sm"> |
||||
{showReminder && ( |
||||
<span className="hidden sm:block"> |
||||
<SendProgressReminder handleSendReminder={handleSendReminder} /> |
||||
</span> |
||||
)} |
||||
<span class={'hidden sm:block'}> |
||||
<MemberRoleBadge role={member.role} /> |
||||
</span> |
||||
{canManageCurrentTeam && ( |
||||
<MemberActionDropdown |
||||
onResendInvite={onResendInvite} |
||||
onDeleteMember={handleDeleteMember} |
||||
isDisabled={member.userId === userId} |
||||
onUpdateMember={onUpdateMember} |
||||
member={member} |
||||
/> |
||||
)} |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
type SendProgressReminderProps = { |
||||
handleSendReminder: () => void; |
||||
}; |
||||
|
||||
function SendProgressReminder(props: SendProgressReminderProps) { |
||||
const { handleSendReminder } = props; |
||||
|
||||
return ( |
||||
<button |
||||
onClick={handleSendReminder} |
||||
className="mr-2 flex items-center gap-1.5 whitespace-nowrap rounded-full bg-orange-100 px-2 py-0.5 text-xs text-orange-700" |
||||
> |
||||
<MailIcon className="h-3 w-3" /> |
||||
<span>Reminder</span> |
||||
</button> |
||||
); |
||||
} |
Loading…
Reference in new issue