@stapel/reviews-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 (164) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/MODULE.md +151 -0
  3. package/README.md +207 -0
  4. package/dist/api/generated/schema.d.ts +360 -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/reviewsApi.d.ts +89 -0
  9. package/dist/api/reviewsApi.d.ts.map +1 -0
  10. package/dist/api/reviewsApi.js +51 -0
  11. package/dist/api/reviewsApi.js.map +1 -0
  12. package/dist/api/types.d.ts +127 -0
  13. package/dist/api/types.d.ts.map +1 -0
  14. package/dist/api/types.js +2 -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/RatingBadge.d.ts +17 -0
  21. package/dist/default/RatingBadge.d.ts.map +1 -0
  22. package/dist/default/RatingBadge.js +23 -0
  23. package/dist/default/RatingBadge.js.map +1 -0
  24. package/dist/default/ReviewFormCard.d.ts +25 -0
  25. package/dist/default/ReviewFormCard.d.ts.map +1 -0
  26. package/dist/default/ReviewFormCard.js +42 -0
  27. package/dist/default/ReviewFormCard.js.map +1 -0
  28. package/dist/default/ReviewListPanel.d.ts +29 -0
  29. package/dist/default/ReviewListPanel.d.ts.map +1 -0
  30. package/dist/default/ReviewListPanel.js +47 -0
  31. package/dist/default/ReviewListPanel.js.map +1 -0
  32. package/dist/default/ReviewsPanel.d.ts +33 -0
  33. package/dist/default/ReviewsPanel.d.ts.map +1 -0
  34. package/dist/default/ReviewsPanel.js +17 -0
  35. package/dist/default/ReviewsPanel.js.map +1 -0
  36. package/dist/default/index.d.ts +31 -0
  37. package/dist/default/index.d.ts.map +1 -0
  38. package/dist/default/index.js +25 -0
  39. package/dist/default/index.js.map +1 -0
  40. package/dist/default/theme.d.ts +24 -0
  41. package/dist/default/theme.d.ts.map +1 -0
  42. package/dist/default/theme.js +39 -0
  43. package/dist/default/theme.js.map +1 -0
  44. package/dist/default/types.d.ts +12 -0
  45. package/dist/default/types.d.ts.map +1 -0
  46. package/dist/default/types.js +2 -0
  47. package/dist/default/types.js.map +1 -0
  48. package/dist/headless/ReviewAggregate.d.ts +63 -0
  49. package/dist/headless/ReviewAggregate.d.ts.map +1 -0
  50. package/dist/headless/ReviewAggregate.js +55 -0
  51. package/dist/headless/ReviewAggregate.js.map +1 -0
  52. package/dist/headless/ReviewForm.d.ts +83 -0
  53. package/dist/headless/ReviewForm.d.ts.map +1 -0
  54. package/dist/headless/ReviewForm.js +75 -0
  55. package/dist/headless/ReviewForm.js.map +1 -0
  56. package/dist/headless/ReviewList.d.ts +47 -0
  57. package/dist/headless/ReviewList.d.ts.map +1 -0
  58. package/dist/headless/ReviewList.js +51 -0
  59. package/dist/headless/ReviewList.js.map +1 -0
  60. package/dist/headless/ReviewsProvider.d.ts +19 -0
  61. package/dist/headless/ReviewsProvider.d.ts.map +1 -0
  62. package/dist/headless/ReviewsProvider.js +15 -0
  63. package/dist/headless/ReviewsProvider.js.map +1 -0
  64. package/dist/i18n/errorsMap.d.ts +12 -0
  65. package/dist/i18n/errorsMap.d.ts.map +1 -0
  66. package/dist/i18n/errorsMap.js +22 -0
  67. package/dist/i18n/errorsMap.js.map +1 -0
  68. package/dist/i18n/es.d.ts +19 -0
  69. package/dist/i18n/es.d.ts.map +1 -0
  70. package/dist/i18n/es.js +66 -0
  71. package/dist/i18n/es.js.map +1 -0
  72. package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
  73. package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
  74. package/dist/i18n/generated/errors.es.gen.js +58 -0
  75. package/dist/i18n/generated/errors.es.gen.js.map +1 -0
  76. package/dist/i18n/generated/errors.gen.d.ts +341 -0
  77. package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
  78. package/dist/i18n/generated/errors.gen.js +174 -0
  79. package/dist/i18n/generated/errors.gen.js.map +1 -0
  80. package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
  81. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
  82. package/dist/i18n/generated/errors.ru.gen.js +58 -0
  83. package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
  84. package/dist/i18n/keys.d.ts +59 -0
  85. package/dist/i18n/keys.d.ts.map +1 -0
  86. package/dist/i18n/keys.js +115 -0
  87. package/dist/i18n/keys.js.map +1 -0
  88. package/dist/i18n/ru.d.ts +22 -0
  89. package/dist/i18n/ru.d.ts.map +1 -0
  90. package/dist/i18n/ru.js +70 -0
  91. package/dist/i18n/ru.js.map +1 -0
  92. package/dist/index.d.ts +98 -0
  93. package/dist/index.d.ts.map +1 -0
  94. package/dist/index.js +92 -0
  95. package/dist/index.js.map +1 -0
  96. package/dist/model/context.d.ts +11 -0
  97. package/dist/model/context.d.ts.map +1 -0
  98. package/dist/model/context.js +16 -0
  99. package/dist/model/context.js.map +1 -0
  100. package/dist/model/list.d.ts +52 -0
  101. package/dist/model/list.d.ts.map +1 -0
  102. package/dist/model/list.js +60 -0
  103. package/dist/model/list.js.map +1 -0
  104. package/dist/model/mutations.d.ts +32 -0
  105. package/dist/model/mutations.d.ts.map +1 -0
  106. package/dist/model/mutations.js +44 -0
  107. package/dist/model/mutations.js.map +1 -0
  108. package/dist/model/queries.d.ts +65 -0
  109. package/dist/model/queries.d.ts.map +1 -0
  110. package/dist/model/queries.js +80 -0
  111. package/dist/model/queries.js.map +1 -0
  112. package/dist/model/queryKeys.d.ts +27 -0
  113. package/dist/model/queryKeys.d.ts.map +1 -0
  114. package/dist/model/queryKeys.js +14 -0
  115. package/dist/model/queryKeys.js.map +1 -0
  116. package/dist/model/rating.d.ts +57 -0
  117. package/dist/model/rating.d.ts.map +1 -0
  118. package/dist/model/rating.js +47 -0
  119. package/dist/model/rating.js.map +1 -0
  120. package/dist/model/refusals.d.ts +51 -0
  121. package/dist/model/refusals.d.ts.map +1 -0
  122. package/dist/model/refusals.js +103 -0
  123. package/dist/model/refusals.js.map +1 -0
  124. package/dist/model/runtime.d.ts +44 -0
  125. package/dist/model/runtime.d.ts.map +1 -0
  126. package/dist/model/runtime.js +15 -0
  127. package/dist/model/runtime.js.map +1 -0
  128. package/llms.txt +80 -0
  129. package/manifest.json +577 -0
  130. package/package.json +115 -0
  131. package/src/analytics/generated/events.json +7 -0
  132. package/src/api/generated/schema.ts +362 -0
  133. package/src/api/reviewsApi.ts +170 -0
  134. package/src/api/types.ts +140 -0
  135. package/src/default/ErrorAlert.tsx +46 -0
  136. package/src/default/RatingBadge.tsx +81 -0
  137. package/src/default/ReviewFormCard.tsx +139 -0
  138. package/src/default/ReviewListPanel.tsx +204 -0
  139. package/src/default/ReviewsPanel.tsx +74 -0
  140. package/src/default/index.ts +30 -0
  141. package/src/default/theme.tsx +66 -0
  142. package/src/default/types.ts +12 -0
  143. package/src/headless/ReviewAggregate.tsx +94 -0
  144. package/src/headless/ReviewForm.tsx +163 -0
  145. package/src/headless/ReviewList.tsx +99 -0
  146. package/src/headless/ReviewsProvider.tsx +20 -0
  147. package/src/i18n/errorsMap.ts +33 -0
  148. package/src/i18n/es.ts +81 -0
  149. package/src/i18n/generated/errors.es.gen.ts +64 -0
  150. package/src/i18n/generated/errors.gen.ts +205 -0
  151. package/src/i18n/generated/errors.json +387 -0
  152. package/src/i18n/generated/errors.ru.gen.ts +64 -0
  153. package/src/i18n/keys.ts +133 -0
  154. package/src/i18n/ru.ts +85 -0
  155. package/src/index.ts +161 -0
  156. package/src/model/context.tsx +28 -0
  157. package/src/model/list.ts +74 -0
  158. package/src/model/mutations.ts +62 -0
  159. package/src/model/queries.ts +122 -0
  160. package/src/model/queryKeys.ts +45 -0
  161. package/src/model/rating.ts +101 -0
  162. package/src/model/refusals.ts +114 -0
  163. package/src/model/runtime.ts +61 -0
  164. package/tsconfig.json +26 -0
