@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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CSSProperties, ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { FlowErrorDisplay } from "@stapel/core";
|
|
3
|
+
export declare function ErrorAlert(props: {
|
|
4
|
+
error: FlowErrorDisplay | undefined;
|
|
5
|
+
style?: CSSProperties | undefined;
|
|
6
|
+
testId?: string | undefined;
|
|
7
|
+
action?: ReactNode;
|
|
8
|
+
}): ReactElement | null;
|
|
9
|
+
//# sourceMappingURL=ErrorAlert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorAlert.d.ts","sourceRoot":"","sources":["../../src/default/ErrorAlert.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,GAAG,YAAY,GAAG,IAAI,CAsBtB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The one error surface this pair's default skin renders, so the split copy
|
|
4
|
+
* core produces reaches a screen the same way everywhere (mirrors the
|
|
5
|
+
* docs/notifications/workspaces etalon).
|
|
6
|
+
*
|
|
7
|
+
* Core's `describeFlowError` splits a failure into the sentence a person
|
|
8
|
+
* reads and the technical detail a support agent quotes (owner report
|
|
9
|
+
* 2026-08-09: the status used to be spliced into the sentence, ending in a
|
|
10
|
+
* bare `" (500)"`). The split only helps if a skin renders both halves at
|
|
11
|
+
* their own weights: `message` at normal weight, `detail` muted and small.
|
|
12
|
+
*
|
|
13
|
+
* `undefined` in — including a `detail` core left `undefined` because there
|
|
14
|
+
* was nothing worth quoting — renders nothing rather than an empty line.
|
|
15
|
+
*/
|
|
16
|
+
import { Alert, Typography } from "antd";
|
|
17
|
+
export function ErrorAlert(props) {
|
|
18
|
+
const { error } = props;
|
|
19
|
+
if (!error)
|
|
20
|
+
return null;
|
|
21
|
+
return (_jsx(Alert, { type: "error", showIcon: true, ...(props.style ? { style: props.style } : {}), ...(props.testId ? { "data-testid": props.testId } : {}), ...(props.action ? { action: props.action } : {}), message: error.message, ...(error.detail
|
|
22
|
+
? {
|
|
23
|
+
description: (_jsx(Typography.Text, { type: "secondary", style: { fontSize: 12 }, children: error.detail })),
|
|
24
|
+
}
|
|
25
|
+
: {}) }));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=ErrorAlert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorAlert.js","sourceRoot":"","sources":["../../src/default/ErrorAlert.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAIzC,MAAM,UAAU,UAAU,CAAC,KAK1B;IACC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACxB,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,CACL,KAAC,KAAK,IACJ,IAAI,EAAC,OAAO,EACZ,QAAQ,WACJ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC3C,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACrD,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAClD,OAAO,EAAE,KAAK,CAAC,OAAO,KAClB,CAAC,KAAK,CAAC,MAAM;YACf,CAAC,CAAC;gBACE,WAAW,EAAE,CACX,KAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAC,WAAW,EAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,YACtD,KAAK,CAAC,MAAM,GACG,CACnB;aACF;YACH,CAAC,CAAC,EAAE,CAAC,GACP,CACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<RatingBadge>` — the stars-and-count line, over either source of the two
|
|
3
|
+
* numbers.
|
|
4
|
+
*
|
|
5
|
+
* The whole component is built around one refusal: when `count` is `0` there
|
|
6
|
+
* is no star row at all, only the "no reviews yet" sentence. antd's `<Rate>`
|
|
7
|
+
* given `value={0}` draws five empty stars, which is a perfectly good
|
|
8
|
+
* rendering of the worst possible score and a completely wrong rendering of
|
|
9
|
+
* "nobody has rated this". `ratingSummary()` makes the distinction, and this
|
|
10
|
+
* skin honours it by not reaching `<Rate>` at all in that arm.
|
|
11
|
+
*/
|
|
12
|
+
import type { ReactElement } from "react";
|
|
13
|
+
import type { ReviewAggregateProps } from "../headless/ReviewAggregate.js";
|
|
14
|
+
import type { ThemeModeProp } from "./types.js";
|
|
15
|
+
export type RatingBadgeProps = ThemeModeProp & Omit<ReviewAggregateProps, "children">;
|
|
16
|
+
export declare function RatingBadge(props: RatingBadgeProps): ReactElement;
|
|
17
|
+
//# sourceMappingURL=RatingBadge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RatingBadge.d.ts","sourceRoot":"","sources":["../../src/default/RatingBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAI1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAI3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAC1C,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAEzC,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAwDjE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Flex, Rate, Skeleton, Typography } from "antd";
|
|
3
|
+
import { matchLoad, toFlowError, useDescribeFlowError, useT } from "@stapel/core";
|
|
4
|
+
import { ReviewAggregate } from "../headless/ReviewAggregate.js";
|
|
5
|
+
import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
|
|
6
|
+
import { ErrorAlert } from "./ErrorAlert.js";
|
|
7
|
+
import { ReviewsSkinTheme } from "./theme.js";
|
|
8
|
+
export function RatingBadge(props) {
|
|
9
|
+
const t = useT();
|
|
10
|
+
const describe = useDescribeFlowError();
|
|
11
|
+
const { mode, ...aggregateProps } = props;
|
|
12
|
+
return (_jsx(ReviewsSkinTheme, { ...(mode !== undefined ? { mode } : {}), children: _jsx(ReviewAggregate, { ...aggregateProps, children: (bag) => (_jsx(Flex, { align: "center", gap: 8, "data-testid": "reviews-rating", children: matchLoad(bag.state, {
|
|
13
|
+
loading: () => (_jsx(Skeleton.Button, { active: true, "data-testid": "reviews-rating-loading" })),
|
|
14
|
+
failed: (error) => (_jsx(ErrorAlert, { testId: "reviews-rating-failed", error: describe(toFlowError(error)) })),
|
|
15
|
+
ready: (summary) => summary.rated ? (_jsxs(_Fragment, { children: [_jsx(Rate, { disabled: true, allowHalf: true, count: bag.max, value: summary.rounded, "data-testid": "reviews-rating-stars" }), _jsx(Typography.Text, { strong: true, children: t(REVIEWS_I18N_KEYS.ratingValue, {
|
|
16
|
+
avg: summary.rounded,
|
|
17
|
+
max: bag.max,
|
|
18
|
+
}) }), _jsx(Typography.Text, { type: "secondary", children: t(REVIEWS_I18N_KEYS.ratingCount, {
|
|
19
|
+
count: summary.count,
|
|
20
|
+
}) })] })) : (_jsx(Typography.Text, { type: "secondary", "data-testid": "reviews-rating-none", children: t(REVIEWS_I18N_KEYS.ratingNone) })),
|
|
21
|
+
}) })) }) }));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=RatingBadge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RatingBadge.js","sourceRoot":"","sources":["../../src/default/RatingBadge.tsx"],"names":[],"mappings":";AAYA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAM9C,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,QAAQ,GAAG,oBAAoB,EAAE,CAAC;IACxC,MAAM,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,GAAG,KAAK,CAAC;IAE1C,OAAO,CACL,KAAC,gBAAgB,OAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YACxD,KAAC,eAAe,OAAK,cAAc,YAChC,CAAC,GAAG,EAAE,EAAE,CAAC,CACR,KAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC,iBAAc,gBAAgB,YACtD,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE;oBACpB,OAAO,EAAE,GAAG,EAAE,CAAC,CACb,KAAC,QAAQ,CAAC,MAAM,IAAC,MAAM,uBAAa,wBAAwB,GAAG,CAChE;oBACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CACjB,KAAC,UAAU,IACT,MAAM,EAAC,uBAAuB,EAC9B,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,GACnC,CACH;oBACD,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CACjB,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CACd,8BACE,KAAC,IAAI,IACH,QAAQ,QACR,SAAS,QACT,KAAK,EAAE,GAAG,CAAC,GAAG,EACd,KAAK,EAAE,OAAO,CAAC,OAAO,iBACV,sBAAsB,GAClC,EACF,KAAC,UAAU,CAAC,IAAI,IAAC,MAAM,kBACpB,CAAC,CAAC,iBAAiB,CAAC,WAAW,EAAE;oCAChC,GAAG,EAAE,OAAO,CAAC,OAAO;oCACpB,GAAG,EAAE,GAAG,CAAC,GAAG;iCACb,CAAC,GACc,EAClB,KAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAC,WAAW,YAC9B,CAAC,CAAC,iBAAiB,CAAC,WAAW,EAAE;oCAChC,KAAK,EAAE,OAAO,CAAC,KAAK;iCACrB,CAAC,GACc,IACjB,CACJ,CAAC,CAAC,CAAC,CACF,KAAC,UAAU,CAAC,IAAI,IACd,IAAI,EAAC,WAAW,iBACJ,qBAAqB,YAEhC,CAAC,CAAC,iBAAiB,CAAC,UAAU,CAAC,GAChB,CACnB;iBACJ,CAAC,GACG,CACR,GACe,GACD,CACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<ReviewFormCard>` — "rate this", in antd.
|
|
3
|
+
*
|
|
4
|
+
* Two outcomes get their own sentence rather than a red banner, because
|
|
5
|
+
* neither is a fault of the person in front of the screen:
|
|
6
|
+
*
|
|
7
|
+
* - **already rated** (`error.400.reviews_duplicate_review`, a 400 — see
|
|
8
|
+
* `model/refusals.ts`): the form collapses into a note. The same note shows
|
|
9
|
+
* when the host told us up front via `alreadyReviewed`, so the two paths to
|
|
10
|
+
* the same fact look the same;
|
|
11
|
+
* - **sent but not visible**: under pre-moderation the created row comes back
|
|
12
|
+
* `pending`, and the author is told it will appear once checked. Saying
|
|
13
|
+
* "published" there, or saying nothing, leaves them hunting for a review
|
|
14
|
+
* that is deliberately invisible to them.
|
|
15
|
+
*/
|
|
16
|
+
import type { ReactElement } from "react";
|
|
17
|
+
import type { ReviewTarget } from "../api/types.js";
|
|
18
|
+
import type { ThemeModeProp } from "./types.js";
|
|
19
|
+
export interface ReviewFormCardProps extends ThemeModeProp {
|
|
20
|
+
readonly target: ReviewTarget;
|
|
21
|
+
/** The host already knows this author has reviewed the target (optimistic). */
|
|
22
|
+
readonly alreadyReviewed?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function ReviewFormCard(props: ReviewFormCardProps): ReactElement;
|
|
25
|
+
//# sourceMappingURL=ReviewFormCard.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Alert, Button, Card, Flex, Input, Rate, Typography } from "antd";
|
|
3
|
+
import { useActionGate, useDescribeFlowError, useT } from "@stapel/core";
|
|
4
|
+
import { ReviewForm } from "../headless/ReviewForm.js";
|
|
5
|
+
import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
|
|
6
|
+
import { ErrorAlert } from "./ErrorAlert.js";
|
|
7
|
+
import { ReviewsSkinTheme } from "./theme.js";
|
|
8
|
+
/** The sentence for a review that exists but may not be on the page. */
|
|
9
|
+
function sentKey(bag) {
|
|
10
|
+
switch (bag.submittedVisibility) {
|
|
11
|
+
case "published":
|
|
12
|
+
return REVIEWS_I18N_KEYS.formSentPublished;
|
|
13
|
+
case "pending":
|
|
14
|
+
return REVIEWS_I18N_KEYS.formSentPending;
|
|
15
|
+
case "hidden":
|
|
16
|
+
return REVIEWS_I18N_KEYS.formSentHidden;
|
|
17
|
+
default:
|
|
18
|
+
return REVIEWS_I18N_KEYS.formSentUnknown;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function FormBody(props) {
|
|
22
|
+
const t = useT();
|
|
23
|
+
const describe = useDescribeFlowError();
|
|
24
|
+
const { bag } = props;
|
|
25
|
+
const gate = useActionGate(bag.canSubmit);
|
|
26
|
+
if (bag.submitted !== null) {
|
|
27
|
+
return (_jsx(Alert, { type: "success", showIcon: true, "data-testid": "reviews-form-sent", message: t(sentKey(bag)) }));
|
|
28
|
+
}
|
|
29
|
+
if (bag.alreadyReviewed) {
|
|
30
|
+
return (_jsx(Typography.Text, { type: "secondary", "data-testid": "reviews-form-duplicate", children: t(REVIEWS_I18N_KEYS.submitBlockedDuplicate) }));
|
|
31
|
+
}
|
|
32
|
+
if (bag.signInRequired) {
|
|
33
|
+
return (_jsx(Typography.Text, { type: "secondary", "data-testid": "reviews-form-sign-in", children: t(REVIEWS_I18N_KEYS.formSignInRequired) }));
|
|
34
|
+
}
|
|
35
|
+
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
|
+
export function ReviewFormCard(props) {
|
|
38
|
+
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 }) })] }) }) }));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=ReviewFormCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReviewFormCard.js","sourceRoot":"","sources":["../../src/default/ReviewFormCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<ReviewListPanel>` — the antd rendering of the review list.
|
|
3
|
+
*
|
|
4
|
+
* Two things it says out loud that a naive list would swallow:
|
|
5
|
+
*
|
|
6
|
+
* 1. A row that is not `published` carries a badge naming its state. It is on
|
|
7
|
+
* screen only because a moderator asked for `include=all`, and a pending
|
|
8
|
+
* or hidden review that looked like an ordinary one would misrepresent
|
|
9
|
+
* what the public can see.
|
|
10
|
+
* 2. The author is a SLOT. The wire carries `author_id` and nothing else — no
|
|
11
|
+
* name, no avatar — so the skin renders a neutral label unless the host
|
|
12
|
+
* passes `renderAuthor`. Printing a raw user id would be both useless to a
|
|
13
|
+
* reader and a gratuitous disclosure.
|
|
14
|
+
*/
|
|
15
|
+
import type { ReactElement, ReactNode } from "react";
|
|
16
|
+
import type { Review, ReviewTarget } from "../api/types.js";
|
|
17
|
+
import type { ThemeModeProp } from "./types.js";
|
|
18
|
+
export interface ReviewListPanelProps extends ThemeModeProp {
|
|
19
|
+
readonly target: ReviewTarget;
|
|
20
|
+
/** Ask for pending/hidden rows (granted only to a moderator of the target). */
|
|
21
|
+
readonly include?: "all";
|
|
22
|
+
readonly limit?: number;
|
|
23
|
+
/** Turn `author_id` into something a reader recognises. See the header. */
|
|
24
|
+
readonly renderAuthor?: (review: Review) => ReactNode;
|
|
25
|
+
/** Format `created_at`. Absent means the raw ISO string is NOT shown. */
|
|
26
|
+
readonly renderDate?: (review: Review) => ReactNode;
|
|
27
|
+
}
|
|
28
|
+
export declare function ReviewListPanel(props: ReviewListPanelProps): ReactElement;
|
|
29
|
+
//# sourceMappingURL=ReviewListPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReviewListPanel.d.ts","sourceRoot":"","sources":["../../src/default/ReviewListPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AASrD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAQ5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;IACtD,yEAAyE;IACzE,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;CACrD;AAiGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CAgEzE"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Card, Empty, Flex, List, Rate, Skeleton, Tag, Typography } from "antd";
|
|
3
|
+
import { matchLoad, toFlowError, useActionGate, useDescribeFlowError, useT, } from "@stapel/core";
|
|
4
|
+
import { ReviewList } from "../headless/ReviewList.js";
|
|
5
|
+
import { REVIEWS_I18N_KEYS } from "../i18n/keys.js";
|
|
6
|
+
import { reviewVisibility } from "../model/list.js";
|
|
7
|
+
import { useReviewsRuntime } from "../model/context.js";
|
|
8
|
+
import { ErrorAlert } from "./ErrorAlert.js";
|
|
9
|
+
import { ReviewsSkinTheme } from "./theme.js";
|
|
10
|
+
/** The badge a non-published row carries. `null` for an ordinary review. */
|
|
11
|
+
function VisibilityTag(props) {
|
|
12
|
+
const t = useT();
|
|
13
|
+
const visibility = reviewVisibility(props.status);
|
|
14
|
+
if (visibility === "published")
|
|
15
|
+
return null;
|
|
16
|
+
if (visibility === "pending") {
|
|
17
|
+
return (_jsx(Tag, { color: "warning", "data-testid": "reviews-row-pending", children: t(REVIEWS_I18N_KEYS.statusPending) }));
|
|
18
|
+
}
|
|
19
|
+
if (visibility === "hidden") {
|
|
20
|
+
return (_jsx(Tag, { color: "error", "data-testid": "reviews-row-hidden", children: t(REVIEWS_I18N_KEYS.statusHidden) }));
|
|
21
|
+
}
|
|
22
|
+
// A state this build does not know. Naming it beats rendering it as an
|
|
23
|
+
// ordinary review (it may be one the server hides) and beats crashing.
|
|
24
|
+
return (_jsx(Tag, { "data-testid": "reviews-row-unknown", children: t(REVIEWS_I18N_KEYS.statusUnknown, { status: props.status }) }));
|
|
25
|
+
}
|
|
26
|
+
function ReviewRow(props) {
|
|
27
|
+
const t = useT();
|
|
28
|
+
const { review } = props;
|
|
29
|
+
return (_jsx(List.Item, { "data-testid": "reviews-row", "data-review-id": review.id, children: _jsxs(Flex, { vertical: true, gap: 4, style: { width: "100%" }, children: [_jsxs(Flex, { align: "center", gap: 8, wrap: true, children: [_jsx(Rate, { disabled: true, count: props.max, value: review.rating }), _jsx(Typography.Text, { strong: true, children: props.renderAuthor?.(review) ?? t(REVIEWS_I18N_KEYS.authorFallback) }), props.renderDate ? (_jsx(Typography.Text, { type: "secondary", children: props.renderDate(review) })) : null, _jsx(VisibilityTag, { status: review.status })] }), review.body.length > 0 ? (_jsx(Typography.Paragraph, { style: { margin: 0 }, children: review.body })) : null, review.response ? (_jsxs(Card, { size: "small", "data-testid": "reviews-row-response", children: [_jsx(Typography.Text, { type: "secondary", children: t(REVIEWS_I18N_KEYS.responseHeading) }), _jsx(Typography.Paragraph, { style: { margin: 0 }, children: review.response.body })] })) : null] }) }));
|
|
30
|
+
}
|
|
31
|
+
function MoreButton(props) {
|
|
32
|
+
const t = useT();
|
|
33
|
+
const gate = useActionGate(props.bag.more);
|
|
34
|
+
return (_jsxs(Flex, { vertical: true, align: "center", gap: 4, children: [_jsx(Button, { onClick: props.bag.loadMore, disabled: gate.disabled, loading: props.bag.loadingMore, "data-testid": "reviews-load-more", "data-analytics": "none", "data-analytics-reason": "paging further into a read \u2014 the host app wraps this with its own tracked(); pairs carry no @stapel/analytics runtime dependency by architecture", children: t(REVIEWS_I18N_KEYS.listLoadMore) }), gate.reason ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "reviews-load-more-reason", children: gate.reason })) : null] }));
|
|
35
|
+
}
|
|
36
|
+
export function ReviewListPanel(props) {
|
|
37
|
+
const t = useT();
|
|
38
|
+
const describe = useDescribeFlowError();
|
|
39
|
+
const runtime = useReviewsRuntime();
|
|
40
|
+
const { mode, target, renderAuthor, renderDate, ...listOptions } = props;
|
|
41
|
+
return (_jsx(ReviewsSkinTheme, { ...(mode !== undefined ? { mode } : {}), children: _jsx(ReviewList, { target: target, ...listOptions, children: (bag) => (_jsxs(Flex, { vertical: true, gap: 8, "data-testid": "reviews-list", children: [_jsx(Typography.Title, { level: 5, style: { margin: 0 }, children: t(REVIEWS_I18N_KEYS.listHeading) }), matchLoad(bag.state, {
|
|
42
|
+
loading: () => (_jsx(Skeleton, { active: true, "data-testid": "reviews-list-loading" })),
|
|
43
|
+
failed: (error) => (_jsx(ErrorAlert, { testId: "reviews-list-failed", error: describe(toFlowError(error)), action: _jsx(Button, { size: "small", onClick: bag.refresh, "data-analytics": "none", "data-analytics-reason": "recovery affordance for a failed read \u2014 host app wraps with its own tracked()", children: t(REVIEWS_I18N_KEYS.listRefresh) }) })),
|
|
44
|
+
ready: (reviews) => reviews.length === 0 ? (_jsx(Empty, { "data-testid": "reviews-list-empty", description: t(REVIEWS_I18N_KEYS.listEmpty) })) : (_jsxs(_Fragment, { children: [_jsx(List, { dataSource: [...reviews], "data-testid": "reviews-list-rows", renderItem: (review) => (_jsx(ReviewRow, { review: review, max: runtime.ratingBounds.max, renderAuthor: renderAuthor, renderDate: renderDate })) }), _jsx(MoreButton, { bag: bag })] })),
|
|
45
|
+
})] })) }) }));
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=ReviewListPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReviewListPanel.js","sourceRoot":"","sources":["../../src/default/ReviewListPanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<ReviewsPanel>` — the composed block a listing detail page drops in: the
|
|
3
|
+
* rating line, the list, and (for a signed-in reader who has not rated yet)
|
|
4
|
+
* the form.
|
|
5
|
+
*
|
|
6
|
+
* The pre-check is the interesting part, and its LIMIT is the reason it lives
|
|
7
|
+
* here rather than inside the form. Given `viewerId`, the panel looks for the
|
|
8
|
+
* viewer's own review among the loaded rows and, finding one, tells the form
|
|
9
|
+
* not to offer itself. That list is published-only, so under pre-moderation
|
|
10
|
+
* the viewer's pending review is invisible even to them: the form is offered,
|
|
11
|
+
* the server refuses with `error.400.reviews_duplicate_review`, and the form
|
|
12
|
+
* shows the same "you have already rated this" sentence. One fact, two
|
|
13
|
+
* discoveries, one sentence — which is why the refusal is a first-class
|
|
14
|
+
* outcome and the pre-check is only an optimisation.
|
|
15
|
+
*/
|
|
16
|
+
import type { ReactElement, ReactNode } from "react";
|
|
17
|
+
import type { Review, ReviewTarget } from "../api/types.js";
|
|
18
|
+
import type { ThemeModeProp } from "./types.js";
|
|
19
|
+
export interface ReviewsPanelProps extends ThemeModeProp {
|
|
20
|
+
readonly target: ReviewTarget;
|
|
21
|
+
/**
|
|
22
|
+
* The reader's user id — the value the backend puts in `author_id`, NOT a
|
|
23
|
+
* profile id. Absent means no pre-check: the form is offered and the server
|
|
24
|
+
* is the only judge.
|
|
25
|
+
*/
|
|
26
|
+
readonly viewerId?: string | null;
|
|
27
|
+
/** Hide the form entirely (a seller looking at their own listing). */
|
|
28
|
+
readonly canReview?: boolean;
|
|
29
|
+
readonly renderAuthor?: (review: Review) => ReactNode;
|
|
30
|
+
readonly renderDate?: (review: Review) => ReactNode;
|
|
31
|
+
}
|
|
32
|
+
export declare function ReviewsPanel(props: ReviewsPanelProps): ReactElement;
|
|
33
|
+
//# sourceMappingURL=ReviewsPanel.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Flex } from "antd";
|
|
3
|
+
import { ReviewList } from "../headless/ReviewList.js";
|
|
4
|
+
import { findOwnReview } from "../model/list.js";
|
|
5
|
+
import { RatingBadge } from "./RatingBadge.js";
|
|
6
|
+
import { ReviewFormCard } from "./ReviewFormCard.js";
|
|
7
|
+
import { ReviewListPanel } from "./ReviewListPanel.js";
|
|
8
|
+
import { ReviewsSkinTheme } from "./theme.js";
|
|
9
|
+
export function ReviewsPanel(props) {
|
|
10
|
+
const { mode, target, viewerId, canReview = true, ...rows } = props;
|
|
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
|
+
// The list is read a second time here, from the SAME query key, so
|
|
13
|
+
// this is a cache hit rather than a second request — the own-review
|
|
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] }) }));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=ReviewsPanel.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@stapel/reviews-react/default` — the antd skin over the headless pair.
|
|
3
|
+
*
|
|
4
|
+
* A separate entry point (the convention every pair's `/default` follows) so a
|
|
5
|
+
* host rendering its own review block never pulls `antd` into its bundle. The
|
|
6
|
+
* main entry has no visual opinion at all and no import path from it reaches
|
|
7
|
+
* this directory — size-limit and the bundle-purity test are the teeth on
|
|
8
|
+
* that.
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { createReviewsRuntime, ReviewsProvider } from "@stapel/reviews-react";
|
|
12
|
+
* import { ReviewsPanel } from "@stapel/reviews-react/default";
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* `<RatingBadge aggregate={…}>` is the seller-rating half: it renders two
|
|
16
|
+
* numbers the composite's projection produced, because stapel-reviews cannot
|
|
17
|
+
* roll a seller's listings up itself (main entry header).
|
|
18
|
+
*/
|
|
19
|
+
export { ReviewsPanel } from "./ReviewsPanel.js";
|
|
20
|
+
export type { ReviewsPanelProps } from "./ReviewsPanel.js";
|
|
21
|
+
export { ReviewListPanel } from "./ReviewListPanel.js";
|
|
22
|
+
export type { ReviewListPanelProps } from "./ReviewListPanel.js";
|
|
23
|
+
export { ReviewFormCard } from "./ReviewFormCard.js";
|
|
24
|
+
export type { ReviewFormCardProps } from "./ReviewFormCard.js";
|
|
25
|
+
export { RatingBadge } from "./RatingBadge.js";
|
|
26
|
+
export type { RatingBadgeProps } from "./RatingBadge.js";
|
|
27
|
+
export { ReviewsSkinTheme } from "./theme.js";
|
|
28
|
+
export type { ReviewsSkinThemeProps } from "./theme.js";
|
|
29
|
+
export { ErrorAlert } from "./ErrorAlert.js";
|
|
30
|
+
export type { ThemeModeProp } from "./types.js";
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@stapel/reviews-react/default` — the antd skin over the headless pair.
|
|
3
|
+
*
|
|
4
|
+
* A separate entry point (the convention every pair's `/default` follows) so a
|
|
5
|
+
* host rendering its own review block never pulls `antd` into its bundle. The
|
|
6
|
+
* main entry has no visual opinion at all and no import path from it reaches
|
|
7
|
+
* this directory — size-limit and the bundle-purity test are the teeth on
|
|
8
|
+
* that.
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { createReviewsRuntime, ReviewsProvider } from "@stapel/reviews-react";
|
|
12
|
+
* import { ReviewsPanel } from "@stapel/reviews-react/default";
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* `<RatingBadge aggregate={…}>` is the seller-rating half: it renders two
|
|
16
|
+
* numbers the composite's projection produced, because stapel-reviews cannot
|
|
17
|
+
* roll a seller's listings up itself (main entry header).
|
|
18
|
+
*/
|
|
19
|
+
export { ReviewsPanel } from "./ReviewsPanel.js";
|
|
20
|
+
export { ReviewListPanel } from "./ReviewListPanel.js";
|
|
21
|
+
export { ReviewFormCard } from "./ReviewFormCard.js";
|
|
22
|
+
export { RatingBadge } from "./RatingBadge.js";
|
|
23
|
+
export { ReviewsSkinTheme } from "./theme.js";
|
|
24
|
+
export { ErrorAlert } from "./ErrorAlert.js";
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CSSProperties, ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { ThemeMode } from "@stapel/tokens-antd";
|
|
3
|
+
export interface ReviewsSkinThemeProps {
|
|
4
|
+
/**
|
|
5
|
+
* Light or dark. Defaults to the mode the host's document declares
|
|
6
|
+
* (`resolveThemeMode()`). Pass explicitly to pin a side.
|
|
7
|
+
*/
|
|
8
|
+
readonly mode?: ThemeMode;
|
|
9
|
+
/** Extra styles merged onto the themed root element (layout only — colors
|
|
10
|
+
* come from the mode). */
|
|
11
|
+
readonly style?: CSSProperties;
|
|
12
|
+
readonly children: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `<ReviewsSkinTheme/>` — the self-theming wrapper of
|
|
16
|
+
* `@stapel/reviews-react/default`. Standalone use is supported (wrap any
|
|
17
|
+
* composition of the skin's parts once); the shipped surfaces
|
|
18
|
+
* (`ReviewsPanel`, `ReviewListPanel`, `ReviewFormCard`, `RatingBadge`)
|
|
19
|
+
* each already wrap themselves, and nested antd
|
|
20
|
+
* `ConfigProvider`s merge, so composing them under one `ReviewsSkinTheme`
|
|
21
|
+
* stays correct.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ReviewsSkinTheme(props: ReviewsSkinThemeProps): ReactElement;
|
|
24
|
+
//# sourceMappingURL=theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/default/theme.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B;8BAC0B;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,YAAY,CAmB3E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The default skin's INTERNAL theme provider — every `/default` surface wraps
|
|
4
|
+
* itself in this, so the skin is readable no matter what the host supplies
|
|
5
|
+
* (tracker #26: a skin set with no internal provider once inherited a theme
|
|
6
|
+
* bridge serving light-mode values inside a dark document — text on
|
|
7
|
+
* background at 1.00:1). The theme derives from `@stapel/tokens` via
|
|
8
|
+
* `@stapel/tokens-antd`'s `toAntdThemeConfig(mode)`; `mode` defaults to what
|
|
9
|
+
* the HOST's document declares (`resolveThemeMode()` — the `data-theme`
|
|
10
|
+
* attribute `tokens.css` keys its dark block on), never to a hardcoded side.
|
|
11
|
+
*
|
|
12
|
+
* This is also what "override styles" means for this pair: a storefront
|
|
13
|
+
* retheming through the §68 token JSON regenerates its `--stapel-*` custom
|
|
14
|
+
* properties and the result page follows with zero code — a far better deal
|
|
15
|
+
* than a prop-per-colour API, and the reason the skin has none.
|
|
16
|
+
*/
|
|
17
|
+
import { useMemo } from "react";
|
|
18
|
+
import { ConfigProvider } from "antd";
|
|
19
|
+
import { resolveThemeMode, toAntdTheme, toAntdThemeConfig, } from "@stapel/tokens-antd";
|
|
20
|
+
/**
|
|
21
|
+
* `<ReviewsSkinTheme/>` — the self-theming wrapper of
|
|
22
|
+
* `@stapel/reviews-react/default`. Standalone use is supported (wrap any
|
|
23
|
+
* composition of the skin's parts once); the shipped surfaces
|
|
24
|
+
* (`ReviewsPanel`, `ReviewListPanel`, `ReviewFormCard`, `RatingBadge`)
|
|
25
|
+
* each already wrap themselves, and nested antd
|
|
26
|
+
* `ConfigProvider`s merge, so composing them under one `ReviewsSkinTheme`
|
|
27
|
+
* stays correct.
|
|
28
|
+
*/
|
|
29
|
+
export function ReviewsSkinTheme(props) {
|
|
30
|
+
const mode = props.mode ?? resolveThemeMode();
|
|
31
|
+
const theme = useMemo(() => toAntdThemeConfig(mode), [mode]);
|
|
32
|
+
const token = useMemo(() => toAntdTheme(mode), [mode]);
|
|
33
|
+
return (_jsx(ConfigProvider, { theme: theme, children: _jsx("div", { "data-reviews-skin-root": true, "data-reviews-skin-mode": mode, style: {
|
|
34
|
+
color: token.colorText,
|
|
35
|
+
backgroundColor: token.colorBgContainer,
|
|
36
|
+
...props.style,
|
|
37
|
+
}, children: props.children }) }));
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/default/theme.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AACtC,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAe7B;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,OAAO,CACL,KAAC,cAAc,IAAC,KAAK,EAAE,KAAK,YAC1B,wEAE0B,IAAI,EAC5B,KAAK,EAAE;gBACL,KAAK,EAAE,KAAK,CAAC,SAAS;gBACtB,eAAe,EAAE,KAAK,CAAC,gBAAgB;gBACvC,GAAG,KAAK,CAAC,KAAK;aACf,YAEA,KAAK,CAAC,QAAQ,GACX,GACS,CAClB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small shared types for the `/default` skin — kept in one place so every
|
|
3
|
+
* surface takes the same `mode` prop and re-exports the same error dialect.
|
|
4
|
+
*/
|
|
5
|
+
export type { FlowError } from "@stapel/core";
|
|
6
|
+
import type { ThemeMode } from "@stapel/tokens-antd";
|
|
7
|
+
/** Every `/default` surface accepts a theme mode; absent means "whatever the
|
|
8
|
+
* host document declares" (`resolveThemeMode()`), never a hardcoded side. */
|
|
9
|
+
export interface ThemeModeProp {
|
|
10
|
+
readonly mode?: ThemeMode;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/default/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;6EAC6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/default/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { LoadState } from "@stapel/core";
|
|
3
|
+
import type { RatingAggregate, ReviewTarget } from "../api/types.js";
|
|
4
|
+
import type { RatingSummary } from "../model/rating.js";
|
|
5
|
+
/** What `<ReviewAggregate>` hands its render prop. */
|
|
6
|
+
export interface ReviewAggregateBag {
|
|
7
|
+
/**
|
|
8
|
+
* The rating, READ — `rated: false` when nobody has rated the target, so a
|
|
9
|
+
* skin can never print the zero the wire carries in that case.
|
|
10
|
+
*/
|
|
11
|
+
readonly state: LoadState<RatingSummary>;
|
|
12
|
+
/** Where the number came from. */
|
|
13
|
+
readonly source: "fetched" | "supplied";
|
|
14
|
+
/** The deployment's rating ceiling, for a star row that draws `max` stars. */
|
|
15
|
+
readonly max: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ReviewAggregateProps {
|
|
18
|
+
/**
|
|
19
|
+
* What this rating is ABOUT. Always required, even when {@link aggregate}
|
|
20
|
+
* is supplied and nothing is fetched: it is the identity of the thing being
|
|
21
|
+
* rated, it keys the cache, and a rating rendered without one is a number
|
|
22
|
+
* nobody can trace back to a subject.
|
|
23
|
+
*/
|
|
24
|
+
readonly target: ReviewTarget;
|
|
25
|
+
/**
|
|
26
|
+
* Numbers somebody else computed. Supplying them makes this component a
|
|
27
|
+
* pure display: no request is made, and `source` reports `"supplied"`.
|
|
28
|
+
*/
|
|
29
|
+
readonly aggregate?: RatingAggregate;
|
|
30
|
+
readonly children: (bag: ReviewAggregateBag) => ReactNode;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The headless rating display — the same bag whether the number came from
|
|
34
|
+
* this module or from the composite. `AllowAny` upstream since 0.3.0, so a
|
|
35
|
+
* guest sees the rating and there is no "sign in" state here.
|
|
36
|
+
*
|
|
37
|
+
* ── Why `aggregate` is a prop, and not a second endpoint ───────────────────
|
|
38
|
+
*
|
|
39
|
+
* stapel-reviews can aggregate exactly ONE `(target_type, target_key)` per
|
|
40
|
+
* call. The storefront's product decision (spec fork F5) is that a review
|
|
41
|
+
* targets the SELLER for a specific listing — `target_type: "listing"`, one
|
|
42
|
+
* per author per listing — and the seller's own rating is therefore a ROLL-UP
|
|
43
|
+
* across every listing they own. No endpoint computes that: the composite
|
|
44
|
+
* does, server-side, as `shop.listing_review_summary`
|
|
45
|
+
* (`stapel_shop/projections.py`), and the roll-up over a seller's keys is the
|
|
46
|
+
* composite's projection to publish, on its own route.
|
|
47
|
+
*
|
|
48
|
+
* So this pair does the half it can honestly do: it renders `{avg, count}` —
|
|
49
|
+
* the projection's own field names, chosen upstream precisely so both modes
|
|
50
|
+
* answer one shape — from wherever the host got them. `source` says which,
|
|
51
|
+
* so a skin that wants to (a demo, an admin view) can show it. A seller
|
|
52
|
+
* header therefore reads
|
|
53
|
+
* `<ReviewAggregate target={{targetType: "seller", targetKey: id}}
|
|
54
|
+
* aggregate={rollup}>`: the target names the subject, the numbers come from
|
|
55
|
+
* the composite, and no request is made against an endpoint that could not
|
|
56
|
+
* have answered it.
|
|
57
|
+
*
|
|
58
|
+
* The gap is real and is recorded in the README rather than papered over with
|
|
59
|
+
* N+1 requests: a page that wants a seller rating today must be served the
|
|
60
|
+
* two numbers by its own backend.
|
|
61
|
+
*/
|
|
62
|
+
export declare function ReviewAggregate(props: ReviewAggregateProps): ReactElement;
|
|
63
|
+
//# sourceMappingURL=ReviewAggregate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReviewAggregate.d.ts","sourceRoot":"","sources":["../../src/headless/ReviewAggregate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAIrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;IACzC,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAC3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CAyBzE"}
|