@stapel/reviews-react 0.1.0 → 0.3.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 (42) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +21 -0
  3. package/MODULE.md +16 -0
  4. package/README.md +8 -0
  5. package/dist/api/generated/schema.d.ts +4 -4
  6. package/dist/default/ReviewFormCard.d.ts +2 -1
  7. package/dist/default/ReviewFormCard.d.ts.map +1 -1
  8. package/dist/default/ReviewFormCard.js +6 -3
  9. package/dist/default/ReviewFormCard.js.map +1 -1
  10. package/dist/default/ReviewsPanel.d.ts +2 -1
  11. package/dist/default/ReviewsPanel.d.ts.map +1 -1
  12. package/dist/default/ReviewsPanel.js +2 -2
  13. package/dist/default/ReviewsPanel.js.map +1 -1
  14. package/dist/default/SignInLink.d.ts +28 -0
  15. package/dist/default/SignInLink.d.ts.map +1 -0
  16. package/dist/default/SignInLink.js +17 -0
  17. package/dist/default/SignInLink.js.map +1 -0
  18. package/dist/default/index.d.ts +2 -0
  19. package/dist/default/index.d.ts.map +1 -1
  20. package/dist/default/index.js +1 -0
  21. package/dist/default/index.js.map +1 -1
  22. package/dist/i18n/es.d.ts.map +1 -1
  23. package/dist/i18n/es.js +1 -0
  24. package/dist/i18n/es.js.map +1 -1
  25. package/dist/i18n/keys.d.ts +2 -0
  26. package/dist/i18n/keys.d.ts.map +1 -1
  27. package/dist/i18n/keys.js +3 -0
  28. package/dist/i18n/keys.js.map +1 -1
  29. package/dist/i18n/ru.d.ts.map +1 -1
  30. package/dist/i18n/ru.js +1 -0
  31. package/dist/i18n/ru.js.map +1 -1
  32. package/llms.txt +1 -1
  33. package/manifest.json +2 -1
  34. package/package.json +14 -14
  35. package/src/analytics/generated/events.json +1 -1
  36. package/src/default/ReviewFormCard.tsx +12 -4
  37. package/src/default/ReviewsPanel.tsx +4 -2
  38. package/src/default/SignInLink.tsx +54 -0
  39. package/src/default/index.ts +2 -0
  40. package/src/i18n/es.ts +1 -0
  41. package/src/i18n/keys.ts +3 -0
  42. package/src/i18n/ru.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1 +1,114 @@
1
1
  # @stapel/reviews-react
2
+
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3e2e2a3: A blocked control now carries the door, not just the reason: `signIn`
8
+
9
+ `actionBlocked` ended the grey-rectangle incident by making every switched-off
10
+ control state its reason. It did not end the next one. "Sign in to save this",
11
+ "sign in to leave a review", "sign in to message the seller" are reasons whose
12
+ next action is a LINK, and no pair took one — so the storefront had to put its
13
+ own notice a screen away from each of the three controls it was about, and
14
+ named it a gap rather than shipping it (Wave D, G-3).
15
+
16
+ All three now take the same prop, core's `SignInCta`:
17
+
18
+ ```tsx
19
+ <ListingCard listing={row} signIn={{ href: `/login?next=${here}` }} />
20
+ <ReviewsPanel target={target} signIn={{ href: `/login?next=${here}` }} />
21
+ <StartChatButton sellerId={sellerId} signIn={{ onSignIn: () => openModal() }} />
22
+ ```
23
+
24
+ `{href}` **or** `{onSignIn}`, never both. Omit it and the reason renders alone,
25
+ with no trailing whitespace where the link is not — a host with no sign-in
26
+ route is a supported host.
27
+
28
+ Two more things each pair had to fix to make the door reachable:
29
+
30
+ - **listings**: the favourite's reason lived only in a `title` on a DISABLED
31
+ button, which receives no pointer events in any browser — core's own
32
+ `actionGate.ts` calls that "a reason nobody can read". It is now text beside
33
+ the heart (`listings-card-favorite-blocked`), with the link inside it. The
34
+ heart is still never hidden from a visitor.
35
+ - **chat**: `StartDirectChat` had no mandate gate at all, so a visitor could
36
+ press "message the seller" and collect a 401 — a refusal delivered at the one
37
+ moment it is useless. The axis is now the first arm of its `firstBlock`, read
38
+ through core's `MandateSource` seam. `member` may write; `guest`/`anonymous`
39
+ are told to sign in; `asking` says we are still asking. `unavailable` stays
40
+ AVAILABLE on purpose: that is what core answers outside a `<MandateProvider>`
41
+ too, and a host that never wired the axis must not lose its button — "we
42
+ could not ask" is not "you may not". This raises chat-react's `@stapel/core`
43
+ floor to `>=0.15.0`, where `useMandate`/`matchMandate` shipped.
44
+
45
+ The link's LABEL is each pair's own (`listings.card.sign_in`,
46
+ `reviews.form.sign_in`, `chat.start.sign_in`), in all three locales — core
47
+ floors `en` and `ru`, and these pairs also ship `es`.
48
+
49
+ ## 0.2.0
50
+
51
+ ### Minor Changes
52
+
53
+ - 23eeaff: New pair: `@stapel/reviews-react` — ratings and reviews of an opaque
54
+ `(target_type, target_key)`, with the four lies the contract makes easy
55
+ refused once, here, instead of being rediscovered per host.
56
+
57
+ - **A zero average is not a zero rating.** `GET /reviews/aggregate` answers
58
+ `{"avg": 0.0, "count": 0}` for a target nobody has rated, and its own schema
59
+ says so. `ratingSummary()` reports `rated: false` there — and has no `avg` on
60
+ that branch of the type — so a star row cannot be drawn from it. Same class
61
+ as `data ?? []`: a number that was never measured, displayed as if it had
62
+ been measured and found to be the worst possible score.
63
+ - **"You have already reviewed this" is a 400.** It is
64
+ `error.400.reviews_duplicate_review`, while the module's ONLY 409
65
+ (`error.409.reviews_already_responded`) is about the seller's reply — so a
66
+ form branching on the status misses the first and mishandles the second.
67
+ Every refusal here is read by CODE. The optimistic pre-check
68
+ (`findOwnReview`) exists and its hole is written down: the list is
69
+ published-only, so under pre-moderation the author's own pending review is
70
+ invisible to its author and the server is the one that says no.
71
+ - **A guest reads the reviews; only the write asks them to sign in.** Both
72
+ reads are anonymous (`IsAuthenticatedOrReadOnly` on the list class — GET
73
+ open, POST still needs an author to attribute the review to — and `AllowAny`
74
+ on the aggregate, both throttled from the module's own settings). So
75
+ `signInRequired` lives on the form bag alone, where a 401 is still the honest
76
+ answer, and an empty list means what it says to everybody. Nothing new became
77
+ visible to a guest: both endpoints were already published-only for a
78
+ non-moderator.
79
+ - **A review that is not published says so.** `pending` / `hidden` rows carry a
80
+ badge, a status this build does not know is named rather than rendered as an
81
+ ordinary review, and the submit outcome reports the created row's status so a
82
+ pre-moderating deployment can say "it will appear once checked".
83
+
84
+ The list envelope comes from codegen. `GET /reviews` returns core's
85
+ `AnchorPagination` envelope and always did, but `ReviewListCreateView`
86
+ instantiates its paginator inside `get()` instead of declaring a
87
+ `pagination_class`, so drf-spectacular used to render the response as a bare
88
+ array and its `anchor`/`limit`/`direction` parameters not at all.
89
+ stapel-reviews 0.3.0 declares both (`components/ReviewPage`, and `direction`
90
+ with an enum), so `ReviewPage`, `ReviewListParams` and `ReviewAnchorDirection`
91
+ are projections of the generated schema rather than copies this package
92
+ maintains.
93
+
94
+ The seller-level rating is a DISPLAY, not a fetch: reviews target the seller
95
+ for a specific listing, so a seller's rating is a roll-up the module cannot
96
+ compute (one `(target_type, target_key)` per call) and the shop composite can
97
+ (`shop.listing_review_summary` → `{avg, count}`, the owner's field names on
98
+ purpose). `<RatingBadge aggregate={…}>` renders those two numbers with no
99
+ request; publishing them over HTTP is the composite's job and is recorded as an
100
+ open gap rather than faked with an N+1 loop.
101
+
102
+ Surface: `createReviewsRuntime`/`ReviewsProvider`, three read/write hooks, the
103
+ pure `rating`/`list`/`refusals` readers, three headless bags, and an opt-in
104
+ `./default` antd skin (`ReviewsPanel`, `ReviewListPanel`, `ReviewFormCard`,
105
+ `RatingBadge`). en/ru/es, with the 9 module-owned error keys authored by the
106
+ pair because stapel-reviews ships no `translations/`. Pinned to stapel-reviews
107
+ 0.3.0 (`>=0.3 <0.4`).
108
+
109
+ Not in scope, and not by omission: `POST {id}/moderate` and
110
+ `POST {id}/response` are both gated on the fail-closed `can_moderate` callback
111
+ and belong to a moderator console and a seller console; the reply is displayed
112
+ and the button to write one does not exist rather than existing switched off.
113
+ No nav manifest either — the pair renders inside somebody else's route.
114
+ `manifest.json` still lists the whole contract.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stapel contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/MODULE.md CHANGED
@@ -46,6 +46,22 @@ author) and `AllowAny` on the aggregate, both with
46
46
  than the project's `DEFAULT_THROTTLE_RATES`. Nothing new became visible to a
