@stapel/listings-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.
- package/CHANGELOG.md +194 -0
- package/LICENSE +21 -0
- package/MODULE.md +97 -3
- package/README.md +67 -2
- package/dist/api/types.d.ts +1 -1
- package/dist/api/types.js +1 -1
- package/dist/default/FavoritesPane.d.ts +19 -4
- package/dist/default/FavoritesPane.d.ts.map +1 -1
- package/dist/default/FavoritesPane.js +13 -3
- package/dist/default/FavoritesPane.js.map +1 -1
- package/dist/default/ListingCard.d.ts +54 -11
- package/dist/default/ListingCard.d.ts.map +1 -1
- package/dist/default/ListingCard.js +32 -5
- package/dist/default/ListingCard.js.map +1 -1
- package/dist/default/ListingComposerPage.d.ts +59 -4
- package/dist/default/ListingComposerPage.d.ts.map +1 -1
- package/dist/default/ListingComposerPage.js +12 -3
- package/dist/default/ListingComposerPage.js.map +1 -1
- package/dist/default/SignInLink.d.ts +28 -0
- package/dist/default/SignInLink.d.ts.map +1 -0
- package/dist/default/SignInLink.js +17 -0
- package/dist/default/SignInLink.js.map +1 -0
- package/dist/default/index.d.ts +5 -3
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +1 -0
- package/dist/default/index.js.map +1 -1
- package/dist/headless/ListingComposer.d.ts +17 -0
- package/dist/headless/ListingComposer.d.ts.map +1 -1
- package/dist/headless/ListingComposer.js +26 -10
- package/dist/headless/ListingComposer.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +1 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.d.ts +2 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +3 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +2 -1
- package/dist/i18n/ru.js.map +1 -1
- package/llms.txt +1 -1
- package/manifest.json +2 -1
- package/nav-manifest.json +1 -1
- package/package.json +16 -16
- package/src/analytics/generated/events.json +1 -1
- package/src/default/FavoritesPane.tsx +40 -11
- package/src/default/ListingCard.tsx +144 -27
- package/src/default/ListingComposerPage.tsx +79 -14
- package/src/default/SignInLink.tsx +54 -0
- package/src/default/index.ts +15 -3
- package/src/headless/ListingComposer.tsx +43 -10
- package/src/i18n/es.ts +1 -0
- package/src/i18n/keys.ts +3 -0
- package/src/i18n/ru.ts +1 -0
package/dist/i18n/keys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,YAAY,EAAE,wBAAwB;IAEtC,4EAA4E;IAC5E,WAAW,EAAE,uBAAuB;IACpC,aAAa,EAAE,yBAAyB;IACxC,eAAe,EAAE,2BAA2B;IAC5C,YAAY,EAAE,wBAAwB;IACtC,aAAa,EAAE,yBAAyB;IACxC,UAAU,EAAE,sBAAsB;IAClC,cAAc,EAAE,0BAA0B;IAC1C,aAAa,EAAE,yBAAyB;IACxC,cAAc,EAAE,0BAA0B;IAE1C,4EAA4E;IAC5E,+CAA+C;IAC/C,qBAAqB,EAAE,kCAAkC;IACzD,qEAAqE;IACrE,yBAAyB,EAAE,uCAAuC;IAClE,yEAAyE;IACzE,wBAAwB,EAAE,qCAAqC;IAC/D,qBAAqB,EAAE,kCAAkC;IACzD,yBAAyB,EAAE,uCAAuC;IAClE,kBAAkB,EAAE,8BAA8B;IAClD,oEAAoE;IACpE,2BAA2B,EAAE,yCAAyC;IAEtE,4EAA4E;IAC5E,WAAW,EAAE,wBAAwB;IACrC,oBAAoB,EAAE,iCAAiC;IACvD,eAAe,EAAE,4BAA4B;IAC7C,eAAe,EAAE,4BAA4B;IAC7C,kBAAkB,EAAE,+BAA+B;IACnD,QAAQ,EAAE,oBAAoB;
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"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"}
|
package/dist/i18n/ru.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { listingsErrorBundleRu } from "./generated/errors.ru.gen.js";
|
|
|
6
6
|
* register it never carry these strings (the main entry does not import this
|
|
7
7
|
* module — gated by size-limit + the bundle-purity test).
|
|
8
8
|
*
|
|
9
|
-
* This is the storefront's DEFAULT language (
|
|
9
|
+
* This is the storefront's DEFAULT language (storefront spec verdict F1:
|
|
10
10
|
* ru-first), which is why the UI copy is here in full and not only the error
|
|
11
11
|
* keys: a seller filling in a listing is the surface where a half-translated
|
|
12
12
|
* form is most obvious.
|
package/dist/i18n/ru.d.ts.map
CHANGED
|
@@ -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,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,oBAAoB,EAAE,
|
|
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,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,oBAAoB,EAAE,cA8KlC,CAAC;AAEF,2DAA2D;AAC3D,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAE7D"}
|
package/dist/i18n/ru.js
CHANGED
|
@@ -6,7 +6,7 @@ export { listingsErrorBundleRu } from "./generated/errors.ru.gen.js";
|
|
|
6
6
|
* register it never carry these strings (the main entry does not import this
|
|
7
7
|
* module — gated by size-limit + the bundle-purity test).
|
|
8
8
|
*
|
|
9
|
-
* This is the storefront's DEFAULT language (
|
|
9
|
+
* This is the storefront's DEFAULT language (storefront spec verdict F1:
|
|
10
10
|
* ru-first), which is why the UI copy is here in full and not only the error
|
|
11
11
|
* keys: a seller filling in a listing is the surface where a half-translated
|
|
12
12
|
* form is most obvious.
|
|
@@ -66,6 +66,7 @@ export const listingsI18nBundleRu = {
|
|
|
66
66
|
"listings.card.favorite_add": "В избранное",
|
|
67
67
|
"listings.card.favorite_remove": "Убрать из избранного",
|
|
68
68
|
"listings.card.open": "Открыть",
|
|
69
|
+
"listings.card.sign_in": "Войти",
|
|
69
70
|
"listings.detail.loading": "Загружаем объявление…",
|
|
70
71
|
"listings.detail.load_failed": "Не удалось загрузить объявление",
|
|
71
72
|
"listings.detail.retry": "Повторить",
|
package/dist/i18n/ru.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAmB;IAClD,GAAG,qBAAqB;IAExB,4EAA4E;IAC5E,6BAA6B,EAAE,yBAAyB;IACxD,0BAA0B,EACxB,2DAA2D;IAC7D,uCAAuC,EACrC,0DAA0D;IAC5D,qCAAqC,EACnC,iDAAiD;IACnD,6BAA6B,EAAE,wBAAwB;IACvD,6BAA6B,EAAE,uBAAuB;IACtD,6BAA6B,EAAE,4BAA4B;IAC3D,sCAAsC,EACpC,2DAA2D;IAC7D,wCAAwC,EACtC,mEAAmE;IAErE,wBAAwB,EAAE,wCAAwC;IAElE,uBAAuB,EAAE,UAAU;IACnC,yBAAyB,EAAE,aAAa;IACxC,2BAA2B,EAAE,cAAc;IAC3C,wBAAwB,EAAE,gBAAgB;IAC1C,yBAAyB,EAAE,YAAY;IACvC,sBAAsB,EAAE,SAAS;IACjC,0BAA0B,EAAE,WAAW;IACvC,yBAAyB,EAAE,mBAAmB;IAC9C,0BAA0B,EAAE,UAAU;IAEtC,kCAAkC,EAChC,mFAAmF;IACrF,uCAAuC,EACrC,mGAAmG;IACrG,qCAAqC,EACnC,0DAA0D;IAC5D,kCAAkC,EAChC,uCAAuC;IACzC,uCAAuC,EACrC,iDAAiD;IACnD,8BAA8B,EAC5B,6DAA6D;IAC/D,yCAAyC,EACvC,yEAAyE;IAE3E,wBAAwB,EAAE,UAAU;IACpC,iCAAiC,EAAE,iBAAiB;IACpD,4BAA4B,EAAE,iBAAiB;IAC/C,4BAA4B,EAAE,aAAa;IAC3C,+BAA+B,EAAE,sBAAsB;IACvD,oBAAoB,EAAE,SAAS;
|
|
1
|
+
{"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"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"}
|
package/llms.txt
CHANGED
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/listings-react",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"backend": {
|
|
6
6
|
"module": "stapel-listings",
|
|
7
7
|
"contract": ">=0.6 <0.7"
|
|
@@ -788,6 +788,7 @@
|
|
|
788
788
|
"listings.card.open",
|
|
789
789
|
"listings.card.photo_unavailable",
|
|
790
790
|
"listings.card.price_absent",
|
|
791
|
+
"listings.card.sign_in",
|
|
791
792
|
"listings.compose.auto_republish",
|
|
792
793
|
"listings.compose.blocked.busy",
|
|
793
794
|
"listings.compose.blocked.details_unavailable",
|
package/nav-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stapel/listings-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Headless React pair for stapel-listings: a typed client over the draft twin, the composer that turns a category schema into a submittable listing, and the owner's dashboard. Two independent axes are rendered as two — lifecycle `status` decides visibility and `moderation_status` decides nothing about it, so an edit to a live listing stays live and says 'under review' instead of vanishing. Feature values are drawn and mirrored by @stapel/attributes-react; photos arrive as an injected upload bag whose `refs` ARE `images_draft` and whose `settled` gates the submit; a publish refusal is routed onto the control that caused it by slug. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -46,13 +46,6 @@
|
|
|
46
46
|
"nav-manifest.json",
|
|
47
47
|
"llms.txt"
|
|
48
48
|
],
|
|
49
|
-
"scripts": {
|
|
50
|
-
"build": "tsc -p tsconfig.json",
|
|
51
|
-
"test": "tsc -p tsconfig.test.json && vitest run --exclude test/prodBundlePurity.test.ts",
|
|
52
|
-
"lint": "eslint .",
|
|
53
|
-
"size": "size-limit",
|
|
54
|
-
"test:pack": "vitest run test/prodBundlePurity.test.ts"
|
|
55
|
-
},
|
|
56
49
|
"size-limit": [
|
|
57
50
|
{
|
|
58
51
|
"name": "index — the headless pair (client + status axes + draft model + hooks): no antd",
|
|
@@ -94,12 +87,6 @@
|
|
|
94
87
|
},
|
|
95
88
|
"devDependencies": {
|
|
96
89
|
"@size-limit/preset-small-lib": "^11.2.0",
|
|
97
|
-
"@stapel/attributes-react": "workspace:^",
|
|
98
|
-
"@stapel/core": "workspace:^",
|
|
99
|
-
"@stapel/image": "workspace:^",
|
|
100
|
-
"@stapel/showcase": "workspace:^",
|
|
101
|
-
"@stapel/tokens": "workspace:^",
|
|
102
|
-
"@stapel/tokens-antd": "workspace:^",
|
|
103
90
|
"@tanstack/react-query": "^5.81.0",
|
|
104
91
|
"@testing-library/react": "^16.3.0",
|
|
105
92
|
"@types/react": "^19.1.0",
|
|
@@ -110,12 +97,25 @@
|
|
|
110
97
|
"react-dom": "^19.1.0",
|
|
111
98
|
"size-limit": "^11.2.0",
|
|
112
99
|
"typescript": "^5.8.3",
|
|
113
|
-
"vitest": "^3.2.4"
|
|
100
|
+
"vitest": "^3.2.4",
|
|
101
|
+
"@stapel/attributes-react": "^0.2.0",
|
|
102
|
+
"@stapel/core": "^0.16.0",
|
|
103
|
+
"@stapel/image": "^0.2.0",
|
|
104
|
+
"@stapel/showcase": "^0.2.0",
|
|
105
|
+
"@stapel/tokens": "^0.5.0",
|
|
106
|
+
"@stapel/tokens-antd": "^0.5.0"
|
|
114
107
|
},
|
|
115
108
|
"engines": {
|
|
116
109
|
"node": ">=22"
|
|
117
110
|
},
|
|
118
111
|
"publishConfig": {
|
|
119
112
|
"access": "public"
|
|
113
|
+
},
|
|
114
|
+
"scripts": {
|
|
115
|
+
"build": "tsc -p tsconfig.json",
|
|
116
|
+
"test": "tsc -p tsconfig.test.json && vitest run --exclude test/prodBundlePurity.test.ts",
|
|
117
|
+
"lint": "eslint .",
|
|
118
|
+
"size": "size-limit",
|
|
119
|
+
"test:pack": "vitest run test/prodBundlePurity.test.ts"
|
|
120
120
|
}
|
|
121
|
-
}
|
|
121
|
+
}
|
|
@@ -10,17 +10,52 @@
|
|
|
10
10
|
import type { ReactElement } from "react";
|
|
11
11
|
import { Alert, Button, Empty, Flex, Space, Spin, Typography } from "antd";
|
|
12
12
|
import { matchList, useDescribeFlowError, useT } from "@stapel/core";
|
|
13
|
+
import type { LinkComponent } from "@stapel/core";
|
|
13
14
|
import { useFavorites } from "../headless/Favorites.js";
|
|
14
15
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
15
16
|
import { ErrorAlert } from "./ErrorAlert.js";
|
|
16
17
|
import { ListingCard } from "./ListingCard.js";
|
|
18
|
+
import type { ListingCardOpenProps } from "./ListingCard.js";
|
|
17
19
|
import { ListingsSkinTheme } from "./theme.js";
|
|
18
20
|
import type { ThemeModeProp } from "./types.js";
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/** How a card in this grid opens — the same one-contract union `<ListingCard>`
|
|
23
|
+
* takes, one level up, so a pane cannot re-introduce the double navigation the
|
|
24
|
+
* card no longer allows. */
|
|
25
|
+
export type FavoritesPaneOpenProps =
|
|
26
|
+
| {
|
|
27
|
+
/** Where a card leads. The container owns routing. */
|
|
28
|
+
readonly hrefFor: (id: number) => string;
|
|
29
|
+
/** The host's `<Link>`, so a click stays inside the SPA. */
|
|
30
|
+
readonly linkComponent?: LinkComponent;
|
|
31
|
+
readonly onOpen?: undefined;
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
readonly onOpen: (id: number) => void;
|
|
35
|
+
readonly hrefFor?: undefined;
|
|
36
|
+
readonly linkComponent?: undefined;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
readonly hrefFor?: undefined;
|
|
40
|
+
readonly onOpen?: undefined;
|
|
41
|
+
readonly linkComponent?: undefined;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type FavoritesPaneProps = ThemeModeProp & FavoritesPaneOpenProps;
|
|
45
|
+
|
|
46
|
+
/** The card's own open props for one row. One arm, never two. */
|
|
47
|
+
function cardOpenProps(
|
|
48
|
+
props: FavoritesPaneOpenProps,
|
|
49
|
+
id: number
|
|
50
|
+
): ListingCardOpenProps {
|
|
51
|
+
if (props.hrefFor !== undefined) {
|
|
52
|
+
const href = props.hrefFor(id);
|
|
53
|
+
return props.linkComponent !== undefined
|
|
54
|
+
? { href, linkComponent: props.linkComponent }
|
|
55
|
+
: { href };
|
|
56
|
+
}
|
|
57
|
+
if (props.onOpen !== undefined) return { onOpen: props.onOpen };
|
|
58
|
+
return {};
|
|
24
59
|
}
|
|
25
60
|
|
|
26
61
|
export function FavoritesPane(props: FavoritesPaneProps): ReactElement {
|
|
@@ -82,13 +117,7 @@ export function FavoritesPane(props: FavoritesPaneProps): ReactElement {
|
|
|
82
117
|
<Flex wrap gap={16} data-testid="listings-favorites-grid">
|
|
83
118
|
{rows.map((row) => (
|
|
84
119
|
<div key={row.id} style={{ width: 240 }}>
|
|
85
|
-
<ListingCard
|
|
86
|
-
listing={row}
|
|
87
|
-
{...(props.hrefFor !== undefined
|
|
88
|
-
? { href: props.hrefFor(row.id) }
|
|
89
|
-
: {})}
|
|
90
|
-
{...(props.onOpen !== undefined ? { onOpen: props.onOpen } : {})}
|
|
91
|
-
/>
|
|
120
|
+
<ListingCard listing={row} {...cardOpenProps(props, row.id)} />
|
|
92
121
|
</div>
|
|
93
122
|
))}
|
|
94
123
|
</Flex>
|
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
* `@stapel/search-react` takes a `renderCard` slot and the container fills it
|
|
5
5
|
* with this (spec §3.7 / §6.2 item 1). The two pairs never import each other;
|
|
6
6
|
* the CONTAINER is the seam, which is why this component takes a plain card
|
|
7
|
-
* row and a plain `href
|
|
7
|
+
* row and a plain `href` rather than reaching for a router.
|
|
8
|
+
*
|
|
9
|
+
* ── One click, one navigation ──────────────────────────────────────────────
|
|
10
|
+
*
|
|
11
|
+
* `href` and `onOpen` used to be two optional props, and a card given both
|
|
12
|
+
* navigated TWICE: the handler ran, and the browser then followed the anchor
|
|
13
|
+
* that was still on the button. They are now three arms of a union — link,
|
|
14
|
+
* button, or neither — and `linkComponent` rides on the link arm so a
|
|
15
|
+
* container can hand in its router's `<Link>` and keep the anchor.
|
|
8
16
|
*
|
|
9
17
|
* ── What it renders without asking the server anything else ────────────────
|
|
10
18
|
*
|
|
@@ -16,13 +24,17 @@
|
|
|
16
24
|
*
|
|
17
25
|
* ── The heart is never hidden ──────────────────────────────────────────────
|
|
18
26
|
*
|
|
19
|
-
* A visitor sees it, blocked, with the reason
|
|
20
|
-
* container
|
|
21
|
-
* favourites exist (private-space canon
|
|
27
|
+
* A visitor sees it, blocked, with the reason IN WORDS beside it and the
|
|
28
|
+
* sign-in link the container supplies (`signIn`, typically `?next=<current>`).
|
|
29
|
+
* Hiding it would teach nobody that favourites exist (private-space canon
|
|
30
|
+
* §6.3, spec §6.2 item 6) — and until 0.3.0 the reason lived only in a tooltip
|
|
31
|
+
* on a disabled button, which receives no pointer events in any browser: a
|
|
32
|
+
* reason nobody could read, and no door to walk through.
|
|
22
33
|
*/
|
|
23
34
|
import type { ReactElement, ReactNode } from "react";
|
|
24
35
|
import { Button, Card, Flex, Tooltip, Typography } from "antd";
|
|
25
|
-
import { useT } from "@stapel/core";
|
|
36
|
+
import { useActionGate, useT } from "@stapel/core";
|
|
37
|
+
import type { LinkComponent, SignInCtaProp } from "@stapel/core";
|
|
26
38
|
import { FeatureBadges } from "@stapel/attributes-react/default";
|
|
27
39
|
import type { ListingCard as ListingCardData } from "../api/types.js";
|
|
28
40
|
import { asFeatureDaoList, featuresDtoFromDaoList, featuresFromDaoList } from "../model/features.js";
|
|
@@ -30,18 +42,53 @@ import { lifecycleCaption } from "../model/status.js";
|
|
|
30
42
|
import { useFavoriteToggle } from "../headless/Favorites.js";
|
|
31
43
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
32
44
|
import { HeartIcon } from "./icons.js";
|
|
45
|
+
import { SignInLink } from "./SignInLink.js";
|
|
33
46
|
import { ListingPhoto } from "./ListingPhoto.js";
|
|
34
47
|
import { ListingsSkinTheme } from "./theme.js";
|
|
35
48
|
import type { ThemeModeProp } from "./types.js";
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
/**
|
|
51
|
+
* How the card opens — ONE of three, and the type says so.
|
|
52
|
+
*
|
|
53
|
+
* It used to be two optional props, and a caller that passed both got two
|
|
54
|
+
* navigations for one click: the handler ran and the browser then followed the
|
|
55
|
+
* anchor anyway. The storefront worked around it by passing `onOpen` only,
|
|
56
|
+
* which cost it a real anchor (no middle-click, no "open in new tab", nothing
|
|
57
|
+
* for a crawler to follow) on the most linkable element in the app.
|
|
58
|
+
*
|
|
59
|
+
* So the union has three arms and no fourth: a link, a button, or neither.
|
|
60
|
+
* `linkComponent` belongs to the link arm because it IS the link — handing a
|
|
61
|
+
* `<Link>` to a card that navigates by callback would be two answers to one
|
|
62
|
+
* question again.
|
|
63
|
+
*/
|
|
64
|
+
export type ListingCardOpenProps =
|
|
65
|
+
| {
|
|
66
|
+
/** Where the card leads. A plain path — the pair never calls
|
|
67
|
+
* `window.location` and never builds a router descriptor. */
|
|
68
|
+
readonly href: string;
|
|
69
|
+
/** The host's `<Link>`, so the click stays inside the SPA. Absent: an
|
|
70
|
+
* antd link-button carrying the `href`, which reloads the page in a
|
|
71
|
+
* router app — correct, just not fast. */
|
|
72
|
+
readonly linkComponent?: LinkComponent;
|
|
73
|
+
readonly onOpen?: undefined;
|
|
74
|
+
}
|
|
75
|
+
| {
|
|
76
|
+
/** The card opens by callback: rendered as a button, with no `href` for
|
|
77
|
+
* the browser to follow after the handler has already navigated. */
|
|
78
|
+
readonly onOpen: (id: number) => void;
|
|
79
|
+
readonly href?: undefined;
|
|
80
|
+
readonly linkComponent?: undefined;
|
|
81
|
+
}
|
|
82
|
+
| {
|
|
83
|
+
/** No open control at all — a card inside a screen that is already the
|
|
84
|
+
* listing. */
|
|
85
|
+
readonly href?: undefined;
|
|
86
|
+
readonly onOpen?: undefined;
|
|
87
|
+
readonly linkComponent?: undefined;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export interface ListingCardBaseProps extends ThemeModeProp, SignInCtaProp {
|
|
38
91
|
readonly listing: ListingCardData;
|
|
39
|
-
/** Where the card leads. The container owns routing, so this is a plain
|
|
40
|
-
* href a `<Link>` or an `<a>` can carry — the pair never calls
|
|
41
|
-
* `window.location`. */
|
|
42
|
-
readonly href?: string;
|
|
43
|
-
/** Called instead of following `href`, for a host with its own navigation. */
|
|
44
|
-
readonly onOpen?: (id: number) => void;
|
|
45
92
|
/** Extra chrome the container adds (a `promoted` tag from search, say —
|
|
46
93
|
* DSA Art. 26 marking belongs to the pair that receives it). */
|
|
47
94
|
readonly badge?: ReactNode;
|
|
@@ -50,6 +97,73 @@ export interface ListingCardProps extends ThemeModeProp {
|
|
|
50
97
|
readonly showFavorite?: boolean;
|
|
51
98
|
}
|
|
52
99
|
|
|
100
|
+
export type ListingCardProps = ListingCardBaseProps & ListingCardOpenProps;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The one control that opens the card: an anchor, a button, or nothing.
|
|
104
|
+
*
|
|
105
|
+
* Exactly one of the three renders, so exactly one navigation happens per
|
|
106
|
+
* click. That is the whole fix — the branch below has no arm in which both a
|
|
107
|
+
* handler and an `href` reach the DOM.
|
|
108
|
+
*/
|
|
109
|
+
function OpenControl(
|
|
110
|
+
props: ListingCardOpenProps & { readonly listingId: number }
|
|
111
|
+
): ReactElement | null {
|
|
112
|
+
const t = useT();
|
|
113
|
+
const label = t(LISTINGS_I18N_KEYS.cardOpen);
|
|
114
|
+
|
|
115
|
+
if (props.href !== undefined) {
|
|
116
|
+
const Link = props.linkComponent;
|
|
117
|
+
// The host's component is rendered as it comes: this pair has no CSS and
|
|
118
|
+
// no way to hand antd's button styling to a foreign element, and a wrapper
|
|
119
|
+
// element around a link is a click target that is not the link. A host
|
|
120
|
+
// that wants the antd look styles its own `<Link>` — it is one component,
|
|
121
|
+
// written once, and it is already the thing that knows the design system.
|
|
122
|
+
return Link !== undefined ? (
|
|
123
|
+
<Link
|
|
124
|
+
href={props.href}
|
|
125
|
+
aria-label={label}
|
|
126
|
+
data-testid="listings-card-open"
|
|
127
|
+
data-analytics="none"
|
|
128
|
+
data-analytics-reason="business action — host app wraps with its own tracked()"
|
|
129
|
+
>
|
|
130
|
+
{label}
|
|
131
|
+
</Link>
|
|
132
|
+
) : (
|
|
133
|
+
<Button
|
|
134
|
+
size="small"
|
|
135
|
+
type="link"
|
|
136
|
+
href={props.href}
|
|
137
|
+
data-testid="listings-card-open"
|
|
138
|
+
data-analytics="none"
|
|
139
|
+
data-analytics-reason="business action — host app wraps with its own tracked()"
|
|
140
|
+
>
|
|
141
|
+
{label}
|
|
142
|
+
</Button>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (props.onOpen !== undefined) {
|
|
147
|
+
const onOpen = props.onOpen;
|
|
148
|
+
return (
|
|
149
|
+
<Button
|
|
150
|
+
size="small"
|
|
151
|
+
type="link"
|
|
152
|
+
data-testid="listings-card-open"
|
|
153
|
+
data-analytics="none"
|
|
154
|
+
data-analytics-reason="business action — host app wraps with its own tracked()"
|
|
155
|
+
onClick={() => {
|
|
156
|
+
onOpen(props.listingId);
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
{label}
|
|
160
|
+
</Button>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
53
167
|
export function ListingCard(props: ListingCardProps): ReactElement {
|
|
54
168
|
const t = useT();
|
|
55
169
|
const { listing } = props;
|
|
@@ -66,6 +180,8 @@ export function ListingCard(props: ListingCardProps): ReactElement {
|
|
|
66
180
|
? `${listing.price} ${listing.currency ?? ""}`.trim()
|
|
67
181
|
: t(LISTINGS_I18N_KEYS.cardPriceAbsent);
|
|
68
182
|
|
|
183
|
+
const favoriteGate = useActionGate(favorite.gate);
|
|
184
|
+
|
|
69
185
|
const favoriteLabel = t(
|
|
70
186
|
favorite.favorited
|
|
71
187
|
? LISTINGS_I18N_KEYS.cardFavoriteRemove
|
|
@@ -127,21 +243,7 @@ export function ListingCard(props: ListingCardProps): ReactElement {
|
|
|
127
243
|
) : null}
|
|
128
244
|
|
|
129
245
|
<Flex gap={8} align="center">
|
|
130
|
-
{props
|
|
131
|
-
<Button
|
|
132
|
-
size="small"
|
|
133
|
-
type="link"
|
|
134
|
-
{...(props.href !== undefined ? { href: props.href } : {})}
|
|
135
|
-
data-testid="listings-card-open"
|
|
136
|
-
data-analytics="none"
|
|
137
|
-
data-analytics-reason="business action — host app wraps with its own tracked()"
|
|
138
|
-
onClick={() => {
|
|
139
|
-
props.onOpen?.(listing.id);
|
|
140
|
-
}}
|
|
141
|
-
>
|
|
142
|
-
{t(LISTINGS_I18N_KEYS.cardOpen)}
|
|
143
|
-
</Button>
|
|
144
|
-
) : null}
|
|
246
|
+
<OpenControl {...props} listingId={listing.id} />
|
|
145
247
|
|
|
146
248
|
{props.showFavorite === false ? null : (
|
|
147
249
|
<Tooltip
|
|
@@ -171,6 +273,21 @@ export function ListingCard(props: ListingCardProps): ReactElement {
|
|
|
171
273
|
</Tooltip>
|
|
172
274
|
)}
|
|
173
275
|
</Flex>
|
|
276
|
+
|
|
277
|
+
{/* The reason IN WORDS, plus the door. A tooltip on a disabled
|
|
278
|
+
button is a reason nobody can read (core's actionGate.ts says so
|
|
279
|
+
in as many words), and a reason with no next action leaves the
|
|
280
|
+
visitor hunting for the header — which is what the storefront
|
|
281
|
+
had to write a paragraph about instead of shipping the screen. */}
|
|
282
|
+
{props.showFavorite === false || favoriteGate.reason === undefined ? null : (
|
|
283
|
+
<Typography.Text
|
|
284
|
+
type="secondary"
|
|
285
|
+
data-testid="listings-card-favorite-blocked"
|
|
286
|
+
>
|
|
287
|
+
{favoriteGate.reason}
|
|
288
|
+
<SignInLink cta={props.signIn} testId="listings-card-sign-in" />
|
|
289
|
+
</Typography.Text>
|
|
290
|
+
)}
|
|
174
291
|
</Flex>
|
|
175
292
|
</Card>
|
|
176
293
|
</ListingsSkinTheme>
|
|
@@ -5,12 +5,29 @@
|
|
|
5
5
|
* the component can be read top to bottom without knowing any of the other
|
|
6
6
|
* pairs:
|
|
7
7
|
*
|
|
8
|
-
* category `
|
|
8
|
+
* category `renderCategoryPicker` — the container's `<CategoryPickerField>`,
|
|
9
|
+
* given `setCategory` to call
|
|
9
10
|
* details `features` — the schema, drawn by `<FeatureFields>` (L0)
|
|
10
|
-
* photos `gallerySlot` — the container's `<MediaGalleryField>`,
|
|
11
|
-
* bag is handed
|
|
11
|
+
* photos `gallerySlot` — the container's `<MediaGalleryField bag>`,
|
|
12
|
+
* whose bag is handed here as `images`
|
|
12
13
|
* the draft this pair
|
|
13
14
|
*
|
|
15
|
+
* ── Why the category slot is a render prop and not a node ──────────────────
|
|
16
|
+
*
|
|
17
|
+
* It was a node (`categorySlot`), and a node cannot be mounted: the composer's
|
|
18
|
+
* category moves only through `bag.setCategory`, and a `ReactNode` handed in
|
|
19
|
+
* from outside has no way to reach it. There was no `onCategoryChange`either,
|
|
20
|
+
* so a container could neither set the category nor learn it — and `features`,
|
|
21
|
+
* the schema of the chosen category, was therefore unreachable rather than
|
|
22
|
+
* merely withheld. The screen could not be mounted at all (storefront Wave D,
|
|
23
|
+
* named gap G-1).
|
|
24
|
+
*
|
|
25
|
+
* `renderCategoryPicker({ value, setCategory })` is the shape
|
|
26
|
+
* `<CategoryPage renderListings>` already uses in the sibling pair, and the
|
|
27
|
+
* controlled pair `category` / `onCategoryChange` is there for the container
|
|
28
|
+
* that holds the id anyway — it must, because the schema read
|
|
29
|
+
* (`useCategoryFeatures(id)`) that fills `features` is keyed by it.
|
|
30
|
+
*
|
|
14
31
|
* ── Every blocked control says which of six reasons it is ──────────────────
|
|
15
32
|
*
|
|
16
33
|
* The publish button is the most-gated control in the fleet, and that is the
|
|
@@ -55,6 +72,19 @@ import { ErrorAlert } from "./ErrorAlert.js";
|
|
|
55
72
|
import { ListingsSkinTheme } from "./theme.js";
|
|
56
73
|
import type { ThemeModeProp } from "./types.js";
|
|
57
74
|
|
|
75
|
+
/**
|
|
76
|
+
* What `renderCategoryPicker` is handed: the current category and the ONLY
|
|
77
|
+
* function that changes it. Same shape as any other bag in this monorepo — the
|
|
78
|
+
* value plus the setter, never a setter alone.
|
|
79
|
+
*/
|
|
80
|
+
export interface ComposerCategorySlot {
|
|
81
|
+
/** The category the draft currently carries. Empty string: none chosen. */
|
|
82
|
+
readonly value: string;
|
|
83
|
+
/** Choose a category. Prunes the answers the new schema does not ask for
|
|
84
|
+
* (one render later, once `features` arrives) and reports upwards. */
|
|
85
|
+
readonly setCategory: (categoryId: string) => void;
|
|
86
|
+
}
|
|
87
|
+
|
|
58
88
|
export interface ListingComposerPageProps extends ThemeModeProp {
|
|
59
89
|
/** Editing an existing listing; absent for a new one. */
|
|
60
90
|
readonly listingId?: number;
|
|
@@ -63,7 +93,33 @@ export interface ListingComposerPageProps extends ThemeModeProp {
|
|
|
63
93
|
readonly features: readonly FeatureDef[];
|
|
64
94
|
readonly featuresLoading?: boolean;
|
|
65
95
|
readonly featuresError?: unknown;
|
|
66
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* The category chooser, handed the value and the setter. This is the slot a
|
|
98
|
+
* container fills with `@stapel/categories-react`'s `<CategoryPickerField>`:
|
|
99
|
+
*
|
|
100
|
+
* ```tsx
|
|
101
|
+
* renderCategoryPicker={({ value, setCategory }) => (
|
|
102
|
+
* <CategoryPickerField
|
|
103
|
+
* value={value === "" ? null : Number(value)}
|
|
104
|
+
* onChange={(id) => setCategory(id === null ? "" : String(id))}
|
|
105
|
+
* />
|
|
106
|
+
* )}
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
readonly renderCategoryPicker?: (slot: ComposerCategorySlot) => ReactNode;
|
|
110
|
+
/**
|
|
111
|
+
* The chosen category, when the container owns that state — the usual case,
|
|
112
|
+
* since the same id keys the `features` read.
|
|
113
|
+
*/
|
|
114
|
+
readonly category?: string;
|
|
115
|
+
/** Called whenever the category changes, controlled or not. */
|
|
116
|
+
readonly onCategoryChange?: (categoryId: string) => void;
|
|
117
|
+
/**
|
|
118
|
+
* @deprecated A node cannot reach `setCategory`, so a picker rendered into
|
|
119
|
+
* this slot could never tell the composer what was chosen. Use
|
|
120
|
+
* `renderCategoryPicker` (or the controlled `category` /
|
|
121
|
+
* `onCategoryChange` pair). Still rendered, so nothing that passed it breaks.
|
|
122
|
+
*/
|
|
67
123
|
readonly categorySlot?: ReactNode;
|
|
68
124
|
/** The photo grid. Its bag is what `images` carries. */
|
|
69
125
|
readonly gallerySlot?: ReactNode;
|
|
@@ -89,6 +145,10 @@ export function ListingComposerPage(
|
|
|
89
145
|
: {}),
|
|
90
146
|
editorTypes: BUILTIN_VALUE_EDITOR_TYPES,
|
|
91
147
|
...(props.images !== undefined ? { images: props.images } : {}),
|
|
148
|
+
...(props.category !== undefined ? { category: props.category } : {}),
|
|
149
|
+
...(props.onCategoryChange !== undefined
|
|
150
|
+
? { onCategoryChange: props.onCategoryChange }
|
|
151
|
+
: {}),
|
|
92
152
|
...(props.onPublished !== undefined
|
|
93
153
|
? {
|
|
94
154
|
// `listing_id` comes back in the response, so the callback never
|
|
@@ -160,16 +220,21 @@ export function ListingComposerPage(
|
|
|
160
220
|
extra={t(LISTINGS_I18N_KEYS.composeCategoryHelp)}
|
|
161
221
|
{...errorOf(CATEGORY_FIELD)}
|
|
162
222
|
>
|
|
163
|
-
{props.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
223
|
+
{props.renderCategoryPicker !== undefined
|
|
224
|
+
? props.renderCategoryPicker({
|
|
225
|
+
value: bag.values.categoryId,
|
|
226
|
+
setCategory: bag.setCategory,
|
|
227
|
+
})
|
|
228
|
+
: (props.categorySlot ?? (
|
|
229
|
+
<Input
|
|
230
|
+
value={bag.values.categoryId}
|
|
231
|
+
aria-label={t(LISTINGS_I18N_KEYS.composeCategory)}
|
|
232
|
+
data-testid="listings-composer-category"
|
|
233
|
+
onChange={(event) => {
|
|
234
|
+
bag.setCategory(event.target.value);
|
|
235
|
+
}}
|
|
236
|
+
/>
|
|
237
|
+
))}
|
|
173
238
|
</Form.Item>
|
|
174
239
|
|
|
175
240
|
{bag.droppedOnCategoryChange.length > 0 ? (
|