@linktr.ee/messaging-react 1.27.0 → 1.28.0-rc-1776231821
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-BFpHsh2u.js +318 -0
- package/dist/Creator-BFpHsh2u.js.map +1 -0
- package/dist/MediaPlayer-DXz4IBLx.js +313 -0
- package/dist/MediaPlayer-DXz4IBLx.js.map +1 -0
- package/dist/Visitor-C1Fcrgd6.js +199 -0
- package/dist/Visitor-C1Fcrgd6.js.map +1 -0
- package/dist/assets/index.css +1 -1
- package/dist/index.d.ts +13 -16
- package/dist/index.js +27 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/components/CustomMessage/CustomMessage.stories.tsx +1 -1
- package/src/components/CustomMessage/index.tsx +0 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +143 -49
- package/src/components/LockedAttachment/components/Creator.tsx +406 -114
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +162 -80
- package/src/components/LockedAttachment/components/Visitor.tsx +205 -145
- package/src/components/LockedAttachment/index.tsx +7 -7
- package/src/components/LockedAttachment/types.ts +1 -5
- package/src/components/LockedAttachment/utils/icons.ts +2 -1
- package/src/components/LockedAttachment/utils/mimeType.test.ts +29 -7
- package/src/components/LockedAttachment/utils/mimeType.ts +3 -1
- package/src/types.ts +0 -1
- package/dist/Creator-B6M8dB0U.js +0 -87
- package/dist/Creator-B6M8dB0U.js.map +0 -1
- package/dist/MediaPlayer-DsjlYGGH.js +0 -539
- package/dist/MediaPlayer-DsjlYGGH.js.map +0 -1
- package/dist/Preview-DqAv16NS.js +0 -87
- package/dist/Preview-DqAv16NS.js.map +0 -1
- package/dist/Visitor-CpmFZRGO.js +0 -175
- package/dist/Visitor-CpmFZRGO.js.map +0 -1
- package/dist/dash.all.min-Duv4lvGS.js +0 -18858
- package/dist/dash.all.min-Duv4lvGS.js.map +0 -1
- package/dist/hls-Bogc7CBn.js +0 -21710
- package/dist/hls-Bogc7CBn.js.map +0 -1
- package/dist/index-Da-xN4Yq.js +0 -16142
- package/dist/index-Da-xN4Yq.js.map +0 -1
- package/dist/index-Dj9rqWcU.js +0 -69
- package/dist/index-Dj9rqWcU.js.map +0 -1
- package/dist/mixin-B6jYfIcp.js +0 -808
- package/dist/mixin-B6jYfIcp.js.map +0 -1
- package/dist/react-BxlQMOfz.js +0 -419
- package/dist/react-BxlQMOfz.js.map +0 -1
- package/dist/react-COAP-MIW.js +0 -377
- package/dist/react-COAP-MIW.js.map +0 -1
- package/dist/react-Cn4WlMcl.js +0 -3108
- package/dist/react-Cn4WlMcl.js.map +0 -1
- package/dist/react-CwTJArKY.js +0 -459
- package/dist/react-CwTJArKY.js.map +0 -1
- package/dist/react-DkfS_atT.js +0 -373
- package/dist/react-DkfS_atT.js.map +0 -1
- package/dist/react-Pea5fum1.js +0 -286
- package/dist/react-Pea5fum1.js.map +0 -1
- package/dist/react-RiBbsUDd.js +0 -534
- package/dist/react-RiBbsUDd.js.map +0 -1
- package/dist/react-dS1WBxxz.js +0 -238
- package/dist/react-dS1WBxxz.js.map +0 -1
|
@@ -1,99 +1,190 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CheckCircleIcon,
|
|
3
3
|
DownloadSimpleIcon,
|
|
4
|
+
LockOpenIcon,
|
|
4
5
|
LockSimpleIcon,
|
|
5
|
-
LockSimpleOpenIcon,
|
|
6
6
|
} from '@phosphor-icons/react'
|
|
7
7
|
import React, { useEffect, useState } from 'react'
|
|
8
8
|
|
|
9
9
|
import { isDevBuild } from '../../../utils/isDevBuild'
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
LockedAttachmentBaseProps,
|
|
12
|
+
LockedAttachmentSource,
|
|
13
|
+
PaymentStatus,
|
|
14
|
+
} from '../types'
|
|
11
15
|
import { renderTypeIcon } from '../utils/icons'
|
|
12
16
|
import { getSourceType } from '../utils/mimeType'
|
|
13
17
|
|
|
14
18
|
import MediaPlayer from './MediaPlayer'
|
|
15
19
|
|
|
16
20
|
export interface VisitorCardProps extends LockedAttachmentBaseProps {
|
|
17
|
-
title?: string
|
|
18
21
|
/**
|
|
19
|
-
* Called when the visitor clicks Unlock. Return the resolved source
|
|
20
|
-
* The component manages loading state and sets source
|
|
22
|
+
* Called when the visitor clicks Unlock. Return the resolved source URL.
|
|
23
|
+
* The component manages loading state and sets source internally on resolution.
|
|
21
24
|
* Omit to hide the Unlock button.
|
|
22
25
|
*/
|
|
23
26
|
onUnlock?: () => Promise<LockedAttachmentSource>
|
|
24
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Called when the visitor clicks Download on an unlocked card.
|
|
29
|
+
*/
|
|
25
30
|
onDownload?: () => void
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
const getLockIcon = (paymentStatus?: PaymentStatus): React.ElementType =>
|
|
29
|
-
|
|
33
|
+
const getLockIcon = (paymentStatus?: PaymentStatus): React.ElementType =>
|
|
34
|
+
paymentStatus === 'paid' ? LockOpenIcon : LockSimpleIcon
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
interface LockOverlayProps {
|
|
38
|
+
icon: React.ElementType
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const LockOverlay: React.FC<LockOverlayProps> = (props) => {
|
|
42
|
+
const { icon: Icon } = props
|
|
43
|
+
return (
|
|
44
|
+
<div className="absolute inset-0 bg-black/30">
|
|
45
|
+
<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" />
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface LockedPreviewProps {
|
|
53
|
+
thumbnail?: string
|
|
54
|
+
mimeType: string
|
|
55
|
+
LockIcon: React.ElementType
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const LockedPreview: React.FC<LockedPreviewProps> = (props) => {
|
|
59
|
+
const { thumbnail, mimeType, LockIcon } = props
|
|
60
|
+
return (
|
|
61
|
+
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
62
|
+
{thumbnail ? (
|
|
63
|
+
<img
|
|
64
|
+
src={thumbnail}
|
|
65
|
+
alt=""
|
|
66
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
67
|
+
/>
|
|
68
|
+
) : (
|
|
69
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
70
|
+
{renderTypeIcon(mimeType, {
|
|
71
|
+
className: 'size-12 text-black/20',
|
|
72
|
+
weight: 'regular',
|
|
73
|
+
})}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
<LockOverlay icon={LockIcon} />
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
30
79
|
}
|
|
31
80
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
81
|
+
|
|
82
|
+
interface ImagePreviewProps {
|
|
83
|
+
source?: string
|
|
84
|
+
thumbnail?: string
|
|
85
|
+
mimeType: string
|
|
86
|
+
title?: string
|
|
87
|
+
paymentStatus?: PaymentStatus
|
|
88
|
+
isLocked: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const ImagePreview: React.FC<ImagePreviewProps> = (props) => {
|
|
92
|
+
const { source, thumbnail, mimeType, title, paymentStatus, isLocked } = props
|
|
93
|
+
const [sourceReady, setSourceReady] = useState(false)
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
setSourceReady(false)
|
|
97
|
+
}, [source])
|
|
98
|
+
|
|
99
|
+
if (isLocked) {
|
|
37
100
|
return (
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
101
|
+
<LockedPreview
|
|
102
|
+
thumbnail={thumbnail}
|
|
103
|
+
mimeType={mimeType}
|
|
104
|
+
LockIcon={getLockIcon(paymentStatus)}
|
|
42
105
|
/>
|
|
43
106
|
)
|
|
44
107
|
}
|
|
45
108
|
|
|
46
109
|
return (
|
|
47
|
-
<div className="
|
|
48
|
-
|
|
110
|
+
<div className="relative overflow-hidden bg-black/5">
|
|
111
|
+
<img
|
|
112
|
+
src={source}
|
|
113
|
+
alt={title}
|
|
114
|
+
className={`block w-full transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}
|
|
115
|
+
onLoad={() => setSourceReady(true)}
|
|
116
|
+
/>
|
|
49
117
|
</div>
|
|
50
118
|
)
|
|
51
119
|
}
|
|
52
120
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
121
|
+
interface DocumentPreviewProps {
|
|
122
|
+
thumbnail?: string
|
|
123
|
+
mimeType: string
|
|
124
|
+
paymentStatus?: PaymentStatus
|
|
125
|
+
isLocked: boolean
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const DocumentPreview: React.FC<DocumentPreviewProps> = (props) => {
|
|
129
|
+
const { thumbnail, mimeType, paymentStatus, isLocked } = props
|
|
130
|
+
return (
|
|
131
|
+
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
132
|
+
{thumbnail ? (
|
|
133
|
+
<img
|
|
134
|
+
src={thumbnail}
|
|
135
|
+
alt=""
|
|
136
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
137
|
+
/>
|
|
138
|
+
) : (
|
|
139
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
140
|
+
{renderTypeIcon(mimeType, {
|
|
141
|
+
className: 'size-12 text-black/20',
|
|
142
|
+
weight: 'regular',
|
|
143
|
+
})}
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
{isLocked && <LockOverlay icon={getLockIcon(paymentStatus)} />}
|
|
57
147
|
</div>
|
|
58
|
-
|
|
59
|
-
|
|
148
|
+
)
|
|
149
|
+
}
|
|
60
150
|
|
|
61
|
-
interface
|
|
151
|
+
interface MediaPreviewProps {
|
|
152
|
+
source?: string
|
|
62
153
|
thumbnail?: string
|
|
63
154
|
mimeType: string
|
|
64
|
-
|
|
155
|
+
paymentStatus?: PaymentStatus
|
|
156
|
+
isLocked: boolean
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const MediaPreview: React.FC<MediaPreviewProps> = (props) => {
|
|
160
|
+
const { source, thumbnail, mimeType, paymentStatus, isLocked } = props
|
|
161
|
+
if (isLocked) {
|
|
162
|
+
return (
|
|
163
|
+
<LockedPreview
|
|
164
|
+
thumbnail={thumbnail}
|
|
165
|
+
mimeType={mimeType}
|
|
166
|
+
LockIcon={getLockIcon(paymentStatus)}
|
|
167
|
+
/>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
return <MediaPlayer source={source!} mimeType={mimeType} poster={thumbnail} />
|
|
65
171
|
}
|
|
66
172
|
|
|
67
|
-
const LockedPreview: React.FC<LockedPreviewProps> = ({
|
|
68
|
-
thumbnail,
|
|
69
|
-
mimeType,
|
|
70
|
-
LockIcon,
|
|
71
|
-
}) => (
|
|
72
|
-
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
73
|
-
<ThumbnailOrIcon src={thumbnail} mimeType={mimeType} />
|
|
74
|
-
<LockOverlay icon={LockIcon} />
|
|
75
|
-
</div>
|
|
76
|
-
)
|
|
77
173
|
|
|
78
174
|
interface CardActionsProps {
|
|
79
175
|
isLocked: boolean
|
|
80
176
|
loading: boolean
|
|
81
177
|
paymentStatus?: PaymentStatus
|
|
82
178
|
source?: string
|
|
83
|
-
LockIcon: React.ElementType
|
|
84
179
|
onUnlock?: () => void
|
|
85
180
|
onDownload?: () => void
|
|
86
181
|
}
|
|
87
182
|
|
|
88
|
-
const CardActions: React.FC<CardActionsProps> = ({
|
|
89
|
-
isLocked,
|
|
90
|
-
|
|
91
|
-
paymentStatus
|
|
92
|
-
|
|
93
|
-
LockIcon,
|
|
94
|
-
onUnlock,
|
|
95
|
-
onDownload,
|
|
96
|
-
}) => {
|
|
183
|
+
const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
184
|
+
const { isLocked, loading, paymentStatus, source, onUnlock, onDownload } =
|
|
185
|
+
props
|
|
186
|
+
const LockIcon = getLockIcon(paymentStatus)
|
|
187
|
+
|
|
97
188
|
if (isLocked && onUnlock) {
|
|
98
189
|
return (
|
|
99
190
|
<button
|
|
@@ -110,13 +201,18 @@ const CardActions: React.FC<CardActionsProps> = ({
|
|
|
110
201
|
</span>
|
|
111
202
|
) : (
|
|
112
203
|
<>
|
|
113
|
-
|
|
204
|
+
{paymentStatus === 'paid' ? (
|
|
205
|
+
<LockOpenIcon className="size-4" weight="fill" />
|
|
206
|
+
) : (
|
|
207
|
+
<LockIcon className="size-4" weight="fill" />
|
|
208
|
+
)}
|
|
114
209
|
{paymentStatus === 'paid' ? 'Open' : 'Unlock'}
|
|
115
210
|
</>
|
|
116
211
|
)}
|
|
117
212
|
</button>
|
|
118
213
|
)
|
|
119
214
|
}
|
|
215
|
+
|
|
120
216
|
if (!isLocked && onDownload && source) {
|
|
121
217
|
return (
|
|
122
218
|
<a
|
|
@@ -131,75 +227,31 @@ const CardActions: React.FC<CardActionsProps> = ({
|
|
|
131
227
|
</a>
|
|
132
228
|
)
|
|
133
229
|
}
|
|
230
|
+
|
|
134
231
|
return null
|
|
135
232
|
}
|
|
136
233
|
|
|
137
|
-
interface VisitorCardMetaProps {
|
|
138
|
-
mimeType: string
|
|
139
|
-
detail?: string
|
|
140
|
-
paymentStatus?: PaymentStatus
|
|
141
|
-
amountText?: string
|
|
142
|
-
}
|
|
143
234
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
{paymentStatus === 'paid' ? (
|
|
157
|
-
<>
|
|
158
|
-
<span className="text-xs font-medium text-black/55">•</span>
|
|
159
|
-
<span className="text-xs font-medium text-[#008236]">Purchased</span>
|
|
160
|
-
<CheckCircleIcon className="size-4 text-[#008236]" weight="bold" />
|
|
161
|
-
</>
|
|
162
|
-
) : (
|
|
163
|
-
amountText && (
|
|
164
|
-
<>
|
|
165
|
-
<span className="text-xs font-medium text-black/55">•</span>
|
|
166
|
-
<span className="text-xs font-medium text-black/55">
|
|
167
|
-
{amountText}
|
|
168
|
-
</span>
|
|
169
|
-
</>
|
|
170
|
-
)
|
|
171
|
-
)}
|
|
172
|
-
</div>
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const VisitorCard: React.FC<VisitorCardProps> = ({
|
|
177
|
-
title,
|
|
178
|
-
amountText,
|
|
179
|
-
thumbnail,
|
|
180
|
-
poster: posterProp,
|
|
181
|
-
source: sourceProp,
|
|
182
|
-
mimeType = 'application/octet-stream',
|
|
183
|
-
detail,
|
|
184
|
-
onUnlock,
|
|
185
|
-
onDownload,
|
|
186
|
-
paymentStatus,
|
|
187
|
-
}) => {
|
|
235
|
+
const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
236
|
+
const {
|
|
237
|
+
title,
|
|
238
|
+
amountText,
|
|
239
|
+
thumbnail,
|
|
240
|
+
source: sourceProp,
|
|
241
|
+
mimeType = 'application/octet-stream',
|
|
242
|
+
detail,
|
|
243
|
+
onUnlock,
|
|
244
|
+
onDownload,
|
|
245
|
+
paymentStatus,
|
|
246
|
+
} = props
|
|
188
247
|
const [source, setSource] = useState(sourceProp)
|
|
189
|
-
const [poster, setPoster] = useState(posterProp)
|
|
190
248
|
const [loading, setLoading] = useState(false)
|
|
191
|
-
const [sourceReady, setSourceReady] = useState(false)
|
|
192
249
|
|
|
193
250
|
useEffect(() => {
|
|
194
251
|
if (sourceProp !== undefined) setSource(sourceProp)
|
|
195
252
|
}, [sourceProp])
|
|
196
253
|
|
|
197
|
-
useEffect(() => {
|
|
198
|
-
if (posterProp !== undefined) setPoster(posterProp)
|
|
199
|
-
}, [posterProp])
|
|
200
|
-
|
|
201
254
|
const isLocked = source === undefined
|
|
202
|
-
const LockIcon = getLockIcon(paymentStatus)
|
|
203
255
|
const sourceType = getSourceType(mimeType)
|
|
204
256
|
|
|
205
257
|
const handleUnlock = async () => {
|
|
@@ -208,12 +260,8 @@ const VisitorCard: React.FC<VisitorCardProps> = ({
|
|
|
208
260
|
try {
|
|
209
261
|
const result = await onUnlock()
|
|
210
262
|
setSource(result.source)
|
|
211
|
-
if (result.poster) setPoster(result.poster)
|
|
212
263
|
} catch (err) {
|
|
213
|
-
|
|
214
|
-
if (isDevBuild()) {
|
|
215
|
-
console.debug('[LockedAttachment] onUnlock failed', err)
|
|
216
|
-
}
|
|
264
|
+
if (isDevBuild()) console.debug('[LockedAttachment] onUnlock failed', err)
|
|
217
265
|
} finally {
|
|
218
266
|
setLoading(false)
|
|
219
267
|
}
|
|
@@ -221,67 +269,79 @@ const VisitorCard: React.FC<VisitorCardProps> = ({
|
|
|
221
269
|
|
|
222
270
|
let mediaPreview: React.ReactNode
|
|
223
271
|
if (sourceType === 'image') {
|
|
224
|
-
mediaPreview =
|
|
225
|
-
<
|
|
272
|
+
mediaPreview = (
|
|
273
|
+
<ImagePreview
|
|
274
|
+
source={source}
|
|
226
275
|
thumbnail={thumbnail}
|
|
227
276
|
mimeType={mimeType}
|
|
228
|
-
|
|
277
|
+
title={title}
|
|
278
|
+
paymentStatus={paymentStatus}
|
|
279
|
+
isLocked={isLocked}
|
|
229
280
|
/>
|
|
230
|
-
) : (
|
|
231
|
-
<div className="relative overflow-hidden bg-black/5">
|
|
232
|
-
<img
|
|
233
|
-
src={source}
|
|
234
|
-
alt={title}
|
|
235
|
-
className={`block w-full transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}
|
|
236
|
-
onLoad={() => setSourceReady(true)}
|
|
237
|
-
/>
|
|
238
|
-
</div>
|
|
239
281
|
)
|
|
240
282
|
} else if (sourceType === 'document') {
|
|
241
283
|
mediaPreview = (
|
|
242
|
-
<
|
|
243
|
-
<ThumbnailOrIcon
|
|
244
|
-
src={isLocked ? thumbnail : poster}
|
|
245
|
-
mimeType={mimeType}
|
|
246
|
-
/>
|
|
247
|
-
{isLocked && <LockOverlay icon={LockIcon} />}
|
|
248
|
-
</div>
|
|
249
|
-
)
|
|
250
|
-
} else {
|
|
251
|
-
mediaPreview = isLocked ? (
|
|
252
|
-
<LockedPreview
|
|
284
|
+
<DocumentPreview
|
|
253
285
|
thumbnail={thumbnail}
|
|
254
286
|
mimeType={mimeType}
|
|
255
|
-
|
|
287
|
+
paymentStatus={paymentStatus}
|
|
288
|
+
isLocked={isLocked}
|
|
256
289
|
/>
|
|
257
|
-
)
|
|
258
|
-
|
|
290
|
+
)
|
|
291
|
+
} else {
|
|
292
|
+
mediaPreview = (
|
|
293
|
+
<MediaPreview
|
|
259
294
|
source={source}
|
|
295
|
+
thumbnail={thumbnail}
|
|
260
296
|
mimeType={mimeType}
|
|
261
|
-
|
|
297
|
+
paymentStatus={paymentStatus}
|
|
298
|
+
isLocked={isLocked}
|
|
262
299
|
/>
|
|
263
300
|
)
|
|
264
301
|
}
|
|
265
302
|
|
|
266
303
|
return (
|
|
267
|
-
<div className="w-[280px] overflow-hidden rounded-3xl bg-white shadow-
|
|
304
|
+
<div className="w-[280px] select-none overflow-hidden rounded-3xl bg-white shadow-card">
|
|
268
305
|
{mediaPreview}
|
|
269
306
|
<div className="px-4 pb-3 pt-3">
|
|
270
307
|
<p className="mb-1.5 truncate text-base font-medium text-black">
|
|
271
308
|
{title}
|
|
272
309
|
</p>
|
|
273
|
-
<
|
|
274
|
-
mimeType
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
310
|
+
<div className="flex items-center gap-1">
|
|
311
|
+
{renderTypeIcon(mimeType, {
|
|
312
|
+
className: 'size-5 shrink-0 text-black/55',
|
|
313
|
+
weight: 'regular',
|
|
314
|
+
})}
|
|
315
|
+
{detail && (
|
|
316
|
+
<span className="text-xs font-medium text-black/55">{detail}</span>
|
|
317
|
+
)}
|
|
318
|
+
{paymentStatus === 'paid' ? (
|
|
319
|
+
<>
|
|
320
|
+
<span className="text-xs font-medium text-black/55">•</span>
|
|
321
|
+
<span className="text-xs font-medium text-[#008236]">
|
|
322
|
+
Purchased
|
|
323
|
+
</span>
|
|
324
|
+
<CheckCircleIcon
|
|
325
|
+
className="size-4 text-[#008236]"
|
|
326
|
+
weight="bold"
|
|
327
|
+
/>
|
|
328
|
+
</>
|
|
329
|
+
) : (
|
|
330
|
+
amountText && (
|
|
331
|
+
<>
|
|
332
|
+
<span className="text-xs font-medium text-black/55">•</span>
|
|
333
|
+
<span className="text-xs font-medium text-black/55">
|
|
334
|
+
{amountText}
|
|
335
|
+
</span>
|
|
336
|
+
</>
|
|
337
|
+
)
|
|
338
|
+
)}
|
|
339
|
+
</div>
|
|
279
340
|
<CardActions
|
|
280
341
|
isLocked={isLocked}
|
|
281
342
|
loading={loading}
|
|
282
343
|
paymentStatus={paymentStatus}
|
|
283
344
|
source={source}
|
|
284
|
-
LockIcon={LockIcon}
|
|
285
345
|
onUnlock={onUnlock ? handleUnlock : undefined}
|
|
286
346
|
onDownload={onDownload}
|
|
287
347
|
/>
|
|
@@ -13,23 +13,23 @@ const LockedAttachmentFallback = () => (
|
|
|
13
13
|
/>
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
+
type Never<T, K extends keyof T = keyof T> = { [P in K]?: never }
|
|
17
|
+
|
|
16
18
|
export type LockedAttachmentProps =
|
|
17
|
-
| (
|
|
18
|
-
| (
|
|
19
|
+
| (CreatorCardProps & Never<VisitorCardProps, 'onUnlock' | 'onDownload'>)
|
|
20
|
+
| (VisitorCardProps & Never<CreatorCardProps, 'isPreview' | 'placeholderTitle' | 'placeholderAmountText' | 'onDismiss'>)
|
|
19
21
|
|
|
20
22
|
const LockedAttachment = (props: LockedAttachmentProps) => {
|
|
21
|
-
if (props.
|
|
22
|
-
const { isCreator: _, ...rest } = props
|
|
23
|
+
if (props.onUnlock != null || props.onDownload != null) {
|
|
23
24
|
return (
|
|
24
25
|
<Suspense fallback={<LockedAttachmentFallback />}>
|
|
25
|
-
<
|
|
26
|
+
<VisitorCardLazy {...props} />
|
|
26
27
|
</Suspense>
|
|
27
28
|
)
|
|
28
29
|
}
|
|
29
|
-
const { isCreator: _, ...rest } = props
|
|
30
30
|
return (
|
|
31
31
|
<Suspense fallback={<LockedAttachmentFallback />}>
|
|
32
|
-
<
|
|
32
|
+
<CreatorCardLazy {...props} />
|
|
33
33
|
</Suspense>
|
|
34
34
|
)
|
|
35
35
|
}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import type { PaymentStatus } from '../../stream-custom-data'
|
|
2
2
|
|
|
3
|
-
/** Shared fields for creator and visitor locked-attachment cards (internal). */
|
|
4
3
|
export interface LockedAttachmentBaseProps {
|
|
4
|
+
title?: string
|
|
5
5
|
mimeType?: string
|
|
6
|
-
/** Blurred preview image shown in the locked/collapsed state. */
|
|
7
6
|
thumbnail?: string
|
|
8
|
-
/** Clean poster image passed to the media player. Falls back to thumbnail. */
|
|
9
|
-
poster?: string
|
|
10
|
-
/** Unlocked media URL. Undefined while locked or pending unlock. */
|
|
11
7
|
source?: string
|
|
12
8
|
detail?: string
|
|
13
9
|
amountText?: string
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ImageIcon,
|
|
12
12
|
SpeakerHighIcon,
|
|
13
13
|
VideoCameraIcon,
|
|
14
|
+
IconProps,
|
|
14
15
|
} from '@phosphor-icons/react'
|
|
15
16
|
import React from 'react'
|
|
16
17
|
|
|
@@ -46,7 +47,7 @@ export function getTypeIcon(mimeType: string): React.ElementType {
|
|
|
46
47
|
/** Use instead of `<TypeIcon />` where TypeIcon = getTypeIcon(mime) to satisfy react-hooks/static-components. */
|
|
47
48
|
export function renderTypeIcon(
|
|
48
49
|
mimeType: string,
|
|
49
|
-
props:
|
|
50
|
+
props: IconProps
|
|
50
51
|
): React.ReactElement {
|
|
51
52
|
return React.createElement(getTypeIcon(mimeType), props)
|
|
52
53
|
}
|
|
@@ -39,12 +39,20 @@ describe('getDocumentIconType', () => {
|
|
|
39
39
|
|
|
40
40
|
it('returns doc for Word types', () => {
|
|
41
41
|
expect(getDocumentIconType('application/msword')).toBe('doc')
|
|
42
|
-
expect(
|
|
42
|
+
expect(
|
|
43
|
+
getDocumentIconType(
|
|
44
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
45
|
+
)
|
|
46
|
+
).toBe('doc')
|
|
43
47
|
})
|
|
44
48
|
|
|
45
49
|
it('returns xls for Excel types', () => {
|
|
46
50
|
expect(getDocumentIconType('application/vnd.ms-excel')).toBe('xls')
|
|
47
|
-
expect(
|
|
51
|
+
expect(
|
|
52
|
+
getDocumentIconType(
|
|
53
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
54
|
+
)
|
|
55
|
+
).toBe('xls')
|
|
48
56
|
})
|
|
49
57
|
|
|
50
58
|
it('returns csv for text/csv', () => {
|
|
@@ -53,7 +61,11 @@ describe('getDocumentIconType', () => {
|
|
|
53
61
|
|
|
54
62
|
it('returns ppt for PowerPoint types', () => {
|
|
55
63
|
expect(getDocumentIconType('application/vnd.ms-powerpoint')).toBe('ppt')
|
|
56
|
-
expect(
|
|
64
|
+
expect(
|
|
65
|
+
getDocumentIconType(
|
|
66
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
67
|
+
)
|
|
68
|
+
).toBe('ppt')
|
|
57
69
|
})
|
|
58
70
|
|
|
59
71
|
it('returns zip for archive types', () => {
|
|
@@ -79,11 +91,17 @@ describe('getDocumentIconType', () => {
|
|
|
79
91
|
})
|
|
80
92
|
|
|
81
93
|
it('returns xls for macro-enabled Excel', () => {
|
|
82
|
-
expect(
|
|
94
|
+
expect(
|
|
95
|
+
getDocumentIconType('application/vnd.ms-excel.sheet.macroEnabled.12')
|
|
96
|
+
).toBe('xls')
|
|
83
97
|
})
|
|
84
98
|
|
|
85
99
|
it('returns ppt for macro-enabled PowerPoint', () => {
|
|
86
|
-
expect(
|
|
100
|
+
expect(
|
|
101
|
+
getDocumentIconType(
|
|
102
|
+
'application/vnd.ms-powerpoint.presentation.macroEnabled.12'
|
|
103
|
+
)
|
|
104
|
+
).toBe('ppt')
|
|
87
105
|
})
|
|
88
106
|
|
|
89
107
|
it('returns generic for unknown types', () => {
|
|
@@ -91,7 +109,11 @@ describe('getDocumentIconType', () => {
|
|
|
91
109
|
expect(getDocumentIconType('application/json')).toBe('generic')
|
|
92
110
|
expect(getDocumentIconType('text/html')).toBe('generic')
|
|
93
111
|
expect(getDocumentIconType('application/vnd.rar')).toBe('generic')
|
|
94
|
-
expect(getDocumentIconType('application/vnd.oasis.opendocument.text')).toBe(
|
|
95
|
-
|
|
112
|
+
expect(getDocumentIconType('application/vnd.oasis.opendocument.text')).toBe(
|
|
113
|
+
'generic'
|
|
114
|
+
)
|
|
115
|
+
expect(
|
|
116
|
+
getDocumentIconType('application/vnd.oasis.opendocument.spreadsheet')
|
|
117
|
+
).toBe('generic')
|
|
96
118
|
})
|
|
97
119
|
})
|
|
@@ -30,6 +30,8 @@ export function getSourceType(mimeType: string): AttachmentSourceType {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function getDocumentIconType(mimeType: string): DocumentIconType {
|
|
33
|
-
const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) =>
|
|
33
|
+
const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) =>
|
|
34
|
+
pattern.test(mimeType)
|
|
35
|
+
)
|
|
34
36
|
return match ? match[1] : 'generic'
|
|
35
37
|
}
|