@stapel/listings-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/MODULE.md +191 -0
- package/README.md +169 -0
- package/dist/api/generated/schema.d.ts +1253 -0
- package/dist/api/generated/schema.d.ts.map +1 -0
- package/dist/api/generated/schema.js +2 -0
- package/dist/api/generated/schema.js.map +1 -0
- package/dist/api/listingsApi.d.ts +145 -0
- package/dist/api/listingsApi.d.ts.map +1 -0
- package/dist/api/listingsApi.js +38 -0
- package/dist/api/listingsApi.js.map +1 -0
- package/dist/api/types.d.ts +156 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +37 -0
- package/dist/api/types.js.map +1 -0
- package/dist/default/ErrorAlert.d.ts +9 -0
- package/dist/default/ErrorAlert.d.ts.map +1 -0
- package/dist/default/ErrorAlert.js +27 -0
- package/dist/default/ErrorAlert.js.map +1 -0
- package/dist/default/FavoritesPane.d.ts +18 -0
- package/dist/default/FavoritesPane.d.ts.map +1 -0
- package/dist/default/FavoritesPane.js +28 -0
- package/dist/default/FavoritesPane.js.map +1 -0
- package/dist/default/ListingCard.d.ts +42 -0
- package/dist/default/ListingCard.d.ts.map +1 -0
- package/dist/default/ListingCard.js +37 -0
- package/dist/default/ListingCard.js.map +1 -0
- package/dist/default/ListingComposerPage.d.ts +46 -0
- package/dist/default/ListingComposerPage.d.ts.map +1 -0
- package/dist/default/ListingComposerPage.js +99 -0
- package/dist/default/ListingComposerPage.js.map +1 -0
- package/dist/default/ListingDetailPane.d.ts +33 -0
- package/dist/default/ListingDetailPane.d.ts.map +1 -0
- package/dist/default/ListingDetailPane.js +48 -0
- package/dist/default/ListingDetailPane.js.map +1 -0
- package/dist/default/ListingPhoto.d.ts +25 -0
- package/dist/default/ListingPhoto.d.ts.map +1 -0
- package/dist/default/ListingPhoto.js +20 -0
- package/dist/default/ListingPhoto.js.map +1 -0
- package/dist/default/MyListingsPane.d.ts +25 -0
- package/dist/default/MyListingsPane.d.ts.map +1 -0
- package/dist/default/MyListingsPane.js +84 -0
- package/dist/default/MyListingsPane.js.map +1 -0
- package/dist/default/StatusTags.d.ts +31 -0
- package/dist/default/StatusTags.d.ts.map +1 -0
- package/dist/default/StatusTags.js +40 -0
- package/dist/default/StatusTags.js.map +1 -0
- package/dist/default/icons.d.ts +16 -0
- package/dist/default/icons.d.ts.map +1 -0
- package/dist/default/icons.js +5 -0
- package/dist/default/icons.js.map +1 -0
- package/dist/default/index.d.ts +35 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +26 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/theme.d.ts +25 -0
- package/dist/default/theme.d.ts.map +1 -0
- package/dist/default/theme.js +40 -0
- package/dist/default/theme.js.map +1 -0
- package/dist/default/types.d.ts +12 -0
- package/dist/default/types.d.ts.map +1 -0
- package/dist/default/types.js +2 -0
- package/dist/default/types.js.map +1 -0
- package/dist/flows/registry.d.ts +35 -0
- package/dist/flows/registry.d.ts.map +1 -0
- package/dist/flows/registry.js +33 -0
- package/dist/flows/registry.js.map +1 -0
- package/dist/headless/Favorites.d.ts +52 -0
- package/dist/headless/Favorites.d.ts.map +1 -0
- package/dist/headless/Favorites.js +78 -0
- package/dist/headless/Favorites.js.map +1 -0
- package/dist/headless/ListingActions.d.ts +30 -0
- package/dist/headless/ListingActions.d.ts.map +1 -0
- package/dist/headless/ListingActions.js +54 -0
- package/dist/headless/ListingActions.js.map +1 -0
- package/dist/headless/ListingComposer.d.ts +133 -0
- package/dist/headless/ListingComposer.d.ts.map +1 -0
- package/dist/headless/ListingComposer.js +246 -0
- package/dist/headless/ListingComposer.js.map +1 -0
- package/dist/headless/ListingDetail.d.ts +81 -0
- package/dist/headless/ListingDetail.d.ts.map +1 -0
- package/dist/headless/ListingDetail.js +101 -0
- package/dist/headless/ListingDetail.js.map +1 -0
- package/dist/headless/ListingsProvider.d.ts +17 -0
- package/dist/headless/ListingsProvider.d.ts.map +1 -0
- package/dist/headless/ListingsProvider.js +16 -0
- package/dist/headless/ListingsProvider.js.map +1 -0
- package/dist/headless/MyListings.d.ts +44 -0
- package/dist/headless/MyListings.d.ts.map +1 -0
- package/dist/headless/MyListings.js +82 -0
- package/dist/headless/MyListings.js.map +1 -0
- package/dist/headless/useMandateGate.d.ts +28 -0
- package/dist/headless/useMandateGate.d.ts.map +1 -0
- package/dist/headless/useMandateGate.js +39 -0
- package/dist/headless/useMandateGate.js.map +1 -0
- package/dist/i18n/errorsMap.d.ts +34 -0
- package/dist/i18n/errorsMap.d.ts.map +1 -0
- package/dist/i18n/errorsMap.js +45 -0
- package/dist/i18n/errorsMap.js.map +1 -0
- package/dist/i18n/es.d.ts +21 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +146 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.es.gen.js +58 -0
- package/dist/i18n/generated/errors.es.gen.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +413 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.gen.js +210 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.js +58 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
- package/dist/i18n/keys.d.ts +170 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +295 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +37 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +162 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +98 -0
- package/dist/index.js.map +1 -0
- package/dist/model/context.d.ts +11 -0
- package/dist/model/context.d.ts.map +1 -0
- package/dist/model/context.js +16 -0
- package/dist/model/context.js.map +1 -0
- package/dist/model/draft.d.ts +138 -0
- package/dist/model/draft.d.ts.map +1 -0
- package/dist/model/draft.js +192 -0
- package/dist/model/draft.js.map +1 -0
- package/dist/model/features.d.ts +69 -0
- package/dist/model/features.d.ts.map +1 -0
- package/dist/model/features.js +73 -0
- package/dist/model/features.js.map +1 -0
- package/dist/model/mineSource.d.ts +47 -0
- package/dist/model/mineSource.d.ts.map +1 -0
- package/dist/model/mineSource.js +45 -0
- package/dist/model/mineSource.js.map +1 -0
- package/dist/model/mutations.d.ts +60 -0
- package/dist/model/mutations.d.ts.map +1 -0
- package/dist/model/mutations.js +165 -0
- package/dist/model/mutations.js.map +1 -0
- package/dist/model/queries.d.ts +75 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +117 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +50 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +24 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/runtime.d.ts +69 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +14 -0
- package/dist/model/runtime.js.map +1 -0
- package/dist/model/status.d.ts +117 -0
- package/dist/model/status.d.ts.map +1 -0
- package/dist/model/status.js +164 -0
- package/dist/model/status.js.map +1 -0
- package/dist/model/transitions.d.ts +41 -0
- package/dist/model/transitions.d.ts.map +1 -0
- package/dist/model/transitions.js +25 -0
- package/dist/model/transitions.js.map +1 -0
- package/dist/model/validation.d.ts +135 -0
- package/dist/model/validation.d.ts.map +1 -0
- package/dist/model/validation.js +191 -0
- package/dist/model/validation.js.map +1 -0
- package/dist/nav/manifest.d.ts +46 -0
- package/dist/nav/manifest.d.ts.map +1 -0
- package/dist/nav/manifest.js +56 -0
- package/dist/nav/manifest.js.map +1 -0
- package/llms.txt +118 -0
- package/manifest.json +1044 -0
- package/nav-manifest.json +84 -0
- package/package.json +121 -0
- package/src/analytics/generated/events.json +7 -0
- package/src/api/generated/schema.ts +1255 -0
- package/src/api/listingsApi.ts +237 -0
- package/src/api/types.ts +193 -0
- package/src/default/ErrorAlert.tsx +46 -0
- package/src/default/FavoritesPane.tsx +123 -0
- package/src/default/ListingCard.tsx +178 -0
- package/src/default/ListingComposerPage.tsx +466 -0
- package/src/default/ListingDetailPane.tsx +271 -0
- package/src/default/ListingPhoto.tsx +62 -0
- package/src/default/MyListingsPane.tsx +333 -0
- package/src/default/StatusTags.tsx +86 -0
- package/src/default/icons.tsx +31 -0
- package/src/default/index.ts +35 -0
- package/src/default/theme.tsx +67 -0
- package/src/default/types.ts +12 -0
- package/src/flows/registry.ts +45 -0
- package/src/headless/Favorites.tsx +137 -0
- package/src/headless/ListingActions.tsx +101 -0
- package/src/headless/ListingComposer.tsx +476 -0
- package/src/headless/ListingDetail.tsx +231 -0
- package/src/headless/ListingsProvider.tsx +20 -0
- package/src/headless/MyListings.tsx +148 -0
- package/src/headless/useMandateGate.ts +40 -0
- package/src/i18n/errorsMap.ts +62 -0
- package/src/i18n/es.ts +198 -0
- package/src/i18n/generated/errors.es.gen.ts +64 -0
- package/src/i18n/generated/errors.gen.ts +241 -0
- package/src/i18n/generated/errors.json +499 -0
- package/src/i18n/generated/errors.ru.gen.ts +64 -0
- package/src/i18n/keys.ts +356 -0
- package/src/i18n/ru.ts +215 -0
- package/src/index.ts +260 -0
- package/src/model/context.tsx +28 -0
- package/src/model/draft.ts +307 -0
- package/src/model/features.ts +122 -0
- package/src/model/mineSource.ts +55 -0
- package/src/model/mutations.ts +224 -0
- package/src/model/queries.ts +150 -0
- package/src/model/queryKeys.ts +76 -0
- package/src/model/runtime.ts +87 -0
- package/src/model/status.ts +279 -0
- package/src/model/transitions.ts +62 -0
- package/src/model/validation.ts +271 -0
- package/src/nav/manifest.ts +97 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { useListingsApi } from "./context.js";
|
|
3
|
+
import { listingsQueryKeys } from "./queryKeys.js";
|
|
4
|
+
/**
|
|
5
|
+
* Every write in the module, each one saying what it invalidates and why.
|
|
6
|
+
*
|
|
7
|
+
* ── The listing id is a VARIABLE, not a hook argument ──────────────────────
|
|
8
|
+
*
|
|
9
|
+
* Every mutation here takes its id in the mutation's variables rather than in
|
|
10
|
+
* the hook's closure, and that is a correctness decision rather than a style
|
|
11
|
+
* one. The composer creates the draft row and saves into it IN THE SAME user
|
|
12
|
+
* gesture: an id-bound hook would still be holding the id from the render
|
|
13
|
+
* before the create, so the save would go to the wrong path (or to none). A
|
|
14
|
+
* variable is read at call time, which is the only time the id is known.
|
|
15
|
+
*
|
|
16
|
+
* ── The invalidation rule ──────────────────────────────────────────────────
|
|
17
|
+
*
|
|
18
|
+
* A write that can move `status` or `moderation_status` invalidates the
|
|
19
|
+
* DETAIL, the STATUS probe and the COUNTERS — the counters because their
|
|
20
|
+
* three tabs are defined by status sets (`views.my_counters`), so a publish
|
|
21
|
+
* silently moves a row from "drafts" to "active" and a dashboard that did not
|
|
22
|
+
* refetch would show the old number beside the new row.
|
|
23
|
+
*
|
|
24
|
+
* Nothing here is optimistic except the favourite toggle, and that exception
|
|
25
|
+
* is argued at its own hook.
|
|
26
|
+
*/
|
|
27
|
+
/** Everything a status-moving write must freshen. */
|
|
28
|
+
function invalidateListing(queryClient, id) {
|
|
29
|
+
void queryClient.invalidateQueries({ queryKey: listingsQueryKeys.detail(id) });
|
|
30
|
+
void queryClient.invalidateQueries({ queryKey: listingsQueryKeys.status(id) });
|
|
31
|
+
void queryClient.invalidateQueries({
|
|
32
|
+
queryKey: listingsQueryKeys.myCounters(),
|
|
33
|
+
});
|
|
34
|
+
void queryClient.invalidateQueries({
|
|
35
|
+
queryKey: listingsQueryKeys.validateDraft(id),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** Start a draft. The server forces `owner` and `status=draft`. */
|
|
39
|
+
export function useCreateDraft() {
|
|
40
|
+
const api = useListingsApi();
|
|
41
|
+
const queryClient = useQueryClient();
|
|
42
|
+
return useMutation({
|
|
43
|
+
mutationFn: (body) => api.createDraft(body),
|
|
44
|
+
retry: false,
|
|
45
|
+
onSuccess: () => {
|
|
46
|
+
void queryClient.invalidateQueries({
|
|
47
|
+
queryKey: listingsQueryKeys.myCounters(),
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Persist draft fields. Always partial — send what the form holds.
|
|
54
|
+
*
|
|
55
|
+
* The validate-draft entry is invalidated rather than written: the saved row
|
|
56
|
+
* comes back, but it carries no verdict, and a stale verdict beside fresh
|
|
57
|
+
* content is the one combination a composer must never show.
|
|
58
|
+
*/
|
|
59
|
+
export function useSaveDraft() {
|
|
60
|
+
const api = useListingsApi();
|
|
61
|
+
const queryClient = useQueryClient();
|
|
62
|
+
return useMutation({
|
|
63
|
+
mutationFn: (input) => api.saveDraft(input.id, input.body),
|
|
64
|
+
retry: false,
|
|
65
|
+
onSuccess: (_data, input) => {
|
|
66
|
+
invalidateListing(queryClient, input.id);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Promote the draft.
|
|
72
|
+
*
|
|
73
|
+
* `retry: false` — the 400 here is a VERDICT about the content (a bare
|
|
74
|
+
* `ValidationBatchResult` body, see `model/validation.ts`), and repeating a
|
|
75
|
+
* verdict three times only delays the moment the composer can lay it on the
|
|
76
|
+
* controls.
|
|
77
|
+
*/
|
|
78
|
+
export function usePublishListing() {
|
|
79
|
+
const api = useListingsApi();
|
|
80
|
+
const queryClient = useQueryClient();
|
|
81
|
+
return useMutation({
|
|
82
|
+
mutationFn: (id) => api.publish(id),
|
|
83
|
+
retry: false,
|
|
84
|
+
onSuccess: (_data, id) => {
|
|
85
|
+
invalidateListing(queryClient, id);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Move to ARCHIVED. 409 `error.409.invalid_listing_transition` carries
|
|
90
|
+
* `params.from_status`, which is what turns the refusal into a sentence. */
|
|
91
|
+
export function useArchiveListing() {
|
|
92
|
+
const api = useListingsApi();
|
|
93
|
+
const queryClient = useQueryClient();
|
|
94
|
+
return useMutation({
|
|
95
|
+
mutationFn: (id) => api.archive(id),
|
|
96
|
+
retry: false,
|
|
97
|
+
onSuccess: (_data, id) => {
|
|
98
|
+
invalidateListing(queryClient, id);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Mark SOLD. Same 409 contract as {@link useArchiveListing}. */
|
|
103
|
+
export function useCompleteListing() {
|
|
104
|
+
const api = useListingsApi();
|
|
105
|
+
const queryClient = useQueryClient();
|
|
106
|
+
return useMutation({
|
|
107
|
+
mutationFn: (id) => api.complete(id),
|
|
108
|
+
retry: false,
|
|
109
|
+
onSuccess: (_data, id) => {
|
|
110
|
+
invalidateListing(queryClient, id);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** Soft-delete. Refused with `error.409.listing_cannot_delete_active` while
|
|
115
|
+
* the listing is PUBLISHED or PENDING. */
|
|
116
|
+
export function useDeleteListing() {
|
|
117
|
+
const api = useListingsApi();
|
|
118
|
+
const queryClient = useQueryClient();
|
|
119
|
+
return useMutation({
|
|
120
|
+
mutationFn: (id) => api.remove(id),
|
|
121
|
+
retry: false,
|
|
122
|
+
onSuccess: (_data, id) => {
|
|
123
|
+
invalidateListing(queryClient, id);
|
|
124
|
+
void queryClient.invalidateQueries({ queryKey: listingsQueryKeys.all });
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Favourite / un-favourite, as ONE mutation over the intended next state.
|
|
130
|
+
*
|
|
131
|
+
* Two endpoints, one intent — and modelling it as two hooks would let a
|
|
132
|
+
* caller call the wrong one for the state it is in. The server is idempotent
|
|
133
|
+
* on both sides (`get_or_create` / `filter().delete()`), so a double-click
|
|
134
|
+
* cannot desynchronise anything.
|
|
135
|
+
*
|
|
136
|
+
* This is the pair's ONE write whose next state the client may predict, and
|
|
137
|
+
* the reason is that the truth is a single boolean the caller already holds.
|
|
138
|
+
* Everything else here moves a LIFECYCLE whose next state is the server's to
|
|
139
|
+
* decide — guessing `pending` versus `published` after a publish would be
|
|
140
|
+
* inventing the very verdict the pair exists to report faithfully.
|
|
141
|
+
*
|
|
142
|
+
* The invalidation reaches the favourites LIST as well as the row: a heart
|
|
143
|
+
* turned off on the favourites page must remove the card, not leave a hollow
|
|
144
|
+
* one behind.
|
|
145
|
+
*/
|
|
146
|
+
export function useFavoriteListing() {
|
|
147
|
+
const api = useListingsApi();
|
|
148
|
+
const queryClient = useQueryClient();
|
|
149
|
+
return useMutation({
|
|
150
|
+
mutationFn: (input) => input.favorited ? api.favorite(input.id) : api.unfavorite(input.id),
|
|
151
|
+
retry: false,
|
|
152
|
+
onSettled: (_data, _error, input) => {
|
|
153
|
+
void queryClient.invalidateQueries({
|
|
154
|
+
queryKey: listingsQueryKeys.detail(input.id),
|
|
155
|
+
});
|
|
156
|
+
void queryClient.invalidateQueries({
|
|
157
|
+
queryKey: listingsQueryKeys.allFavorites(),
|
|
158
|
+
});
|
|
159
|
+
void queryClient.invalidateQueries({
|
|
160
|
+
queryKey: listingsQueryKeys.allLists(),
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=mutations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutations.js","sourceRoot":"","sources":["../../src/model/mutations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { UseQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import type { StapelApiError } from "@stapel/core";
|
|
3
|
+
import type { ValidationBatchResult } from "@stapel/attributes-react";
|
|
4
|
+
import type { ListingDetail, ListingPageParams, ListingStatusInfo, MyCounters, PaginatedListingCards } from "../api/types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Read hooks over the listings API (frontend-standard §2). Keys are
|
|
7
|
+
* namespaced (see `listingsQueryKeys`).
|
|
8
|
+
*
|
|
9
|
+
* ── Two gating regimes in one module, and the line between them ────────────
|
|
10
|
+
*
|
|
11
|
+
* `useListing` / `useListingStatus` / `useListingCards` call
|
|
12
|
+
* `IsAuthenticatedOrReadOnly` and `AllowAny` endpoints: a visitor reads them
|
|
13
|
+
* with no session at all, so gating on `useActiveSessionReady` would make a
|
|
14
|
+
* shop window wait for a login bootstrap it has no stake in. Core's own doc
|
|
15
|
+
* comment carves out exactly this case.
|
|
16
|
+
*
|
|
17
|
+
* `useMyCounters` / `useMyFavorites` / `useValidateDraft` are
|
|
18
|
+
* `IsAuthenticated` and DO gate: firing them before the session substrate has
|
|
19
|
+
* settled produces a 401 that means "we asked too early", which is
|
|
20
|
+
* indistinguishable on screen from "you are signed out".
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* One listing in full.
|
|
24
|
+
*
|
|
25
|
+
* `retry: false` on purpose. The failures here are verdicts about the
|
|
26
|
+
* request: a 404 for a listing that is gone, a 403 for one that is not
|
|
27
|
+
* yours. Retrying a 404 three times only delays the moment the page can say
|
|
28
|
+
* which it was — and for a soft-deleted listing, `useListingStatus` is the
|
|
29
|
+
* read that can actually explain it (see `headless/ListingDetail.tsx`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function useListing(id: number | undefined, options?: {
|
|
32
|
+
readonly enabled?: boolean;
|
|
33
|
+
}): UseQueryResult<ListingDetail, StapelApiError>;
|
|
34
|
+
/**
|
|
35
|
+
* The status probe — both axes plus `is_deleted`, for ANY listing id.
|
|
36
|
+
*
|
|
37
|
+
* The one read that survives a soft delete (`Listing.all_objects`), which is
|
|
38
|
+
* why a detail page runs it beside the detail rather than only after a
|
|
39
|
+
* failure: "this listing was removed" is a sentence, and a bare 404 is not.
|
|
40
|
+
*/
|
|
41
|
+
export declare function useListingStatus(id: number | undefined, options?: {
|
|
42
|
+
readonly enabled?: boolean;
|
|
43
|
+
}): UseQueryResult<ListingStatusInfo, StapelApiError>;
|
|
44
|
+
/**
|
|
45
|
+
* A keyset page of PUBLISHED cards.
|
|
46
|
+
*
|
|
47
|
+
* `keepPreviousData`: paging a grid that blanks between pages reads as a
|
|
48
|
+
* failure, and the previous page is still a true answer about the previous
|
|
49
|
+
* cursor. The FIRST load has nothing to keep and is `loading`; a failure is
|
|
50
|
+
* still `failed` rather than stale rows pretending to be current.
|
|
51
|
+
*/
|
|
52
|
+
export declare function useListingCards(params?: ListingPageParams, options?: {
|
|
53
|
+
readonly enabled?: boolean;
|
|
54
|
+
}): UseQueryResult<PaginatedListingCards, StapelApiError>;
|
|
55
|
+
/** The dashboard's three counts. Session-gated. */
|
|
56
|
+
export declare function useMyCounters(options?: {
|
|
57
|
+
readonly enabled?: boolean;
|
|
58
|
+
}): UseQueryResult<MyCounters, StapelApiError>;
|
|
59
|
+
/** A keyset page of the caller's favourites. Session-gated. */
|
|
60
|
+
export declare function useMyFavorites(params?: ListingPageParams, options?: {
|
|
61
|
+
readonly enabled?: boolean;
|
|
62
|
+
}): UseQueryResult<PaginatedListingCards, StapelApiError>;
|
|
63
|
+
/**
|
|
64
|
+
* What publishing WOULD say, without publishing.
|
|
65
|
+
*
|
|
66
|
+
* Disabled by default: it is a round trip per call and the composer's own
|
|
67
|
+
* mirror answers the same question for free most of the time. A composer
|
|
68
|
+
* turns it on when the person asks ("check my listing") and after a save, so
|
|
69
|
+
* the SERVER's opinion of the saved draft is what the button reports —
|
|
70
|
+
* `staleTime: 0` because the draft is exactly the thing that just moved.
|
|
71
|
+
*/
|
|
72
|
+
export declare function useValidateDraft(id: number | undefined, options?: {
|
|
73
|
+
readonly enabled?: boolean;
|
|
74
|
+
}): UseQueryResult<ValidationBatchResult, StapelApiError>;
|
|
75
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,qBAAqB,EACtB,MAAM,iBAAiB,CAAC;AAIzB;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,aAAa,EAAE,cAAc,CAAC,CAQ/C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAQnD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,MAAM,CAAC,EAAE,iBAAiB,EAC1B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,qBAAqB,EAAE,cAAc,CAAC,CASvD;AAED,mDAAmD;AACnD,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,cAAc,CAAC,UAAU,EAAE,cAAc,CAAC,CAS7C;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAC5B,MAAM,CAAC,EAAE,iBAAiB,EAC1B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,qBAAqB,EAAE,cAAc,CAAC,CAUvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,qBAAqB,EAAE,cAAc,CAAC,CAUvD"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { useActiveSessionReady } from "@stapel/core";
|
|
3
|
+
import { useListingsApi } from "./context.js";
|
|
4
|
+
import { listingsQueryKeys, pageKey } from "./queryKeys.js";
|
|
5
|
+
/**
|
|
6
|
+
* Read hooks over the listings API (frontend-standard §2). Keys are
|
|
7
|
+
* namespaced (see `listingsQueryKeys`).
|
|
8
|
+
*
|
|
9
|
+
* ── Two gating regimes in one module, and the line between them ────────────
|
|
10
|
+
*
|
|
11
|
+
* `useListing` / `useListingStatus` / `useListingCards` call
|
|
12
|
+
* `IsAuthenticatedOrReadOnly` and `AllowAny` endpoints: a visitor reads them
|
|
13
|
+
* with no session at all, so gating on `useActiveSessionReady` would make a
|
|
14
|
+
* shop window wait for a login bootstrap it has no stake in. Core's own doc
|
|
15
|
+
* comment carves out exactly this case.
|
|
16
|
+
*
|
|
17
|
+
* `useMyCounters` / `useMyFavorites` / `useValidateDraft` are
|
|
18
|
+
* `IsAuthenticated` and DO gate: firing them before the session substrate has
|
|
19
|
+
* settled produces a 401 that means "we asked too early", which is
|
|
20
|
+
* indistinguishable on screen from "you are signed out".
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* One listing in full.
|
|
24
|
+
*
|
|
25
|
+
* `retry: false` on purpose. The failures here are verdicts about the
|
|
26
|
+
* request: a 404 for a listing that is gone, a 403 for one that is not
|
|
27
|
+
* yours. Retrying a 404 three times only delays the moment the page can say
|
|
28
|
+
* which it was — and for a soft-deleted listing, `useListingStatus` is the
|
|
29
|
+
* read that can actually explain it (see `headless/ListingDetail.tsx`).
|
|
30
|
+
*/
|
|
31
|
+
export function useListing(id, options) {
|
|
32
|
+
const api = useListingsApi();
|
|
33
|
+
return useQuery({
|
|
34
|
+
queryKey: listingsQueryKeys.detail(id ?? -1),
|
|
35
|
+
queryFn: ({ signal }) => api.retrieve(id, { signal }),
|
|
36
|
+
enabled: (options?.enabled ?? true) && id !== undefined,
|
|
37
|
+
retry: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The status probe — both axes plus `is_deleted`, for ANY listing id.
|
|
42
|
+
*
|
|
43
|
+
* The one read that survives a soft delete (`Listing.all_objects`), which is
|
|
44
|
+
* why a detail page runs it beside the detail rather than only after a
|
|
45
|
+
* failure: "this listing was removed" is a sentence, and a bare 404 is not.
|
|
46
|
+
*/
|
|
47
|
+
export function useListingStatus(id, options) {
|
|
48
|
+
const api = useListingsApi();
|
|
49
|
+
return useQuery({
|
|
50
|
+
queryKey: listingsQueryKeys.status(id ?? -1),
|
|
51
|
+
queryFn: ({ signal }) => api.status(id, { signal }),
|
|
52
|
+
enabled: (options?.enabled ?? true) && id !== undefined,
|
|
53
|
+
retry: false,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A keyset page of PUBLISHED cards.
|
|
58
|
+
*
|
|
59
|
+
* `keepPreviousData`: paging a grid that blanks between pages reads as a
|
|
60
|
+
* failure, and the previous page is still a true answer about the previous
|
|
61
|
+
* cursor. The FIRST load has nothing to keep and is `loading`; a failure is
|
|
62
|
+
* still `failed` rather than stale rows pretending to be current.
|
|
63
|
+
*/
|
|
64
|
+
export function useListingCards(params, options) {
|
|
65
|
+
const api = useListingsApi();
|
|
66
|
+
return useQuery({
|
|
67
|
+
queryKey: listingsQueryKeys.list(pageKey(params)),
|
|
68
|
+
queryFn: ({ signal }) => api.list(params, { signal }),
|
|
69
|
+
enabled: options?.enabled ?? true,
|
|
70
|
+
placeholderData: keepPreviousData,
|
|
71
|
+
retry: false,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** The dashboard's three counts. Session-gated. */
|
|
75
|
+
export function useMyCounters(options) {
|
|
76
|
+
const api = useListingsApi();
|
|
77
|
+
const sessionReady = useActiveSessionReady();
|
|
78
|
+
return useQuery({
|
|
79
|
+
queryKey: listingsQueryKeys.myCounters(),
|
|
80
|
+
queryFn: ({ signal }) => api.myCounters({ signal }),
|
|
81
|
+
enabled: (options?.enabled ?? true) && sessionReady,
|
|
82
|
+
retry: false,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** A keyset page of the caller's favourites. Session-gated. */
|
|
86
|
+
export function useMyFavorites(params, options) {
|
|
87
|
+
const api = useListingsApi();
|
|
88
|
+
const sessionReady = useActiveSessionReady();
|
|
89
|
+
return useQuery({
|
|
90
|
+
queryKey: listingsQueryKeys.myFavorites(pageKey(params)),
|
|
91
|
+
queryFn: ({ signal }) => api.myFavorites(params, { signal }),
|
|
92
|
+
enabled: (options?.enabled ?? true) && sessionReady,
|
|
93
|
+
placeholderData: keepPreviousData,
|
|
94
|
+
retry: false,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* What publishing WOULD say, without publishing.
|
|
99
|
+
*
|
|
100
|
+
* Disabled by default: it is a round trip per call and the composer's own
|
|
101
|
+
* mirror answers the same question for free most of the time. A composer
|
|
102
|
+
* turns it on when the person asks ("check my listing") and after a save, so
|
|
103
|
+
* the SERVER's opinion of the saved draft is what the button reports —
|
|
104
|
+
* `staleTime: 0` because the draft is exactly the thing that just moved.
|
|
105
|
+
*/
|
|
106
|
+
export function useValidateDraft(id, options) {
|
|
107
|
+
const api = useListingsApi();
|
|
108
|
+
const sessionReady = useActiveSessionReady();
|
|
109
|
+
return useQuery({
|
|
110
|
+
queryKey: listingsQueryKeys.validateDraft(id ?? -1),
|
|
111
|
+
queryFn: ({ signal }) => api.validateDraft(id, { signal }),
|
|
112
|
+
enabled: (options?.enabled ?? false) && id !== undefined && sessionReady,
|
|
113
|
+
staleTime: 0,
|
|
114
|
+
retry: false,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespaced TanStack Query keys (frontend-standard §2 — namespaced keys).
|
|
3
|
+
* Everything under the `"listings"` root so a host can invalidate the whole
|
|
4
|
+
* module or match a single read. Explicit tuple return types satisfy
|
|
5
|
+
* `--isolatedDeclarations`.
|
|
6
|
+
*
|
|
7
|
+
* THE KEY IS THE REQUEST for the paginated reads: a page is keyed on the very
|
|
8
|
+
* parameter object the client is handed, normalized so that "no cursor" and
|
|
9
|
+
* "an explicitly undefined cursor" cannot cache twice. Two states that
|
|
10
|
+
* produce the same request therefore share a cache entry, and one that
|
|
11
|
+
* produces a different request cannot silently reuse a page — the stale-page
|
|
12
|
+
* bug (new filter, cached rows) is unwritable rather than merely avoided.
|
|
13
|
+
*/
|
|
14
|
+
import type { ListingPageParams } from "../api/types.js";
|
|
15
|
+
/** The normalized page cursor a list read is keyed on. */
|
|
16
|
+
export interface ListingPageKey {
|
|
17
|
+
readonly anchor: string | null;
|
|
18
|
+
readonly direction: string | null;
|
|
19
|
+
readonly limit: number | null;
|
|
20
|
+
}
|
|
21
|
+
/** Normalize a page's parameters into its key. Exported because a test that
|
|
22
|
+
* asserts "these two states hit one cache entry" must build the key the same
|
|
23
|
+
* way the hook does. */
|
|
24
|
+
export declare function pageKey(params?: ListingPageParams): ListingPageKey;
|
|
25
|
+
export declare const listingsQueryKeys: {
|
|
26
|
+
readonly all: readonly ["listings"];
|
|
27
|
+
/** The published card list. */
|
|
28
|
+
list(page: ListingPageKey): readonly ["listings", "list", ListingPageKey];
|
|
29
|
+
/** One listing in full. */
|
|
30
|
+
detail(id: number): readonly ["listings", "detail", number];
|
|
31
|
+
/** The AllowAny status probe — a SEPARATE entry from the detail on purpose:
|
|
32
|
+
* it answers for a soft-deleted listing the detail 404s on, so caching them
|
|
33
|
+
* together would let a 404 evict the one read that can explain it. */
|
|
34
|
+
status(id: number): readonly ["listings", "status", number];
|
|
35
|
+
/** The dashboard's three counts. */
|
|
36
|
+
myCounters(): readonly ["listings", "my", "counters"];
|
|
37
|
+
/** The caller's favourites, one keyset page. */
|
|
38
|
+
myFavorites(page: ListingPageKey): readonly ["listings", "my", "favorites", ListingPageKey];
|
|
39
|
+
/** The owner's own rows, per tab — see `headless/MyListings.tsx` for why
|
|
40
|
+
* this read goes through an injected source. */
|
|
41
|
+
mine(tab: string, page: ListingPageKey): readonly ["listings", "my", "listings", string, ListingPageKey];
|
|
42
|
+
/** `GET /{pk}/validate-draft/` — the dry run of a publish. */
|
|
43
|
+
validateDraft(id: number): readonly ["listings", "validate-draft", number];
|
|
44
|
+
/** Every favourites PAGE, for an invalidation after a toggle — the cursor
|
|
45
|
+
* is not known at the write, so the prefix is what a write can target. */
|
|
46
|
+
allFavorites(): readonly ["listings", "my", "favorites"];
|
|
47
|
+
/** Every published-card PAGE, same reason. */
|
|
48
|
+
allLists(): readonly ["listings", "list"];
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=queryKeys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryKeys.d.ts","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;wBAEwB;AACxB,wBAAgB,OAAO,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAMlE;AAID,eAAO,MAAM,iBAAiB,EAAE;IAC9B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACpC,+BAA+B;IAC/B,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1E,2BAA2B;IAC3B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D;;0EAEsE;IACtE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D,oCAAoC;IACpC,UAAU,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IACtD,gDAAgD;IAChD,WAAW,CACT,IAAI,EAAE,cAAc,GACnB,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAC5D;oDACgD;IAChD,IAAI,CACF,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,cAAc,GACnB,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IACnE,8DAA8D;IAC9D,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC3E;8EAC0E;IAC1E,YAAY,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IACzD,8CAA8C;IAC9C,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;CAY3C,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Normalize a page's parameters into its key. Exported because a test that
|
|
2
|
+
* asserts "these two states hit one cache entry" must build the key the same
|
|
3
|
+
* way the hook does. */
|
|
4
|
+
export function pageKey(params) {
|
|
5
|
+
return {
|
|
6
|
+
anchor: params?.anchor ?? null,
|
|
7
|
+
direction: params?.direction ?? null,
|
|
8
|
+
limit: params?.limit ?? null,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const ROOT = "listings";
|
|
12
|
+
export const listingsQueryKeys = {
|
|
13
|
+
all: [ROOT],
|
|
14
|
+
list: (page) => [ROOT, "list", page],
|
|
15
|
+
detail: (id) => [ROOT, "detail", id],
|
|
16
|
+
status: (id) => [ROOT, "status", id],
|
|
17
|
+
myCounters: () => [ROOT, "my", "counters"],
|
|
18
|
+
myFavorites: (page) => [ROOT, "my", "favorites", page],
|
|
19
|
+
mine: (tab, page) => [ROOT, "my", "listings", tab, page],
|
|
20
|
+
validateDraft: (id) => [ROOT, "validate-draft", id],
|
|
21
|
+
allFavorites: () => [ROOT, "my", "favorites"],
|
|
22
|
+
allLists: () => [ROOT, "list"],
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=queryKeys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAsBA;;wBAEwB;AACxB,MAAM,UAAU,OAAO,CAAC,MAA0B;IAChD,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI;QAC9B,SAAS,EAAE,MAAM,EAAE,SAAS,IAAI,IAAI;QACpC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,IAAI,GAAG,UAAmB,CAAC;AAEjC,MAAM,CAAC,MAAM,iBAAiB,GA6B1B;IACF,GAAG,EAAE,CAAC,IAAI,CAAC;IACX,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC;IACpC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC;IACtD,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC;IACxD,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC;IACnD,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC;IAC7C,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;CAC/B,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CreateModuleRuntimeOptions, ModuleRuntime } from "@stapel/core";
|
|
2
|
+
import type { StapelImage } from "@stapel/image";
|
|
3
|
+
import type { ListingsApi } from "../api/listingsApi.js";
|
|
4
|
+
import type { ListingDraftLimits } from "./validation.js";
|
|
5
|
+
/**
|
|
6
|
+
* Turn a stored image reference into something renderable.
|
|
7
|
+
*
|
|
8
|
+
* ── Why this is a seam and not a URL builder ───────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* `Listing.images` is a list of OPAQUE CDN references (`<type>/<hash>`), and
|
|
11
|
+
* the listings contract offers no way to resolve one: there is no
|
|
12
|
+
* `GET /listings/{pk}/images/`, and stapel-cdn's `file/exists/` is
|
|
13
|
+
* owner-scoped (`uploaded_by=request.user`), so `@stapel/cdn-react`'s
|
|
14
|
+
* `useCdnRef` can resolve a person's OWN draft and can never render a
|
|
15
|
+
* stranger's gallery — it says so itself. There is therefore no public
|
|
16
|
+
* read-by-reference anywhere in this fleet's contracts today.
|
|
17
|
+
*
|
|
18
|
+
* The honest answer is a seam, not an invented URL convention: a deployment
|
|
19
|
+
* knows where its CDN serves from, and hands that knowledge in once. A pair
|
|
20
|
+
* that guessed `${cdnBase}/${ref}` would be writing a contract nobody agreed
|
|
21
|
+
* to, and it would break the first host that puts a signature on the path.
|
|
22
|
+
*
|
|
23
|
+
* Returning a full `StapelImage` rather than a string is what buys the
|
|
24
|
+
* variant ladder: with `variants` populated, `@stapel/image`'s `<Image>`
|
|
25
|
+
* measures the slot and picks a tier; with only `url`, it shows that one.
|
|
26
|
+
* Both are supported, and a resolver that has nothing for a reference returns
|
|
27
|
+
* `undefined` — the skin then says "photo unavailable" instead of drawing a
|
|
28
|
+
* broken `<img>`. Recorded as an upstream gap in MODULE.md.
|
|
29
|
+
*/
|
|
30
|
+
export type ListingImageResolver = (ref: string) => StapelImage | undefined;
|
|
31
|
+
export interface CreateListingsRuntimeOptions extends CreateModuleRuntimeOptions {
|
|
32
|
+
/** See {@link ListingImageResolver}. Absent = no gallery is drawn, and the
|
|
33
|
+
* skin names the reason. */
|
|
34
|
+
readonly resolveImage?: ListingImageResolver;
|
|
35
|
+
/**
|
|
36
|
+
* The deployment's own ceilings, mirrored client-side. Partial: only the
|
|
37
|
+
* ones that differ from `stapel_listings/conf.py`'s library defaults.
|
|
38
|
+
*/
|
|
39
|
+
readonly limits?: Partial<ListingDraftLimits>;
|
|
40
|
+
/** Currency a new draft starts in. Owner verdict F6: RUB. */
|
|
41
|
+
readonly currency?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The wired listings runtime — core's `ModuleRuntime` bound to this pair's
|
|
45
|
+
* API (slim wave §21/S2), plus the three pieces of deployment knowledge the
|
|
46
|
+
* pair cannot derive: how to render a reference, where the ceilings are, and
|
|
47
|
+
* what currency the shop trades in.
|
|
48
|
+
*
|
|
49
|
+
* ```tsx
|
|
50
|
+
* const runtime = createListingsRuntime({
|
|
51
|
+
* baseUrl: "/listings/api/v1/",
|
|
52
|
+
* resolveImage: (ref) => myCdn.describe(ref),
|
|
53
|
+
* });
|
|
54
|
+
* <ListingsProvider runtime={runtime}>…</ListingsProvider>
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* NOT session-gated as a whole. The public reads (`retrieve`, `status`,
|
|
58
|
+
* `list`) are `IsAuthenticatedOrReadOnly` and answer a visitor who will never
|
|
59
|
+
* sign in; the owner reads and every write are `IsAuthenticated` and the
|
|
60
|
+
* hooks that call them gate on `useActiveSessionReady` individually. Gating
|
|
61
|
+
* the module would make a shop window wait for a login bootstrap.
|
|
62
|
+
*/
|
|
63
|
+
export interface ListingsRuntime extends ModuleRuntime<ListingsApi> {
|
|
64
|
+
readonly resolveImage: ListingImageResolver | undefined;
|
|
65
|
+
readonly limits: ListingDraftLimits;
|
|
66
|
+
readonly currency: string;
|
|
67
|
+
}
|
|
68
|
+
export declare function createListingsRuntime(options: CreateListingsRuntimeOptions): ListingsRuntime;
|
|
69
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;AAE5E,MAAM,WAAW,4BACf,SAAQ,0BAA0B;IAClC;gCAC4B;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC;IAC7C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC9C,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa,CAAC,WAAW,CAAC;IACjE,QAAQ,CAAC,YAAY,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,eAAe,CAQjB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createModuleRuntime } from "@stapel/core";
|
|
2
|
+
import { createListingsApi } from "../api/listingsApi.js";
|
|
3
|
+
import { DEFAULT_LISTING_CURRENCY } from "../api/types.js";
|
|
4
|
+
import { DEFAULT_DRAFT_LIMITS } from "./validation.js";
|
|
5
|
+
export function createListingsRuntime(options) {
|
|
6
|
+
const base = createModuleRuntime((client) => createListingsApi(client), options);
|
|
7
|
+
return {
|
|
8
|
+
...base,
|
|
9
|
+
resolveImage: options.resolveImage,
|
|
10
|
+
limits: { ...DEFAULT_DRAFT_LIMITS, ...options.limits },
|
|
11
|
+
currency: options.currency ?? DEFAULT_LISTING_CURRENCY,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAsEvD,MAAM,UAAU,qBAAqB,CACnC,OAAqC;IAErC,MAAM,IAAI,GAAG,mBAAmB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IACjF,OAAO;QACL,GAAG,IAAI;QACP,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,MAAM,EAAE,EAAE,GAAG,oBAAoB,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE;QACtD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,wBAAwB;KACvD,CAAC;AACJ,CAAC"}
|