@linktr.ee/messaging-react 1.28.0-rc-1776233688 → 1.28.1
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/{Creator-BJ6UBWsR.js → Creator-VyMyIk2b.js} +87 -84
- package/dist/Creator-VyMyIk2b.js.map +1 -0
- package/dist/{MediaPlayer-DXz4IBLx.js → MediaPlayer-Bf-xPB8Z.js} +99 -94
- package/dist/MediaPlayer-Bf-xPB8Z.js.map +1 -0
- package/dist/Visitor-C4WqnN8H.js +218 -0
- package/dist/Visitor-C4WqnN8H.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +80 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/CustomMessage/CustomMessage.stories.tsx +11 -6
- package/src/components/CustomMessage/index.tsx +1 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +15 -28
- package/src/components/LockedAttachment/components/Creator.tsx +7 -3
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +10 -2
- package/src/components/LockedAttachment/components/Visitor.tsx +75 -33
- package/dist/Creator-BJ6UBWsR.js.map +0 -1
- package/dist/MediaPlayer-DXz4IBLx.js.map +0 -1
- package/dist/Visitor-C1Fcrgd6.js +0 -199
- package/dist/Visitor-C1Fcrgd6.js.map +0 -1
package/package.json
CHANGED
|
@@ -106,6 +106,11 @@ interface TemplateProps {
|
|
|
106
106
|
| 'MESSAGE_PAID'
|
|
107
107
|
| 'MESSAGE_CHATBOT'
|
|
108
108
|
| 'MESSAGE_ATTACHMENT'
|
|
109
|
+
payment_status?:
|
|
110
|
+
| 'pending'
|
|
111
|
+
| 'paid'
|
|
112
|
+
| 'failed'
|
|
113
|
+
| 'refunded'
|
|
109
114
|
amount_text?: string
|
|
110
115
|
attachment_title?: string
|
|
111
116
|
attachment_mime_type?: string
|
|
@@ -316,7 +321,12 @@ PaidAttachment.args = {
|
|
|
316
321
|
},
|
|
317
322
|
{
|
|
318
323
|
id: 'msg-2',
|
|
319
|
-
text: '
|
|
324
|
+
text: 'Yes, of course!',
|
|
325
|
+
user: storyUsers.creator,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: 'msg-3',
|
|
329
|
+
text: 'let me know if you have any questions!',
|
|
320
330
|
user: storyUsers.creator,
|
|
321
331
|
metadata: {
|
|
322
332
|
custom_type: 'MESSAGE_ATTACHMENT',
|
|
@@ -327,11 +337,6 @@ PaidAttachment.args = {
|
|
|
327
337
|
attachment_detail: '1:20',
|
|
328
338
|
},
|
|
329
339
|
},
|
|
330
|
-
{
|
|
331
|
-
id: 'msg-3',
|
|
332
|
-
text: 'let me know if you have any questions!',
|
|
333
|
-
user: storyUsers.creator,
|
|
334
|
-
},
|
|
335
340
|
{
|
|
336
341
|
id: 'msg-4',
|
|
337
342
|
text: 'Looks amazing, unlocking now!',
|
|
@@ -202,7 +202,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
202
202
|
}}
|
|
203
203
|
>
|
|
204
204
|
{isAttachment ? (
|
|
205
|
-
<div className="flex flex-col gap-
|
|
205
|
+
<div className={classNames("flex flex-col gap-1", isMine ? "items-end" : "items-start")}>
|
|
206
206
|
<LockedAttachment
|
|
207
207
|
title={message.metadata?.attachment_title}
|
|
208
208
|
mimeType={message.metadata?.attachment_mime_type}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
|
-
import LockedAttachment
|
|
4
|
+
import LockedAttachment from '.'
|
|
5
5
|
|
|
6
6
|
const VIDEO_THUMBNAIL_BLURRED = '/video-thumbnail-blurred.jpg'
|
|
7
7
|
const VIDEO_SOURCE = '/video-source.mp4'
|
|
@@ -22,23 +22,11 @@ const meta: Meta = {
|
|
|
22
22
|
}
|
|
23
23
|
export default meta
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const withDelay = <T,>(fn: () => Promise<T> | T) => async (): Promise<T> => {
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
27
|
+
return fn()
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const Interactive = ({ unlockedSource, ...props }: InteractiveProps) => (
|
|
30
|
-
<LockedAttachment
|
|
31
|
-
{...props}
|
|
32
|
-
onUnlock={async () => {
|
|
33
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
34
|
-
const res = await fetch(unlockedSource)
|
|
35
|
-
const blob = await res.blob()
|
|
36
|
-
return { source: URL.createObjectURL(blob) }
|
|
37
|
-
}}
|
|
38
|
-
onDownload={() => {}}
|
|
39
|
-
/>
|
|
40
|
-
)
|
|
41
|
-
|
|
42
30
|
const VARIANTS = [
|
|
43
31
|
{
|
|
44
32
|
label: 'Video',
|
|
@@ -149,13 +137,14 @@ export const Visitor: StoryFn = () => (
|
|
|
149
137
|
</td>
|
|
150
138
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
151
139
|
<td key={mimeType} className="align-top">
|
|
152
|
-
<
|
|
140
|
+
<LockedAttachment
|
|
153
141
|
title={title}
|
|
154
142
|
amountText="AU$9.99"
|
|
155
143
|
thumbnail={thumbnail}
|
|
156
144
|
mimeType={mimeType}
|
|
157
145
|
detail={detail}
|
|
158
|
-
|
|
146
|
+
onUnlock={withDelay(() => ({ source }))}
|
|
147
|
+
onDownload={() => {}}
|
|
159
148
|
/>
|
|
160
149
|
</td>
|
|
161
150
|
))}
|
|
@@ -166,14 +155,15 @@ export const Visitor: StoryFn = () => (
|
|
|
166
155
|
</td>
|
|
167
156
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
168
157
|
<td key={mimeType} className="align-top">
|
|
169
|
-
<
|
|
158
|
+
<LockedAttachment
|
|
170
159
|
title={title}
|
|
171
160
|
amountText="AU$9.99"
|
|
172
161
|
thumbnail={thumbnail}
|
|
173
162
|
mimeType={mimeType}
|
|
174
163
|
detail={detail}
|
|
175
|
-
unlockedSource={source}
|
|
176
164
|
paymentStatus="paid"
|
|
165
|
+
onUnlock={withDelay(() => ({ source }))}
|
|
166
|
+
onDownload={() => {}}
|
|
177
167
|
/>
|
|
178
168
|
</td>
|
|
179
169
|
))}
|
|
@@ -192,7 +182,7 @@ export const Visitor: StoryFn = () => (
|
|
|
192
182
|
detail={detail}
|
|
193
183
|
amountText="AU$9.99"
|
|
194
184
|
paymentStatus="paid"
|
|
195
|
-
onDownload={() =>
|
|
185
|
+
onDownload={() => new Promise((resolve) => setTimeout(resolve, 2000))}
|
|
196
186
|
/>
|
|
197
187
|
</td>
|
|
198
188
|
))}
|
|
@@ -212,7 +202,6 @@ export const Creator: StoryFn = () => (
|
|
|
212
202
|
{VARIANTS.map(({ mimeType, detail, thumbnail, source }) => (
|
|
213
203
|
<td key={mimeType} className="align-top">
|
|
214
204
|
<LockedAttachment
|
|
215
|
-
isCreator
|
|
216
205
|
isPreview
|
|
217
206
|
thumbnail={thumbnail}
|
|
218
207
|
source={source}
|
|
@@ -230,7 +219,6 @@ export const Creator: StoryFn = () => (
|
|
|
230
219
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
231
220
|
<td key={mimeType} className="align-top">
|
|
232
221
|
<LockedAttachment
|
|
233
|
-
isCreator
|
|
234
222
|
title={title}
|
|
235
223
|
thumbnail={thumbnail}
|
|
236
224
|
source={source}
|
|
@@ -249,7 +237,7 @@ export const Creator: StoryFn = () => (
|
|
|
249
237
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
250
238
|
<td key={mimeType} className="align-top">
|
|
251
239
|
<LockedAttachment
|
|
252
|
-
|
|
240
|
+
|
|
253
241
|
title={title}
|
|
254
242
|
thumbnail={thumbnail}
|
|
255
243
|
source={source}
|
|
@@ -267,7 +255,6 @@ export const Creator: StoryFn = () => (
|
|
|
267
255
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
268
256
|
<td key={mimeType} className="align-top">
|
|
269
257
|
<LockedAttachment
|
|
270
|
-
isCreator
|
|
271
258
|
title={title}
|
|
272
259
|
thumbnail={thumbnail}
|
|
273
260
|
source={source}
|
|
@@ -294,7 +281,6 @@ export const NoThumbnail: StoryFn = () => (
|
|
|
294
281
|
{NO_THUMBNAIL_VARIANTS.map(({ title, mimeType, detail, source }) => (
|
|
295
282
|
<td key={mimeType} className="align-top">
|
|
296
283
|
<LockedAttachment
|
|
297
|
-
isCreator
|
|
298
284
|
title={title}
|
|
299
285
|
source={source}
|
|
300
286
|
mimeType={mimeType}
|
|
@@ -310,12 +296,13 @@ export const NoThumbnail: StoryFn = () => (
|
|
|
310
296
|
</td>
|
|
311
297
|
{NO_THUMBNAIL_VARIANTS.map(({ title, mimeType, detail, source }) => (
|
|
312
298
|
<td key={mimeType} className="align-top">
|
|
313
|
-
<
|
|
299
|
+
<LockedAttachment
|
|
314
300
|
title={title}
|
|
315
301
|
amountText="AU$9.99"
|
|
316
302
|
mimeType={mimeType}
|
|
317
303
|
detail={detail}
|
|
318
|
-
|
|
304
|
+
onUnlock={withDelay(() => ({ source }))}
|
|
305
|
+
onDownload={() => {}}
|
|
319
306
|
/>
|
|
320
307
|
</td>
|
|
321
308
|
))}
|
|
@@ -131,9 +131,12 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
|
|
|
131
131
|
}
|
|
132
132
|
}, [playing, seeking])
|
|
133
133
|
|
|
134
|
+
const [audioReady, setAudioReady] = useState(false)
|
|
135
|
+
|
|
134
136
|
useEffect(() => {
|
|
135
137
|
setPlaying(false)
|
|
136
138
|
setPlayed(0)
|
|
139
|
+
setAudioReady(false)
|
|
137
140
|
}, [source])
|
|
138
141
|
|
|
139
142
|
const getFraction = useCallback(
|
|
@@ -187,6 +190,7 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
|
|
|
187
190
|
ref={audioRef}
|
|
188
191
|
src={source}
|
|
189
192
|
loop
|
|
193
|
+
onCanPlay={() => setAudioReady(true)}
|
|
190
194
|
onEnded={() => {
|
|
191
195
|
setPlaying(false)
|
|
192
196
|
setPlayed(0)
|
|
@@ -198,10 +202,10 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
|
|
|
198
202
|
<CollapsedThumbnail
|
|
199
203
|
thumbnail={thumbnail}
|
|
200
204
|
mimeType={mimeType}
|
|
201
|
-
overlayIcon={source ? (playing ? PauseIcon : PlayIcon) : undefined}
|
|
202
|
-
onClick={source ? toggle : undefined}
|
|
205
|
+
overlayIcon={source && audioReady ? (playing ? PauseIcon : PlayIcon) : undefined}
|
|
206
|
+
onClick={source && audioReady ? toggle : undefined}
|
|
203
207
|
/>
|
|
204
|
-
{source && (
|
|
208
|
+
{source && audioReady && (
|
|
205
209
|
<div className="absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent">
|
|
206
210
|
<div
|
|
207
211
|
ref={trackRef}
|
|
@@ -63,6 +63,8 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
63
63
|
const [scrubberHovered, setScrubberHovered] = useState(false)
|
|
64
64
|
const [videoAspect, setVideoAspect] = useState<number | null>(null)
|
|
65
65
|
const [buffering, setBuffering] = useState(false)
|
|
66
|
+
/** True until the first canPlay fires for the current source — hides controls/spinner behind poster. */
|
|
67
|
+
const [initialLoad, setInitialLoad] = useState(true)
|
|
66
68
|
/** Set when autoplay/play() was rejected so user can start via gesture (no controls UI). */
|
|
67
69
|
const [manualPlayRequired, setManualPlayRequired] = useState(false)
|
|
68
70
|
const playerRef = useRef<HTMLMediaElement>(null)
|
|
@@ -71,6 +73,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
71
73
|
|
|
72
74
|
useEffect(() => {
|
|
73
75
|
setManualPlayRequired(false)
|
|
76
|
+
setInitialLoad(true)
|
|
74
77
|
}, [source])
|
|
75
78
|
|
|
76
79
|
useEffect(() => {
|
|
@@ -209,6 +212,11 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
209
212
|
})}
|
|
210
213
|
</div>
|
|
211
214
|
)}
|
|
215
|
+
{initialLoad && poster && (
|
|
216
|
+
<div className="absolute inset-0 z-20">
|
|
217
|
+
<img src={poster} alt="" className="h-full w-full object-cover" />
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
212
220
|
<div className="absolute inset-0">
|
|
213
221
|
{sourceType === 'audio' ? (
|
|
214
222
|
<audio
|
|
@@ -218,7 +226,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
218
226
|
muted={muted}
|
|
219
227
|
style={{ width: '100%', height: '100%' }}
|
|
220
228
|
onLoadStart={() => setBuffering(true)}
|
|
221
|
-
onCanPlay={() => setBuffering(false)}
|
|
229
|
+
onCanPlay={() => { setBuffering(false); setInitialLoad(false) }}
|
|
222
230
|
onWaiting={() => setBuffering(true)}
|
|
223
231
|
onPlay={() => setManualPlayRequired(false)}
|
|
224
232
|
onEnded={() => {
|
|
@@ -240,7 +248,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
240
248
|
playsInline
|
|
241
249
|
style={{ width: '100%', height: '100%' }}
|
|
242
250
|
onLoadStart={() => setBuffering(true)}
|
|
243
|
-
onCanPlay={() => setBuffering(false)}
|
|
251
|
+
onCanPlay={() => { setBuffering(false); setInitialLoad(false) }}
|
|
244
252
|
onWaiting={() => setBuffering(true)}
|
|
245
253
|
onPlay={() => setManualPlayRequired(false)}
|
|
246
254
|
onLoadedMetadata={() => {
|
|
@@ -24,10 +24,11 @@ export interface VisitorCardProps extends LockedAttachmentBaseProps {
|
|
|
24
24
|
* Omit to hide the Unlock button.
|
|
25
25
|
*/
|
|
26
26
|
onUnlock?: () => Promise<LockedAttachmentSource>
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
28
|
* Called when the visitor clicks Download on an unlocked card.
|
|
29
|
+
* May return a Promise — if so, the component shows a loader until it resolves.
|
|
29
30
|
*/
|
|
30
|
-
onDownload?: () => void
|
|
31
|
+
onDownload?: () => void | Promise<void>
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const getLockIcon = (paymentStatus?: PaymentStatus): React.ElementType =>
|
|
@@ -36,14 +37,23 @@ const getLockIcon = (paymentStatus?: PaymentStatus): React.ElementType =>
|
|
|
36
37
|
|
|
37
38
|
interface LockOverlayProps {
|
|
38
39
|
icon: React.ElementType
|
|
40
|
+
loading?: boolean
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const LockOverlay: React.FC<LockOverlayProps> = (props) => {
|
|
42
|
-
const { icon: Icon } = props
|
|
44
|
+
const { icon: Icon, loading } = props
|
|
43
45
|
return (
|
|
44
46
|
<div className="absolute inset-0 bg-black/30">
|
|
45
47
|
<div className="absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60">
|
|
46
|
-
|
|
48
|
+
{loading ? (
|
|
49
|
+
<span className="flex items-center gap-[2px]">
|
|
50
|
+
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.3s]" />
|
|
51
|
+
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.15s]" />
|
|
52
|
+
<span className="size-1 rounded-full bg-white animate-bounce" />
|
|
53
|
+
</span>
|
|
54
|
+
) : (
|
|
55
|
+
<Icon className="size-4 text-white" weight="fill" />
|
|
56
|
+
)}
|
|
47
57
|
</div>
|
|
48
58
|
</div>
|
|
49
59
|
)
|
|
@@ -53,10 +63,11 @@ interface LockedPreviewProps {
|
|
|
53
63
|
thumbnail?: string
|
|
54
64
|
mimeType: string
|
|
55
65
|
LockIcon: React.ElementType
|
|
66
|
+
loading?: boolean
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
const LockedPreview: React.FC<LockedPreviewProps> = (props) => {
|
|
59
|
-
const { thumbnail, mimeType, LockIcon } = props
|
|
70
|
+
const { thumbnail, mimeType, LockIcon, loading } = props
|
|
60
71
|
return (
|
|
61
72
|
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
62
73
|
{thumbnail ? (
|
|
@@ -73,7 +84,7 @@ const LockedPreview: React.FC<LockedPreviewProps> = (props) => {
|
|
|
73
84
|
})}
|
|
74
85
|
</div>
|
|
75
86
|
)}
|
|
76
|
-
<LockOverlay icon={LockIcon} />
|
|
87
|
+
<LockOverlay icon={LockIcon} loading={loading} />
|
|
77
88
|
</div>
|
|
78
89
|
)
|
|
79
90
|
}
|
|
@@ -86,10 +97,11 @@ interface ImagePreviewProps {
|
|
|
86
97
|
title?: string
|
|
87
98
|
paymentStatus?: PaymentStatus
|
|
88
99
|
isLocked: boolean
|
|
100
|
+
loading?: boolean
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
const ImagePreview: React.FC<ImagePreviewProps> = (props) => {
|
|
92
|
-
const { source, thumbnail, mimeType, title, paymentStatus, isLocked } = props
|
|
104
|
+
const { source, thumbnail, mimeType, title, paymentStatus, isLocked, loading } = props
|
|
93
105
|
const [sourceReady, setSourceReady] = useState(false)
|
|
94
106
|
|
|
95
107
|
useEffect(() => {
|
|
@@ -102,6 +114,7 @@ const ImagePreview: React.FC<ImagePreviewProps> = (props) => {
|
|
|
102
114
|
thumbnail={thumbnail}
|
|
103
115
|
mimeType={mimeType}
|
|
104
116
|
LockIcon={getLockIcon(paymentStatus)}
|
|
117
|
+
loading={loading}
|
|
105
118
|
/>
|
|
106
119
|
)
|
|
107
120
|
}
|
|
@@ -123,10 +136,11 @@ interface DocumentPreviewProps {
|
|
|
123
136
|
mimeType: string
|
|
124
137
|
paymentStatus?: PaymentStatus
|
|
125
138
|
isLocked: boolean
|
|
139
|
+
loading?: boolean
|
|
126
140
|
}
|
|
127
141
|
|
|
128
142
|
const DocumentPreview: React.FC<DocumentPreviewProps> = (props) => {
|
|
129
|
-
const { thumbnail, mimeType, paymentStatus, isLocked } = props
|
|
143
|
+
const { thumbnail, mimeType, paymentStatus, isLocked, loading } = props
|
|
130
144
|
return (
|
|
131
145
|
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
132
146
|
{thumbnail ? (
|
|
@@ -143,7 +157,7 @@ const DocumentPreview: React.FC<DocumentPreviewProps> = (props) => {
|
|
|
143
157
|
})}
|
|
144
158
|
</div>
|
|
145
159
|
)}
|
|
146
|
-
{isLocked && <LockOverlay icon={getLockIcon(paymentStatus)} />}
|
|
160
|
+
{isLocked && <LockOverlay icon={getLockIcon(paymentStatus)} loading={loading} />}
|
|
147
161
|
</div>
|
|
148
162
|
)
|
|
149
163
|
}
|
|
@@ -154,16 +168,18 @@ interface MediaPreviewProps {
|
|
|
154
168
|
mimeType: string
|
|
155
169
|
paymentStatus?: PaymentStatus
|
|
156
170
|
isLocked: boolean
|
|
171
|
+
loading?: boolean
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
const MediaPreview: React.FC<MediaPreviewProps> = (props) => {
|
|
160
|
-
const { source, thumbnail, mimeType, paymentStatus, isLocked } = props
|
|
175
|
+
const { source, thumbnail, mimeType, paymentStatus, isLocked, loading } = props
|
|
161
176
|
if (isLocked) {
|
|
162
177
|
return (
|
|
163
178
|
<LockedPreview
|
|
164
179
|
thumbnail={thumbnail}
|
|
165
180
|
mimeType={mimeType}
|
|
166
181
|
LockIcon={getLockIcon(paymentStatus)}
|
|
182
|
+
loading={loading}
|
|
167
183
|
/>
|
|
168
184
|
)
|
|
169
185
|
}
|
|
@@ -171,9 +187,18 @@ const MediaPreview: React.FC<MediaPreviewProps> = (props) => {
|
|
|
171
187
|
}
|
|
172
188
|
|
|
173
189
|
|
|
190
|
+
const LoadingDots = () => (
|
|
191
|
+
<span className="flex items-center gap-1">
|
|
192
|
+
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.3s]" />
|
|
193
|
+
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.15s]" />
|
|
194
|
+
<span className="size-1 rounded-full bg-white animate-bounce" />
|
|
195
|
+
</span>
|
|
196
|
+
)
|
|
197
|
+
|
|
174
198
|
interface CardActionsProps {
|
|
175
199
|
isLocked: boolean
|
|
176
|
-
|
|
200
|
+
unlockLoading: boolean
|
|
201
|
+
downloadLoading: boolean
|
|
177
202
|
paymentStatus?: PaymentStatus
|
|
178
203
|
source?: string
|
|
179
204
|
onUnlock?: () => void
|
|
@@ -181,8 +206,7 @@ interface CardActionsProps {
|
|
|
181
206
|
}
|
|
182
207
|
|
|
183
208
|
const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
184
|
-
const { isLocked,
|
|
185
|
-
props
|
|
209
|
+
const { isLocked, unlockLoading, downloadLoading, paymentStatus, source, onUnlock, onDownload } = props
|
|
186
210
|
const LockIcon = getLockIcon(paymentStatus)
|
|
187
211
|
|
|
188
212
|
if (isLocked && onUnlock) {
|
|
@@ -190,15 +214,11 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
190
214
|
<button
|
|
191
215
|
type="button"
|
|
192
216
|
onClick={onUnlock}
|
|
193
|
-
disabled={
|
|
217
|
+
disabled={unlockLoading}
|
|
194
218
|
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-[#121110] px-4 text-sm font-medium leading-none text-white disabled:opacity-70"
|
|
195
219
|
>
|
|
196
|
-
{
|
|
197
|
-
<
|
|
198
|
-
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.3s]" />
|
|
199
|
-
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.15s]" />
|
|
200
|
-
<span className="size-1 rounded-full bg-white animate-bounce" />
|
|
201
|
-
</span>
|
|
220
|
+
{unlockLoading ? (
|
|
221
|
+
<LoadingDots />
|
|
202
222
|
) : (
|
|
203
223
|
<>
|
|
204
224
|
{paymentStatus === 'paid' ? (
|
|
@@ -215,16 +235,21 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
215
235
|
|
|
216
236
|
if (!isLocked && onDownload && source) {
|
|
217
237
|
return (
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
target="_blank"
|
|
221
|
-
rel="noopener noreferrer"
|
|
238
|
+
<button
|
|
239
|
+
type="button"
|
|
222
240
|
onClick={onDownload}
|
|
223
|
-
|
|
241
|
+
disabled={downloadLoading}
|
|
242
|
+
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-[#121110] px-4 text-sm font-medium leading-none text-white disabled:opacity-70"
|
|
224
243
|
>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
244
|
+
{downloadLoading ? (
|
|
245
|
+
<LoadingDots />
|
|
246
|
+
) : (
|
|
247
|
+
<>
|
|
248
|
+
<DownloadSimpleIcon className="size-4" weight="bold" />
|
|
249
|
+
Download
|
|
250
|
+
</>
|
|
251
|
+
)}
|
|
252
|
+
</button>
|
|
228
253
|
)
|
|
229
254
|
}
|
|
230
255
|
|
|
@@ -245,7 +270,8 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
245
270
|
paymentStatus,
|
|
246
271
|
} = props
|
|
247
272
|
const [source, setSource] = useState(sourceProp)
|
|
248
|
-
const [
|
|
273
|
+
const [unlockLoading, setUnlockLoading] = useState(false)
|
|
274
|
+
const [downloadLoading, setDownloadLoading] = useState(false)
|
|
249
275
|
|
|
250
276
|
useEffect(() => {
|
|
251
277
|
if (sourceProp !== undefined) setSource(sourceProp)
|
|
@@ -256,14 +282,26 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
256
282
|
|
|
257
283
|
const handleUnlock = async () => {
|
|
258
284
|
if (!onUnlock) return
|
|
259
|
-
|
|
285
|
+
setUnlockLoading(true)
|
|
260
286
|
try {
|
|
261
287
|
const result = await onUnlock()
|
|
262
288
|
setSource(result.source)
|
|
263
289
|
} catch (err) {
|
|
264
290
|
if (isDevBuild()) console.debug('[LockedAttachment] onUnlock failed', err)
|
|
265
291
|
} finally {
|
|
266
|
-
|
|
292
|
+
setUnlockLoading(false)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const handleDownload = async () => {
|
|
297
|
+
if (!onDownload) return
|
|
298
|
+
setDownloadLoading(true)
|
|
299
|
+
try {
|
|
300
|
+
await onDownload()
|
|
301
|
+
} catch (err) {
|
|
302
|
+
if (isDevBuild()) console.debug('[LockedAttachment] onDownload failed', err)
|
|
303
|
+
} finally {
|
|
304
|
+
setDownloadLoading(false)
|
|
267
305
|
}
|
|
268
306
|
}
|
|
269
307
|
|
|
@@ -277,6 +315,7 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
277
315
|
title={title}
|
|
278
316
|
paymentStatus={paymentStatus}
|
|
279
317
|
isLocked={isLocked}
|
|
318
|
+
loading={unlockLoading}
|
|
280
319
|
/>
|
|
281
320
|
)
|
|
282
321
|
} else if (sourceType === 'document') {
|
|
@@ -286,6 +325,7 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
286
325
|
mimeType={mimeType}
|
|
287
326
|
paymentStatus={paymentStatus}
|
|
288
327
|
isLocked={isLocked}
|
|
328
|
+
loading={unlockLoading}
|
|
289
329
|
/>
|
|
290
330
|
)
|
|
291
331
|
} else {
|
|
@@ -296,6 +336,7 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
296
336
|
mimeType={mimeType}
|
|
297
337
|
paymentStatus={paymentStatus}
|
|
298
338
|
isLocked={isLocked}
|
|
339
|
+
loading={unlockLoading}
|
|
299
340
|
/>
|
|
300
341
|
)
|
|
301
342
|
}
|
|
@@ -339,11 +380,12 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
339
380
|
</div>
|
|
340
381
|
<CardActions
|
|
341
382
|
isLocked={isLocked}
|
|
342
|
-
|
|
383
|
+
unlockLoading={unlockLoading}
|
|
384
|
+
downloadLoading={downloadLoading}
|
|
343
385
|
paymentStatus={paymentStatus}
|
|
344
386
|
source={source}
|
|
345
387
|
onUnlock={onUnlock ? handleUnlock : undefined}
|
|
346
|
-
onDownload={onDownload}
|
|
388
|
+
onDownload={onDownload ? handleDownload : undefined}
|
|
347
389
|
/>
|
|
348
390
|
</div>
|
|
349
391
|
</div>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Creator-BJ6UBWsR.js","sources":["../src/components/LockedAttachment/components/Creator.tsx"],"sourcesContent":["import {\n CheckCircleIcon,\n EyeIcon,\n EyeSlashIcon,\n LockIcon,\n LockOpenIcon,\n PauseIcon,\n PlayIcon,\n XIcon,\n} from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { LockedAttachmentBaseProps } from '../types'\nimport { renderTypeIcon } from '../utils/icons'\nimport { getSourceType } from '../utils/mimeType'\n\nimport MediaPlayer from './MediaPlayer'\n\nexport interface CreatorCardProps extends LockedAttachmentBaseProps {\n isPreview?: boolean\n placeholderTitle?: string\n placeholderAmountText?: string\n onDismiss?: () => void\n}\n\ninterface CloseButtonProps {\n onClose: () => void\n}\n\nconst CloseButton: React.FC<CloseButtonProps> = (props) => {\n const { onClose } = props\n return (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"absolute left-3 top-3 z-40 flex size-8 items-center justify-center rounded-full bg-black/60 text-white\"\n aria-label=\"Close preview\"\n >\n <EyeIcon className=\"size-4\" weight=\"fill\" />\n </button>\n )\n}\n\ninterface CollapsedThumbnailProps {\n thumbnail?: string\n mimeType: string\n overlayIcon?: React.ElementType\n darkOverlay?: boolean\n onClick?: () => void\n}\n\nconst CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (props) => {\n const { thumbnail, mimeType, overlayIcon: OverlayIcon, darkOverlay, onClick } = props\n return (\n <button\n type=\"button\"\n disabled={!onClick}\n className={classNames(\n 'relative aspect-video block w-full overflow-hidden border-0 bg-black/5 p-0 text-left appearance-none',\n { 'cursor-pointer': !!onClick, 'cursor-default': !onClick }\n )}\n onClick={onClick}\n aria-label={OverlayIcon ? 'Toggle preview' : undefined}\n >\n {thumbnail ? (\n <img\n src={thumbnail}\n alt=\"\"\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n ) : (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {renderTypeIcon(mimeType, {\n className: 'size-12 text-black/20',\n weight: 'regular',\n })}\n </div>\n )}\n {darkOverlay && (\n <div className=\"pointer-events-none absolute inset-0 bg-black/30\" />\n )}\n {OverlayIcon && (\n <div className=\"pointer-events-none absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60 text-white\">\n <OverlayIcon className=\"size-4\" weight=\"fill\" />\n </div>\n )}\n </button>\n )\n}\n\n\ninterface AudioPreviewProps {\n source?: string\n thumbnail?: string\n mimeType: string\n}\n\nconst AudioPreview: React.FC<AudioPreviewProps> = (props) => {\n const { source, thumbnail, mimeType } = props\n const [playing, setPlaying] = useState(false)\n const [played, setPlayed] = useState(0)\n const [seeking, setSeeking] = useState(false)\n const audioRef = useRef<HTMLAudioElement>(null)\n const trackRef = useRef<HTMLDivElement>(null)\n const rafRef = useRef<number | null>(null)\n\n useEffect(() => {\n const el = audioRef.current\n if (!el) return\n if (playing) {\n void el.play().catch(() => setPlaying(false))\n } else {\n el.pause()\n }\n }, [playing])\n\n useEffect(() => {\n if (!playing) {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)\n return\n }\n const tick = () => {\n const el = audioRef.current\n if (el && el.duration && !seeking) setPlayed(el.currentTime / el.duration)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)\n }\n }, [playing, seeking])\n\n useEffect(() => {\n setPlaying(false)\n setPlayed(0)\n }, [source])\n\n const getFraction = useCallback(\n (e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {\n const track = trackRef.current\n if (!track) return 0\n const clientX =\n 'touches' in e\n ? (e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0)\n : e.clientX\n const rect = track.getBoundingClientRect()\n return Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))\n },\n []\n )\n\n const seekTo = useCallback((fraction: number) => {\n const el = audioRef.current\n if (el && el.duration) el.currentTime = fraction * el.duration\n }, [])\n\n useEffect(() => {\n if (!seeking) return\n const onMove = (e: MouseEvent | TouchEvent) => {\n const f = getFraction(e)\n setPlayed(f)\n seekTo(f)\n }\n const onUp = (e: MouseEvent | TouchEvent) => {\n setSeeking(false)\n seekTo(getFraction(e))\n }\n window.addEventListener('mousemove', onMove)\n window.addEventListener('mouseup', onUp)\n window.addEventListener('touchmove', onMove, { passive: true })\n window.addEventListener('touchend', onUp)\n return () => {\n window.removeEventListener('mousemove', onMove)\n window.removeEventListener('mouseup', onUp)\n window.removeEventListener('touchmove', onMove)\n window.removeEventListener('touchend', onUp)\n }\n }, [seeking, getFraction, seekTo])\n\n const toggle = useCallback(() => setPlaying((p) => !p), [])\n\n return (\n <div className=\"relative\">\n {source && (\n <audio\n ref={audioRef}\n src={source}\n loop\n onEnded={() => {\n setPlaying(false)\n setPlayed(0)\n }}\n >\n <track kind=\"captions\" />\n </audio>\n )}\n <CollapsedThumbnail\n thumbnail={thumbnail}\n mimeType={mimeType}\n overlayIcon={source ? (playing ? PauseIcon : PlayIcon) : undefined}\n onClick={source ? toggle : undefined}\n />\n {source && (\n <div className=\"absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent\">\n <div\n ref={trackRef}\n role=\"slider\"\n aria-label=\"Playback position\"\n aria-valuenow={Math.round(played * 100)}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n className=\"relative flex h-4 w-full cursor-pointer items-center\"\n onMouseDown={(e) => {\n e.stopPropagation()\n setSeeking(true)\n const f = getFraction(e)\n setPlayed(f)\n seekTo(f)\n }}\n onTouchStart={(e) => {\n e.stopPropagation()\n setSeeking(true)\n const f = getFraction(e)\n setPlayed(f)\n seekTo(f)\n }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))\n if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))\n }}\n >\n <div className=\"w-full overflow-hidden rounded-full bg-white/30 h-1\">\n <div\n className=\"h-full rounded-full bg-white\"\n style={{ width: `${Math.round(played * 100)}%` }}\n />\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n\ninterface VideoPreviewProps {\n source?: string\n thumbnail?: string\n mimeType: string\n}\n\nconst VideoPreview: React.FC<VideoPreviewProps> = (props) => {\n const { source, thumbnail, mimeType } = props\n const [expanded, setExpanded] = useState(false)\n const collapse = () => setExpanded(false)\n\n useEffect(() => {\n setExpanded(false)\n }, [source])\n\n if (!source) {\n return <CollapsedThumbnail thumbnail={thumbnail} mimeType={mimeType} />\n }\n\n return (\n <div\n className={classNames('relative overflow-hidden', {\n 'aspect-video': !expanded,\n })}\n >\n <MediaPlayer\n source={source}\n mimeType={mimeType}\n poster={thumbnail}\n playing={expanded}\n loop\n controls={false}\n showProgress\n onContainerClick={collapse}\n muted\n />\n {!expanded && (\n <button\n type=\"button\"\n className=\"absolute inset-0 block cursor-pointer border-0 p-0 text-left appearance-none\"\n onClick={() => setExpanded(true)}\n aria-label=\"Expand video preview\"\n >\n {thumbnail ? (\n <img\n src={thumbnail}\n alt=\"\"\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n ) : (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {renderTypeIcon(mimeType, {\n className: 'size-12 text-black/20',\n weight: 'regular',\n })}\n </div>\n )}\n <div className=\"pointer-events-none absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60 text-white\">\n <EyeSlashIcon className=\"size-4\" weight=\"fill\" />\n </div>\n </button>\n )}\n {expanded && <CloseButton onClose={collapse} />}\n </div>\n )\n}\n\ninterface ImagePreviewProps {\n source?: string\n thumbnail?: string\n mimeType: string\n title?: string\n}\n\nconst ImagePreview: React.FC<ImagePreviewProps> = (props) => {\n const { source, thumbnail, mimeType, title } = props\n const [expanded, setExpanded] = useState(false)\n const collapse = () => setExpanded(false)\n\n useEffect(() => {\n setExpanded(false)\n }, [source])\n\n if (expanded && source) {\n return (\n <div className=\"relative\">\n <button\n type=\"button\"\n className=\"block w-full cursor-pointer border-0 p-0 text-left appearance-none\"\n onClick={collapse}\n aria-label=\"Close preview\"\n >\n <img src={source} alt={title ?? ''} className=\"block w-full\" />\n </button>\n <CloseButton onClose={collapse} />\n </div>\n )\n }\n\n return (\n <CollapsedThumbnail\n thumbnail={thumbnail}\n mimeType={mimeType}\n overlayIcon={source ? EyeSlashIcon : undefined}\n onClick={source ? () => setExpanded(true) : undefined}\n />\n )\n}\n\n\nconst CreatorCard: React.FC<CreatorCardProps> = (props) => {\n const {\n title,\n mimeType = 'application/octet-stream',\n thumbnail,\n source,\n detail,\n amountText,\n placeholderTitle = 'Attachment title',\n placeholderAmountText,\n paymentStatus,\n onDismiss,\n isPreview = false,\n } = props\n const sourceType = getSourceType(mimeType)\n const displayAmountText = amountText ?? placeholderAmountText\n const isPlaceholderAmount = !amountText && !!placeholderAmountText\n\n let mediaPreview: React.ReactNode\n if (isPreview && sourceType === 'audio') {\n mediaPreview = (\n <AudioPreview source={source} thumbnail={thumbnail} mimeType={mimeType} />\n )\n } else if (isPreview && sourceType === 'video') {\n mediaPreview = (\n <VideoPreview source={source} thumbnail={thumbnail} mimeType={mimeType} />\n )\n } else if (isPreview && sourceType === 'image') {\n mediaPreview = (\n <ImagePreview\n source={source}\n thumbnail={thumbnail}\n mimeType={mimeType}\n title={title}\n />\n )\n } else {\n const lockedOverlayIcon = onDismiss\n ? undefined\n : paymentStatus === 'paid'\n ? LockOpenIcon\n : LockIcon\n mediaPreview = (\n <CollapsedThumbnail\n thumbnail={thumbnail}\n mimeType={mimeType}\n overlayIcon={lockedOverlayIcon}\n darkOverlay\n />\n )\n }\n\n return (\n <div className=\"relative w-[280px] select-none overflow-hidden rounded-3xl bg-white shadow-card\">\n {onDismiss && (\n <button\n type=\"button\"\n onClick={onDismiss}\n className=\"absolute right-3 top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white\"\n aria-label=\"Dismiss attachment\"\n >\n <XIcon className=\"size-4\" weight=\"bold\" />\n </button>\n )}\n {mediaPreview}\n <div className=\"px-4 pb-3 pt-3\">\n <p\n className={classNames('mb-1.5 truncate text-base font-medium', {\n 'text-black/30': !title,\n 'text-black': !!title,\n })}\n >\n {title || placeholderTitle}\n </p>\n <div className=\"flex items-center gap-1\">\n {renderTypeIcon(mimeType, {\n className: 'size-5 shrink-0 text-black/55',\n weight: 'regular',\n })}\n {detail && (\n <span className=\"text-xs font-medium text-black/55\">{detail}</span>\n )}\n {paymentStatus === 'paid' ? (\n <>\n <span className=\"text-xs font-medium text-black/55\">•</span>\n <span className=\"text-xs font-medium text-[#008236]\">\n Purchased\n </span>\n <CheckCircleIcon\n className=\"size-4 text-[#008236]\"\n weight=\"bold\"\n />\n </>\n ) : (\n displayAmountText && (\n <>\n <span\n className={classNames('text-xs font-medium', {\n 'text-black/30': isPlaceholderAmount,\n 'text-black/55': !isPlaceholderAmount,\n })}\n >\n •\n </span>\n <span\n className={classNames('text-xs font-medium', {\n 'text-black/30': isPlaceholderAmount,\n 'text-black/55': !isPlaceholderAmount,\n })}\n >\n {displayAmountText}\n </span>\n </>\n )\n )}\n </div>\n </div>\n </div>\n )\n}\n\nexport default CreatorCard\n"],"names":["CloseButton","props","onClose","jsx","EyeIcon","CollapsedThumbnail","thumbnail","mimeType","OverlayIcon","darkOverlay","onClick","jsxs","classNames","AudioPreview","source","playing","setPlaying","useState","played","setPlayed","seeking","setSeeking","audioRef","useRef","trackRef","rafRef","useEffect","el","tick","getFraction","useCallback","e","track","clientX","_a","_b","rect","seekTo","fraction","onMove","f","onUp","toggle","p","PauseIcon","PlayIcon","VideoPreview","expanded","setExpanded","collapse","MediaPlayer","EyeSlashIcon","ImagePreview","title","CreatorCard","detail","amountText","placeholderTitle","placeholderAmountText","paymentStatus","onDismiss","isPreview","sourceType","getSourceType","displayAmountText","isPlaceholderAmount","mediaPreview","LockOpenIcon","LockIcon","XIcon","renderTypeIcon","Fragment","CheckCircleIcon"],"mappings":"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"}
|