47
47
  guest: both endpoints were already published-only for a non-moderator.
48
48
 
49
+ ## The sentence and the door
50
+
51
+ `signInRequired` is true at exactly the right moment — the reads are anonymous
52
+ and the POST is not, so the author learns it when there is something to attach
53
+ to a name. What it did not have was a next action: the skin printed "sign in to
54
+ leave a review" and stopped, and the storefront had to put its own notice a
55
+ screen away from the control it was about (Wave D, G-3).
56
+
57
+ `<ReviewFormCard signIn={…}>` (and `<ReviewsPanel signIn={…}>`, which passes it
58
+ down) renders the link INSIDE the element that carries the sentence.
59
+ `SignInCta` is core's — `{href}` or `{onSignIn}`, never both — so the prop is
60
+ spelled identically in `@stapel/chat-react` and `@stapel/listings-react`; the
61
+ LABEL is this pair's (`reviews.form.sign_in`, all three locales), because core
62
+ floors only `en` and `ru`. Omitted, the sentence renders alone and carries no
63
+ trailing whitespace where the link is not.
64
+
49
65
  ## The three contract facts this package still absorbs
50
66
 
51
67
  ### 1. The list body is the pagination envelope — now declared
package/README.md CHANGED
@@ -36,6 +36,14 @@ export function ListingReviews({ listingId, me }) {
36
36
  }
