@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.
- package/CHANGELOG.md +1 -0
- package/MODULE.md +151 -0
- package/README.md +207 -0
- package/dist/api/generated/schema.d.ts +360 -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/reviewsApi.d.ts +89 -0
- package/dist/api/reviewsApi.d.ts.map +1 -0
- package/dist/api/reviewsApi.js +51 -0
- package/dist/api/reviewsApi.js.map +1 -0
- package/dist/api/types.d.ts +127 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -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/RatingBadge.d.ts +17 -0
- package/dist/default/RatingBadge.d.ts.map +1 -0
- package/dist/default/RatingBadge.js +23 -0
- package/dist/default/RatingBadge.js.map +1 -0
- package/dist/default/ReviewFormCard.d.ts +25 -0
- package/dist/default/ReviewFormCard.d.ts.map +1 -0
- package/dist/default/ReviewFormCard.js +42 -0
- package/dist/default/ReviewFormCard.js.map +1 -0
- package/dist/default/ReviewListPanel.d.ts +29 -0
- package/dist/default/ReviewListPanel.d.ts.map +1 -0
- package/dist/default/ReviewListPanel.js +47 -0
- package/dist/default/ReviewListPanel.js.map +1 -0
- package/dist/default/ReviewsPanel.d.ts +33 -0
- package/dist/default/ReviewsPanel.d.ts.map +1 -0
- package/dist/default/ReviewsPanel.js +17 -0
- package/dist/default/ReviewsPanel.js.map +1 -0
- package/dist/default/index.d.ts +31 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +25 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/theme.d.ts +24 -0
- package/dist/default/theme.d.ts.map +1 -0
- package/dist/default/theme.js +39 -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/headless/ReviewAggregate.d.ts +63 -0
- package/dist/headless/ReviewAggregate.d.ts.map +1 -0
- package/dist/headless/ReviewAggregate.js +55 -0
- package/dist/headless/ReviewAggregate.js.map +1 -0
- package/dist/headless/ReviewForm.d.ts +83 -0
- package/dist/headless/ReviewForm.d.ts.map +1 -0
- package/dist/headless/ReviewForm.js +75 -0
- package/dist/headless/ReviewForm.js.map +1 -0
- package/dist/headless/ReviewList.d.ts +47 -0
- package/dist/headless/ReviewList.d.ts.map +1 -0
- package/dist/headless/ReviewList.js +51 -0
- package/dist/headless/ReviewList.js.map +1 -0
- package/dist/headless/ReviewsProvider.d.ts +19 -0
- package/dist/headless/ReviewsProvider.d.ts.map +1 -0
- package/dist/headless/ReviewsProvider.js +15 -0
- package/dist/headless/ReviewsProvider.js.map +1 -0
- package/dist/i18n/errorsMap.d.ts +12 -0
- package/dist/i18n/errorsMap.d.ts.map +1 -0
- package/dist/i18n/errorsMap.js +22 -0
- package/dist/i18n/errorsMap.js.map +1 -0
- package/dist/i18n/es.d.ts +19 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +66 -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 +341 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.gen.js +174 -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 +59 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +115 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +22 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +70 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +92 -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/list.d.ts +52 -0
- package/dist/model/list.d.ts.map +1 -0
- package/dist/model/list.js +60 -0
- package/dist/model/list.js.map +1 -0
- package/dist/model/mutations.d.ts +32 -0
- package/dist/model/mutations.d.ts.map +1 -0
- package/dist/model/mutations.js +44 -0
- package/dist/model/mutations.js.map +1 -0
- package/dist/model/queries.d.ts +65 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +80 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +27 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +14 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/rating.d.ts +57 -0
- package/dist/model/rating.d.ts.map +1 -0
- package/dist/model/rating.js +47 -0
- package/dist/model/rating.js.map +1 -0
- package/dist/model/refusals.d.ts +51 -0
- package/dist/model/refusals.d.ts.map +1 -0
- package/dist/model/refusals.js +103 -0
- package/dist/model/refusals.js.map +1 -0
- package/dist/model/runtime.d.ts +44 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +15 -0
- package/dist/model/runtime.js.map +1 -0
- package/llms.txt +80 -0
- package/manifest.json +577 -0
- package/package.json +115 -0
- package/src/analytics/generated/events.json +7 -0
- package/src/api/generated/schema.ts +362 -0
- package/src/api/reviewsApi.ts +170 -0
- package/src/api/types.ts +140 -0
- package/src/default/ErrorAlert.tsx +46 -0
- package/src/default/RatingBadge.tsx +81 -0
- package/src/default/ReviewFormCard.tsx +139 -0
- package/src/default/ReviewListPanel.tsx +204 -0
- package/src/default/ReviewsPanel.tsx +74 -0
- package/src/default/index.ts +30 -0
- package/src/default/theme.tsx +66 -0
- package/src/default/types.ts +12 -0
- package/src/headless/ReviewAggregate.tsx +94 -0
- package/src/headless/ReviewForm.tsx +163 -0
- package/src/headless/ReviewList.tsx +99 -0
- package/src/headless/ReviewsProvider.tsx +20 -0
- package/src/i18n/errorsMap.ts +33 -0
- package/src/i18n/es.ts +81 -0
- package/src/i18n/generated/errors.es.gen.ts +64 -0
- package/src/i18n/generated/errors.gen.ts +205 -0
- package/src/i18n/generated/errors.json +387 -0
- package/src/i18n/generated/errors.ru.gen.ts +64 -0
- package/src/i18n/keys.ts +133 -0
- package/src/i18n/ru.ts +85 -0
- package/src/index.ts +161 -0
- package/src/model/context.tsx +28 -0
- package/src/model/list.ts +74 -0
- package/src/model/mutations.ts +62 -0
- package/src/model/queries.ts +122 -0
- package/src/model/queryKeys.ts +45 -0
- package/src/model/rating.ts +101 -0
- package/src/model/refusals.ts +114 -0
- package/src/model/runtime.ts +61 -0
- 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"}
|
package/dist/i18n/es.js
ADDED
|
@@ -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"}
|