@stapel/listings-react 0.7.0 → 0.8.1
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 +88 -0
- package/dist/api/generated/schema.d.ts +92 -2
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/default/ListingCard.d.ts +56 -0
- package/dist/default/ListingCard.d.ts.map +1 -1
- package/dist/default/ListingCard.js +92 -29
- package/dist/default/ListingCard.js.map +1 -1
- package/dist/default/ListingComposerPage.d.ts +21 -7
- package/dist/default/ListingComposerPage.d.ts.map +1 -1
- package/dist/default/ListingComposerPage.js.map +1 -1
- package/dist/headless/Favorites.d.ts.map +1 -1
- package/dist/headless/Favorites.js +19 -7
- package/dist/headless/Favorites.js.map +1 -1
- package/dist/headless/ListingDetail.d.ts.map +1 -1
- package/dist/headless/ListingDetail.js +7 -5
- package/dist/headless/ListingDetail.js.map +1 -1
- package/dist/headless/useMandateGate.d.ts +33 -2
- package/dist/headless/useMandateGate.d.ts.map +1 -1
- package/dist/headless/useMandateGate.js +42 -4
- package/dist/headless/useMandateGate.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +2 -1
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/generated/errors.es.gen.d.ts +1 -1
- package/dist/i18n/generated/errors.es.gen.js +1 -1
- package/dist/i18n/generated/errors.gen.d.ts +6 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.gen.js +3 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.d.ts +1 -1
- package/dist/i18n/generated/errors.ru.gen.js +1 -1
- package/dist/i18n/keys.d.ts +11 -1
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +12 -2
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +2 -1
- 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/llms.txt +3 -3
- package/manifest.json +30 -21
- package/nav-manifest.json +1 -1
- package/package.json +8 -8
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +92 -2
- package/src/default/ListingCard.tsx +233 -108
- package/src/default/ListingComposerPage.tsx +21 -7
- package/src/headless/Favorites.tsx +26 -7
- package/src/headless/ListingDetail.tsx +12 -5
- package/src/headless/useMandateGate.ts +49 -5
- package/src/i18n/es.ts +3 -1
- package/src/i18n/generated/errors.es.gen.ts +1 -1
- package/src/i18n/generated/errors.gen.ts +3 -0
- package/src/i18n/generated/errors.json +7 -0
- package/src/i18n/generated/errors.ru.gen.ts +1 -1
- package/src/i18n/keys.ts +12 -2
- package/src/i18n/ru.ts +3 -1
- package/src/index.ts +5 -1
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
import { matchMandate, useMandate } from "@stapel/core";
|
|
2
|
-
import type { ActionAvailability } from "@stapel/core";
|
|
1
|
+
import { matchMandate, useElevation, useMandate } from "@stapel/core";
|
|
2
|
+
import type { ActionAvailability, Elevation } from "@stapel/core";
|
|
3
3
|
import { actionAvailable, actionBlocked } from "@stapel/core";
|
|
4
4
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The names this pair's gated writes use when asking core's elevation seam
|
|
8
|
+
* whether an anonymous visitor may be given an identity instead of a
|
|
9
|
+
* refusal. A host lists the ones it permits (see `ElevationSource.actions`);
|
|
10
|
+
* anything it leaves out keeps its wall.
|
|
11
|
+
*
|
|
12
|
+
* Only `favorite` is named. Publishing a listing is deliberately absent and
|
|
13
|
+
* stays absent: a seller who cannot be reached again is not a seller, and an
|
|
14
|
+
* automatically-minted account is exactly that. Saving something for later
|
|
15
|
+
* is the opposite case — losing a shortlist because a stranger had not
|
|
16
|
+
* registered is friction with nothing behind it.
|
|
17
|
+
*/
|
|
18
|
+
export const LISTINGS_ELEVATION_ACTIONS = {
|
|
19
|
+
favorite: "listings.favorite",
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
6
22
|
/**
|
|
7
23
|
* "May this person act at all?" — the first gate on every write in the pair.
|
|
8
24
|
*
|
|
@@ -28,13 +44,41 @@ import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
|
28
44
|
* than throwing, so a pair rendered in a host that never wired the axis
|
|
29
45
|
* degrades to "we could not check" — visible, fixable, and not a blank page.
|
|
30
46
|
*/
|
|
31
|
-
export function useMandateGate(): ActionAvailability {
|
|
47
|
+
export function useMandateGate(action?: string): ActionAvailability {
|
|
48
|
+
return useElevatableMandateGate(action).gate;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The same gate, with the elevation handle the caller needs to ACT on it.
|
|
53
|
+
*
|
|
54
|
+
* A control that passes an `action` is saying "for this one act, an
|
|
55
|
+
* anonymous visitor may be given an identity rather than a refusal" — and
|
|
56
|
+
* then it must run its write through `elevation.run`, or the write goes out
|
|
57
|
+
* before the account exists and buys a 401. The two halves are returned
|
|
58
|
+
* together so a caller cannot take the unblocked gate and forget the mint.
|
|
59
|
+
*
|
|
60
|
+
* Passing no `action` is the default and the majority: publishing, saving a
|
|
61
|
+
* draft, marking something sold. Those keep the wall.
|
|
62
|
+
*/
|
|
63
|
+
export function useElevatableMandateGate(action?: string): {
|
|
64
|
+
readonly gate: ActionAvailability;
|
|
65
|
+
readonly elevation: Elevation;
|
|
66
|
+
} {
|
|
32
67
|
const mandate = useMandate();
|
|
33
|
-
|
|
68
|
+
const elevation = useElevation(action);
|
|
69
|
+
const gate = matchMandate<ActionAvailability>(mandate, {
|
|
34
70
|
member: () => actionAvailable(),
|
|
35
71
|
guest: () => actionBlocked(LISTINGS_I18N_KEYS.blockedGuest),
|
|
36
|
-
|
|
72
|
+
// The one arm elevation changes. An identity can be minted at the moment
|
|
73
|
+
// of the act for the actions the host listed, so the control is offered
|
|
74
|
+
// rather than refused — and the mandate axis itself is untouched, which
|
|
75
|
+
// is what keeps the composer's wall standing for the same visitor.
|
|
76
|
+
anonymous: () =>
|
|
77
|
+
elevation.covers
|
|
78
|
+
? actionAvailable()
|
|
79
|
+
: actionBlocked(LISTINGS_I18N_KEYS.blockedSignIn),
|
|
37
80
|
asking: () => actionBlocked(LISTINGS_I18N_KEYS.blockedMandateUnknown),
|
|
38
81
|
unavailable: () => actionBlocked(LISTINGS_I18N_KEYS.blockedMandateUnknown),
|
|
39
82
|
});
|
|
83
|
+
return { gate, elevation };
|
|
40
84
|
}
|
package/src/i18n/es.ts
CHANGED
|
@@ -32,6 +32,8 @@ export const listingsI18nBundleEs: I18nDictionary = {
|
|
|
32
32
|
"Estado de anuncio desconocido: «{status}»",
|
|
33
33
|
"error.400.publish_validation_failed":
|
|
34
34
|
"El anuncio no pasó la revisión y no se publicó",
|
|
35
|
+
"error.403.listing_anonymous_not_allowed":
|
|
36
|
+
"Para publicar un anuncio, inicia sesión o crea una cuenta",
|
|
35
37
|
"error.403.listing_not_owner": "Este anuncio no es tuyo",
|
|
36
38
|
"error.404.listing_not_found": "No se encontró el anuncio",
|
|
37
39
|
"error.409.already_favorited": "El anuncio ya está en favoritos",
|
|
@@ -72,7 +74,7 @@ export const listingsI18nBundleEs: I18nDictionary = {
|
|
|
72
74
|
"listings.card.price_absent": "Precio a consultar",
|
|
73
75
|
"listings.card.favorite_add": "Guardar en favoritos",
|
|
74
76
|
"listings.card.favorite_remove": "Quitar de favoritos",
|
|
75
|
-
"listings.card.
|
|
77
|
+
"listings.card.untitled": "Anuncio sin título",
|
|
76
78
|
"listings.card.sign_in": "Iniciar sesión",
|
|
77
79
|
|
|
78
80
|
"listings.detail.loading": "Cargando el anuncio…",
|
|
@@ -8,7 +8,7 @@ import type { ListingsErrorCode } from "./errors.gen.js";
|
|
|
8
8
|
/**
|
|
9
9
|
* `es` texts for the backend error codes this catalog carries.
|
|
10
10
|
*
|
|
11
|
-
* PARTIAL, and deliberately typed to say so:
|
|
11
|
+
* PARTIAL, and deliberately typed to say so: 23 key(s) owned by
|
|
12
12
|
* stapel_attributes, stapel_listings are absent, because that owner ships no locale catalog
|
|
13
13
|
* (ERRORS_LOCALE_EXEMPT_OWNERS). English for them still comes from the registry
|
|
14
14
|
* artifact via the en bundle; the pair layers its own authored `es`
|
|
@@ -74,6 +74,7 @@ export const LISTINGS_ERRORS = {
|
|
|
74
74
|
"error.401.unauthorized": { status: 401, params: [], remediation: "reauthenticate", en: "Authentication required" },
|
|
75
75
|
"error.402.payment_required": { status: 402, params: [], remediation: "retry", en: "Payment required" },
|
|
76
76
|
"error.403.forbidden": { status: 403, params: [], remediation: "retry", en: "You do not have permission to perform this action" },
|
|
77
|
+
"error.403.listing_anonymous_not_allowed": { status: 403, params: [], remediation: "retry", en: "A guest account may not publish a listing" },
|
|
77
78
|
"error.403.listing_not_owner": { status: 403, params: [], remediation: "retry", en: "Not your listing" },
|
|
78
79
|
"error.403.network_blocked": { status: 403, params: [], remediation: "contact_support", en: "Requests from this network are not allowed" },
|
|
79
80
|
"error.403.verification_enrollment_required": { status: 403, params: [], remediation: "verify", en: "Verification factor enrollment required" },
|
|
@@ -144,6 +145,7 @@ export const LISTINGS_ERROR_CODES: readonly ListingsErrorCode[] = [
|
|
|
144
145
|
"error.401.unauthorized",
|
|
145
146
|
"error.402.payment_required",
|
|
146
147
|
"error.403.forbidden",
|
|
148
|
+
"error.403.listing_anonymous_not_allowed",
|
|
147
149
|
"error.403.listing_not_owner",
|
|
148
150
|
"error.403.network_blocked",
|
|
149
151
|
"error.403.verification_enrollment_required",
|
|
@@ -216,6 +218,7 @@ export const listingsErrorBundleEn: Record<ListingsErrorCode, string> = {
|
|
|
216
218
|
"error.401.unauthorized": "Authentication required",
|
|
217
219
|
"error.402.payment_required": "Payment required",
|
|
218
220
|
"error.403.forbidden": "You do not have permission to perform this action",
|
|
221
|
+
"error.403.listing_anonymous_not_allowed": "A guest account may not publish a listing",
|
|
219
222
|
"error.403.listing_not_owner": "Not your listing",
|
|
220
223
|
"error.403.network_blocked": "Requests from this network are not allowed",
|
|
221
224
|
"error.403.verification_enrollment_required": "Verification factor enrollment required",
|
|
@@ -326,6 +326,13 @@
|
|
|
326
326
|
"remediation": "retry",
|
|
327
327
|
"en": "You do not have permission to perform this action"
|
|
328
328
|
},
|
|
329
|
+
{
|
|
330
|
+
"code": "error.403.listing_anonymous_not_allowed",
|
|
331
|
+
"status": 403,
|
|
332
|
+
"params": [],
|
|
333
|
+
"remediation": "retry",
|
|
334
|
+
"en": "A guest account may not publish a listing"
|
|
335
|
+
},
|
|
329
336
|
{
|
|
330
337
|
"code": "error.403.listing_not_owner",
|
|
331
338
|
"status": 403,
|
|
@@ -8,7 +8,7 @@ import type { ListingsErrorCode } from "./errors.gen.js";
|
|
|
8
8
|
/**
|
|
9
9
|
* `ru` texts for the backend error codes this catalog carries.
|
|
10
10
|
*
|
|
11
|
-
* PARTIAL, and deliberately typed to say so:
|
|
11
|
+
* PARTIAL, and deliberately typed to say so: 23 key(s) owned by
|
|
12
12
|
* stapel_attributes, stapel_listings are absent, because that owner ships no locale catalog
|
|
13
13
|
* (ERRORS_LOCALE_EXEMPT_OWNERS). English for them still comes from the registry
|
|
14
14
|
* artifact via the en bundle; the pair layers its own authored `ru`
|
package/src/i18n/keys.ts
CHANGED
|
@@ -62,7 +62,17 @@ export const LISTINGS_I18N_KEYS = {
|
|
|
62
62
|
cardPriceAbsent: "listings.card.price_absent",
|
|
63
63
|
cardFavoriteAdd: "listings.card.favorite_add",
|
|
64
64
|
cardFavoriteRemove: "listings.card.favorite_remove",
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The accessible name of a card whose listing has no title.
|
|
67
|
+
*
|
|
68
|
+
* The whole card is ONE anchor now, and an anchor's name is its `aria-label`
|
|
69
|
+
* — so a titleless listing would otherwise be a link announced as nothing at
|
|
70
|
+
* all, in a list of forty.
|
|
71
|
+
*
|
|
72
|
+
* This replaces `listings.card.open` (retired), which named a separate
|
|
73
|
+
* "Open" control that no longer exists: the card itself is the control.
|
|
74
|
+
*/
|
|
75
|
+
cardUntitled: "listings.card.untitled",
|
|
66
76
|
/** The door beside a blocked favourite: the container supplies WHERE. */
|
|
67
77
|
cardSignIn: "listings.card.sign_in",
|
|
68
78
|
|
|
@@ -246,7 +256,7 @@ export const listingsI18nBundleEn: Record<string, string> = {
|
|
|
246
256
|
"listings.card.price_absent": "Price on request",
|
|
247
257
|
"listings.card.favorite_add": "Save to favourites",
|
|
248
258
|
"listings.card.favorite_remove": "Remove from favourites",
|
|
249
|
-
"listings.card.
|
|
259
|
+
"listings.card.untitled": "Untitled listing",
|
|
250
260
|
"listings.card.sign_in": "Sign in",
|
|
251
261
|
|
|
252
262
|
"listings.detail.loading": "Loading the listing…",
|
package/src/i18n/ru.ts
CHANGED
|
@@ -48,6 +48,8 @@ export const listingsI18nBundleRu: I18nDictionary = {
|
|
|
48
48
|
"Неизвестный статус объявления: «{status}»",
|
|
49
49
|
"error.400.publish_validation_failed":
|
|
50
50
|
"Объявление не прошло проверку и не опубликовано",
|
|
51
|
+
"error.403.listing_anonymous_not_allowed":
|
|
52
|
+
"Чтобы подать объявление, войдите или зарегистрируйтесь",
|
|
51
53
|
"error.403.listing_not_owner": "Это не ваше объявление",
|
|
52
54
|
"error.404.listing_not_found": "Объявление не найдено",
|
|
53
55
|
"error.409.already_favorited": "Объявление уже в избранном",
|
|
@@ -88,7 +90,7 @@ export const listingsI18nBundleRu: I18nDictionary = {
|
|
|
88
90
|
"listings.card.price_absent": "Цена по запросу",
|
|
89
91
|
"listings.card.favorite_add": "В избранное",
|
|
90
92
|
"listings.card.favorite_remove": "Убрать из избранного",
|
|
91
|
-
"listings.card.
|
|
93
|
+
"listings.card.untitled": "Объявление без названия",
|
|
92
94
|
"listings.card.sign_in": "Войти",
|
|
93
95
|
|
|
94
96
|
"listings.detail.loading": "Загружаем объявление…",
|
package/src/index.ts
CHANGED
|
@@ -214,7 +214,11 @@ export type {
|
|
|
214
214
|
|
|
215
215
|
// ── headless (renderless components + their bags) ────────────────────────────
|
|
216
216
|
export { ListingsProvider } from "./headless/ListingsProvider.js";
|
|
217
|
-
export {
|
|
217
|
+
export {
|
|
218
|
+
useMandateGate,
|
|
219
|
+
useElevatableMandateGate,
|
|
220
|
+
LISTINGS_ELEVATION_ACTIONS,
|
|
221
|
+
} from "./headless/useMandateGate.js";
|
|
218
222
|
export { ListingDetail, useListingDetail } from "./headless/ListingDetail.js";
|
|
219
223
|
export type {
|
|
220
224
|
ListingDetailBag,
|