-
Notifications
You must be signed in to change notification settings - Fork 0
Add admin block/unblock listing controls to view listing page #317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: rohit-r-kumar <175348946+rohit-r-kumar@users.noreply.github.com>
Co-authored-by: rohit-r-kumar <175348946+rohit-r-kumar@users.noreply.github.com>
Co-authored-by: rohit-r-kumar <175348946+rohit-r-kumar@users.noreply.github.com>
Co-authored-by: rohit-r-kumar <175348946+rohit-r-kumar@users.noreply.github.com>
…eters Co-authored-by: rohit-r-kumar <175348946+rohit-r-kumar@users.noreply.github.com>
…pilot/add-block-unblock-listing-buttons-again
|
@sourcery-ai review |
apps/ui-sharethrift/src/components/layouts/home/components/view-listing/view-listing.test.tsx
Fixed
Show fixed
Hide fixed
Reviewer's GuideImplements end-to-end admin block/unblock functionality for item listings: adds a domain-level blocked setter and application service, exposes a new GraphQL blockListing mutation and tests, wires Apollo mutations and admin-only UI controls on the view listing page (including blocked-state UX and access restrictions), updates admin listings to reuse the shared unblock mutation, and hardens MongoDB integration tests plus some package/json formatting and test configuration tweaks. Sequence diagram for admin blocking a listing from the view listing pagesequenceDiagram
actor Admin
participant Browser
participant ViewListingContainer
participant ViewListing
participant BlockListingContainer
participant ApolloClient
participant GraphQLServer
participant ItemListingResolvers
participant ItemListingApplicationService
participant ItemListingUnitOfWork
participant ItemListingRepository
participant ItemListingDomain
Admin->>Browser: Click Block Listing button
Browser->>BlockListingContainer: onClick open block modal
Admin->>BlockListingContainer: Confirm block in BlockListingModal
BlockListingContainer->>ApolloClient: mutate BlockListingContainerBlockListing(id)
ApolloClient->>GraphQLServer: blockListing(id)
GraphQLServer->>ItemListingResolvers: Mutation.blockListing(args, context)
ItemListingResolvers->>ItemListingApplicationService: Listing.ItemListing.block({ id })
ItemListingApplicationService->>ItemListingUnitOfWork: withScopedTransaction(callback)
ItemListingUnitOfWork->>ItemListingRepository: getById(id)
ItemListingRepository-->>ItemListingUnitOfWork: listing entity
ItemListingUnitOfWork->>ItemListingDomain: listing.blocked = true
ItemListingDomain-->>ItemListingUnitOfWork: updated listing
ItemListingUnitOfWork->>ItemListingRepository: save(listing)
ItemListingRepository-->>ItemListingUnitOfWork: saved entity reference
ItemListingUnitOfWork-->>ItemListingApplicationService: entity reference
ItemListingApplicationService-->>ItemListingResolvers: entity reference
ItemListingResolvers-->>GraphQLServer: true
GraphQLServer-->>ApolloClient: { data: { blockListing: true } }
ApolloClient->>ApolloClient: refetch ViewListing(id)
ApolloClient-->>ViewListingContainer: updated itemListing(state Blocked)
ViewListingContainer->>ViewListing: props listing with state Blocked, isAdmin true
ViewListing->>BlockListingContainer: props isBlocked true
ViewListing-->>Browser: Render blocked state banner and Unblock Listing button
Sequence diagram for non-admin viewing a blocked listingsequenceDiagram
actor User
participant Browser
participant ViewListingContainer
participant ApolloClient
participant GraphQLServer
participant ItemListingResolvers
User->>Browser: Navigate to listing detail URL
Browser->>ViewListingContainer: Mount component
ViewListingContainer->>ApolloClient: query ViewListing(id)
ApolloClient->>GraphQLServer: itemListing(id)
GraphQLServer->>ItemListingResolvers: Query.itemListing
ItemListingResolvers-->>GraphQLServer: itemListing(state Blocked)
GraphQLServer-->>ApolloClient: { data: { itemListing } }
ApolloClient-->>ViewListingContainer: listingData with state Blocked
ViewListingContainer->>ApolloClient: query ViewListingCurrentUser
ApolloClient->>GraphQLServer: currentUser
GraphQLServer-->>ApolloClient: { data: { currentUser userIsAdmin false } }
ApolloClient-->>ViewListingContainer: currentUserData
ViewListingContainer->>ViewListingContainer: isBlocked true, isAdmin false, cannotViewBlockedListing true
ViewListingContainer-->>Browser: Render Listing Not Available placeholder via ComponentQueryLoader
Class diagram for listing blocking domain, services, and UI componentsclassDiagram
class ItemListingProps {
}
class ItemListingDomain {
-ItemListingProps props
+setBlocked(value boolean) void
+set blocked(value boolean) void
}
class ItemListingApplicationService {
+create(command CreateItemListingCommand) ItemListingEntityReference
+queryAll(command ItemListingQueryAllCommand) ItemListingEntityReference[]
+cancel(command ItemListingCancelCommand) ItemListingEntityReference
+update(command ItemListingUpdateCommand) ItemListingEntityReference
+deleteListings(command ItemListingDeleteCommand) void
+unblock(command ItemListingUnblockCommand) ItemListingEntityReference
+block(command ItemListingBlockCommand) ItemListingEntityReference
+queryPaged(command PagedQueryCommand) PagedResult
}
class ItemListingUnitOfWork {
+withScopedTransaction(callback function) Promise
}
class ItemListingRepository {
+getById(id string) Promise~ItemListingDomain~
+save(listing ItemListingDomain) Promise~ItemListingEntityReference~
}
class ItemListingResolvers {
+adminListings(args AdminListingsArgs, context GraphContext) AdminListingsResult
+unblockListing(parent unknown, args IdArgs, context GraphContext) boolean
+blockListing(parent unknown, args IdArgs, context GraphContext) boolean
+cancelItemListing(parent unknown, args IdArgs, context GraphContext) ItemListingMutationResult
+deleteItemListing(parent unknown, args IdArgs, context GraphContext) ItemListingMutationResult
}
class BlockListingContainer {
-listingId string
-listingTitle string
-isBlocked boolean
-sharerId string
-blockModalVisible boolean
-unblockModalVisible boolean
+BlockListingContainer(props BlockListingContainerProps) ReactElement
+handleBlockConfirm() Promise~void~
+handleUnblockConfirm() Promise~void~
}
class BlockListingContainerProps {
+listingId string
+listingTitle string
+isBlocked boolean
+sharerId string
}
class BlockListingModal {
+BlockListingModal(props BlockListingModalProps) ReactElement
}
class BlockListingModalProps {
+visible boolean
+listingTitle string
+onConfirm() void
+onCancel() void
+loading boolean
}
class UnblockListingModal {
+UnblockListingModal(props UnblockListingModalProps) ReactElement
}
class UnblockListingModalProps {
+visible boolean
+listingTitle string
+listingSharer string
+blockReason string
+blockDescription string
+onConfirm() void
+onCancel() void
+loading boolean
}
class ViewListingContainer {
+ViewListingContainer(props ViewListingContainerProps) ReactElement
-listingId string
-isAdmin boolean
-isBlocked boolean
-cannotViewBlockedListing boolean
}
class ViewListingContainerProps {
+isAuthenticated boolean
}
class ViewListing {
+ViewListing(props ViewListingProps) ReactElement
}
class ViewListingProps {
+listing ItemListing
+userIsSharer boolean
+isAuthenticated boolean
+currentUserId string
+userReservationRequest ReservationRequest
+sharedTimeAgo string
+isAdmin boolean
}
ItemListingDomain --> ItemListingProps : uses
ItemListingUnitOfWork --> ItemListingRepository : manages
ItemListingRepository --> ItemListingDomain : returns
ItemListingApplicationService --> ItemListingUnitOfWork : uses
ItemListingApplicationService --> ItemListingDomain : updates
ItemListingResolvers --> ItemListingApplicationService : calls
ViewListingContainer --> ViewListing : renders
ViewListing --> BlockListingContainer : renders
BlockListingContainer --> BlockListingModal : renders
BlockListingContainer --> UnblockListingModal : renders
BlockListingContainer --> BlockListingContainerProps : uses
BlockListingModal --> BlockListingModalProps : uses
UnblockListingModal --> UnblockListingModalProps : uses
ViewListing --> ViewListingProps : uses
ViewListingContainer --> ViewListingContainerProps : uses
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes and found some issues that need to be addressed.
- The
blockListing/unblockListingmutations currently only have a TODO comment for role-based authorization; given these are admin-only actions, add explicit auth checks in the resolvers (and ideally in the application service layer) so non-admin callers cannot invoke them even if they bypass the UI. - The requirement that non-admin users cannot view blocked listings is currently enforced only in the UI (
cannotViewBlockedListing), but theitemListingquery still returns the listing; consider enforcing this constraint in the GraphQL query resolver so blocked listings are not exposed to non-admin clients at all.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `blockListing`/`unblockListing` mutations currently only have a TODO comment for role-based authorization; given these are admin-only actions, add explicit auth checks in the resolvers (and ideally in the application service layer) so non-admin callers cannot invoke them even if they bypass the UI.
- The requirement that non-admin users cannot view blocked listings is currently enforced only in the UI (`cannotViewBlockedListing`), but the `itemListing` query still returns the listing; consider enforcing this constraint in the GraphQL query resolver so blocked listings are not exposed to non-admin clients at all.
## Individual Comments
### Comment 1
<location> `packages/sthrift/graphql/src/schema/types/listing/item-listing.resolvers.ts:124-122` </location>
<code_context>
});
return true;
},
+ blockListing: async (_parent, args, context) => {
+ // Admin-note: role-based authorization should be implemented here (security)
+ await context.applicationServices.Listing.ItemListing.block({
+ id: args.id,
+ });
+ return true;
+ },
cancelItemListing: async (
</code_context>
<issue_to_address>
**🚨 issue (security):** The new `blockListing` mutation currently exposes a powerful operation without any enforced authorization.
This mutation calls `Listing.ItemListing.block` using only the provided `id`, and the only auth reference is a TODO. Because `block` effectively hides content, please wire this into your central auth/role mechanism (or match existing admin-only mutation patterns) before release. A concrete check here is important even if other API-level guards are expected, to avoid issues from future misconfiguration.
</issue_to_address>
### Comment 2
<location> `apps/ui-sharethrift/src/components/layouts/home/components/view-listing/view-listing.test.tsx:22` </location>
<code_context>
+ writable: true,
+});
+
+vi.mock('react', async () => {
+ const actual = await vi.importActual<typeof import('react')>('react');
+ return {
</code_context>
<issue_to_address>
**issue (complexity):** Consider replacing the many trivial, specification-style tests with a small set of behavior-focused integration tests that render `ViewListing` and assert real DOM interactions and visuals instead of constants and booleans.
You can significantly reduce complexity and improve value by replacing the “specification doc” style tests with a few focused behavioral tests.
Concrete suggestions:
1. **Remove the global `React` mock and state mocking**
Mocking `useState` globally adds risk and doesn’t help with the current tests (which don’t exercise state anyway).
```ts
// ❌ Remove this entirely
vi.mock('react', async () => {
const actual = await vi.importActual<typeof import('react')>('react');
return {
...actual,
useState: vi.fn((initialValue) =>
actual.useState ? actual.useState(initialValue) : [initialValue, vi.fn()],
),
};
});
```
You can rely on the real `useState` and test state via user interactions.
2. **Replace trivial tests with real component rendering**
Most tests are just type checks or `expect(true).toBe(true)`. Instead, render `<ViewListing />` and assert on actual DOM behavior using RTL (or your existing test utils).
Example: combine “Back Button” tests into a single behavioral test:
```ts
import { render, screen, fireEvent } from '@testing-library/react';
import { ViewListing } from './ViewListing';
it('navigates home when clicking the back button and has correct accessibility', () => {
const originalHref = window.location.href;
Object.defineProperty(window, 'location', {
value: { href: '/' },
writable: true,
});
render(<ViewListing {...defaultProps} />);
const backButton = screen.getByRole('button', { name: /back/i });
expect(backButton).toBeInTheDocument();
fireEvent.click(backButton);
expect(window.location.href).toBe('/');
// if you use an icon you can assert by label/text/class if needed
});
```
Then delete the individual tests:
```ts
// ❌ These can be removed once the behavioral test exists
// - 'should render back button'
// - 'should have LeftOutlined icon'
// - 'should navigate to home on back button click'
// - 'should have aria-label for accessibility'
// - 'should have primary button styling'
```
3. **Group related assertions into fewer, meaningful tests**
Instead of 10+ tests around the admin panel/block UI that only check props/constants, write 1–2 tests that cover the flow:
```ts
it('shows admin controls and opens block modal for admins on listed listing', async () => {
render(
<ViewListing
{...defaultProps}
isAdmin={true}
listing={{ ...mockListing, state: 'Listed' }}
/>,
);
// Control panel visible
expect(screen.getByText(/block listing/i)).toBeInTheDocument();
// Click block -> modal opens
fireEvent.click(screen.getByText(/block listing/i));
expect(screen.getByRole('dialog', { name: /block listing/i })).toBeVisible();
});
```
Then, delete the “Admin Control Panel” + “Block/Unblock Modals” tests that only assert on local constants or `expect(true).toBe(true)`.
4. **Test blocked listing visuals behaviorally, not via computed booleans**
You can check opacity/pointer-events using style assertions instead of computing `canDisable` in the test:
```ts
it('dims and disables content for blocked listings when user is not admin', () => {
render(
<ViewListing
{...defaultProps}
isAdmin={false}
listing={{ ...mockListing, state: 'Blocked' }}
/>,
);
const content = screen.getByTestId('view-listing-content'); // or a container selector
expect(content).toHaveStyle({ opacity: '0.5', pointerEvents: 'none' });
});
```
Then remove:
```ts
// ❌ 'Blocked Listing Visual Effects' tests that only compute booleans or expect(true).toBe(true)
```
5. **Drop “Props Interface” and “CSS/Styling” tests that don’t render**
Tests that assert types of `defaultProps` or constant strings (`expect('error').toBe('error')`) don’t provide runtime safety. They can be removed entirely, or replaced with a single integration-style test that asserts the alert actually renders:
```ts
it('shows a blocked alert for blocked listings', () => {
render(
<ViewListing
{...defaultProps}
listing={{ ...mockListing, state: 'Blocked' }}
/>,
);
const alert = screen.getByRole('alert');
expect(alert).toHaveTextContent(/this listing is currently blocked/i);
});
```
Then delete:
```ts
// ❌ 'Props Interface' suite
// ❌ 'CSS and Styling' suite
// ❌ layout/media-query tests that only contain expect(true).toBe(true)
```
6. **Collapse edge cases into a small number of integration tests**
Instead of many small tests that just check a prop value (`expect(props.sharedTimeAgo).toBeUndefined()`), focus on what changes in the UI:
```ts
it('renders sharer information with fallback sharer when missing', () => {
render(
<ViewListing
{...defaultProps}
listing={{ ...mockListing, sharer: null }}
/>,
);
// whatever UI represents 'Unknown' sharer
expect(screen.getByText(/unknown/i)).toBeInTheDocument();
});
```
---
Applying the above will:
- Remove the global `react` mock.
- Replace dozens of trivial tests with ~5–10 meaningful, behavior-focused tests.
- Keep all feature intent (back navigation, admin controls, block/unblock, visuals, edge cases) while dramatically reducing mental overhead.
</issue_to_address>
### Comment 3
<location> `apps/ui-sharethrift/src/components/layouts/home/components/view-listing/view-listing.container.test.tsx:34` </location>
<code_context>
+ },
+}));
+
+describe('ViewListingContainer', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
</code_context>
<issue_to_address>
**issue (complexity):** Consider replacing the many placeholder test blocks with a small set of focused, behavior-driven tests that actually render the container and assert real GraphQL, auth, and mutation behavior.
You can significantly reduce complexity here by replacing the broad “test matrix” of placeholder specs with a few focused, behavior-driven tests that actually exercise the container logic and only mock what’s needed.
### 1. Remove placeholder tests and consolidate describes
All of these are currently no-ops:
```ts
describe('Props and Initialization', () => {
it('should accept isAuthenticated prop', () => {
expect(true).toBe(true);
});
it('should fetch listing data on mount', () => {
expect(true).toBe(true);
});
it('should skip user query when not authenticated', () => {
expect(true).toBe(true);
});
});
// ...similar patterns in other describe blocks
```
Instead, replace them with a small set of focused integration-style tests that actually render the container and assert its behavior.
### 2. Tighten mocks to actual usage and assert against them
Right now you mock `useQuery`, `useMutation`, `useParams`, and `message` but never assert how they’re used. You can keep these mocks but scope them to real behavior tests.
Example refactor to one concise test for the “fetch listing & skip user when not authenticated” behavior:
```ts
import { render } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ViewListingContainer } from './ViewListingContainer';
import { useQuery, useMutation } from '@apollo/client/react';
import { useParams } from 'react-router-dom';
import { message } from 'antd';
vi.mock('react-router-dom', () => ({
useParams: vi.fn(),
useNavigate: vi.fn(() => vi.fn()),
}));
vi.mock('@apollo/client/react', () => ({
useQuery: vi.fn(),
useMutation: vi.fn(),
}));
vi.mock('antd', () => ({
message: {
success: vi.fn(),
error: vi.fn(),
},
}));
describe('ViewListingContainer', () => {
const mockUseQuery = useQuery as unknown as vi.Mock;
const mockUseMutation = useMutation as unknown as vi.Mock;
const mockUseParams = useParams as unknown as vi.Mock;
beforeEach(() => {
vi.clearAllMocks();
mockUseParams.mockReturnValue({ listingId: 'test-listing-123' });
mockUseQuery.mockImplementation((doc, options) => {
// you can branch on doc or options if needed
return { data: null, loading: false, error: null };
});
mockUseMutation.mockReturnValue([vi.fn(), { loading: false }]);
});
it('executes listing query with listingId and skips user query when not authenticated', () => {
render(<ViewListingContainer isAuthenticated={false} />);
// Example: first query is listing, second would be user (but skipped)
expect(mockUseQuery).toHaveBeenCalledWith(
expect.anything(), // ViewListingDocument
expect.objectContaining({
variables: { listingId: 'test-listing-123' },
fetchPolicy: 'cache-first',
}),
);
// assert skip/user behavior — depends on how you coded it:
expect(mockUseQuery).not.toHaveBeenCalledWith(
expect.objectContaining({ /* User query doc */ }),
expect.objectContaining({ skip: false }),
);
});
});
```
### 3. Focus on a few core behavior tests
Instead of many empty describes (`Admin Functionality`, `Block/Unblock Mutations`, `Loading States`, etc.), add a handful of tests that each cover multiple expectations:
- GraphQL wiring:
- listing query called with `listingId` and correct `fetchPolicy`
- user/reservation queries called only when `isAuthenticated` and `reserverId` condition is met
- Admin/blocked logic:
- `hasData` vs `noDataComponent` for blocked listing as admin vs non-admin
- Mutations:
- `onBlock` and `onUnblock` call the right mutations, refetch, and surface `message.success`/`message.error`
Example for block mutation + message behavior:
```ts
it('calls block mutation and shows success message', async () => {
const blockMutation = vi.fn().mockResolvedValue({ data: {} });
mockUseMutation.mockReturnValueOnce([blockMutation, { loading: false }]);
const { getByRole } = render(
<ViewListingContainer isAuthenticated isAdmin />
);
// however the button is exposed from your presentational component:
getByRole('button', { name: /block listing/i }).click();
expect(blockMutation).toHaveBeenCalledWith({
variables: { listingId: 'test-listing-123' },
});
// depending on your async handling you might need `await waitFor(...)`
expect(message.success).toHaveBeenCalled();
});
```
### 4. Remove unused describe blocks instead of stubbing them
If you don’t yet have logic for a section (e.g., detailed “Time Ago Calculation” in the container), omit that describe block entirely. Placeholder `expect(true).toBe(true)` tests add cognitive load without coverage.
---
In short, replace the large matrix of placeholder describes/its with ~4–8 real tests that:
- render the container,
- assert the actual GraphQL calls & skip logic,
- assert admin/blocked behavior,
- assert mutations + messages.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
packages/sthrift/graphql/src/schema/types/listing/item-listing.resolvers.ts
Outdated
Show resolved
Hide resolved
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
|
When logging in as personal user, the items i blocked as an admin were still visible on the feed even though they couldnt be accessed on click. not sure if this is an issue or not but wanted to point it out. Also, when a listing is blocked, an admin can no longer view the details of the listing and can just see the picture. Wouldn't it be worth seeing the full post as an admin to get context why it was blocked? |
|
@jason-t-hankins have a look |
…pilot/add-block-unblock-listing-buttons-again
…pilot/add-block-unblock-listing-buttons-again
|
I still think the blocked listings should be removed from the home view, but maybe this is out of the scope of this PR? |
...-sharethrift/src/components/layouts/home/components/view-listing/block-listing.container.tsx
Show resolved
Hide resolved
...-sharethrift/src/components/layouts/home/components/view-listing/block-listing.container.tsx
Show resolved
Hide resolved
...-sharethrift/src/components/layouts/home/components/view-listing/block-listing.container.tsx
Show resolved
Hide resolved
jason-t-hankins
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be good for JasonM after this, just left a suggestion for the ant design icons for block/unblock
jasonmorais
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some unaddressed comments, reworks, and branch code that was brought over
...e/account/admin-dashboard/components/admin-listings-table/admin-listings-table.container.tsx
Outdated
Show resolved
Hide resolved
...dmin-dashboard/components/admin-listings-table/admin-listings-table.view-listing.stories.tsx
Show resolved
Hide resolved
...-sharethrift/src/components/layouts/home/components/view-listing/block-listing.container.tsx
Show resolved
Hide resolved
...-sharethrift/src/components/layouts/home/components/view-listing/block-listing.container.tsx
Show resolved
Hide resolved
...-sharethrift/src/components/layouts/home/components/view-listing/block-listing.container.tsx
Show resolved
Hide resolved
packages/sthrift/application-services/src/contexts/listing/item/block.test.ts
Show resolved
Hide resolved
packages/sthrift/domain/src/domain/contexts/listing/item/item-listing.value-objects.ts
Show resolved
Hide resolved
packages/sthrift/event-handler/src/services/reservation-request-notification-service.ts
Outdated
Show resolved
Hide resolved
packages/sthrift/graphql/src/schema/types/listing/item-listing.resolvers.ts
Outdated
Show resolved
Hide resolved
packages/sthrift/graphql/src/schema/types/listing/item-listing.resolvers.ts
Outdated
Show resolved
Hide resolved
…pilot/add-block-unblock-listing-buttons-again






Admins can now block and unblock listings from the listing detail page. Non-admin users cannot view blocked listings.
Backend
block()mirrors existingunblock()pattern, uses domain model'ssetBlocked(true)blockListingmutation alongside existingunblockListingFrontend
listing.state === 'Blocked', visible only whencurrentUser.userIsAdminNotes
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
storybook.js.org/opt/hostedtoolcache/node/22.21.1/x64/bin/node node /home/REDACTED/work/sharethrift/sharethrift/apps/ui-sharethrift/node_modules/.bin/../storybook/bin/index.cjs dev -p 6006 arethrift/apps/ui-sharethrift/src/components/layouts/home/components/listing-page.container.grapsh hit-r-kumar@users.noreply.github.com> odules/npm/node_modules/@npmcli/run-script/lib/node-gyp-bin/sh ayouts/home/components/view-listing/block-listing-modal.tsx apps/ui-sharethrift/src/components/(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.
Summary by Sourcery
Add admin listing block/unblock capabilities and UI to the view listing page, wire them through GraphQL and application services, and adjust tests and tooling to support the new behavior.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests: