feat: public item detail view for shared and public setups
All checks were successful
CI / ci (push) Successful in 1m23s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 15s

Items in shared/public setups are now viewable without auth. Clicking
an item in a shared setup navigates to /items/:id?setup=:setupId&share=token
which fetches the item via a public endpoint authorized by the setup's
visibility or share token. Read-only mode hides all owner controls.

- Added getSetupItemById service function
- Added GET /api/shared/:token/items/:itemId endpoint
- Added GET /api/setups/:setupId/items/:itemId/public endpoint
- Added usePublicSetupItem and useSharedSetupItem hooks
- Item detail page detects setup context and switches to public fetch
- Back link returns to setup instead of collection in setup context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 20:17:54 +02:00
parent 731d677da6
commit 4b26a6c88e
5 changed files with 212 additions and 15 deletions

View File

@@ -83,6 +83,51 @@ export function useSharedSetup(token: string | null) {
});
}
interface SetupItem {
id: number;
name: string;
brand?: string | null;
weightGrams: number | null;
priceCents: number | null;
quantity: number;
categoryId: number;
notes: string | null;
productUrl: string | null;
imageFilename: string | null;
imageUrl?: string | null;
globalItemId: number | null;
createdAt: string;
categoryName: string;
categoryIcon: string;
classification: string;
}
export function usePublicSetupItem(
setupId: number | null,
itemId: number | null,
) {
return useQuery({
queryKey: ["setups", setupId, "items", itemId, "public"],
queryFn: () =>
apiGet<SetupItem>(`/api/setups/${setupId}/items/${itemId}/public`),
enabled: setupId != null && itemId != null,
retry: (count, error) =>
error instanceof ApiError && error.status === 404 ? false : count < 3,
});
}
export function useSharedSetupItem(
token: string | null,
itemId: number | null,
) {
return useQuery({
queryKey: ["shared-setup", token, "items", itemId],
queryFn: () => apiGet<SetupItem>(`/api/shared/${token}/items/${itemId}`),
enabled: !!token && itemId != null,
retry: false,
});
}
export function useCreateSetup() {
const queryClient = useQueryClient();
return useMutation({