Adjusting role actions
Deploy / deploy-dev (push) Successful in 2m29s
Deploy / deploy-prod (push) Has been skipped

This commit is contained in:
blaisadmin
2026-06-05 21:09:31 -04:00
parent c3bec15c63
commit bb589e3489
4 changed files with 419 additions and 24 deletions
+106 -19
View File
@@ -1676,6 +1676,7 @@ function App() {
const [editingMedicationId, setEditingMedicationId] = useState('');
const [deletingMedicationId, setDeletingMedicationId] = useState('');
const [savingMedicationAdministrationId, setSavingMedicationAdministrationId] = useState('');
const [updatingWorkspaceMemberId, setUpdatingWorkspaceMemberId] = useState('');
const [removingWorkspaceMemberId, setRemovingWorkspaceMemberId] = useState('');
const [expandedSettingsSection, setExpandedSettingsSection] = useState<SettingsSection | null>(null);
@@ -1683,6 +1684,11 @@ function App() {
() => birds.find((bird) => bird.id === selectedBirdId) ?? null,
[birds, selectedBirdId],
);
const isBillingOwner = Boolean(
authSession?.user.email &&
workspace?.billingEmail &&
authSession.user.email.trim().toLowerCase() === workspace.billingEmail.trim().toLowerCase(),
);
const selectedBirdAdoptionTransferCode = selectedBird ? adoptionTransferCodes[selectedBird.id] ?? '' : '';
const editingBird = useMemo(
() => birds.find((bird) => bird.id === editingBirdId) ?? null,
@@ -4986,7 +4992,15 @@ function App() {
email: data.member.inviteEmail,
};
setWorkspaceMembers((current) => [...current, nextMember]);
setWorkspaceMembers((current) => {
const existingIndex = current.findIndex((member) => member.id === nextMember.id);
if (existingIndex === -1) {
return [...current, nextMember];
}
return current.map((member) => (member.id === nextMember.id ? nextMember : member));
});
setWorkspaceMemberForm(emptyWorkspaceMemberForm);
} catch (memberError) {
setError(memberError instanceof Error ? memberError.message : 'Unable to add rescue team member.');
@@ -4995,6 +5009,40 @@ function App() {
}
};
const handleUpdateWorkspaceMemberRole = async (memberId: string, role: WorkspaceRole) => {
setError('');
setUpdatingWorkspaceMemberId(memberId);
try {
const response = await apiFetch(`/workspace/members/${memberId}`, authToken, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ role }),
});
if (!response.ok) {
throw new Error(await readErrorMessage(response, 'Unable to update collaborator role.'));
}
const data = (await readJsonSafely<{ member?: WorkspaceMember }>(response)) ?? {};
if (!data.member) {
throw new Error('Unable to update collaborator role.');
}
const updatedMember = {
...data.member,
email: data.member.inviteEmail,
};
setWorkspaceMembers((current) => current.map((member) => (member.id === memberId ? updatedMember : member)));
} catch (memberError) {
setError(memberError instanceof Error ? memberError.message : 'Unable to update collaborator role.');
} finally {
setUpdatingWorkspaceMemberId('');
}
};
const handleRemoveWorkspaceMember = async (memberId: string) => {
setError('');
setRemovingWorkspaceMemberId(memberId);
@@ -7877,7 +7925,6 @@ function App() {
})
}
>
<option value="owner">Owner</option>
<option value="assistant">Assistant</option>
<option value="caregiver">Caregiver</option>
<option value="viewer">Viewer</option>
@@ -7890,23 +7937,63 @@ function App() {
<div className="recent-list">
{workspaceMembers.length ? (
workspaceMembers.map((member) => (
<article key={member.id} className="vet-visit-card">
<strong>{member.name}</strong>
<span>
{formatWorkspaceRole(member.role)} {member.email || member.inviteEmail}
</span>
<small>{member.acceptedAt ? 'Active access' : 'Invitation pending'}</small>
<button
className="secondary-button"
onClick={() => handleRemoveWorkspaceMember(member.id)}
type="button"
disabled={removingWorkspaceMemberId === member.id || member.role === 'owner'}
>
{member.role === 'owner' ? 'Owner' : removingWorkspaceMemberId === member.id ? 'Removing...' : 'Remove'}
</button>
</article>
))
workspaceMembers.map((member) => {
const memberEmail = member.email || member.inviteEmail || '';
const memberIsBillingOwner = Boolean(
workspace?.billingEmail &&
memberEmail.trim().toLowerCase() === workspace.billingEmail.trim().toLowerCase(),
);
const canRemoveOwner = member.role === 'owner' && isBillingOwner && member.id !== activeMembership?.id;
const canChangeOwnerRole =
member.role === 'owner' &&
activeMembership?.role === 'owner' &&
member.id !== activeMembership.id &&
(isBillingOwner || !memberIsBillingOwner);
const canRemoveMember = member.role !== 'owner' || canRemoveOwner;
return (
<article key={member.id} className="vet-visit-card">
<strong>{member.name}</strong>
<span>
{formatWorkspaceRole(member.role)} {member.email || member.inviteEmail}
</span>
<small>{member.acceptedAt ? 'Active access' : 'Invitation pending'}</small>
<label>
Role
<select
value={member.role}
onChange={(event) => handleUpdateWorkspaceMemberRole(member.id, event.target.value as WorkspaceRole)}
disabled={
(member.role === 'owner' && !canChangeOwnerRole) ||
updatingWorkspaceMemberId === member.id ||
removingWorkspaceMemberId === member.id
}
>
{member.role === 'owner' ? <option value="owner">Owner</option> : null}
<option value="assistant">Assistant</option>
<option value="caregiver">Caregiver</option>
<option value="viewer">Viewer</option>
</select>
</label>
<button
className="danger-button"
onClick={() => handleRemoveWorkspaceMember(member.id)}
type="button"
disabled={
removingWorkspaceMemberId === member.id ||
updatingWorkspaceMemberId === member.id ||
!canRemoveMember
}
>
{removingWorkspaceMemberId === member.id
? 'Removing...'
: canRemoveMember
? 'Remove access'
: 'Owner'}
</button>
</article>
);
})
) : (
<article className="vet-visit-card empty-card">
<strong>No collaborators yet</strong>