@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.
Files changed (225) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/MODULE.md +191 -0
  3. package/README.md +169 -0
  4. package/dist/api/generated/schema.d.ts +1253 -0
  5. package/dist/api/generated/schema.d.ts.map +1 -0
  6. package/dist/api/generated/schema.js +2 -0
  7. package/dist/api/generated/schema.js.map +1 -0
  8. package/dist/api/listingsApi.d.ts +145 -0
  9. package/dist/api/listingsApi.d.ts.map +1 -0
  10. package/dist/api/listingsApi.js +38 -0
  11. package/dist/api/listingsApi.js.map +1 -0
  12. package/dist/api/types.d.ts +156 -0
  13. package/dist/api/types.d.ts.map +1 -0
  14. package/dist/api/types.js +37 -0
  15. package/dist/api/types.js.map +1 -0
  16. package/dist/default/ErrorAlert.d.ts +9 -0
  17. package/dist/default/ErrorAlert.d.ts.map +1 -0
  18. package/dist/default/ErrorAlert.js +27 -0
  19. package/dist/default/ErrorAlert.js.map +1 -0
  20. package/dist/default/FavoritesPane.d.ts +18 -0
  21. package/dist/default/FavoritesPane.d.ts.map +1 -0
  22. package/dist/default/FavoritesPane.js +28 -0
  23. package/dist/default/FavoritesPane.js.map +1 -0
  24. package/dist/default/ListingCard.d.ts +42 -0
  25. package/dist/default/ListingCard.d.ts.map +1 -0
  26. package/dist/default/ListingCard.js +37 -0
  27. package/dist/default/ListingCard.js.map +1 -0
  28. package/dist/default/ListingComposerPage.d.ts +46 -0
  29. package/dist/default/ListingComposerPage.d.ts.map +1 -0
  30. package/dist/default/ListingComposerPage.js +99 -0
  31. package/dist/default/ListingComposerPage.js.map +1 -0
  32. package/dist/default/ListingDetailPane.d.ts +33 -0
  33. package/dist/default/ListingDetailPane.d.ts.map +1 -0
  34. package/dist/default/ListingDetailPane.js +48 -0
  35. package/dist/default/ListingDetailPane.js.map +1 -0
  36. package/dist/default/ListingPhoto.d.ts +25 -0
  37. package/dist/default/ListingPhoto.d.ts.map +1 -0
  38. package/dist/default/ListingPhoto.js +20 -0
  39. package/dist/default/ListingPhoto.js.map +1 -0
  40. package/dist/default/MyListingsPane.d.ts +25 -0
  41. package/dist/default/MyListingsPane.d.ts.map +1 -0
  42. package/dist/default/MyListingsPane.js +84 -0
  43. package/dist/default/MyListingsPane.js.map +1 -0
  44. package/dist/default/StatusTags.d.ts +31 -0
  45. package/dist/default/StatusTags.d.ts.map +1 -0
  46. package/dist/default/StatusTags.js +40 -0
  47. package/dist/default/StatusTags.js.map +1 -0
  48. package/dist/default/icons.d.ts +16 -0
  49. package/dist/default/icons.d.ts.map +1 -0
  50. package/dist/default/icons.js +5 -0
  51. package/dist/default/icons.js.map +1 -0
  52. package/dist/default/index.d.ts +35 -0
  53. package/dist/default/index.d.ts.map +1 -0
  54. package/dist/default/index.js +26 -0
  55. package/dist/default/index.js.map +1 -0
  56. package/dist/default/theme.d.ts +25 -0
  57. package/dist/default/theme.d.ts.map +1 -0
  58. package/dist/default/theme.js +40 -0
  59. package/dist/default/theme.js.map +1 -0
  60. package/dist/default/types.d.ts +12 -0
  61. package/dist/default/types.d.ts.map +1 -0
  62. package/dist/default/types.js +2 -0
  63. package/dist/default/types.js.map +1 -0
  64. package/dist/flows/registry.d.ts +35 -0
  65. package/dist/flows/registry.d.ts.map +1 -0
  66. package/dist/flows/registry.js +33 -0
  67. package/dist/flows/registry.js.map +1 -0
  68. package/dist/headless/Favorites.d.ts +52 -0
  69. package/dist/headless/Favorites.d.ts.map +1 -0
  70. package/dist/headless/Favorites.js +78 -0
  71. package/dist/headless/Favorites.js.map +1 -0
  72. package/dist/headless/ListingActions.d.ts +30 -0
  73. package/dist/headless/ListingActions.d.ts.map +1 -0
  74. package/dist/headless/ListingActions.js +54 -0
  75. package/dist/headless/ListingActions.js.map +1 -0
  76. package/dist/headless/ListingComposer.d.ts +133 -0
  77. package/dist/headless/ListingComposer.d.ts.map +1 -0
  78. package/dist/headless/ListingComposer.js +246 -0
  79. package/dist/headless/ListingComposer.js.map +1 -0
  80. package/dist/headless/ListingDetail.d.ts +81 -0
  81. package/dist/headless/ListingDetail.d.ts.map +1 -0
  82. package/dist/headless/ListingDetail.js +101 -0
  83. package/dist/headless/ListingDetail.js.map +1 -0
  84. package/dist/headless/ListingsProvider.d.ts +17 -0
  85. package/dist/headless/ListingsProvider.d.ts.map +1 -0
  86. package/dist/headless/ListingsProvider.js +16 -0
  87. package/dist/headless/ListingsProvider.js.map +1 -0
  88. package/dist/headless/MyListings.d.ts +44 -0
  89. package/dist/headless/MyListings.d.ts.map +1 -0
  90. package/dist/headless/MyListings.js +82 -0
  91. package/dist/headless/MyListings.js.map +1 -0
  92. package/dist/headless/useMandateGate.d.ts +28 -0
  93. package/dist/headless/useMandateGate.d.ts.map +1 -0
  94. package/dist/headless/useMandateGate.js +39 -0
  95. package/dist/headless/useMandateGate.js.map +1 -0
  96. package/dist/i18n/errorsMap.d.ts +34 -0
  97. package/dist/i18n/errorsMap.d.ts.map +1 -0
  98. package/dist/i18n/errorsMap.js +45 -0
  99. package/dist/i18n/errorsMap.js.map +1 -0
  100. package/dist/i18n/es.d.ts +21 -0
  101. package/dist/i18n/es.d.ts.map +1 -0
  102. package/dist/i18n/es.js +146 -0
  103. package/dist/i18n/es.js.map +1 -0
  104. package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
  105. package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
  106. package/dist/i18n/generated/errors.es.gen.js +58 -0
  107. package/dist/i18n/generated/errors.es.gen.js.map +1 -0
  108. package/dist/i18n/generated/errors.gen.d.ts +413 -0
  109. package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
  110. package/dist/i18n/generated/errors.gen.js +210 -0
  111. package/dist/i18n/generated/errors.gen.js.map +1 -0
  112. package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
  113. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
  114. package/dist/i18n/generated/errors.ru.gen.js +58 -0
  115. package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
  116. package/dist/i18n/keys.d.ts +170 -0
  117. package/dist/i18n/keys.d.ts.map +1 -0
  118. package/dist/i18n/keys.js +295 -0
  119. package/dist/i18n/keys.js.map +1 -0
  120. package/dist/i18n/ru.d.ts +37 -0
  121. package/dist/i18n/ru.d.ts.map +1 -0
  122. package/dist/i18n/ru.js +162 -0
  123. package/dist/i18n/ru.js.map +1 -0
  124. package/dist/index.d.ts +103 -0
  125. package/dist/index.d.ts.map +1 -0
  126. package/dist/index.js +98 -0
  127. package/dist/index.js.map +1 -0
  128. package/dist/model/context.d.ts +11 -0
  129. package/dist/model/context.d.ts.map +1 -0
  130. package/dist/model/context.js +16 -0
  131. package/dist/model/context.js.map +1 -0
  132. package/dist/model/draft.d.ts +138 -0
  133. package/dist/model/draft.d.ts.map +1 -0
  134. package/dist/model/draft.js +192 -0
  135. package/dist/model/draft.js.map +1 -0
  136. package/dist/model/features.d.ts +69 -0
  137. package/dist/model/features.d.ts.map +1 -0
  138. package/dist/model/features.js +73 -0
  139. package/dist/model/features.js.map +1 -0
  140. package/dist/model/mineSource.d.ts +47 -0
  141. package/dist/model/mineSource.d.ts.map +1 -0
  142. package/dist/model/mineSource.js +45 -0
  143. package/dist/model/mineSource.js.map +1 -0
  144. package/dist/model/mutations.d.ts +60 -0
  145. package/dist/model/mutations.d.ts.map +1 -0
  146. package/dist/model/mutations.js +165 -0
  147. package/dist/model/mutations.js.map +1 -0
  148. package/dist/model/queries.d.ts +75 -0
  149. package/dist/model/queries.d.ts.map +1 -0
  150. package/dist/model/queries.js +117 -0
  151. package/dist/model/queries.js.map +1 -0
  152. package/dist/model/queryKeys.d.ts +50 -0
  153. package/dist/model/queryKeys.d.ts.map +1 -0
  154. package/dist/model/queryKeys.js +24 -0
  155. package/dist/model/queryKeys.js.map +1 -0
  156. package/dist/model/runtime.d.ts +69 -0
  157. package/dist/model/runtime.d.ts.map +1 -0
  158. package/dist/model/runtime.js +14 -0
  159. package/dist/model/runtime.js.map +1 -0
  160. package/dist/model/status.d.ts +117 -0
  161. package/dist/model/status.d.ts.map +1 -0
  162. package/dist/model/status.js +164 -0
  163. package/dist/model/status.js.map +1 -0
  164. package/dist/model/transitions.d.ts +41 -0
  165. package/dist/model/transitions.d.ts.map +1 -0
  166. package/dist/model/transitions.js +25 -0
  167. package/dist/model/transitions.js.map +1 -0
  168. package/dist/model/validation.d.ts +135 -0
  169. package/dist/model/validation.d.ts.map +1 -0
  170. package/dist/model/validation.js +191 -0
  171. package/dist/model/validation.js.map +1 -0
  172. package/dist/nav/manifest.d.ts +46 -0
  173. package/dist/nav/manifest.d.ts.map +1 -0
  174. package/dist/nav/manifest.js +56 -0
  175. package/dist/nav/manifest.js.map +1 -0
  176. package/llms.txt +118 -0
  177. package/manifest.json +1044 -0
  178. package/nav-manifest.json +84 -0
  179. package/package.json +121 -0
  180. package/src/analytics/generated/events.json +7 -0
  181. package/src/api/generated/schema.ts +1255 -0
  182. package/src/api/listingsApi.ts +237 -0
  183. package/src/api/types.ts +193 -0
  184. package/src/default/ErrorAlert.tsx +46 -0
  185. package/src/default/FavoritesPane.tsx +123 -0
  186. package/src/default/ListingCard.tsx +178 -0
  187. package/src/default/ListingComposerPage.tsx +466 -0
  188. package/src/default/ListingDetailPane.tsx +271 -0
  189. package/src/default/ListingPhoto.tsx +62 -0
  190. package/src/default/MyListingsPane.tsx +333 -0
  191. package/src/default/StatusTags.tsx +86 -0
  192. package/src/default/icons.tsx +31 -0
  193. package/src/default/index.ts +35 -0
  194. package/src/default/theme.tsx +67 -0
  195. package/src/default/types.ts +12 -0
  196. package/src/flows/registry.ts +45 -0
  197. package/src/headless/Favorites.tsx +137 -0
  198. package/src/headless/ListingActions.tsx +101 -0
  199. package/src/headless/ListingComposer.tsx +476 -0
  200. package/src/headless/ListingDetail.tsx +231 -0
  201. package/src/headless/ListingsProvider.tsx +20 -0
  202. package/src/headless/MyListings.tsx +148 -0
  203. package/src/headless/useMandateGate.ts +40 -0
  204. package/src/i18n/errorsMap.ts +62 -0
  205. package/src/i18n/es.ts +198 -0
  206. package/src/i18n/generated/errors.es.gen.ts +64 -0
  207. package/src/i18n/generated/errors.gen.ts +241 -0
  208. package/src/i18n/generated/errors.json +499 -0
  209. package/src/i18n/generated/errors.ru.gen.ts +64 -0
  210. package/src/i18n/keys.ts +356 -0
  211. package/src/i18n/ru.ts +215 -0
  212. package/src/index.ts +260 -0
  213. package/src/model/context.tsx +28 -0
  214. package/src/model/draft.ts +307 -0
  215. package/src/model/features.ts +122 -0
  216. package/src/model/mineSource.ts +55 -0
  217. package/src/model/mutations.ts +224 -0
  218. package/src/model/queries.ts +150 -0
  219. package/src/model/queryKeys.ts +76 -0
  220. package/src/model/runtime.ts +87 -0
  221. package/src/model/status.ts +279 -0
  222. package/src/model/transitions.ts +62 -0
  223. package/src/model/validation.ts +271 -0
  224. package/src/nav/manifest.ts +97 -0
  225. package/tsconfig.json +26 -0
