import { Box, Button, config, Icon, IconButton, Icons, Spinner, Text, toRem } from 'folds'; import React, { useCallback, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { VerificationRequest } from 'matrix-js-sdk/lib/crypto-api'; import { AsyncState, AsyncStatus, useAsync } from '../../hooks/useAsyncCallback'; import { VerificationStatus } from '../../hooks/useDeviceVerificationStatus'; import { DeviceVerificationStatus } from '../DeviceVerificationStatus'; import { DeviceVerification } from '../DeviceVerification'; import { useOtherUserDevices, UserDevice } from '../../hooks/useOtherUserDevices'; import { ContainerColor } from '../../styles/ContainerColor.css'; import { UserHero, UserHeroName } from './UserHero'; import { getMxIdServer, mxcUrlToHttp } from '../../utils/matrix'; import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { usePowerLevels } from '../../hooks/usePowerLevels'; import { useRoom } from '../../hooks/useRoom'; import { useUserPresence } from '../../hooks/useUserPresence'; import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips'; import { useCrossSigningActive } from '../../hooks/useCrossSigning'; import { MemberVerificationBadge } from '../MemberVerificationBadge'; import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile'; import { PowerChip } from './PowerChip'; import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration'; import { useIgnoredUsers } from '../../hooks/useIgnoredUsers'; import { useMembership } from '../../hooks/useMembership'; import { Membership } from '../../../types/matrix/room'; import { useRoomCreators } from '../../hooks/useRoomCreators'; import { useRoomPermissions } from '../../hooks/useRoomPermissions'; import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare'; import { CreatorChip } from './CreatorChip'; import { getDirectCreatePath, withSearchParam } from '../../pages/pathUtils'; import { DirectCreateSearchParams } from '../../pages/paths'; import { useExtendedProfile } from '../../hooks/useExtendedProfile'; type VerifyDeviceButtonProps = { userId: string; deviceId: string; }; function VerifyDeviceButton({ userId, deviceId }: VerifyDeviceButtonProps) { const mx = useMatrixClient(); const [requestState, setRequestState] = useState>({ status: AsyncStatus.Idle, }); const requestVerification = useAsync( useCallback(() => { const crypto = mx.getCrypto(); if (!crypto) return Promise.reject(new Error('Crypto unavailable')); return crypto.requestDeviceVerification(userId, deviceId); }, [mx, userId, deviceId]), setRequestState, ); const handleExit = useCallback(() => setRequestState({ status: AsyncStatus.Idle }), []); const requesting = requestState.status === AsyncStatus.Loading; return ( <> {requestState.status === AsyncStatus.Success && ( )} ); } type UserDeviceRowProps = { userId: string; device: UserDevice; }; function UserDeviceRow({ userId, device }: UserDeviceRowProps) { const mx = useMatrixClient(); const crypto = mx.getCrypto() ?? undefined; return ( {(status) => { const color = status === VerificationStatus.Verified ? 'var(--tc-positive-normal, #5effc4)' : status === VerificationStatus.Unverified ? 'var(--tc-warning-normal, #ffcc55)' : 'var(--tc-surface-low-contrast)'; return ( {device.displayName ?? device.deviceId} {device.displayName && ( {device.deviceId} )} {status === VerificationStatus.Unverified && ( )} ); }} ); } type UserDeviceSessionsProps = { userId: string; }; function UserDeviceSessions({ userId }: UserDeviceSessionsProps) { const devicesState = useOtherUserDevices(userId); const [expanded, setExpanded] = useState(false); if (devicesState.status === 'loading') { return ( Loading sessions… ); } if (devicesState.status === 'error') { return ( Could not load sessions. ); } const devices = devicesState.devices; if (devices.length === 0) return null; return ( Sessions {devices.length} setExpanded((v) => !v)} > {expanded && ( {devices.map((device) => ( ))} )} ); } type UserRoomProfileProps = { userId: string; }; export function UserRoomProfile({ userId }: UserRoomProfileProps) { const mx = useMatrixClient(); const crossSigningActive = useCrossSigningActive(); const useAuthentication = useMediaAuthentication(); const navigate = useNavigate(); const closeUserRoomProfile = useCloseUserRoomProfile(); const ignoredUsers = useIgnoredUsers(); const ignored = ignoredUsers.includes(userId); const room = useRoom(); const showEncryption = crossSigningActive; const powerLevels = usePowerLevels(room); const creators = useRoomCreators(room); const permissions = useRoomPermissions(creators, powerLevels); const { hasMorePower } = useMemberPowerCompare(creators, powerLevels); const myUserId = mx.getSafeUserId(); const creator = creators.has(userId); const canKickUser = permissions.action('kick', myUserId) && hasMorePower(myUserId, userId); const canBanUser = permissions.action('ban', myUserId) && hasMorePower(myUserId, userId); const canUnban = permissions.action('ban', myUserId); const canInvite = permissions.action('invite', myUserId); const member = room.getMember(userId); const membership = useMembership(room, userId); const server = getMxIdServer(userId); const displayName = getMemberDisplayName(room, userId); const avatarMxc = getMemberAvatarMxc(room, userId); const avatarUrl = (avatarMxc && mxcUrlToHttp(mx, avatarMxc, useAuthentication)) ?? undefined; const presence = useUserPresence(userId); const extProfile = useExtendedProfile(userId); const handleMessage = () => { closeUserRoomProfile(); const directSearchParam: DirectCreateSearchParams = { userId, }; navigate(withSearchParam(getDirectCreatePath(), directSearchParam)); }; return ( {showEncryption && } {userId !== myUserId && ( )} {server && } {creator ? : } {userId !== myUserId && } {userId !== myUserId && } {ignored && } {member && membership === Membership.Ban && ( )} {member && membership === Membership.Leave && member.events.member && member.events.member.getSender() !== userId && ( )} {member && membership === Membership.Invite && ( )} {showEncryption && userId !== myUserId && } ); }