/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
import {useLingui} from '@lingui/react/macro';
import {ClockIcon, CrownIcon, PencilIcon, UserListIcon, UsersIcon} from '@phosphor-icons/react';
import {observer} from 'mobx-react-lite';
import React from 'react';
import * as GuildMemberActionCreators from '~/actions/GuildMemberActionCreators';
import * as ModalActionCreators from '~/actions/ModalActionCreators';
import {modal} from '~/actions/ModalActionCreators';
import {Permissions} from '~/Constants';
import {BanMemberModal} from '~/components/modals/BanMemberModal';
import {ChangeNicknameModal} from '~/components/modals/ChangeNicknameModal';
import {KickMemberModal} from '~/components/modals/KickMemberModal';
import {RemoveTimeoutModal} from '~/components/modals/RemoveTimeoutModal';
import {TimeoutMemberModal} from '~/components/modals/TimeoutMemberModal';
import {TransferOwnershipModal} from '~/components/modals/TransferOwnershipModal';
import {useRoleHierarchy} from '~/hooks/useRoleHierarchy';
import type {GuildMemberRecord} from '~/records/GuildMemberRecord';
import type {UserRecord} from '~/records/UserRecord';
import AuthenticationStore from '~/stores/AuthenticationStore';
import GuildMemberStore from '~/stores/GuildMemberStore';
import GuildStore from '~/stores/GuildStore';
import PermissionStore from '~/stores/PermissionStore';
import * as ColorUtils from '~/utils/ColorUtils';
import * as PermissionUtils from '~/utils/PermissionUtils';
import {MenuGroup} from '../MenuGroup';
import {MenuItem} from '../MenuItem';
import {MenuItemCheckbox} from '../MenuItemCheckbox';
import {MenuItemSubmenu} from '../MenuItemSubmenu';
import itemStyles from './MenuItems.module.css';
interface TransferOwnershipMenuItemProps {
guildId: string;
user: UserRecord;
member: GuildMemberRecord;
onClose: () => void;
}
export const TransferOwnershipMenuItem: React.FC = observer(
function TransferOwnershipMenuItem({guildId, user, member, onClose}) {
const {t} = useLingui();
const handleTransferOwnership = React.useCallback(() => {
onClose();
ModalActionCreators.push(
modal(() => ),
);
}, [guildId, user, member, onClose]);
return (
} onClick={handleTransferOwnership}>
{t`Transfer Ownership`}
);
},
);
interface KickMemberMenuItemProps {
guildId: string;
user: UserRecord;
onClose: () => void;
}
export const KickMemberMenuItem: React.FC = observer(function KickMemberMenuItem({
guildId,
user,
onClose,
}) {
const {t} = useLingui();
const handleKickMember = React.useCallback(() => {
onClose();
ModalActionCreators.push(modal(() => ));
}, [guildId, user, onClose]);
return (
} onClick={handleKickMember} danger>
{t`Kick Member`}
);
});
interface BanMemberMenuItemProps {
guildId: string;
user: UserRecord;
onClose: () => void;
}
export const BanMemberMenuItem: React.FC = observer(function BanMemberMenuItem({
guildId,
user,
onClose,
}) {
const {t} = useLingui();
const handleBanMember = React.useCallback(() => {
onClose();
ModalActionCreators.push(modal(() => ));
}, [guildId, user, onClose]);
return (
} onClick={handleBanMember} danger>
{t`Ban Member`}
);
});
interface ManageRolesMenuItemProps {
guildId: string;
member: GuildMemberRecord;
}
export const ManageRolesMenuItem: React.FC = observer(function ManageRolesMenuItem({
guildId,
member,
}) {
const {t} = useLingui();
const guild = GuildStore.getGuild(guildId);
const currentMember = GuildMemberStore.getMember(guildId, member.user.id);
const {canManageRole} = useRoleHierarchy(guild);
const canManageRoles = PermissionStore.can(Permissions.MANAGE_ROLES, {guildId});
const allRoles = React.useMemo(() => {
if (!guild) return [];
return Object.values(guild.roles)
.filter((role) => !role.isEveryone)
.sort((a, b) => b.position - a.position)
.map((role) => ({
role,
canManage: canManageRole({id: role.id, position: role.position, permissions: role.permissions}),
}));
}, [guild, canManageRole]);
const handleToggleRole = React.useCallback(
async (roleId: string, hasRole: boolean, canToggle: boolean) => {
if (!canToggle) return;
if (hasRole) {
await GuildMemberActionCreators.removeRole(guildId, member.user.id, roleId);
} else {
await GuildMemberActionCreators.addRole(guildId, member.user.id, roleId);
}
},
[guildId, member.user.id],
);
if (allRoles.length === 0) return null;
return (
}
selectionMode="multiple"
render={() => (
{allRoles.map(({role, canManage}) => {
const hasRole = currentMember?.roles.has(role.id) ?? false;
const canToggle = canManageRoles && canManage;
return (
handleToggleRole(role.id, hasRole, canToggle)}
closeOnChange={false}
>
);
})}
)}
/>
);
});
interface ChangeNicknameMenuItemProps {
guildId: string;
user: UserRecord;
member: GuildMemberRecord;
onClose: () => void;
}
export const ChangeNicknameMenuItem: React.FC = observer(function ChangeNicknameMenuItem({
guildId,
user,
member,
onClose,
}) {
const {t} = useLingui();
const currentUserId = AuthenticationStore.currentUserId;
const isCurrentUser = user.id === currentUserId;
const guild = GuildStore.getGuild(guildId);
const {canManageTarget} = useRoleHierarchy(guild);
const hasChangeNicknamePermission = PermissionStore.can(Permissions.CHANGE_NICKNAME, {guildId});
const hasManageNicknamesPermission = PermissionStore.can(Permissions.MANAGE_NICKNAMES, {guildId});
const canManageNicknames =
(isCurrentUser && hasChangeNicknamePermission) || (hasManageNicknamesPermission && canManageTarget(user.id));
const handleChangeNickname = React.useCallback(() => {
onClose();
ModalActionCreators.push(modal(() => ));
}, [guildId, user, member, onClose]);
if (!canManageNicknames) return null;
return (
} onClick={handleChangeNickname}>
{isCurrentUser ? t`Change Nickname` : t`Change Nickname`}
);
});
interface TimeoutMemberMenuItemProps {
guildId: string;
user: UserRecord;
member: GuildMemberRecord;
onClose: () => void;
}
export const TimeoutMemberMenuItem: React.FC = observer(function TimeoutMemberMenuItem({
guildId,
user,
member,
onClose,
}) {
const {t} = useLingui();
const guild = GuildStore.getGuild(guildId);
const guildSnapshot = guild?.toJSON();
const targetHasModerateMembersPermission =
guildSnapshot !== undefined && PermissionUtils.can(Permissions.MODERATE_MEMBERS, user.id, guildSnapshot);
const handleTimeoutMember = React.useCallback(() => {
onClose();
ModalActionCreators.push(modal(() => ));
}, [guildId, user, onClose]);
const handleRemoveTimeout = React.useCallback(() => {
onClose();
ModalActionCreators.push(modal(() => ));
}, [guildId, user, onClose]);
if (targetHasModerateMembersPermission) {
return null;
}
const isTimedOut = member.isTimedOut();
const handleClick = isTimedOut ? handleRemoveTimeout : handleTimeoutMember;
return (
} onClick={handleClick} danger={!isTimedOut}>
{isTimedOut ? t`Remove Timeout` : t`Timeout`}
);
});