@@ -0,0 +1,133 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { ActionAvailability, FlowError, LoadState } from "@stapel/core";
3
+ import type { FeatureDef } from "@stapel/attributes-react";
4
+ import type { ListingDetail as ListingDetailData, ListingDraft, PublishResponse } from "../api/types.js";
5
+ import type { ListingDraftValues, ListingLocation } from "../model/draft.js";
6
+ import type { PublishRefusal } from "../model/validation.js";
7
+ /**
8
+ * The submission flow — the pair's long pole, and the place four contracts
9
+ * meet: the draft twin (listings), the value editors (attributes), the upload
10
+ * queue (cdn) and the category schema (categories).
11
+ *
12
+ * ── The steps, and why they are not a `@flow_step` funnel ──────────────────
13
+ *
14
+ * pick a category → create the draft row → save into it → publish
15
+ *
16
+ * The server does not sequence these (see `flows/registry.ts`). What DOES
17
+ * sequence them is here, and its stages are named so analytics can follow the
18
+ * funnel without the backend having declared one.
19
+ *
20
+ * ── The three seams, and why none of them is an import ─────────────────────
21
+ *
22
+ * `@stapel/listings-react`, `@stapel/cdn-react` and `@stapel/categories-react`
23
+ * are all L2 pairs, and L2 pairs never import each other (the monorepo README
24
+ * states the direction). So:
25
+ *
26
+ * - **the category schema** arrives as `features` — a plain
27
+ * `readonly FeatureDef[]`. The container gets it from
28
+ * `@stapel/categories-react`'s `useCategoryFeatures`;
29
+ * - **the gallery** arrives as {@link ListingImagesBag} — two members,
30
+ * satisfied STRUCTURALLY by `useUploadQueue()`'s bag from
31
+ * `@stapel/cdn-react`. `bag.refs` IS the value of `images_draft` (same
32
+ * order, first tile is the cover) and `bag.settled` is the submit gate
33
+ * that stops a publish while photos are in flight. That pair wrote its bag
34
+ * to this contract on purpose (its §13.6 note 9);
35
+ * - **the value editors** come from `@stapel/attributes-react`, which is L0
36
+ * and therefore a legitimate dependency. Only `editorTypes` crosses back:
37
+ * the headless half must judge renderability without importing a skin, so
38
+ * the skin's `BUILTIN_VALUE_EDITOR_TYPES` is passed IN.
39
+ *
40
+ * ── Reopening a draft is NOT possible on stapel-listings 0.6.0 ─────────────
41
+ *
42
+ * No read returns the `*_draft` twin: `GET /{pk}/` serializes the PUBLISHED
43
+ * fields (`ListingDetailSerializer`), and `ListingDraftSerializer` appears
44
+ * only as the RESPONSE of create / save-draft. So editing a live listing
45
+ * works completely — the composer seeds from the published half, which is
46
+ * exactly what the person sees on the page — while a draft abandoned and
47
+ * reopened later comes back empty. The composer says so
48
+ * (`draftNotReadable`) instead of showing a blank form as if nothing had been
49
+ * typed. Upstream ask, recorded in MODULE.md: put the draft twin on the
50
+ * retrieve, or add `GET /{pk}/draft/`.
51
+ */
52
+ /** The two members of `@stapel/cdn-react`'s upload bag this composer needs.
53
+ * Declared structurally so the dependency stays a wiring decision. */
54
+ export interface ListingImagesBag {
55
+ /** Settled CDN references in display order — the value of `images_draft`. */
56
+ readonly refs: readonly string[];
57
+ /** Available when every pick has a reference; blocked WITH the reason
58
+ * (still uploading / one failed) otherwise. */
59
+ readonly settled: ActionAvailability;
60
+ }
61
+ /** Where the submission has got to. Named so a funnel can be measured. */
62
+ export type ComposeStage = "choosing_category" | "editing" | "saving" | "publishing" | "published";
63
+ /** What a successful publish turned out to be — the 0.5.0 distinction, taken
64
+ * from the server's own answer rather than guessed from what we sent. */
65
+ export type PublishOutcome = "submitted_for_review" | "live_edit_under_review";
66
+ export interface UseListingComposerOptions {
67
+ /** An existing listing to edit. Absent: a new one, whose row is created on
68
+ * the first save. */
69
+ readonly listingId?: number;
70
+ /** The chosen category's features. Empty while none is chosen. */
71
+ readonly features: readonly FeatureDef[];
72
+ /** The schema read is in flight. Blocks the submit with its own reason —
73
+ * publishing against a half-known schema is how a mandatory attribute gets
74
+ * skipped. */
75
+ readonly featuresLoading?: boolean;
76
+ /** The schema read failed. Same block, different sentence. */
77
+ readonly featuresError?: unknown;
78
+ /** Value types the rendering half can draw — pass
79
+ * `BUILTIN_VALUE_EDITOR_TYPES` from `@stapel/attributes-react/default`. */
80
+ readonly editorTypes?: readonly string[];
81
+ /** The upload queue. Absent: no gallery, and `images` is set through
82
+ * `setValue` by whatever the host uses instead. */
83
+ readonly images?: ListingImagesBag;
84
+ /** Seed for a brand-new draft (a category preselected from the URL, say). */
85
+ readonly initialValues?: Partial<ListingDraftValues>;
86
+ onDraftCreated?: (draft: ListingDraft) => void;
87
+ onPublished?: (response: PublishResponse, outcome: PublishOutcome) => void;
88
+ }
89
+ export interface ListingComposerBag {
90
+ readonly stage: ComposeStage;
91
+ readonly listingId: number | undefined;
92
+ readonly values: ListingDraftValues;
93
+ /** The listing being edited, when there is one. `loading` until it lands —
94
+ a composer must not let someone publish over a listing it has not read. */
95
+ readonly listingState: LoadState<ListingDetailData> | undefined;
96
+ /** Editing something already PUBLISHED: the submit becomes "send changes",
97
+ * and the listing stays visible throughout. */
98
+ readonly isLiveEdit: boolean;
99
+ /** The reopened draft came back empty because no read returns the draft
100
+ * twin — see this module's header. */
101
+ readonly draftNotReadable: boolean;
102
+ setValue<K extends keyof ListingDraftValues>(key: K, value: ListingDraftValues[K]): void;
103
+ setLocation(location: ListingLocation): void;
104
+ setFeature(slug: string, value: unknown): void;
105
+ /** Changing category keeps the answers the new schema also asks for. */
106
+ setCategory(categoryId: string): void;
107
+ /** Slugs cleared by the last category change — named, not silently lost. */
108
+ readonly droppedOnCategoryChange: readonly string[];
109
+ /** The client mirror, always current. */
110
+ readonly mirror: Readonly<Record<string, FlowError>>;
111
+ /** What a control should actually show: the mirror once the person has
112
+ * tried to submit, overlaid by the server's verdict when there is one. */
113
+ readonly fieldErrors: Readonly<Record<string, FlowError>>;
114
+ /** The server's last publish refusal, unrouted, for a summary line. */
115
+ readonly refusal: PublishRefusal | undefined;
116
+ /** Value types this build cannot draw — the fact, from attributes-react. */
117
+ readonly unsupported: readonly string[];
118
+ readonly saveGate: ActionAvailability;
119
+ readonly publishGate: ActionAvailability;
120
+ save(): void;
121
+ publish(): void;
122
+ readonly saving: boolean;
123
+ readonly publishing: boolean;
124
+ readonly saved: boolean;
125
+ readonly saveError: unknown;
126
+ readonly outcome: PublishOutcome | undefined;
127
+ }
128
+ export declare function useListingComposer(options: UseListingComposerOptions): ListingComposerBag;
129
+ /** Renderless: the bag, handed to a render prop. */
130
+ export declare function ListingComposer(props: UseListingComposerOptions & {
131
+ children: (bag: ListingComposerBag) => ReactNode;
132
+ }): ReactElement;
133
+ //# sourceMappingURL=ListingComposer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListingComposer.d.ts","sourceRoot":"","sources":["../../src/headless/ListingComposer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AASrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,0BAA0B,CAAC;AAExE,OAAO,KAAK,EACV,aAAa,IAAI,iBAAiB,EAClC,YAAY,EACZ,eAAe,EAChB,MAAM,iBAAiB,CAAC;AAgBzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAI7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAI7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH;sEACsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;mDAC+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;CACtC;AAED,0EAA0E;AAC1E,MAAM,MAAM,YAAY,GACpB,mBAAmB,GACnB,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,WAAW,CAAC;AAEhB;yEACyE;AACzE,MAAM,MAAM,cAAc,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;AAE/E,MAAM,WAAW,yBAAyB;IACxC;yBACqB;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;IACzC;;kBAEc;IACd,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;+EAC2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;uDACmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;CAC5E;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC;8EAC0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,iBAAiB,CAAC,GAAG,SAAS,CAAC;IAChE;mDAC+C;IAC/C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B;0CACsC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IAEnC,QAAQ,CAAC,CAAC,SAAS,MAAM,kBAAkB,EACzC,GAAG,EAAE,CAAC,EACN,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC3B,IAAI,CAAC;IACR,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,wEAAwE;IACxE,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,4EAA4E;IAC5E,QAAQ,CAAC,uBAAuB,EAAE,SAAS,MAAM,EAAE,CAAC;IAEpD,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACrD;8EAC0E;IAC1E,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC1D,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,CAAC;IAC7C,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAExC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;IACzC,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,CAAC;CAC9C;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GACjC,kBAAkB,CA4RpB;AAED,oDAAoD;AACpD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,yBAAyB,GAAG;IACjC,QAAQ,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAClD,GACA,YAAY,CAGd"}
@@ -0,0 +1,246 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { actionAvailable, actionBlocked, firstBlock, loadFailed, loadLoading, loadReady, } from "@stapel/core";
4
+ import { toFeaturesDto, unsupportedTypes } from "@stapel/attributes-react";
5
+ import { useListingsRuntime } from "../model/context.js";
6
+ import { useListing } from "../model/queries.js";
7
+ import { useCreateDraft, usePublishListing, useSaveDraft, } from "../model/mutations.js";
8
+ import { createDraftBody, draftPatchFromValues, draftValuesFromDetail, droppedFeatureSlugs, emptyDraftValues, retainKnownFeatureValues, } from "../model/draft.js";
9
+ import { asFeatureDaoList } from "../model/features.js";
10
+ import { featuresDtoFromDaoList } from "../model/features.js";
11
+ import { mirrorDraft, publishRefusal } from "../model/validation.js";
12
+ import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
13
+ import { useMandateGate } from "./useMandateGate.js";
14
+ export function useListingComposer(options) {
15
+ const runtime = useListingsRuntime();
16
+ const mandate = useMandateGate();
17
+ const [listingId, setListingId] = useState(options.listingId);
18
+ const [values, setValues] = useState(() => ({
19
+ ...emptyDraftValues({ currency: runtime.currency }),
20
+ ...options.initialValues,
21
+ }));
22
+ const [showErrors, setShowErrors] = useState(false);
23
+ const [dropped, setDropped] = useState([]);
24
+ const [refusal, setRefusal] = useState(undefined);
25
+ const [outcome, setOutcome] = useState(undefined);
26
+ const [saved, setSaved] = useState(false);
27
+ const existing = useListing(options.listingId);
28
+ const createDraft = useCreateDraft();
29
+ const saveDraft = useSaveDraft();
30
+ const publishListing = usePublishListing();
31
+ // Seed once from the listing being edited. `seeded` is a ref rather than a
32
+ // dependency guard because re-seeding on a refetch would throw away
33
+ // everything typed since — a background invalidation must never rewrite a
34
+ // form somebody is in the middle of.
35
+ const seeded = useRef(false);
36
+ useEffect(() => {
37
+ if (seeded.current)
38
+ return;
39
+ const detail = existing.data;
40
+ if (detail === undefined)
41
+ return;
42
+ seeded.current = true;
43
+ setListingId(detail.id);
44
+ setValues(draftValuesFromDetail(detail, featuresDtoFromDaoList(asFeatureDaoList(detail.features)), { currency: runtime.currency }));
45
+ }, [existing.data, runtime.currency]);
46
+ // Pruning runs on the SCHEMA arriving, one render after the category
47
+ // changed: `setCategory` cannot prune against features it has not been
48
+ // handed. Gated on the schema being settled, because pruning against a
49
+ // half-loaded (or previous) category's features would delete answers that
50
+ // are perfectly valid for the one now chosen.
51
+ const schemaSettled = options.featuresLoading !== true && options.featuresError === undefined;
52
+ const { features } = options;
53
+ useEffect(() => {
54
+ if (!schemaSettled || features.length === 0)
55
+ return;
56
+ setValues((current) => {
57
+ const gone = droppedFeatureSlugs(current.features, features);
58
+ if (gone.length === 0)
59
+ return current;
60
+ setDropped(gone);
61
+ return {
62
+ ...current,
63
+ features: retainKnownFeatureValues(current.features, features),
64
+ };
65
+ });
66
+ }, [schemaSettled, features]);
67
+ // The gallery is the upload bag's, whenever there is one: two sources of
68
+ // truth for the same list is how a publish sends photos the person removed.
69
+ const images = options.images?.refs ?? values.images;
70
+ const effectiveValues = useMemo(() => ({ ...values, images }), [values, images]);
71
+ const featuresDto = useMemo(() => toFeaturesDto(options.features, effectiveValues.features), [options.features, effectiveValues.features]);
72
+ const mirror = useMemo(() => mirrorDraft(effectiveValues, options.features, featuresDto, runtime.limits), [effectiveValues, options.features, featuresDto, runtime.limits]);
73
+ const unsupported = useMemo(() => unsupportedTypes(options.features, options.editorTypes ?? []), [options.features, options.editorTypes]);
74
+ const isLiveEdit = existing.data?.status === "published";
75
+ const draftNotReadable = existing.data !== undefined &&
76
+ existing.data.status !== "published" &&
77
+ (existing.data.title ?? "").length === 0 &&
78
+ (existing.data.description ?? "").length === 0;
79
+ const fieldErrors = useMemo(() => {
80
+ const shown = showErrors ? mirror : {};
81
+ return refusal?.kind === "invalid_draft"
82
+ ? { ...shown, ...refusal.fieldErrors }
83
+ : shown;
84
+ }, [showErrors, mirror, refusal]);
85
+ const listingState = options.listingId === undefined
86
+ ? undefined
87
+ : existing.status === "error"
88
+ ? loadFailed(existing.error)
89
+ : existing.data !== undefined
90
+ ? loadReady(existing.data)
91
+ : loadLoading();
92
+ const busy = createDraft.isPending || saveDraft.isPending;
93
+ const schemaGate = options.featuresError !== undefined
94
+ ? actionBlocked(LISTINGS_I18N_KEYS.composeBlockedDetailsUnavailable)
95
+ : options.featuresLoading === true
96
+ ? actionBlocked(LISTINGS_I18N_KEYS.composeDetailsLoading)
97
+ : actionAvailable();
98
+ const categoryGate = effectiveValues.categoryId.length > 0
99
+ ? actionAvailable()
100
+ : actionBlocked(LISTINGS_I18N_KEYS.composeBlockedNoCategory);
101
+ const busyGate = busy
102
+ ? actionBlocked(LISTINGS_I18N_KEYS.composeBlockedBusy)
103
+ : actionAvailable();
104
+ const saveGate = firstBlock(mandate, categoryGate, busyGate);
105
+ // The publish gate is the save gate PLUS everything that must be true for a
106
+ // submission to be complete. Order is the order a person would be told:
107
+ // may you act at all → is the form even answerable → are the photos in →
108
+ // is the form itself clean.
109
+ const publishGate = firstBlock(mandate, categoryGate, schemaGate, unsupported.length > 0
110
+ ? actionBlocked(LISTINGS_I18N_KEYS.composeBlockedUnsupportedType, {
111
+ types: unsupported.join(", "),
112
+ })
113
+ : actionAvailable(), options.images?.settled ?? actionAvailable(), busyGate, publishListing.isPending
114
+ ? actionBlocked(LISTINGS_I18N_KEYS.composeBlockedBusy)
115
+ : actionAvailable(), Object.keys(mirror).length > 0
116
+ ? actionBlocked(LISTINGS_I18N_KEYS.composeBlockedMirror)
117
+ : actionAvailable());
118
+ /** Create the row on demand: the id is what every other write needs. */
119
+ const ensureListingId = useCallback(async () => {
120
+ if (listingId !== undefined && listingId > 0)
121
+ return listingId;
122
+ if (effectiveValues.categoryId.length === 0)
123
+ return undefined;
124
+ const draft = await createDraft.mutateAsync(createDraftBody(effectiveValues.categoryId));
125
+ setListingId(draft.id);
126
+ options.onDraftCreated?.(draft);
127
+ return draft.id;
128
+ }, [listingId, effectiveValues.categoryId, createDraft, options]);
129
+ const persist = useCallback(async () => {
130
+ const id = await ensureListingId();
131
+ if (id === undefined)
132
+ return undefined;
133
+ // The id comes from `ensureListingId`, not from this closure's render:
134
+ // creating the row and saving into it happen in ONE gesture, and the
135
+ // state update that records the new id has not committed yet.
136
+ await saveDraft.mutateAsync({
137
+ id,
138
+ body: draftPatchFromValues(effectiveValues, options.features),
139
+ });
140
+ setSaved(true);
141
+ return id;
142
+ }, [ensureListingId, saveDraft, effectiveValues, options.features]);
143
+ const save = useCallback(() => {
144
+ setShowErrors(true);
145
+ if (!saveGate.available)
146
+ return;
147
+ setRefusal(undefined);
148
+ void persist().catch(() => {
149
+ // The mutation's own `error` carries it; swallowing the rejection here
150
+ // only stops an unhandled-rejection warning for a failure the bag
151
+ // already reports.
152
+ });
153
+ }, [saveGate.available, persist]);
154
+ const publish = useCallback(() => {
155
+ setShowErrors(true);
156
+ if (!publishGate.available)
157
+ return;
158
+ setRefusal(undefined);
159
+ void (async () => {
160
+ try {
161
+ // Always save first: publish reads the STORED draft, so publishing
162
+ // without saving would promote whatever was there before the last
163
+ // keystroke — and the person would be told their listing is fine
164
+ // while a field they just fixed is still wrong on the server.
165
+ const id = await persist();
166
+ if (id === undefined)
167
+ return;
168
+ const response = await publishListing.mutateAsync(id);
169
+ const result = response.status === "published"
170
+ ? "live_edit_under_review"
171
+ : "submitted_for_review";
172
+ setOutcome(result);
173
+ options.onPublished?.(response, result);
174
+ }
175
+ catch (thrown) {
176
+ setRefusal(publishRefusal(thrown));
177
+ }
178
+ })();
179
+ }, [publishGate.available, persist, publishListing, options]);
180
+ const stage = outcome !== undefined
181
+ ? "published"
182
+ : publishListing.isPending
183
+ ? "publishing"
184
+ : busy
185
+ ? "saving"
186
+ : effectiveValues.categoryId.length === 0
187
+ ? "choosing_category"
188
+ : "editing";
189
+ return {
190
+ stage,
191
+ listingId,
192
+ values: effectiveValues,
193
+ listingState,
194
+ isLiveEdit,
195
+ draftNotReadable,
196
+ setValue: (key, value) => {
197
+ setSaved(false);
198
+ setValues((current) => ({ ...current, [key]: value }));
199
+ },
200
+ setLocation: (location) => {
201
+ setSaved(false);
202
+ setValues((current) => ({ ...current, location }));
203
+ },
204
+ setFeature: (slug, value) => {
205
+ setSaved(false);
206
+ setValues((current) => ({
207
+ ...current,
208
+ features: { ...current.features, [slug]: value },
209
+ }));
210
+ },
211
+ setCategory: (categoryId) => {
212
+ setSaved(false);
213
+ setValues((current) => {
214
+ if (current.categoryId === categoryId)
215
+ return current;
216
+ // The features of the NEW category are not known yet (the container's
217
+ // schema read has not run), so nothing is pruned here — pruning
218
+ // happens in the effect below, once they arrive. Recording the
219
+ // intent, not guessing the outcome.
220
+ return { ...current, categoryId };
221
+ });
222
+ setDropped([]);
223
+ setRefusal(undefined);
224
+ },
225
+ droppedOnCategoryChange: dropped,
226
+ mirror,
227
+ fieldErrors,
228
+ refusal,
229
+ unsupported,
230
+ saveGate,
231
+ publishGate,
232
+ save,
233
+ publish,
234
+ saving: busy,
235
+ publishing: publishListing.isPending,
236
+ saved,
237
+ saveError: saveDraft.error ?? createDraft.error,
238
+ outcome,
239
+ };
240
+ }
241
+ /** Renderless: the bag, handed to a render prop. */
242
+ export function ListingComposer(props) {
243
+ const bag = useListingComposer(props);
244
+ return _jsx(_Fragment, { children: props.children(bag) });
245
+ }
246
+ //# sourceMappingURL=ListingComposer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListingComposer.js","sourceRoot":"","sources":["../../src/headless/ListingComposer.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { ActionAvailability, LoadState } from "@stapel/core";
3
+ import type { ListingDetail as ListingDetailData, ListingFeatureView, ListingStatusInfo } from "../api/types.js";
4
+ import type { ListingStatusView } from "../model/status.js";
5
+ /**
6
+ * Everything a listing page renders, with the two absences it must be able to
7
+ * tell apart.
8
+ *
9
+ * ── Why a second read runs beside the detail ───────────────────────────────
10
+ *
11
+ * `GET /listings/{pk}/` reads `Listing.objects`, which excludes soft-deleted
12
+ * rows: a removed listing answers 404, and a 404 is also what a made-up id
13
+ * answers. `GET /listings/{pk}/status/` reads `Listing.all_objects` and is
14
+ * `AllowAny`, so it still answers for the removed one. Running both is what
15
+ * turns "there is no listing at this address" and "this listing was removed"
16
+ * into two different sentences — and the second is the one a person following
17
+ * a stale link actually needs.
18
+ *
19
+ * The probe is not a fallback fired after a failure: it runs in parallel, so
20
+ * the page resolves in one round trip rather than two sequential ones.
21
+ *
22
+ * ── Visibility is reported, not assumed ────────────────────────────────────
23
+ *
24
+ * The detail endpoint has NO `published()` filter on its queryset, so a draft
25
+ * and a taken-down listing both answer 200 to anyone holding the id. The bag
26
+ * therefore carries `publiclyVisible` (computed from `status`, the one field
27
+ * that decides it) and `viewerIsOwner`, and a skin says which of the two
28
+ * situations it is in. Rendering a draft as a live shop page would be the
29
+ * pair repeating the server's omission instead of covering it.
30
+ */
31
+ export interface ListingDetailBag {
32
+ readonly id: number;
33
+ readonly state: LoadState<ListingDetailData>;
34
+ /** The status probe, which answers for rows the detail cannot see. */
35
+ readonly statusState: LoadState<ListingStatusInfo>;
36
+ /** True when the detail 404s AND the probe says the row is soft-deleted —
37
+ * i.e. it existed and is gone, rather than never having existed. */
38
+ readonly removed: boolean;
39
+ /** The detail 404s and the probe finds nothing either. */
40
+ readonly notFound: boolean;
41
+ /** Both axes plus the sentence that comes out of the pair of them. */
42
+ readonly status: ListingStatusView | undefined;
43
+ readonly publiclyVisible: boolean;
44
+ /** Whether the person reading this is the owner, when both the probe and
45
+ * the viewer's id are known. `undefined` means "we did not ask" — a public
46
+ * page has no reason to. */
47
+ readonly viewerIsOwner: boolean | undefined;
48
+ /** The ordered specs table. */
49
+ readonly features: readonly ListingFeatureView[];
50
+ /** The `show_at_title` subset — what a heading appends. */
51
+ readonly titleFeatures: readonly ListingFeatureView[];
52
+ /** The `show_as_badge` subset — what a card shows. */
53
+ readonly badgeFeatures: readonly ListingFeatureView[];
54
+ /** Stored rows this build could not key. Reported, never rounded to zero. */
55
+ readonly unreadableFeatures: number;
56
+ /** Ordered CDN references. Resolving one to an image is the runtime's job
57
+ * (`resolveImage`), because no contract in this fleet resolves a
58
+ * stranger's reference — see `model/runtime.ts`. */
59
+ readonly images: readonly string[];
60
+ readonly isFavorited: boolean | undefined;
61
+ /** Saving a favourite needs a mandate; the block says which of the four
62
+ * reasons applies. */
63
+ readonly favoriteGate: ActionAvailability;
64
+ toggleFavorite(): void;
65
+ readonly favoriteInFlight: boolean;
66
+ refetch(): void;
67
+ }
68
+ export interface UseListingDetailOptions {
69
+ /** The reader's own id, when the host knows it — enables `viewerIsOwner`.
70
+ * A uuid string, matching `ListingDetail.owner` / `ListingStatus.owner_id`. */
71
+ readonly viewerId?: string;
72
+ }
73
+ export declare function useListingDetail(id: number, options?: UseListingDetailOptions): ListingDetailBag;
74
+ /** Renderless: the bag, handed to a render prop. Bring your own visuals, or
75
+ * import `<ListingDetailPane>` from `./default`. */
76
+ export declare function ListingDetail(props: {
77
+ id: number;
78
+ viewerId?: string;
79
+ children: (bag: ListingDetailBag) => ReactNode;
80
+ }): ReactElement;
81
+ //# sourceMappingURL=ListingDetail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListingDetail.d.ts","sourceRoot":"","sources":["../../src/headless/ListingDetail.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAUrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EACV,aAAa,IAAI,iBAAiB,EAClC,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAc5D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAC7C,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACnD;wEACoE;IACpE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC;;gCAE4B;IAC5B,QAAQ,CAAC,aAAa,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5C,+BAA+B;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACjD,2DAA2D;IAC3D,QAAQ,CAAC,aAAa,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACtD,sDAAsD;IACtD,QAAQ,CAAC,aAAa,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACtD,6EAA6E;IAC7E,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;wDAEoD;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C;0BACsB;IACtB,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,cAAc,IAAI,IAAI,CAAC;IACvB,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC;mFAC+E;IAC/E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CA4GlB;AAED;oDACoD;AACpD,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,SAAS,CAAC;CAChD,GAAG,YAAY,CAMf"}
@@ -0,0 +1,101 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { actionAvailable, actionBlocked, errorStatus, firstBlock, loadFailed, loadLoading, loadReady, } from "@stapel/core";
4
+ import { useListing, useListingStatus } from "../model/queries.js";
5
+ import { useFavoriteListing } from "../model/mutations.js";
6
+ import { asFeatureDaoList, featuresFromDaoList, unreadableFeatureCount } from "../model/features.js";
7
+ import { listingStatusView } from "../model/status.js";
8
+ import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
9
+ import { useMandateGate } from "./useMandateGate.js";
10
+ /**
11
+ * `images` is `string[] | NULL` on the wire, and the null is the SERVER
12
+ * saying "this listing has no photos" — not this pair saying "we do not
13
+ * know". Whether the read landed at all is carried by `state`, one field
14
+ * over, so collapsing the null here loses nothing: a named constant makes
15
+ * that argument visible where a bare `?? []` would look like the flattening
16
+ * `no-flattened-load-state` bans.
17
+ */
18
+ const NO_IMAGES = [];
19
+ export function useListingDetail(id, options = {}) {
20
+ const detail = useListing(id);
21
+ const probe = useListingStatus(id);
22
+ const favorite = useFavoriteListing();
23
+ const mandate = useMandateGate();
24
+ const state = detail.status === "error"
25
+ ? loadFailed(detail.error)
26
+ : detail.data !== undefined
27
+ ? loadReady(detail.data)
28
+ : loadLoading();
29
+ const statusState = probe.status === "error"
30
+ ? loadFailed(probe.error)
31
+ : probe.data !== undefined
32
+ ? loadReady(probe.data)
33
+ : loadLoading();
34
+ const detailIs404 = detail.status === "error" && errorStatus(detail.error) === 404;
35
+ const removed = detailIs404 && probe.data?.is_deleted === true;
36
+ const notFound = detailIs404 && !removed && probe.status === "error";
37
+ const status = useMemo(() => {
38
+ if (detail.data !== undefined) {
39
+ return listingStatusView(detail.data.status ?? "draft", detail.data.moderation_status ?? "pending");
40
+ }
41
+ // The probe still knows both axes for a row the detail cannot return —
42
+ // `moderation_status` arrives as a bare string there, so it is narrowed
43
+ // by the same table rather than trusted.
44
+ if (probe.data !== undefined) {
45
+ const moderation = probe.data.moderation_status;
46
+ return listingStatusView(probe.data.status, moderation === "approved" ||
47
+ moderation === "rejected" ||
48
+ moderation === "needs_review"
49
+ ? moderation
50
+ : "pending");
51
+ }
52
+ return undefined;
53
+ }, [detail.data, probe.data]);
54
+ const owner = detail.data?.owner ?? probe.data?.owner_id;
55
+ const viewerIsOwner = options.viewerId === undefined || owner === undefined
56
+ ? undefined
57
+ : owner === options.viewerId;
58
+ const features = useMemo(() => featuresFromDaoList(asFeatureDaoList(detail.data?.features)), [detail.data]);
59
+ const titleFeatures = useMemo(() => featuresFromDaoList(asFeatureDaoList(detail.data?.features_title)), [detail.data]);
60
+ const badgeFeatures = useMemo(() => featuresFromDaoList(asFeatureDaoList(detail.data?.features_badges)), [detail.data]);
61
+ const isFavorited = detail.data?.is_favorited ?? undefined;
62
+ const favoriteGate = firstBlock(mandate, favorite.isPending
63
+ ? actionBlocked(LISTINGS_I18N_KEYS.blockedInFlight)
64
+ : actionAvailable(), detail.data === undefined
65
+ ? actionBlocked(LISTINGS_I18N_KEYS.detailLoading)
66
+ : actionAvailable());
67
+ return {
68
+ id,
69
+ state,
70
+ statusState,
71
+ removed,
72
+ notFound,
73
+ status,
74
+ publiclyVisible: status?.lifecycle.publiclyVisible ?? false,
75
+ viewerIsOwner,
76
+ features,
77
+ titleFeatures,
78
+ badgeFeatures,
79
+ unreadableFeatures: unreadableFeatureCount(asFeatureDaoList(detail.data?.features)),
80
+ images: detail.data?.images ?? NO_IMAGES,
81
+ isFavorited,
82
+ favoriteGate,
83
+ toggleFavorite: () => {
84
+ if (!favoriteGate.available)
85
+ return;
86
+ favorite.mutate({ id, favorited: isFavorited !== true });
87
+ },
88
+ favoriteInFlight: favorite.isPending,
89
+ refetch: () => {
90
+ void detail.refetch();
91
+ void probe.refetch();
92
+ },
93
+ };
94
+ }
95
+ /** Renderless: the bag, handed to a render prop. Bring your own visuals, or
96
+ * import `<ListingDetailPane>` from `./default`. */
97
+ export function ListingDetail(props) {
98
+ const bag = useListingDetail(props.id, props.viewerId !== undefined ? { viewerId: props.viewerId } : {});
99
+ return _jsx(_Fragment, { children: props.children(bag) });
100
+ }
101
+ //# sourceMappingURL=ListingDetail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListingDetail.js","sourceRoot":"","sources":["../../src/headless/ListingDetail.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { ListingsRuntime } from "../model/runtime.js";
3
+ /**
4
+ * Renderless provider — puts one wired {@link ListingsRuntime} in context for
5
+ * every hook and headless component in the pair. Bring your own visual shell.
6
+ *
7
+ * ```tsx
8
+ * <ListingsProvider runtime={createListingsRuntime({ baseUrl: "/listings/api/v1/" })}>
9
+ * <YourShopWindow />
10
+ * </ListingsProvider>
11
+ * ```
12
+ */
13
+ export declare function ListingsProvider(props: {
14
+ runtime: ListingsRuntime;
15
+ children: ReactNode;
16
+ }): ReactElement;
17
+ //# sourceMappingURL=ListingsProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListingsProvider.d.ts","sourceRoot":"","sources":["../../src/headless/ListingsProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,YAAY,CAEf"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ModuleProvider } from "../model/context.js";
3
+ /**
4
+ * Renderless provider — puts one wired {@link ListingsRuntime} in context for
5
+ * every hook and headless component in the pair. Bring your own visual shell.
6
+ *
7
+ * ```tsx
8
+ * <ListingsProvider runtime={createListingsRuntime({ baseUrl: "/listings/api/v1/" })}>
9
+ * <YourShopWindow />
10
+ * </ListingsProvider>
11
+ * ```
12
+ */
13
+ export function ListingsProvider(props) {
14
+ return _jsx(ModuleProvider, { runtime: props.runtime, children: props.children });
15
+ }
16
+ //# sourceMappingURL=ListingsProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListingsProvider.js","sourceRoot":"","sources":["../../src/headless/ListingsProvider.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAGhC;IACC,OAAO,KAAC,cAAc,IAAC,OAAO,EAAE,KAAK,CAAC,OAAO,YAAG,KAAK,CAAC,QAAQ,GAAkB,CAAC;AACnF,CAAC"}