@@ -0,0 +1,55 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { loadFailed, loadLoading, loadReady } from "@stapel/core";
3
+ import { useReviewsRuntime } from "../model/context.js";
4
+ import { useReviewAggregate } from "../model/queries.js";
5
+ import { ratingSummary } from "../model/rating.js";
6
+ /**
7
+ * The headless rating display — the same bag whether the number came from
8
+ * this module or from the composite. `AllowAny` upstream since 0.3.0, so a
9
+ * guest sees the rating and there is no "sign in" state here.
10
+ *
11
+ * ── Why `aggregate` is a prop, and not a second endpoint ───────────────────
12
+ *
13
+ * stapel-reviews can aggregate exactly ONE `(target_type, target_key)` per
14
+ * call. The storefront's product decision (spec fork F5) is that a review
15
+ * targets the SELLER for a specific listing — `target_type: "listing"`, one
16
+ * per author per listing — and the seller's own rating is therefore a ROLL-UP
17
+ * across every listing they own. No endpoint computes that: the composite
18
+ * does, server-side, as `shop.listing_review_summary`
19
+ * (`stapel_shop/projections.py`), and the roll-up over a seller's keys is the
20
+ * composite's projection to publish, on its own route.
21
+ *
22
+ * So this pair does the half it can honestly do: it renders `{avg, count}` —
23
+ * the projection's own field names, chosen upstream precisely so both modes
24
+ * answer one shape — from wherever the host got them. `source` says which,
25
+ * so a skin that wants to (a demo, an admin view) can show it. A seller
26
+ * header therefore reads
27
+ * `<ReviewAggregate target={{targetType: "seller", targetKey: id}}
28
+ * aggregate={rollup}>`: the target names the subject, the numbers come from
29
+ * the composite, and no request is made against an endpoint that could not
30
+ * have answered it.
31
+ *
32
+ * The gap is real and is recorded in the README rather than papered over with
33
+ * N+1 requests: a page that wants a seller rating today must be served the
34
+ * two numbers by its own backend.
35
+ */
36
+ export function ReviewAggregate(props) {
37
+ const runtime = useReviewsRuntime();
38
+ const supplied = props.aggregate;
39
+ const query = useReviewAggregate(props.target, {
40
+ enabled: supplied === undefined,
41
+ });
42
+ const state = supplied !== undefined
43
+ ? loadReady(ratingSummary(supplied))
44
+ : query.status === "error"
45
+ ? loadFailed(query.error)
46
+ : query.data === undefined
47
+ ? loadLoading()
48
+ : loadReady(ratingSummary(query.data));
49
+ return (_jsx(_Fragment, { children: props.children({
50
+ state,
51
+ source: supplied !== undefined ? "supplied" : "fetched",
52
+ max: runtime.ratingBounds.max,
53
+ }) }));
54
+ }
55
+ //# sourceMappingURL=ReviewAggregate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewAggregate.js","sourceRoot":"","sources":["../../src/headless/ReviewAggregate.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGlE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAgCnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC;IACjC,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE;QAC7C,OAAO,EAAE,QAAQ,KAAK,SAAS;KAChC,CAAC,CAAC;IAEH,MAAM,KAAK,GACT,QAAQ,KAAK,SAAS;QACpB,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,OAAO;YACxB,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;YACzB,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS;gBACxB,CAAC,CAAC,WAAW,EAAE;gBACf,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAE/C,OAAO,CACL,4BACG,KAAK,CAAC,QAAQ,CAAC;YACd,KAAK;YACL,MAAM,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;YACvD,GAAG,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;SAC9B,CAAC,GACD,CACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,83 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { ActionAvailability, FlowError } from "@stapel/core";
3
+ import type { Review, ReviewTarget } from "../api/types.js";
4
+ import type { ReviewRatingBounds } from "../model/runtime.js";
5
+ /** What `<ReviewForm>` hands its render prop. */
6
+ export interface ReviewFormBag {
7
+ /** The chosen rating, or `null` while nothing is chosen. */
8
+ readonly rating: number | null;
9
+ readonly setRating: (rating: number) => void;
10
+ readonly body: string;
11
+ readonly setBody: (body: string) => void;
12
+ /** The deployment's inclusive bounds — the star row draws exactly these. */
13
+ readonly bounds: ReviewRatingBounds;
14
+ /** Send it. A no-op while {@link canSubmit} is blocked. */
15
+ readonly submit: () => void;
16
+ /** Whether the button may be pressed, and if not, the sentence saying why. */
17
+ readonly canSubmit: ActionAvailability;
18
+ readonly submitting: boolean;
19
+ /**
20
+ * The persisted review, once it exists. Its `status` is the whole point:
21
+ * `published` means it is already on the page, `pending` means the
22
+ * deployment pre-moderates and it is NOT — and the person who just wrote it
23
+ * must be told that rather than left looking for it.
24
+ */
25
+ readonly submitted: Review | null;
26
+ /** `submitted?.status`, narrowed, with a fourth arm for an unknown state. */
27
+ readonly submittedVisibility: "published" | "pending" | "hidden" | "unknown" | null;
28
+ /**
29
+ * The server said this author has already reviewed this target
30
+ * (`error.400.reviews_duplicate_review` — a 400, not a 409; see
31
+ * `model/refusals.ts`). Or the host told us so up front via
32
+ * `alreadyReviewed`.
33
+ */
34
+ readonly alreadyReviewed: boolean;
35
+ /**
36
+ * The write failed with 401 — the form was offered to a signed-out reader.
37
+ *
38
+ * The LAST place this state survives. Since stapel-reviews 0.3.0 both reads
39
+ * are anonymous (`IsAuthenticatedOrReadOnly` on the list class, `AllowAny`
40
+ * on the aggregate), so a guest reads the reviews and only the POST refuses
41
+ * — there has to be an author to attribute the review to. That makes this a
42
+ * prompt to sign in at the moment it is actually true, rather than a wall in
43
+ * front of content.
44
+ */
45
+ readonly signInRequired: boolean;
46
+ /** The type's `can_review` callback refused this author for this target. */
47
+ readonly forbidden: boolean;
48
+ /**
49
+ * The refusal, for the error surface — `undefined` once it has been
50
+ * translated into one of the named flags above AND that flag is what the
51
+ * skin renders. Duplicate/forbidden/sign-in are named states with their own
52
+ * copy; everything else (an invalid rating the mirror let through, an
53
+ * outage) stays here.
54
+ */
55
+ readonly error: FlowError | undefined;
56
+ }
57
+ export interface ReviewFormProps {
58
+ readonly target: ReviewTarget;
59
+ /**
60
+ * The host already knows this author has reviewed the target — typically
61
+ * from `findOwnReview()` over the loaded list. An OPTIMISTIC input: it
62
+ * cannot see a pending review (the list is published-only), so a `false`
63
+ * here never means "the server will accept this".
64
+ */
65
+ readonly alreadyReviewed?: boolean;
66
+ readonly children: (bag: ReviewFormBag) => ReactNode;
67
+ }
68
+ /**
69
+ * The headless "rate this" form.
70
+ *
71
+ * The one rule it exists to hold: **the refusal is read by code, never by
72
+ * status**. "You have already reviewed this" arrives as a 400 whose code is
73
+ * `error.400.reviews_duplicate_review`, while the module's only 409 means the
74
+ * owner's reply already exists. A form that branched on the number would miss
75
+ * the first and mishandle the second.
76
+ *
77
+ * The second rule: a submitted review is not necessarily a visible one. The
78
+ * bag reports the created row's `status` so a pre-moderated deployment can
79
+ * say "sent — it will appear once it is checked", instead of leaving the
80
+ * author to hunt for a review that is deliberately invisible.
81
+ */
82
+ export declare function ReviewForm(props: ReviewFormProps): ReactElement;
83
+ //# sourceMappingURL=ReviewForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewForm.d.ts","sourceRoot":"","sources":["../../src/headless/ReviewForm.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAQ9D,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,mBAAmB,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC;IACpF;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,SAAS,CAAC;CACtD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAkE/D"}
@@ -0,0 +1,75 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useState } from "react";
3
+ import { actionAvailable, actionBlocked, firstBlock } from "@stapel/core";
4
+ import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
5
+ import { useReviewsRuntime } from "../model/context.js";
6
+ import { useSubmitReview } from "../model/mutations.js";
7
+ import { reviewVisibility } from "../model/list.js";
8
+ import { isDuplicateReview, isReviewingForbidden, isSignInRequired, toReviewsError, } from "../model/refusals.js";
9
+ /**
10
+ * The headless "rate this" form.
11
+ *
12
+ * The one rule it exists to hold: **the refusal is read by code, never by
13
+ * status**. "You have already reviewed this" arrives as a 400 whose code is
14
+ * `error.400.reviews_duplicate_review`, while the module's only 409 means the
15
+ * owner's reply already exists. A form that branched on the number would miss
16
+ * the first and mishandle the second.
17
+ *
18
+ * The second rule: a submitted review is not necessarily a visible one. The
19
+ * bag reports the created row's `status` so a pre-moderated deployment can
20
+ * say "sent — it will appear once it is checked", instead of leaving the
21
+ * author to hunt for a review that is deliberately invisible.
22
+ */
23
+ export function ReviewForm(props) {
24
+ const { target, alreadyReviewed = false, children } = props;
25
+ const runtime = useReviewsRuntime();
26
+ const mutation = useSubmitReview(target);
27
+ // Destructured because `mutation` is a new object every render while
28
+ // `mutate` is stable (TanStack v5 contract) — depending on the object would
29
+ // rebuild `submit` on every state change and re-render the whole render
30
+ // prop below it.
31
+ const { mutate } = mutation;
32
+ const [rating, setRating] = useState(null);
33
+ const [body, setBody] = useState("");
34
+ const submitted = mutation.data ?? null;
35
+ const failure = mutation.error ?? undefined;
36
+ const duplicate = failure !== undefined && isDuplicateReview(failure);
37
+ const forbidden = failure !== undefined && isReviewingForbidden(failure);
38
+ const signInRequired = failure !== undefined && isSignInRequired(failure);
39
+ const alreadyKnown = alreadyReviewed || duplicate;
40
+ const canSubmit = firstBlock(alreadyKnown
41
+ ? actionBlocked(REVIEWS_I18N_KEYS.submitBlockedDuplicate)
42
+ : actionAvailable(), submitted !== null
43
+ ? actionBlocked(REVIEWS_I18N_KEYS.submitBlockedSubmitted)
44
+ : actionAvailable(), mutation.isPending
45
+ ? actionBlocked(REVIEWS_I18N_KEYS.submitBlockedPending)
46
+ : actionAvailable(), rating === null
47
+ ? actionBlocked(REVIEWS_I18N_KEYS.submitBlockedNoRating)
48
+ : actionAvailable(), forbidden
49
+ ? actionBlocked(REVIEWS_I18N_KEYS.submitBlockedForbidden)
50
+ : actionAvailable());
51
+ const submit = useCallback(() => {
52
+ if (rating === null)
53
+ return;
54
+ mutate({ rating, ...(body.length > 0 ? { body } : {}) });
55
+ }, [rating, body, mutate]);
56
+ return (_jsx(_Fragment, { children: children({
57
+ rating,
58
+ setRating,
59
+ body,
60
+ setBody,
61
+ bounds: runtime.ratingBounds,
62
+ submit,
63
+ canSubmit,
64
+ submitting: mutation.isPending,
65
+ submitted,
66
+ submittedVisibility: submitted === null ? null : reviewVisibility(submitted.status),
67
+ alreadyReviewed: alreadyKnown,
68
+ signInRequired,
69
+ forbidden,
70
+ error: failure === undefined || duplicate || forbidden || signInRequired
71
+ ? undefined
72
+ : toReviewsError(failure),
73
+ }) }));
74
+ }
75
+ //# sourceMappingURL=ReviewForm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewForm.js","sourceRoot":"","sources":["../../src/headless/ReviewForm.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { ActionAvailability, LoadState } from "@stapel/core";
3
+ import type { Review, ReviewTarget } from "../api/types.js";
4
+ import type { UseReviewListOptions } from "../model/queries.js";
5
+ /** What `<ReviewList>` hands its render prop. */
6
+ export interface ReviewListBag {
7
+ readonly target: ReviewTarget;
8
+ /**
9
+ * The rows, newest first — `loading` / `ready` / `failed`, never a
10
+ * defaulted empty array. An empty READY list is the only honest way to say
11
+ * "this target has no reviews".
12
+ */
13
+ readonly state: LoadState<readonly Review[]>;
14
+ /** Ask for the next (older) page. */
15
+ readonly loadMore: () => void;
16
+ /** Whether there is another page, and if not, why the control is off. */
17
+ readonly more: ActionAvailability;
18
+ /** A next page is in flight (the rows already on screen stay on screen). */
19
+ readonly loadingMore: boolean;
20
+ /** Re-read the first page. */
21
+ readonly refresh: () => void;
22
+ /**
23
+ * The scope actually requested. `"all"` means pending/hidden rows were
24
+ * ASKED for — the server grants them only to a moderator/owner and narrows
25
+ * everyone else silently, so this is never a promise that they are here.
26
+ */
27
+ readonly include: "all" | undefined;
28
+ }
29
+ export interface ReviewListProps extends UseReviewListOptions {
30
+ readonly target: ReviewTarget;
31
+ readonly children: (bag: ReviewListBag) => ReactNode;
32
+ }
33
+ /**
34
+ * The headless review list: anchor-paginated, newest first, over one opaque
35
+ * `(target_type, target_key)`, readable by anyone.
36
+ *
37
+ * There is no `signInRequired` here: since stapel-reviews 0.3.0 the list is
38
+ * `IsAuthenticatedOrReadOnly`, so a guest gets the published rows. An empty
39
+ * READY list therefore means what it says — nobody has reviewed this target —
40
+ * and it is a state a signed-out visitor can legitimately reach.
41
+ *
42
+ * No markup, no strings — a render prop and a bag (frontend-standard §2). The
43
+ * antd rendering of the same bag is `@stapel/reviews-react/default`'s
44
+ * `<ReviewListPanel>`.
45
+ */
46
+ export declare function ReviewList(props: ReviewListProps): ReactElement;
47
+ //# sourceMappingURL=ReviewList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewList.d.ts","sourceRoot":"","sources":["../../src/headless/ReviewList.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAI5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC7C,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,8BAA8B;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,KAAK,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,SAAS,CAAC;CACtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAuC/D"}
@@ -0,0 +1,51 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback } from "react";
3
+ import { actionAvailable, actionBlocked, loadFailed, loadLoading, loadReady, } from "@stapel/core";
4
+ import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
5
+ import { reviewsFromPages } from "../model/list.js";
6
+ import { useReviewList } from "../model/queries.js";
7
+ /**
8
+ * The headless review list: anchor-paginated, newest first, over one opaque
9
+ * `(target_type, target_key)`, readable by anyone.
10
+ *
11
+ * There is no `signInRequired` here: since stapel-reviews 0.3.0 the list is
12
+ * `IsAuthenticatedOrReadOnly`, so a guest gets the published rows. An empty
13
+ * READY list therefore means what it says — nobody has reviewed this target —
14
+ * and it is a state a signed-out visitor can legitimately reach.
15
+ *
16
+ * No markup, no strings — a render prop and a bag (frontend-standard §2). The
17
+ * antd rendering of the same bag is `@stapel/reviews-react/default`'s
18
+ * `<ReviewListPanel>`.
19
+ */
20
+ export function ReviewList(props) {
21
+ const { target, children, ...options } = props;
22
+ const query = useReviewList(target, options);
23
+ const { fetchNextPage, refetch } = query;
24
+ const loadMore = useCallback(() => {
25
+ void fetchNextPage();
26
+ }, [fetchNextPage]);
27
+ const refresh = useCallback(() => {
28
+ void refetch();
29
+ }, [refetch]);
30
+ const rows = reviewsFromPages(query.data);
31
+ const state = query.status === "error"
32
+ ? loadFailed(query.error)
33
+ : rows === undefined
34
+ ? loadLoading()
35
+ : loadReady(rows);
36
+ const more = query.hasNextPage
37
+ ? query.isFetchingNextPage
38
+ ? actionBlocked(REVIEWS_I18N_KEYS.moreBlockedPending)
39
+ : actionAvailable()
40
+ : actionBlocked(REVIEWS_I18N_KEYS.moreBlockedEnd);
41
+ return (_jsx(_Fragment, { children: children({
42
+ target,
43
+ state,
44
+ loadMore,
45
+ more,
46
+ loadingMore: query.isFetchingNextPage,
47
+ refresh,
48
+ include: options.include,
49
+ }) }));
50
+ }
51
+ //# sourceMappingURL=ReviewList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewList.js","sourceRoot":"","sources":["../../src/headless/ReviewList.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EACL,eAAe,EACf,aAAa,EACb,UAAU,EACV,WAAW,EACX,SAAS,GACV,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiCpD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC;IAC/C,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAEzC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;QAChC,KAAK,aAAa,EAAE,CAAC;IACvB,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IACpB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,KAAK,OAAO,EAAE,CAAC;IACjB,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,KAAK,GACT,KAAK,CAAC,MAAM,KAAK,OAAO;QACtB,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;QACzB,CAAC,CAAC,IAAI,KAAK,SAAS;YAClB,CAAC,CAAC,WAAW,EAAE;YACf,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAExB,MAAM,IAAI,GAAuB,KAAK,CAAC,WAAW;QAChD,CAAC,CAAC,KAAK,CAAC,kBAAkB;YACxB,CAAC,CAAC,aAAa,CAAC,iBAAiB,CAAC,kBAAkB,CAAC;YACrD,CAAC,CAAC,eAAe,EAAE;QACrB,CAAC,CAAC,aAAa,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;IAEpD,OAAO,CACL,4BACG,QAAQ,CAAC;YACR,MAAM;YACN,KAAK;YACL,QAAQ;YACR,IAAI;YACJ,WAAW,EAAE,KAAK,CAAC,kBAAkB;YACrC,OAAO;YACP,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,GACD,CACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { ReviewsRuntime } from "../model/runtime.js";
3
+ /**
4
+ * Provides the wired {@link ReviewsRuntime} to every reviews hook and headless
5
+ * component below it. Bring your own visual shell — this component renders
6
+ * nothing of its own. (Core's `createModuleContext` provider, bound to this
7
+ * pair — slim wave §21/S2.)
8
+ *
9
+ * ```tsx
10
+ * const runtime = createReviewsRuntime({ baseUrl: "/reviews/api/v1" });
11
+ * // give runtime.client to core's <StapelConfigProvider config={{ client }}>
12
+ * <ReviewsProvider runtime={runtime}>{app}</ReviewsProvider>
13
+ * ```
14
+ */
15
+ export declare const ReviewsProvider: (props: {
16
+ runtime: ReviewsRuntime;
17
+ children: ReactNode;
18
+ }) => ReactElement;
19
+ //# sourceMappingURL=ReviewsProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewsProvider.d.ts","sourceRoot":"","sources":["../../src/headless/ReviewsProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,EAAE,CAAC,KAAK,EAAE;IACpC,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,SAAS,CAAC;CACrB,KAAK,YAA6B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { ModuleProvider } from "../model/context.js";
2
+ /**
3
+ * Provides the wired {@link ReviewsRuntime} to every reviews hook and headless
4
+ * component below it. Bring your own visual shell — this component renders
5
+ * nothing of its own. (Core's `createModuleContext` provider, bound to this
6
+ * pair — slim wave §21/S2.)
7
+ *
8
+ * ```tsx
9
+ * const runtime = createReviewsRuntime({ baseUrl: "/reviews/api/v1" });
10
+ * // give runtime.client to core's <StapelConfigProvider config={{ client }}>
11
+ * <ReviewsProvider runtime={runtime}>{app}</ReviewsProvider>
12
+ * ```
13
+ */
14
+ export const ReviewsProvider = ModuleProvider;
15
+ //# sourceMappingURL=ReviewsProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewsProvider.js","sourceRoot":"","sources":["../../src/headless/ReviewsProvider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAGP,cAAc,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Remediation } from "./generated/errors.gen.js";
2
+ export { REVIEWS_ERRORS, REVIEWS_ERROR_CODES, reviewsErrorBundleEn, } from "./generated/errors.gen.js";
3
+ export type { ReviewsErrorCode, ReviewsErrorSpec, Remediation, } from "./generated/errors.gen.js";
4
+ /**
5
+ * Resolve a backend error code to its remediation hint, or `undefined` for a
6
+ * code this module doesn't know (a cross-cutting `stapel.http.*` fallback, or
7
+ * one of this pair's own client-side rules such as
8
+ * `reviews.submit.blocked.no_rating` — which is not a backend code and
9
+ * deliberately has no entry here).
10
+ */
11
+ export declare function explainReviewsError(code: string): Remediation | undefined;
12
+ //# sourceMappingURL=errorsMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorsMap.d.ts","sourceRoot":"","sources":["../../src/i18n/errorsMap.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,GACZ,MAAM,2BAA2B,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGzE"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The pair's error map (frontend-standard §4 checklist #7): the generated
3
+ * `code → { status, params, remediation, en }` catalog plus a tiny `explain()`
4
+ * lookup. Backs the manifest `errors` block and gives hosts a mechanical UX
5
+ * branch beside `t(code, params)`. The map itself is generated from the
6
+ * backend registry (`pnpm gen:errors`); this file only adds the lookup helper
7
+ * and re-exports the public surface.
8
+ */
9
+ import { REVIEWS_ERRORS } from "./generated/errors.gen.js";
10
+ export { REVIEWS_ERRORS, REVIEWS_ERROR_CODES, reviewsErrorBundleEn, } from "./generated/errors.gen.js";
11
+ /**
12
+ * Resolve a backend error code to its remediation hint, or `undefined` for a
13
+ * code this module doesn't know (a cross-cutting `stapel.http.*` fallback, or
14
+ * one of this pair's own client-side rules such as
15
+ * `reviews.submit.blocked.no_rating` — which is not a backend code and
16
+ * deliberately has no entry here).
17
+ */
18
+ export function explainReviewsError(code) {
19
+ return REVIEWS_ERRORS[code]
20
+ ?.remediation;
21
+ }
22
+ //# sourceMappingURL=errorsMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorsMap.js","sourceRoot":"","sources":["../../src/i18n/errorsMap.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AAOnC;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAQ,cAA+D,CAAC,IAAI,CAAC;QAC3E,EAAE,WAAW,CAAC;AAClB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { I18nDictionary, I18nEngine } from "@stapel/core";
2
+ export { reviewsErrorBundleEs } from "./generated/errors.es.gen.js";
3
+ /**
4
+ * Spanish bundle for reviews-react — the `@stapel/reviews-react/i18n/es`
5
+ * subpath (i18n-shipping.md §2), opt-in exactly like `./i18n/ru`.
6
+ *
7
+ * Same two sources, same reason (see `./ru.ts`): 42 cross-cutting keys come
8
+ * from stapel-core's catalogue through the generated bundle, and the 9 keys
9
+ * stapel-reviews owns are authored below until upstream ships
10
+ * `translations/errors.es.json`.
11
+ *
12
+ * The UI copy is carried here too, not just the error keys: a review form is
13
+ * a surface a buyer reads word by word, and half a translation is worse than
14
+ * either language on its own (the chat-react precedent).
15
+ */
16
+ export declare const reviewsI18nBundleEs: I18nDictionary;
17
+ /** Register the Spanish bundle into a core i18n engine. */
18
+ export declare function registerReviewsI18nEs(engine: I18nEngine, locale?: string): void;
19
+ //# sourceMappingURL=es.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"es.d.ts","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,EAAE,cAyDjC,CAAC;AAEF,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAE7E"}
@@ -0,0 +1,66 @@
1
+ import { reviewsErrorBundleEs } from "./generated/errors.es.gen.js";
2
+ export { reviewsErrorBundleEs } from "./generated/errors.es.gen.js";
3
+ /**
4
+ * Spanish bundle for reviews-react — the `@stapel/reviews-react/i18n/es`
5
+ * subpath (i18n-shipping.md §2), opt-in exactly like `./i18n/ru`.
6
+ *
7
+ * Same two sources, same reason (see `./ru.ts`): 42 cross-cutting keys come
8
+ * from stapel-core's catalogue through the generated bundle, and the 9 keys
9
+ * stapel-reviews owns are authored below until upstream ships
10
+ * `translations/errors.es.json`.
11
+ *
12
+ * The UI copy is carried here too, not just the error keys: a review form is
13
+ * a surface a buyer reads word by word, and half a translation is worse than
14
+ * either language on its own (the chat-react precedent).
15
+ */
16
+ export const reviewsI18nBundleEs = {
17
+ // Backend error codes — generated es texts for every key core owns.
18
+ ...reviewsErrorBundleEs,
19
+ // Backend error codes stapel-reviews owns — authored here.
20
+ "error.400.reviews_duplicate_review": "Ya has valorado esto",
21
+ "error.400.reviews_invalid_moderation_action": "La acción de moderación debe ser una de: ocultar, publicar",
22
+ "error.400.reviews_invalid_rating": "La valoración está fuera del rango permitido",
23
+ "error.400.reviews_response_not_allowed": "Las respuestas están desactivadas para este tipo de objeto",
24
+ "error.400.reviews_unknown_target_type": "Tipo de objeto de reseña desconocido",
25
+ "error.403.reviews_cannot_moderate": "No puedes moderar las reseñas de este objeto",
26
+ "error.403.reviews_cannot_review": "No puedes reseñar este objeto",
27
+ "error.404.reviews_review_not_found": "Reseña no encontrada",
28
+ "error.409.reviews_already_responded": "Esta reseña ya tiene una respuesta",
29
+ // UI copy.
30
+ "reviews.error.unknown": "Algo ha fallado con las reseñas",
31
+ "reviews.list.heading": "Reseñas",
32
+ "reviews.list.empty": "Todavía no hay reseñas",
33
+ "reviews.list.load_more": "Ver más",
34
+ "reviews.list.refresh": "Actualizar",
35
+ "reviews.list.more.blocked.end": "Eso es todo",
36
+ "reviews.list.more.blocked.pending": "Cargando…",
37
+ "reviews.review.author_fallback": "Un cliente",
38
+ "reviews.review.response_heading": "Respuesta del vendedor",
39
+ "reviews.status.pending": "Pendiente de moderación",
40
+ "reviews.status.hidden": "Oculta por moderación",
41
+ "reviews.status.unknown": "Estado desconocido: {status}",
42
+ "reviews.rating.none": "Todavía no hay reseñas",
43
+ "reviews.rating.value": "{avg} de {max}",
44
+ "reviews.rating.count": "{count} reseñas",
45
+ "reviews.rating.star_label": "{index} de {max}",
46
+ "reviews.form.heading": "Valora esto",
47
+ "reviews.form.rating_label": "Tu valoración",
48
+ "reviews.form.body_label": "Tu reseña",
49
+ "reviews.form.body_placeholder": "¿Qué tal fue el trato? (opcional)",
50
+ "reviews.form.submit": "Enviar",
51
+ "reviews.form.sent.published": "Gracias — tu reseña está publicada",
52
+ "reviews.form.sent.pending": "Gracias — tu reseña aparecerá cuando se haya revisado",
53
+ "reviews.form.sent.hidden": "Tu reseña se ha guardado, pero no se muestra",
54
+ "reviews.form.sent.unknown": "Tu reseña se ha guardado",
55
+ "reviews.form.sign_in_required": "Inicia sesión para dejar una reseña",
56
+ "reviews.submit.blocked.no_rating": "Elige primero una valoración",
57
+ "reviews.submit.blocked.pending": "Enviando…",
58
+ "reviews.submit.blocked.duplicate": "Ya has valorado esto",
59
+ "reviews.submit.blocked.submitted": "Tu reseña se ha enviado",
60
+ "reviews.submit.blocked.forbidden": "No puedes reseñar esto",
61
+ };
62
+ /** Register the Spanish bundle into a core i18n engine. */
63
+ export function registerReviewsI18nEs(engine, locale = "es") {
64
+ engine.registerBundle(locale, reviewsI18nBundleEs);
65
+ }
66
+ //# sourceMappingURL=es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"es.js","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmB;IACjD,oEAAoE;IACpE,GAAG,oBAAoB;IAEvB,2DAA2D;IAC3D,oCAAoC,EAAE,sBAAsB;IAC5D,6CAA6C,EAC3C,4DAA4D;IAC9D,kCAAkC,EAAE,8CAA8C;IAClF,wCAAwC,EACtC,4DAA4D;IAC9D,uCAAuC,EAAE,sCAAsC;IAC/E,mCAAmC,EACjC,8CAA8C;IAChD,iCAAiC,EAAE,+BAA+B;IAClE,oCAAoC,EAAE,sBAAsB;IAC5D,qCAAqC,EAAE,oCAAoC;IAE3E,WAAW;IACX,uBAAuB,EAAE,iCAAiC;IAE1D,sBAAsB,EAAE,SAAS;IACjC,oBAAoB,EAAE,wBAAwB;IAC9C,wBAAwB,EAAE,SAAS;IACnC,sBAAsB,EAAE,YAAY;IACpC,+BAA+B,EAAE,aAAa;IAC9C,mCAAmC,EAAE,WAAW;IAEhD,gCAAgC,EAAE,YAAY;IAC9C,iCAAiC,EAAE,wBAAwB;IAE3D,wBAAwB,EAAE,yBAAyB;IACnD,uBAAuB,EAAE,uBAAuB;IAChD,wBAAwB,EAAE,8BAA8B;IAExD,qBAAqB,EAAE,wBAAwB;IAC/C,sBAAsB,EAAE,gBAAgB;IACxC,sBAAsB,EAAE,iBAAiB;IACzC,2BAA2B,EAAE,kBAAkB;IAE/C,sBAAsB,EAAE,aAAa;IACrC,2BAA2B,EAAE,eAAe;IAC5C,yBAAyB,EAAE,WAAW;IACtC,+BAA+B,EAAE,mCAAmC;IACpE,qBAAqB,EAAE,QAAQ;IAC/B,6BAA6B,EAAE,oCAAoC;IACnE,2BAA2B,EACzB,uDAAuD;IACzD,0BAA0B,EAAE,8CAA8C;IAC1E,2BAA2B,EAAE,0BAA0B;IACvD,+BAA+B,EAAE,qCAAqC;IAEtE,kCAAkC,EAAE,8BAA8B;IAClE,gCAAgC,EAAE,WAAW;IAC7C,kCAAkC,EAAE,sBAAsB;IAC1D,kCAAkC,EAAE,yBAAyB;IAC7D,kCAAkC,EAAE,wBAAwB;CAC7D,CAAC;AAEF,2DAA2D;AAC3D,MAAM,UAAU,qBAAqB,CAAC,MAAkB,EAAE,MAAM,GAAG,IAAI;IACrE,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { ReviewsErrorCode } from "./errors.gen.js";
2
+ /**
3
+ * `es` texts for the backend error codes this catalog carries.
4
+ *
5
+ * PARTIAL, and deliberately typed to say so: 9 key(s) owned by
6
+ * stapel_reviews are absent, because that owner ships no locale catalog
7
+ * (ERRORS_LOCALE_EXEMPT_OWNERS). English for them still comes from the registry
8
+ * artifact via the en bundle; the pair layers its own authored `es`
9
+ * strings over this one until upstream ships translations. `Partial` is what
10
+ * makes the gap visible to TypeScript instead of a silent English fallback.
11
+ *
12
+ * Import from the pair's `./i18n/es` subpath, NOT the main entry, so
13
+ * the locale stays out of hosts that don't ship it.
14
+ */
15
+ export declare const reviewsErrorBundleEs: Partial<Record<ReviewsErrorCode, string>>;
16
+ //# sourceMappingURL=errors.es.gen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.es.gen.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated/errors.es.gen.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CA2C1E,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `es` texts for the backend error codes this catalog carries.
3
+ *
4
+ * PARTIAL, and deliberately typed to say so: 9 key(s) owned by
5
+ * stapel_reviews are absent, because that owner ships no locale catalog
6
+ * (ERRORS_LOCALE_EXEMPT_OWNERS). English for them still comes from the registry
7
+ * artifact via the en bundle; the pair layers its own authored `es`
8
+ * strings over this one until upstream ships translations. `Partial` is what
9
+ * makes the gap visible to TypeScript instead of a silent English fallback.
10
+ *
11
+ * Import from the pair's `./i18n/es` subpath, NOT the main entry, so
12
+ * the locale stays out of hosts that don't ship it.
13
+ */
14
+ export const reviewsErrorBundleEs = {
15
+ "error.400.bad_request": "Solicitud incorrecta",
16
+ "error.400.captcha_invalid": "La verificación del captcha ha fallado. Inténtalo de nuevo.",
17
+ "error.400.captcha_required": "Se requiere el token del captcha.",
18
+ "error.400.expected_list": "Se esperaba una lista de elementos",
19
+ "error.400.field.blank": "{field} no puede estar vacío",
20
+ "error.400.field.does_not_exist": "{field} no existe",
21
+ "error.400.field.invalid": "{field} no es válido",
22
+ "error.400.field.invalid_choice": "{field} no es una opción válida",
23
+ "error.400.field.max_length": "{field} debe tener como máximo {max_length} caracteres",
24
+ "error.400.field.max_value": "{field} debe ser como máximo {max_value}",
25
+ "error.400.field.min_length": "{field} debe tener al menos {min_length} caracteres",
26
+ "error.400.field.min_value": "{field} debe ser como mínimo {min_value}",
27
+ "error.400.field.null": "{field} no puede ser nulo",
28
+ "error.400.field.required": "{field} es obligatorio",
29
+ "error.400.field.unique": "{field} debe ser único",
30
+ "error.400.invalid_ad_id": "ID de anuncio no válido",
31
+ "error.400.validation_error": "Error de validación",
32
+ "error.400.verification_failed": "La verificación ha fallado",
33
+ "error.400.verification_invalid_factor": "Este factor de verificación no está disponible",
34
+ "error.401.unauthorized": "Se requiere autenticación",
35
+ "error.402.payment_required": "Se requiere pago",
36
+ "error.403.forbidden": "No tienes permiso para realizar esta acción",
37
+ "error.403.network_blocked": "No se permiten solicitudes desde esta red.",
38
+ "error.403.verification_enrollment_required": "Es necesario registrar un factor de verificación.",
39
+ "error.403.verification_required": "Se requiere verificación adicional",
40
+ "error.404.ad_not_found": "Anuncio no encontrado",
41
+ "error.404.not_found": "Recurso solicitado no encontrado",
42
+ "error.404.verification_challenge_not_found": "Desafío de verificación no encontrado o caducado",
43
+ "error.405.method_not_allowed": "Método no permitido",
44
+ "error.406.not_acceptable": "No aceptable",
45
+ "error.408.request_timeout": "Tiempo de espera de la solicitud agotado",
46
+ "error.409.conflict": "El recurso ya existe",
47
+ "error.410.gone": "El recurso se ha eliminado permanentemente",
48
+ "error.413.payload_too_large": "El cuerpo de la solicitud es demasiado grande",
49
+ "error.415.unsupported_media_type": "Tipo de contenido no compatible",
50
+ "error.422.unprocessable_entity": "Entidad no procesable",
51
+ "error.423.locked": "El recurso está bloqueado",
52
+ "error.423.verification_locked": "Demasiados intentos fallidos — verificación bloqueada",
53
+ "error.429.rate_limit": "Demasiados intentos. Inténtalo de nuevo en {retry_after_minutes} minutos.",
54
+ "error.429.too_many_requests": "Demasiadas solicitudes. Inténtalo de nuevo más tarde.",
55
+ "error.500.internal": "Algo salió mal",
56
+ "error.503.mandate_unavailable": "No se puede verificar el mandato del espacio de trabajo",
57
+ };
58
+ //# sourceMappingURL=errors.es.gen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.es.gen.js","sourceRoot":"","sources":["../../../src/i18n/generated/errors.es.gen.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA8C;IAC7E,uBAAuB,EAAE,sBAAsB;IAC/C,2BAA2B,EAAE,6DAA6D;IAC1F,4BAA4B,EAAE,mCAAmC;IACjE,yBAAyB,EAAE,oCAAoC;IAC/D,uBAAuB,EAAE,8BAA8B;IACvD,gCAAgC,EAAE,mBAAmB;IACrD,yBAAyB,EAAE,sBAAsB;IACjD,gCAAgC,EAAE,iCAAiC;IACnE,4BAA4B,EAAE,wDAAwD;IACtF,2BAA2B,EAAE,0CAA0C;IACvE,4BAA4B,EAAE,qDAAqD;IACnF,2BAA2B,EAAE,0CAA0C;IACvE,sBAAsB,EAAE,2BAA2B;IACnD,0BAA0B,EAAE,wBAAwB;IACpD,wBAAwB,EAAE,wBAAwB;IAClD,yBAAyB,EAAE,yBAAyB;IACpD,4BAA4B,EAAE,qBAAqB;IACnD,+BAA+B,EAAE,4BAA4B;IAC7D,uCAAuC,EAAE,gDAAgD;IACzF,wBAAwB,EAAE,2BAA2B;IACrD,4BAA4B,EAAE,kBAAkB;IAChD,qBAAqB,EAAE,6CAA6C;IACpE,2BAA2B,EAAE,4CAA4C;IACzE,4CAA4C,EAAE,mDAAmD;IACjG,iCAAiC,EAAE,oCAAoC;IACvE,wBAAwB,EAAE,uBAAuB;IACjD,qBAAqB,EAAE,kCAAkC;IACzD,4CAA4C,EAAE,kDAAkD;IAChG,8BAA8B,EAAE,qBAAqB;IACrD,0BAA0B,EAAE,cAAc;IAC1C,2BAA2B,EAAE,0CAA0C;IACvE,oBAAoB,EAAE,sBAAsB;IAC5C,gBAAgB,EAAE,4CAA4C;IAC9D,6BAA6B,EAAE,+CAA+C;IAC9E,kCAAkC,EAAE,iCAAiC;IACrE,gCAAgC,EAAE,uBAAuB;IACzD,kBAAkB,EAAE,2BAA2B;IAC/C,+BAA+B,EAAE,uDAAuD;IACxF,sBAAsB,EAAE,2EAA2E;IACnG,6BAA6B,EAAE,uDAAuD;IACtF,oBAAoB,EAAE,gBAAgB;IACtC,+BAA+B,EAAE,yDAAyD;CAC3F,CAAC"}