@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "1.28.0-rc-1776233688",
3
+ "version": "1.28.1",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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: 'yes here it is',
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-2">
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, { type VisitorCardProps } from '.'
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
- type InteractiveProps = Omit<VisitorCardProps, 'onUnlock' | 'onDownload'> & {
26
- unlockedSource: string
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
- <Interactive
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
- unlockedSource={source}
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
- <Interactive
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
- isCreator
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
- <Interactive
299
+ <LockedAttachment
314
300
  title={title}
315
301
  amountText="AU$9.99"
316
302
  mimeType={mimeType}
317
303
  detail={detail}
318
- unlockedSource={source}
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
- <Icon className="size-4 text-white" weight="fill" />
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
- loading: boolean
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, loading, paymentStatus, source, onUnlock, onDownload } =
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={loading}
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
- {loading ? (
197
- <span className="flex items-center gap-1">
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
- <a
219
- href={source}
220
- target="_blank"
221
- rel="noopener noreferrer"
238
+ <button
239
+ type="button"
222
240
  onClick={onDownload}
223
- 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"
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
- <DownloadSimpleIcon className="size-4" weight="bold" />
226
- Download
227
- </a>
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 [loading, setLoading] = useState(false)
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
- setLoading(true)
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
- setLoading(false)
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
- loading={loading}
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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"}