/* * 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 * as ModalActionCreators from '@app/actions/ModalActionCreators'; import {modal} from '@app/actions/ModalActionCreators'; import * as RelationshipActionCreators from '@app/actions/RelationshipActionCreators'; import * as TextCopyActionCreators from '@app/actions/TextCopyActionCreators'; import {BanMemberModal} from '@app/components/modals/BanMemberModal'; import {ChangeNicknameModal} from '@app/components/modals/ChangeNicknameModal'; import {ConfirmModal} from '@app/components/modals/ConfirmModal'; import {KickMemberModal} from '@app/components/modals/KickMemberModal'; import {TransferOwnershipModal} from '@app/components/modals/TransferOwnershipModal'; import styles from '@app/components/modals/UserProfileActionsSheet.module.css'; import {MenuBottomSheet, type MenuGroupType} from '@app/components/uikit/menu_bottom_sheet/MenuBottomSheet'; import {useRoleHierarchy} from '@app/hooks/useRoleHierarchy'; import type {GuildMemberRecord} from '@app/records/GuildMemberRecord'; import type {UserRecord} from '@app/records/UserRecord'; import AuthenticationStore from '@app/stores/AuthenticationStore'; import GuildMemberStore from '@app/stores/GuildMemberStore'; import GuildStore from '@app/stores/GuildStore'; import PermissionStore from '@app/stores/PermissionStore'; import RelationshipStore from '@app/stores/RelationshipStore'; import {Permissions} from '@fluxer/constants/src/ChannelConstants'; import {RelationshipTypes} from '@fluxer/constants/src/UserConstants'; import {useLingui} from '@lingui/react/macro'; import { CopyIcon, CrownIcon, FlagIcon, GavelIcon, GlobeIcon, IdentificationCardIcon, PencilIcon, ProhibitIcon, SignOutIcon, UserMinusIcon, } from '@phosphor-icons/react'; import {observer} from 'mobx-react-lite'; interface UserProfileActionsSheetProps { isOpen: boolean; onClose: () => void; user: UserRecord; isCurrentUser?: boolean; hasGuildProfile?: boolean; showGlobalProfile?: boolean; onToggleProfileView?: () => void; guildId?: string; guildMember?: GuildMemberRecord | null; } export const UserProfileActionsSheet: React.FC = observer( ({ isOpen, onClose, user, isCurrentUser = false, hasGuildProfile = false, showGlobalProfile = false, onToggleProfileView, guildId, guildMember, }) => { const {t, i18n} = useLingui(); const relationshipType = RelationshipStore.getRelationship(user.id)?.type; const guild = guildId ? GuildStore.getGuild(guildId) : null; const member = guildMember ?? (guildId ? GuildMemberStore.getMember(guildId, user.id) : null); const currentUserId = AuthenticationStore.currentUserId; const canKickMembers = guildId ? PermissionStore.can(Permissions.KICK_MEMBERS, {guildId}) : false; const canBanMembers = guildId ? PermissionStore.can(Permissions.BAN_MEMBERS, {guildId}) : false; const hasChangeNicknamePermission = guildId ? PermissionStore.can(Permissions.CHANGE_NICKNAME, {guildId}) : false; const hasManageNicknamesPermission = guildId ? PermissionStore.can(Permissions.MANAGE_NICKNAMES, {guildId}) : false; const isOwner = guild?.ownerId === currentUserId; const {canManageTarget} = useRoleHierarchy(guild); const canKick = !isCurrentUser && canKickMembers && member && canManageTarget(user.id); const canBan = !isCurrentUser && canBanMembers && member && canManageTarget(user.id); const canTransfer = !isCurrentUser && isOwner && member; const canManageNicknames = member && ((isCurrentUser && hasChangeNicknamePermission) || (hasManageNicknamesPermission && canManageTarget(user.id))); const handleCopyFluxerTag = () => { TextCopyActionCreators.copy(i18n, `${user.username}#${user.discriminator}`, true); onClose(); }; const handleCopyUserId = () => { TextCopyActionCreators.copy(i18n, user.id, true); onClose(); }; const handleRemoveFriend = () => { onClose(); ModalActionCreators.push( modal(() => ( { RelationshipActionCreators.removeRelationship(user.id); }} /> )), ); }; const handleBlockUser = () => { onClose(); ModalActionCreators.push( modal(() => ( { RelationshipActionCreators.blockUser(user.id); }} /> )), ); }; const handleUnblockUser = () => { onClose(); ModalActionCreators.push( modal(() => ( { RelationshipActionCreators.removeRelationship(user.id); }} /> )), ); }; const handleChangeNickname = () => { if (!guildId || !member) return; onClose(); ModalActionCreators.push(modal(() => )); }; const handleKickMember = () => { if (!guildId) return; onClose(); ModalActionCreators.push(modal(() => )); }; const handleBanMember = () => { if (!guildId) return; onClose(); ModalActionCreators.push(modal(() => )); }; const handleTransferOwnership = () => { if (!guildId || !member) return; onClose(); ModalActionCreators.push( modal(() => ), ); }; const menuGroups: Array = []; if (hasGuildProfile && onToggleProfileView) { menuGroups.push({ items: [ { icon: , label: showGlobalProfile ? t`View Community Profile` : t`View Global Profile`, onClick: () => { onToggleProfileView(); onClose(); }, }, ], }); } menuGroups.push({ items: [ { icon: , label: t`Copy FluxerTag`, onClick: handleCopyFluxerTag, }, { icon: , label: t`Copy User ID`, onClick: handleCopyUserId, }, ], }); if (guildId && member) { const guildItems = []; if (canManageNicknames) { guildItems.push({ icon: , label: isCurrentUser ? t`Change Nickname` : t`Change Nickname`, onClick: handleChangeNickname, }); } if (guildItems.length > 0) { menuGroups.push({items: guildItems}); } if (canTransfer) { menuGroups.push({ items: [ { icon: , label: t`Transfer Ownership`, onClick: handleTransferOwnership, danger: true, }, ], }); } if (canKick || canBan) { const moderationItems = []; if (canKick) { moderationItems.push({ icon: , label: t`Kick`, onClick: handleKickMember, danger: true, }); } if (canBan) { moderationItems.push({ icon: , label: t`Ban`, onClick: handleBanMember, danger: true, }); } menuGroups.push({items: moderationItems}); } } if (!isCurrentUser) { if (relationshipType === RelationshipTypes.FRIEND) { menuGroups.push({ items: [ { icon: , label: t`Remove Friend`, onClick: handleRemoveFriend, danger: true, }, ], }); } const reportBlockItems = [ { icon: , label: t`Report User`, onClick: () => {}, danger: true, }, ]; if (relationshipType !== RelationshipTypes.BLOCKED) { reportBlockItems.push({ icon: , label: t`Block`, onClick: handleBlockUser, danger: true, }); } else { reportBlockItems.push({ icon: , label: t`Unblock`, onClick: handleUnblockUser, danger: false, }); } menuGroups.push({items: reportBlockItems}); } return ; }, );