@linktr.ee/messaging-react 3.10.0-rc-1783660962 → 3.10.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.
Files changed (86) hide show
  1. package/dist/Card-B-L2MST4.js +107 -0
  2. package/dist/Card-B-L2MST4.js.map +1 -0
  3. package/dist/Card-B56QxJW3.cjs +2 -0
  4. package/dist/Card-B56QxJW3.cjs.map +1 -0
  5. package/dist/Card-Bqh-BQaX.js +132 -0
  6. package/dist/Card-Bqh-BQaX.js.map +1 -0
  7. package/dist/Card-ZCE6Lhfc.cjs +2 -0
  8. package/dist/Card-ZCE6Lhfc.cjs.map +1 -0
  9. package/dist/Card-g4JD-2ES.js +163 -0
  10. package/dist/Card-g4JD-2ES.js.map +1 -0
  11. package/dist/Card-wac6AMNc.cjs +2 -0
  12. package/dist/Card-wac6AMNc.cjs.map +1 -0
  13. package/dist/LockedThumbnail-BAQmoIff.js +220 -0
  14. package/dist/LockedThumbnail-BAQmoIff.js.map +1 -0
  15. package/dist/LockedThumbnail-Cr0Gqs3Y.cjs +2 -0
  16. package/dist/LockedThumbnail-Cr0Gqs3Y.cjs.map +1 -0
  17. package/dist/assets/index.css +1 -1
  18. package/dist/index-BPhZyaxT.cjs +2 -0
  19. package/dist/index-BPhZyaxT.cjs.map +1 -0
  20. package/dist/{index-DFZrAiLB.js → index-BxzzWK4K.js} +1249 -1276
  21. package/dist/index-BxzzWK4K.js.map +1 -0
  22. package/dist/index.cjs +1 -1
  23. package/dist/index.d.ts +101 -164
  24. package/dist/index.js +1 -1
  25. package/dist/testing.d.ts +0 -1
  26. package/package.json +2 -2
  27. package/src/components/ChannelList/CustomChannelPreview.test.tsx +0 -95
  28. package/src/components/ChannelList/CustomChannelPreview.tsx +9 -21
  29. package/src/components/CustomMessage/CustomMessage.stories.tsx +1 -54
  30. package/src/components/CustomMessage/MessageTag.tsx +0 -8
  31. package/src/components/CustomMessage/context.tsx +1 -1
  32. package/src/components/CustomMessage/index.tsx +42 -76
  33. package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +3 -5
  34. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +450 -0
  35. package/src/components/LockedAttachment/components/Composer/Card.tsx +221 -0
  36. package/src/components/LockedAttachment/components/Composer/index.ts +2 -0
  37. package/src/components/LockedAttachment/components/Received/Card.tsx +191 -0
  38. package/src/components/{CustomMessage/LockedAttachment/components/Media → LockedAttachment/components/Received}/CardActions.tsx +2 -9
  39. package/src/components/LockedAttachment/components/Received/index.ts +2 -0
  40. package/src/components/LockedAttachment/components/Sent/Card.tsx +177 -0
  41. package/src/components/LockedAttachment/components/Sent/index.ts +2 -0
  42. package/src/components/{CustomMessage/LockedAttachment → LockedAttachment}/components/_shared/CardBody.tsx +7 -8
  43. package/src/components/{CustomMessage/LockedAttachment → LockedAttachment}/components/_shared/GalleryThumbnail.tsx +39 -31
  44. package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +39 -0
  45. package/src/components/{CustomMessage/LockedAttachment/components/_shared/MediaShell.tsx → LockedAttachment/components/_shared/LockedCardShell.tsx} +8 -8
  46. package/src/components/{CustomMessage/LockedAttachment/components/_shared/SingleThumbnail.tsx → LockedAttachment/components/_shared/LockedThumbnail.tsx} +7 -11
  47. package/src/components/LockedAttachment/index.tsx +63 -0
  48. package/src/components/LockedAttachment/types.ts +51 -0
  49. package/src/index.ts +8 -3
  50. package/src/stream-custom-data.ts +0 -1
  51. package/src/styles.css +0 -12
  52. package/src/types.ts +1 -1
  53. package/dist/Card-415OfRhw.cjs +0 -2
  54. package/dist/Card-415OfRhw.cjs.map +0 -1
  55. package/dist/Card-CyWdUMCB.cjs +0 -2
  56. package/dist/Card-CyWdUMCB.cjs.map +0 -1
  57. package/dist/Card-V5d6qhDT.js +0 -694
  58. package/dist/Card-V5d6qhDT.js.map +0 -1
  59. package/dist/Card-qS17qnN0.js +0 -103
  60. package/dist/Card-qS17qnN0.js.map +0 -1
  61. package/dist/LoadingDots-Ct3WmVlT.js +0 -59
  62. package/dist/LoadingDots-Ct3WmVlT.js.map +0 -1
  63. package/dist/LoadingDots-D9GGphgM.cjs +0 -2
  64. package/dist/LoadingDots-D9GGphgM.cjs.map +0 -1
  65. package/dist/index-Bx_zjNMR.cjs +0 -2
  66. package/dist/index-Bx_zjNMR.cjs.map +0 -1
  67. package/dist/index-DFZrAiLB.js.map +0 -1
  68. package/src/components/CustomMessage/LockedAttachment/LockedAttachment.stories.tsx +0 -541
  69. package/src/components/CustomMessage/LockedAttachment/LockedAttachment.test.tsx +0 -117
  70. package/src/components/CustomMessage/LockedAttachment/components/Media/Card.tsx +0 -358
  71. package/src/components/CustomMessage/LockedAttachment/components/Media/types.ts +0 -79
  72. package/src/components/CustomMessage/LockedAttachment/components/Text/Card.tsx +0 -153
  73. package/src/components/CustomMessage/LockedAttachment/components/Text/types.ts +0 -58
  74. package/src/components/CustomMessage/LockedAttachment/components/_shared/CardThumbnail.tsx +0 -92
  75. package/src/components/CustomMessage/LockedAttachment/components/_shared/LoadingDots.tsx +0 -36
  76. package/src/components/CustomMessage/LockedAttachment/components/_shared/LockBadge.tsx +0 -63
  77. package/src/components/CustomMessage/LockedAttachment/components/_shared/PreviewToggle.tsx +0 -58
  78. package/src/components/CustomMessage/LockedAttachment/components/_shared/PriceLockRow.tsx +0 -21
  79. package/src/components/CustomMessage/LockedAttachment/components/_shared/PurchaseStatusBadge.tsx +0 -74
  80. package/src/components/CustomMessage/LockedAttachment/components/_shared/SkeletonTextLines.tsx +0 -29
  81. package/src/components/CustomMessage/LockedAttachment/components/_shared/TextShell.tsx +0 -28
  82. package/src/components/CustomMessage/LockedAttachment/components/_shared/usePreviewToggle.test.ts +0 -67
  83. package/src/components/CustomMessage/LockedAttachment/components/_shared/usePreviewToggle.ts +0 -89
  84. package/src/components/CustomMessage/LockedAttachment/components/_shared/useReceivedSource.ts +0 -83
  85. package/src/components/CustomMessage/LockedAttachment/index.tsx +0 -105
  86. package/src/components/CustomMessage/LockedAttachment/types.ts +0 -44
