Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
0215549
Initial plan
Copilot Nov 25, 2025
736d36d
Initial exploration - understanding codebase structure
Copilot Nov 25, 2025
733c14e
Add block/unblock user functionality for admins on user profiles
Copilot Nov 25, 2025
12b1e50
Merge branch 'main' of https://github.com/simnova/sharethrift into co…
kishor-gupta Nov 25, 2025
064795e
Add UserProfile component and update routing in AccountRoutes
kishor-gupta Nov 25, 2025
dab5257
Refactor user profile queries and update block/unblock functionality …
kishor-gupta Nov 25, 2025
e333d83
Enhance block/unblock user modals with improved form handling and use…
kishor-gupta Nov 25, 2025
2158080
Add loading states for block/unblock user actions in admin users table
kishor-gupta Nov 25, 2025
f8b2f26
Refactor AdminUsersTableContainer for improved readability and mainta…
kishor-gupta Nov 25, 2025
54f5882
Refactor block/unblock user modals for improved functionality and cla…
kishor-gupta Nov 26, 2025
e1e1eca
Merge branch 'main' of https://github.com/simnova/sharethrift into co…
kishor-gupta Nov 26, 2025
95d1638
feat(security-requirements): Add comprehensive security requirements …
kishor-gupta Nov 26, 2025
b14907d
feat(admin-users-table): Add storybook stories for AdminUsersTableCon…
kishor-gupta Nov 27, 2025
df9da59
feat(admin-users-table): Add storybook stories for AdminUsersTable wi…
kishor-gupta Nov 27, 2025
7352921
fix(dependencies): update node-forge version to 1.3.2 in lockfiles
kishor-gupta Nov 28, 2025
6dff0d2
feat(block-user-modal): enhance block user modal to include reason an…
kishor-gupta Nov 28, 2025
1da010e
feat(block-user-functionality): update block user modal and related c…
kishor-gupta Nov 28, 2025
e2e14c7
feat(block-user-functionality): refactor block user modal to use Bloc…
kishor-gupta Nov 28, 2025
da8d61e
feat(unblock-user-modal): update unblock user modal to display a stat…
kishor-gupta Nov 28, 2025
b4297e2
feat(view-user-profile): refactor user profile component to improve u…
kishor-gupta Nov 28, 2025
c96cc36
feat(profile-actions): implement block and unblock user functionality…
kishor-gupta Dec 3, 2025
d6f982d
Merge branch 'main' of https://github.com/simnova/sharethrift into co…
kishor-gupta Dec 3, 2025
ccd552e
Merge branch 'main' of https://github.com/simnova/sharethrift into co…
kishor-gupta Dec 4, 2025
8390667
fix(personal-users): update billing status to uppercase 'ACTIVE'
kishor-gupta Dec 4, 2025
e4306b2
fix(profile-actions): update button type and class for unblock user f…
kishor-gupta Dec 4, 2025
2699126
Merge branch 'main' of https://github.com/simnova/sharethrift into co…
kishor-gupta Dec 5, 2025
a70a56b
fix(view-user-profile): handle potential null values for viewedUser a…
kishor-gupta Dec 5, 2025
0dd4404
simplify block user confirmation handling - removed try-catch
kishor-gupta Dec 5, 2025
6efa57a
fix(admin-users-table): improve user name display logic in block/unbl…
kishor-gupta Dec 5, 2025
35e0315
fix(view-user-profile): ensure userId is non-null when blocking/unblo…
kishor-gupta Dec 5, 2025
8210e59
added jws to resolve vulnerabilities
kishor-gupta Dec 5, 2025
02f7541
feat(user-profile): add story to verify UserProfile component exports
kishor-gupta Dec 8, 2025
72ccc90
add stories for BlockUserModal and UnblockUserModal components
kishor-gupta Dec 8, 2025
a6ab501
feat(view-user-profile): add story for ViewUserProfileContainer compo…
kishor-gupta Dec 9, 2025
b57a491
fix(block-user-modal): add error handling for form validation in Bloc…
kishor-gupta Dec 9, 2025
b230949
Merge branch 'main' of https://github.com/simnova/sharethrift into co…
kishor-gupta Dec 9, 2025
4e2f579
fix(dependencies): update jws version in pnpm-lock and pnpm-workspace…
kishor-gupta Dec 9, 2025
d1e0564
fix(pnpm-lock): add newline at end of file for zwitch dependency
kishor-gupta Dec 9, 2025
2199c5f
fix(pnpm-lock): add newline at end of file for zwitch dependency
kishor-gupta Dec 9, 2025
fe4eb4f
Merge branch 'main' of https://github.com/simnova/sharethrift into co…
kishor-gupta Dec 12, 2025
1299325
sourcery-ai bot suggestion || feat: add block/unblock user functional…
kishor-gupta Dec 12, 2025
2cfb36a
feat: update block user functionality to include TODO for backend int…
kishor-gupta Dec 15, 2025
8d2a774
feat: update BlockUserModal to support async onConfirm and improve er…
kishor-gupta Dec 15, 2025
1d07e16
feat: refactor BlockUserModal and UnblockUserModal props for consistency
kishor-gupta Dec 15, 2025
bed42ec
feat: simplify owner label logic in ProfileView component
kishor-gupta Dec 15, 2025
69a11a7
feat: enhance user display name logic in AdminUsersTable and ViewUser…
kishor-gupta Dec 15, 2025
7423228
feat: update user permission checks to "userIsAdmin" instead of canVi…
kishor-gupta Dec 16, 2025
6bb39b2
feat: remove unnecessary role permissions from AdminUser query
kishor-gupta Dec 16, 2025
b9a0970
Initial plan
Copilot Dec 26, 2025
b0ad8c6
Add user appeal submission UI for blocked users
Copilot Dec 26, 2025
52df2d8
Add admin dashboard appeals management UI
Copilot Dec 26, 2025
d470c37
Fix build errors and type issues in appeal components
Copilot Dec 26, 2025
1ff8d68
Add unit tests for UserAppeal component
Copilot Dec 26, 2025
1accd4b
Address code review feedback with TODOs for future improvements
Copilot Dec 26, 2025
48f8e50
Add comprehensive documentation for User Appeal Block feature
Copilot Dec 26, 2025
d326fd6
Remove obsolete unit tests for UserAppeal component
kishor-gupta Dec 26, 2025
9cf8d3d
Confit || just state ||
kishor-gupta Dec 26, 2025
ae28d45
Merge branch 'copilot/add-block-unblock-user-functionality' to this b…
kishor-gupta Dec 26, 2025
83fd36f
Fix user population issue in appeal request creation
Copilot Dec 29, 2025
520db33
Fix type property setter error in appeal request initialization
Copilot Dec 30, 2025
a406d21
Fix GraphQL enum serialization error - align state values to lowercase
Copilot Dec 30, 2025
7f7e0d4
Fix type enum serialization - align discriminator names with GraphQL …
Copilot Dec 30, 2025
d9fc58d
Refactor appeal request type values to lowercase for consistency
kishor-gupta Dec 30, 2025
4f5d0c2
Merge branch 'copilot/enable-appeal-request-submission' of https://gi…
kishor-gupta Dec 30, 2025
110987c
fixed create-appeal request for block user
kishor-gupta Jan 5, 2026
f1f20a1
Update model names for Listing and User appeal requests to be more de…
kishor-gupta Jan 5, 2026
cf7c694
Refactor UserAppeal component to remove isBlocked prop and streamline…
kishor-gupta Jan 5, 2026
7c3205b
Merge branch 'main' of https://github.com/simnova/sharethrift into co…
kishor-gupta Jan 5, 2026
43978ea
refactor: streamline profile listing adaptation and improve readability
kishor-gupta Jan 6, 2026
1ee2268
separated funtions
kishor-gupta Jan 6, 2026
a253106
refactor: consolidate loading states for block and unblock actions in…
kishor-gupta Jan 7, 2026
e38dc8e
refactor: enhance profile permissions handling and improve component …
kishor-gupta Jan 7, 2026
53502e2
refactor: simplify admin user checks for blocking functionality
kishor-gupta Jan 7, 2026
e6d00c4
refactor: remove commented-out block duration functionality from Bloc…
kishor-gupta Jan 7, 2026
2aaa9dd
feat: add block and unblock user functionality with modals in profile…
kishor-gupta Jan 7, 2026
7106aa7
Merge branch 'main' into copilot/add-block-unblock-user-functionality
kishor-gupta Jan 7, 2026
21fb9a0
feat: implement user blocking functionality with modal handling in pr…
kishor-gupta Jan 7, 2026
839d30e
refactor: streamline permission checks and error handling in user blo…
kishor-gupta Jan 7, 2026
db7742b
Merge branch 'copilot/add-block-unblock-user-functionality' of https:…
kishor-gupta Jan 7, 2026
0466ba2
feat: add user display name utility and refactor modal user name hand…
kishor-gupta Jan 7, 2026
9467929
Merge branch 'main' into copilot/add-block-unblock-user-functionality
kishor-gupta Jan 8, 2026
5a36b79
feat: enhance permissions handling for user actions
kishor-gupta Jan 8, 2026
7cec7cd
Merge branch 'copilot/add-block-unblock-user-functionality' of https:…
kishor-gupta Jan 8, 2026
4836590
feat: add isBlocked property to profileUser object in ProfileViewCont…
kishor-gupta Jan 8, 2026
c68f3e6
feat: add handling for non-existent user appeal request ID in queries…
kishor-gupta Jan 8, 2026
1debcf7
feat: update user appeal request query to use input parameter and fix…
kishor-gupta Jan 8, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
query AdminAppealsTableContainerGetAllUserAppealRequests(
$input: GetAllUserAppealRequestsInput!
) {
getAllUserAppealRequests(input: $input) {
items {
id
reason
state
type
createdAt
updatedAt
user {
id
account {
username
email
profile {
firstName
lastName
}
}
}
}
total
page
pageSize
}
}

