@stapel/listings-react 0.9.1 → 0.11.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 +50 -0
- package/dist/default/FeedGrid.d.ts +42 -0
- package/dist/default/FeedGrid.d.ts.map +1 -0
- package/dist/default/FeedGrid.js +23 -0
- package/dist/default/FeedGrid.js.map +1 -0
- package/dist/default/ListingCard.d.ts +31 -0
- package/dist/default/ListingCard.d.ts.map +1 -1
- package/dist/default/ListingCard.js +12 -4
- package/dist/default/ListingCard.js.map +1 -1
- package/dist/default/ListingComposerPage.d.ts.map +1 -1
- package/dist/default/ListingComposerPage.js +10 -4
- package/dist/default/ListingComposerPage.js.map +1 -1
- package/dist/default/ListingFeedCard.d.ts +77 -0
- package/dist/default/ListingFeedCard.d.ts.map +1 -0
- package/dist/default/ListingFeedCard.js +75 -0
- package/dist/default/ListingFeedCard.js.map +1 -0
- package/dist/default/ListingSerpCard.d.ts +114 -0
- package/dist/default/ListingSerpCard.d.ts.map +1 -0
- package/dist/default/ListingSerpCard.js +72 -0
- package/dist/default/ListingSerpCard.js.map +1 -0
- package/dist/default/favorite.d.ts +49 -0
- package/dist/default/favorite.d.ts.map +1 -0
- package/dist/default/favorite.js +25 -0
- package/dist/default/favorite.js.map +1 -0
- package/dist/default/icons.d.ts +17 -3
- package/dist/default/icons.d.ts.map +1 -1
- package/dist/default/icons.js +13 -1
- package/dist/default/icons.js.map +1 -1
- package/dist/default/index.d.ts +17 -1
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +14 -1
- package/dist/default/index.js.map +1 -1
- package/dist/headless/ListingComposer.d.ts.map +1 -1
- package/dist/headless/ListingComposer.js +14 -6
- package/dist/headless/ListingComposer.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +4 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.d.ts +11 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +15 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +4 -0
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model/validation.d.ts +20 -0
- package/dist/model/validation.d.ts.map +1 -1
- package/dist/model/validation.js +63 -1
- package/dist/model/validation.js.map +1 -1
- package/llms.txt +3 -1
- package/manifest.json +47 -1
- package/nav-manifest.json +1 -1
- package/package.json +10 -10
- package/src/analytics/generated/events.json +1 -1
- package/src/default/FeedGrid.tsx +70 -0
- package/src/default/ListingCard.tsx +21 -5
- package/src/default/ListingComposerPage.tsx +18 -3
- package/src/default/ListingFeedCard.tsx +232 -0
- package/src/default/ListingSerpCard.tsx +374 -0
- package/src/default/favorite.tsx +91 -0
- package/src/default/icons.tsx +37 -3
- package/src/default/index.ts +24 -1
- package/src/headless/ListingComposer.tsx +19 -6
- package/src/i18n/es.ts +4 -0
- package/src/i18n/keys.ts +15 -0
- package/src/i18n/ru.ts +4 -0
- package/src/index.ts +2 -0
- package/src/model/validation.ts +64 -1
package/src/model/validation.ts
CHANGED
|
@@ -93,6 +93,31 @@ export const TITLE_FIELD = "title";
|
|
|
93
93
|
export const PRICE_FIELD = "price";
|
|
94
94
|
export const IMAGES_FIELD = "images";
|
|
95
95
|
export const CATEGORY_FIELD = "category_id";
|
|
96
|
+
/** The coordinate pair, as one control: a latitude alone is not a place. */
|
|
97
|
+
export const LOCATION_FIELD = "location";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The API's own field names → the control that holds them.
|
|
101
|
+
*
|
|
102
|
+
* The draft serializer writes `*_draft` columns, and the two coordinates are
|
|
103
|
+
* one field on screen. Without this table a per-field 400 has nowhere to land
|
|
104
|
+
* and becomes a banner nobody can act on — which is the whole of blocker C2's
|
|
105
|
+
* second half: a save refused for `lat_draft` painted "Validation error" over
|
|
106
|
+
* the footer while the location control, the only thing the person could have
|
|
107
|
+
* changed, stayed clean.
|
|
108
|
+
*/
|
|
109
|
+
const CONTROL_OF_API_FIELD: Readonly<Record<string, string>> = {
|
|
110
|
+
title_draft: TITLE_FIELD,
|
|
111
|
+
description_draft: DESCRIPTION_FIELD,
|
|
112
|
+
price_draft: PRICE_FIELD,
|
|
113
|
+
images_draft: IMAGES_FIELD,
|
|
114
|
+
category_id: CATEGORY_FIELD,
|
|
115
|
+
lat_draft: LOCATION_FIELD,
|
|
116
|
+
lon_draft: LOCATION_FIELD,
|
|
117
|
+
location_id_draft: LOCATION_FIELD,
|
|
118
|
+
location_label_draft: LOCATION_FIELD,
|
|
119
|
+
stock_quantity: "stockQuantity",
|
|
120
|
+
};
|
|
96
121
|
|
|
97
122
|
/** A client-side refusal. `status: 0` on purpose: a rule this build applied
|
|
98
123
|
* must never be indistinguishable from one that came over the wire. */
|
|
@@ -162,7 +187,7 @@ export function mirrorListingFields(
|
|
|
162
187
|
const hasLat = lat !== null && lat.length > 0;
|
|
163
188
|
const hasLon = lon !== null && lon.length > 0;
|
|
164
189
|
if (hasLat !== hasLon) {
|
|
165
|
-
out[
|
|
190
|
+
out[LOCATION_FIELD] = mirrored(LISTINGS_I18N_KEYS.composeGeoIncomplete);
|
|
166
191
|
}
|
|
167
192
|
|
|
168
193
|
return out;
|
|
@@ -244,6 +269,44 @@ export function publishRefusal(thrown: unknown): PublishRefusal {
|
|
|
244
269
|
};
|
|
245
270
|
}
|
|
246
271
|
|
|
272
|
+
/**
|
|
273
|
+
* A per-field refusal in the ORDINARY envelope → the control it belongs to.
|
|
274
|
+
*
|
|
275
|
+
* `save-draft` and `create` do not answer a batch: DRF's field validation
|
|
276
|
+
* raises, and stapel-core's handler folds it into one envelope carrying
|
|
277
|
+
* `params.field` — the field name, and often the ONLY thing in the response
|
|
278
|
+
* that says what went wrong, because a DRF code the registry does not know
|
|
279
|
+
* (`max_decimal_places`, say) collapses to the generic
|
|
280
|
+
* `error.400.validation_error`, whose sentence is "Validation error" and
|
|
281
|
+
* nothing else.
|
|
282
|
+
*
|
|
283
|
+
* So the field name is the payload. Put on the control, it is the difference
|
|
284
|
+
* between a red banner the person cannot act on and a red field they can.
|
|
285
|
+
* Returns `{}` for anything that names no field, or names one this composer
|
|
286
|
+
* does not draw — a banner is right for those, and a refusal routed to a
|
|
287
|
+
* control that does not exist would vanish.
|
|
288
|
+
*/
|
|
289
|
+
export function envelopeFieldErrors(
|
|
290
|
+
thrown: unknown
|
|
291
|
+
): Readonly<Record<string, FlowError>> {
|
|
292
|
+
if (!isStapelApiError(thrown)) return {};
|
|
293
|
+
const named = thrown.params["field"];
|
|
294
|
+
if (typeof named !== "string" || named.length === 0) return {};
|
|
295
|
+
const control = CONTROL_OF_API_FIELD[named];
|
|
296
|
+
if (control === undefined) return {};
|
|
297
|
+
return {
|
|
298
|
+
[control]: {
|
|
299
|
+
code: thrown.code,
|
|
300
|
+
// `field` is the fleet's routing param; the control key is what a
|
|
301
|
+
// sentence should name, not the column the server happened to use.
|
|
302
|
+
params: { ...thrown.params, field: control },
|
|
303
|
+
status: thrown.status,
|
|
304
|
+
message: thrown.message,
|
|
305
|
+
language: thrown.language,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
247
310
|
/**
|
|
248
311
|
* A server batch → refusals keyed by CONTROL.
|
|
249
312
|
*
|