@stapel/chat-react 0.2.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 +46 -0
- package/MODULE.md +32 -0
- package/README.md +21 -0
- 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/StartChatButton.d.ts +7 -3
- package/dist/default/StartChatButton.d.ts.map +1 -1
- package/dist/default/StartChatButton.js +3 -2
- package/dist/default/StartChatButton.js.map +1 -1
- package/dist/default/index.d.ts +2 -0
- 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/StartDirectChat.d.ts +5 -0
- package/dist/headless/StartDirectChat.d.ts.map +1 -1
- package/dist/headless/StartDirectChat.js +29 -2
- package/dist/headless/StartDirectChat.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +3 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.d.ts +6 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +9 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +3 -0
- package/dist/i18n/ru.js.map +1 -1
- package/llms.txt +1 -1
- package/manifest.json +4 -1
- package/nav-manifest.json +1 -1
- package/package.json +3 -3
- package/src/analytics/generated/events.json +1 -1
- package/src/default/SignInLink.tsx +54 -0
- package/src/default/StartChatButton.tsx +11 -4
- package/src/default/index.ts +2 -0
- package/src/headless/StartDirectChat.tsx +36 -1
- package/src/i18n/es.ts +3 -0
- package/src/i18n/keys.ts +9 -0
- package/src/i18n/ru.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @stapel/chat-react
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3e2e2a3: A blocked control now carries the door, not just the reason: `signIn`
|
|
8
|
+
|
|
9
|
+
`actionBlocked` ended the grey-rectangle incident by making every switched-off
|
|
10
|
+
control state its reason. It did not end the next one. "Sign in to save this",
|
|
11
|
+
"sign in to leave a review", "sign in to message the seller" are reasons whose
|
|
12
|
+
next action is a LINK, and no pair took one — so the storefront had to put its
|
|
13
|
+
own notice a screen away from each of the three controls it was about, and
|
|
14
|
+
named it a gap rather than shipping it (Wave D, G-3).
|
|
15
|
+
|
|
16
|
+
All three now take the same prop, core's `SignInCta`:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<ListingCard listing={row} signIn={{ href: `/login?next=${here}` }} />
|
|
20
|
+
<ReviewsPanel target={target} signIn={{ href: `/login?next=${here}` }} />
|
|
21
|
+
<StartChatButton sellerId={sellerId} signIn={{ onSignIn: () => openModal() }} />
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`{href}` **or** `{onSignIn}`, never both. Omit it and the reason renders alone,
|
|
25
|
+
with no trailing whitespace where the link is not — a host with no sign-in
|
|
26
|
+
route is a supported host.
|
|
27
|
+
|
|
28
|
+
Two more things each pair had to fix to make the door reachable:
|
|
29
|
+
|
|
30
|
+
- **listings**: the favourite's reason lived only in a `title` on a DISABLED
|
|
31
|
+
button, which receives no pointer events in any browser — core's own
|
|
32
|
+
`actionGate.ts` calls that "a reason nobody can read". It is now text beside
|
|
33
|
+
the heart (`listings-card-favorite-blocked`), with the link inside it. The
|
|
34
|
+
heart is still never hidden from a visitor.
|
|
35
|
+
- **chat**: `StartDirectChat` had no mandate gate at all, so a visitor could
|
|
36
|
+
press "message the seller" and collect a 401 — a refusal delivered at the one
|
|
37
|
+
moment it is useless. The axis is now the first arm of its `firstBlock`, read
|
|
38
|
+
through core's `MandateSource` seam. `member` may write; `guest`/`anonymous`
|
|
39
|
+
are told to sign in; `asking` says we are still asking. `unavailable` stays
|
|
40
|
+
AVAILABLE on purpose: that is what core answers outside a `<MandateProvider>`
|
|
41
|
+
too, and a host that never wired the axis must not lose its button — "we
|
|
42
|
+
could not ask" is not "you may not". This raises chat-react's `@stapel/core`
|
|
43
|
+
floor to `>=0.15.0`, where `useMandate`/`matchMandate` shipped.
|
|
44
|
+
|
|
45
|
+
The link's LABEL is each pair's own (`listings.card.sign_in`,
|
|
46
|
+
`reviews.form.sign_in`, `chat.start.sign_in`), in all three locales — core
|
|
47
|
+
floors `en` and `ru`, and these pairs also ship `es`.
|
|
48
|
+
|
|
3
49
|
## 0.2.0
|
|
4
50
|
|
|
5
51
|
### Minor Changes
|
package/MODULE.md
CHANGED
|
@@ -92,6 +92,38 @@ Unlike notifications-react, the `es` subpath here carries hand-written UI copy
|
|
|
92
92
|
too: a marketplace's buyer-to-seller chat is the surface where a half-translated
|
|
93
93
|
screen shows most.
|
|
94
94
|
|
|
95
|
+
## The gate before the click, and the door beside the reason
|
|
96
|
+
|
|
97
|
+
`POST /conversations/` is `IsAuthenticated`. Until 0.3.0 `StartDirectChat`
|
|
98
|
+
blocked only on the two seller-shaped reasons (no seller on the listing; the
|
|
99
|
+
listing is your own), so a visitor pressed the button and got a 401 — a
|
|
100
|
+
refusal delivered at the one moment it is useless.
|
|
101
|
+
|
|
102
|
+
The mandate axis is now the FIRST arm of that `firstBlock`, read through core's
|
|
103
|
+
`MandateSource` seam and never derived here (a storefront's derivation is "is
|
|
104
|
+
there a session?", a tenant app's is `@stapel/workspaces-react`'s). Four of the
|
|
105
|
+
five `matchMandate` arms behave as expected; the fifth is a deliberate
|
|
106
|
+
exception:
|
|
107
|
+
|
|
108
|
+
| arm | outcome |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `member` | available |
|
|
111
|
+
| `guest`, `anonymous` | blocked — `chat.start.blocked.sign_in` |
|
|
112
|
+
| `asking` | blocked — `chat.start.blocked.mandate_unknown` ("still asking", not "you may not") |
|
|
113
|
+
| `unavailable` | **available** |
|
|
114
|
+
|
|
115
|
+
`unavailable` is what core answers outside a `<MandateProvider>` too, so
|
|
116
|
+
refusing there would take the button away from every host that never wired the
|
|
117
|
+
axis — and "we could not ask" is not "you may not" (the storefront spec's own
|
|
118
|
+
§7.4 negative leg). If the guess is wrong, the module answers 401 exactly as it
|
|
119
|
+
did before.
|
|
120
|
+
|
|
121
|
+
`<StartChatButton signIn={…}>` renders the way out next to that sentence.
|
|
122
|
+
`SignInCta` is core's (`{href}` or `{onSignIn}`, never both) so the prop is
|
|
123
|
+
spelled identically in `@stapel/reviews-react` and `@stapel/listings-react`;
|
|
124
|
+
the LABEL is this pair's (`chat.start.sign_in`, in all three locales), because
|
|
125
|
+
core floors only `en` and `ru`.
|
|
126
|
+
|
|
95
127
|
## Extension seams (frontend-standard §7)
|
|
96
128
|
|
|
97
129
|
- The client is injected via `<ChatProvider>` / core's `StapelConfigProvider`
|
package/README.md
CHANGED
|
@@ -100,6 +100,27 @@ Get-or-create: a direct thread is keyed by the participant pair under a unique
|
|
|
100
100
|
constraint, so pressing twice — or writing to the same seller about a second
|
|
101
101
|
listing — lands in the same conversation.
|
|
102
102
|
|
|
103
|
+
### Who may press it, and where a visitor goes
|
|
104
|
+
|
|
105
|
+
`POST /conversations/` is `IsAuthenticated`, so the first gate is the mandate
|
|
106
|
+
axis, read through core's `MandateSource` seam. A visitor is told to sign in
|
|
107
|
+
BEFORE the click instead of collecting a 401 after it — and the sentence comes
|
|
108
|
+
with the door:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<StartChatButton sellerId={sellerId} signIn={{ href: `/login?next=${here}` }} />
|
|
112
|
+
<StartChatButton sellerId={sellerId} signIn={{ onSignIn: () => openModal() }} />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`signIn` is core's `SignInCta` — `{href}` **or** `{onSignIn}`, never both — and
|
|
116
|
+
it is the same prop `@stapel/reviews-react` and `@stapel/listings-react` take.
|
|
117
|
+
Omit it and the reason still renders, alone, which is what a host with no
|
|
118
|
+
sign-in route wants.
|
|
119
|
+
|
|
120
|
+
Outside a `<MandateProvider>` core answers `unresolved/unavailable`, and that
|
|
121
|
+
arm stays **available**: a host that never wired the axis keeps its button, and
|
|
122
|
+
"we could not ask" is not "you may not".
|
|
123
|
+
|
|
103
124
|
## The antd skin (opt-in)
|
|
104
125
|
|
|
105
126
|
```tsx
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The door beside a blocked control.
|
|
3
|
+
*
|
|
4
|
+
* `actionBlocked` ended the grey-rectangle incident by making every
|
|
5
|
+
* switched-off control state its reason. It did not end the next problem:
|
|
6
|
+
* "sign in to message the seller" is a reason whose next action is a LINK, and
|
|
7
|
+
* this pair rendered the sentence and stopped there — leaving the visitor to
|
|
8
|
+
* find the header themselves (storefront Wave D, G-3).
|
|
9
|
+
*
|
|
10
|
+
* WHERE that link goes is the container's business, never the pair's: the
|
|
11
|
+
* storefront's is `/login?next=<current>`, a tenant app's may be a modal. So
|
|
12
|
+
* the shape is core's `SignInCta` — `{href}` or `{onSignIn}`, never both — and
|
|
13
|
+
* the copy is this pair's, because core floors `en` and `ru` while this pair
|
|
14
|
+
* also ships `es`.
|
|
15
|
+
*
|
|
16
|
+
* A host that routes internally passes `onSignIn`; the `href` arm renders a
|
|
17
|
+
* plain anchor on purpose, because arriving at a sign-in page is one of the
|
|
18
|
+
* few navigations a full load costs nothing.
|
|
19
|
+
*/
|
|
20
|
+
import type { ReactElement } from "react";
|
|
21
|
+
import type { SignInCta } from "@stapel/core";
|
|
22
|
+
export interface SignInLinkProps {
|
|
23
|
+
/** Absent: no link — a host with no sign-in route shows the reason alone. */
|
|
24
|
+
readonly cta: SignInCta | undefined;
|
|
25
|
+
readonly testId: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function SignInLink(props: SignInLinkProps): ReactElement | null;
|
|
28
|
+
//# sourceMappingURL=SignInLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignInLink.d.ts","sourceRoot":"","sources":["../../src/default/SignInLink.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAsBtE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Typography } from "antd";
|
|
3
|
+
import { useT } from "@stapel/core";
|
|
4
|
+
import { CHAT_I18N_KEYS } from "../i18n/keys.js";
|
|
5
|
+
export function SignInLink(props) {
|
|
6
|
+
const t = useT();
|
|
7
|
+
const { cta } = props;
|
|
8
|
+
if (cta === undefined)
|
|
9
|
+
return null;
|
|
10
|
+
// The separating space belongs HERE, not at the call site: a reason with no
|
|
11
|
+
// door must render as exactly its own sentence, and a `{" "}` left behind by
|
|
12
|
+
// an absent link is a trailing space in every caller's assertion.
|
|
13
|
+
return (_jsxs(_Fragment, { children: [" ", _jsx(Typography.Link, { "data-testid": props.testId, "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", ...(cta.href !== undefined
|
|
14
|
+
? { href: cta.href }
|
|
15
|
+
: { onClick: cta.onSignIn }), children: t(CHAT_I18N_KEYS.startSignIn) })] }));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=SignInLink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignInLink.js","sourceRoot":"","sources":["../../src/default/SignInLink.tsx"],"names":[],"mappings":";AAoBA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAQjD,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IACtB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,4EAA4E;IAC5E,6EAA6E;IAC7E,kEAAkE;IAClE,OAAO,CACL,8BACG,GAAG,EACJ,KAAC,UAAU,CAAC,IAAI,mBACD,KAAK,CAAC,MAAM,oBACV,MAAM,2BACC,8DAAyD,KAC3E,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS;oBACzB,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE;oBACpB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,YAE7B,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,GACd,IACjB,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
* `<StartChatButton/>` — "message the seller" on a listing card, skinned.
|
|
3
3
|
*
|
|
4
4
|
* The control is either pressable or switched off WITH a sentence beside it
|
|
5
|
-
* (no seller on the listing; the listing is your own
|
|
6
|
-
* grey rectangle with no explanation
|
|
5
|
+
* (no seller on the listing; the listing is your own; you are not signed in).
|
|
6
|
+
* It never renders as a grey rectangle with no explanation — and when the host
|
|
7
|
+
* supplies `signIn`, the sentence comes with the door: a stated reason whose
|
|
8
|
+
* next action is a link the visitor cannot find on their own is only half an
|
|
9
|
+
* answer (storefront Wave D, G-3).
|
|
7
10
|
*/
|
|
8
11
|
import type { ReactElement } from "react";
|
|
12
|
+
import type { SignInCtaProp } from "@stapel/core";
|
|
9
13
|
import type { Conversation } from "../api/types.js";
|
|
10
|
-
export interface StartChatButtonProps {
|
|
14
|
+
export interface StartChatButtonProps extends SignInCtaProp {
|
|
11
15
|
sellerId: string | null | undefined;
|
|
12
16
|
viewerId?: string | null;
|
|
13
17
|
onOpened?: (conversation: Conversation) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartChatButton.d.ts","sourceRoot":"","sources":["../../src/default/StartChatButton.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"StartChatButton.d.ts","sourceRoot":"","sources":["../../src/default/StartChatButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAiC,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAMpD,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAwCD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CAmBzE"}
|
|
@@ -4,15 +4,16 @@ import { useActionGate, useErrorDisplay, useT } from "@stapel/core";
|
|
|
4
4
|
import { StartDirectChat } from "../headless/StartDirectChat.js";
|
|
5
5
|
import { CHAT_I18N_KEYS } from "../i18n/keys.js";
|
|
6
6
|
import { ErrorAlert } from "./ErrorAlert.js";
|
|
7
|
+
import { SignInLink } from "./SignInLink.js";
|
|
7
8
|
function StartChatBody(props) {
|
|
8
9
|
const t = useT();
|
|
9
10
|
const gate = useActionGate(props.availability);
|
|
10
11
|
const errorDisplay = useErrorDisplay(CHAT_I18N_KEYS.unknownError);
|
|
11
12
|
return (_jsxs(Space, { direction: "vertical", style: { width: props.block ? "100%" : undefined }, children: [_jsx(Button, { type: "primary", block: props.block ?? false, disabled: gate.disabled, loading: props.isStarting, onClick: props.start, "data-testid": "chat-start-button", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", children: props.isStarting
|
|
12
13
|
? t(CHAT_I18N_KEYS.startStarting)
|
|
13
|
-
: t(CHAT_I18N_KEYS.startButton) }), gate.reason ? (
|
|
14
|
+
: t(CHAT_I18N_KEYS.startButton) }), gate.reason ? (_jsxs(Typography.Text, { type: "secondary", "data-testid": "chat-start-blocked", children: [gate.reason, _jsx(SignInLink, { cta: props.signIn, testId: "chat-start-sign-in" })] })) : null, _jsx(ErrorAlert, { error: errorDisplay(props.error) })] }));
|
|
14
15
|
}
|
|
15
16
|
export function StartChatButton(props) {
|
|
16
|
-
return (_jsx(StartDirectChat, { sellerId: props.sellerId, ...(props.viewerId !== undefined ? { viewerId: props.viewerId } : {}), ...(props.onOpened !== undefined ? { onOpened: props.onOpened } : {}), children: ({ availability, isStarting, error, start }) => (_jsx(StartChatBody, { availability: availability, isStarting: isStarting, error: error, start: start, block: props.block })) }));
|
|
17
|
+
return (_jsx(StartDirectChat, { sellerId: props.sellerId, ...(props.viewerId !== undefined ? { viewerId: props.viewerId } : {}), ...(props.onOpened !== undefined ? { onOpened: props.onOpened } : {}), children: ({ availability, isStarting, error, start }) => (_jsx(StartChatBody, { availability: availability, isStarting: isStarting, error: error, start: start, block: props.block, signIn: props.signIn })) }));
|
|
17
18
|
}
|
|
18
19
|
//# sourceMappingURL=StartChatButton.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartChatButton.js","sourceRoot":"","sources":["../../src/default/StartChatButton.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"StartChatButton.js","sourceRoot":"","sources":["../../src/default/StartChatButton.tsx"],"names":[],"mappings":";AAWA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAS7C,SAAS,aAAa,CAAC,KAOtB;IACC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,eAAe,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAClE,OAAO,CACL,MAAC,KAAK,IAAC,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,aAC5E,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,KAAK,EAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ,EACvB,OAAO,EAAE,KAAK,CAAC,UAAU,EACzB,OAAO,EAAE,KAAK,CAAC,KAAK,iBACR,mBAAmB,oBAChB,MAAM,2BACC,8DAAyD,YAE9E,KAAK,CAAC,UAAU;oBACf,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC;oBACjC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,GAC1B,EACR,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CACb,MAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAC,WAAW,iBAAa,oBAAoB,aAC/D,IAAI,CAAC,MAAM,EACZ,KAAC,UAAU,IAAC,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAC,oBAAoB,GAAG,IAC7C,CACnB,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,UAAU,IAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,GAAI,IAC1C,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,OAAO,CACL,KAAC,eAAe,IACd,QAAQ,EAAE,KAAK,CAAC,QAAQ,KACpB,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAClE,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAErE,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAC/C,KAAC,aAAa,IACZ,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,GACpB,CACH,GACe,CACnB,CAAC;AACJ,CAAC"}
|
package/dist/default/index.d.ts
CHANGED
|
@@ -18,4 +18,6 @@ export { ConversationThreadPanel } from "./ConversationThreadPanel.js";
|
|
|
18
18
|
export type { ConversationThreadPanelProps } from "./ConversationThreadPanel.js";
|
|
19
19
|
export { StartChatButton } from "./StartChatButton.js";
|
|
20
20
|
export type { StartChatButtonProps } from "./StartChatButton.js";
|
|
21
|
+
export { SignInLink } from "./SignInLink.js";
|
|
22
|
+
export type { SignInLinkProps } from "./SignInLink.js";
|
|
21
23
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/default/index.js
CHANGED
|
@@ -15,4 +15,5 @@
|
|
|
15
15
|
export { ConversationListPanel } from "./ConversationListPanel.js";
|
|
16
16
|
export { ConversationThreadPanel } from "./ConversationThreadPanel.js";
|
|
17
17
|
export { StartChatButton } from "./StartChatButton.js";
|
|
18
|
+
export { SignInLink } from "./SignInLink.js";
|
|
18
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -21,6 +21,11 @@ export interface StartDirectChatBag {
|
|
|
21
21
|
* seller all land in the same conversation rather than fanning out into
|
|
22
22
|
* near-duplicate threads.
|
|
23
23
|
*
|
|
24
|
+
* WHO MAY PRESS IT. The write is `IsAuthenticated`, so the first gate is the
|
|
25
|
+
* mandate axis: a visitor is told to sign in BEFORE the click rather than
|
|
26
|
+
* being handed a 401 after it. `<StartChatButton signIn={…}>` puts the door
|
|
27
|
+
* next to that sentence.
|
|
28
|
+
*
|
|
24
29
|
* WHAT THIS BUTTON CANNOT DO, AND WHY IT SAYS SO HERE. It cannot open a
|
|
25
30
|
* thread ABOUT a listing. The thread is keyed by the pair of people, and
|
|
26
31
|
* `CreateConversationRequest.scope_key` — which looks like the place to put a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartDirectChat.d.ts","sourceRoot":"","sources":["../../src/headless/StartDirectChat.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"StartDirectChat.d.ts","sourceRoot":"","sources":["../../src/headless/StartDirectChat.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAIpD,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+BAA+B;IAC/B,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAClD,GAAG,SAAS,CAsDZ"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
|
-
import { actionAvailable, actionBlocked, firstBlock } from "@stapel/core";
|
|
2
|
+
import { actionAvailable, actionBlocked, firstBlock, matchMandate, useMandate, } from "@stapel/core";
|
|
3
3
|
import { useStartDirectChat } from "../model/mutations.js";
|
|
4
4
|
import { CHAT_I18N_KEYS } from "../i18n/keys.js";
|
|
5
5
|
/**
|
|
@@ -12,6 +12,11 @@ import { CHAT_I18N_KEYS } from "../i18n/keys.js";
|
|
|
12
12
|
* seller all land in the same conversation rather than fanning out into
|
|
13
13
|
* near-duplicate threads.
|
|
14
14
|
*
|
|
15
|
+
* WHO MAY PRESS IT. The write is `IsAuthenticated`, so the first gate is the
|
|
16
|
+
* mandate axis: a visitor is told to sign in BEFORE the click rather than
|
|
17
|
+
* being handed a 401 after it. `<StartChatButton signIn={…}>` puts the door
|
|
18
|
+
* next to that sentence.
|
|
19
|
+
*
|
|
15
20
|
* WHAT THIS BUTTON CANNOT DO, AND WHY IT SAYS SO HERE. It cannot open a
|
|
16
21
|
* thread ABOUT a listing. The thread is keyed by the pair of people, and
|
|
17
22
|
* `CreateConversationRequest.scope_key` — which looks like the place to put a
|
|
@@ -22,7 +27,29 @@ import { CHAT_I18N_KEYS } from "../i18n/keys.js";
|
|
|
22
27
|
export function StartDirectChat(props) {
|
|
23
28
|
const start = useStartDirectChat();
|
|
24
29
|
const { sellerId, viewerId, onOpened } = props;
|
|
25
|
-
|
|
30
|
+
/**
|
|
31
|
+
* May this person write at all? `POST /conversations/` is
|
|
32
|
+
* `IsAuthenticated`, so a visitor pressing this bought a 401 — a refusal
|
|
33
|
+
* delivered after the click, which is the one moment it is useless. The
|
|
34
|
+
* axis is read through core's `MandateSource` seam, never derived here: a
|
|
35
|
+
* storefront's derivation is "is there a session?", a tenant app's is
|
|
36
|
+
* `@stapel/workspaces-react`'s.
|
|
37
|
+
*
|
|
38
|
+
* `unavailable` stays AVAILABLE, unlike the other four arms. Outside a
|
|
39
|
+
* `<MandateProvider>` core answers `unresolved/unavailable`, and a host that
|
|
40
|
+
* never wired the axis must not lose its button — "we could not ask" is not
|
|
41
|
+
* "you may not" (the storefront spec's own §7.4 negative leg), and if the
|
|
42
|
+
* guess is wrong the module answers 401 exactly as it did before.
|
|
43
|
+
*/
|
|
44
|
+
const mandate = useMandate();
|
|
45
|
+
const mandateGate = matchMandate(mandate, {
|
|
46
|
+
member: () => actionAvailable(),
|
|
47
|
+
guest: () => actionBlocked(CHAT_I18N_KEYS.startBlockedSignIn),
|
|
48
|
+
anonymous: () => actionBlocked(CHAT_I18N_KEYS.startBlockedSignIn),
|
|
49
|
+
asking: () => actionBlocked(CHAT_I18N_KEYS.startBlockedMandateUnknown),
|
|
50
|
+
unavailable: () => actionAvailable(),
|
|
51
|
+
});
|
|
52
|
+
const availability = firstBlock(mandateGate, sellerId === null || sellerId === undefined || sellerId.length === 0
|
|
26
53
|
? actionBlocked(CHAT_I18N_KEYS.startBlockedUnknownSeller)
|
|
27
54
|
: actionAvailable(), viewerId != null && viewerId === sellerId
|
|
28
55
|
? actionBlocked(CHAT_I18N_KEYS.startBlockedSelf)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartDirectChat.js","sourceRoot":"","sources":["../../src/headless/StartDirectChat.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,
|
|
1
|
+
{"version":3,"file":"StartDirectChat.js","sourceRoot":"","sources":["../../src/headless/StartDirectChat.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EACL,eAAe,EACf,aAAa,EACb,UAAU,EACV,YAAY,EACZ,UAAU,GACX,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAajD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,eAAe,CAAC,KAQ/B;IACC,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAE/C;;;;;;;;;;;;;OAaG;IACH,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,YAAY,CAAqB,OAAO,EAAE;QAC5D,MAAM,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE;QAC/B,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,kBAAkB,CAAC;QAC7D,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,kBAAkB,CAAC;QACjE,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,0BAA0B,CAAC;QACtE,WAAW,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE;KACrC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAuB,UAAU,CACjD,WAAW,EACX,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAClE,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,yBAAyB,CAAC;QACzD,CAAC,CAAC,eAAe,EAAE,EACrB,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK,QAAQ;QACvC,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,gBAAgB,CAAC;QAChD,CAAC,CAAC,eAAe,EAAE,CACtB,CAAC;IAEF,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAS,EAAE;QACrC,IAAI,CAAC,YAAY,CAAC,SAAS,IAAI,SAAS;YAAE,OAAO;QACjD,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO;QACxD,MAAM,CACJ,EAAE,MAAM,EAAE,QAAQ,EAAE,EACpB,EAAE,SAAS,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,CAC1D,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEpE,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,YAAY;QACZ,UAAU,EAAE,SAAS;QACrB,YAAY,EAAE,IAAI;QAClB,KAAK;QACL,KAAK,EAAE,OAAO;KACf,CAAC,CAAC;AACL,CAAC"}
|
package/dist/i18n/es.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"es.d.ts","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"es.d.ts","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAoE9B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAG3D"}
|
package/dist/i18n/es.js
CHANGED
|
@@ -57,6 +57,9 @@ export const chatI18nBundleEs = {
|
|
|
57
57
|
"chat.start.starting": "Abriendo…",
|
|
58
58
|
"chat.start.blocked.self": "Este anuncio es tuyo.",
|
|
59
59
|
"chat.start.blocked.unknown_seller": "Este anuncio no tiene vendedor a quien escribir.",
|
|
60
|
+
"chat.start.blocked.sign_in": "Inicia sesión para escribir al vendedor.",
|
|
61
|
+
"chat.start.blocked.mandate_unknown": "Comprobando tu sesión…",
|
|
62
|
+
"chat.start.sign_in": "Iniciar sesión",
|
|
60
63
|
"chat.transport.live": "En directo",
|
|
61
64
|
"chat.transport.polling": "Actualizando cada pocos segundos",
|
|
62
65
|
"chat.transport.idle": "En pausa",
|
package/dist/i18n/es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"es.js","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,oEAAoE;IACpE,GAAG,iBAAiB;IAEpB,6EAA6E;IAC7E,qCAAqC,EACnC,gEAAgE;IAClE,8BAA8B,EAAE,gDAAgD;IAChF,8BAA8B,EAC5B,4DAA4D;IAC9D,+BAA+B,EAC7B,+DAA+D;IACjE,6BAA6B,EAAE,kCAAkC;IACjE,8BAA8B,EAC5B,8DAA8D;IAChE,8BAA8B,EAC5B,kEAAkE;IACpE,4BAA4B,EAC1B,wDAAwD;IAC1D,6BAA6B,EAC3B,wDAAwD;IAC1D,gCAAgC,EAAE,oCAAoC;IACtE,uCAAuC,EAAE,4BAA4B;IACrE,iCAAiC,EAAE,+CAA+C;IAElF,mEAAmE;IACnE,oBAAoB,EAAE,yCAAyC;IAE/D,iBAAiB,EAAE,UAAU;IAC7B,iBAAiB,EAAE,gCAAgC;IACnD,mBAAmB,EAAE,0BAA0B;IAC/C,qBAAqB,EAAE,YAAY;IACnC,eAAe,EAAE,eAAe;IAChC,iBAAiB,EAAE,YAAY;IAC/B,kBAAkB,EAAE,kBAAkB;IACtC,gBAAgB,EAAE,OAAO;IAEzB,kBAAkB,EAAE,iBAAiB;IACrC,iBAAiB,EAAE,OAAO;IAC1B,mBAAmB,EAAE,SAAS;IAE9B,qBAAqB,EAAE,oBAAoB;IAC3C,mBAAmB,EAAE,kCAAkC;IACvD,mBAAmB,EAAE,YAAY;IACjC,wBAAwB,EAAE,yBAAyB;IACnD,uBAAuB,EAAE,0CAA0C;IACnE,oBAAoB,EAAE,SAAS;IAE/B,2BAA2B,EAAE,qBAAqB;IAClD,oBAAoB,EAAE,QAAQ;IAC9B,uBAAuB,EAAE,WAAW;IACpC,6BAA6B,EAAE,uBAAuB;IACtD,gCAAgC,EAC9B,qDAAqD;IAEvD,mBAAmB,EAAE,sBAAsB;IAC3C,qBAAqB,EAAE,WAAW;IAClC,yBAAyB,EAAE,uBAAuB;IAClD,mCAAmC,EAAE,kDAAkD;
|
|
1
|
+
{"version":3,"file":"es.js","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,oEAAoE;IACpE,GAAG,iBAAiB;IAEpB,6EAA6E;IAC7E,qCAAqC,EACnC,gEAAgE;IAClE,8BAA8B,EAAE,gDAAgD;IAChF,8BAA8B,EAC5B,4DAA4D;IAC9D,+BAA+B,EAC7B,+DAA+D;IACjE,6BAA6B,EAAE,kCAAkC;IACjE,8BAA8B,EAC5B,8DAA8D;IAChE,8BAA8B,EAC5B,kEAAkE;IACpE,4BAA4B,EAC1B,wDAAwD;IAC1D,6BAA6B,EAC3B,wDAAwD;IAC1D,gCAAgC,EAAE,oCAAoC;IACtE,uCAAuC,EAAE,4BAA4B;IACrE,iCAAiC,EAAE,+CAA+C;IAElF,mEAAmE;IACnE,oBAAoB,EAAE,yCAAyC;IAE/D,iBAAiB,EAAE,UAAU;IAC7B,iBAAiB,EAAE,gCAAgC;IACnD,mBAAmB,EAAE,0BAA0B;IAC/C,qBAAqB,EAAE,YAAY;IACnC,eAAe,EAAE,eAAe;IAChC,iBAAiB,EAAE,YAAY;IAC/B,kBAAkB,EAAE,kBAAkB;IACtC,gBAAgB,EAAE,OAAO;IAEzB,kBAAkB,EAAE,iBAAiB;IACrC,iBAAiB,EAAE,OAAO;IAC1B,mBAAmB,EAAE,SAAS;IAE9B,qBAAqB,EAAE,oBAAoB;IAC3C,mBAAmB,EAAE,kCAAkC;IACvD,mBAAmB,EAAE,YAAY;IACjC,wBAAwB,EAAE,yBAAyB;IACnD,uBAAuB,EAAE,0CAA0C;IACnE,oBAAoB,EAAE,SAAS;IAE/B,2BAA2B,EAAE,qBAAqB;IAClD,oBAAoB,EAAE,QAAQ;IAC9B,uBAAuB,EAAE,WAAW;IACpC,6BAA6B,EAAE,uBAAuB;IACtD,gCAAgC,EAC9B,qDAAqD;IAEvD,mBAAmB,EAAE,sBAAsB;IAC3C,qBAAqB,EAAE,WAAW;IAClC,yBAAyB,EAAE,uBAAuB;IAClD,mCAAmC,EAAE,kDAAkD;IACvF,4BAA4B,EAAE,0CAA0C;IACxE,oCAAoC,EAAE,wBAAwB;IAC9D,oBAAoB,EAAE,gBAAgB;IAEtC,qBAAqB,EAAE,YAAY;IACnC,wBAAwB,EAAE,kCAAkC;IAC5D,qBAAqB,EAAE,UAAU;IAEjC,wBAAwB,EAAE,UAAU;CACrC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC9C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAChD,CAAC"}
|
package/dist/i18n/keys.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare const CHAT_I18N_KEYS: {
|
|
|
35
35
|
readonly startStarting: "chat.start.starting";
|
|
36
36
|
readonly startBlockedSelf: "chat.start.blocked.self";
|
|
37
37
|
readonly startBlockedUnknownSeller: "chat.start.blocked.unknown_seller";
|
|
38
|
+
/** A visitor: the POST is `IsAuthenticated`, so say so before the click. */
|
|
39
|
+
readonly startBlockedSignIn: "chat.start.blocked.sign_in";
|
|
40
|
+
/** We have not finished asking who this is. Not "you may not". */
|
|
41
|
+
readonly startBlockedMandateUnknown: "chat.start.blocked.mandate_unknown";
|
|
42
|
+
/** The door beside a blocked "message the seller": the container says WHERE. */
|
|
43
|
+
readonly startSignIn: "chat.start.sign_in";
|
|
38
44
|
readonly transportLive: "chat.transport.live";
|
|
39
45
|
readonly transportPolling: "chat.transport.polling";
|
|
40
46
|
readonly transportIdle: "chat.transport.idle";
|
package/dist/i18n/keys.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAuCzB,4EAA4E;;IAE5E,kEAAkE;;IAElE,gFAAgF;;;;;;;;;;;;;;;;;;CA+BxE,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAE/E;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,cA+C9B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAExE"}
|
package/dist/i18n/keys.js
CHANGED
|
@@ -41,6 +41,12 @@ export const CHAT_I18N_KEYS = {
|
|
|
41
41
|
startStarting: "chat.start.starting",
|
|
42
42
|
startBlockedSelf: "chat.start.blocked.self",
|
|
43
43
|
startBlockedUnknownSeller: "chat.start.blocked.unknown_seller",
|
|
44
|
+
/** A visitor: the POST is `IsAuthenticated`, so say so before the click. */
|
|
45
|
+
startBlockedSignIn: "chat.start.blocked.sign_in",
|
|
46
|
+
/** We have not finished asking who this is. Not "you may not". */
|
|
47
|
+
startBlockedMandateUnknown: "chat.start.blocked.mandate_unknown",
|
|
48
|
+
/** The door beside a blocked "message the seller": the container says WHERE. */
|
|
49
|
+
startSignIn: "chat.start.sign_in",
|
|
44
50
|
// Transport (the seam is invisible to the UI's BEHAVIOUR, but a person may
|
|
45
51
|
// still be told whether the thread is live or on a timer)
|
|
46
52
|
transportLive: "chat.transport.live",
|
|
@@ -106,6 +112,9 @@ export const chatI18nBundleEn = {
|
|
|
106
112
|
"chat.start.starting": "Opening…",
|
|
107
113
|
"chat.start.blocked.self": "This is your own listing.",
|
|
108
114
|
"chat.start.blocked.unknown_seller": "This listing has no seller to write to.",
|
|
115
|
+
"chat.start.blocked.sign_in": "Sign in to message the seller.",
|
|
116
|
+
"chat.start.blocked.mandate_unknown": "Checking your session…",
|
|
117
|
+
"chat.start.sign_in": "Sign in",
|
|
109
118
|
"chat.transport.live": "Live",
|
|
110
119
|
"chat.transport.polling": "Refreshing every few seconds",
|
|
111
120
|
"chat.transport.idle": "Paused",
|
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,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY,EAAE,oBAAoB;IAElC,oBAAoB;IACpB,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,iBAAiB;IAC5B,WAAW,EAAE,mBAAmB;IAChC,YAAY,EAAE,qBAAqB;IACnC,OAAO,EAAE,eAAe;IACxB,SAAS,EAAE,iBAAiB;IAC5B,UAAU,EAAE,kBAAkB;IAC9B,QAAQ,EAAE,gBAAgB;IAE1B,uEAAuE;IACvE,+CAA+C;IAC/C,UAAU,EAAE,kBAAkB;IAC9B,SAAS,EAAE,iBAAiB;IAC5B,WAAW,EAAE,mBAAmB;IAEhC,SAAS;IACT,aAAa,EAAE,qBAAqB;IACpC,WAAW,EAAE,mBAAmB;IAChC,WAAW,EAAE,mBAAmB;IAChC,eAAe,EAAE,wBAAwB;IACzC,eAAe,EAAE,uBAAuB;IACxC,YAAY,EAAE,oBAAoB;IAElC,WAAW;IACX,mBAAmB,EAAE,2BAA2B;IAChD,YAAY,EAAE,oBAAoB;IAClC,eAAe,EAAE,uBAAuB;IACxC,oBAAoB,EAAE,6BAA6B;IACnD,sBAAsB,EAAE,gCAAgC;IAExD,uBAAuB;IACvB,WAAW,EAAE,mBAAmB;IAChC,aAAa,EAAE,qBAAqB;IACpC,gBAAgB,EAAE,yBAAyB;IAC3C,yBAAyB,EAAE,mCAAmC;
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"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"}
|
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;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,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;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAkE9B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAG3D"}
|
package/dist/i18n/ru.js
CHANGED
|
@@ -61,6 +61,9 @@ export const chatI18nBundleRu = {
|
|
|
61
61
|
"chat.start.starting": "Открываем…",
|
|
62
62
|
"chat.start.blocked.self": "Это ваше собственное объявление.",
|
|
63
63
|
"chat.start.blocked.unknown_seller": "У этого объявления не указан продавец.",
|
|
64
|
+
"chat.start.blocked.sign_in": "Войдите, чтобы написать продавцу.",
|
|
65
|
+
"chat.start.blocked.mandate_unknown": "Проверяем вашу сессию…",
|
|
66
|
+
"chat.start.sign_in": "Войти",
|
|
64
67
|
"chat.transport.live": "На связи",
|
|
65
68
|
"chat.transport.polling": "Обновляется каждые несколько секунд",
|
|
66
69
|
"chat.transport.idle": "Приостановлено",
|
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,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,oEAAoE;IACpE,GAAG,iBAAiB;IAEpB,6EAA6E;IAC7E,qCAAqC,EAAE,qCAAqC;IAC5E,8BAA8B,EAAE,uCAAuC;IACvE,8BAA8B,EAC5B,yDAAyD;IAC3D,+BAA+B,EAC7B,sDAAsD;IACxD,6BAA6B,EAAE,yBAAyB;IACxD,8BAA8B,EAC5B,yDAAyD;IAC3D,8BAA8B,EAAE,4CAA4C;IAC5E,4BAA4B,EAC1B,wDAAwD;IAC1D,6BAA6B,EAC3B,kDAAkD;IACpD,gCAAgC,EAAE,8BAA8B;IAChE,uCAAuC,EAAE,kBAAkB;IAC3D,iCAAiC,EAAE,kCAAkC;IAErE,mEAAmE;IACnE,oBAAoB,EAAE,0CAA0C;IAEhE,iBAAiB,EAAE,WAAW;IAC9B,iBAAiB,EAAE,oBAAoB;IACvC,mBAAmB,EAAE,oBAAoB;IACzC,qBAAqB,EAAE,cAAc;IACrC,eAAe,EAAE,kBAAkB;IACnC,iBAAiB,EAAE,WAAW;IAC9B,kBAAkB,EAAE,wBAAwB;IAC5C,gBAAgB,EAAE,SAAS;IAE3B,kBAAkB,EAAE,kBAAkB;IACtC,iBAAiB,EAAE,kBAAkB;IACrC,mBAAmB,EAAE,WAAW;IAEhC,qBAAqB,EAAE,qBAAqB;IAC5C,mBAAmB,EAAE,sCAAsC;IAC3D,mBAAmB,EAAE,WAAW;IAChC,wBAAwB,EAAE,uBAAuB;IACjD,uBAAuB,EAAE,uBAAuB;IAChD,oBAAoB,EAAE,qBAAqB;IAE3C,2BAA2B,EAAE,qBAAqB;IAClD,oBAAoB,EAAE,WAAW;IACjC,uBAAuB,EAAE,WAAW;IACpC,6BAA6B,EAAE,yBAAyB;IACxD,gCAAgC,EAC9B,iDAAiD;IAEnD,mBAAmB,EAAE,mBAAmB;IACxC,qBAAqB,EAAE,YAAY;IACnC,yBAAyB,EAAE,kCAAkC;IAC7D,mCAAmC,EAAE,wCAAwC;
|
|
1
|
+
{"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,oEAAoE;IACpE,GAAG,iBAAiB;IAEpB,6EAA6E;IAC7E,qCAAqC,EAAE,qCAAqC;IAC5E,8BAA8B,EAAE,uCAAuC;IACvE,8BAA8B,EAC5B,yDAAyD;IAC3D,+BAA+B,EAC7B,sDAAsD;IACxD,6BAA6B,EAAE,yBAAyB;IACxD,8BAA8B,EAC5B,yDAAyD;IAC3D,8BAA8B,EAAE,4CAA4C;IAC5E,4BAA4B,EAC1B,wDAAwD;IAC1D,6BAA6B,EAC3B,kDAAkD;IACpD,gCAAgC,EAAE,8BAA8B;IAChE,uCAAuC,EAAE,kBAAkB;IAC3D,iCAAiC,EAAE,kCAAkC;IAErE,mEAAmE;IACnE,oBAAoB,EAAE,0CAA0C;IAEhE,iBAAiB,EAAE,WAAW;IAC9B,iBAAiB,EAAE,oBAAoB;IACvC,mBAAmB,EAAE,oBAAoB;IACzC,qBAAqB,EAAE,cAAc;IACrC,eAAe,EAAE,kBAAkB;IACnC,iBAAiB,EAAE,WAAW;IAC9B,kBAAkB,EAAE,wBAAwB;IAC5C,gBAAgB,EAAE,SAAS;IAE3B,kBAAkB,EAAE,kBAAkB;IACtC,iBAAiB,EAAE,kBAAkB;IACrC,mBAAmB,EAAE,WAAW;IAEhC,qBAAqB,EAAE,qBAAqB;IAC5C,mBAAmB,EAAE,sCAAsC;IAC3D,mBAAmB,EAAE,WAAW;IAChC,wBAAwB,EAAE,uBAAuB;IACjD,uBAAuB,EAAE,uBAAuB;IAChD,oBAAoB,EAAE,qBAAqB;IAE3C,2BAA2B,EAAE,qBAAqB;IAClD,oBAAoB,EAAE,WAAW;IACjC,uBAAuB,EAAE,WAAW;IACpC,6BAA6B,EAAE,yBAAyB;IACxD,gCAAgC,EAC9B,iDAAiD;IAEnD,mBAAmB,EAAE,mBAAmB;IACxC,qBAAqB,EAAE,YAAY;IACnC,yBAAyB,EAAE,kCAAkC;IAC7D,mCAAmC,EAAE,wCAAwC;IAC7E,4BAA4B,EAAE,mCAAmC;IACjE,oCAAoC,EAAE,wBAAwB;IAC9D,oBAAoB,EAAE,OAAO;IAE7B,qBAAqB,EAAE,UAAU;IACjC,wBAAwB,EAAE,qCAAqC;IAC/D,qBAAqB,EAAE,gBAAgB;IAEvC,wBAAwB,EAAE,WAAW;CACtC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC9C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAChD,CAAC"}
|
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/chat-react",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"backend": {
|
|
6
6
|
"module": "stapel-chat",
|
|
7
7
|
"contract": ">=0.2 <0.3"
|
|
@@ -528,9 +528,12 @@
|
|
|
528
528
|
"chat.list.title",
|
|
529
529
|
"chat.list.unread",
|
|
530
530
|
"chat.nav.conversations",
|
|
531
|
+
"chat.start.blocked.mandate_unknown",
|
|
531
532
|
"chat.start.blocked.self",
|
|
533
|
+
"chat.start.blocked.sign_in",
|
|
532
534
|
"chat.start.blocked.unknown_seller",
|
|
533
535
|
"chat.start.button",
|
|
536
|
+
"chat.start.sign_in",
|
|
534
537
|
"chat.start.starting",
|
|
535
538
|
"chat.thread.beginning",
|
|
536
539
|
"chat.thread.empty",
|
package/nav-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stapel/chat-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Headless React pair for stapel-chat: typed API client, TanStack Query hooks, a seq-ordered thread store, and ONE transport seam behind which the module's own resumable WebSocket protocol (hello/welcome/replay/replay_done/message/error) and seq-anchored polling are interchangeable — the UI never knows which is live. Conversation list with unread badges, thread replay + live tail, REST send, direct-thread idempotency for \"message the seller\", monotonic read markers. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
}
|
|
71
71
|
],
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@stapel/core": ">=0.
|
|
73
|
+
"@stapel/core": ">=0.15.0 <1.0.0",
|
|
74
74
|
"@tanstack/react-query": "^5.0.0",
|
|
75
75
|
"antd": ">=5.20.0 <7",
|
|
76
76
|
"react": ">=19"
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"size-limit": "^11.2.0",
|
|
95
95
|
"typescript": "^5.8.3",
|
|
96
96
|
"vitest": "^3.2.4",
|
|
97
|
-
"@stapel/core": "^0.
|
|
97
|
+
"@stapel/core": "^0.16.0",
|
|
98
98
|
"@stapel/showcase": "^0.2.0",
|
|
99
99
|
"@stapel/tokens": "^0.5.0"
|
|
100
100
|
},
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The door beside a blocked control.
|
|
3
|
+
*
|
|
4
|
+
* `actionBlocked` ended the grey-rectangle incident by making every
|
|
5
|
+
* switched-off control state its reason. It did not end the next problem:
|
|
6
|
+
* "sign in to message the seller" is a reason whose next action is a LINK, and
|
|
7
|
+
* this pair rendered the sentence and stopped there — leaving the visitor to
|
|
8
|
+
* find the header themselves (storefront Wave D, G-3).
|
|
9
|
+
*
|
|
10
|
+
* WHERE that link goes is the container's business, never the pair's: the
|
|
11
|
+
* storefront's is `/login?next=<current>`, a tenant app's may be a modal. So
|
|
12
|
+
* the shape is core's `SignInCta` — `{href}` or `{onSignIn}`, never both — and
|
|
13
|
+
* the copy is this pair's, because core floors `en` and `ru` while this pair
|
|
14
|
+
* also ships `es`.
|
|
15
|
+
*
|
|
16
|
+
* A host that routes internally passes `onSignIn`; the `href` arm renders a
|
|
17
|
+
* plain anchor on purpose, because arriving at a sign-in page is one of the
|
|
18
|
+
* few navigations a full load costs nothing.
|
|
19
|
+
*/
|
|
20
|
+
import type { ReactElement } from "react";
|
|
21
|
+
import { Typography } from "antd";
|
|
22
|
+
import { useT } from "@stapel/core";
|
|
23
|
+
import type { SignInCta } from "@stapel/core";
|
|
24
|
+
import { CHAT_I18N_KEYS } from "../i18n/keys.js";
|
|
25
|
+
|
|
26
|
+
export interface SignInLinkProps {
|
|
27
|
+
/** Absent: no link — a host with no sign-in route shows the reason alone. */
|
|
28
|
+
readonly cta: SignInCta | undefined;
|
|
29
|
+
readonly testId: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function SignInLink(props: SignInLinkProps): ReactElement | null {
|
|
33
|
+
const t = useT();
|
|
34
|
+
const { cta } = props;
|
|
35
|
+
if (cta === undefined) return null;
|
|
36
|
+
// The separating space belongs HERE, not at the call site: a reason with no
|
|
37
|
+
// door must render as exactly its own sentence, and a `{" "}` left behind by
|
|
38
|
+
// an absent link is a trailing space in every caller's assertion.
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
{" "}
|
|
42
|
+
<Typography.Link
|
|
43
|
+
data-testid={props.testId}
|
|
44
|
+
data-analytics="none"
|
|
45
|
+
data-analytics-reason="business action — host app wraps with its own tracked()"
|
|
46
|
+
{...(cta.href !== undefined
|
|
47
|
+
? { href: cta.href }
|
|
48
|
+
: { onClick: cta.onSignIn })}
|
|
49
|
+
>
|
|
50
|
+
{t(CHAT_I18N_KEYS.startSignIn)}
|
|
51
|
+
</Typography.Link>
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
* `<StartChatButton/>` — "message the seller" on a listing card, skinned.
|
|
3
3
|
*
|
|
4
4
|
* The control is either pressable or switched off WITH a sentence beside it
|
|
5
|
-
* (no seller on the listing; the listing is your own
|
|
6
|
-
* grey rectangle with no explanation
|
|
5
|
+
* (no seller on the listing; the listing is your own; you are not signed in).
|
|
6
|
+
* It never renders as a grey rectangle with no explanation — and when the host
|
|
7
|
+
* supplies `signIn`, the sentence comes with the door: a stated reason whose
|
|
8
|
+
* next action is a link the visitor cannot find on their own is only half an
|
|
9
|
+
* answer (storefront Wave D, G-3).
|
|
7
10
|
*/
|
|
8
11
|
import type { ReactElement } from "react";
|
|
9
12
|
import { Button, Space, Typography } from "antd";
|
|
10
13
|
import { useActionGate, useErrorDisplay, useT } from "@stapel/core";
|
|
11
|
-
import type { ActionAvailability } from "@stapel/core";
|
|
14
|
+
import type { ActionAvailability, SignInCta, SignInCtaProp } from "@stapel/core";
|
|
12
15
|
import type { Conversation } from "../api/types.js";
|
|
13
16
|
import { StartDirectChat } from "../headless/StartDirectChat.js";
|
|
14
17
|
import { CHAT_I18N_KEYS } from "../i18n/keys.js";
|
|
15
18
|
import { ErrorAlert } from "./ErrorAlert.js";
|
|
19
|
+
import { SignInLink } from "./SignInLink.js";
|
|
16
20
|
|
|
17
|
-
export interface StartChatButtonProps {
|
|
21
|
+
export interface StartChatButtonProps extends SignInCtaProp {
|
|
18
22
|
sellerId: string | null | undefined;
|
|
19
23
|
viewerId?: string | null;
|
|
20
24
|
onOpened?: (conversation: Conversation) => void;
|
|
@@ -27,6 +31,7 @@ function StartChatBody(props: {
|
|
|
27
31
|
error: unknown;
|
|
28
32
|
start: () => void;
|
|
29
33
|
block: boolean | undefined;
|
|
34
|
+
signIn: SignInCta | undefined;
|
|
30
35
|
}): ReactElement {
|
|
31
36
|
const t = useT();
|
|
32
37
|
const gate = useActionGate(props.availability);
|
|
@@ -50,6 +55,7 @@ function StartChatBody(props: {
|
|
|
50
55
|
{gate.reason ? (
|
|
51
56
|
<Typography.Text type="secondary" data-testid="chat-start-blocked">
|
|
52
57
|
{gate.reason}
|
|
58
|
+
<SignInLink cta={props.signIn} testId="chat-start-sign-in" />
|
|
53
59
|
</Typography.Text>
|
|
54
60
|
) : null}
|
|
55
61
|
<ErrorAlert error={errorDisplay(props.error)} />
|
|
@@ -71,6 +77,7 @@ export function StartChatButton(props: StartChatButtonProps): ReactElement {
|
|
|
71
77
|
error={error}
|
|
72
78
|
start={start}
|
|
73
79
|
block={props.block}
|
|
80
|
+
signIn={props.signIn}
|
|
74
81
|
/>
|
|
75
82
|
)}
|
|
76
83
|
</StartDirectChat>
|
package/src/default/index.ts
CHANGED
|
@@ -18,3 +18,5 @@ export { ConversationThreadPanel } from "./ConversationThreadPanel.js";
|
|
|
18
18
|
export type { ConversationThreadPanelProps } from "./ConversationThreadPanel.js";
|
|
19
19
|
export { StartChatButton } from "./StartChatButton.js";
|
|
20
20
|
export type { StartChatButtonProps } from "./StartChatButton.js";
|
|
21
|
+
export { SignInLink } from "./SignInLink.js";
|
|
22
|
+
export type { SignInLinkProps } from "./SignInLink.js";
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
actionAvailable,
|
|
5
|
+
actionBlocked,
|
|
6
|
+
firstBlock,
|
|
7
|
+
matchMandate,
|
|
8
|
+
useMandate,
|
|
9
|
+
} from "@stapel/core";
|
|
4
10
|
import type { ActionAvailability } from "@stapel/core";
|
|
5
11
|
import type { Conversation } from "../api/types.js";
|
|
6
12
|
import { useStartDirectChat } from "../model/mutations.js";
|
|
@@ -27,6 +33,11 @@ export interface StartDirectChatBag {
|
|
|
27
33
|
* seller all land in the same conversation rather than fanning out into
|
|
28
34
|
* near-duplicate threads.
|
|
29
35
|
*
|
|
36
|
+
* WHO MAY PRESS IT. The write is `IsAuthenticated`, so the first gate is the
|
|
37
|
+
* mandate axis: a visitor is told to sign in BEFORE the click rather than
|
|
38
|
+
* being handed a 401 after it. `<StartChatButton signIn={…}>` puts the door
|
|
39
|
+
* next to that sentence.
|
|
40
|
+
*
|
|
30
41
|
* WHAT THIS BUTTON CANNOT DO, AND WHY IT SAYS SO HERE. It cannot open a
|
|
31
42
|
* thread ABOUT a listing. The thread is keyed by the pair of people, and
|
|
32
43
|
* `CreateConversationRequest.scope_key` — which looks like the place to put a
|
|
@@ -46,7 +57,31 @@ export function StartDirectChat(props: {
|
|
|
46
57
|
const start = useStartDirectChat();
|
|
47
58
|
const { sellerId, viewerId, onOpened } = props;
|
|
48
59
|
|
|
60
|
+
/**
|
|
61
|
+
* May this person write at all? `POST /conversations/` is
|
|
62
|
+
* `IsAuthenticated`, so a visitor pressing this bought a 401 — a refusal
|
|
63
|
+
* delivered after the click, which is the one moment it is useless. The
|
|
64
|
+
* axis is read through core's `MandateSource` seam, never derived here: a
|
|
65
|
+
* storefront's derivation is "is there a session?", a tenant app's is
|
|
66
|
+
* `@stapel/workspaces-react`'s.
|
|
67
|
+
*
|
|
68
|
+
* `unavailable` stays AVAILABLE, unlike the other four arms. Outside a
|
|
69
|
+
* `<MandateProvider>` core answers `unresolved/unavailable`, and a host that
|
|
70
|
+
* never wired the axis must not lose its button — "we could not ask" is not
|
|
71
|
+
* "you may not" (the storefront spec's own §7.4 negative leg), and if the
|
|
72
|
+
* guess is wrong the module answers 401 exactly as it did before.
|
|
73
|
+
*/
|
|
74
|
+
const mandate = useMandate();
|
|
75
|
+
const mandateGate = matchMandate<ActionAvailability>(mandate, {
|
|
76
|
+
member: () => actionAvailable(),
|
|
77
|
+
guest: () => actionBlocked(CHAT_I18N_KEYS.startBlockedSignIn),
|
|
78
|
+
anonymous: () => actionBlocked(CHAT_I18N_KEYS.startBlockedSignIn),
|
|
79
|
+
asking: () => actionBlocked(CHAT_I18N_KEYS.startBlockedMandateUnknown),
|
|
80
|
+
unavailable: () => actionAvailable(),
|
|
81
|
+
});
|
|
82
|
+
|
|
49
83
|
const availability: ActionAvailability = firstBlock(
|
|
84
|
+
mandateGate,
|
|
50
85
|
sellerId === null || sellerId === undefined || sellerId.length === 0
|
|
51
86
|
? actionBlocked(CHAT_I18N_KEYS.startBlockedUnknownSeller)
|
|
52
87
|
: actionAvailable(),
|
package/src/i18n/es.ts
CHANGED
|
@@ -75,6 +75,9 @@ export const chatI18nBundleEs: I18nDictionary = {
|
|
|
75
75
|
"chat.start.starting": "Abriendo…",
|
|
76
76
|
"chat.start.blocked.self": "Este anuncio es tuyo.",
|
|
77
77
|
"chat.start.blocked.unknown_seller": "Este anuncio no tiene vendedor a quien escribir.",
|
|
78
|
+
"chat.start.blocked.sign_in": "Inicia sesión para escribir al vendedor.",
|
|
79
|
+
"chat.start.blocked.mandate_unknown": "Comprobando tu sesión…",
|
|
80
|
+
"chat.start.sign_in": "Iniciar sesión",
|
|
78
81
|
|
|
79
82
|
"chat.transport.live": "En directo",
|
|
80
83
|
"chat.transport.polling": "Actualizando cada pocos segundos",
|
package/src/i18n/keys.ts
CHANGED
|
@@ -48,6 +48,12 @@ export const CHAT_I18N_KEYS = {
|
|
|
48
48
|
startStarting: "chat.start.starting",
|
|
49
49
|
startBlockedSelf: "chat.start.blocked.self",
|
|
50
50
|
startBlockedUnknownSeller: "chat.start.blocked.unknown_seller",
|
|
51
|
+
/** A visitor: the POST is `IsAuthenticated`, so say so before the click. */
|
|
52
|
+
startBlockedSignIn: "chat.start.blocked.sign_in",
|
|
53
|
+
/** We have not finished asking who this is. Not "you may not". */
|
|
54
|
+
startBlockedMandateUnknown: "chat.start.blocked.mandate_unknown",
|
|
55
|
+
/** The door beside a blocked "message the seller": the container says WHERE. */
|
|
56
|
+
startSignIn: "chat.start.sign_in",
|
|
51
57
|
|
|
52
58
|
// Transport (the seam is invisible to the UI's BEHAVIOUR, but a person may
|
|
53
59
|
// still be told whether the thread is live or on a timer)
|
|
@@ -126,6 +132,9 @@ export const chatI18nBundleEn: I18nDictionary = {
|
|
|
126
132
|
"chat.start.starting": "Opening…",
|
|
127
133
|
"chat.start.blocked.self": "This is your own listing.",
|
|
128
134
|
"chat.start.blocked.unknown_seller": "This listing has no seller to write to.",
|
|
135
|
+
"chat.start.blocked.sign_in": "Sign in to message the seller.",
|
|
136
|
+
"chat.start.blocked.mandate_unknown": "Checking your session…",
|
|
137
|
+
"chat.start.sign_in": "Sign in",
|
|
129
138
|
|
|
130
139
|
"chat.transport.live": "Live",
|
|
131
140
|
"chat.transport.polling": "Refreshing every few seconds",
|
package/src/i18n/ru.ts
CHANGED
|
@@ -77,6 +77,9 @@ export const chatI18nBundleRu: I18nDictionary = {
|
|
|
77
77
|
"chat.start.starting": "Открываем…",
|
|
78
78
|
"chat.start.blocked.self": "Это ваше собственное объявление.",
|
|
79
79
|
"chat.start.blocked.unknown_seller": "У этого объявления не указан продавец.",
|
|
80
|
+
"chat.start.blocked.sign_in": "Войдите, чтобы написать продавцу.",
|
|
81
|
+
"chat.start.blocked.mandate_unknown": "Проверяем вашу сессию…",
|
|
82
|
+
"chat.start.sign_in": "Войти",
|
|
80
83
|
|
|
81
84
|
"chat.transport.live": "На связи",
|
|
82
85
|
"chat.transport.polling": "Обновляется каждые несколько секунд",
|