mutation AdminAppealsTableContainerUpdateUserAppealRequestState(
$input: UpdateUserAppealRequestStateInput!
) {
updateUserAppealRequestState(input: $input) {
id
state
updatedAt
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { useQuery, useMutation } from '@apollo/client/react';
import { AdminAppealsTable } from './admin-appeals-table.tsx';
import { ComponentQueryLoader } from '@sthrift/ui-components';
import { useState } from 'react';
import { message } from 'antd';
import {
AdminAppealsTableContainerGetAllUserAppealRequestsDocument,
AdminAppealsTableContainerUpdateUserAppealRequestStateDocument,
type AdminAppealsTableContainerGetAllUserAppealRequestsQuery,
} from '../../../../../../../generated.tsx';
import type { AdminAppealData } from './admin-appeals-table.types.ts';

export const AdminAppealsTableContainer: React.FC = () => {
const [searchText, setSearchText] = useState('');
const [statusFilters, setStatusFilters] = useState<string[]>([]);
const [sorter, setSorter] = useState<{
field: string;
order: 'ascend' | 'descend';
} | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);

const { data, loading, error, refetch } = useQuery<
AdminAppealsTableContainerGetAllUserAppealRequestsQuery
>(AdminAppealsTableContainerGetAllUserAppealRequestsDocument, {
variables: {
input: {
page: currentPage,
pageSize: pageSize,
stateFilters: statusFilters.length > 0 ? statusFilters : undefined,
sorter: sorter
? {
field: sorter.field,
direction: sorter.order === 'ascend' ? 'ASC' : 'DESC',
}
: undefined,
},
},
});

const [updateAppealState] = useMutation(
AdminAppealsTableContainerUpdateUserAppealRequestStateDocument,
);

const handleSearch = (text: string) => {
setSearchText(text);
setCurrentPage(1);
};

const handleStatusFilter = (filters: string[]) => {
setStatusFilters(filters);
setCurrentPage(1);
};

const handleTableChange = (
_pagination: unknown,
_filters: unknown,
sorter: unknown,
) => {
// Handle sorting
if (sorter && typeof sorter === 'object' && 'field' in sorter) {
const { field, order } = sorter as {
field: string;
order?: 'ascend' | 'descend';
};
if (order) {
setSorter({ field, order });
} else {
setSorter(null);
}
}
};

const handlePageChange = (page: number, pageSize: number) => {
setCurrentPage(page);
setPageSize(pageSize);
};

const handleAction = async (
action: 'accept' | 'deny' | 'view-user',
appealId: string,
) => {
// TODO: Implement navigation to user profile
// The view-user action should navigate to the user's profile page
// Example: navigate(`/admin/users/${appealId}`)
if (action === 'view-user') {
console.log('Navigate to user:', appealId);
message.info('User profile navigation not yet implemented');
return;
}

try {
const newState =
action === 'accept' ? ('accepted' as const) : ('denied' as const);

await updateAppealState({
variables: {
input: {
id: appealId,
state: newState,
},
},
});

message.success(
`Appeal ${action === 'accept' ? 'accepted' : 'denied'} successfully`,
);
refetch();
} catch (error) {
console.error(`Failed to ${action} appeal:`, error);
message.error(`Failed to ${action} appeal. Please try again.`);
}
};

const appealsList = data?.getAllUserAppealRequests?.items || [];

// TODO: PERFORMANCE - Move search filtering to server-side
// Current implementation filters on the client which won't scale well
// The GraphQL schema needs to be extended to support searchText parameter
// Issue: https://github.com/simnova/sharethrift/issues/XXX
const filteredAppeals = searchText
? appealsList.filter(
(appeal) =>
appeal.user.account?.profile?.firstName
?.toLowerCase()
.includes(searchText.toLowerCase()) ||
appeal.user.account?.profile?.lastName
?.toLowerCase()
.includes(searchText.toLowerCase()) ||
appeal.user.account?.email
?.toLowerCase()
.includes(searchText.toLowerCase()),
)
: appealsList;

const appealsData: AdminAppealData[] = filteredAppeals.map((appeal) => ({
id: appeal.id,
userId: appeal.user.id,
userName: `${appeal.user.account?.profile?.firstName || ''} ${appeal.user.account?.profile?.lastName || ''}`.trim(),
userEmail: appeal.user.account?.email || '',
reason: appeal.reason,
state: appeal.state as 'requested' | 'accepted' | 'denied',
type: appeal.type as 'user' | 'listing',
createdAt: appeal.createdAt,
updatedAt: appeal.updatedAt,
}));

return (
<ComponentQueryLoader
loading={loading}
error={error}
hasData={data?.getAllUserAppealRequests ?? null}
hasDataComponent={
<AdminAppealsTable
data={appealsData}
searchText={searchText}
statusFilters={statusFilters}
sorter={sorter || undefined}
currentPage={currentPage}
pageSize={pageSize}
total={data?.getAllUserAppealRequests?.total || 0}
loading={loading}
onSearch={handleSearch}
onStatusFilter={handleStatusFilter}
onTableChange={handleTableChange}
onPageChange={handlePageChange}
onAction={handleAction}
/>
}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AdminAppealsTable } from './admin-appeals-table';
import type { AdminAppealData } from './admin-appeals-table.types';

const meta = {
title:
'Layouts/Home/Account/AdminDashboard/Components/AdminAppealsTable',
component: AdminAppealsTable,
parameters: {
layout: 'padded',
},
tags: ['autodocs'],
} satisfies Meta<typeof AdminAppealsTable>;

export default meta;
type Story = StoryObj<typeof meta>;

const mockAppeals: AdminAppealData[] = [
{
id: '1',
userId: 'user1',
userName: 'John Doe',
userEmail: 'john.doe@example.com',
reason:
'I believe my account was blocked by mistake. I have always followed the community guidelines.',
state: 'requested',
type: 'user',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
{
id: '2',
userId: 'user2',
userName: 'Jane Smith',
userEmail: 'jane.smith@example.com',
reason:
'I apologize for the late return. There was a family emergency.',
state: 'accepted',
type: 'user',
createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
},
{
id: '3',
userId: 'user3',
userName: 'Bob Johnson',
userEmail: 'bob.johnson@example.com',
reason: 'I disagree with the block decision.',
state: 'denied',
type: 'user',
createdAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
updatedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
},
{
id: '4',
userId: 'user4',
userName: 'Alice Williams',
userEmail: 'alice.williams@example.com',
reason:
'My listing was blocked unfairly. I have updated it according to the guidelines.',
state: 'requested',
type: 'listing',
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
},
];

export const Default: Story = {
args: {
data: mockAppeals,
searchText: '',
statusFilters: [],
currentPage: 1,
pageSize: 10,
total: mockAppeals.length,
loading: false,
onSearch: (text: string) => console.log('Search:', text),
onStatusFilter: (filters: string[]) => console.log('Filter:', filters),
onTableChange: (pagination, filters, sorter) =>
console.log('Table change:', { pagination, filters, sorter }),
onPageChange: (page, pageSize) =>
console.log('Page change:', { page, pageSize }),
onAction: (action, appealId) =>
console.log('Action:', { action, appealId }),
},
};

export const WithPendingAppeals: Story = {
args: {
...Default.args,
data: mockAppeals.filter((a) => a.state === 'requested'),
statusFilters: ['requested'],
},
};

export const WithAcceptedAppeals: Story = {
args: {
...Default.args,
data: mockAppeals.filter((a) => a.state === 'accepted'),
statusFilters: ['accepted'],
},
};

export const Empty: Story = {
args: {
...Default.args,
data: [],
total: 0,
},
};

export const Loading: Story = {
args: {
...Default.args,
loading: true,
},
};
Loading
Loading