@@ -1,358 +0,0 @@
1
- import { PencilSimpleIcon, XIcon } from '@phosphor-icons/react'
2
- import React from 'react'
3
-
4
- import { getSourceType } from '../../../../AttachmentCard'
5
- import CardBody from '../_shared/CardBody'
6
- import CardThumbnail, {
7
- galleryBodyIcon,
8
- isGalleryCard,
9
- } from '../_shared/CardThumbnail'
10
- import MediaShell from '../_shared/MediaShell'
11
- import PreviewToggle from '../_shared/PreviewToggle'
12
- import PurchaseStatusBadge from '../_shared/PurchaseStatusBadge'
13
- import { usePreviewToggle } from '../_shared/usePreviewToggle'
14
- import { useReceivedSource } from '../_shared/useReceivedSource'
15
-
16
- import ReceivedCardActions from './CardActions'
17
- import type {
18
- MediaCardDraftProps,
19
- MediaCardOtherProps,
20
- MediaCardOwnProps,
21
- MediaCardPreviewProps,
22
- MediaCardProps,
23
- } from './types'
24
-
25
- export type { MediaCardProps } from './types'
26
-
27
- /**
28
- * Media card while still drafting — the locked thumbnail (single or gallery)
29
- * the composer taps to preview the attachment they're about to send, plus
30
- * the title/price body with optional dismiss X and edit pencil.
31
- */
32
- const DraftMediaCard: React.FC<MediaCardDraftProps> = ({
33
- title,
34
- mimeType = 'application/octet-stream',
35
- thumbnailUrl,
36
- detail,
37
- amountText,
38
- placeholderTitle,
39
- placeholderAmountText,
40
- gallery = [],
41
- onDismiss,
42
- onPreviewClick,
43
- onFetchSource,
44
- onEditClick,
45
- }) => {
46
- const isGallery = isGalleryCard(gallery)
47
-
48
- // Gallery is always previewable in the composer because each item's source
49
- // is already provided up-front via `gallery[*].sourceUrl`.
50
- const { source, isPreviewVisible, isLoadingPreview, togglePreview } =
51
- usePreviewToggle({ isGallery, onFetchSource, onPreviewClick })
52
-
53
- const showLocked = !isPreviewVisible
54
- const isBusy = isLoadingPreview
55
-
56
- const statusBadge = (
57
- <React.Fragment>
58
- <span className="text-xs font-medium text-white/55">&bull;</span>
59
- <span
60
- className={
61
- amountText
62
- ? 'text-xs font-medium text-white/55'
63
- : 'text-xs font-medium text-white/30'
64
- }
65
- >
66
- {amountText || placeholderAmountText}
67
- </span>
68
- </React.Fragment>
69
- )
70
-
71
- const dismissButton = onDismiss ? (
72
- <button
73
- type="button"
74
- onClick={(e) => {
75
- // Stop the click from bubbling up into the outer preview-toggle
76
- // wrapper when both are wired up at the same time.
77
- e.stopPropagation()
78
- onDismiss()
79
- }}
80
- className="flex size-6 items-center justify-center rounded-full bg-[#121110] text-white"
81
- aria-label="Dismiss attachment"
82
- >
83
- <XIcon className="size-3" weight="bold" />
84
- </button>
85
- ) : undefined
86
-
87
- const editButton = onEditClick ? (
88
- <button
89
- type="button"
90
- onClick={onEditClick}
91
- aria-label="Edit attachment"
92
- className="flex size-10 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15"
93
- >
94
- <PencilSimpleIcon className="size-5" weight="regular" />
95
- </button>
96
- ) : undefined
97
-
98
- return (
99
- <MediaShell variant="dark">
100
- <PreviewToggle
101
- onToggle={togglePreview}
102
- isBusy={isBusy}
103
- isPreviewVisible={!showLocked}
104
- >
105
- <CardThumbnail
106
- variant="dark"
107
- gallery={gallery}
108
- title={title}
109
- showLocked={showLocked}
110
- mimeType={mimeType}
111
- thumbnailUrl={thumbnailUrl}
112
- source={source}
113
- topRight={dismissButton}
114
- />
115
- </PreviewToggle>
116
-
117
- <CardBody
118
- variant="dark"
119
- title={title}
120
- placeholderTitle={placeholderTitle}
121
- mimeType={mimeType}
122
- detail={detail}
123
- statusBadge={statusBadge}
124
- icon={isGallery ? galleryBodyIcon('dark') : undefined}
125
- trailingAction={editButton}
126
- />
127
- </MediaShell>
128
- )
129
- }
130
-
131
- /**
132
- * Static mockup of what the recipient will see before purchase — always
133
- * locked/blurred (the lock badge renders from `showLocked` alone, no toggle
134
- * wiring needed), no interactivity, no real content revealed.
135
- */
136
- const PreviewMediaCard: React.FC<MediaCardPreviewProps> = ({
137
- title,
138
- mimeType = 'application/octet-stream',
139
- thumbnailUrl,
140
- detail,
141
- amountText,
142
- placeholderTitle,
143
- placeholderAmountText,
144
- gallery = [],
145
- }) => {
146
- const isGallery = isGalleryCard(gallery)
147
-
148
- const statusBadge = (
149
- <React.Fragment>
150
- <span className="text-xs font-medium text-black/55">&bull;</span>
151
- <span
152
- className={
153
- amountText
154
- ? 'text-xs font-medium text-black/55'
155
- : 'text-xs font-medium text-black/30'
156
- }
157
- >
158
- {amountText || placeholderAmountText}
159
- </span>
160
- </React.Fragment>
161
- )
162
-
163
- return (
164
- <MediaShell variant="light">
165
- <CardThumbnail
166
- variant="light"
167
- gallery={gallery}
168
- title={title}
169
- showLocked
170
- mimeType={mimeType}
171
- thumbnailUrl={thumbnailUrl}
172
- />
173
-
174
- <CardBody
175
- variant="light"
176
- title={title}
177
- placeholderTitle={placeholderTitle}
178
- mimeType={mimeType}
179
- detail={detail}
180
- statusBadge={statusBadge}
181
- icon={isGallery ? galleryBodyIcon('light') : undefined}
182
- />
183
- </MediaShell>
184
- )
185
- }
186
- /**
187
- * The sender's ('mine') view of a paid media attachment: the locked
188
- * thumbnail (single or gallery) they can tap to preview their own
189
- * attachment, plus the title/purchase-status body.
190
- */
191
- const OwnMediaCard: React.FC<MediaCardOwnProps> = ({
192
- title,
193
- mimeType = 'application/octet-stream',
194
- thumbnailUrl,
195
- detail,
196
- amountText,
197
- placeholderTitle,
198
- paymentStatus = 'pending',
199
- gallery = [],
200
- onPreviewClick,
201
- onFetchSource,
202
- }) => {
203
- const isGallery = isGalleryCard(gallery)
204
-
205
- // Gallery is always previewable for the sender because each item's source
206
- // is already provided up-front via `gallery[*].sourceUrl`.
207
- const { source, isPreviewVisible, isLoadingPreview, togglePreview } =
208
- usePreviewToggle({ isGallery, onFetchSource, onPreviewClick })
209
-
210
- const showLocked = !isPreviewVisible
211
- const isBusy = isLoadingPreview
212
-
213
- return (
214
- <MediaShell variant="dark">
215
- <PreviewToggle
216
- onToggle={togglePreview}
217
- isBusy={isBusy}
218
- isPreviewVisible={!showLocked}
219
- >
220
- <CardThumbnail
221
- variant="dark"
222
- gallery={gallery}
223
- title={title}
224
- showLocked={showLocked}
225
- paymentStatus={paymentStatus}
226
- mimeType={mimeType}
227
- thumbnailUrl={thumbnailUrl}
228
- source={source}
229
- />
230
- </PreviewToggle>
231
-
232
- <CardBody
233
- variant="dark"
234
- title={title}
235
- placeholderTitle={placeholderTitle}
236
- mimeType={mimeType}
237
- detail={detail}
238
- statusBadge={
239
- <PurchaseStatusBadge
240
- isMine={true}
241
- paymentStatus={paymentStatus}
242
- amountText={amountText}
243
- />
244
- }
245
- icon={isGallery ? galleryBodyIcon('dark') : undefined}
246
- />
247
- </MediaShell>
248
- )
249
- }
250
-
251
- /**
252
- * The recipient's ('other') view of a paid media attachment: the locked
253
- * thumbnail (single or gallery) with an Unlock/Download CTA. The source is
254
- * revealed by fetching once — on click when already paid, or automatically
255
- * the first time a paid card scrolls into view.
256
- */
257
- const OtherMediaCard: React.FC<MediaCardOtherProps> = ({
258
- title,
259
- amountText,
260
- thumbnailUrl,
261
- mimeType = 'application/octet-stream',
262
- detail,
263
- gallery = [],
264
- onUnlockClick,
265
- onFetchSource,
266
- onDownloadClick,
267
- paymentStatus,
268
- isUnlocking = false,
269
- }) => {
270
- const isGallery = isGalleryCard(gallery)
271
-
272
- const { source, cardRef, handleUnlockClick } = useReceivedSource({
273
- paymentStatus,
274
- onFetchSource,
275
- onUnlockClick,
276
- })
277
-
278
- const effectiveSourceUrl = source?.sourceUrl
279
- const effectiveRedeemUrl = source?.redeemUrl
280
-
281
- // For gallery, the per-item sources are carried by `gallery` itself, so
282
- // the lock state is driven by paymentStatus rather than the single
283
- // `effectiveSourceUrl`. Everything else still respects the source fetch.
284
- const isLocked = isGallery
285
- ? paymentStatus !== 'paid'
286
- : effectiveSourceUrl === undefined
287
- const sourceType = getSourceType(mimeType)
288
-
289
- // For gallery, ReceivedCardActions falls back to a plain <button>
290
- // (no anchor) because the gallery has no single sourceUrl to link to —
291
- // the per-item sources live on `gallery` itself.
292
- const actionSourceUrl = isGallery ? undefined : effectiveSourceUrl
293
-
294
- return (
295
- <MediaShell
296
- variant="light"
297
- rootRef={cardRef}
298
- data-testid="locked-attachment"
299
- >
300
- <CardThumbnail
301
- variant="light"
302
- gallery={gallery}
303
- title={title}
304
- showLocked={isLocked}
305
- paymentStatus={paymentStatus}
306
- mimeType={mimeType}
307
- thumbnailUrl={thumbnailUrl}
308
- source={source}
309
- containedImage={!isLocked && sourceType === 'document'}
310
- />
311
-
312
- <CardBody
313
- variant="light"
314
- title={title}
315
- mimeType={mimeType}
316
- detail={detail}
317
- statusBadge={
318
- <PurchaseStatusBadge
319
- isMine={false}
320
- paymentStatus={paymentStatus}
321
- amountText={amountText}
322
- />
323
- }
324
- icon={isGallery ? galleryBodyIcon('light') : undefined}
325
- action={
326
- <ReceivedCardActions
327
- isLocked={isLocked}
328
- isUnlocking={isUnlocking}
329
- sourceUrl={actionSourceUrl}
330
- redeemUrl={effectiveRedeemUrl}
331
- onUnlockClicked={handleUnlockClick}
332
- onDownloadClicked={onDownloadClick}
333
- />
334
- }
335
- />
336
- </MediaShell>
337
- )
338
- }
339
-
340
- /**
341
- * The card either party sees for a paid media attachment. `isDraft`/`isPreview`
342
- * select the composer's own drafting view / a static receiver-side mockup;
343
- * otherwise `isMine` selects the sender's sent view vs. the recipient's
344
- * unlock/download view.
345
- */
346
- const MediaCard: React.FC<MediaCardProps> = (props) => {
347
- if (props.isDraft) {
348
- return <DraftMediaCard {...props} />
349
- } else if (props.isPreview) {
350
- return <PreviewMediaCard {...props} />
351
- } else if (props.isMine) {
352
- return <OwnMediaCard {...props} />
353
- } else {
354
- return <OtherMediaCard {...props} />
355
- }
356
- }
357
-
358
- export default MediaCard
@@ -1,79 +0,0 @@
1
- import type {
2
- FetchSourceHandler,
3
- LockedAttachmentGalleryItem,
4
- PaymentStatus,
5
- } from '../../types'
6
-
7
- interface MediaCardBaseProps {
8
- /** Attachment title. */
9
- title?: string
10
- /** Fallback shown when `title` is unset. */
11
- placeholderTitle?: string
12
- /** Drives icon/thumbnail rendering. */
13
- mimeType?: string
14
- /** Poster image, locked or unlocked. */
15
- thumbnailUrl?: string
16
- /** Secondary description text. */
17
- detail?: string
18
- /** Amount label. */
19
- amountText?: string
20
- /** 2+ items renders as a carousel instead of a single thumbnail. */
21
- gallery?: LockedAttachmentGalleryItem[]
22
- }
23
-
24
- export interface MediaCardDraftProps extends MediaCardBaseProps {
25
- /** True while composing — shows the real (unredacted) thumbnail. */
26
- isDraft: true
27
- isPreview?: false
28
- /** Fallback amount before a price is set. */
29
- placeholderAmountText?: string
30
- /** Lazily fetches the source on first tap; cached and reused after. */
31
- onFetchSource?: FetchSourceHandler
32
- /** Fired on first tap to preview. */
33
- onPreviewClick?: () => void
34
- /** Removes the draft attachment. */
35
- onDismiss?: () => void
36
- /** Renders a pencil button to edit the attachment metadata. */
37
- onEditClick?: () => void
38
- }
39
-
40
- export interface MediaCardPreviewProps extends MediaCardBaseProps {
41
- /** True to render exactly as the recipient will see it before purchase — always locked/blurred. */
42
- isDraft?: false
43
- isPreview: true
44
- /** Fallback amount before a price is set. */
45
- placeholderAmountText?: string
46
- }
47
-
48
- interface MediaCardSentBaseProps extends MediaCardBaseProps {
49
- /** False once sent. */
50
- isDraft?: false
51
- isPreview?: false
52
- /** Drives the lock/skeleton/badge state. */
53
- paymentStatus?: PaymentStatus
54
- onFetchSource?: FetchSourceHandler
55
- }
56
-
57
- export type MediaCardOwnProps = MediaCardSentBaseProps & {
58
- /** Sender's own view. */
59
- isMine: true
60
- /** Fired on first tap to preview own attachment. */
61
- onPreviewClick?: () => void
62
- }
63
-
64
- export type MediaCardOtherProps = MediaCardSentBaseProps & {
65
- /** Recipient's view. */
66
- isMine: false
67
- /** Opens checkout on click. Omit to hide the button. */
68
- onUnlockClick?: () => void
69
- /** Omit to hide the Download button. */
70
- onDownloadClick?: () => void
71
- /** Spinner on Unlock while checkout runs. */
72
- isUnlocking?: boolean
73
- }
74
-
75
- export type MediaCardProps =
76
- | MediaCardDraftProps
77
- | MediaCardPreviewProps
78
- | MediaCardOwnProps
79
- | MediaCardOtherProps
@@ -1,153 +0,0 @@
1
- import { LockSimpleIcon, XIcon } from '@phosphor-icons/react'
2
- import type React from 'react'
3
-
4
- import LoadingDots from '../_shared/LoadingDots'
5
- import PriceLockRow from '../_shared/PriceLockRow'
6
- import PurchaseStatusBadge from '../_shared/PurchaseStatusBadge'
7
- import SkeletonTextLines from '../_shared/SkeletonTextLines'
8
- import TextShell from '../_shared/TextShell'
9
-
10
- import type {
11
- TextCardDraftProps,
12
- TextCardOtherProps,
13
- TextCardOwnProps,
14
- TextCardPreviewProps,
15
- TextCardProps,
16
- } from './types'
17
-
18
- export type { TextCardProps } from './types'
19
-
20
- const DraftTextCard: React.FC<TextCardDraftProps> = ({
21
- renderedText,
22
- amountText,
23
- placeholderAmountText,
24
- onDismiss,
25
- }) => (
26
- <TextShell isMine className="pr-8">
27
- {renderedText || <SkeletonTextLines variant="dark" />}
28
- <PriceLockRow>
29
- <span className={amountText ? 'text-white/55' : 'text-white/30'}>
30
- {amountText || placeholderAmountText}
31
- </span>
32
- </PriceLockRow>
33
- {onDismiss && (
34
- <button
35
- type="button"
36
- onClick={onDismiss}
37
- className="absolute right-3 top-3 flex size-6 shrink-0 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15"
38
- aria-label="Dismiss attachment"
39
- >
40
- <XIcon className="size-3" weight="bold" />
41
- </button>
42
- )}
43
- </TextShell>
44
- )
45
-
46
- const PreviewTextCard: React.FC<TextCardPreviewProps> = ({
47
- amountText,
48
- placeholderAmountText,
49
- }) => (
50
- <TextShell isMine={false}>
51
- <SkeletonTextLines variant="light" />
52
- <PriceLockRow>
53
- <span className="opacity-55">{amountText || placeholderAmountText}</span>
54
- </PriceLockRow>
55
- </TextShell>
56
- )
57
-
58
- const OwnTextCard: React.FC<TextCardOwnProps> = ({
59
- renderedText,
60
- amountText,
61
- paymentStatus = 'pending',
62
- }) => {
63
- const isPaid = paymentStatus === 'paid'
64
-
65
- return (
66
- <TextShell isMine>
67
- {isPaid && renderedText ? (
68
- renderedText
69
- ) : (
70
- <SkeletonTextLines variant="dark" />
71
- )}
72
- <PriceLockRow>
73
- {amountText && <span className="text-white/55">{amountText}</span>}
74
- <PurchaseStatusBadge isMine paymentStatus={paymentStatus} />
75
- </PriceLockRow>
76
- </TextShell>
77
- )
78
- }
79
-
80
- const OtherTextCard: React.FC<TextCardOtherProps> = ({
81
- renderedText,
82
- amountText,
83
- paymentStatus,
84
- onUnlockClick,
85
- isUnlocking = false,
86
- }) => {
87
- const isPaid = paymentStatus === 'paid'
88
-
89
- if (isPaid && renderedText) {
90
- return (
91
- <TextShell isMine={false}>
92
- {renderedText}
93
- <PriceLockRow>
94
- {amountText != null && (
95
- <span className="opacity-55">{amountText}</span>
96
- )}
97
- <PurchaseStatusBadge isMine={false} paymentStatus="paid" />
98
- </PriceLockRow>
99
- </TextShell>
100
- )
101
- }
102
-
103
- const ariaLabel = isUnlocking
104
- ? 'Unlocking message.'
105
- : 'Unlock this message.'
106
-
107
- return (
108
- <TextShell isMine={false}>
109
- <div className="flex items-center gap-3">
110
- <div className="flex flex-1 flex-col gap-2">
111
- <SkeletonTextLines variant="light" />
112
- <span className="sr-only" aria-live="polite">
113
- {ariaLabel}
114
- </span>
115
- <PriceLockRow>
116
- {amountText != null && (
117
- <span className="opacity-55">{amountText}</span>
118
- )}
119
- </PriceLockRow>
120
- </div>
121
- <button
122
- type="button"
123
- onClick={onUnlockClick}
124
- disabled={isUnlocking}
125
- aria-label={isUnlocking ? 'Unlocking message' : 'Unlock message'}
126
- aria-busy={isUnlocking}
127
- title={isUnlocking ? 'Unlocking message' : 'Unlock message'}
128
- className="flex size-10 shrink-0 items-center justify-center rounded-full bg-[#121110] text-white hover:bg-[#2a2928]"
129
- >
130
- {isUnlocking ? (
131
- <LoadingDots />
132
- ) : (
133
- <LockSimpleIcon className="size-4" weight="fill" aria-hidden />
134
- )}
135
- </button>
136
- </div>
137
- </TextShell>
138
- )
139
- }
140
-
141
- const TextCard: React.FC<TextCardProps> = (props) => {
142
- if (props.isDraft) {
143
- return <DraftTextCard {...props} />
144
- } else if (props.isPreview) {
145
- return <PreviewTextCard {...props} />
146
- } else if (props.isMine) {
147
- return <OwnTextCard {...props} />
148
- } else {
149
- return <OtherTextCard {...props} />
150
- }
151
- }
152
-
153
- export default TextCard
@@ -1,58 +0,0 @@
1
- import type React from 'react'
2
-
3
- import type { PaymentStatus } from '../../types'
4
-
5
- interface TextCardBaseProps {
6
- /** Amount label. */
7
- amountText?: string
8
- }
9
-
10
- export interface TextCardDraftProps extends TextCardBaseProps {
11
- /** True while composing — shows the real draft text. */
12
- isDraft: true
13
- isPreview?: false
14
- /** Real draft content, not yet redacted. */
15
- renderedText?: React.ReactNode
16
- /** Fallback amount before a price is set. */
17
- placeholderAmountText?: string
18
- /** Removes the draft attachment. */
19
- onDismiss?: () => void
20
- }
21
-
22
- export interface TextCardPreviewProps extends TextCardBaseProps {
23
- /** True to render exactly as the recipient will see it before purchase — skeleton lines only. */
24
- isDraft?: false
25
- isPreview: true
26
- /** Fallback amount before a price is set. */
27
- placeholderAmountText?: string
28
- }
29
-
30
- interface TextCardSentBaseProps extends TextCardBaseProps {
31
- /** False once sent. */
32
- isDraft?: false
33
- isPreview?: false
34
- /** Revealed content once paid. */
35
- renderedText?: React.ReactNode
36
- /** Drives the lock/skeleton/badge state. */
37
- paymentStatus?: PaymentStatus
38
- }
39
-
40
- export type TextCardOwnProps = TextCardSentBaseProps & {
41
- /** Sender's own view. */
42
- isMine: true
43
- }
44
-
45
- export type TextCardOtherProps = TextCardSentBaseProps & {
46
- /** Recipient's view. */
47
- isMine: false
48
- /** Spinner on Unlock while checkout runs. */
49
- isUnlocking?: boolean
50
- /** Opens checkout on click. Omit to hide the button. */
51
- onUnlockClick?: () => void
52
- }
53
-
54
- export type TextCardProps =
55
- | TextCardDraftProps
56
- | TextCardPreviewProps
57
- | TextCardOwnProps
58
- | TextCardOtherProps