37
37
  ```
38
38
 
39
+ `<ReviewsPanel signIn={{ href: `/login?next=${here}` }}>` (and
40
+ `<ReviewFormCard>` directly) puts the door beside "sign in to leave a review":
41
+ the reads are anonymous, the POST is not, so the sentence is true at exactly
42
+ the moment it appears — and until 0.4.0 it dead-ended, because no pair took a
43
+ sign-in href. `signIn` is core's `SignInCta`, `{href}` **or** `{onSignIn}`,
44
+ the same prop `@stapel/chat-react` and `@stapel/listings-react` take. Omit it
45
+ and the sentence renders alone.
46
+
39
47
  `"listing"` is **your** registry key, not this library's. stapel-reviews ships
40
48
  an empty `TARGET_TYPES` registry and knows nothing about listings; the shop
41
49
  composite registers that name in `stapel_shop/preset.py`. This package
@@ -11,7 +11,7 @@ export interface paths {
11
11
  * non-moderators), or create a review.
12
12
  *
13
13
  * ``GET`` is anonymously readable (storefront F5 verdict,
14
- * darom-storefront-design.md §13.8 note 2) — published-only filtering
14
+ * the storefront spec §13.8 note 2) — published-only filtering
15
15
  * already guarantees a guest sees nothing a moderator would need to hide.
16
16
  * ``POST`` still requires a real identity (there is an author to attribute
17
17
  * the review to), so the class gate is DRF's own
@@ -28,7 +28,7 @@ export interface paths {
28
28
  * non-moderators), or create a review.
29
29
  *
30
30
  * ``GET`` is anonymously readable (storefront F5 verdict,
31
- * darom-storefront-design.md §13.8 note 2) — published-only filtering
31
+ * the storefront spec §13.8 note 2) — published-only filtering
32
32
  * already guarantees a guest sees nothing a moderator would need to hide.
33
33
  * ``POST`` still requires a real identity (there is an author to attribute
34
34
  * the review to), so the class gate is DRF's own
@@ -101,7 +101,7 @@ export interface paths {
101
101
  * for a target.
102
102
  *
103
103
  * Anonymously readable for the same reason as the list's ``GET``
104
- * (storefront F5 verdict, darom-storefront-design.md §13.8 note 2): the
104
+ * (storefront F5 verdict, the storefront spec §13.8 note 2): the
105
105
  * aggregate is computed over published reviews only, so a guest learns
106
106
  * nothing a moderator would need withheld.
107
107
  *
@@ -178,7 +178,7 @@ export interface components {
178
178
  * only fires for ``GenericAPIView.pagination_class`` — never sees
179
179
  * ``ReviewAnchorPagination``, and the envelope has to be declared by hand
180
180
  * or spectacular renders the response as a bare array
181
- * (darom-storefront-design.md §13.8 note 3).
181
+ * (the storefront spec §13.8 note 3).
182
182
  */
183
183
  ReviewPage: {
184
184
  items: components["schemas"]["ReviewResponse"][];
@@ -14,9 +14,10 @@
14
14
  * that is deliberately invisible to them.
15
15
  */
16
16
  import type { ReactElement } from "react";
17
+ import type { SignInCtaProp } from "@stapel/core";
17
18
  import type { ReviewTarget } from "../api/types.js";
18
19
  import type { ThemeModeProp } from "./types.js";
19
- export interface ReviewFormCardProps extends ThemeModeProp {
20
+ export interface ReviewFormCardProps extends ThemeModeProp, SignInCtaProp {
20
21
  readonly target: ReviewTarget;
21
22
  /** The host already knows this author has reviewed the target (optimistic). */
22
23
  readonly alreadyReviewed?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"ReviewFormCard.d.ts","sourceRoot":"","sources":["../../src/default/ReviewFormCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAMpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AA2FD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAiBvE"}
1
+ {"version":3,"file":"ReviewFormCard.d.ts","sourceRoot":"","sources":["../../src/default/ReviewFormCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAOpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,mBAAoB,SAAQ,aAAa,EAAE,aAAa;IACvE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AA+FD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAmBvE"}
@@ -4,6 +4,7 @@ import { useActionGate, useDescribeFlowError, useT } from "@stapel/core";
4
4
  import { ReviewForm } from "../headless/ReviewForm.js";
5
5
  import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
6
6
  import { ErrorAlert } from "./ErrorAlert.js";
7
+ import { SignInLink } from "./SignInLink.js";
7
8
  import { ReviewsSkinTheme } from "./theme.js";
8
9
  /** The sentence for a review that exists but may not be on the page. */
9
10
  function sentKey(bag) {
@@ -30,13 +31,15 @@ function FormBody(props) {
30
31
  return (_jsx(Typography.Text, { type: "secondary", "data-testid": "reviews-form-duplicate", children: t(REVIEWS_I18N_KEYS.submitBlockedDuplicate) }));
31
32
  }
32
33
  if (bag.signInRequired) {
33
- return (_jsx(Typography.Text, { type: "secondary", "data-testid": "reviews-form-sign-in", children: t(REVIEWS_I18N_KEYS.formSignInRequired) }));
34
+ return (_jsxs(Typography.Text, { type: "secondary", "data-testid": "reviews-form-sign-in", children: [t(REVIEWS_I18N_KEYS.formSignInRequired), _jsx(SignInLink, { cta: props.signIn, testId: "reviews-form-sign-in-cta" })] }));
34
35
  }
35
36
  return (_jsxs(Flex, { vertical: true, gap: 8, children: [_jsx(Typography.Text, { children: t(REVIEWS_I18N_KEYS.formRatingLabel) }), _jsx(Rate, { count: bag.bounds.max, value: bag.rating ?? 0, onChange: bag.setRating, "data-testid": "reviews-form-rate" }), _jsx(Typography.Text, { children: t(REVIEWS_I18N_KEYS.formBodyLabel) }), _jsx(Input.TextArea, { value: bag.body, onChange: (event) => bag.setBody(event.target.value), placeholder: t(REVIEWS_I18N_KEYS.formBodyPlaceholder), rows: 3, "data-testid": "reviews-form-body" }), bag.error ? (_jsx(ErrorAlert, { testId: "reviews-form-failed", error: describe(bag.error) })) : null, _jsxs(Flex, { vertical: true, gap: 4, align: "flex-start", children: [_jsx(Button, { type: "primary", onClick: bag.submit, disabled: gate.disabled, loading: bag.submitting, "data-testid": "reviews-form-submit", "data-analytics": "none", "data-analytics-reason": "business action \u2014 the host app wraps this with its own tracked(); the pair ships no analytics runtime and no flow machine for a single POST", children: t(REVIEWS_I18N_KEYS.formSubmit) }), gate.reason ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "reviews-form-blocked", children: gate.reason })) : null] })] }));
36
37
  }
37
38
  export function ReviewFormCard(props) {
38
39
  const t = useT();
39
- const { mode, ...formProps } = props;
40
- return (_jsx(ReviewsSkinTheme, { ...(mode !== undefined ? { mode } : {}), children: _jsx(Card, { size: "small", "data-testid": "reviews-form", children: _jsxs(Flex, { vertical: true, gap: 8, children: [_jsx(Typography.Title, { level: 5, style: { margin: 0 }, children: t(REVIEWS_I18N_KEYS.formHeading) }), _jsx(ReviewForm, { ...formProps, children: (bag) => _jsx(FormBody, { bag: bag }) })] }) }) }));
40
+ // `signIn` is this card's, not the headless form's: the form knows THAT the
41
+ // author must sign in, the container knows WHERE.
42
+ const { mode, signIn, ...formProps } = props;
43
+ return (_jsx(ReviewsSkinTheme, { ...(mode !== undefined ? { mode } : {}), children: _jsx(Card, { size: "small", "data-testid": "reviews-form", children: _jsxs(Flex, { vertical: true, gap: 8, children: [_jsx(Typography.Title, { level: 5, style: { margin: 0 }, children: t(REVIEWS_I18N_KEYS.formHeading) }), _jsx(ReviewForm, { ...formProps, children: (bag) => _jsx(FormBody, { bag: bag, signIn: signIn }) })] }) }) }));
41
44
  }
42
45
  //# sourceMappingURL=ReviewFormCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReviewFormCard.js","sourceRoot":"","sources":["../../src/default/ReviewFormCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ReviewFormCard.js","sourceRoot":"","sources":["../../src/default/ReviewFormCard.tsx"],"names":[],"mappings":"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"}
@@ -14,9 +14,10 @@
14
14
  * outcome and the pre-check is only an optimisation.
15
15
  */
16
16
  import type { ReactElement, ReactNode } from "react";
17
+ import type { SignInCtaProp } from "@stapel/core";
17
18
  import type { Review, ReviewTarget } from "../api/types.js";
18
19
  import type { ThemeModeProp } from "./types.js";
19
- export interface ReviewsPanelProps extends ThemeModeProp {
20
+ export interface ReviewsPanelProps extends ThemeModeProp, SignInCtaProp {
20
21
  readonly target: ReviewTarget;
21
22
  /**
22
23
  * The reader's user id — the value the backend puts in `author_id`, NOT a
@@ -1 +1 @@
1
- {"version":3,"file":"ReviewsPanel.d.ts","sourceRoot":"","sources":["../../src/default/ReviewsPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;CACrD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAiCnE"}
1
+ {"version":3,"file":"ReviewsPanel.d.ts","sourceRoot":"","sources":["../../src/default/ReviewsPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,iBAAkB,SAAQ,aAAa,EAAE,aAAa;IACrE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;CACrD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAkCnE"}
@@ -7,11 +7,11 @@ import { ReviewFormCard } from "./ReviewFormCard.js";
7
7
  import { ReviewListPanel } from "./ReviewListPanel.js";
8
8
  import { ReviewsSkinTheme } from "./theme.js";
9
9
  export function ReviewsPanel(props) {
10
- const { mode, target, viewerId, canReview = true, ...rows } = props;
10
+ const { mode, target, viewerId, canReview = true, signIn, ...rows } = props;
11
11
  return (_jsx(ReviewsSkinTheme, { ...(mode !== undefined ? { mode } : {}), children: _jsxs(Flex, { vertical: true, gap: 16, "data-testid": "reviews-panel", children: [_jsx(RatingBadge, { target: target, ...(mode !== undefined ? { mode } : {}) }), _jsx(ReviewListPanel, { target: target, ...(mode !== undefined ? { mode } : {}), ...rows }), canReview ? (
12
12
  // The list is read a second time here, from the SAME query key, so
13
13
  // this is a cache hit rather than a second request — the own-review
14
14
  // pre-check needs the rows and the panel needs them rendered.
15
- _jsx(ReviewList, { target: target, children: (bag) => (_jsx(ReviewFormCard, { target: target, ...(mode !== undefined ? { mode } : {}), alreadyReviewed: findOwnReview(bag.state.status === "ready" ? bag.state.data : undefined, viewerId) !== undefined })) })) : null] }) }));
15
+ _jsx(ReviewList, { target: target, children: (bag) => (_jsx(ReviewFormCard, { target: target, ...(mode !== undefined ? { mode } : {}), ...(signIn !== undefined ? { signIn } : {}), alreadyReviewed: findOwnReview(bag.state.status === "ready" ? bag.state.data : undefined, viewerId) !== undefined })) })) : null] }) }));
16
16
  }
17
17
  //# sourceMappingURL=ReviewsPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReviewsPanel.js","sourceRoot":"","sources":["../../src/default/ReviewsPanel.tsx"],"names":[],"mappings":";AAgBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiB9C,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IACpE,OAAO,CACL,KAAC,gBAAgB,OAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YACxD,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,EAAE,iBAAc,eAAe,aACjD,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,KAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAI,EACzE,KAAC,eAAe,IACd,MAAM,EAAE,MAAM,KACV,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACpC,IAAI,GACR,EACD,SAAS,CAAC,CAAC,CAAC;gBACX,mEAAmE;gBACnE,oEAAoE;gBACpE,8DAA8D;gBAC9D,KAAC,UAAU,IAAC,MAAM,EAAE,MAAM,YACvB,CAAC,GAAG,EAAE,EAAE,CAAC,CACR,KAAC,cAAc,IACb,MAAM,EAAE,MAAM,KACV,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EACxC,eAAe,EACb,aAAa,CACX,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EACzD,QAAQ,CACT,KAAK,SAAS,GAEjB,CACH,GACU,CACd,CAAC,CAAC,CAAC,IAAI,IACH,GACU,CACpB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ReviewsPanel.js","sourceRoot":"","sources":["../../src/default/ReviewsPanel.tsx"],"names":[],"mappings":";AAgBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiB9C,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IAC5E,OAAO,CACL,KAAC,gBAAgB,OAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YACxD,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,EAAE,iBAAc,eAAe,aACjD,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,KAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAI,EACzE,KAAC,eAAe,IACd,MAAM,EAAE,MAAM,KACV,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACpC,IAAI,GACR,EACD,SAAS,CAAC,CAAC,CAAC;gBACX,mEAAmE;gBACnE,oEAAoE;gBACpE,8DAA8D;gBAC9D,KAAC,UAAU,IAAC,MAAM,EAAE,MAAM,YACvB,CAAC,GAAG,EAAE,EAAE,CAAC,CACR,KAAC,cAAc,IACb,MAAM,EAAE,MAAM,KACV,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACpC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAC5C,eAAe,EACb,aAAa,CACX,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EACzD,QAAQ,CACT,KAAK,SAAS,GAEjB,CACH,GACU,CACd,CAAC,CAAC,CAAC,IAAI,IACH,GACU,CACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The door beside a blocked control.
3
+ *
4
+ * `actionBlocked` ended the grey-rectangle incident by making every
5
+ * switched-off control state its reason. It did not end the next problem:
6
+ * "sign in to leave a review" is a reason whose next action is a LINK, and
7
+ * this pair rendered the sentence and stopped there — leaving the visitor to
8
+ * find the header themselves (storefront Wave D, G-3).
9
+ *
10
+ * WHERE that link goes is the container's business, never the pair's: the
11
+ * storefront's is `/login?next=<current>`, a tenant app's may be a modal. So
12
+ * the shape is core's `SignInCta` — `{href}` or `{onSignIn}`, never both — and
13
+ * the copy is this pair's, because core floors `en` and `ru` while this pair
14
+ * also ships `es`.
15
+ *
16
+ * A host that routes internally passes `onSignIn`; the `href` arm renders a
17
+ * plain anchor on purpose, because arriving at a sign-in page is one of the
18
+ * few navigations a full load costs nothing.
19
+ */
20
+ import type { ReactElement } from "react";
21
+ import type { SignInCta } from "@stapel/core";
22
+ export interface SignInLinkProps {
23
+ /** Absent: no link — a host with no sign-in route shows the reason alone. */
24
+ readonly cta: SignInCta | undefined;
25
+ readonly testId: string;
26
+ }
27
+ export declare function SignInLink(props: SignInLinkProps): ReactElement | null;
28
+ //# sourceMappingURL=SignInLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignInLink.d.ts","sourceRoot":"","sources":["../../src/default/SignInLink.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAsBtE"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Typography } from "antd";
3
+ import { useT } from "@stapel/core";
4
+ import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
5
+ export function SignInLink(props) {
6
+ const t = useT();
7
+ const { cta } = props;
8
+ if (cta === undefined)
9
+ return null;
10
+ // The separating space belongs HERE, not at the call site: a reason with no
11
+ // door must render as exactly its own sentence, and a `{" "}` left behind by
12
+ // an absent link is a trailing space in every caller's assertion.
13
+ return (_jsxs(_Fragment, { children: [" ", _jsx(Typography.Link, { "data-testid": props.testId, "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", ...(cta.href !== undefined
14
+ ? { href: cta.href }
15
+ : { onClick: cta.onSignIn }), children: t(REVIEWS_I18N_KEYS.formSignIn) })] }));
16
+ }
17
+ //# sourceMappingURL=SignInLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignInLink.js","sourceRoot":"","sources":["../../src/default/SignInLink.tsx"],"names":[],"mappings":";AAoBA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAQpD,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IACtB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,4EAA4E;IAC5E,6EAA6E;IAC7E,kEAAkE;IAClE,OAAO,CACL,8BACG,GAAG,EACJ,KAAC,UAAU,CAAC,IAAI,mBACD,KAAK,CAAC,MAAM,oBACV,MAAM,2BACC,8DAAyD,KAC3E,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS;oBACzB,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE;oBACpB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,YAE7B,CAAC,CAAC,iBAAiB,CAAC,UAAU,CAAC,GAChB,IACjB,CACJ,CAAC;AACJ,CAAC"}
@@ -28,4 +28,6 @@ export { ReviewsSkinTheme } from "./theme.js";
28
28
  export type { ReviewsSkinThemeProps } from "./theme.js";
29
29
  export { ErrorAlert } from "./ErrorAlert.js";
30
30
  export type { ThemeModeProp } from "./types.js";
31
+ export { SignInLink } from "./SignInLink.js";
32
+ export type { SignInLinkProps } from "./SignInLink.js";
31
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -22,4 +22,5 @@ export { ReviewFormCard } from "./ReviewFormCard.js";
22
22
  export { RatingBadge } from "./RatingBadge.js";
23
23
  export { ReviewsSkinTheme } from "./theme.js";
24
24
  export { ErrorAlert } from "./ErrorAlert.js";
25
+ export { SignInLink } from "./SignInLink.js";
25
26
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1 +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"}
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,cA0DjC,CAAC;AAEF,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAE7E"}
package/dist/i18n/es.js CHANGED
@@ -53,6 +53,7 @@ export const reviewsI18nBundleEs = {
53
53
  "reviews.form.sent.hidden": "Tu reseña se ha guardado, pero no se muestra",
54
54
  "reviews.form.sent.unknown": "Tu reseña se ha guardado",
55
55
  "reviews.form.sign_in_required": "Inicia sesión para dejar una reseña",
56
+ "reviews.form.sign_in": "Iniciar sesión",
56
57
  "reviews.submit.blocked.no_rating": "Elige primero una valoración",
57
58
  "reviews.submit.blocked.pending": "Enviando…",
58
59
  "reviews.submit.blocked.duplicate": "Ya has valorado esto",
@@ -1 +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"}
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;IACtE,sBAAsB,EAAE,gBAAgB;IAExC,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"}
@@ -33,6 +33,8 @@ export declare const REVIEWS_I18N_KEYS: {
33
33
  readonly formSentHidden: "reviews.form.sent.hidden";
34
34
  readonly formSentUnknown: "reviews.form.sent.unknown";
35
35
  readonly formSignInRequired: "reviews.form.sign_in_required";
36
+ /** The door beside that sentence — the container supplies WHERE it leads. */
37
+ readonly formSignIn: "reviews.form.sign_in";
36
38
  readonly submitBlockedNoRating: "reviews.submit.blocked.no_rating";
37
39
  readonly submitBlockedPending: "reviews.submit.blocked.pending";
38
40
  readonly submitBlockedDuplicate: "reviews.submit.blocked.duplicate";
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqEpB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,cAyCjC,CAAC;AAEF,2DAA2D;AAC3D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAE3E"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6C5B,6EAA6E;;;;;;;;;;;;;;;;CA0BrE,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,cA0CjC,CAAC;AAEF,2DAA2D;AAC3D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAE3E"}
package/dist/i18n/keys.js CHANGED
@@ -46,6 +46,8 @@ export const REVIEWS_I18N_KEYS = {
46
46
  formSentHidden: "reviews.form.sent.hidden",
47
47
  formSentUnknown: "reviews.form.sent.unknown",
48
48
  formSignInRequired: "reviews.form.sign_in_required",
49
+ /** The door beside that sentence — the container supplies WHERE it leads. */
50
+ formSignIn: "reviews.form.sign_in",
49
51
  // Blocked controls — every one of these is the `code` of an
50
52
  // ActionAvailability, so a switched-off button always has a sentence.
51
53
  submitBlockedNoRating: "reviews.submit.blocked.no_rating",
@@ -102,6 +104,7 @@ export const reviewsI18nBundleEn = {
102
104
  "reviews.form.sent.hidden": "Your review was saved but is not shown",
103
105
  "reviews.form.sent.unknown": "Your review was saved",
104
106
  "reviews.form.sign_in_required": "Sign in to leave a review",
107
+ "reviews.form.sign_in": "Sign in",
105
108
  "reviews.submit.blocked.no_rating": "Choose a rating first",
106
109
  "reviews.submit.blocked.pending": "Sending…",
107
110
  "reviews.submit.blocked.duplicate": "You have already rated this",
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ru.d.ts","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,EAAE,cA0DjC,CAAC;AAEF,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAE7E"}
1
+ {"version":3,"file":"ru.d.ts","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,EAAE,cA2DjC,CAAC;AAEF,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAE7E"}
package/dist/i18n/ru.js CHANGED
@@ -57,6 +57,7 @@ export const reviewsI18nBundleRu = {
57
57
  "reviews.form.sent.hidden": "Отзыв сохранён, но не показывается",
58
58
  "reviews.form.sent.unknown": "Отзыв сохранён",
59
59
  "reviews.form.sign_in_required": "Войдите, чтобы оставить отзыв",
60
+ "reviews.form.sign_in": "Войти",
60
61
  "reviews.submit.blocked.no_rating": "Сначала выберите оценку",
61
62
  "reviews.submit.blocked.pending": "Отправляем…",
62
63
  "reviews.submit.blocked.duplicate": "Вы уже оценили это",
@@ -1 +1 @@
1
- {"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmB;IACjD,oEAAoE;IACpE,GAAG,oBAAoB;IAEvB,wEAAwE;IACxE,UAAU;IACV,oCAAoC,EAAE,oBAAoB;IAC1D,6CAA6C,EAC3C,+DAA+D;IACjE,kCAAkC,EAAE,kCAAkC;IACtE,wCAAwC,EACtC,yCAAyC;IAC3C,uCAAuC,EAAE,gCAAgC;IACzE,mCAAmC,EACjC,kDAAkD;IACpD,iCAAiC,EAAE,6CAA6C;IAChF,oCAAoC,EAAE,iBAAiB;IACvD,qCAAqC,EAAE,8BAA8B;IAErE,WAAW;IACX,uBAAuB,EAAE,gCAAgC;IAEzD,sBAAsB,EAAE,QAAQ;IAChC,oBAAoB,EAAE,kBAAkB;IACxC,wBAAwB,EAAE,cAAc;IACxC,sBAAsB,EAAE,UAAU;IAClC,+BAA+B,EAAE,gBAAgB;IACjD,mCAAmC,EAAE,YAAY;IAEjD,gCAAgC,EAAE,YAAY;IAC9C,iCAAiC,EAAE,gBAAgB;IAEnD,wBAAwB,EAAE,mBAAmB;IAC7C,uBAAuB,EAAE,kBAAkB;IAC3C,wBAAwB,EAAE,iCAAiC;IAE3D,qBAAqB,EAAE,kBAAkB;IACzC,sBAAsB,EAAE,gBAAgB;IACxC,sBAAsB,EAAE,kBAAkB;IAC1C,2BAA2B,EAAE,kBAAkB;IAE/C,sBAAsB,EAAE,SAAS;IACjC,2BAA2B,EAAE,aAAa;IAC1C,yBAAyB,EAAE,WAAW;IACtC,+BAA+B,EAAE,oCAAoC;IACrE,qBAAqB,EAAE,WAAW;IAClC,6BAA6B,EAAE,iCAAiC;IAChE,2BAA2B,EACzB,qDAAqD;IACvD,0BAA0B,EAAE,oCAAoC;IAChE,2BAA2B,EAAE,gBAAgB;IAC7C,+BAA+B,EAAE,+BAA+B;IAEhE,kCAAkC,EAAE,yBAAyB;IAC7D,gCAAgC,EAAE,aAAa;IAC/C,kCAAkC,EAAE,oBAAoB;IACxD,kCAAkC,EAAE,qBAAqB;IACzD,kCAAkC,EAAE,mCAAmC;CACxE,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"}
1
+ {"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmB;IACjD,oEAAoE;IACpE,GAAG,oBAAoB;IAEvB,wEAAwE;IACxE,UAAU;IACV,oCAAoC,EAAE,oBAAoB;IAC1D,6CAA6C,EAC3C,+DAA+D;IACjE,kCAAkC,EAAE,kCAAkC;IACtE,wCAAwC,EACtC,yCAAyC;IAC3C,uCAAuC,EAAE,gCAAgC;IACzE,mCAAmC,EACjC,kDAAkD;IACpD,iCAAiC,EAAE,6CAA6C;IAChF,oCAAoC,EAAE,iBAAiB;IACvD,qCAAqC,EAAE,8BAA8B;IAErE,WAAW;IACX,uBAAuB,EAAE,gCAAgC;IAEzD,sBAAsB,EAAE,QAAQ;IAChC,oBAAoB,EAAE,kBAAkB;IACxC,wBAAwB,EAAE,cAAc;IACxC,sBAAsB,EAAE,UAAU;IAClC,+BAA+B,EAAE,gBAAgB;IACjD,mCAAmC,EAAE,YAAY;IAEjD,gCAAgC,EAAE,YAAY;IAC9C,iCAAiC,EAAE,gBAAgB;IAEnD,wBAAwB,EAAE,mBAAmB;IAC7C,uBAAuB,EAAE,kBAAkB;IAC3C,wBAAwB,EAAE,iCAAiC;IAE3D,qBAAqB,EAAE,kBAAkB;IACzC,sBAAsB,EAAE,gBAAgB;IACxC,sBAAsB,EAAE,kBAAkB;IAC1C,2BAA2B,EAAE,kBAAkB;IAE/C,sBAAsB,EAAE,SAAS;IACjC,2BAA2B,EAAE,aAAa;IAC1C,yBAAyB,EAAE,WAAW;IACtC,+BAA+B,EAAE,oCAAoC;IACrE,qBAAqB,EAAE,WAAW;IAClC,6BAA6B,EAAE,iCAAiC;IAChE,2BAA2B,EACzB,qDAAqD;IACvD,0BAA0B,EAAE,oCAAoC;IAChE,2BAA2B,EAAE,gBAAgB;IAC7C,+BAA+B,EAAE,+BAA+B;IAChE,sBAAsB,EAAE,OAAO;IAE/B,kCAAkC,EAAE,yBAAyB;IAC7D,gCAAgC,EAAE,aAAa;IAC/C,kCAAkC,EAAE,oBAAoB;IACxD,kCAAkC,EAAE,qBAAqB;IACzD,kCAAkC,EAAE,mCAAmC;CACxE,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"}
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @stapel/reviews-react 0.1.0
1
+ # @stapel/reviews-react 0.3.0
2
2
 
3
3
  Headless React flow pair for stapel-reviews (contract >=0.3 <0.4) — business + state, zero visual opinion.
4
4
  Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
3
3
  "package": "@stapel/reviews-react",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "backend": {
6
6
  "module": "stapel-reviews",
7
7
  "contract": ">=0.3 <0.4"
@@ -475,6 +475,7 @@
475
475
  "reviews.form.sent.pending",
476
476
  "reviews.form.sent.published",
477
477
  "reviews.form.sent.unknown",
478
+ "reviews.form.sign_in",
478
479
  "reviews.form.sign_in_required",
479
480
  "reviews.form.submit",
480
481
  "reviews.list.empty",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/reviews-react",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Headless React pair for stapel-reviews: ratings and reviews addressed the way the module addresses them — by an opaque (target_type, target_key) pair, never by a foreign key. An anchor-paginated list a guest can read, an aggregate that renders `no reviews yet` instead of a zero-star rating when count is 0, a submit form that reads the duplicate refusal by CODE (the module answers 400, not 409) and asks for a sign-in only where the write actually needs one, and moderation states shown as they are rather than hidden. The seller-level rating is a display over the composite's projection, because reviews itself cannot roll one up. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,13 +44,6 @@
44
44
  "manifest.json",
45
45
  "llms.txt"
46
46
  ],
47
- "scripts": {
48
- "build": "tsc -p tsconfig.json",
49
- "test": "tsc -p tsconfig.test.json && vitest run --exclude test/prodBundlePurity.test.ts",
50
- "test:pack": "vitest run test/prodBundlePurity.test.ts",
51
- "lint": "eslint .",
52
- "size": "size-limit"
53
- },
54
47
  "size-limit": [
55
48
  {
56
49
  "name": "index — the headless pair (client + list window + aggregate + submit): no antd",
@@ -90,10 +83,6 @@
90
83
  },
91
84
  "devDependencies": {
92
85
  "@size-limit/preset-small-lib": "^11.2.0",
93
- "@stapel/core": "workspace:^",
94
- "@stapel/showcase": "workspace:^",
95
- "@stapel/tokens": "workspace:^",
96
- "@stapel/tokens-antd": "workspace:^",
97
86
  "@tanstack/react-query": "^5.81.0",
98
87
  "@testing-library/react": "^16.3.0",
99
88
  "@types/react": "^19.1.0",
@@ -104,12 +93,23 @@
104
93
  "react-dom": "^19.1.0",
105
94
  "size-limit": "^11.2.0",
106
95
  "typescript": "^5.8.3",
107
- "vitest": "^3.2.4"
96
+ "vitest": "^3.2.4",
97
+ "@stapel/core": "^0.16.0",
98
+ "@stapel/showcase": "^0.2.0",
99
+ "@stapel/tokens": "^0.5.0",
100
+ "@stapel/tokens-antd": "^0.5.0"
108
101
  },
109
102
  "engines": {
110
103
  "node": ">=22"
111
104
  },
112
105
  "publishConfig": {
113
106
  "access": "public"
107
+ },
108
+ "scripts": {
109
+ "build": "tsc -p tsconfig.json",
110
+ "test": "tsc -p tsconfig.test.json && vitest run --exclude test/prodBundlePurity.test.ts",
111
+ "test:pack": "vitest run test/prodBundlePurity.test.ts",
112
+ "lint": "eslint .",
113
+ "size": "size-limit"
114
114
  }
115
- }
115
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-events.mjs — do not edit; drift-gated (pnpm gen:events:check)",
3
3
  "package": "@stapel/reviews-react",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "defined": [],
6
6
  "flows": []
7
7
  }
@@ -16,15 +16,17 @@
16
16
  import type { ReactElement } from "react";
17
17
  import { Alert, Button, Card, Flex, Input, Rate, Typography } from "antd";
18
18
  import { useActionGate, useDescribeFlowError, useT } from "@stapel/core";
19
+ import type { SignInCta, SignInCtaProp } from "@stapel/core";
19
20
  import type { ReviewTarget } from "../api/types.js";
20
21
  import { ReviewForm } from "../headless/ReviewForm.js";
21
22
  import type { ReviewFormBag } from "../headless/ReviewForm.js";
22
23
  import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
23
24
  import { ErrorAlert } from "./ErrorAlert.js";
25
+ import { SignInLink } from "./SignInLink.js";
24
26
  import { ReviewsSkinTheme } from "./theme.js";
25
27
  import type { ThemeModeProp } from "./types.js";
26
28
 
27
- export interface ReviewFormCardProps extends ThemeModeProp {
29
+ export interface ReviewFormCardProps extends ThemeModeProp, SignInCtaProp {
28
30
  readonly target: ReviewTarget;
29
31
  /** The host already knows this author has reviewed the target (optimistic). */
30
32
  readonly alreadyReviewed?: boolean;
@@ -44,7 +46,10 @@ function sentKey(bag: ReviewFormBag): string {
44
46
  }
45
47
  }
46
48
 
47
- function FormBody(props: { bag: ReviewFormBag }): ReactElement {
49
+ function FormBody(props: {
50
+ bag: ReviewFormBag;
51
+ signIn: SignInCta | undefined;
52
+ }): ReactElement {
48
53
  const t = useT();
49
54
  const describe = useDescribeFlowError();
50
55
  const { bag } = props;
@@ -73,6 +78,7 @@ function FormBody(props: { bag: ReviewFormBag }): ReactElement {
73
78
  return (
74
79
  <Typography.Text type="secondary" data-testid="reviews-form-sign-in">
75
80
  {t(REVIEWS_I18N_KEYS.formSignInRequired)}
81
+ <SignInLink cta={props.signIn} testId="reviews-form-sign-in-cta" />
76
82
  </Typography.Text>
77
83
  );
78
84
  }
@@ -121,7 +127,9 @@ function FormBody(props: { bag: ReviewFormBag }): ReactElement {
121
127
 
122
128
  export function ReviewFormCard(props: ReviewFormCardProps): ReactElement {
123
129
  const t = useT();
124
- const { mode, ...formProps } = props;
130
+ // `signIn` is this card's, not the headless form's: the form knows THAT the
131
+ // author must sign in, the container knows WHERE.
132
+ const { mode, signIn, ...formProps } = props;
125
133
  return (
126
134
  <ReviewsSkinTheme {...(mode !== undefined ? { mode } : {})}>
127
135
  <Card size="small" data-testid="reviews-form">
@@ -130,7 +138,7 @@ export function ReviewFormCard(props: ReviewFormCardProps): ReactElement {
130
138
  {t(REVIEWS_I18N_KEYS.formHeading)}
131
139
  </Typography.Title>
132
140
  <ReviewForm {...formProps}>
133
- {(bag) => <FormBody bag={bag} />}
141
+ {(bag) => <FormBody bag={bag} signIn={signIn} />}
134
142
  </ReviewForm>
135
143
  </Flex>
136
144
  </Card>
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import type { ReactElement, ReactNode } from "react";
17
17
  import { Flex } from "antd";
18
+ import type { SignInCtaProp } from "@stapel/core";
18
19
  import type { Review, ReviewTarget } from "../api/types.js";
19
20
  import { ReviewList } from "../headless/ReviewList.js";
20
21
  import { findOwnReview } from "../model/list.js";
@@ -24,7 +25,7 @@ import { ReviewListPanel } from "./ReviewListPanel.js";
24
25
  import { ReviewsSkinTheme } from "./theme.js";
25
26
  import type { ThemeModeProp } from "./types.js";
26
27
 
27
- export interface ReviewsPanelProps extends ThemeModeProp {
28
+ export interface ReviewsPanelProps extends ThemeModeProp, SignInCtaProp {
28
29
  readonly target: ReviewTarget;
29
30
  /**
30
31
  * The reader's user id — the value the backend puts in `author_id`, NOT a
@@ -39,7 +40,7 @@ export interface ReviewsPanelProps extends ThemeModeProp {
39
40
  }
40
41
 
41
42
  export function ReviewsPanel(props: ReviewsPanelProps): ReactElement {
42
- const { mode, target, viewerId, canReview = true, ...rows } = props;
43
+ const { mode, target, viewerId, canReview = true, signIn, ...rows } = props;
43
44
  return (
44
45
  <ReviewsSkinTheme {...(mode !== undefined ? { mode } : {})}>
45
46
  <Flex vertical gap={16} data-testid="reviews-panel">
@@ -58,6 +59,7 @@ export function ReviewsPanel(props: ReviewsPanelProps): ReactElement {
58
59
  <ReviewFormCard
59
60
  target={target}
60
61
  {...(mode !== undefined ? { mode } : {})}
62
+ {...(signIn !== undefined ? { signIn } : {})}
61
63
  alreadyReviewed={
62
64
  findOwnReview(
63
65
  bag.state.status === "ready" ? bag.state.data : undefined,
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The door beside a blocked control.
3
+ *
4
+ * `actionBlocked` ended the grey-rectangle incident by making every
5
+ * switched-off control state its reason. It did not end the next problem:
6
+ * "sign in to leave a review" is a reason whose next action is a LINK, and
7
+ * this pair rendered the sentence and stopped there — leaving the visitor to
8
+ * find the header themselves (storefront Wave D, G-3).
9
+ *
10
+ * WHERE that link goes is the container's business, never the pair's: the
11
+ * storefront's is `/login?next=<current>`, a tenant app's may be a modal. So
12
+ * the shape is core's `SignInCta` — `{href}` or `{onSignIn}`, never both — and
13
+ * the copy is this pair's, because core floors `en` and `ru` while this pair
14
+ * also ships `es`.
15
+ *
16
+ * A host that routes internally passes `onSignIn`; the `href` arm renders a
17
+ * plain anchor on purpose, because arriving at a sign-in page is one of the
18
+ * few navigations a full load costs nothing.
19
+ */
20
+ import type { ReactElement } from "react";
21
+ import { Typography } from "antd";
22
+ import { useT } from "@stapel/core";
23
+ import type { SignInCta } from "@stapel/core";
24
+ import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
25
+
26
+ export interface SignInLinkProps {
27
+ /** Absent: no link — a host with no sign-in route shows the reason alone. */
28
+ readonly cta: SignInCta | undefined;
29
+ readonly testId: string;
30
+ }
31
+
32
+ export function SignInLink(props: SignInLinkProps): ReactElement | null {
33
+ const t = useT();
34
+ const { cta } = props;
35
+ if (cta === undefined) return null;
36
+ // The separating space belongs HERE, not at the call site: a reason with no
37
+ // door must render as exactly its own sentence, and a `{" "}` left behind by
38
+ // an absent link is a trailing space in every caller's assertion.
39
+ return (
40
+ <>
41
+ {" "}
42
+ <Typography.Link
43
+ data-testid={props.testId}
44
+ data-analytics="none"
45
+ data-analytics-reason="business action — host app wraps with its own tracked()"
46
+ {...(cta.href !== undefined
47
+ ? { href: cta.href }
48
+ : { onClick: cta.onSignIn })}
49
+ >
50
+ {t(REVIEWS_I18N_KEYS.formSignIn)}
51
+ </Typography.Link>
52
+ </>
53
+ );
54
+ }
@@ -28,3 +28,5 @@ export { ReviewsSkinTheme } from "./theme.js";
28
28
  export type { ReviewsSkinThemeProps } from "./theme.js";
29
29
  export { ErrorAlert } from "./ErrorAlert.js";
30
30
  export type { ThemeModeProp } from "./types.js";
31
+ export { SignInLink } from "./SignInLink.js";
32
+ export type { SignInLinkProps } from "./SignInLink.js";
package/src/i18n/es.ts CHANGED
@@ -67,6 +67,7 @@ export const reviewsI18nBundleEs: I18nDictionary = {
67
67
  "reviews.form.sent.hidden": "Tu reseña se ha guardado, pero no se muestra",
68
68
  "reviews.form.sent.unknown": "Tu reseña se ha guardado",
69
69
  "reviews.form.sign_in_required": "Inicia sesión para dejar una reseña",
70
+ "reviews.form.sign_in": "Iniciar sesión",
70
71
 
71
72
  "reviews.submit.blocked.no_rating": "Elige primero una valoración",
72
73
  "reviews.submit.blocked.pending": "Enviando…",
package/src/i18n/keys.ts CHANGED
@@ -53,6 +53,8 @@ export const REVIEWS_I18N_KEYS = {
53
53
  formSentHidden: "reviews.form.sent.hidden",
54
54
  formSentUnknown: "reviews.form.sent.unknown",
55
55
  formSignInRequired: "reviews.form.sign_in_required",
56
+ /** The door beside that sentence — the container supplies WHERE it leads. */
57
+ formSignIn: "reviews.form.sign_in",
56
58
 
57
59
  // Blocked controls — every one of these is the `code` of an
58
60
  // ActionAvailability, so a switched-off button always has a sentence.
@@ -119,6 +121,7 @@ export const reviewsI18nBundleEn: I18nDictionary = {
119
121
  "reviews.form.sent.hidden": "Your review was saved but is not shown",
120
122
  "reviews.form.sent.unknown": "Your review was saved",
121
123
  "reviews.form.sign_in_required": "Sign in to leave a review",
124
+ "reviews.form.sign_in": "Sign in",
122
125
 
123
126
  "reviews.submit.blocked.no_rating": "Choose a rating first",
124
127
  "reviews.submit.blocked.pending": "Sending…",
package/src/i18n/ru.ts CHANGED
@@ -71,6 +71,7 @@ export const reviewsI18nBundleRu: I18nDictionary = {
71
71
  "reviews.form.sent.hidden": "Отзыв сохранён, но не показывается",
72
72
  "reviews.form.sent.unknown": "Отзыв сохранён",
73
73
  "reviews.form.sign_in_required": "Войдите, чтобы оставить отзыв",
74
+ "reviews.form.sign_in": "Войти",
74
75
 
75
76
  "reviews.submit.blocked.no_rating": "Сначала выберите оценку",
76
77
  "reviews.submit.blocked.pending": "Отправляем…",