@civitai/blocks-react 0.47.0 → 0.49.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/README.md +89 -6
- package/dist/hooks/SfwGate.d.ts +16 -9
- package/dist/hooks/SfwGate.d.ts.map +1 -1
- package/dist/hooks/SfwGate.js +14 -8
- package/dist/hooks/SfwGate.js.map +1 -1
- package/dist/hooks/useCollectionFollow.d.ts +141 -0
- package/dist/hooks/useCollectionFollow.d.ts.map +1 -0
- package/dist/hooks/useCollectionFollow.js +202 -0
- package/dist/hooks/useCollectionFollow.js.map +1 -0
- package/dist/hooks/useDomainMaturity.d.ts +57 -17
- package/dist/hooks/useDomainMaturity.d.ts.map +1 -1
- package/dist/hooks/useDomainMaturity.js +37 -11
- package/dist/hooks/useDomainMaturity.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/iframeTransport.d.ts.map +1 -1
- package/dist/internal/iframeTransport.js +5 -2
- package/dist/internal/iframeTransport.js.map +1 -1
- package/dist/internal/liveHost.d.ts.map +1 -1
- package/dist/internal/liveHost.js +33 -0
- package/dist/internal/liveHost.js.map +1 -1
- package/dist/internal/mockHost.d.ts +39 -2
- package/dist/internal/mockHost.d.ts.map +1 -1
- package/dist/internal/mockHost.js +61 -0
- package/dist/internal/mockHost.js.map +1 -1
- package/dist/internal/requestTimeouts.d.ts.map +1 -1
- package/dist/internal/requestTimeouts.js +8 -0
- package/dist/internal/requestTimeouts.js.map +1 -1
- package/dist/internal/transport.d.ts +28 -1
- package/dist/internal/transport.d.ts.map +1 -1
- package/dist/internal/transport.js +25 -0
- package/dist/internal/transport.js.map +1 -1
- package/dist/internal/validate.d.ts +20 -0
- package/dist/internal/validate.d.ts.map +1 -1
- package/dist/internal/validate.js +60 -0
- package/dist/internal/validate.js.map +1 -1
- package/dist/ui/FollowButton.d.ts +85 -0
- package/dist/ui/FollowButton.d.ts.map +1 -0
- package/dist/ui/FollowButton.js +162 -0
- package/dist/ui/FollowButton.js.map +1 -0
- package/dist/ui/TipButton.d.ts +115 -0
- package/dist/ui/TipButton.d.ts.map +1 -0
- package/dist/ui/TipButton.js +308 -0
- package/dist/ui/TipButton.js.map +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +2 -0
- package/dist/ui/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { TipParams } from '../hooks/useTip.js';
|
|
2
|
+
import type { ButtonSize, ButtonVariant } from './Button.js';
|
|
3
|
+
export interface TipButtonProps {
|
|
4
|
+
/** The recipient. The SENDER is always the token subject — server-self-bound. */
|
|
5
|
+
toUserId: number;
|
|
6
|
+
/** Buzz to send. Must be a positive integer. */
|
|
7
|
+
amount: number;
|
|
8
|
+
/** Optional context recorded on the transaction. */
|
|
9
|
+
entityType?: TipParams['entityType'];
|
|
10
|
+
entityId?: number;
|
|
11
|
+
/**
|
|
12
|
+
* What the viewer is tipping FOR, lower-case and singular — "creator",
|
|
13
|
+
* "curator", "post". Appears in the confirm question and both accessible
|
|
14
|
+
* names.
|
|
15
|
+
*
|
|
16
|
+
* 🔴 It is spliced into an `aria-label`, so pass a bare noun, not a sentence.
|
|
17
|
+
*/
|
|
18
|
+
noun: string;
|
|
19
|
+
/**
|
|
20
|
+
* This viewer has ALREADY tipped this target in this session. Renders the
|
|
21
|
+
* settled state and skips the handshake.
|
|
22
|
+
*
|
|
23
|
+
* 🔴 THERE IS NO SERVER FIELD FOR THIS. `useTip` returns the transaction echo
|
|
24
|
+
* and nothing reads back "has this viewer tipped X?" — so without a value
|
|
25
|
+
* here the settled state is local-only and any remount re-arms the control.
|
|
26
|
+
* That is not a no-op the way a duplicate vote would be: a second press is a
|
|
27
|
+
* SECOND TRANSFER. Record it in your own per-viewer storage when the tip
|
|
28
|
+
* resolves and feed it back.
|
|
29
|
+
*/
|
|
30
|
+
tipped?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The viewer's remaining daily tip allowance, in Buzz. When supplied and
|
|
33
|
+
* smaller than `amount`, the control refuses locally with a note instead of
|
|
34
|
+
* sending a request the server would reject.
|
|
35
|
+
*
|
|
36
|
+
* 🔴 DELIBERATELY A PROP, NOT AN INTERNAL `useTipAllowance()`. This control is
|
|
37
|
+
* rendered per card / per rail, so fetching inside it would fan one screen out
|
|
38
|
+
* into N identical HTTP reads. Hold ONE `useTipAllowance()` in the view and
|
|
39
|
+
* pass `allowance?.remaining` down; call its `refetch()` from `onTipped`.
|
|
40
|
+
*
|
|
41
|
+
* Omitted → no local ceiling check, and the server's own limit is the only
|
|
42
|
+
* gate. That is correct, just later and less legible.
|
|
43
|
+
*/
|
|
44
|
+
remaining?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Why tipping is unavailable, e.g. "You can't tip your own collection."
|
|
47
|
+
* Present ⇒ the control is disabled and this is shown as its `title` and
|
|
48
|
+
* appended to its accessible name.
|
|
49
|
+
*
|
|
50
|
+
* 🔴 USE THIS FOR THE SELF-TIP CASE. The server answers a self-tip with a 403,
|
|
51
|
+
* so without it the viewer presses, confirms, and is told a transfer failed —
|
|
52
|
+
* for something that was never going to be allowed.
|
|
53
|
+
*/
|
|
54
|
+
disabledReason?: string;
|
|
55
|
+
/** Disable for any other reason (view still loading, no target resolved yet). */
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Called after a SUCCESSFUL transfer, with the amount sent. Refetch your
|
|
59
|
+
* allowance and record the `tipped` flag here.
|
|
60
|
+
*/
|
|
61
|
+
onTipped?: (amount: number) => void;
|
|
62
|
+
/** Button size preset. Defaults to `'md'`. */
|
|
63
|
+
size?: ButtonSize;
|
|
64
|
+
/** Variant for the trigger. Defaults to `'light'`. */
|
|
65
|
+
variant?: ButtonVariant;
|
|
66
|
+
/**
|
|
67
|
+
* Test hook for the TRIGGER. The other four are DERIVED by suffix:
|
|
68
|
+
* `<id>-confirm`, `<id>-cancel`, `<id>-done`, `<id>-prompt`. Omitted, the ids
|
|
69
|
+
* are `tip-button`, `tip-confirm`, `tip-cancel`, `tip-done`,
|
|
70
|
+
* `tip-confirm-prompt`.
|
|
71
|
+
*
|
|
72
|
+
* 🔴 Grep for the SUFFIX, never the composed value.
|
|
73
|
+
*/
|
|
74
|
+
'data-testid'?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Send a Buzz tip, behind an in-block two-step confirm.
|
|
78
|
+
*
|
|
79
|
+
* 🔴 THE CONFIRM IS THIS COMPONENT'S, NOT HOST CHROME — state that honestly
|
|
80
|
+
* rather than implying platform mediation. `useTip` posts to the block-token-
|
|
81
|
+
* gated tip endpoint directly (scope `social:tip:self`), so unlike the
|
|
82
|
+
* collection-follow bridge NOTHING outside the iframe asks the viewer anything.
|
|
83
|
+
* A one-press money spend is therefore reachable by construction, and the
|
|
84
|
+
* two-step handshake here is the only thing between a stray tap and a transfer.
|
|
85
|
+
* Do not add a prop to skip it.
|
|
86
|
+
*
|
|
87
|
+
* 🔴 IT MINTS ONE IDEMPOTENCY KEY PER LOGICAL TIP AND REUSES IT ON RETRY. That
|
|
88
|
+
* is the property a hand-rolled button most reliably misses: `useTip` mints a
|
|
89
|
+
* FRESH key per call when you do not pass one, so retrying after a timeout
|
|
90
|
+
* whose response was merely LOST sends a SECOND transfer. Here the key is
|
|
91
|
+
* minted when the control arms and rotates only after a tip actually settles,
|
|
92
|
+
* so "try again" on a failed press is collapsed server-side to one transfer.
|
|
93
|
+
*
|
|
94
|
+
* The caller decides who sees it: `tip` rejects for an anonymous viewer and a
|
|
95
|
+
* self-tip 403s. Pass `disabledReason` for the self case rather than offering an
|
|
96
|
+
* error.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // ONE allowance read for the whole view, passed down.
|
|
100
|
+
* const { allowance, refetch } = useTipAllowance();
|
|
101
|
+
* <TipButton
|
|
102
|
+
* noun="curator"
|
|
103
|
+
* toUserId={collection.curator.id}
|
|
104
|
+
* amount={50}
|
|
105
|
+
* entityType="Collection"
|
|
106
|
+
* entityId={collection.id}
|
|
107
|
+
* remaining={allowance?.remaining}
|
|
108
|
+
* tipped={tippedCurators.has(collection.id)}
|
|
109
|
+
* disabledReason={isSelf ? "You can't tip your own collection." : undefined}
|
|
110
|
+
* onTipped={() => { refetch(); markTipped(collection.id); }}
|
|
111
|
+
* data-testid="tip-curator"
|
|
112
|
+
* />
|
|
113
|
+
*/
|
|
114
|
+
export declare function TipButton({ toUserId, amount, entityType, entityId, noun, tipped, remaining, disabledReason, disabled, onTipped, size, variant, 'data-testid': testId, }: TipButtonProps): React.JSX.Element;
|
|
115
|
+
//# sourceMappingURL=TipButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TipButton.d.ts","sourceRoot":"","sources":["../../src/ui/TipButton.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAU7D,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,8CAA8C;IAC9C,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,MAAM,EACN,UAAU,EACV,QAAQ,EACR,IAAI,EACJ,MAAc,EACd,SAAS,EACT,cAAc,EACd,QAAgB,EAChB,QAAQ,EACR,IAAW,EACX,OAAiB,EACjB,aAAa,EAAE,MAAM,GACtB,EAAE,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAmTpC"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
3
|
+
import { useTip } from '../hooks/useTip.js';
|
|
4
|
+
import { Button } from './Button.js';
|
|
5
|
+
import { Group } from './Group.js';
|
|
6
|
+
import { useBlocksStyles } from './styles.js';
|
|
7
|
+
const NOTE_STYLE = {
|
|
8
|
+
fontSize: 12,
|
|
9
|
+
lineHeight: 1.45,
|
|
10
|
+
color: 'var(--civitai-color-text-dimmed)',
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Send a Buzz tip, behind an in-block two-step confirm.
|
|
14
|
+
*
|
|
15
|
+
* 🔴 THE CONFIRM IS THIS COMPONENT'S, NOT HOST CHROME — state that honestly
|
|
16
|
+
* rather than implying platform mediation. `useTip` posts to the block-token-
|
|
17
|
+
* gated tip endpoint directly (scope `social:tip:self`), so unlike the
|
|
18
|
+
* collection-follow bridge NOTHING outside the iframe asks the viewer anything.
|
|
19
|
+
* A one-press money spend is therefore reachable by construction, and the
|
|
20
|
+
* two-step handshake here is the only thing between a stray tap and a transfer.
|
|
21
|
+
* Do not add a prop to skip it.
|
|
22
|
+
*
|
|
23
|
+
* 🔴 IT MINTS ONE IDEMPOTENCY KEY PER LOGICAL TIP AND REUSES IT ON RETRY. That
|
|
24
|
+
* is the property a hand-rolled button most reliably misses: `useTip` mints a
|
|
25
|
+
* FRESH key per call when you do not pass one, so retrying after a timeout
|
|
26
|
+
* whose response was merely LOST sends a SECOND transfer. Here the key is
|
|
27
|
+
* minted when the control arms and rotates only after a tip actually settles,
|
|
28
|
+
* so "try again" on a failed press is collapsed server-side to one transfer.
|
|
29
|
+
*
|
|
30
|
+
* The caller decides who sees it: `tip` rejects for an anonymous viewer and a
|
|
31
|
+
* self-tip 403s. Pass `disabledReason` for the self case rather than offering an
|
|
32
|
+
* error.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // ONE allowance read for the whole view, passed down.
|
|
36
|
+
* const { allowance, refetch } = useTipAllowance();
|
|
37
|
+
* <TipButton
|
|
38
|
+
* noun="curator"
|
|
39
|
+
* toUserId={collection.curator.id}
|
|
40
|
+
* amount={50}
|
|
41
|
+
* entityType="Collection"
|
|
42
|
+
* entityId={collection.id}
|
|
43
|
+
* remaining={allowance?.remaining}
|
|
44
|
+
* tipped={tippedCurators.has(collection.id)}
|
|
45
|
+
* disabledReason={isSelf ? "You can't tip your own collection." : undefined}
|
|
46
|
+
* onTipped={() => { refetch(); markTipped(collection.id); }}
|
|
47
|
+
* data-testid="tip-curator"
|
|
48
|
+
* />
|
|
49
|
+
*/
|
|
50
|
+
export function TipButton({ toUserId, amount, entityType, entityId, noun, tipped = false, remaining, disabledReason, disabled = false, onTipped, size = 'md', variant = 'light', 'data-testid': testId, }) {
|
|
51
|
+
useBlocksStyles();
|
|
52
|
+
const { tip } = useTip();
|
|
53
|
+
const [confirming, setConfirming] = useState(false);
|
|
54
|
+
const [busy, setBusy] = useState(false);
|
|
55
|
+
const [done, setDone] = useState(false);
|
|
56
|
+
const [failure, setFailure] = useState(null);
|
|
57
|
+
const confirmRef = useRef(null);
|
|
58
|
+
const cancelRef = useRef(null);
|
|
59
|
+
const doneRef = useRef(null);
|
|
60
|
+
/** Monotonic id of the current attempt — see `ReportButton` for the measured why. */
|
|
61
|
+
const attemptRef = useRef(0);
|
|
62
|
+
/**
|
|
63
|
+
* The idempotency keys this mount has already reported a landed transfer for.
|
|
64
|
+
*
|
|
65
|
+
* 🔴 KEYED, NOT A BOOLEAN — and the boolean it replaces was wrong in the
|
|
66
|
+
* OPPOSITE direction from the bug it fixed. "At most once per mount" collapses
|
|
67
|
+
* two transfers that are genuinely distinct: Cancel does not abort POST #1, so
|
|
68
|
+
* if the parent then moves `amount` or the entity a NEW key is minted, the
|
|
69
|
+
* server does NOT collapse them, and 150 Buzz moves while the app is told 50.
|
|
70
|
+
* `onTipped` is where a caller refetches the allowance and records its
|
|
71
|
+
* `tipped` flag, so the second transfer left no record — re-creating on
|
|
72
|
+
* remount the very harm the previous round cited. The correct scope is once
|
|
73
|
+
* per KEY: same key ⇒ the server saw one transfer ⇒ report once; different
|
|
74
|
+
* key ⇒ two transfers ⇒ report both.
|
|
75
|
+
*
|
|
76
|
+
* 🔴 A REF, NOT STATE. The success path reads it in the same tick it would
|
|
77
|
+
* write it, so two promises resolving in one turn both see stale state and
|
|
78
|
+
* both report; a ref is written synchronously, so the second sees the first.
|
|
79
|
+
*/
|
|
80
|
+
const reportedKeysRef = useRef(new Set());
|
|
81
|
+
/**
|
|
82
|
+
* The idempotency key for THIS logical tip. Stable across retries, which is
|
|
83
|
+
* the point; `useId()` seeds it so two TipButtons mounted in one tree never
|
|
84
|
+
* collide, and the target + amount are folded in so that changing either (an
|
|
85
|
+
* amount preset switcher) correctly starts a NEW logical tip rather than
|
|
86
|
+
* colliding with the abandoned one.
|
|
87
|
+
*
|
|
88
|
+
* 🔴 EVERY FIELD OF THE TIP'S IDENTITY IS IN THE KEY, and the earlier version
|
|
89
|
+
* omitted two. `entityType`/`entityId` are sent in the POST body and recorded
|
|
90
|
+
* on the transaction, so they are part of WHICH tip this is — leaving them out
|
|
91
|
+
* meant that changing the entity after a failed attempt reused the same key,
|
|
92
|
+
* and if the first attempt had actually landed (the lost-response case this
|
|
93
|
+
* mechanism exists for) the second, deliberate tip to a DIFFERENT object was
|
|
94
|
+
* collapsed into it and the block was told it succeeded. Under-charge rather
|
|
95
|
+
* than double-charge, but wrong either way.
|
|
96
|
+
*
|
|
97
|
+
* 🔴 NOT ROTATED AFTER A SUCCESS, and what licenses that is narrower than it
|
|
98
|
+
* first appears. `done` is never cleared, so a control that has settled
|
|
99
|
+
* THROUGH ITS OWN SUCCESS PATH cannot send again from this mount. `settled` is
|
|
100
|
+
* `done || tipped`, and the `tipped` half is a PROP — a parent can withdraw it
|
|
101
|
+
* — so terminality is a property of `done`, not of `settled`.
|
|
102
|
+
*
|
|
103
|
+
* ⚠️ An earlier revision continued "…which is why the success path reports
|
|
104
|
+
* UNCONDITIONALLY". It no longer does — it reports once per KEY — and the
|
|
105
|
+
* sentence is corrected rather than deleted because it was load-bearing. The
|
|
106
|
+
* invariant that actually licenses keeping the key is unchanged: `done` is set
|
|
107
|
+
* on the first landed transfer for a given key, and a repeat under the SAME
|
|
108
|
+
* key is that same transfer. Change either and rotate.
|
|
109
|
+
*/
|
|
110
|
+
const keySeed = useId();
|
|
111
|
+
const idempotencyKey = `${keySeed}:${toUserId}:${amount}:${entityType ?? '-'}:${entityId ?? '-'}`;
|
|
112
|
+
const settled = done || tipped;
|
|
113
|
+
// Both guards are about a NUMBER reaching a money path, and both were once
|
|
114
|
+
// missing: `amount={0}` rendered "Tip 0" and posted it, and an unusable
|
|
115
|
+
// `remaining` silently REMOVED the ceiling rather than blocking.
|
|
116
|
+
//
|
|
117
|
+
// `amount` must be a real, positive number. `Infinity` is not a tippable
|
|
118
|
+
// quantity, so `isFinite` is load-bearing on its own rather than a longer
|
|
119
|
+
// spelling of `> 0` — and it does not coerce, so a non-number is rejected too.
|
|
120
|
+
const amountValid = Number.isFinite(amount) && amount > 0;
|
|
121
|
+
// 🔴 THREE cases for `remaining`, not two, and a previous revision lost one
|
|
122
|
+
// while fixing another. `NaN` is an UNUSABLE reading — every comparison
|
|
123
|
+
// against it is false — so it must BLOCK. `Infinity` is a MEANINGFUL reading,
|
|
124
|
+
// an unlimited allowance, so blocking it refuses a viewer who is allowed
|
|
125
|
+
// everything; sweeping the two together under `!Number.isFinite` got that
|
|
126
|
+
// wrong. And a NON-NUMBER must block for the same reason as `NaN`: 🔴
|
|
127
|
+
// `Number.isNaN` does NOT coerce, so the `!isFinite` → `isNaN` fix silently
|
|
128
|
+
// dropped the type check and let `remaining: 'abc'` through — removing the
|
|
129
|
+
// ceiling, the wrong direction, and precisely what the comment it replaced
|
|
130
|
+
// claimed could not happen.
|
|
131
|
+
const overAllowance = remaining !== undefined &&
|
|
132
|
+
(typeof remaining !== 'number' || Number.isNaN(remaining) || amount > remaining);
|
|
133
|
+
const blocked = disabled || disabledReason !== undefined || !amountValid;
|
|
134
|
+
const ids = testId
|
|
135
|
+
? {
|
|
136
|
+
trigger: testId,
|
|
137
|
+
confirm: `${testId}-confirm`,
|
|
138
|
+
cancel: `${testId}-cancel`,
|
|
139
|
+
done: `${testId}-done`,
|
|
140
|
+
prompt: `${testId}-prompt`,
|
|
141
|
+
}
|
|
142
|
+
: {
|
|
143
|
+
trigger: 'tip-button',
|
|
144
|
+
confirm: 'tip-confirm',
|
|
145
|
+
cancel: 'tip-cancel',
|
|
146
|
+
done: 'tip-done',
|
|
147
|
+
prompt: 'tip-confirm-prompt',
|
|
148
|
+
};
|
|
149
|
+
// Move focus with the control at both transitions — each step REPLACES the
|
|
150
|
+
// element the viewer just activated, so without this a keyboard user is
|
|
151
|
+
// dropped to <body>. Same three effects, and the same reasoning, as
|
|
152
|
+
// `ReportButton`; keyed on the transition, never on the request lifecycle.
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (confirming)
|
|
155
|
+
confirmRef.current?.focus();
|
|
156
|
+
}, [confirming]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (done)
|
|
159
|
+
doneRef.current?.focus();
|
|
160
|
+
}, [done]);
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (busy)
|
|
163
|
+
cancelRef.current?.focus();
|
|
164
|
+
}, [busy]);
|
|
165
|
+
// Server/app truth ENDS the handshake rather than leaving it mounted beneath
|
|
166
|
+
// the settled note, where a late rejection could resurrect a stale failure
|
|
167
|
+
// against a tip the viewer has already seen settle.
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (tipped) {
|
|
170
|
+
attemptRef.current += 1;
|
|
171
|
+
setConfirming(false);
|
|
172
|
+
setFailure(null);
|
|
173
|
+
setBusy(false);
|
|
174
|
+
}
|
|
175
|
+
}, [tipped]);
|
|
176
|
+
const confirm = useCallback(async () => {
|
|
177
|
+
// 🔴 RE-CHECK AT THE SPEND, NOT ONLY AT THE ARM. `blocked` gates the trigger,
|
|
178
|
+
// but the prompt stays mounted across a re-render — so a parent moving
|
|
179
|
+
// `amount` to 0 (or NaN) AFTER the viewer armed the control left an enabled
|
|
180
|
+
// Send that posted it. An amount switcher mid-handshake is a flow this
|
|
181
|
+
// component's own JSDoc contemplates, so this is reachable, not theoretical.
|
|
182
|
+
if (!amountValid || overAllowance || blocked) {
|
|
183
|
+
// 🔴 NAME THE ACTUAL REASON. A fixed "That amount cannot be sent." is
|
|
184
|
+
// FALSE when the block is a `disabledReason` (the amount is fine, and a
|
|
185
|
+
// real reason string is in hand) — and this file's own comment about the
|
|
186
|
+
// stale-closure bug calls that message out as "false about the amount"
|
|
187
|
+
// while shipping it on a path it pins. Order matches the gate.
|
|
188
|
+
// 🔴 `||`, NOT `??` — a mutation found this, and it is the same distinction
|
|
189
|
+
// as the error channel's. `blocked` tests `disabledReason !== undefined`,
|
|
190
|
+
// so an EMPTY-STRING reason (a consumer writing
|
|
191
|
+
// `disabledReason={bad ? reason : ''}`) blocks — and `??` would then set an
|
|
192
|
+
// EMPTY failure message, rendering a blank refusal, which is the worst
|
|
193
|
+
// outcome on a money control. `||` falls through to a real sentence.
|
|
194
|
+
setFailure(disabledReason ||
|
|
195
|
+
(!amountValid
|
|
196
|
+
? 'That amount cannot be sent.'
|
|
197
|
+
: overAllowance
|
|
198
|
+
? 'Over your remaining daily tip allowance.'
|
|
199
|
+
: 'Tipping is unavailable right now.'));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const attempt = (attemptRef.current += 1);
|
|
203
|
+
const current = () => attemptRef.current === attempt;
|
|
204
|
+
setBusy(true);
|
|
205
|
+
setFailure(null);
|
|
206
|
+
try {
|
|
207
|
+
await tip({ toUserId, amount, ...(entityType ? { entityType } : {}), ...(entityId !== undefined ? { entityId } : {}) }, { idempotencyKey });
|
|
208
|
+
// 🔴 A LANDED TRANSFER IS REPORTED UNCONDITIONALLY — no `current()` check
|
|
209
|
+
// on this path, deliberately, and the earlier version's check here was the
|
|
210
|
+
// defect rather than the safety.
|
|
211
|
+
//
|
|
212
|
+
// Buzz has MOVED by the time this line runs. Nothing the viewer or the
|
|
213
|
+
// parent did in the meantime can un-send it: Cancel resets this control's
|
|
214
|
+
// UI but does not abort the POST, and a parent flipping `tipped` mid-flight
|
|
215
|
+
// bumps the attempt for its own reasons. Suppressing the settle in either
|
|
216
|
+
// case left the app never learning the transfer succeeded — so `onTipped`
|
|
217
|
+
// never fired, the allowance was never refetched, and no `tipped` record
|
|
218
|
+
// was written, which re-armed the control over money that was already gone.
|
|
219
|
+
//
|
|
220
|
+
// Showing "Tipped" for a transfer that happened is correct even if the
|
|
221
|
+
// viewer pressed Cancel a moment earlier; showing "Tip 50" again is what
|
|
222
|
+
// invites the second one. Only the FAILURE path below respects supersession
|
|
223
|
+
// — there, nothing moved, so an abandoned attempt has nothing to report.
|
|
224
|
+
//
|
|
225
|
+
// 🔴 BUT AT MOST ONCE PER KEY, and "unconditionally" without this was a
|
|
226
|
+
// REGRESSION the previous revision introduced. Cancel-then-retry sends a SECOND POST
|
|
227
|
+
// carrying the SAME idempotency key, so the server collapses the pair into
|
|
228
|
+
// ONE transfer while both promises resolve — and an unguarded report then
|
|
229
|
+
// called `onTipped(amount)` twice for money that moved once. `onTipped` is
|
|
230
|
+
// handed the amount precisely so a caller can decrement an allowance with
|
|
231
|
+
// it, so double-firing double-counts. The answer is "once per KEY" — not
|
|
232
|
+
// the old "never after supersession", not a bare "always", and not the
|
|
233
|
+
// "once per mount" an intermediate revision used, which collapsed two
|
|
234
|
+
// GENUINELY DISTINCT transfers (see `reportedKeysRef`).
|
|
235
|
+
if (!reportedKeysRef.current.has(idempotencyKey)) {
|
|
236
|
+
reportedKeysRef.current.add(idempotencyKey);
|
|
237
|
+
setDone(true);
|
|
238
|
+
setConfirming(false);
|
|
239
|
+
setFailure(null);
|
|
240
|
+
onTipped?.(amount);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
if (!current())
|
|
245
|
+
return;
|
|
246
|
+
// Show the SERVER's message. Unlike a report, a failed tip has real and
|
|
247
|
+
// varied causes the viewer can act on (insufficient balance, over the
|
|
248
|
+
// daily cap, self-tip) and a fixed "could not send" throws all of them
|
|
249
|
+
// away.
|
|
250
|
+
setFailure(err instanceof Error && err.message ? err.message : 'Could not send the tip.');
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
if (current())
|
|
254
|
+
setBusy(false);
|
|
255
|
+
}
|
|
256
|
+
// 🔴 `amountValid`, `overAllowance` and `blocked` MUST be here. The gate at
|
|
257
|
+
// the top of this callback reads all three, and the previous revision listed
|
|
258
|
+
// none of them — so they were frozen at whatever render last recreated the
|
|
259
|
+
// callback, and the gate decided a SPEND on stale values. It failed in both
|
|
260
|
+
// directions and there is no eslint in this repo to catch it:
|
|
261
|
+
//
|
|
262
|
+
// • STALE-RESTRICTIVE, and permanent: a parent that tops up `remaining`,
|
|
263
|
+
// or clears `disabled` / `disabledReason` (the "view still loading"
|
|
264
|
+
// usage this component's own JSDoc names), left Send refusing a
|
|
265
|
+
// perfectly good tip with a message that is false about the amount —
|
|
266
|
+
// for the life of the mount, since nothing else moved a dep.
|
|
267
|
+
// • STALE-PERMISSIVE: the mirror case the gate's own comment claims to
|
|
268
|
+
// cover — `remaining` dropping, or `disabledReason` appearing, AFTER
|
|
269
|
+
// arming — sailed straight through.
|
|
270
|
+
//
|
|
271
|
+
// 🔴 IT WAS ALSO CONSUMER-DEPENDENT, which is why no test caught it: an
|
|
272
|
+
// INLINE `onTipped={() => {}}` (the shape the @example uses) recreates the
|
|
273
|
+
// callback every render and hides the whole thing, while a consumer doing
|
|
274
|
+
// the idiomatic `useCallback` gets the wedge. Listing the derived booleans
|
|
275
|
+
// rather than the raw props keeps this honest — add an input to the gate and
|
|
276
|
+
// the dep is already named.
|
|
277
|
+
}, [
|
|
278
|
+
amount,
|
|
279
|
+
amountValid,
|
|
280
|
+
blocked,
|
|
281
|
+
entityId,
|
|
282
|
+
entityType,
|
|
283
|
+
idempotencyKey,
|
|
284
|
+
onTipped,
|
|
285
|
+
overAllowance,
|
|
286
|
+
tip,
|
|
287
|
+
toUserId,
|
|
288
|
+
]);
|
|
289
|
+
if (settled) {
|
|
290
|
+
return (_jsx("span", { ref: doneRef, tabIndex: -1, "data-testid": ids.done, role: "status", style: { ...NOTE_STYLE, outline: 'none' }, children: `Tipped ${noun}` }));
|
|
291
|
+
}
|
|
292
|
+
if (!confirming) {
|
|
293
|
+
const reason = disabledReason ?? (overAllowance ? 'Over your remaining daily tip allowance.' : undefined);
|
|
294
|
+
return (_jsx(Button, { size: size, variant: variant, disabled: blocked || overAllowance, onClick: () => setConfirming(true), "data-testid": ids.trigger, ...(reason ? { title: reason } : {}), "aria-label": reason ? `Tip the ${noun} — unavailable: ${reason}` : `Tip the ${noun} ${amount} Buzz`, children: `Tip ${amount}` }));
|
|
295
|
+
}
|
|
296
|
+
return (_jsxs(Group, { gap: 6, align: "center", wrap: false, "data-testid": ids.prompt, children: [_jsx("span", { style: NOTE_STYLE, ...(failure ? { role: 'alert' } : {}), children: failure ?? `Send ${amount} Buzz to the ${noun}?` }), _jsx(Button, { ref: confirmRef, size: "sm", loading: busy, onClick: () => void confirm(), "data-testid": ids.confirm, "aria-label": `Confirm sending ${amount} Buzz to the ${noun}`, children: "Send" }), _jsx(Button, { ref: cancelRef, size: "sm", variant: "subtle",
|
|
297
|
+
// 🔴 NOT disabled in flight, for the reason `ReportButton` records: a
|
|
298
|
+
// reply that never arrives would otherwise leave both controls dead with
|
|
299
|
+
// no way back short of a remount. The attempt token closes the race
|
|
300
|
+
// without taking the escape hatch away.
|
|
301
|
+
onClick: () => {
|
|
302
|
+
attemptRef.current += 1;
|
|
303
|
+
setBusy(false);
|
|
304
|
+
setFailure(null);
|
|
305
|
+
setConfirming(false);
|
|
306
|
+
}, "data-testid": ids.cancel, "aria-label": "Cancel the tip", children: "Cancel" })] }));
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=TipButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TipButton.js","sourceRoot":"","sources":["../../src/ui/TipButton.tsx"],"names":[],"mappings":"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"}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -40,6 +40,10 @@ export { Badge } from './Badge.js';
|
|
|
40
40
|
export type { BadgeProps, BadgeVariant, BadgeSize } from './Badge.js';
|
|
41
41
|
export { ReportButton } from './ReportButton.js';
|
|
42
42
|
export type { ReportButtonProps } from './ReportButton.js';
|
|
43
|
+
export { FollowButton } from './FollowButton.js';
|
|
44
|
+
export type { FollowButtonProps } from './FollowButton.js';
|
|
45
|
+
export { TipButton } from './TipButton.js';
|
|
46
|
+
export type { TipButtonProps } from './TipButton.js';
|
|
43
47
|
export { ResourceCard, resourceDisplayName } from './ResourceCard.js';
|
|
44
48
|
export type { ResourceCardProps, ResourceCardVariant } from './ResourceCard.js';
|
|
45
49
|
export { Modal } from './Modal.js';
|
package/dist/ui/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAK3D,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAG9E,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/index.js
CHANGED
|
@@ -34,6 +34,8 @@ export { Alert } from './Alert.js';
|
|
|
34
34
|
export { Loader } from './Loader.js';
|
|
35
35
|
export { Badge } from './Badge.js';
|
|
36
36
|
export { ReportButton } from './ReportButton.js';
|
|
37
|
+
export { FollowButton } from './FollowButton.js';
|
|
38
|
+
export { TipButton } from './TipButton.js';
|
|
37
39
|
export { ResourceCard, resourceDisplayName } from './ResourceCard.js';
|
|
38
40
|
export { Modal } from './Modal.js';
|
|
39
41
|
export { Slider } from './Slider.js';
|
package/dist/ui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,oEAAoE;AACpE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGpF,4EAA4E;AAC5E,6EAA6E;AAC7E,mEAAmE;AACnE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG/D,gCAAgC;AAChC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,uBAAuB;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,oEAAoE;AACpE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGpF,4EAA4E;AAC5E,6EAA6E;AAC7E,mEAAmE;AACnE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG/D,gCAAgC;AAChC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,uBAAuB;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civitai/blocks-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "React hooks and iframe transport for Civitai Apps. Pairs with @civitai/app-sdk/blocks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"react-dom": "^19.0.0",
|
|
51
51
|
"typescript": "^5.9.2",
|
|
52
52
|
"vitest": "^4.1.11",
|
|
53
|
-
"@civitai/app-sdk": "^0.
|
|
53
|
+
"@civitai/app-sdk": "^0.39.0"
|
|
54
54
|
},
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|