feat(workshop): rebuild install and publish as pages
Replace the stacked install modals (browse -> details -> import review) with page-based flows so the primary action is always reachable: - /manage/custom-widgets/workshop browses submissions as a card grid with large previews, sitting in a tab next to the installed widgets - /manage/custom-widgets/workshop/[id] merges the listing, security review and source setup onto one page with a sticky install footer - /manage/custom-widgets/publish/[id] replaces the publish modal - /manage/workshop redirects; its navbar entry is gone - the Custom CSS picker collapses from three modals to one Extract useCustomWidgetImport and ImportReviewContent so the Workshop page and the file/clipboard dialog share one import pipeline. Votes now show the viewer's own choice filled Reddit-style, backed by a new user-votes query, and screenshots get a zoomable lightbox. Rework the installed list onto ManageCollectionItem: show the widget description, replace the cryptic "JSX - 2" badge with plain request and origin facts, and swap the toggle and bare pencil for a status badge, a labelled Edit button and a menu entry for enable/disable. Fixes found while reviewing: - importing could run twice and duplicate a widget; guard on pending and succeeded, and return to the installed list instead of the heavy edit form - a background refetch of the submission (window focus, or the invalidation a vote triggers) reset the source setup and silently wiped part-typed credentials; key the reset on the sources, not the object - breadcrumbs raised MISSING_MESSAGE for unmapped dynamic segments; fall back to the raw segment via t.has - publishing had no in-flight guard and could post duplicate listings - tooltips on disabled vote and report controls never fired, making the sign-in hint unreachable
This commit is contained in:
38 files changed
+2303
-1700
No files matched your search
@@ -657,6 +657,8 @@ export const workshopQueryKeys = {
|
||||
] as const,
|
||||
comments: (backend: WorkshopBackend, submission: string) =>
|
||||
[...workshopQueryKeys.all, "comments", backend.baseUrl, submission] as const,
|
||||
userVotes: (backend: WorkshopBackend) =>
|
||||
[...workshopQueryKeys.all, "user-votes", backend.baseUrl, backend.currentUser?.id ?? "anonymous"] as const,
|
||||
};
|
||||
|
||||
export const workshopListQueryOptions = (backend: WorkshopBackend, options: WorkshopListOptions = {}) =>
|
||||
@@ -683,6 +685,14 @@ export const workshopReportSummariesQueryOptions = (backend: WorkshopBackend, su
|
||||
enabled: submission.length > 0,
|
||||
});
|
||||
|
||||
/** The votes the signed-in user already cast, so controls can render their own choice. */
|
||||
export const workshopUserVotesQueryOptions = (backend: WorkshopBackend) =>
|
||||
queryOptions({
|
||||
queryKey: workshopQueryKeys.userVotes(backend),
|
||||
queryFn: () => backend.listVotesForCurrentUser(),
|
||||
enabled: backend.currentUser !== null,
|
||||
});
|
||||
|
||||
export const workshopCommentsQueryOptions = (backend: WorkshopBackend, submission: string) =>
|
||||
queryOptions({
|
||||
queryKey: workshopQueryKeys.comments(backend, submission),
|
||||
@@ -771,12 +781,17 @@ export function useWorkshopCommentsQuery(backend: WorkshopBackend, submission: s
|
||||
return useQuery(workshopCommentsQueryOptions(backend, submission));
|
||||
}
|
||||
|
||||
export function useWorkshopUserVotesQuery(backend: WorkshopBackend) {
|
||||
return useQuery(workshopUserVotesQueryOptions(backend));
|
||||
}
|
||||
|
||||
function useWorkshopInvalidation(backend: WorkshopBackend) {
|
||||
const queryClient = useQueryClient();
|
||||
return {
|
||||
lists: () => queryClient.invalidateQueries({ queryKey: workshopQueryKeys.lists(backend.baseUrl) }),
|
||||
details: () => queryClient.invalidateQueries({ queryKey: workshopQueryKeys.details(backend.baseUrl) }),
|
||||
detail: (id: string) => queryClient.invalidateQueries({ queryKey: workshopQueryKeys.detail(backend, id) }),
|
||||
userVotes: () => queryClient.invalidateQueries({ queryKey: workshopQueryKeys.userVotes(backend) }),
|
||||
removeDetail: (id: string) => queryClient.removeQueries({ queryKey: workshopQueryKeys.detail(backend, id) }),
|
||||
reports: () => queryClient.invalidateQueries({ queryKey: workshopQueryKeys.reports(backend) }),
|
||||
reportSummaries: (submission: string) =>
|
||||
@@ -814,7 +829,7 @@ export function useWorkshopVoteMutation(backend: WorkshopBackend) {
|
||||
const invalidation = useWorkshopInvalidation(backend);
|
||||
return useMutation({
|
||||
...workshopVoteMutationOptions(backend),
|
||||
onSuccess: async () => Promise.all([invalidation.lists(), invalidation.details()]),
|
||||
onSuccess: async () => Promise.all([invalidation.lists(), invalidation.details(), invalidation.userVotes()]),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user