@linktr.ee/messaging-react 1.31.0-rc-1776677746 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Creator-DGe3CQ_j.js → Card-C5t3dZ5q.js} +177 -150
- package/dist/Card-C5t3dZ5q.js.map +1 -0
- package/dist/Card-Cn2va-Qr.js +205 -0
- package/dist/Card-Cn2va-Qr.js.map +1 -0
- package/dist/index.d.ts +35 -30
- package/dist/index.js +951 -956
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelView.tsx +24 -36
- package/src/components/CustomMessage/CustomMessage.stories.tsx +1 -14
- package/src/components/CustomMessage/context.tsx +20 -0
- package/src/components/CustomMessage/index.tsx +39 -28
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +8 -13
- package/src/components/LockedAttachment/components/Creator/Card.tsx +159 -0
- package/src/components/LockedAttachment/components/Creator/CardAudioPreview.tsx +161 -0
- package/src/components/LockedAttachment/components/Creator/CardCollapsedThumbnail.tsx +58 -0
- package/src/components/LockedAttachment/components/Creator/CardImagePreview.tsx +56 -0
- package/src/components/LockedAttachment/components/Creator/CardVideoPreview.tsx +91 -0
- package/src/components/LockedAttachment/components/Creator/index.tsx +2 -0
- package/src/components/LockedAttachment/components/Visitor/Card.tsx +186 -0
- package/src/components/LockedAttachment/components/Visitor/CardActions.tsx +71 -0
- package/src/components/LockedAttachment/components/Visitor/CardImagePreview.tsx +39 -0
- package/src/components/LockedAttachment/components/Visitor/CardMediaPreview.tsx +36 -0
- package/src/components/LockedAttachment/components/Visitor/CardThumbnailPreview.tsx +45 -0
- package/src/components/LockedAttachment/components/Visitor/index.ts +2 -0
- package/src/components/LockedAttachment/index.tsx +16 -23
- package/src/components/LockedAttachment/types.ts +14 -1
- package/src/components/MessagingShell/index.tsx +0 -6
- package/src/index.ts +4 -1
- package/src/types.ts +0 -21
- package/dist/Creator-DGe3CQ_j.js.map +0 -1
- package/dist/Visitor-DyJTWB2_.js +0 -204
- package/dist/Visitor-DyJTWB2_.js.map +0 -1
- package/src/components/LockedAttachment/components/Creator.tsx +0 -470
- package/src/components/LockedAttachment/components/Visitor.tsx +0 -356
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ArrowLeftIcon, DotsThreeIcon, StarIcon } from '@phosphor-icons/react'
|
|
2
2
|
import classNames from 'classnames'
|
|
3
3
|
import React, { useCallback, useRef } from 'react'
|
|
4
|
-
import { Channel as ChannelType
|
|
4
|
+
import { Channel as ChannelType } from 'stream-chat'
|
|
5
5
|
import {
|
|
6
6
|
Channel,
|
|
7
7
|
Window,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from 'stream-chat-react'
|
|
14
14
|
|
|
15
15
|
import { useChannelStar } from '../hooks/useChannelStar'
|
|
16
|
-
import type { ChannelViewProps
|
|
16
|
+
import type { ChannelViewProps } from '../types'
|
|
17
17
|
|
|
18
18
|
import { Avatar } from './Avatar'
|
|
19
19
|
import { ChannelInfoDialog } from './ChannelInfoDialog'
|
|
@@ -207,9 +207,6 @@ const ChannelViewInner: React.FC<{
|
|
|
207
207
|
onReportParticipantClick?: () => void
|
|
208
208
|
showStarButton?: boolean
|
|
209
209
|
chatbotVotingEnabled?: boolean
|
|
210
|
-
onAttachmentUnlockClick?: (message: LocalMessage, channel: ChannelType) => void
|
|
211
|
-
onAttachmentDownloadClick?: (message: LocalMessage, channel: ChannelType) => void
|
|
212
|
-
onAttachmentUnlocked?: (message: LocalMessage, channel: ChannelType) => LockedAttachmentSource
|
|
213
210
|
renderChannelBanner?: () => React.ReactNode
|
|
214
211
|
customProfileContent?: React.ReactNode
|
|
215
212
|
customChannelActions?: React.ReactNode
|
|
@@ -230,9 +227,6 @@ const ChannelViewInner: React.FC<{
|
|
|
230
227
|
onReportParticipantClick,
|
|
231
228
|
showStarButton = false,
|
|
232
229
|
chatbotVotingEnabled = false,
|
|
233
|
-
onAttachmentUnlockClick,
|
|
234
|
-
onAttachmentDownloadClick,
|
|
235
|
-
onAttachmentUnlocked,
|
|
236
230
|
renderChannelBanner,
|
|
237
231
|
customProfileContent,
|
|
238
232
|
customChannelActions,
|
|
@@ -278,30 +272,30 @@ const ChannelViewInner: React.FC<{
|
|
|
278
272
|
infoDialogRef.current?.close()
|
|
279
273
|
}, [])
|
|
280
274
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
275
|
+
// Prevents all message instances from unmounting when ChannelViewInner re-renders
|
|
276
|
+
const MessageOverride = useCallback(
|
|
277
|
+
(props: MessageUIComponentProps) => {
|
|
278
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
279
|
+
const { message } = useMessageContext('ChannelView')
|
|
280
|
+
const messageNode = (
|
|
281
|
+
<CustomMessage
|
|
282
|
+
{...props}
|
|
283
|
+
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
284
|
+
/>
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if (!renderMessage || !message) {
|
|
288
|
+
return messageNode
|
|
289
|
+
}
|
|
296
290
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
291
|
+
return renderMessage(messageNode, message)
|
|
292
|
+
},
|
|
293
|
+
[chatbotVotingEnabled, renderMessage]
|
|
294
|
+
)
|
|
300
295
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
>
|
|
296
|
+
return (
|
|
297
|
+
<>
|
|
298
|
+
<WithComponents overrides={{ Message: MessageOverride }}>
|
|
305
299
|
<Window>
|
|
306
300
|
{/* Custom Channel Header */}
|
|
307
301
|
<div className="p-4">
|
|
@@ -378,9 +372,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
378
372
|
onMessageSent,
|
|
379
373
|
showStarButton = false,
|
|
380
374
|
chatbotVotingEnabled = false,
|
|
381
|
-
onAttachmentUnlockClick,
|
|
382
|
-
onAttachmentDownloadClick,
|
|
383
|
-
onAttachmentUnlocked,
|
|
384
375
|
renderChannelBanner,
|
|
385
376
|
customProfileContent,
|
|
386
377
|
customChannelActions,
|
|
@@ -459,9 +450,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
459
450
|
onReportParticipantClick={onReportParticipantClick}
|
|
460
451
|
showStarButton={showStarButton}
|
|
461
452
|
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
462
|
-
onAttachmentUnlockClick={onAttachmentUnlockClick}
|
|
463
|
-
onAttachmentDownloadClick={onAttachmentDownloadClick}
|
|
464
|
-
onAttachmentUnlocked={onAttachmentUnlocked}
|
|
465
453
|
renderChannelBanner={renderChannelBanner}
|
|
466
454
|
customProfileContent={customProfileContent}
|
|
467
455
|
customChannelActions={customChannelActions}
|
|
@@ -132,28 +132,15 @@ const TemplateInner: React.FC<{
|
|
|
132
132
|
createMockChannel(client, messages).then(setChannel)
|
|
133
133
|
}, [client, messages])
|
|
134
134
|
|
|
135
|
-
const isVisitor = currentUser.id === storyUsers.visitor.id
|
|
136
|
-
|
|
137
135
|
const MessageComponent = React.useMemo(() => {
|
|
138
|
-
const handleAttachmentUnlockClicked = () => {
|
|
139
|
-
alert('Unlocking...')
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const handleAttachmentDownloadClicked = () => {
|
|
143
|
-
alert(`Downloading...`)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
136
|
return function CustomMessageComponent(props: MessageUIComponentProps) {
|
|
147
137
|
return (
|
|
148
138
|
<CustomMessage
|
|
149
139
|
{...props}
|
|
150
|
-
onAttachmentUnlockClick={isVisitor ? () => handleAttachmentUnlockClicked() : undefined}
|
|
151
|
-
onAttachmentDownloadClick={isVisitor ? () => handleAttachmentDownloadClicked() : undefined}
|
|
152
|
-
onAttachmentUnlocked={undefined}
|
|
153
140
|
/>
|
|
154
141
|
)
|
|
155
142
|
}
|
|
156
|
-
}, [
|
|
143
|
+
}, [])
|
|
157
144
|
|
|
158
145
|
if (!channel) {
|
|
159
146
|
return <div className="p-4">Loading...</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
import { defaultLockedAttachmentContextValue, type LockedAttachmentContextValue } from '../LockedAttachment/types'
|
|
4
|
+
|
|
5
|
+
export interface CustomMessageRegistry {
|
|
6
|
+
LockedAttachment: LockedAttachmentContextValue
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const customMessageDefaults: CustomMessageRegistry = {
|
|
10
|
+
LockedAttachment: defaultLockedAttachmentContextValue,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CustomMessageContext = createContext<Partial<CustomMessageRegistry>>({})
|
|
14
|
+
|
|
15
|
+
export const CustomMessageProvider = CustomMessageContext.Provider
|
|
16
|
+
|
|
17
|
+
export function useCustomMessage<K extends keyof CustomMessageRegistry>(key: K): CustomMessageRegistry[K] {
|
|
18
|
+
const ctx = useContext(CustomMessageContext)
|
|
19
|
+
return (ctx[key] ?? customMessageDefaults[key]) as CustomMessageRegistry[K]
|
|
20
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
2
|
import React, { useMemo, useState } from 'react'
|
|
3
|
-
import { type Channel, type LocalMessage } from 'stream-chat'
|
|
4
3
|
import {
|
|
5
4
|
Attachment as DefaultAttachment,
|
|
6
5
|
EditMessageModal as DefaultEditMessageModal,
|
|
@@ -32,29 +31,29 @@ import {
|
|
|
32
31
|
|
|
33
32
|
import { useMessageVote } from '../../hooks/useMessageVote'
|
|
34
33
|
import { Avatar } from '../Avatar'
|
|
35
|
-
import LockedAttachment
|
|
34
|
+
import LockedAttachment from '../LockedAttachment'
|
|
36
35
|
|
|
37
|
-
import {
|
|
36
|
+
import { useCustomMessage } from './context'
|
|
37
|
+
import {
|
|
38
|
+
MessageTag,
|
|
39
|
+
isAttachmentMessage,
|
|
40
|
+
isChatbotMessage,
|
|
41
|
+
isTipOnlyMessage,
|
|
42
|
+
} from './MessageTag'
|
|
38
43
|
import { MessageVoteButtons } from './MessageVoteButtons'
|
|
39
44
|
|
|
40
|
-
type
|
|
45
|
+
type CustomMessageUIComponentProps = MessageUIComponentProps & {
|
|
41
46
|
chatbotVotingEnabled?: boolean
|
|
42
|
-
onAttachmentUnlockClick?: (message: LocalMessage, channel: Channel) => void
|
|
43
|
-
onAttachmentDownloadClick?: (message: LocalMessage, channel: Channel) => void
|
|
44
|
-
onAttachmentUnlocked?: (message: LocalMessage, channel: Channel) => LockedAttachmentSource
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
type
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
type CustomMessageWithContextProps = MessageContextValue & {
|
|
50
|
+
chatbotVotingEnabled?: boolean
|
|
51
|
+
}
|
|
50
52
|
|
|
51
53
|
const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
52
54
|
const {
|
|
53
55
|
additionalMessageInputProps,
|
|
54
56
|
chatbotVotingEnabled,
|
|
55
|
-
onAttachmentUnlockClick,
|
|
56
|
-
onAttachmentDownloadClick,
|
|
57
|
-
onAttachmentUnlocked,
|
|
58
57
|
editing,
|
|
59
58
|
endOfGroup,
|
|
60
59
|
firstOfGroup,
|
|
@@ -72,6 +71,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
72
71
|
|
|
73
72
|
const { client } = useChatContext('CustomMessage')
|
|
74
73
|
const { channel } = useChannelStateContext('CustomMessage')
|
|
74
|
+
const { isUnlocking, onUnlockClick, onFetchSource, onDownloadClick } = useCustomMessage('LockedAttachment')
|
|
75
75
|
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false)
|
|
76
76
|
const reminder = useMessageReminder(message.id)
|
|
77
77
|
const { selected: voteState, voteUp, voteDown } = useMessageVote(message)
|
|
@@ -208,18 +208,32 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
208
208
|
}}
|
|
209
209
|
>
|
|
210
210
|
{isAttachment ? (
|
|
211
|
-
<div className={
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
211
|
+
<div className={`flex flex-col gap-1 ${isMine ? 'items-end' : 'items-start'}`}>
|
|
212
|
+
{isMine ? (
|
|
213
|
+
<LockedAttachment.Creator
|
|
214
|
+
title={message.metadata?.attachment_title}
|
|
215
|
+
mimeType={message.metadata?.attachment_mime_type}
|
|
216
|
+
thumbnailUrl={message.metadata?.attachment_thumbnail}
|
|
217
|
+
amountText={message.metadata?.amount_text}
|
|
218
|
+
detail={message.metadata?.attachment_detail}
|
|
219
|
+
paymentStatus={message.metadata?.payment_status}
|
|
220
|
+
/>
|
|
221
|
+
) : (
|
|
222
|
+
<LockedAttachment.Visitor
|
|
223
|
+
title={message.metadata?.attachment_title}
|
|
224
|
+
mimeType={message.metadata?.attachment_mime_type}
|
|
225
|
+
thumbnailUrl={message.metadata?.attachment_thumbnail}
|
|
226
|
+
amountText={message.metadata?.amount_text}
|
|
227
|
+
detail={message.metadata?.attachment_detail}
|
|
228
|
+
paymentStatus={message.metadata?.payment_status}
|
|
229
|
+
isUnlocking={isUnlocking(message.id)}
|
|
230
|
+
onUnlockClick={() => onUnlockClick?.(message, channel)}
|
|
231
|
+
onFetchSource={async () =>
|
|
232
|
+
await onFetchSource?.(message, channel)
|
|
233
|
+
}
|
|
234
|
+
onDownloadClick={() => onDownloadClick?.(message, channel)}
|
|
235
|
+
/>
|
|
236
|
+
)}
|
|
223
237
|
{message.text && (
|
|
224
238
|
<div className="str-chat__message-bubble-wrapper">
|
|
225
239
|
<div className="str-chat__message-bubble">
|
|
@@ -294,9 +308,6 @@ const MemoizedCustomMessage = React.memo(
|
|
|
294
308
|
CustomMessageWithContext,
|
|
295
309
|
(prev, next) => {
|
|
296
310
|
if (prev.chatbotVotingEnabled !== next.chatbotVotingEnabled) return false
|
|
297
|
-
if (prev.onAttachmentUnlockClick !== next.onAttachmentUnlockClick) return false
|
|
298
|
-
if (prev.onAttachmentDownloadClick !== next.onAttachmentDownloadClick) return false
|
|
299
|
-
if (prev.onAttachmentUnlocked !== next.onAttachmentUnlocked) return false
|
|
300
311
|
return areMessageUIPropsEqual(prev, next)
|
|
301
312
|
}
|
|
302
313
|
) as typeof CustomMessageWithContext
|
|
@@ -17,7 +17,6 @@ const AUDIO_SOURCE = '/audio-source.mp3'
|
|
|
17
17
|
|
|
18
18
|
const meta: Meta = {
|
|
19
19
|
title: 'Components/LockedAttachment',
|
|
20
|
-
component: LockedAttachment,
|
|
21
20
|
parameters: { layout: 'centered' },
|
|
22
21
|
}
|
|
23
22
|
export default meta
|
|
@@ -99,7 +98,7 @@ export const Visitor: StoryFn = () => (
|
|
|
99
98
|
</td>
|
|
100
99
|
{VARIANTS.map(({ title, mimeType, detail, thumbnailUrl }) => (
|
|
101
100
|
<td key={mimeType} className="align-top">
|
|
102
|
-
<LockedAttachment
|
|
101
|
+
<LockedAttachment.Visitor
|
|
103
102
|
title={title}
|
|
104
103
|
amountText="AU$9.99"
|
|
105
104
|
thumbnailUrl={thumbnailUrl}
|
|
@@ -107,7 +106,6 @@ export const Visitor: StoryFn = () => (
|
|
|
107
106
|
detail={detail}
|
|
108
107
|
onUnlockClick={() => {}}
|
|
109
108
|
onDownloadClick={() => {}}
|
|
110
|
-
onUnlocked={undefined}
|
|
111
109
|
/>
|
|
112
110
|
</td>
|
|
113
111
|
))}
|
|
@@ -118,7 +116,7 @@ export const Visitor: StoryFn = () => (
|
|
|
118
116
|
</td>
|
|
119
117
|
{VARIANTS.map(({ title, mimeType, detail, thumbnailUrl }) => (
|
|
120
118
|
<td key={mimeType} className="align-top">
|
|
121
|
-
<LockedAttachment
|
|
119
|
+
<LockedAttachment.Visitor
|
|
122
120
|
title={title}
|
|
123
121
|
amountText="AU$9.99"
|
|
124
122
|
thumbnailUrl={thumbnailUrl}
|
|
@@ -127,7 +125,6 @@ export const Visitor: StoryFn = () => (
|
|
|
127
125
|
paymentStatus="paid"
|
|
128
126
|
onUnlockClick={() => {}}
|
|
129
127
|
onDownloadClick={() => {}}
|
|
130
|
-
onUnlocked={undefined}
|
|
131
128
|
/>
|
|
132
129
|
</td>
|
|
133
130
|
))}
|
|
@@ -138,16 +135,15 @@ export const Visitor: StoryFn = () => (
|
|
|
138
135
|
</td>
|
|
139
136
|
{VARIANTS.map(({ title, mimeType, detail, thumbnailUrl, sourceUrl }) => (
|
|
140
137
|
<td key={mimeType} className="align-top">
|
|
141
|
-
<LockedAttachment
|
|
138
|
+
<LockedAttachment.Visitor
|
|
142
139
|
title={title}
|
|
143
140
|
thumbnailUrl={thumbnailUrl}
|
|
144
141
|
mimeType={mimeType}
|
|
145
142
|
detail={detail}
|
|
146
143
|
amountText="AU$9.99"
|
|
147
144
|
paymentStatus="paid"
|
|
148
|
-
onUnlockClick={() => {}}
|
|
145
|
+
onUnlockClick={() => Promise.resolve({ sourceUrl })}
|
|
149
146
|
onDownloadClick={() => {}}
|
|
150
|
-
onUnlocked={() => ({ sourceUrl })}
|
|
151
147
|
/>
|
|
152
148
|
</td>
|
|
153
149
|
))}
|
|
@@ -166,7 +162,7 @@ export const Creator: StoryFn = () => (
|
|
|
166
162
|
</td>
|
|
167
163
|
{VARIANTS.map(({ mimeType, detail, thumbnailUrl, sourceUrl }) => (
|
|
168
164
|
<td key={mimeType} className="align-top">
|
|
169
|
-
<LockedAttachment
|
|
165
|
+
<LockedAttachment.Creator
|
|
170
166
|
isPreview={true}
|
|
171
167
|
thumbnailUrl={thumbnailUrl}
|
|
172
168
|
sourceUrl={sourceUrl}
|
|
@@ -184,7 +180,7 @@ export const Creator: StoryFn = () => (
|
|
|
184
180
|
</td>
|
|
185
181
|
{VARIANTS.map(({ title, mimeType, detail, thumbnailUrl, sourceUrl }) => (
|
|
186
182
|
<td key={mimeType} className="align-top">
|
|
187
|
-
<LockedAttachment
|
|
183
|
+
<LockedAttachment.Creator
|
|
188
184
|
title={title}
|
|
189
185
|
thumbnailUrl={thumbnailUrl}
|
|
190
186
|
sourceUrl={sourceUrl}
|
|
@@ -202,7 +198,7 @@ export const Creator: StoryFn = () => (
|
|
|
202
198
|
</td>
|
|
203
199
|
{VARIANTS.map(({ title, mimeType, detail, thumbnailUrl, sourceUrl }) => (
|
|
204
200
|
<td key={mimeType} className="align-top">
|
|
205
|
-
<LockedAttachment
|
|
201
|
+
<LockedAttachment.Creator
|
|
206
202
|
title={title}
|
|
207
203
|
thumbnailUrl={thumbnailUrl}
|
|
208
204
|
sourceUrl={sourceUrl}
|
|
@@ -220,7 +216,7 @@ export const Creator: StoryFn = () => (
|
|
|
220
216
|
</td>
|
|
221
217
|
{VARIANTS.map(({ title, mimeType, detail, thumbnailUrl, sourceUrl }) => (
|
|
222
218
|
<td key={mimeType} className="align-top">
|
|
223
|
-
<LockedAttachment
|
|
219
|
+
<LockedAttachment.Creator
|
|
224
220
|
title={title}
|
|
225
221
|
thumbnailUrl={thumbnailUrl}
|
|
226
222
|
sourceUrl={sourceUrl}
|
|
@@ -236,4 +232,3 @@ export const Creator: StoryFn = () => (
|
|
|
236
232
|
</tbody>
|
|
237
233
|
</Table>
|
|
238
234
|
)
|
|
239
|
-
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CheckCircleIcon,
|
|
3
|
+
LockIcon,
|
|
4
|
+
LockOpenIcon,
|
|
5
|
+
XIcon,
|
|
6
|
+
} from '@phosphor-icons/react'
|
|
7
|
+
import classNames from 'classnames'
|
|
8
|
+
import React from 'react'
|
|
9
|
+
|
|
10
|
+
import type { LockedAttachmentBaseProps } from '../../types'
|
|
11
|
+
import { renderTypeIcon } from '../../utils/icons'
|
|
12
|
+
import { getSourceType } from '../../utils/mimeType'
|
|
13
|
+
|
|
14
|
+
import AudioPreview from './CardAudioPreview'
|
|
15
|
+
import CollapsedThumbnail from './CardCollapsedThumbnail'
|
|
16
|
+
import ImagePreview from './CardImagePreview'
|
|
17
|
+
import VideoPreview from './CardVideoPreview'
|
|
18
|
+
|
|
19
|
+
export interface CreatorCardProps extends LockedAttachmentBaseProps {
|
|
20
|
+
isPreview?: boolean
|
|
21
|
+
placeholderTitle?: string
|
|
22
|
+
placeholderAmountText?: string
|
|
23
|
+
sourceUrl?: string
|
|
24
|
+
onDismiss?: () => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const CreatorCard: React.FC<CreatorCardProps> = (props) => {
|
|
28
|
+
const {
|
|
29
|
+
title,
|
|
30
|
+
mimeType = 'application/octet-stream',
|
|
31
|
+
thumbnailUrl,
|
|
32
|
+
sourceUrl,
|
|
33
|
+
detail,
|
|
34
|
+
amountText,
|
|
35
|
+
placeholderTitle = 'Attachment title',
|
|
36
|
+
placeholderAmountText,
|
|
37
|
+
paymentStatus,
|
|
38
|
+
onDismiss,
|
|
39
|
+
isPreview = false,
|
|
40
|
+
} = props
|
|
41
|
+
const sourceType = getSourceType(mimeType)
|
|
42
|
+
const displayAmountText = amountText ?? placeholderAmountText
|
|
43
|
+
const isPlaceholderAmount = !amountText && !!placeholderAmountText
|
|
44
|
+
|
|
45
|
+
let mediaPreview: React.ReactNode
|
|
46
|
+
if (isPreview && sourceType === 'audio') {
|
|
47
|
+
mediaPreview = (
|
|
48
|
+
<AudioPreview
|
|
49
|
+
key={sourceUrl}
|
|
50
|
+
sourceUrl={sourceUrl}
|
|
51
|
+
thumbnailUrl={thumbnailUrl}
|
|
52
|
+
mimeType={mimeType}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
} else if (isPreview && sourceType === 'video') {
|
|
56
|
+
mediaPreview = (
|
|
57
|
+
<VideoPreview
|
|
58
|
+
key={sourceUrl}
|
|
59
|
+
sourceUrl={sourceUrl}
|
|
60
|
+
thumbnailUrl={thumbnailUrl}
|
|
61
|
+
mimeType={mimeType}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
} else if (isPreview && sourceType === 'image') {
|
|
65
|
+
mediaPreview = (
|
|
66
|
+
<ImagePreview
|
|
67
|
+
key={sourceUrl}
|
|
68
|
+
sourceUrl={sourceUrl}
|
|
69
|
+
thumbnailUrl={thumbnailUrl}
|
|
70
|
+
mimeType={mimeType}
|
|
71
|
+
title={title}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
} else {
|
|
75
|
+
const lockedOverlayIcon = onDismiss
|
|
76
|
+
? undefined
|
|
77
|
+
: paymentStatus === 'paid'
|
|
78
|
+
? LockOpenIcon
|
|
79
|
+
: LockIcon
|
|
80
|
+
mediaPreview = (
|
|
81
|
+
<CollapsedThumbnail
|
|
82
|
+
thumbnailUrl={thumbnailUrl}
|
|
83
|
+
mimeType={mimeType}
|
|
84
|
+
overlayIcon={lockedOverlayIcon}
|
|
85
|
+
darkOverlay
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="relative w-[280px] select-none overflow-hidden rounded-[24px] bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]">
|
|
92
|
+
{onDismiss && (
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={onDismiss}
|
|
96
|
+
className="absolute right-3 top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white"
|
|
97
|
+
aria-label="Dismiss attachment"
|
|
98
|
+
>
|
|
99
|
+
<XIcon className="size-4" weight="bold" />
|
|
100
|
+
</button>
|
|
101
|
+
)}
|
|
102
|
+
{mediaPreview}
|
|
103
|
+
<div className="px-4 pb-3 pt-3">
|
|
104
|
+
<p
|
|
105
|
+
className={classNames('mb-1.5 truncate text-base font-medium', {
|
|
106
|
+
'text-black/30': !title,
|
|
107
|
+
'text-black': !!title,
|
|
108
|
+
})}
|
|
109
|
+
>
|
|
110
|
+
{title || placeholderTitle}
|
|
111
|
+
</p>
|
|
112
|
+
<div className="flex items-center gap-1">
|
|
113
|
+
{renderTypeIcon(mimeType, {
|
|
114
|
+
className: 'size-5 shrink-0 text-black/55',
|
|
115
|
+
weight: 'regular',
|
|
116
|
+
})}
|
|
117
|
+
{detail && (
|
|
118
|
+
<span className="text-xs font-medium text-black/55">{detail}</span>
|
|
119
|
+
)}
|
|
120
|
+
{paymentStatus === 'paid' ? (
|
|
121
|
+
<>
|
|
122
|
+
<span className="text-xs font-medium text-black/55">•</span>
|
|
123
|
+
<span className="text-xs font-medium text-[#008236]">
|
|
124
|
+
Purchased
|
|
125
|
+
</span>
|
|
126
|
+
<CheckCircleIcon
|
|
127
|
+
className="size-4 text-[#008236]"
|
|
128
|
+
weight="bold"
|
|
129
|
+
/>
|
|
130
|
+
</>
|
|
131
|
+
) : (
|
|
132
|
+
displayAmountText && (
|
|
133
|
+
<>
|
|
134
|
+
<span
|
|
135
|
+
className={classNames('text-xs font-medium', {
|
|
136
|
+
'text-black/30': isPlaceholderAmount,
|
|
137
|
+
'text-black/55': !isPlaceholderAmount,
|
|
138
|
+
})}
|
|
139
|
+
>
|
|
140
|
+
•
|
|
141
|
+
</span>
|
|
142
|
+
<span
|
|
143
|
+
className={classNames('text-xs font-medium', {
|
|
144
|
+
'text-black/30': isPlaceholderAmount,
|
|
145
|
+
'text-black/55': !isPlaceholderAmount,
|
|
146
|
+
})}
|
|
147
|
+
>
|
|
148
|
+
{displayAmountText}
|
|
149
|
+
</span>
|
|
150
|
+
</>
|
|
151
|
+
)
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default CreatorCard
|