@stapel/listings-react 0.4.0 → 0.7.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 +102 -0
- package/dist/api/generated/schema.d.ts +2 -2
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/default/FavoritesPane.d.ts +40 -3
- package/dist/default/FavoritesPane.d.ts.map +1 -1
- package/dist/default/FavoritesPane.js +30 -18
- package/dist/default/FavoritesPane.js.map +1 -1
- package/dist/default/ListingCard.d.ts +25 -4
- package/dist/default/ListingCard.d.ts.map +1 -1
- package/dist/default/ListingCard.js +19 -12
- package/dist/default/ListingCard.js.map +1 -1
- package/dist/default/ListingComposerPage.d.ts +163 -23
- package/dist/default/ListingComposerPage.d.ts.map +1 -1
- package/dist/default/ListingComposerPage.js +85 -43
- package/dist/default/ListingComposerPage.js.map +1 -1
- package/dist/default/ListingDetailPane.d.ts +41 -4
- package/dist/default/ListingDetailPane.d.ts.map +1 -1
- package/dist/default/ListingDetailPane.js +38 -25
- package/dist/default/ListingDetailPane.js.map +1 -1
- package/dist/default/ListingPhoto.d.ts +13 -1
- package/dist/default/ListingPhoto.d.ts.map +1 -1
- package/dist/default/ListingPhoto.js +47 -9
- package/dist/default/ListingPhoto.js.map +1 -1
- package/dist/default/ListingPrice.d.ts +13 -0
- package/dist/default/ListingPrice.d.ts.map +1 -0
- package/dist/default/ListingPrice.js +60 -0
- package/dist/default/ListingPrice.js.map +1 -0
- package/dist/default/MyListingsPane.d.ts +7 -28
- package/dist/default/MyListingsPane.d.ts.map +1 -1
- package/dist/default/MyListingsPane.js +136 -67
- package/dist/default/MyListingsPane.js.map +1 -1
- package/dist/default/SignInLink.js +8 -7
- package/dist/default/SignInLink.js.map +1 -1
- package/dist/default/StatusTags.d.ts +16 -5
- package/dist/default/StatusTags.d.ts.map +1 -1
- package/dist/default/StatusTags.js +19 -13
- package/dist/default/StatusTags.js.map +1 -1
- package/dist/default/index.d.ts +15 -9
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +13 -6
- package/dist/default/index.js.map +1 -1
- package/dist/headless/Favorites.d.ts +6 -0
- package/dist/headless/Favorites.d.ts.map +1 -1
- package/dist/headless/Favorites.js +4 -0
- package/dist/headless/Favorites.js.map +1 -1
- package/dist/headless/ListingActions.d.ts +12 -0
- package/dist/headless/ListingActions.d.ts.map +1 -1
- package/dist/headless/ListingActions.js +3 -0
- package/dist/headless/ListingActions.js.map +1 -1
- package/dist/headless/ListingComposer.d.ts +23 -0
- package/dist/headless/ListingComposer.d.ts.map +1 -1
- package/dist/headless/ListingComposer.js +33 -7
- package/dist/headless/ListingComposer.js.map +1 -1
- package/dist/i18n/es.d.ts +6 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +21 -4
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.d.ts +19 -2
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +35 -5
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts +16 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +33 -4
- package/dist/i18n/ru.js.map +1 -1
- package/dist/model/draft.d.ts +9 -6
- package/dist/model/draft.d.ts.map +1 -1
- package/dist/model/draft.js +3 -1
- package/dist/model/draft.js.map +1 -1
- package/dist/model/features.d.ts +21 -7
- package/dist/model/features.d.ts.map +1 -1
- package/dist/model/features.js +26 -0
- package/dist/model/features.js.map +1 -1
- package/llms.txt +7 -6
- package/manifest.json +53 -11
- package/nav-manifest.json +1 -1
- package/package.json +17 -12
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +2 -2
- package/src/default/FavoritesPane.tsx +156 -82
- package/src/default/ListingCard.tsx +88 -46
- package/src/default/ListingComposerPage.tsx +403 -187
- package/src/default/ListingDetailPane.tsx +200 -98
- package/src/default/ListingPhoto.tsx +89 -16
- package/src/default/ListingPrice.tsx +77 -0
- package/src/default/MyListingsPane.tsx +286 -228
- package/src/default/SignInLink.tsx +4 -4
- package/src/default/StatusTags.tsx +36 -18
- package/src/default/index.ts +18 -7
- package/src/headless/Favorites.tsx +10 -0
- package/src/headless/ListingActions.tsx +19 -0
- package/src/headless/ListingComposer.tsx +56 -5
- package/src/i18n/es.ts +26 -4
- package/src/i18n/keys.ts +42 -5
- package/src/i18n/ru.ts +38 -4
- package/src/model/draft.ts +12 -7
- package/src/model/features.ts +48 -7
- package/dist/default/ErrorAlert.d.ts +0 -9
- package/dist/default/ErrorAlert.d.ts.map +0 -1
- package/dist/default/ErrorAlert.js +0 -27
- package/dist/default/ErrorAlert.js.map +0 -1
- package/dist/default/theme.d.ts +0 -25
- package/dist/default/theme.d.ts.map +0 -1
- package/dist/default/theme.js +0 -40
- package/dist/default/theme.js.map +0 -1
- package/src/default/ErrorAlert.tsx +0 -46
- package/src/default/theme.tsx +0 -67
|
@@ -1,45 +1,58 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Button, Descriptions, Divider, Flex, Typography } from "antd";
|
|
3
|
+
import { ErrorAlert, EmptyState, GatedButton, GatedControl, SkinTheme, } from "@stapel/tokens-antd/skin";
|
|
4
|
+
import { SlotPlaceholder, matchLoad, useI18n, useT, } from "@stapel/core";
|
|
5
|
+
import { spacing } from "@stapel/tokens";
|
|
4
6
|
import { FeatureValueList } from "@stapel/attributes-react/default";
|
|
5
7
|
import { formatFeatureValue } from "@stapel/attributes-react";
|
|
6
8
|
import { useListingDetail } from "../headless/ListingDetail.js";
|
|
9
|
+
import { useListingActions } from "../headless/ListingActions.js";
|
|
7
10
|
import { asFeatureDaoList, featuresDtoFromDaoList } from "../model/features.js";
|
|
8
11
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
9
|
-
import { ErrorAlert } from "./ErrorAlert.js";
|
|
10
12
|
import { HeartIcon } from "./icons.js";
|
|
11
13
|
import { ListingPhoto } from "./ListingPhoto.js";
|
|
14
|
+
import { ListingPrice } from "./ListingPrice.js";
|
|
12
15
|
import { ListingStatusBlock } from "./StatusTags.js";
|
|
13
|
-
|
|
16
|
+
/** The reading measure of the page body. A detail page is prose plus a spec
|
|
17
|
+
* table; past this it stops being one column and starts being a stripe across
|
|
18
|
+
* a 2560px pane. */
|
|
19
|
+
export const DETAIL_MEASURE = "60rem";
|
|
20
|
+
/** The narrowest a gallery tile may get before the grid drops a column. A
|
|
21
|
+
* measure rather than a pixel: the tiles then fill whatever the ELEMENT is,
|
|
22
|
+
* which is §83's geometry rule — one photo per row on a phone, three on a
|
|
23
|
+
* desktop pane, and no `width: 320` that is near-full-bleed on one and a
|
|
24
|
+
* postage stamp on the other. */
|
|
25
|
+
export const DETAIL_PHOTO_MIN = "14rem";
|
|
14
26
|
export function ListingDetailPane(props) {
|
|
15
27
|
const t = useT();
|
|
16
28
|
const { locale } = useI18n();
|
|
17
|
-
const describe = useDescribeFlowError();
|
|
18
29
|
const bag = useListingDetail(props.id, props.viewerId !== undefined ? { viewerId: props.viewerId } : {});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const owner = bag.viewerIsOwner === true;
|
|
31
|
+
const actions = useListingActions(props.id, bag.status?.lifecycle.status);
|
|
32
|
+
const editGate = actions.editGate(props.onEdit !== undefined);
|
|
33
|
+
const favoriteLabel = t(bag.isFavorited === true
|
|
34
|
+
? LISTINGS_I18N_KEYS.cardFavoriteRemove
|
|
35
|
+
: LISTINGS_I18N_KEYS.cardFavoriteAdd);
|
|
36
|
+
return (_jsx(SkinTheme, { surface: "base", style: { maxWidth: DETAIL_MEASURE, padding: spacing[4] }, ...(props.mode !== undefined ? { mode: props.mode } : {}), children: _jsxs(Flex, { vertical: true, gap: spacing[4], "data-testid": "listings-detail", children: [bag.removed ? (_jsx(ErrorAlert, { testId: "listings-detail-removed", message: t(LISTINGS_I18N_KEYS.detailRemoved) })) : null, matchLoad(bag.state, {
|
|
37
|
+
loading: () => (_jsx("div", { role: "status", "aria-busy": "true", "aria-label": t(LISTINGS_I18N_KEYS.detailLoading), "data-testid": "listings-detail-loading", "data-stapel-load-state": "loading" })),
|
|
38
|
+
failed: (error) => bag.removed ? null : bag.notFound ? (_jsx(EmptyState, { testId: "listings-detail-error", title: t(LISTINGS_I18N_KEYS.detailNotFound) })) : (_jsx(ErrorAlert, { testId: "listings-detail-error", thrown: error, message: t(LISTINGS_I18N_KEYS.detailLoadFailed), onRetry: bag.refetch, retryLabel: t(LISTINGS_I18N_KEYS.detailRetry) })),
|
|
39
|
+
ready: (listing) => (_jsxs(_Fragment, { children: [owner && bag.status !== undefined ? (_jsxs(Flex, { vertical: true, gap: spacing[2], "data-testid": "listings-detail-owner-view", children: [_jsx(ListingStatusBlock, { status: bag.status }), !bag.publiclyVisible ? (_jsx(Typography.Text, { type: "secondary", children: t(LISTINGS_I18N_KEYS.detailOwnerOnlyView) })) : null] })) : null, !owner && !bag.publiclyVisible ? (_jsx(ErrorAlert, { testId: "listings-detail-not-published", message: t(LISTINGS_I18N_KEYS.detailNotPublished), variant: "inline" })) : null, _jsx("div", { "data-testid": "listings-detail-gallery", style: {
|
|
40
|
+
display: "grid",
|
|
41
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${DETAIL_PHOTO_MIN}, 1fr))`,
|
|
42
|
+
gap: spacing[3],
|
|
43
|
+
}, children: bag.images.length === 0 ? (_jsx(ListingPhoto, { imageRef: undefined, alt: listing.title ?? String(listing.id) })) : (bag.images.map((ref, index) => (_jsx(ListingPhoto, { imageRef: ref, alt: t(LISTINGS_I18N_KEYS.detailPhotoAlt, {
|
|
31
44
|
index: index + 1,
|
|
32
45
|
total: bag.images.length,
|
|
33
|
-
})
|
|
46
|
+
}) }, ref)))) }), _jsx(Typography.Title, { level: 3, "data-testid": "listings-detail-title", children: listing.title ?? "" }), bag.titleFeatures.length > 0 ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "listings-detail-title-features", children: bag.titleFeatures
|
|
34
47
|
.map((view) => formatFeatureValue(view.feature, view.value, { t, locale }))
|
|
35
48
|
.filter((text) => text !== undefined)
|
|
36
|
-
.join(" · ") })) : null, _jsx(Typography.Title, { level: 4, "data-testid": "listings-detail-price", children:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
.join(" · ") })) : null, _jsx(Typography.Title, { level: 4, "data-testid": "listings-detail-price", children: _jsx(ListingPrice, { amount: listing.price, ...(listing.currency !== undefined
|
|
50
|
+
? { currency: listing.currency }
|
|
51
|
+
: {}) }) }), _jsxs(Flex, { wrap: true, gap: spacing[3], align: "flex-start", "data-testid": "listings-detail-actions", children: [owner ? (_jsxs(_Fragment, { children: [_jsx(GatedButton, { gate: editGate, type: "primary", testId: "listings-detail-edit", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: () => {
|
|
52
|
+
props.onEdit?.(props.id);
|
|
53
|
+
}, children: t(LISTINGS_I18N_KEYS.detailEdit) }), _jsx(GatedButton, { gate: actions.archive, danger: true, testId: "listings-detail-take-down", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: actions.doArchive, children: t(LISTINGS_I18N_KEYS.detailTakeDown) })] })) : (_jsx("div", { "data-testid": "listings-detail-contact", children: props.contactSlot ?? _jsx(SlotPlaceholder, { name: "contactSlot" }) })), owner ? null : (_jsx(GatedControl, { gate: bag.favoriteGate, testId: "listings-detail-favorite-gate", children: (bind) => (_jsx(Button, { disabled: bind.disabled, "data-disabled-reason": "the enclosing <GatedControl> renders the gate's reason beside this button", ...(bind["aria-describedby"] !== undefined
|
|
54
|
+
? { "aria-describedby": bind["aria-describedby"] }
|
|
55
|
+
: {}), "aria-label": favoriteLabel, "aria-pressed": bag.isFavorited === true, icon: _jsx(HeartIcon, { filled: bag.isFavorited === true }), "data-testid": "listings-detail-favorite", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: bag.toggleFavorite, children: favoriteLabel })) })), props.actions] }), actions.error !== undefined && actions.error !== null ? (_jsx(ErrorAlert, { testId: "listings-detail-action-error", thrown: actions.error, variant: "inline" })) : null, _jsx(Divider, {}), _jsx(Typography.Title, { level: 5, children: t(LISTINGS_I18N_KEYS.detailDescription) }), _jsx(Typography.Paragraph, { "data-testid": "listings-detail-description", children: listing.description ?? "" }), _jsx(Typography.Title, { level: 5, children: t(LISTINGS_I18N_KEYS.detailSpecs) }), bag.features.length === 0 ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "listings-detail-no-specs", children: t(LISTINGS_I18N_KEYS.detailNoSpecs) })) : (_jsx(FeatureValueList, { features: bag.features.map((view) => view.feature), values: featuresDtoFromDaoList(asFeatureDaoList(listing.features)) })), bag.unreadableFeatures > 0 ? (_jsx(Typography.Text, { type: "warning", "data-testid": "listings-detail-unreadable", children: t(LISTINGS_I18N_KEYS.detailUnreadableFeatures, {
|
|
43
56
|
count: bag.unreadableFeatures,
|
|
44
57
|
}) })) : null, _jsxs(Descriptions, { size: "small", column: 1, children: [listing.stock_quantity != null ? (_jsx(Descriptions.Item, { label: t(LISTINGS_I18N_KEYS.detailStock), children: _jsx("span", { "data-testid": "listings-detail-stock", children: listing.stock_quantity }) })) : null, listing.location_label !== undefined &&
|
|
45
58
|
listing.location_label.length > 0 ? (_jsx(Descriptions.Item, { label: t(LISTINGS_I18N_KEYS.composeLocationLabel), children: listing.location_label })) : null] }), props.footer] })),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingDetailPane.js","sourceRoot":"","sources":["../../src/default/ListingDetailPane.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"ListingDetailPane.js","sourceRoot":"","sources":["../../src/default/ListingDetailPane.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A stored CDN reference, drawn — or
|
|
2
|
+
* A stored CDN reference, drawn — or a designed placeholder saying why not.
|
|
3
3
|
*
|
|
4
4
|
* `Listing.images` carries opaque `<type>/<hash>` strings and no contract in
|
|
5
5
|
* this fleet resolves a stranger's reference (`model/runtime.ts` argues it at
|
|
@@ -8,12 +8,24 @@
|
|
|
8
8
|
* says so instead of emitting a broken `<img>`. An empty grey box that never
|
|
9
9
|
* loads teaches a person nothing; a sentence gets the wiring fixed.
|
|
10
10
|
*
|
|
11
|
+
* The placeholder is TOKENISED and theme-aware. It was antd's `<Empty>`, whose
|
|
12
|
+
* illustration is a flat `#d9d9d9` slab: it glared white on every dark page in
|
|
13
|
+
* the visual pass and read as a broken image rather than as a designed "no
|
|
14
|
+
* photo yet". It is now the aspect-ratio box the photo would have occupied,
|
|
15
|
+
* painted from `colorFillQuaternary` with a camera glyph in
|
|
16
|
+
* `colorTextQuaternary`, so it belongs to whichever side the theme is on and
|
|
17
|
+
* never shifts the layout when a real photo lands.
|
|
18
|
+
*
|
|
11
19
|
* When the resolver DOES answer, `@stapel/image`'s `<Image>` takes over: it
|
|
12
20
|
* measures the slot, picks the variant tier that fits, blur-ups from
|
|
13
21
|
* `preview_b64` and never downgrades on a re-measure. None of that logic
|
|
14
22
|
* belongs here — it is why `@stapel/image` exists.
|
|
15
23
|
*/
|
|
16
24
|
import type { CSSProperties, ReactElement } from "react";
|
|
25
|
+
/** The default shape of a listing photo box everywhere in this skin. Declared
|
|
26
|
+
* once so a card, a detail hero and a dashboard thumbnail cannot drift into
|
|
27
|
+
* three different crops. */
|
|
28
|
+
export declare const LISTING_PHOTO_ASPECT = "4 / 3";
|
|
17
29
|
export interface ListingPhotoProps {
|
|
18
30
|
/** The stored reference, or `undefined` for a listing with no photos. */
|
|
19
31
|
readonly imageRef: string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingPhoto.d.ts","sourceRoot":"","sources":["../../src/default/ListingPhoto.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ListingPhoto.d.ts","sourceRoot":"","sources":["../../src/default/ListingPhoto.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AASzD;;4BAE4B;AAC5B,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAwBD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAmEnE"}
|
|
@@ -1,20 +1,58 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { Typography, theme as antdTheme } from "antd";
|
|
3
4
|
import { Image } from "@stapel/image";
|
|
4
5
|
import { useT } from "@stapel/core";
|
|
6
|
+
import { radii, spacing } from "@stapel/tokens";
|
|
5
7
|
import { useListingsRuntime } from "../model/context.js";
|
|
6
8
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
9
|
+
/** The default shape of a listing photo box everywhere in this skin. Declared
|
|
10
|
+
* once so a card, a detail hero and a dashboard thumbnail cannot drift into
|
|
11
|
+
* three different crops. */
|
|
12
|
+
export const LISTING_PHOTO_ASPECT = "4 / 3";
|
|
13
|
+
/** A camera outline in `currentColor` — the house convention (`icons.tsx`):
|
|
14
|
+
* no icon dependency, and it inherits the theme rather than carrying a
|
|
15
|
+
* colour. `aria-hidden` because the sentence beside it is the label. */
|
|
16
|
+
function CameraGlyph() {
|
|
17
|
+
return (_jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M3 8.5A2.5 2.5 0 0 1 5.5 6h1.7l1.2-2h6.2l1.2 2h1.7A2.5 2.5 0 0 1 20 8.5v8A2.5 2.5 0 0 1 17.5 19h-11A2.5 2.5 0 0 1 4 16.5z" }), _jsx("circle", { cx: "12", cy: "12.5", r: "3.2" })] }));
|
|
18
|
+
}
|
|
7
19
|
export function ListingPhoto(props) {
|
|
8
20
|
const t = useT();
|
|
21
|
+
const { token } = antdTheme.useToken();
|
|
9
22
|
const runtime = useListingsRuntime();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
23
|
+
// Memoised on the REFERENCE, not called in render. A host resolver is a
|
|
24
|
+
// plain function returning a fresh object (`resolveImage: (ref) => ({ … })`
|
|
25
|
+
// is the documented shape), so calling it inline handed `<Image>` a new
|
|
26
|
+
// `meta` identity on every render of this card — which is a load `<Image>`
|
|
27
|
+
// then has to decide is or is not the same one. It defends itself now, and
|
|
28
|
+
// a caller still should not manufacture the churn.
|
|
29
|
+
const resolve = runtime.resolveImage;
|
|
30
|
+
const imageRef = props.imageRef;
|
|
31
|
+
const meta = useMemo(() => (imageRef === undefined || resolve === undefined ? undefined : resolve(imageRef)), [imageRef, resolve]);
|
|
13
32
|
if (meta === undefined) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
const caption = t(props.imageRef === undefined
|
|
34
|
+
? LISTINGS_I18N_KEYS.cardNoPhoto
|
|
35
|
+
: LISTINGS_I18N_KEYS.cardPhotoUnavailable);
|
|
36
|
+
return (_jsxs("div", { role: "img", "aria-label": caption, "data-testid": "listings-photo-absent", style: {
|
|
37
|
+
display: "flex",
|
|
38
|
+
flexDirection: "column",
|
|
39
|
+
alignItems: "center",
|
|
40
|
+
justifyContent: "center",
|
|
41
|
+
gap: spacing[1],
|
|
42
|
+
width: "100%",
|
|
43
|
+
aspectRatio: LISTING_PHOTO_ASPECT,
|
|
44
|
+
background: token.colorFillQuaternary,
|
|
45
|
+
color: token.colorTextQuaternary,
|
|
46
|
+
borderRadius: radii.md,
|
|
47
|
+
...props.style,
|
|
48
|
+
}, children: [_jsx(CameraGlyph, {}), _jsx(Typography.Text, { type: "secondary", style: { fontSize: token.fontSizeSM, textAlign: "center" }, children: caption })] }));
|
|
17
49
|
}
|
|
18
|
-
return (_jsx(Image, { meta: meta, alt: props.alt, "data-testid": "listings-photo",
|
|
50
|
+
return (_jsx(Image, { meta: meta, alt: props.alt, "data-testid": "listings-photo", style: {
|
|
51
|
+
width: "100%",
|
|
52
|
+
aspectRatio: LISTING_PHOTO_ASPECT,
|
|
53
|
+
objectFit: "cover",
|
|
54
|
+
borderRadius: radii.md,
|
|
55
|
+
...props.style,
|
|
56
|
+
} }));
|
|
19
57
|
}
|
|
20
58
|
//# sourceMappingURL=ListingPhoto.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingPhoto.js","sourceRoot":"","sources":["../../src/default/ListingPhoto.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"ListingPhoto.js","sourceRoot":"","sources":["../../src/default/ListingPhoto.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
export interface ListingPriceProps {
|
|
3
|
+
/** The amount as the wire spells it — a decimal string. */
|
|
4
|
+
readonly amount: string | undefined;
|
|
5
|
+
/** The code the amount is quoted in (`Listing.currency`). */
|
|
6
|
+
readonly currency?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The price as text — the caller owns the typography, so one component serves
|
|
10
|
+
* the card's strong line and the detail page's display heading.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ListingPrice(props: ListingPriceProps): ReactElement;
|
|
13
|
+
//# sourceMappingURL=ListingPrice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListingPrice.d.ts","sourceRoot":"","sources":["../../src/default/ListingPrice.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAS1C,MAAM,WAAW,iBAAiB;IAChC,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAwBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAKnE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `<ListingPrice>` — the asking price, formatted as money.
|
|
4
|
+
*
|
|
5
|
+
* ── The defect this closes ─────────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* Every price on this pair's screens was `` `${price} ${currency}` `` —
|
|
8
|
+
* `4500.00 RUB`. An ISO code where a glyph belongs, no thousands grouping, a
|
|
9
|
+
* forced `.00` on a whole number, and the same string in `ru` and `es` as in
|
|
10
|
+
* `en`. `@stapel/currencies-react` exists precisely so no pair spells that
|
|
11
|
+
* template literal again: `formatMoney` is `Intl.NumberFormat` over a decimal
|
|
12
|
+
* STRING (never a `number` — `4500.10` is not a float), per locale and per the
|
|
13
|
+
* currency's own minor units.
|
|
14
|
+
*
|
|
15
|
+
* ── Two branches, because the catalogue is optional ────────────────────────
|
|
16
|
+
*
|
|
17
|
+
* A deployment that mounted `<CurrenciesProvider>` has the catalogue, so the
|
|
18
|
+
* price gets the currency's real glyph even where the locale carries none
|
|
19
|
+
* (`en` has no `₽`) and the formatting is the one the rest of that host's
|
|
20
|
+
* money uses. A host that did not mount it still gets a properly formatted,
|
|
21
|
+
* properly localized number through the pure `formatMoney` — the pair does not
|
|
22
|
+
* require a second module to render a price, and it does not silently fall
|
|
23
|
+
* back to the template literal either.
|
|
24
|
+
*
|
|
25
|
+
* The branch is decided by a context READ, and each arm is its own component,
|
|
26
|
+
* so no hook is called conditionally. `@stapel/currencies-react` is an
|
|
27
|
+
* OPTIONAL peer for exactly this reason: absent, only the plain arm ever
|
|
28
|
+
* mounts.
|
|
29
|
+
*/
|
|
30
|
+
import { useContext } from "react";
|
|
31
|
+
import { useI18n, useT } from "@stapel/core";
|
|
32
|
+
import { CurrenciesRuntimeContext, formatMoney, useMoney, } from "@stapel/currencies-react";
|
|
33
|
+
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
34
|
+
/** Is there a price at all? A listing may carry none, and "no price" is a
|
|
35
|
+
* sentence, not a zero. */
|
|
36
|
+
function hasAmount(props) {
|
|
37
|
+
return props.amount !== undefined && props.amount.length > 0;
|
|
38
|
+
}
|
|
39
|
+
function PlainPrice(props) {
|
|
40
|
+
const { locale } = useI18n();
|
|
41
|
+
return (_jsx(_Fragment, { children: formatMoney(props.amount ?? "", (props.currency ?? "").toUpperCase(), {
|
|
42
|
+
locale,
|
|
43
|
+
}) }));
|
|
44
|
+
}
|
|
45
|
+
function CataloguePrice(props) {
|
|
46
|
+
const money = useMoney();
|
|
47
|
+
return _jsx(_Fragment, { children: money.format(props.amount ?? "", (props.currency ?? "").toUpperCase()) });
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The price as text — the caller owns the typography, so one component serves
|
|
51
|
+
* the card's strong line and the detail page's display heading.
|
|
52
|
+
*/
|
|
53
|
+
export function ListingPrice(props) {
|
|
54
|
+
const t = useT();
|
|
55
|
+
const wired = useContext(CurrenciesRuntimeContext) !== null;
|
|
56
|
+
if (!hasAmount(props))
|
|
57
|
+
return _jsx(_Fragment, { children: t(LISTINGS_I18N_KEYS.cardPriceAbsent) });
|
|
58
|
+
return wired ? _jsx(CataloguePrice, { ...props }) : _jsx(PlainPrice, { ...props });
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=ListingPrice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListingPrice.js","sourceRoot":"","sources":["../../src/default/ListingPrice.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,wBAAwB,EACxB,WAAW,EACX,QAAQ,GACT,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AASrD;2BAC2B;AAC3B,SAAS,SAAS,CAAC,KAAwB;IACzC,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,UAAU,CAAC,KAAwB;IAC1C,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC;IAC7B,OAAO,CACL,4BACG,WAAW,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE;YACrE,MAAM;SACP,CAAC,GACD,CACJ,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAwB;IAC9C,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,OAAO,4BAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,GAAI,CAAC;AACvF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,KAAK,GAAG,UAAU,CAAC,wBAAwB,CAAC,KAAK,IAAI,CAAC;IAC5D,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,4BAAG,CAAC,CAAC,kBAAkB,CAAC,eAAe,CAAC,GAAI,CAAC;IAC3E,OAAO,KAAK,CAAC,CAAC,CAAC,KAAC,cAAc,OAAK,KAAK,GAAI,CAAC,CAAC,CAAC,KAAC,UAAU,OAAK,KAAK,GAAI,CAAC;AAC3E,CAAC"}
|
|
@@ -1,37 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `<MyListingsPane>` — the seller's dashboard.
|
|
3
|
-
*
|
|
4
|
-
* Three counts and the rows behind them, both real since stapel-listings
|
|
5
|
-
* 0.7.0 gave the owner's own listings a route (`GET my/listings/`). Until
|
|
6
|
-
* then this pane showed the counts and NAMED the missing endpoint where the
|
|
7
|
-
* rows should be; `model/mineSource.ts` keeps that history and the seam it
|
|
8
|
-
* left behind.
|
|
9
|
-
*
|
|
10
|
-
* Three things this pane refuses to smooth over:
|
|
11
|
-
*
|
|
12
|
-
* 1. **Both axes on every row.** "Published" and "changes under review" appear
|
|
13
|
-
* beside each other, which is the entire reason `model/status.ts` exists: a
|
|
14
|
-
* dashboard that derived one from the other would tell a seller their live
|
|
15
|
-
* listing is offline, or never tell them their edit is being screened.
|
|
16
|
-
* `moderation_status` is on the owner card, so the row reads the real
|
|
17
|
-
* value rather than the `"approved"` stand-in it used before 0.7.0.
|
|
18
|
-
* 2. **Takedowns are not in a tab.** The three tabs are the SERVER's status
|
|
19
|
-
* groupings and `blocked` is in none of them, because `my/counters` counts
|
|
20
|
-
* it in none of them. Folding it into one would make a tab's rows and its
|
|
21
|
-
* badge describe different sets; leaving it out entirely would hide the
|
|
22
|
-
* one listing whose owner most needs to know. So it sits above the tabs,
|
|
23
|
-
* where it cannot be missed.
|
|
24
|
-
* 3. **An empty tab says which emptiness it is.** "No drafts" and "nothing
|
|
25
|
-
* sold yet" are different sentences and one generic "nothing here" is
|
|
26
|
-
* neither.
|
|
27
|
-
*/
|
|
28
1
|
import type { ReactElement } from "react";
|
|
2
|
+
import type { SignInCtaProp } from "@stapel/core";
|
|
29
3
|
import type { MyListingsSource } from "../model/mineSource.js";
|
|
30
4
|
import type { ThemeModeProp } from "./types.js";
|
|
31
|
-
export interface MyListingsPaneProps extends ThemeModeProp {
|
|
5
|
+
export interface MyListingsPaneProps extends ThemeModeProp, SignInCtaProp {
|
|
32
6
|
/** How the host gets the caller's own rows. Absent: the contract's own
|
|
33
7
|
* `GET my/listings/`, which is what a storefront wants. */
|
|
34
8
|
readonly source?: MyListingsSource;
|
|
9
|
+
/**
|
|
10
|
+
* Open the composer on one of these listings. ABSENT IS A REAL ANSWER: the
|
|
11
|
+
* Edit button then states that this app has no editing screen instead of
|
|
12
|
+
* offering a click that does nothing.
|
|
13
|
+
*/
|
|
35
14
|
readonly onEdit?: (id: number) => void;
|
|
36
15
|
}
|
|
37
16
|
export declare function MyListingsPane(props: MyListingsPaneProps): ReactElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MyListingsPane.d.ts","sourceRoot":"","sources":["../../src/default/MyListingsPane.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MyListingsPane.d.ts","sourceRoot":"","sources":["../../src/default/MyListingsPane.tsx"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAU1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAIlD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAS/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAoJhD,MAAM,WAAW,mBAAoB,SAAQ,aAAa,EAAE,aAAa;IACvE;+DAC2D;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAkOvE"}
|