@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,192 @@
|
|
|
1
|
+
import { fromFeaturesDto, toFeaturesDto } from "@stapel/attributes-react";
|
|
2
|
+
import { DEFAULT_LISTING_CURRENCY } from "../api/types.js";
|
|
3
|
+
export const EMPTY_LOCATION = {
|
|
4
|
+
locationId: "",
|
|
5
|
+
locationLabel: "",
|
|
6
|
+
lat: null,
|
|
7
|
+
lon: null,
|
|
8
|
+
geohash: "",
|
|
9
|
+
};
|
|
10
|
+
/** A blank draft. `currency` defaults to the storefront's (owner verdict F6 —
|
|
11
|
+
* RUB), overridable per runtime and per call. */
|
|
12
|
+
export function emptyDraftValues(options = {}) {
|
|
13
|
+
return {
|
|
14
|
+
categoryId: options.categoryId ?? "",
|
|
15
|
+
title: "",
|
|
16
|
+
description: "",
|
|
17
|
+
price: "",
|
|
18
|
+
currency: options.currency ?? DEFAULT_LISTING_CURRENCY,
|
|
19
|
+
language: options.language ?? "",
|
|
20
|
+
images: [],
|
|
21
|
+
location: EMPTY_LOCATION,
|
|
22
|
+
features: {},
|
|
23
|
+
countable: false,
|
|
24
|
+
stockQuantity: null,
|
|
25
|
+
autoRepublish: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function text(value) {
|
|
29
|
+
return value ?? "";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A reopened draft: the server's `*_draft` fields → the composer's values.
|
|
33
|
+
*
|
|
34
|
+
* `features_draft` is `null` on a listing nobody has filled in yet, which is
|
|
35
|
+
* not the same as `{}` on the wire but is the same thing to a composer.
|
|
36
|
+
*/
|
|
37
|
+
export function draftValuesFromWire(draft, options = {}) {
|
|
38
|
+
const base = emptyDraftValues(options);
|
|
39
|
+
return {
|
|
40
|
+
categoryId: text(draft.category_id),
|
|
41
|
+
title: text(draft.title_draft),
|
|
42
|
+
description: text(draft.description_draft),
|
|
43
|
+
price: text(draft.price_draft),
|
|
44
|
+
currency: draft.currency !== undefined && draft.currency.length > 0
|
|
45
|
+
? draft.currency
|
|
46
|
+
: base.currency,
|
|
47
|
+
language: text(draft.language),
|
|
48
|
+
images: draft.images_draft ?? [],
|
|
49
|
+
location: {
|
|
50
|
+
locationId: text(draft.location_id_draft),
|
|
51
|
+
locationLabel: text(draft.location_label_draft),
|
|
52
|
+
lat: draft.lat_draft ?? null,
|
|
53
|
+
lon: draft.lon_draft ?? null,
|
|
54
|
+
geohash: text(draft.geohash_draft),
|
|
55
|
+
},
|
|
56
|
+
features: fromFeaturesDto((draft.features_draft ?? {})),
|
|
57
|
+
countable: draft.countable ?? false,
|
|
58
|
+
stockQuantity: draft.stock_quantity ?? null,
|
|
59
|
+
autoRepublish: draft.auto_republish ?? false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Editing something already PUBLISHED: the published half → the composer.
|
|
64
|
+
*
|
|
65
|
+
* A live listing's `*_draft` fields hold whatever was last submitted, which
|
|
66
|
+
* after a publish is the same content — but a listing published before this
|
|
67
|
+
* pair existed, or one whose draft was cleared, would open empty. Seeding
|
|
68
|
+
* from the PUBLISHED fields is what makes "edit" show the listing a person
|
|
69
|
+
* can actually see. Feature values come back from the DAO projection, which
|
|
70
|
+
* is the one place the published values live.
|
|
71
|
+
*/
|
|
72
|
+
export function draftValuesFromDetail(detail, featuresDto, options = {}) {
|
|
73
|
+
const base = emptyDraftValues(options);
|
|
74
|
+
return {
|
|
75
|
+
categoryId: detail.category_id,
|
|
76
|
+
title: text(detail.title),
|
|
77
|
+
description: text(detail.description),
|
|
78
|
+
price: text(detail.price),
|
|
79
|
+
currency: detail.currency !== undefined && detail.currency.length > 0
|
|
80
|
+
? detail.currency
|
|
81
|
+
: base.currency,
|
|
82
|
+
language: text(detail.language),
|
|
83
|
+
images: detail.images ?? [],
|
|
84
|
+
location: {
|
|
85
|
+
locationId: text(detail.location_id),
|
|
86
|
+
locationLabel: text(detail.location_label),
|
|
87
|
+
lat: detail.lat ?? null,
|
|
88
|
+
lon: detail.lon ?? null,
|
|
89
|
+
geohash: text(detail.geohash),
|
|
90
|
+
},
|
|
91
|
+
features: fromFeaturesDto(featuresDto),
|
|
92
|
+
countable: detail.countable ?? false,
|
|
93
|
+
stockQuantity: detail.stock_quantity ?? null,
|
|
94
|
+
autoRepublish: detail.auto_republish ?? false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The ONE boundary between the two descriptions of a feature value.
|
|
99
|
+
*
|
|
100
|
+
* `toFeaturesDto` speaks the engine's own structural shape (`{type: string,
|
|
101
|
+
* value: unknown}` plus whatever a type adds) — it has to, because the type
|
|
102
|
+
* vocabulary is an OPEN registry server-side (`EXTRA_TYPES`, runtime
|
|
103
|
+
* `register_feature_type`), so a deployment can legitimately submit a type
|
|
104
|
+
* this generated union has never heard of. The generated union describes the
|
|
105
|
+
* ten BUILTIN types precisely, which is exactly right for reading and one
|
|
106
|
+
* notch too narrow for writing.
|
|
107
|
+
*
|
|
108
|
+
* Rather than widen the wire type or narrow the engine's, the conversion is
|
|
109
|
+
* named here, once, with the reason attached. A cast scattered at call sites
|
|
110
|
+
* would be the same code with nowhere to write this down.
|
|
111
|
+
*/
|
|
112
|
+
function toWireFeatures(features, values) {
|
|
113
|
+
return toFeaturesDto(features, values);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The composer's values → the `save-draft` body.
|
|
117
|
+
*
|
|
118
|
+
* `features` needs the category schema to be tagged, so it is passed in. A
|
|
119
|
+
* feature the schema no longer declares is dropped by `toFeaturesDto` only if
|
|
120
|
+
* it is absent from `features`; keeping a stale slug in the payload would be
|
|
121
|
+
* refused per-feature by 0.6.0's M-7 rule
|
|
122
|
+
* (`error.400.listing_feature_not_allowed`), which is why
|
|
123
|
+
* {@link retainKnownFeatureValues} prunes on the way in rather than letting
|
|
124
|
+
* the server explain it.
|
|
125
|
+
*/
|
|
126
|
+
export function draftPatchFromValues(values, features) {
|
|
127
|
+
return {
|
|
128
|
+
category_id: values.categoryId,
|
|
129
|
+
title_draft: values.title,
|
|
130
|
+
description_draft: values.description,
|
|
131
|
+
price_draft: values.price.length > 0 ? values.price : null,
|
|
132
|
+
currency: values.currency,
|
|
133
|
+
...(values.language.length > 0 ? { language: values.language } : {}),
|
|
134
|
+
images_draft: [...values.images],
|
|
135
|
+
location_id_draft: values.location.locationId,
|
|
136
|
+
location_label_draft: values.location.locationLabel,
|
|
137
|
+
geohash_draft: values.location.geohash,
|
|
138
|
+
lat_draft: values.location.lat,
|
|
139
|
+
lon_draft: values.location.lon,
|
|
140
|
+
features_draft: toWireFeatures(features, values.features),
|
|
141
|
+
countable: values.countable,
|
|
142
|
+
// The pair mirrors the model's cross-field rule rather than sending a
|
|
143
|
+
// contradiction: a service carries no quantity, and `validate_countable
|
|
144
|
+
// _stock` refuses a `stock_quantity` beside `countable: false`.
|
|
145
|
+
stock_quantity: values.countable ? values.stockQuantity : null,
|
|
146
|
+
auto_republish: values.autoRepublish,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The body for CREATING a draft: `category_id` and nothing else.
|
|
151
|
+
*
|
|
152
|
+
* `perform_create` forces `owner` and `status`, and everything else has a
|
|
153
|
+
* model default, so a create that also carried the form's current contents
|
|
154
|
+
* would be a second write of data the very next `save-draft` sends anyway —
|
|
155
|
+
* and would fail the whole submission on a field the person could still fix.
|
|
156
|
+
* Create the row, then save into it.
|
|
157
|
+
*/
|
|
158
|
+
export function createDraftBody(categoryId) {
|
|
159
|
+
return { category_id: categoryId };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Switching category: keep the answers whose slug the new schema also
|
|
163
|
+
* declares, drop the rest.
|
|
164
|
+
*
|
|
165
|
+
* Spec §4.1 asks for exactly this, and the reason is the same one the forms
|
|
166
|
+
* spec gives for `error.409.forms_version_superseded`: a person who picked
|
|
167
|
+
* "Phones", filled in Brand and Condition, then realised they meant "Used
|
|
168
|
+
* phones" should not retype what both categories ask for. A value whose slug
|
|
169
|
+
* is gone IS dropped, because 0.6.0 rejects an unknown slug per feature —
|
|
170
|
+
* carrying it would turn a category change into a publish refusal about a
|
|
171
|
+
* field the composer no longer draws.
|
|
172
|
+
*/
|
|
173
|
+
export function retainKnownFeatureValues(values, features) {
|
|
174
|
+
const known = new Set(features.map((feature) => feature.slug));
|
|
175
|
+
const out = {};
|
|
176
|
+
for (const [slug, value] of Object.entries(values)) {
|
|
177
|
+
if (known.has(slug))
|
|
178
|
+
out[slug] = value;
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
/** Slugs that were answered and are NOT in the new schema — what
|
|
183
|
+
* {@link retainKnownFeatureValues} just dropped. A composer tells the person
|
|
184
|
+
* ("2 answers do not apply to this category") instead of losing them
|
|
185
|
+
* silently. */
|
|
186
|
+
export function droppedFeatureSlugs(values, features) {
|
|
187
|
+
const known = new Set(features.map((feature) => feature.slug));
|
|
188
|
+
return Object.keys(values)
|
|
189
|
+
.filter((slug) => !known.has(slug))
|
|
190
|
+
.sort();
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/model/draft.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stored DAO projection → something `@stapel/attributes-react` can draw.
|
|
3
|
+
*
|
|
4
|
+
* A listing carries its attribute values THREE times, all of them DAO lists:
|
|
5
|
+
* `features` (everything, ordered), `features_title` (the ones flagged for
|
|
6
|
+
* the title line) and `features_badges` (the ones flagged for the card). A
|
|
7
|
+
* DAO is the value together with the display configuration its type needs —
|
|
8
|
+
* `prefix`, `precision`, `trueLabel`, `unitType`, … all inline beside
|
|
9
|
+
* `value` — which is the single most useful property of this contract:
|
|
10
|
+
*
|
|
11
|
+
* **a card can render its badges without ever fetching the category.**
|
|
12
|
+
*
|
|
13
|
+
* `formatFeatureValue` wants a `(FeatureDef, FeatureValueDto)` pair, so this
|
|
14
|
+
* module splits a DAO back into one. The split is mechanical, and it is
|
|
15
|
+
* written down once here rather than at each of the three call sites.
|
|
16
|
+
*
|
|
17
|
+
* ── Where the config comes from, and why the whole row is handed over ──────
|
|
18
|
+
*
|
|
19
|
+
* `FeatureConfig` has an index signature, so passing the DAO itself as the
|
|
20
|
+
* config costs nothing and loses nothing: the type-specific keys are already
|
|
21
|
+
* at the top level of the row, and the four presentation flags (`name`,
|
|
22
|
+
* `order`, `title`, `badge`) are simply ignored by every formatter. Copying a
|
|
23
|
+
* hand-picked subset instead would be a list to keep in step with ten types.
|
|
24
|
+
*
|
|
25
|
+
* ── What a DAO does NOT carry ──────────────────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* `select`'s `options`. `normalize_to_dao` stores the chosen VALUES, not the
|
|
28
|
+
* option table, so `formatFeatureValue` falls back to the raw option value —
|
|
29
|
+
* which is a translation KEY when the config is translatable (the default),
|
|
30
|
+
* so a host whose bundle carries the catalogue's option copy still reads a
|
|
31
|
+
* word. A host whose bundle does not sees the key, on purpose: a visible
|
|
32
|
+
* `option.condition.used` gets fixed, an invented "Used" ships wrong.
|
|
33
|
+
*/
|
|
34
|
+
import type { FeatureValueDto } from "@stapel/attributes-react";
|
|
35
|
+
import type { ListingFeatureDao, ListingFeatureView } from "../api/types.js";
|
|
36
|
+
/**
|
|
37
|
+
* One DAO row → the definition/value pair the display half consumes.
|
|
38
|
+
*
|
|
39
|
+
* `undefined` for a row with no `slug`: the stored projection always injects
|
|
40
|
+
* one (`build_features_list`: `{**dao, "slug": slug}`), so a row without it
|
|
41
|
+
* is a malformed record, and a synthesized index would key a badge to a
|
|
42
|
+
* position that changes whenever the category does.
|
|
43
|
+
*/
|
|
44
|
+
export declare function featureFromDao(dao: ListingFeatureDao): ListingFeatureView | undefined;
|
|
45
|
+
/** A stored projection → the pairs a display component iterates. Malformed
|
|
46
|
+
* rows are dropped HERE, in one place, and counted by {@link
|
|
47
|
+
* unreadableFeatureCount} so a skin can say how many rather than pretend the
|
|
48
|
+
* listing had fewer attributes. */
|
|
49
|
+
export declare function featuresFromDaoList(daos: readonly ListingFeatureDao[] | null | undefined): readonly ListingFeatureView[];
|
|
50
|
+
/** How many rows of a stored projection this build could not key. */
|
|
51
|
+
export declare function unreadableFeatureCount(daos: readonly ListingFeatureDao[] | null | undefined): number;
|
|
52
|
+
/**
|
|
53
|
+
* A stored projection → the `{slug: {type, value}}` envelope, which is what
|
|
54
|
+
* a composer reopening a PUBLISHED listing needs: `draftValuesFromDetail`
|
|
55
|
+
* seeds its editors from the published values, and the published values live
|
|
56
|
+
* only in the DAO list.
|
|
57
|
+
*/
|
|
58
|
+
export declare function featuresDtoFromDaoList(daos: readonly ListingFeatureDao[] | null | undefined): Readonly<Record<string, FeatureValueDto>>;
|
|
59
|
+
/**
|
|
60
|
+
* The `features` field of a detail response, typed.
|
|
61
|
+
*
|
|
62
|
+
* The generated `FeatureDao` union is unusable (the discriminator defect —
|
|
63
|
+
* see `api/types.ts`), and `features` is a `JSONField` on the serializer, so
|
|
64
|
+
* this cast is the boundary between "the schema's description" and "the row
|
|
65
|
+
* the JSONField actually emitted". It lives in ONE function so there is one
|
|
66
|
+
* place to change when upstream fixes the mapping.
|
|
67
|
+
*/
|
|
68
|
+
export declare function asFeatureDaoList(value: unknown): readonly ListingFeatureDao[];
|
|
69
|
+
//# sourceMappingURL=features.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../../src/model/features.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAK7E;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,iBAAiB,GACrB,kBAAkB,GAAG,SAAS,CAqBhC;AAED;;;mCAGmC;AACnC,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GACpD,SAAS,kBAAkB,EAAE,CAO/B;AAED,qEAAqE;AACrE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GACpD,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GACpD,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAM3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,iBAAiB,EAAE,CAE7E"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Keys that belong to the DAO envelope rather than to the type's config. */
|
|
2
|
+
const ENVELOPE = new Set(["slug", "value", "name", "order", "title", "badge"]);
|
|
3
|
+
/**
|
|
4
|
+
* One DAO row → the definition/value pair the display half consumes.
|
|
5
|
+
*
|
|
6
|
+
* `undefined` for a row with no `slug`: the stored projection always injects
|
|
7
|
+
* one (`build_features_list`: `{**dao, "slug": slug}`), so a row without it
|
|
8
|
+
* is a malformed record, and a synthesized index would key a badge to a
|
|
9
|
+
* position that changes whenever the category does.
|
|
10
|
+
*/
|
|
11
|
+
export function featureFromDao(dao) {
|
|
12
|
+
if (typeof dao.slug !== "string" || dao.slug.length === 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
const config = {};
|
|
15
|
+
for (const [key, value] of Object.entries(dao)) {
|
|
16
|
+
if (!ENVELOPE.has(key))
|
|
17
|
+
config[key] = value;
|
|
18
|
+
}
|
|
19
|
+
const feature = {
|
|
20
|
+
slug: dao.slug,
|
|
21
|
+
config,
|
|
22
|
+
name: dao.name ?? null,
|
|
23
|
+
...(dao.translate !== undefined ? { translate: dao.translate } : {}),
|
|
24
|
+
};
|
|
25
|
+
const value = typeof dao.type === "string" && dao.type.length > 0
|
|
26
|
+
? { type: dao.type, value: dao.value }
|
|
27
|
+
: undefined;
|
|
28
|
+
return { feature, value };
|
|
29
|
+
}
|
|
30
|
+
/** A stored projection → the pairs a display component iterates. Malformed
|
|
31
|
+
* rows are dropped HERE, in one place, and counted by {@link
|
|
32
|
+
* unreadableFeatureCount} so a skin can say how many rather than pretend the
|
|
33
|
+
* listing had fewer attributes. */
|
|
34
|
+
export function featuresFromDaoList(daos) {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const dao of daos ?? []) {
|
|
37
|
+
const view = featureFromDao(dao);
|
|
38
|
+
if (view !== undefined)
|
|
39
|
+
out.push(view);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/** How many rows of a stored projection this build could not key. */
|
|
44
|
+
export function unreadableFeatureCount(daos) {
|
|
45
|
+
return (daos ?? []).length - featuresFromDaoList(daos).length;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A stored projection → the `{slug: {type, value}}` envelope, which is what
|
|
49
|
+
* a composer reopening a PUBLISHED listing needs: `draftValuesFromDetail`
|
|
50
|
+
* seeds its editors from the published values, and the published values live
|
|
51
|
+
* only in the DAO list.
|
|
52
|
+
*/
|
|
53
|
+
export function featuresDtoFromDaoList(daos) {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const view of featuresFromDaoList(daos)) {
|
|
56
|
+
if (view.value !== undefined)
|
|
57
|
+
out[view.feature.slug] = view.value;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The `features` field of a detail response, typed.
|
|
63
|
+
*
|
|
64
|
+
* The generated `FeatureDao` union is unusable (the discriminator defect —
|
|
65
|
+
* see `api/types.ts`), and `features` is a `JSONField` on the serializer, so
|
|
66
|
+
* this cast is the boundary between "the schema's description" and "the row
|
|
67
|
+
* the JSONField actually emitted". It lives in ONE function so there is one
|
|
68
|
+
* place to change when upstream fixes the mapping.
|
|
69
|
+
*/
|
|
70
|
+
export function asFeatureDaoList(value) {
|
|
71
|
+
return Array.isArray(value) ? value : [];
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=features.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features.js","sourceRoot":"","sources":["../../src/model/features.ts"],"names":[],"mappings":"AAoCA,6EAA6E;AAC7E,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAsB;IAEtB,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE5E,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC;IAED,MAAM,OAAO,GAAe;QAC1B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM;QACN,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI;QACtB,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrE,CAAC;IAEF,MAAM,KAAK,GACT,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QACjD,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE;QACtC,CAAC,CAAC,SAAS,CAAC;IAEhB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;;mCAGmC;AACnC,MAAM,UAAU,mBAAmB,CACjC,IAAqD;IAErD,MAAM,GAAG,GAAyB,EAAE,CAAC;IACrC,KAAK,MAAM,GAAG,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,IAAI,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,sBAAsB,CACpC,IAAqD;IAErD,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AAChE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAqD;IAErD,MAAM,GAAG,GAAoC,EAAE,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;IACpE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAsC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE GAP: stapel-listings cannot list a person their own listings.
|
|
3
|
+
*
|
|
4
|
+
* This is the one place in the pair where a contract is missing rather than
|
|
5
|
+
* awkward, so it gets its own module and its own name.
|
|
6
|
+
*
|
|
7
|
+
* `GET /listings/` answers `qs.published()` and takes no owner parameter
|
|
8
|
+
* (`views.ListingViewSet.get_queryset`): it returns the whole marketplace's
|
|
9
|
+
* shop window and can be narrowed to nobody. The only owner-scoped reads in
|
|
10
|
+
* the module are `my/counters` (three integers) and `my/favorites`. So a
|
|
11
|
+
* seller's own DRAFTS are unreachable by any call this contract offers — and
|
|
12
|
+
* drafts are not indexed either, so routing around it through
|
|
13
|
+
* `@stapel/search-react`'s `owner=` would return the published subset and
|
|
14
|
+
* quietly call it "everything".
|
|
15
|
+
*
|
|
16
|
+
* The storefront spec assumed `GET /listings/` was "for mine" (§4.1). It is
|
|
17
|
+
* not, and this pair records the gap rather than papering over it.
|
|
18
|
+
*
|
|
19
|
+
* Two upstream asks, in preference order:
|
|
20
|
+
* 1. an `?owner=me` filter (or a `my/listings` action) on the list endpoint,
|
|
21
|
+
* with the same `IDAnchorPagination` envelope the other two use;
|
|
22
|
+
* 2. failing that, a `status` filter on it, so at least the published subset
|
|
23
|
+
* can be narrowed without a search module.
|
|
24
|
+
*
|
|
25
|
+
* Until then a host injects a {@link MyListingsSource} and the dashboard
|
|
26
|
+
* works completely; without one it shows the real counters and NAMES the
|
|
27
|
+
* absence, because "we cannot ask" and "you have no listings" are different
|
|
28
|
+
* sentences.
|
|
29
|
+
*/
|
|
30
|
+
import { StapelApiError } from "@stapel/core";
|
|
31
|
+
import type { ListingPageParams, PaginatedListingCards } from "../api/types.js";
|
|
32
|
+
import type { MyListingsTab } from "./status.js";
|
|
33
|
+
/** One page of the caller's own listings, however the host can get them. */
|
|
34
|
+
export type MyListingsSource = (args: {
|
|
35
|
+
readonly tab: MyListingsTab;
|
|
36
|
+
readonly page: ListingPageParams;
|
|
37
|
+
readonly signal?: AbortSignal;
|
|
38
|
+
}) => Promise<PaginatedListingCards>;
|
|
39
|
+
/**
|
|
40
|
+
* The error a dashboard without a source reports.
|
|
41
|
+
*
|
|
42
|
+
* `status: 0` on purpose: this is a fact about the deployment's wiring, not
|
|
43
|
+
* something a server said, and a client-side refusal must never be
|
|
44
|
+
* indistinguishable from one that came over the wire.
|
|
45
|
+
*/
|
|
46
|
+
export declare const MY_LISTINGS_SOURCE_MISSING: StapelApiError;
|
|
47
|
+
//# sourceMappingURL=mineSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mineSource.d.ts","sourceRoot":"","sources":["../../src/model/mineSource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,4EAA4E;AAC5E,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE;IACpC,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAErC;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,cAMvC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE GAP: stapel-listings cannot list a person their own listings.
|
|
3
|
+
*
|
|
4
|
+
* This is the one place in the pair where a contract is missing rather than
|
|
5
|
+
* awkward, so it gets its own module and its own name.
|
|
6
|
+
*
|
|
7
|
+
* `GET /listings/` answers `qs.published()` and takes no owner parameter
|
|
8
|
+
* (`views.ListingViewSet.get_queryset`): it returns the whole marketplace's
|
|
9
|
+
* shop window and can be narrowed to nobody. The only owner-scoped reads in
|
|
10
|
+
* the module are `my/counters` (three integers) and `my/favorites`. So a
|
|
11
|
+
* seller's own DRAFTS are unreachable by any call this contract offers — and
|
|
12
|
+
* drafts are not indexed either, so routing around it through
|
|
13
|
+
* `@stapel/search-react`'s `owner=` would return the published subset and
|
|
14
|
+
* quietly call it "everything".
|
|
15
|
+
*
|
|
16
|
+
* The storefront spec assumed `GET /listings/` was "for mine" (§4.1). It is
|
|
17
|
+
* not, and this pair records the gap rather than papering over it.
|
|
18
|
+
*
|
|
19
|
+
* Two upstream asks, in preference order:
|
|
20
|
+
* 1. an `?owner=me` filter (or a `my/listings` action) on the list endpoint,
|
|
21
|
+
* with the same `IDAnchorPagination` envelope the other two use;
|
|
22
|
+
* 2. failing that, a `status` filter on it, so at least the published subset
|
|
23
|
+
* can be narrowed without a search module.
|
|
24
|
+
*
|
|
25
|
+
* Until then a host injects a {@link MyListingsSource} and the dashboard
|
|
26
|
+
* works completely; without one it shows the real counters and NAMES the
|
|
27
|
+
* absence, because "we cannot ask" and "you have no listings" are different
|
|
28
|
+
* sentences.
|
|
29
|
+
*/
|
|
30
|
+
import { StapelApiError } from "@stapel/core";
|
|
31
|
+
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
32
|
+
/**
|
|
33
|
+
* The error a dashboard without a source reports.
|
|
34
|
+
*
|
|
35
|
+
* `status: 0` on purpose: this is a fact about the deployment's wiring, not
|
|
36
|
+
* something a server said, and a client-side refusal must never be
|
|
37
|
+
* indistinguishable from one that came over the wire.
|
|
38
|
+
*/
|
|
39
|
+
export const MY_LISTINGS_SOURCE_MISSING = new StapelApiError({
|
|
40
|
+
code: LISTINGS_I18N_KEYS.mineSourceMissing,
|
|
41
|
+
message: "No MyListingsSource was supplied, and stapel-listings has no " +
|
|
42
|
+
"owner-scoped list endpoint to fall back on.",
|
|
43
|
+
status: 0,
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=mineSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mineSource.js","sourceRoot":"","sources":["../../src/model/mineSource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AASrD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAmB,IAAI,cAAc,CAAC;IAC3E,IAAI,EAAE,kBAAkB,CAAC,iBAAiB;IAC1C,OAAO,EACL,+DAA+D;QAC/D,6CAA6C;IAC/C,MAAM,EAAE,CAAC;CACV,CAAC,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { UseMutationResult } from "@tanstack/react-query";
|
|
2
|
+
import type { StapelApiError } from "@stapel/core";
|
|
3
|
+
import type { DeleteResponse, FavoriteToggleResponse, ListingActionResponse, ListingDraft, ListingDraftPatch, PublishResponse } from "../api/types.js";
|
|
4
|
+
/** Start a draft. The server forces `owner` and `status=draft`. */
|
|
5
|
+
export declare function useCreateDraft(): UseMutationResult<ListingDraft, StapelApiError, ListingDraftPatch>;
|
|
6
|
+
/** What a `save-draft` write carries. */
|
|
7
|
+
export interface SaveDraftInput {
|
|
8
|
+
readonly id: number;
|
|
9
|
+
readonly body: ListingDraftPatch;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Persist draft fields. Always partial — send what the form holds.
|
|
13
|
+
*
|
|
14
|
+
* The validate-draft entry is invalidated rather than written: the saved row
|
|
15
|
+
* comes back, but it carries no verdict, and a stale verdict beside fresh
|
|
16
|
+
* content is the one combination a composer must never show.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useSaveDraft(): UseMutationResult<ListingDraft, StapelApiError, SaveDraftInput>;
|
|
19
|
+
/**
|
|
20
|
+
* Promote the draft.
|
|
21
|
+
*
|
|
22
|
+
* `retry: false` — the 400 here is a VERDICT about the content (a bare
|
|
23
|
+
* `ValidationBatchResult` body, see `model/validation.ts`), and repeating a
|
|
24
|
+
* verdict three times only delays the moment the composer can lay it on the
|
|
25
|
+
* controls.
|
|
26
|
+
*/
|
|
27
|
+
export declare function usePublishListing(): UseMutationResult<PublishResponse, StapelApiError, number>;
|
|
28
|
+
/** Move to ARCHIVED. 409 `error.409.invalid_listing_transition` carries
|
|
29
|
+
* `params.from_status`, which is what turns the refusal into a sentence. */
|
|
30
|
+
export declare function useArchiveListing(): UseMutationResult<ListingActionResponse, StapelApiError, number>;
|
|
31
|
+
/** Mark SOLD. Same 409 contract as {@link useArchiveListing}. */
|
|
32
|
+
export declare function useCompleteListing(): UseMutationResult<ListingActionResponse, StapelApiError, number>;
|
|
33
|
+
/** Soft-delete. Refused with `error.409.listing_cannot_delete_active` while
|
|
34
|
+
* the listing is PUBLISHED or PENDING. */
|
|
35
|
+
export declare function useDeleteListing(): UseMutationResult<DeleteResponse, StapelApiError, number>;
|
|
36
|
+
/** What a favourite toggle carries: the id and the state it should END in. */
|
|
37
|
+
export interface FavoriteInput {
|
|
38
|
+
readonly id: number;
|
|
39
|
+
readonly favorited: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Favourite / un-favourite, as ONE mutation over the intended next state.
|
|
43
|
+
*
|
|
44
|
+
* Two endpoints, one intent — and modelling it as two hooks would let a
|
|
45
|
+
* caller call the wrong one for the state it is in. The server is idempotent
|
|
46
|
+
* on both sides (`get_or_create` / `filter().delete()`), so a double-click
|
|
47
|
+
* cannot desynchronise anything.
|
|
48
|
+
*
|
|
49
|
+
* This is the pair's ONE write whose next state the client may predict, and
|
|
50
|
+
* the reason is that the truth is a single boolean the caller already holds.
|
|
51
|
+
* Everything else here moves a LIFECYCLE whose next state is the server's to
|
|
52
|
+
* decide — guessing `pending` versus `published` after a publish would be
|
|
53
|
+
* inventing the very verdict the pair exists to report faithfully.
|
|
54
|
+
*
|
|
55
|
+
* The invalidation reaches the favourites LIST as well as the row: a heart
|
|
56
|
+
* turned off on the favourites page must remove the card, not leave a hollow
|
|
57
|
+
* one behind.
|
|
58
|
+
*/
|
|
59
|
+
export declare function useFavoriteListing(): UseMutationResult<FavoriteToggleResponse, StapelApiError, FavoriteInput>;
|
|
60
|
+
//# sourceMappingURL=mutations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/model/mutations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EACV,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,eAAe,EAChB,MAAM,iBAAiB,CAAC;AAwCzB,mEAAmE;AACnE,wBAAgB,cAAc,IAAI,iBAAiB,CACjD,YAAY,EACZ,cAAc,EACd,iBAAiB,CAClB,CAYA;AAED,yCAAyC;AACzC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,iBAAiB,CAC/C,YAAY,EACZ,cAAc,EACd,cAAc,CACf,CAUA;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,CACpD,eAAe,EACf,cAAc,EACd,MAAM,CACP,CAUA;AAED;4EAC4E;AAC5E,wBAAgB,iBAAiB,IAAI,iBAAiB,CACpD,qBAAqB,EACrB,cAAc,EACd,MAAM,CACP,CAUA;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,IAAI,iBAAiB,CACrD,qBAAqB,EACrB,cAAc,EACd,MAAM,CACP,CAUA;AAED;0CAC0C;AAC1C,wBAAgB,gBAAgB,IAAI,iBAAiB,CACnD,cAAc,EACd,cAAc,EACd,MAAM,CACP,CAWA;AAED,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,iBAAiB,CACrD,sBAAsB,EACtB,cAAc,EACd,aAAa,CACd,CAmBA"}
|