Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions scripts/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,36 @@ async function buildSeedData() {
supportNotify: true,
});

// Standard Admin (Has userWrite, but NOT isSuper)
// Use this account to see if it can deactivate partners but NOT Super Admins
const staffAdmin = await createUser({
email: "staffadmin@test.com",
name: "Staff Admin",
passwordHash,
type: UserType.STAFF,
enabled: true,
pending: false,
isSuper: false,
userRead: true,
userWrite: true,
itemNotify: true,
offerWrite: true,
requestRead: true,
requestWrite: true,
allocationRead: true,
allocationWrite: true,
archivedRead: true,
distributionRead: true,
distributionWrite: true,
shipmentRead: true,
shipmentWrite: true,
signoffWrite: true,
wishlistRead: true,
supportRead: true,
supportWrite: true,
supportNotify: true,
});

// Create distribution lead staff user
const distributionLead = await createUser({
email: "distribution@test.com",
Expand Down Expand Up @@ -233,6 +263,7 @@ async function buildSeedData() {

console.log("✓ Created users");
console.log(` - Super Admin: ${superAdmin.email}`);
console.log(` - Staff Admin: ${staffAdmin.email}`);
console.log(` - Distribution Lead: ${distributionLead.email}`);
console.log(` - Partner 1: ${partner1.email}`);
console.log(` - Partner 2: ${partner2.email}`);
Expand Down
41 changes: 40 additions & 1 deletion src/app/accountManagement/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ interface AccountUserResponse {
tag: string | null;
enabled: boolean;
pending: boolean;
isSuper?: boolean;
userWrite?: boolean;
invite?: {
token: string;
expiration: string;
Expand Down Expand Up @@ -141,6 +143,14 @@ export default function AccountManagementPage() {
const handleDeactivateAccount = (user: AccountRow) => {
if (!canManageAccounts) return;
if (user.pending) return;
if (currentUser && user.id === Number(currentUser.id)) {
toast.error("You cannot deactivate your own account.");
return;
}
if (user.id === Number(currentUser?.id) || isProtectedUser(user)) {
toast.error("You do not have permission to modify this account's status.");
return;
}
setSelectedUser(user);
setDeactivateModalOpen(true);
};
Comment thread
ND68 marked this conversation as resolved.
Expand Down Expand Up @@ -216,6 +226,18 @@ export default function AccountManagementPage() {
const confirmDeactivateAccount = async () => {
if (!canManageAccounts) return;
if (!selectedUser || selectedUser.pending) return;
if (currentUser && selectedUser.id === Number(currentUser.id)) {
toast.error("You cannot deactivate your own account.");
setDeactivateModalOpen(false);
setSelectedUser(null);
return;
}
if (Boolean(selectedUser.isSuper && selectedUser.userWrite)) {
toast.error("You cannot deactivate accounts with super admin permissions.");
setDeactivateModalOpen(false);
setSelectedUser(null);
return;
}

try {
const isCurrentlyEnabled = selectedUser.enabled;
Expand Down Expand Up @@ -336,6 +358,19 @@ export default function AccountManagementPage() {
},
];

const isProtectedUser = (item: AccountRow) => {
if (!currentUser) return true;

// Super Admins are protected from everyone
if (item.isSuper) return true;

// If the current user is NOT a Super Admin, then anyone with userWrite
// is protected (prevents horizontal deactivations)
if (!currentUser.isSuper && item.userWrite) return true;

return false;
};

if (canManageAccounts) {
baseColumns.push({
id: "manage",
Expand All @@ -353,6 +388,7 @@ export default function AccountManagementPage() {
onDeactivateAccount={() => handleDeactivateAccount(item)}
onSendReminder={() => handleSendReminder(item)}
canManage={canManageAccounts}
hideDeactivateOption={item.id === Number(currentUser?.id) || isProtectedUser(item)}
/>
</div>
),
Expand Down Expand Up @@ -466,7 +502,10 @@ This will restore the user's access to the system.`
isStaffAccount={selectedUser ? isStaff(selectedUser.type) : true}
existingTags={tags ?? []}
onManagePermissions={
selectedUser && !selectedUser.pending && isStaff(selectedUser.type)
selectedUser &&
!selectedUser.pending &&
isStaff(selectedUser.type) &&
!isProtectedUser(selectedUser)
? () => handleManagePermissions(selectedUser)
: undefined
}
Expand Down
14 changes: 10 additions & 4 deletions src/components/AccountManagement/AccountDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ interface AccountDropdownProps {
onDeactivateAccount?: () => void;
onSendReminder?: () => void;
canManage?: boolean;
/** When true, hide Deactivate/Activate option (e.g. for self or protected users) */
hideDeactivateOption?: boolean;
}

export default function AccountDropdown({
Expand All @@ -34,6 +36,7 @@ export default function AccountDropdown({
onDeactivateAccount,
onSendReminder,
canManage = true,
hideDeactivateOption = false,
}: AccountDropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const buttonRef = useRef<HTMLButtonElement>(null);
Expand Down Expand Up @@ -64,7 +67,7 @@ export default function AccountDropdown({
];
} else {
const isEnabled = user?.enabled !== false;
return [
const items: DropdownItem[] = [
{
icon: <PencilSimple size={18} />,
label: "Edit account",
Expand All @@ -73,15 +76,18 @@ export default function AccountDropdown({
setIsOpen(false);
},
},
{
];
if (!hideDeactivateOption) {
items.push({
icon: isEnabled ? <EyeSlash size={18} /> : <Eye size={18} />,
label: isEnabled ? "Deactivate account" : "Activate account",
onClick: () => {
onDeactivateAccount?.();
setIsOpen(false);
},
},
];
});
}
return items;
}
};

Expand Down
8 changes: 6 additions & 2 deletions src/screens/dashboard/AdminDashboardScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ export default function AdminDashboardScreen() {
);
}

const hasUserRead = user.isSuper || user.userRead;

return (
<div className="w-full">
<h1 className="text-3xl font-bold mb-8">Admin Dashboard</h1>
<h1 className="text-3xl font-bold mb-8">
{hasUserRead ? "Admin Dashboard" : "Dashboard"}
</h1>

<AnalyticsSection />
{hasUserRead && <AnalyticsSection/>}

<MapSectionWithData />
</div>
Expand Down
12 changes: 10 additions & 2 deletions src/services/userService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export default class UserService {
partnerDetails: true,
streamUserId: true,
streamUserToken: true,
isSuper: true,
userWrite: true,
invite: {
select: {
token: true,
Expand Down Expand Up @@ -392,14 +394,20 @@ export default class UserService {

if (
data.enabled === false &&
(existingUser.userWrite || existingUser.isSuper)
(existingUser.userWrite && existingUser.isSuper)
) {
throw new AuthorizationError(
"Cannot deactivate users with userWrite or isSuper permissions"
"Cannot deactivate users with userWrite and isSuper permissions"
);
}

if (data.permissions) {
if (existingUser.isSuper && existingUser.userWrite) {
throw new AuthorizationError(
"Cannot modify permissions for users with isSuper and userWrite"
);
}

const finalPermissions = {
...existingUser,
...data.permissions,
Expand Down
Loading