@linktr.ee/messaging-react 3.10.0 → 3.11.0-rc-1783756377
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/dist/Card-415OfRhw.cjs +2 -0
- package/dist/Card-415OfRhw.cjs.map +1 -0
- package/dist/Card-BnbNynLQ.js +694 -0
- package/dist/Card-BnbNynLQ.js.map +1 -0
- package/dist/Card-UnwKmYzt.cjs +2 -0
- package/dist/Card-UnwKmYzt.cjs.map +1 -0
- package/dist/Card-qS17qnN0.js +103 -0
- package/dist/Card-qS17qnN0.js.map +1 -0
- package/dist/LoadingDots-Ct3WmVlT.js +59 -0
- package/dist/LoadingDots-Ct3WmVlT.js.map +1 -0
- package/dist/LoadingDots-D9GGphgM.cjs +2 -0
- package/dist/LoadingDots-D9GGphgM.cjs.map +1 -0
- package/dist/assets/index.css +1 -1
- package/dist/index-CyHyftBi.cjs +2 -0
- package/dist/index-CyHyftBi.cjs.map +1 -0
- package/dist/{index-BxzzWK4K.js → index-DJXjS2H9.js} +1397 -1358
- package/dist/index-DJXjS2H9.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +196 -101
- package/dist/index.js +1 -1
- package/dist/testing.d.ts +1 -0
- package/package.json +2 -2
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +95 -0
- package/src/components/ChannelList/CustomChannelPreview.tsx +21 -9
- package/src/components/ChannelList/index.test.tsx +46 -0
- package/src/components/ChannelList/index.tsx +12 -0
- package/src/components/CustomMessage/CustomMessage.stories.tsx +54 -1
- package/src/components/CustomMessage/LockedAttachment/LockedAttachment.stories.tsx +541 -0
- package/src/components/CustomMessage/LockedAttachment/LockedAttachment.test.tsx +117 -0
- package/src/components/CustomMessage/LockedAttachment/components/Media/Card.tsx +358 -0
- package/src/components/{LockedAttachment/components/Received → CustomMessage/LockedAttachment/components/Media}/CardActions.tsx +9 -2
- package/src/components/CustomMessage/LockedAttachment/components/Media/types.ts +79 -0
- package/src/components/CustomMessage/LockedAttachment/components/Text/Card.tsx +153 -0
- package/src/components/CustomMessage/LockedAttachment/components/Text/types.ts +58 -0
- package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/CardBody.tsx +8 -7
- package/src/components/CustomMessage/LockedAttachment/components/_shared/CardThumbnail.tsx +92 -0
- package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/GalleryThumbnail.tsx +31 -39
- package/src/components/CustomMessage/LockedAttachment/components/_shared/LoadingDots.tsx +36 -0
- package/src/components/CustomMessage/LockedAttachment/components/_shared/LockBadge.tsx +63 -0
- package/src/components/{LockedAttachment/components/_shared/LockedCardShell.tsx → CustomMessage/LockedAttachment/components/_shared/MediaShell.tsx} +8 -8
- package/src/components/CustomMessage/LockedAttachment/components/_shared/PreviewToggle.tsx +58 -0
- package/src/components/CustomMessage/LockedAttachment/components/_shared/PriceLockRow.tsx +21 -0
- package/src/components/CustomMessage/LockedAttachment/components/_shared/PurchaseStatusBadge.tsx +74 -0
- package/src/components/{LockedAttachment/components/_shared/LockedThumbnail.tsx → CustomMessage/LockedAttachment/components/_shared/SingleThumbnail.tsx} +11 -7
- package/src/components/CustomMessage/LockedAttachment/components/_shared/SkeletonTextLines.tsx +29 -0
- package/src/components/CustomMessage/LockedAttachment/components/_shared/TextShell.tsx +28 -0
- package/src/components/CustomMessage/LockedAttachment/components/_shared/usePreviewToggle.test.ts +67 -0
- package/src/components/CustomMessage/LockedAttachment/components/_shared/usePreviewToggle.ts +89 -0
- package/src/components/CustomMessage/LockedAttachment/components/_shared/useReceivedSource.ts +83 -0
- package/src/components/CustomMessage/LockedAttachment/index.tsx +105 -0
- package/src/components/CustomMessage/LockedAttachment/types.ts +44 -0
- package/src/components/CustomMessage/MessageTag.tsx +8 -0
- package/src/components/CustomMessage/context.tsx +1 -1
- package/src/components/CustomMessage/index.tsx +76 -42
- package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +5 -3
- package/src/index.ts +3 -8
- package/src/stream-custom-data.ts +1 -0
- package/src/styles.css +12 -0
- package/src/types.ts +35 -2
- package/dist/Card-B-L2MST4.js +0 -107
- package/dist/Card-B-L2MST4.js.map +0 -1
- package/dist/Card-B56QxJW3.cjs +0 -2
- package/dist/Card-B56QxJW3.cjs.map +0 -1
- package/dist/Card-Bqh-BQaX.js +0 -132
- package/dist/Card-Bqh-BQaX.js.map +0 -1
- package/dist/Card-ZCE6Lhfc.cjs +0 -2
- package/dist/Card-ZCE6Lhfc.cjs.map +0 -1
- package/dist/Card-g4JD-2ES.js +0 -163
- package/dist/Card-g4JD-2ES.js.map +0 -1
- package/dist/Card-wac6AMNc.cjs +0 -2
- package/dist/Card-wac6AMNc.cjs.map +0 -1
- package/dist/LockedThumbnail-BAQmoIff.js +0 -220
- package/dist/LockedThumbnail-BAQmoIff.js.map +0 -1
- package/dist/LockedThumbnail-Cr0Gqs3Y.cjs +0 -2
- package/dist/LockedThumbnail-Cr0Gqs3Y.cjs.map +0 -1
- package/dist/index-BPhZyaxT.cjs +0 -2
- package/dist/index-BPhZyaxT.cjs.map +0 -1
- package/dist/index-BxzzWK4K.js.map +0 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +0 -450
- package/src/components/LockedAttachment/components/Composer/Card.tsx +0 -221
- package/src/components/LockedAttachment/components/Composer/index.ts +0 -2
- package/src/components/LockedAttachment/components/Received/Card.tsx +0 -191
- package/src/components/LockedAttachment/components/Received/index.ts +0 -2
- package/src/components/LockedAttachment/components/Sent/Card.tsx +0 -177
- package/src/components/LockedAttachment/components/Sent/index.ts +0 -2
- package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +0 -39
- package/src/components/LockedAttachment/index.tsx +0 -63
- package/src/components/LockedAttachment/types.ts +0 -51
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { CheckCircleIcon, ImagesIcon } from '@phosphor-icons/react'
|
|
2
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
3
|
-
|
|
4
|
-
import { getSourceType } from '../../../AttachmentCard'
|
|
5
|
-
import type {
|
|
6
|
-
LockedAttachmentBaseProps,
|
|
7
|
-
LockedAttachmentSource,
|
|
8
|
-
} from '../../types'
|
|
9
|
-
import CardBody from '../_shared/CardBody'
|
|
10
|
-
import GalleryThumbnail from '../_shared/GalleryThumbnail'
|
|
11
|
-
import LockedCardShell from '../_shared/LockedCardShell'
|
|
12
|
-
import LockedThumbnail from '../_shared/LockedThumbnail'
|
|
13
|
-
|
|
14
|
-
import ReceivedCardActions from './CardActions'
|
|
15
|
-
|
|
16
|
-
export interface ReceivedCardProps extends LockedAttachmentBaseProps {
|
|
17
|
-
/**
|
|
18
|
-
* Called when the recipient clicks Unlock on an unpaid attachment.
|
|
19
|
-
* Use this to open a checkout flow. Omit to hide the Unlock button.
|
|
20
|
-
*/
|
|
21
|
-
onUnlockClick?: () => void
|
|
22
|
-
/**
|
|
23
|
-
* Called to fetch the attachment source — fired automatically when
|
|
24
|
-
* `paymentStatus` transitions to `'paid'`, or immediately on click when
|
|
25
|
-
* `paymentStatus` is already `'paid'`. Return a `LockedAttachmentSource`
|
|
26
|
-
* to unlock the card.
|
|
27
|
-
*/
|
|
28
|
-
onFetchSource?: () => Promise<LockedAttachmentSource | void>
|
|
29
|
-
/**
|
|
30
|
-
* Called when the recipient clicks Download on an unlocked card.
|
|
31
|
-
* Omit to hide the Download button.
|
|
32
|
-
*/
|
|
33
|
-
onDownloadClick?: () => void
|
|
34
|
-
/**
|
|
35
|
-
* When true, shows a loading spinner on the Unlock button.
|
|
36
|
-
* Driven by the LockedAttachmentContext (e.g. checkout in progress).
|
|
37
|
-
*/
|
|
38
|
-
isUnlocking?: boolean
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* The card the recipient sees in chat for a paid attachment.
|
|
43
|
-
* Matches the Received column of the messaging design system in Figma:
|
|
44
|
-
* locked → blurred thumbnail + Unlock CTA, unlocked → clear image + Download CTA.
|
|
45
|
-
*/
|
|
46
|
-
const ReceivedCard: React.FC<ReceivedCardProps> = ({
|
|
47
|
-
title,
|
|
48
|
-
amountText,
|
|
49
|
-
thumbnailUrl,
|
|
50
|
-
mimeType = 'application/octet-stream',
|
|
51
|
-
detail,
|
|
52
|
-
gallery,
|
|
53
|
-
onUnlockClick,
|
|
54
|
-
onFetchSource,
|
|
55
|
-
onDownloadClick,
|
|
56
|
-
paymentStatus,
|
|
57
|
-
isUnlocking = false,
|
|
58
|
-
}) => {
|
|
59
|
-
const isGallery = (gallery?.length ?? 0) >= 2
|
|
60
|
-
const [source, setSource] = useState<LockedAttachmentSource | undefined>()
|
|
61
|
-
|
|
62
|
-
const cardRef = useRef<HTMLDivElement>(null)
|
|
63
|
-
const fetchingRef = useRef(false)
|
|
64
|
-
|
|
65
|
-
const onFetchSourceRef = useRef(onFetchSource)
|
|
66
|
-
onFetchSourceRef.current = onFetchSource
|
|
67
|
-
|
|
68
|
-
const effectiveSourceUrl = source?.sourceUrl
|
|
69
|
-
const effectiveRedeemUrl = source?.redeemUrl
|
|
70
|
-
|
|
71
|
-
const fetchSource = useCallback(async (): Promise<void> => {
|
|
72
|
-
if (fetchingRef.current) return
|
|
73
|
-
fetchingRef.current = true
|
|
74
|
-
try {
|
|
75
|
-
const result = await onFetchSourceRef.current?.()
|
|
76
|
-
if (result) setSource(result)
|
|
77
|
-
} finally {
|
|
78
|
-
fetchingRef.current = false
|
|
79
|
-
}
|
|
80
|
-
}, [])
|
|
81
|
-
|
|
82
|
-
const handleUnlockClick = useCallback(() => {
|
|
83
|
-
if (paymentStatus === 'paid') {
|
|
84
|
-
void fetchSource()
|
|
85
|
-
} else {
|
|
86
|
-
onUnlockClick?.()
|
|
87
|
-
}
|
|
88
|
-
}, [paymentStatus, fetchSource, onUnlockClick])
|
|
89
|
-
|
|
90
|
-
// Auto-fetch the source once the paid card scrolls into view.
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (!cardRef.current) return
|
|
93
|
-
if (paymentStatus !== 'paid' || source !== undefined) return
|
|
94
|
-
|
|
95
|
-
const observer = new IntersectionObserver(
|
|
96
|
-
([entry]) => {
|
|
97
|
-
if (entry.isIntersecting) {
|
|
98
|
-
void fetchSource()
|
|
99
|
-
observer.disconnect()
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
{ threshold: 1.0 }
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
observer.observe(cardRef.current)
|
|
106
|
-
return () => observer.disconnect()
|
|
107
|
-
}, [paymentStatus, source, fetchSource])
|
|
108
|
-
|
|
109
|
-
// For gallery, the per-item sources are carried by `gallery` itself, so
|
|
110
|
-
// the lock state is driven by paymentStatus rather than the single
|
|
111
|
-
// `effectiveSourceUrl`. Everything else still respects the source fetch.
|
|
112
|
-
const isLocked = isGallery
|
|
113
|
-
? paymentStatus !== 'paid'
|
|
114
|
-
: effectiveSourceUrl === undefined
|
|
115
|
-
const sourceType = getSourceType(mimeType)
|
|
116
|
-
|
|
117
|
-
const statusBadge =
|
|
118
|
-
paymentStatus === 'paid' ? (
|
|
119
|
-
<React.Fragment>
|
|
120
|
-
<span className="text-xs font-medium text-black/55">•</span>
|
|
121
|
-
<span className="text-xs font-medium text-[#008236]">Purchased</span>
|
|
122
|
-
<CheckCircleIcon className="size-4 text-[#008236]" weight="bold" />
|
|
123
|
-
</React.Fragment>
|
|
124
|
-
) : amountText != null ? (
|
|
125
|
-
<React.Fragment>
|
|
126
|
-
<span className="text-xs font-medium text-black/55">•</span>
|
|
127
|
-
<span className="text-xs font-medium text-black/55">{amountText}</span>
|
|
128
|
-
</React.Fragment>
|
|
129
|
-
) : null
|
|
130
|
-
|
|
131
|
-
// For gallery, ReceivedCardActions falls back to a plain <button>
|
|
132
|
-
// (no anchor) because the gallery has no single sourceUrl to link to —
|
|
133
|
-
// the per-item sources live on `gallery` itself.
|
|
134
|
-
const actionSourceUrl = isGallery ? undefined : effectiveSourceUrl
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<LockedCardShell
|
|
138
|
-
variant="light"
|
|
139
|
-
rootRef={cardRef}
|
|
140
|
-
data-testid="locked-attachment"
|
|
141
|
-
>
|
|
142
|
-
{isGallery ? (
|
|
143
|
-
<GalleryThumbnail
|
|
144
|
-
variant="light"
|
|
145
|
-
gallery={gallery!}
|
|
146
|
-
title={title}
|
|
147
|
-
showLocked={isLocked}
|
|
148
|
-
paymentStatus={paymentStatus}
|
|
149
|
-
/>
|
|
150
|
-
) : (
|
|
151
|
-
<LockedThumbnail
|
|
152
|
-
variant="light"
|
|
153
|
-
mimeType={mimeType}
|
|
154
|
-
thumbnailUrl={thumbnailUrl}
|
|
155
|
-
title={title}
|
|
156
|
-
source={source}
|
|
157
|
-
showLocked={isLocked}
|
|
158
|
-
paymentStatus={paymentStatus}
|
|
159
|
-
containedImage={
|
|
160
|
-
!isLocked && (sourceType === 'image' || sourceType === 'document')
|
|
161
|
-
}
|
|
162
|
-
/>
|
|
163
|
-
)}
|
|
164
|
-
|
|
165
|
-
<CardBody
|
|
166
|
-
variant="light"
|
|
167
|
-
title={title}
|
|
168
|
-
mimeType={mimeType}
|
|
169
|
-
detail={detail}
|
|
170
|
-
statusBadge={statusBadge}
|
|
171
|
-
icon={
|
|
172
|
-
isGallery ? (
|
|
173
|
-
<ImagesIcon className="size-5 shrink-0 text-black/55" />
|
|
174
|
-
) : undefined
|
|
175
|
-
}
|
|
176
|
-
action={
|
|
177
|
-
<ReceivedCardActions
|
|
178
|
-
isLocked={isLocked}
|
|
179
|
-
isUnlocking={isUnlocking}
|
|
180
|
-
sourceUrl={actionSourceUrl}
|
|
181
|
-
redeemUrl={effectiveRedeemUrl}
|
|
182
|
-
onUnlockClicked={handleUnlockClick}
|
|
183
|
-
onDownloadClicked={onDownloadClick}
|
|
184
|
-
/>
|
|
185
|
-
}
|
|
186
|
-
/>
|
|
187
|
-
</LockedCardShell>
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export default ReceivedCard
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { CheckCircleIcon, ImagesIcon } from '@phosphor-icons/react'
|
|
2
|
-
import React, { useCallback, useRef, useState } from 'react'
|
|
3
|
-
|
|
4
|
-
import type {
|
|
5
|
-
LockedAttachmentBaseProps,
|
|
6
|
-
LockedAttachmentSource,
|
|
7
|
-
} from '../../types'
|
|
8
|
-
import CardBody from '../_shared/CardBody'
|
|
9
|
-
import GalleryThumbnail from '../_shared/GalleryThumbnail'
|
|
10
|
-
import LockedCardShell from '../_shared/LockedCardShell'
|
|
11
|
-
import LockedThumbnail from '../_shared/LockedThumbnail'
|
|
12
|
-
|
|
13
|
-
export interface SentCardProps extends LockedAttachmentBaseProps {
|
|
14
|
-
/** Placeholder shown in the title slot when no title is set. */
|
|
15
|
-
placeholderTitle?: string
|
|
16
|
-
/** Fired the first time the sender taps the thumbnail to preview their own attachment. */
|
|
17
|
-
onPreviewClick?: () => void
|
|
18
|
-
/**
|
|
19
|
-
* Lazily loads the underlying source so the sender can preview the attachment.
|
|
20
|
-
* Called the first time the thumbnail is tapped; the returned source is cached
|
|
21
|
-
* and reused on subsequent toggles.
|
|
22
|
-
*/
|
|
23
|
-
onFetchSource?: () => Promise<LockedAttachmentSource | void>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The card the sender sees in chat after a paid attachment has been posted.
|
|
28
|
-
* Matches the Sent column of the messaging design system in Figma.
|
|
29
|
-
*/
|
|
30
|
-
const SentCard: React.FC<SentCardProps> = ({
|
|
31
|
-
title,
|
|
32
|
-
mimeType = 'application/octet-stream',
|
|
33
|
-
thumbnailUrl,
|
|
34
|
-
detail,
|
|
35
|
-
amountText,
|
|
36
|
-
placeholderTitle = 'Attachment title',
|
|
37
|
-
paymentStatus,
|
|
38
|
-
gallery,
|
|
39
|
-
onPreviewClick,
|
|
40
|
-
onFetchSource,
|
|
41
|
-
}) => {
|
|
42
|
-
const [source, setSource] = useState<LockedAttachmentSource | undefined>()
|
|
43
|
-
const [isPreviewVisible, setIsPreviewVisible] = useState(false)
|
|
44
|
-
const [isLoadingPreview, setIsLoadingPreview] = useState(false)
|
|
45
|
-
const fetchingRef = useRef(false)
|
|
46
|
-
|
|
47
|
-
const isGallery = (gallery?.length ?? 0) >= 2
|
|
48
|
-
|
|
49
|
-
const handleToggle = useCallback(async () => {
|
|
50
|
-
onPreviewClick?.()
|
|
51
|
-
if (isPreviewVisible) {
|
|
52
|
-
setIsPreviewVisible(false)
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
// Gallery items carry their own per-item sources on the `gallery` prop, so
|
|
56
|
-
// we just flip visibility — no async source fetch is needed.
|
|
57
|
-
if (isGallery) {
|
|
58
|
-
setIsPreviewVisible(true)
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
if (source) {
|
|
62
|
-
setIsPreviewVisible(true)
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
if (!onFetchSource) return
|
|
66
|
-
if (fetchingRef.current) return
|
|
67
|
-
fetchingRef.current = true
|
|
68
|
-
setIsLoadingPreview(true)
|
|
69
|
-
try {
|
|
70
|
-
const result = await onFetchSource()
|
|
71
|
-
if (result) {
|
|
72
|
-
setSource(result)
|
|
73
|
-
setIsPreviewVisible(true)
|
|
74
|
-
}
|
|
75
|
-
} finally {
|
|
76
|
-
fetchingRef.current = false
|
|
77
|
-
setIsLoadingPreview(false)
|
|
78
|
-
}
|
|
79
|
-
}, [isPreviewVisible, isGallery, source, onPreviewClick, onFetchSource])
|
|
80
|
-
|
|
81
|
-
// Gallery is always previewable for the sender because each item's source
|
|
82
|
-
// is already provided up-front via `gallery[*].sourceUrl`.
|
|
83
|
-
const togglePreview =
|
|
84
|
-
isGallery || onFetchSource || onPreviewClick ? handleToggle : undefined
|
|
85
|
-
const showLocked = !isPreviewVisible
|
|
86
|
-
const isBusy = isLoadingPreview
|
|
87
|
-
|
|
88
|
-
const statusBadge =
|
|
89
|
-
paymentStatus === 'paid' ? (
|
|
90
|
-
<React.Fragment>
|
|
91
|
-
<span className="text-xs font-medium text-white/55">•</span>
|
|
92
|
-
<span className="text-xs font-medium text-[#34c759]">Sold</span>
|
|
93
|
-
<CheckCircleIcon className="size-4 text-[#34c759]" weight="bold" />
|
|
94
|
-
</React.Fragment>
|
|
95
|
-
) : amountText ? (
|
|
96
|
-
<React.Fragment>
|
|
97
|
-
<span className="text-xs font-medium text-white/55">•</span>
|
|
98
|
-
<span className="text-xs font-medium text-white/55">{amountText}</span>
|
|
99
|
-
</React.Fragment>
|
|
100
|
-
) : null
|
|
101
|
-
|
|
102
|
-
const thumbnail = isGallery ? (
|
|
103
|
-
<GalleryThumbnail
|
|
104
|
-
variant="dark"
|
|
105
|
-
gallery={gallery!}
|
|
106
|
-
title={title}
|
|
107
|
-
showLocked={showLocked}
|
|
108
|
-
paymentStatus={paymentStatus}
|
|
109
|
-
/>
|
|
110
|
-
) : (
|
|
111
|
-
<LockedThumbnail
|
|
112
|
-
variant="dark"
|
|
113
|
-
mimeType={mimeType}
|
|
114
|
-
thumbnailUrl={thumbnailUrl}
|
|
115
|
-
title={title}
|
|
116
|
-
source={source}
|
|
117
|
-
showLocked={showLocked}
|
|
118
|
-
paymentStatus={paymentStatus}
|
|
119
|
-
/>
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
<LockedCardShell variant="dark">
|
|
124
|
-
{togglePreview ? (
|
|
125
|
-
// Uses a `<div role="button">` rather than a native `<button>` so the
|
|
126
|
-
// gallery's carousel arrow `<button>`s rendered inside `thumbnail`
|
|
127
|
-
// don't produce invalid nested interactive elements.
|
|
128
|
-
<div
|
|
129
|
-
role="button"
|
|
130
|
-
tabIndex={isBusy ? -1 : 0}
|
|
131
|
-
aria-label="Toggle preview"
|
|
132
|
-
aria-busy={isBusy}
|
|
133
|
-
aria-pressed={!showLocked}
|
|
134
|
-
aria-disabled={isBusy || undefined}
|
|
135
|
-
onClick={isBusy ? undefined : togglePreview}
|
|
136
|
-
onKeyDown={(e) => {
|
|
137
|
-
if (isBusy) return
|
|
138
|
-
// Only handle keys that originate on the wrapper itself, not on
|
|
139
|
-
// inner interactive elements (gallery carousel arrows). Without
|
|
140
|
-
// this guard, pressing Enter/Space on a focused child would bubble
|
|
141
|
-
// up and toggle the preview in addition to activating the child.
|
|
142
|
-
if (e.target !== e.currentTarget) return
|
|
143
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
144
|
-
e.preventDefault()
|
|
145
|
-
void togglePreview()
|
|
146
|
-
}
|
|
147
|
-
}}
|
|
148
|
-
className={
|
|
149
|
-
!isBusy
|
|
150
|
-
? 'block w-full cursor-pointer text-left'
|
|
151
|
-
: 'block w-full text-left'
|
|
152
|
-
}
|
|
153
|
-
>
|
|
154
|
-
{thumbnail}
|
|
155
|
-
</div>
|
|
156
|
-
) : (
|
|
157
|
-
thumbnail
|
|
158
|
-
)}
|
|
159
|
-
|
|
160
|
-
<CardBody
|
|
161
|
-
variant="dark"
|
|
162
|
-
title={title}
|
|
163
|
-
placeholderTitle={placeholderTitle}
|
|
164
|
-
mimeType={mimeType}
|
|
165
|
-
detail={detail}
|
|
166
|
-
statusBadge={statusBadge}
|
|
167
|
-
icon={
|
|
168
|
-
isGallery ? (
|
|
169
|
-
<ImagesIcon className="size-5 shrink-0 text-white/55" />
|
|
170
|
-
) : undefined
|
|
171
|
-
}
|
|
172
|
-
/>
|
|
173
|
-
</LockedCardShell>
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export default SentCard
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LockOpenIcon,
|
|
3
|
-
LockSimpleIcon,
|
|
4
|
-
PlayIcon,
|
|
5
|
-
} from '@phosphor-icons/react'
|
|
6
|
-
import React from 'react'
|
|
7
|
-
|
|
8
|
-
import { getSourceType } from '../../../AttachmentCard'
|
|
9
|
-
import type { PaymentStatus } from '../../types'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Centered 48x48 lock badge that overlays the thumbnail of any locked attachment.
|
|
13
|
-
* Uses `bg-black/25` from the design system's `alpha-black-25` token.
|
|
14
|
-
*
|
|
15
|
-
* For playable media (video/audio), the badge shows a play icon instead of a
|
|
16
|
-
* lock — matching the convention used by `GalleryThumbnail`.
|
|
17
|
-
*/
|
|
18
|
-
export const LockBadge: React.FC<{
|
|
19
|
-
paymentStatus?: PaymentStatus
|
|
20
|
-
mimeType?: string
|
|
21
|
-
}> = ({ paymentStatus, mimeType }) => {
|
|
22
|
-
const sourceType = mimeType ? getSourceType(mimeType) : undefined
|
|
23
|
-
const isPlayable = sourceType === 'video' || sourceType === 'audio'
|
|
24
|
-
|
|
25
|
-
const Icon = isPlayable
|
|
26
|
-
? PlayIcon
|
|
27
|
-
: paymentStatus === 'paid'
|
|
28
|
-
? LockOpenIcon
|
|
29
|
-
: LockSimpleIcon
|
|
30
|
-
return (
|
|
31
|
-
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
32
|
-
<div className="flex size-12 items-center justify-center rounded-full bg-black/25 text-white">
|
|
33
|
-
<Icon className="size-6" weight="fill" />
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export default LockBadge
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import React, { Suspense } from 'react'
|
|
2
|
-
|
|
3
|
-
import type { ComposerCardProps } from './components/Composer/Card'
|
|
4
|
-
import type { ReceivedCardProps } from './components/Received/Card'
|
|
5
|
-
import type { SentCardProps } from './components/Sent/Card'
|
|
6
|
-
|
|
7
|
-
const ComposerCardLazy = React.lazy(() => import('./components/Composer/Card'))
|
|
8
|
-
const SentCardLazy = React.lazy(() => import('./components/Sent/Card'))
|
|
9
|
-
const ReceivedCardLazy = React.lazy(() => import('./components/Received/Card'))
|
|
10
|
-
|
|
11
|
-
const LockedAttachmentFallback = () => (
|
|
12
|
-
<div
|
|
13
|
-
className="w-[280px] min-h-[200px] animate-pulse rounded-md bg-black/[0.06] shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_1px_2px_rgba(0,0,0,0.04),0_8px_32px_rgba(0,0,0,0.1)]"
|
|
14
|
-
aria-hidden
|
|
15
|
-
/>
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
const Composer = (props: ComposerCardProps) => (
|
|
19
|
-
<Suspense fallback={<LockedAttachmentFallback />}>
|
|
20
|
-
<ComposerCardLazy {...props} />
|
|
21
|
-
</Suspense>
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
const Sent = (props: SentCardProps) => (
|
|
25
|
-
<Suspense fallback={<LockedAttachmentFallback />}>
|
|
26
|
-
<SentCardLazy {...props} />
|
|
27
|
-
</Suspense>
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
const Received = (props: ReceivedCardProps) => (
|
|
31
|
-
<Suspense fallback={<LockedAttachmentFallback />}>
|
|
32
|
-
<ReceivedCardLazy {...props} />
|
|
33
|
-
</Suspense>
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @deprecated Renamed to `LockedAttachment.Sent` (post-send sender card) or
|
|
38
|
-
* `LockedAttachment.Composer` (drafting state). This alias maps to `Sent`
|
|
39
|
-
* because that matches the previous in-thread usage; pre-send / drafting
|
|
40
|
-
* usages (e.g. with `onDismiss`) should migrate to `LockedAttachment.Composer`.
|
|
41
|
-
*/
|
|
42
|
-
const Creator = Sent
|
|
43
|
-
|
|
44
|
-
/** @deprecated Renamed to `LockedAttachment.Received`. */
|
|
45
|
-
const Visitor = Received
|
|
46
|
-
|
|
47
|
-
const LockedAttachment = { Composer, Sent, Received, Creator, Visitor }
|
|
48
|
-
|
|
49
|
-
export default LockedAttachment
|
|
50
|
-
export type { ComposerCardProps, SentCardProps, ReceivedCardProps }
|
|
51
|
-
/**
|
|
52
|
-
* @deprecated Renamed to `SentCardProps`. Drafting usages (with `onDismiss`)
|
|
53
|
-
* should migrate to `ComposerCardProps`.
|
|
54
|
-
*/
|
|
55
|
-
export type CreatorCardProps = SentCardProps
|
|
56
|
-
/** @deprecated Renamed to `ReceivedCardProps`. */
|
|
57
|
-
export type VisitorCardProps = ReceivedCardProps
|
|
58
|
-
export type {
|
|
59
|
-
PaymentStatus,
|
|
60
|
-
LockedAttachmentSource,
|
|
61
|
-
LockedAttachmentGalleryItem,
|
|
62
|
-
LockedAttachmentContextValue,
|
|
63
|
-
} from './types'
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { Channel, LocalMessage } from 'stream-chat'
|
|
2
|
-
|
|
3
|
-
import type { PaymentStatus } from '../../stream-custom-data'
|
|
4
|
-
|
|
5
|
-
export interface LockedAttachmentBaseProps {
|
|
6
|
-
title?: string
|
|
7
|
-
mimeType?: string
|
|
8
|
-
thumbnailUrl?: string
|
|
9
|
-
detail?: string
|
|
10
|
-
amountText?: string
|
|
11
|
-
paymentStatus?: PaymentStatus
|
|
12
|
-
/**
|
|
13
|
-
* When provided with 2+ items, the card renders as a mixed-media carousel
|
|
14
|
-
* (e.g. a couple of photos + a video) instead of a single thumbnail. Each
|
|
15
|
-
* item brings its own thumbnail and optional source so that
|
|
16
|
-
* `LockedAttachment.Composer` / `.Sent` / `.Received` can all share the
|
|
17
|
-
* same carousel chrome.
|
|
18
|
-
*/
|
|
19
|
-
gallery?: LockedAttachmentGalleryItem[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface LockedAttachmentGalleryItem {
|
|
23
|
-
/** MIME type of this carousel item — drives the per-item play / lock affordance. */
|
|
24
|
-
mimeType: string
|
|
25
|
-
/** Poster image used for both the locked (blurred) and unlocked preview state. */
|
|
26
|
-
thumbnailUrl?: string
|
|
27
|
-
/** Underlying source (image or video URL) — shown only when unlocked. */
|
|
28
|
-
sourceUrl?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface LockedAttachmentSource {
|
|
32
|
-
/** Proxied URL used by the media player for in-app playback. */
|
|
33
|
-
sourceUrl: string
|
|
34
|
-
/** URL opened when the visitor clicks Download — may be a file or a web destination. */
|
|
35
|
-
redeemUrl?: string
|
|
36
|
-
/** Thumbnail URL from the fetched asset — overrides the metadata thumbnail when present. */
|
|
37
|
-
thumbnailUrl?: string
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type { PaymentStatus }
|
|
41
|
-
|
|
42
|
-
export interface LockedAttachmentContextValue {
|
|
43
|
-
isUnlocking: (id: string) => boolean
|
|
44
|
-
onUnlockClick?: (message: LocalMessage, channel: Channel) => void
|
|
45
|
-
onDownloadClick?: (message: LocalMessage, channel: Channel) => void
|
|
46
|
-
onFetchSource?: (message: LocalMessage, channel: Channel) => Promise<LockedAttachmentSource | void>
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const defaultLockedAttachmentContextValue: LockedAttachmentContextValue = {
|
|
50
|
-
isUnlocking: () => false,
|
|
51
|
-
}
|