From 021554958d3c79cc1272cea9ce895cce850f0c0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:01:37 +0000 Subject: [PATCH 01/71] Initial plan From 736d36dc440631dee3617af91c01a04144afaca5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:15:53 +0000 Subject: [PATCH 02/71] Initial exploration - understanding codebase structure Co-authored-by: kishor-gupta <61230727+kishor-gupta@users.noreply.github.com> --- codegen.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codegen.yml b/codegen.yml index 9ba119f90..8721097b6 100644 --- a/codegen.yml +++ b/codegen.yml @@ -107,6 +107,6 @@ generates: - 'typescript-operations' - 'typed-document-node' -hooks: - afterAllFileWrite: - - npx prettier --write \ No newline at end of file +# hooks: +# afterAllFileWrite: +# - npx prettier --write \ No newline at end of file From 733c14ef7d4f66873bac23e088280107bb2fa672 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:26:34 +0000 Subject: [PATCH 03/71] Add block/unblock user functionality for admins on user profiles - Created BlockUserModal and UnblockUserModal components - Added ViewUserProfileContainer for viewing any user's profile (admin only) - Updated ProfileView to support block/unblock buttons and blocked state - Added route for /account/profile/:userId - Integrated block/unblock mutations in profile view - Added blocked user warning banner and grayed-out UI state - Wired admin dashboard to navigate to user profiles Co-authored-by: kishor-gupta <61230727+kishor-gupta@users.noreply.github.com> --- .../admin-users-table.container.tsx | 4 +- .../components/layouts/home/account/index.tsx | 2 + .../components/profile-view.container.tsx | 3 + .../profile/components/profile-view.tsx | 137 ++++++++++++++- .../view-user-profile.container.graphql | 96 +++++++++++ .../view-user-profile.container.tsx | 158 ++++++++++++++++++ .../shared/user-modals/block-user-modal.tsx | 77 +++++++++ .../components/shared/user-modals/index.ts | 4 + .../shared/user-modals/unblock-user-modal.tsx | 67 ++++++++ 9 files changed, 538 insertions(+), 10 deletions(-) create mode 100644 apps/ui-sharethrift/src/components/layouts/home/account/profile/components/view-user-profile.container.graphql create mode 100644 apps/ui-sharethrift/src/components/layouts/home/account/profile/components/view-user-profile.container.tsx create mode 100644 apps/ui-sharethrift/src/components/shared/user-modals/block-user-modal.tsx create mode 100644 apps/ui-sharethrift/src/components/shared/user-modals/index.ts create mode 100644 apps/ui-sharethrift/src/components/shared/user-modals/unblock-user-modal.tsx diff --git a/apps/ui-sharethrift/src/components/layouts/home/account/admin-dashboard/components/admin-users-table/admin-users-table.container.tsx b/apps/ui-sharethrift/src/components/layouts/home/account/admin-dashboard/components/admin-users-table/admin-users-table.container.tsx index a217b8f74..515ead42d 100644 --- a/apps/ui-sharethrift/src/components/layouts/home/account/admin-dashboard/components/admin-users-table/admin-users-table.container.tsx +++ b/apps/ui-sharethrift/src/components/layouts/home/account/admin-dashboard/components/admin-users-table/admin-users-table.container.tsx @@ -131,8 +131,8 @@ export const AdminUsersTableContainer: React.FC { return ( } /> + } /> } /> ); diff --git a/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/profile-view.container.tsx b/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/profile-view.container.tsx index d3b7094e9..3a87aefc6 100644 --- a/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/profile-view.container.tsx +++ b/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/profile-view.container.tsx @@ -72,6 +72,9 @@ export const ProfileViewContainer: React.FC = () => { user={profileUser} listings={listings} isOwnProfile={true} + isBlocked={false} + isAdmin={false} + canBlockUser={false} onEditSettings={handleEditSettings} onListingClick={handleListingClick} /> diff --git a/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/profile-view.tsx b/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/profile-view.tsx index 53a7a6140..0ff7a727e 100644 --- a/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/profile-view.tsx +++ b/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/profile-view.tsx @@ -8,12 +8,22 @@ import { Col, Space, Divider, + Alert, } from 'antd'; import { ListingsGrid } from '@sthrift/ui-components'; -import { SettingOutlined, UserOutlined } from '@ant-design/icons'; +import { + SettingOutlined, + UserOutlined, + StopOutlined, + CheckCircleOutlined, +} from '@ant-design/icons'; import '../components/profile-view.overrides.css'; import type { ItemListing } from '../../../../../../generated'; import type { ProfileUser } from './profile-view.types'; +import { + BlockUserModal, + UnblockUserModal, +} from '../../../../../shared/user-modals'; const { Text } = Typography; @@ -23,16 +33,42 @@ interface ProfileViewProps { user: ProfileUser; listings: ItemListing[]; isOwnProfile: boolean; + isBlocked?: boolean; + isAdmin?: boolean; + canBlockUser?: boolean; onEditSettings: () => void; onListingClick: (listingId: string) => void; + onBlockUser?: () => void; + onUnblockUser?: () => void; + blockModalVisible?: boolean; + unblockModalVisible?: boolean; + onBlockModalCancel?: () => void; + onUnblockModalCancel?: () => void; + onBlockModalConfirm?: (reason: string) => void; + onUnblockModalConfirm?: () => void; + blockLoading?: boolean; + unblockLoading?: boolean; } export const ProfileView: React.FC> = ({ user, listings, isOwnProfile, + isBlocked = false, + isAdmin = false, + canBlockUser = false, onEditSettings, onListingClick, + onBlockUser, + onUnblockUser, + blockModalVisible = false, + unblockModalVisible = false, + onBlockModalCancel = () => {}, + onUnblockModalCancel = () => {}, + onBlockModalConfirm = () => {}, + onUnblockModalConfirm = () => {}, + blockLoading = false, + unblockLoading = false, }) => { const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('en-US', { @@ -43,10 +79,27 @@ export const ProfileView: React.FC> = ({ return (
+ {/* Blocked User Warning - Only visible to admins */} + {isBlocked && isAdmin && ( + + )} + {/* Profile Header */} - + {/* Mobile Account Settings Button */} - {isOwnProfile && ( + {isOwnProfile ? (
+ ) : ( + canBlockUser && ( +
+ {isBlocked ? ( + + ) : ( + + )} +
+ ) )} @@ -75,9 +150,9 @@ export const ProfileView: React.FC> = ({ {user.firstName}{' '} {user.lastName?.charAt(0)}.
- {/* Desktop Account Settings Button */} - {isOwnProfile && ( -
+ {/* Desktop Block/Unblock or Account Settings Buttons */} +
+ {isOwnProfile ? ( -
- )} + ) : ( + canBlockUser && ( + <> + {isBlocked ? ( + + ) : ( + + )} + + ) + )} +

@{user.username}

@@ -182,6 +283,26 @@ export const ProfileView: React.FC> = ({ )} + + {/* Block/Unblock Modals */} + {canBlockUser && ( + <> + + + + )} ); }; diff --git a/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/view-user-profile.container.graphql b/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/view-user-profile.container.graphql new file mode 100644 index 000000000..7a23b8d4d --- /dev/null +++ b/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/view-user-profile.container.graphql @@ -0,0 +1,96 @@ +query HomeAccountProfileViewUserByIdCurrentUser { + currentUser { + ... on PersonalUser { + id + userIsAdmin + } + ... on AdminUser { + id + userIsAdmin + role { + permissions { + userPermissions { + canViewAllUsers + canBlockUsers + } + } + } + } + } +} + +query HomeAccountProfileViewUserById($userId: ObjectID!) { + userById(id: $userId) { + ... on PersonalUser { + id + userType + isBlocked + createdAt + account { + ...UserProfileViewPersonalUserAccountFields + } + } + ... on AdminUser { + id + userType + isBlocked + createdAt + account { + ...UserProfileViewAdminUserAccountFields + } + } + } +} + +# Note: For admin viewing other users' profiles, we'll hide listings for now +# since there's no listingsByUserId query available in the schema + +fragment UserProfileViewPersonalUserAccountFields on PersonalUserAccount { + accountType + email + username + profile { + ...UserProfileViewPersonalUserProfileFields + } +} + +fragment UserProfileViewPersonalUserProfileFields on PersonalUserAccountProfile { + firstName + lastName + location { + city + state + } +} + +fragment UserProfileViewAdminUserAccountFields on AdminUserAccount { + accountType + email + username + profile { + ...UserProfileViewAdminUserProfileFields + } +} + +fragment UserProfileViewAdminUserProfileFields on AdminUserAccountProfile { + firstName + lastName + location { + city + state + } +} + +mutation BlockUser($userId: ObjectID!) { + blockUser(userId: $userId) { + id + isBlocked + } +} + +mutation UnblockUser($userId: ObjectID!) { + unblockUser(userId: $userId) { + id + isBlocked + } +} diff --git a/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/view-user-profile.container.tsx b/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/view-user-profile.container.tsx new file mode 100644 index 000000000..9b83c414b --- /dev/null +++ b/apps/ui-sharethrift/src/components/layouts/home/account/profile/components/view-user-profile.container.tsx @@ -0,0 +1,158 @@ +import { useNavigate, useParams } from 'react-router-dom'; +import { ProfileView } from './profile-view.tsx'; +import { useQuery, useMutation } from '@apollo/client/react'; +import { ComponentQueryLoader } from '@sthrift/ui-components'; +import { + HomeAccountProfileViewUserByIdCurrentUserDocument, + HomeAccountProfileViewUserByIdDocument, + BlockUserDocument, + UnblockUserDocument, +} from '../../../../../../generated.tsx'; +import { useState } from 'react'; +import { message } from 'antd'; +import type { ItemListing } from '../../../../../../generated.tsx'; + +export const ViewUserProfileContainer: React.FC = () => { + const navigate = useNavigate(); + const { userId } = useParams<{ userId: string }>(); + const [blockModalVisible, setBlockModalVisible] = useState(false); + const [unblockModalVisible, setUnblockModalVisible] = useState(false); + + const { + data: currentUserData, + loading: currentUserLoading, + error: currentUserError, + } = useQuery(HomeAccountProfileViewUserByIdCurrentUserDocument); + + const { + data: userQueryData, + loading: userLoading, + error: userError, + refetch: refetchUser, + } = useQuery(HomeAccountProfileViewUserByIdDocument, { + variables: { userId: userId || '' }, + skip: !userId, + }); + + const [blockUser, { loading: blockLoading }] = useMutation(BlockUserDocument, { + onCompleted: () => { + message.success('User blocked successfully'); + setBlockModalVisible(false); + refetchUser(); + }, + onError: (err) => { + message.error(`Failed to block user: ${err.message}`); + }, + }); + + const [unblockUser, { loading: unblockLoading }] = useMutation( + UnblockUserDocument, + { + onCompleted: () => { + message.success('User unblocked successfully'); + setUnblockModalVisible(false); + refetchUser(); + }, + onError: (err) => { + message.error(`Failed to unblock user: ${err.message}`); + }, + } + ); + + // Check if userId is missing after all hooks + if (!userId) { + navigate('/account/profile'); + return null; + } + + const handleEditSettings = () => { + navigate('/account/settings'); + }; + + const handleListingClick = (listingId: string) => { + navigate(`/listing/${listingId}`); + }; + + const handleBlockUser = (reason: string) => { + console.log('Block reason:', reason); + blockUser({ variables: { userId } }); + }; + + const handleUnblockUser = () => { + unblockUser({ variables: { userId } }); + }; + + const currentUser = currentUserData?.currentUser; + const viewedUser = userQueryData?.userById; + + // Check if current user is admin with permission to block users + const isAdmin = + currentUser?.__typename === 'AdminUser' && + currentUser?.role?.permissions?.userPermissions?.canBlockUsers; + + // Check if current user can view this profile + const canViewProfile = + currentUser?.__typename === 'AdminUser' && + currentUser?.role?.permissions?.userPermissions?.canViewAllUsers; + + // Blocked users can only be viewed by admins + if (viewedUser?.isBlocked && !canViewProfile) { + message.error('This user profile is not available'); + navigate('/home'); + return null; + } + + if (!viewedUser || !currentUser) { + return null; + } + + const { account, createdAt, isBlocked } = viewedUser; + const isOwnProfile = currentUser.id === viewedUser.id; + + const profileUser = { + id: viewedUser.id, + firstName: account?.profile?.firstName || '', + lastName: account?.profile?.lastName || '', + username: account?.username || '', + email: account?.email || '', + accountType: account?.accountType || '', + location: { + city: account?.profile?.location?.city || '', + state: account?.profile?.location?.state || '', + }, + createdAt: createdAt || '', + }; + + // For now, don't show listings when viewing other users' profiles + const listings: ItemListing[] = []; + + return ( + setBlockModalVisible(true)} + onUnblockUser={() => setUnblockModalVisible(true)} + blockModalVisible={blockModalVisible} + unblockModalVisible={unblockModalVisible} + onBlockModalCancel={() => setBlockModalVisible(false)} + onUnblockModalCancel={() => setUnblockModalVisible(false)} + onBlockModalConfirm={handleBlockUser} + onUnblockModalConfirm={handleUnblockUser} + blockLoading={blockLoading} + unblockLoading={unblockLoading} + /> + } + /> + ); +}; diff --git a/apps/ui-sharethrift/src/components/shared/user-modals/block-user-modal.tsx b/apps/ui-sharethrift/src/components/shared/user-modals/block-user-modal.tsx new file mode 100644 index 000000000..590e5f66e --- /dev/null +++ b/apps/ui-sharethrift/src/components/shared/user-modals/block-user-modal.tsx @@ -0,0 +1,77 @@ +import { Modal, Typography, Input } from 'antd'; +import { ExclamationCircleOutlined } from '@ant-design/icons'; +import { useState } from 'react'; + +const { Text, Paragraph } = Typography; +const { TextArea } = Input; + +export interface BlockUserModalProps { + visible: boolean; + userName: string; + onConfirm: (reason: string) => void; + onCancel: () => void; + loading?: boolean; +} + +export const BlockUserModal: React.FC> = ({ + visible, + userName, + onConfirm, + onCancel, + loading = false, +}) => { + const [reason, setReason] = useState(''); + + const handleOk = () => { + onConfirm(reason); + setReason(''); // Reset after confirm + }; + + const handleCancel = () => { + setReason(''); // Reset on cancel + onCancel(); + }; + + return ( + + + Block User + + } + open={visible} + onOk={handleOk} + onCancel={handleCancel} + okText="Block" + okButtonProps={{ danger: true, loading }} + cancelButtonProps={{ disabled: loading }} + width={500} + maskClosable={!loading} + closable={!loading} + > +
+ + Are you sure you want to block {userName}? + + + Blocking this user will prevent them from accessing the platform and + interacting with other users. + +
+ Reason for blocking: +