Updates to team functionality

pull/4269/head
Kamran Ahmed 1 year ago
parent fc8ce296be
commit 5b541dfb3d
  1. 5
      src/components/CreateTeam/RoleDropdown.tsx
  2. 26
      src/components/TeamMembers/MemberActionDropdown.tsx
  3. 42
      src/components/TeamMembers/TeamMemberItem.tsx
  4. 24
      src/components/TeamMembers/TeamMembersPage.tsx
  5. 8
      src/components/TeamSettings/UpdateTeamForm.tsx
  6. 2
      src/components/Toast.tsx

@ -86,10 +86,7 @@ export function RoleDropdown(props: RoleDropdownProps) {
}`} }`}
> >
<span <span
className={`capitalize ${ className={`capitalize`}>
selectedRole === 'admin' ? 'text-blue-600' : ''
} ${selectedRole === 'manager' ? 'text-cyan-600' : ''}`}
>
{selectedRole || 'Select Role'} {selectedRole || 'Select Role'}
</span> </span>
<ChevronDownIcon <ChevronDownIcon

@ -1,9 +1,9 @@
import { useRef, useState } from 'preact/hooks'; import { useRef, useState } from 'preact/hooks';
import type { TeamMemberDocument } from './TeamMembersPage'; import type { TeamMemberDocument } from './TeamMembersPage';
import { httpDelete, httpPatch } from '../../lib/http';
import MoreIcon from '../../icons/more-vertical.svg'; import MoreIcon from '../../icons/more-vertical.svg';
import { useOutsideClick } from '../../hooks/use-outside-click'; import { useOutsideClick } from '../../hooks/use-outside-click';
import { useToast } from '../../hooks/use-toast'; import { useToast } from '../../hooks/use-toast';
import { MailIcon } from '../ReactIcons/MailIcon';
export function MemberActionDropdown({ export function MemberActionDropdown({
member, member,
@ -11,14 +11,19 @@ export function MemberActionDropdown({
onDeleteMember, onDeleteMember,
onResendInvite, onResendInvite,
isDisabled = false, isDisabled = false,
onSendProgressReminder,
allowProgressReminder = false,
allowUpdateRole = true,
}: { }: {
onDeleteMember: () => void; onDeleteMember: () => void;
onUpdateMember: () => void; onUpdateMember: () => void;
onResendInvite: () => void; onResendInvite: () => void;
onSendProgressReminder: () => void;
isDisabled: boolean; isDisabled: boolean;
allowProgressReminder: boolean;
allowUpdateRole: boolean;
member: TeamMemberDocument; member: TeamMemberDocument;
}) { }) {
const toast = useToast();
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -35,6 +40,8 @@ export function MemberActionDropdown({
setIsOpen(false); setIsOpen(false);
}, },
}, },
...(allowUpdateRole
? [
{ {
name: 'Update Role', name: 'Update Role',
handleClick: () => { handleClick: () => {
@ -42,6 +49,19 @@ export function MemberActionDropdown({
setIsOpen(false); setIsOpen(false);
}, },
}, },
]
: []),
...(allowProgressReminder
? [
{
name: 'Send Progress Reminder',
handleClick: () => {
onSendProgressReminder();
setIsOpen(false);
},
},
]
: []),
...(['invited'].includes(member.status) ...(['invited'].includes(member.status)
? [ ? [
{ {
@ -67,7 +87,7 @@ export function MemberActionDropdown({
{isOpen && ( {isOpen && (
<div <div
ref={menuRef} ref={menuRef}
className="align-right absolute right-0 top-full z-50 mt-1 w-32 rounded-md bg-slate-800 px-2 py-2 text-white shadow-md" className="align-right absolute right-0 top-full z-50 mt-1 w-[200px] rounded-md bg-slate-800 px-2 py-2 text-white shadow-md"
> >
<ul> <ul>
{actions.map((action, index) => { {actions.map((action, index) => {

@ -2,6 +2,8 @@ import { MailIcon } from '../ReactIcons/MailIcon';
import { MemberActionDropdown } from './MemberActionDropdown'; import { MemberActionDropdown } from './MemberActionDropdown';
import { MemberRoleBadge } from './RoleBadge'; import { MemberRoleBadge } from './RoleBadge';
import type { TeamMemberItem } from './TeamMembersPage'; import type { TeamMemberItem } from './TeamMembersPage';
import { $canManageCurrentTeam } from '../../stores/team';
import { useStore } from '@nanostores/preact';
type TeamMemberProps = { type TeamMemberProps = {
member: TeamMemberItem; member: TeamMemberItem;
@ -9,9 +11,9 @@ type TeamMemberProps = {
index: number; index: number;
teamId: string; teamId: string;
canManageCurrentTeam: boolean; canManageCurrentTeam: boolean;
handleDeleteMember: () => void; onDeleteMember: () => void;
onUpdateMember: () => void; onUpdateMember: () => void;
handleSendReminder: () => void; onSendProgressReminder: () => void;
onResendInvite: () => void; onResendInvite: () => void;
}; };
@ -23,16 +25,17 @@ export function TeamMemberItem(props: TeamMemberProps) {
onUpdateMember, onUpdateMember,
canManageCurrentTeam, canManageCurrentTeam,
userId, userId,
handleDeleteMember, onDeleteMember,
handleSendReminder, onSendProgressReminder,
} = props; } = props;
const showNoProgress = const canManageTeam = useStore($canManageCurrentTeam);
member.progress.length === 0 && member.status === 'joined'; const showNoProgressBadge = !member.hasProgress && member.status === 'joined';
const showReminder = const allowProgressReminder =
member.progress.length === 0 && canManageTeam &&
!member.hasProgress &&
member.status === 'joined' && member.status === 'joined' &&
!(member.userId === userId); member.userId !== userId;
return ( return (
<div <div
@ -53,15 +56,12 @@ export function TeamMemberItem(props: TeamMemberProps) {
<div> <div>
<div className="mb-1 flex items-center gap-2 sm:hidden"> <div className="mb-1 flex items-center gap-2 sm:hidden">
<MemberRoleBadge role={member.role} /> <MemberRoleBadge role={member.role} />
{showReminder && (
<SendProgressReminder handleSendReminder={handleSendReminder} />
)}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<h3 className="inline-grid grid-cols-[auto_auto_auto] items-center font-medium"> <h3 className="inline-grid grid-cols-[auto_auto_auto] items-center font-medium">
<span className="truncate">{member.name}</span> <span className="truncate">{member.name}</span>
{showNoProgress && ( {showNoProgressBadge && (
<span className="ml-2 rounded-full bg-gray-600 px-2 py-0.5 text-xs font-normal text-white sm:inline"> <span className="ml-2 rounded-full bg-red-400 px-2 py-0.5 text-xs font-normal text-white">
No Progress No Progress
</span> </span>
)} )}
@ -91,18 +91,16 @@ export function TeamMemberItem(props: TeamMemberProps) {
</div> </div>
<div className="flex shrink-0 items-center text-sm"> <div className="flex shrink-0 items-center text-sm">
{showReminder && (
<span className="hidden sm:block">
<SendProgressReminder handleSendReminder={handleSendReminder} />
</span>
)}
<span class={'hidden sm:block'}> <span class={'hidden sm:block'}>
<MemberRoleBadge role={member.role} /> <MemberRoleBadge role={member.role} />
</span> </span>
{canManageCurrentTeam && ( {canManageCurrentTeam && (
<MemberActionDropdown <MemberActionDropdown
allowUpdateRole={member.status !== 'rejected'}
allowProgressReminder={allowProgressReminder}
onResendInvite={onResendInvite} onResendInvite={onResendInvite}
onDeleteMember={handleDeleteMember} onSendProgressReminder={onSendProgressReminder}
onDeleteMember={onDeleteMember}
isDisabled={member.userId === userId} isDisabled={member.userId === userId}
onUpdateMember={onUpdateMember} onUpdateMember={onUpdateMember}
member={member} member={member}
@ -123,10 +121,10 @@ function SendProgressReminder(props: SendProgressReminderProps) {
return ( return (
<button <button
onClick={handleSendReminder} 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" className="ml-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" /> <MailIcon className="h-3 w-3" />
<span>Reminder</span> <span>Remind</span>
</button> </button>
); );
} }

@ -1,6 +1,5 @@
import { useEffect, useState } from 'preact/hooks'; import { useEffect, useState } from 'preact/hooks';
import { httpDelete, httpGet, httpPatch } from '../../lib/http'; import { httpDelete, httpGet, httpPatch } from '../../lib/http';
import { MemberActionDropdown } from './MemberActionDropdown';
import { useAuth } from '../../hooks/use-auth'; import { useAuth } from '../../hooks/use-auth';
import { pageProgressMessage } from '../../stores/page'; import { pageProgressMessage } from '../../stores/page';
import type { TeamDocument } from '../CreateTeam/CreateTeamForm'; import type { TeamDocument } from '../CreateTeam/CreateTeamForm';
@ -13,7 +12,6 @@ import { UpdateMemberPopup } from './UpdateMemberPopup';
import { useStore } from '@nanostores/preact'; import { useStore } from '@nanostores/preact';
import { $canManageCurrentTeam } from '../../stores/team'; import { $canManageCurrentTeam } from '../../stores/team';
import { useToast } from '../../hooks/use-toast'; import { useToast } from '../../hooks/use-toast';
import { MemberRoleBadge } from './RoleBadge';
import { TeamMemberItem } from './TeamMemberItem'; import { TeamMemberItem } from './TeamMemberItem';
export interface TeamMemberDocument { export interface TeamMemberDocument {
@ -43,7 +41,7 @@ export interface UserResourceProgressDocument {
export interface TeamMemberItem extends TeamMemberDocument { export interface TeamMemberItem extends TeamMemberDocument {
name: string; name: string;
avatar: string; avatar: string;
progress: UserResourceProgressDocument[]; hasProgress: boolean;
} }
export function TeamMembersPage() { export function TeamMembersPage() {
@ -187,7 +185,7 @@ export function TeamMembersPage() {
/> />
)} )}
<div> <div>
<div className="rounded-b-sm rounded-t-md border"> <div className="rounded-md border">
<div className="flex items-center justify-between gap-2 border-b p-3"> <div className="flex items-center justify-between gap-2 border-b p-3">
<p className="hidden text-sm sm:block"> <p className="hidden text-sm sm:block">
{teamMembers.length} people in the team. {teamMembers.length} people in the team.
@ -211,7 +209,7 @@ export function TeamMembersPage() {
}); });
}} }}
canManageCurrentTeam={canManageCurrentTeam} canManageCurrentTeam={canManageCurrentTeam}
handleDeleteMember={() => { onDeleteMember={() => {
deleteMember(teamId, member._id!).finally(() => { deleteMember(teamId, member._id!).finally(() => {
pageProgressMessage.set(''); pageProgressMessage.set('');
}); });
@ -219,7 +217,7 @@ export function TeamMembersPage() {
onUpdateMember={() => { onUpdateMember={() => {
setMemberToUpdate(member); setMemberToUpdate(member);
}} }}
handleSendReminder={() => { onSendProgressReminder={() => {
handleSendReminder(teamId, member._id!).finally(() => { handleSendReminder(teamId, member._id!).finally(() => {
pageProgressMessage.set(''); pageProgressMessage.set('');
}); });
@ -231,8 +229,8 @@ export function TeamMembersPage() {
{invitedMembers.length > 0 && ( {invitedMembers.length > 0 && (
<div className="mt-6"> <div className="mt-6">
<h3 className="text-xl font-medium">Invited Members</h3> <h3 className="text-xs uppercase text-gray-400">Invited Members</h3>
<div className="mt-2 rounded-b-sm rounded-t-md border"> <div className="mt-2 rounded-md border">
{invitedMembers.map((member, index) => { {invitedMembers.map((member, index) => {
return ( return (
<TeamMemberItem <TeamMemberItem
@ -247,7 +245,7 @@ export function TeamMembersPage() {
}); });
}} }}
canManageCurrentTeam={canManageCurrentTeam} canManageCurrentTeam={canManageCurrentTeam}
handleDeleteMember={() => { onDeleteMember={() => {
deleteMember(teamId, member._id!).finally(() => { deleteMember(teamId, member._id!).finally(() => {
pageProgressMessage.set(''); pageProgressMessage.set('');
}); });
@ -255,7 +253,7 @@ export function TeamMembersPage() {
onUpdateMember={() => { onUpdateMember={() => {
setMemberToUpdate(member); setMemberToUpdate(member);
}} }}
handleSendReminder={() => { onSendProgressReminder={() => {
handleSendReminder(teamId, member._id!).finally(() => { handleSendReminder(teamId, member._id!).finally(() => {
pageProgressMessage.set(''); pageProgressMessage.set('');
}); });
@ -269,7 +267,7 @@ export function TeamMembersPage() {
{rejectedMembers.length > 0 && ( {rejectedMembers.length > 0 && (
<div className="mt-6"> <div className="mt-6">
<h3 className="text-xl font-medium">Rejected Members</h3> <h3 className="text-xs uppercase text-gray-400">Rejected Invites</h3>
<div className="mt-2 rounded-b-sm rounded-t-md border"> <div className="mt-2 rounded-b-sm rounded-t-md border">
{rejectedMembers.map((member, index) => { {rejectedMembers.map((member, index) => {
return ( return (
@ -285,7 +283,7 @@ export function TeamMembersPage() {
}); });
}} }}
canManageCurrentTeam={canManageCurrentTeam} canManageCurrentTeam={canManageCurrentTeam}
handleDeleteMember={() => { onDeleteMember={() => {
deleteMember(teamId, member._id!).finally(() => { deleteMember(teamId, member._id!).finally(() => {
pageProgressMessage.set(''); pageProgressMessage.set('');
}); });
@ -293,7 +291,7 @@ export function TeamMembersPage() {
onUpdateMember={() => { onUpdateMember={() => {
setMemberToUpdate(member); setMemberToUpdate(member);
}} }}
handleSendReminder={() => { onSendProgressReminder={() => {
handleSendReminder(teamId, member._id!).finally(() => { handleSendReminder(teamId, member._id!).finally(() => {
pageProgressMessage.set(''); pageProgressMessage.set('');
}); });

@ -141,10 +141,16 @@ export function UpdateTeamForm() {
/> />
</div> </div>
<div className="mt-4 flex w-full flex-col"> <div className="mt-4 flex w-full flex-col">
<label for="website" className="text-sm leading-none text-slate-500"> <label
for="website"
className={`text-sm leading-none text-slate-500 ${
teamType === 'company' ? 'after:content-["*"]' : ''
}`}
>
Website Website
</label> </label>
<input <input
required={teamType === 'company'}
type="text" type="text"
name="website" name="website"
id="website" id="website"

@ -51,7 +51,7 @@ export function Toaster(props: Props) {
onClick={() => { onClick={() => {
$toastMessage.set(undefined); $toastMessage.set(undefined);
}} }}
className={`fixed bottom-5 left-1/2 z-50 min-w-[300px] max-w-[300px] animate-fade-slide-up sm:min-w-[auto]`} className={`fixed bottom-5 left-1/2 z-50 min-w-[375px] max-w-[375px] animate-fade-slide-up sm:min-w-[auto]`}
> >
<div <div
className={`flex -translate-x-1/2 transform cursor-pointer items-center gap-2 rounded-md border border-gray-200 bg-white py-3 pl-4 pr-5 text-black shadow-md hover:bg-gray-50`} className={`flex -translate-x-1/2 transform cursor-pointer items-center gap-2 rounded-md border border-gray-200 bg-white py-3 pl-4 pr-5 text-black shadow-md hover:bg-gray-50`}

Loading…
Cancel
Save