@linktr.ee/messaging-react 1.33.3 → 1.35.0-rc-1777516745
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Card-BXKUE7JN.js +195 -0
- package/dist/Card-BXKUE7JN.js.map +1 -0
- package/dist/Card-J-rj8Q6w.js +167 -0
- package/dist/Card-J-rj8Q6w.js.map +1 -0
- package/dist/assets/index.css +1 -1
- package/dist/{index-Ydi1pTAi.js → index-B3H4GLek.js} +1067 -987
- package/dist/index-B3H4GLek.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/Avatar/Avatar.stories.tsx +29 -0
- package/src/components/Avatar/index.tsx +43 -20
- package/src/components/ChannelView.stories.tsx +45 -1
- package/src/components/ChannelView.tsx +9 -5
- package/src/components/CustomMessage/CustomMessage.stories.tsx +75 -15
- package/src/components/CustomMessage/MessageTag.stories.tsx +1 -1
- package/src/components/CustomMessage/index.tsx +8 -5
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +114 -0
- package/src/components/CustomTypingIndicator/index.tsx +101 -0
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +94 -66
- package/src/components/LockedAttachment/components/Creator/Card.tsx +55 -23
- package/src/components/LockedAttachment/components/Creator/CardThumbnail.tsx +2 -2
- package/src/components/LockedAttachment/components/Visitor/Card.tsx +9 -16
- package/src/components/LockedAttachment/components/Visitor/CardActions.tsx +3 -12
- package/src/components/LockedAttachment/components/Visitor/CardThumbnail.tsx +2 -2
- package/src/components/MediaMessage/MediaMessage.stories.tsx +77 -71
- package/src/components/MediaMessage/index.tsx +1 -1
- package/src/styles.css +27 -5
- package/dist/Card-BfA8wq8O.js +0 -181
- package/dist/Card-BfA8wq8O.js.map +0 -1
- package/dist/Card-Bpud_enW.js +0 -177
- package/dist/Card-Bpud_enW.js.map +0 -1
- package/dist/index-Ydi1pTAi.js.map +0 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { Event } from 'stream-chat'
|
|
3
|
+
import {
|
|
4
|
+
useChannelStateContext,
|
|
5
|
+
useChatContext,
|
|
6
|
+
useTypingContext,
|
|
7
|
+
} from 'stream-chat-react'
|
|
8
|
+
|
|
9
|
+
import { Avatar } from '../Avatar'
|
|
10
|
+
|
|
11
|
+
interface CustomTypingIndicatorProps {
|
|
12
|
+
threadList?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Circle = ({ cx, index }: { cx: string; index: number }) => (
|
|
16
|
+
<circle className="fill-pebble" cx={cx} cy="4" r="3.9">
|
|
17
|
+
<animateTransform
|
|
18
|
+
attributeName="transform"
|
|
19
|
+
type="translate"
|
|
20
|
+
values="0 0; 0 -2.25; 0 0;"
|
|
21
|
+
dur="900ms"
|
|
22
|
+
begin={`${120 * index}ms`} // 0ms, 120ms, 240ms
|
|
23
|
+
repeatCount="indefinite"
|
|
24
|
+
/>
|
|
25
|
+
</circle>
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const CustomTypingIndicator = ({ threadList }: CustomTypingIndicatorProps) => {
|
|
29
|
+
const { channel, channelConfig, thread } = useChannelStateContext()
|
|
30
|
+
const { client } = useChatContext()
|
|
31
|
+
const { typing = {} } = useTypingContext()
|
|
32
|
+
|
|
33
|
+
if (channelConfig?.typing_events === false) {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const typingInChannel = !threadList
|
|
38
|
+
? Object.values(typing).filter(
|
|
39
|
+
({ parent_id, user }: Event) =>
|
|
40
|
+
user?.id !== client.user?.id && !parent_id
|
|
41
|
+
)
|
|
42
|
+
: []
|
|
43
|
+
|
|
44
|
+
const typingInThread = threadList
|
|
45
|
+
? Object.values(typing).filter(
|
|
46
|
+
({ parent_id, user }: Event) =>
|
|
47
|
+
user?.id !== client.user?.id && parent_id === thread?.id
|
|
48
|
+
)
|
|
49
|
+
: []
|
|
50
|
+
|
|
51
|
+
const typingUsers = threadList ? typingInThread : typingInChannel
|
|
52
|
+
if (!typingUsers.length) {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const typingUser = typingUsers[0]?.user
|
|
57
|
+
const memberUser =
|
|
58
|
+
typingUser?.id && channel.state.members[typingUser.id]
|
|
59
|
+
? channel.state.members[typingUser.id].user
|
|
60
|
+
: undefined
|
|
61
|
+
|
|
62
|
+
const avatarId = typingUser?.id ?? memberUser?.id ?? 'typing-user'
|
|
63
|
+
const avatarName =
|
|
64
|
+
typingUser?.name ?? memberUser?.name ?? typingUser?.id ?? 'Typing user'
|
|
65
|
+
const avatarImage = typingUser?.image ?? memberUser?.image
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className="str-chat__typing-indicator !items-end"
|
|
70
|
+
data-testid="typing-indicator"
|
|
71
|
+
style={{ insetInlineStart: 0, insetInlineEnd: 'auto' }}
|
|
72
|
+
>
|
|
73
|
+
<div className="shrink-0" aria-hidden="true">
|
|
74
|
+
<Avatar
|
|
75
|
+
id={avatarId}
|
|
76
|
+
name={avatarName}
|
|
77
|
+
image={avatarImage}
|
|
78
|
+
size={24}
|
|
79
|
+
shape="circle"
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div className="px-4 py-3 rounded-lg bg-[#F1F0EE] h-12 flex flex-col justify-end">
|
|
84
|
+
<svg
|
|
85
|
+
aria-hidden="true"
|
|
86
|
+
className="block overflow-visible"
|
|
87
|
+
viewBox="0 0 32 8"
|
|
88
|
+
width="32"
|
|
89
|
+
height="8"
|
|
90
|
+
overflow="visible"
|
|
91
|
+
>
|
|
92
|
+
<Circle cx="4" index={0} />
|
|
93
|
+
<Circle cx="16" index={1} />
|
|
94
|
+
<Circle cx="28" index={2} />
|
|
95
|
+
</svg>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default CustomTypingIndicator
|
|
@@ -20,8 +20,8 @@ const AUDIO_THUMBNAIL = '/audio-thumbnail.jpg'
|
|
|
20
20
|
const AUDIO_SOURCE = '/audio-source.mp3'
|
|
21
21
|
|
|
22
22
|
const meta: Meta = {
|
|
23
|
-
title: '
|
|
24
|
-
parameters: { layout: '
|
|
23
|
+
title: 'LockedAttachment',
|
|
24
|
+
parameters: { layout: 'fullscreen' },
|
|
25
25
|
}
|
|
26
26
|
export default meta
|
|
27
27
|
|
|
@@ -62,10 +62,19 @@ const VARIANTS = [
|
|
|
62
62
|
thumbnailUnlockedUrl: DOCUMENT_THUMBNAIL,
|
|
63
63
|
sourceUrl: DOCUMENT_SOURCE,
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
label: 'Unknown',
|
|
67
|
+
title: 'Unknown Attachment',
|
|
68
|
+
mimeType: 'application/octet-stream',
|
|
69
|
+
detail: '2.4 MB',
|
|
70
|
+
thumbnailUrl: undefined,
|
|
71
|
+
thumbnailUnlockedUrl: undefined,
|
|
72
|
+
sourceUrl: DOCUMENT_SOURCE,
|
|
73
|
+
},
|
|
65
74
|
]
|
|
66
75
|
|
|
67
76
|
const Table = ({ children }: { children: React.ReactNode }) => (
|
|
68
|
-
<div className="p-12">
|
|
77
|
+
<div className="min-h-screen w-full p-12 bg-[#F9F7F4]">
|
|
69
78
|
<table className="border-separate border-spacing-4">{children}</table>
|
|
70
79
|
</div>
|
|
71
80
|
)
|
|
@@ -90,8 +99,9 @@ const TableHead = ({
|
|
|
90
99
|
</thead>
|
|
91
100
|
)
|
|
92
101
|
|
|
93
|
-
export const
|
|
102
|
+
export const Received: StoryFn = () => {
|
|
94
103
|
const [isPaid, setPaid] = useState<string | undefined>()
|
|
104
|
+
const [isUnlocking, setUnlocking] = useState<string | undefined>()
|
|
95
105
|
|
|
96
106
|
return (
|
|
97
107
|
<Table>
|
|
@@ -118,38 +128,24 @@ export const Visitor: StoryFn = () => {
|
|
|
118
128
|
detail={detail}
|
|
119
129
|
paymentStatus={isPaid === mimeType ? 'paid' : undefined}
|
|
120
130
|
amountText="AU$9.99"
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
})
|
|
131
|
+
isUnlocking={isUnlocking === mimeType}
|
|
132
|
+
onUnlockClick={() => {
|
|
133
|
+
setUnlocking(mimeType)
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
setUnlocking(undefined)
|
|
136
|
+
setPaid(mimeType)
|
|
137
|
+
}, 1500)
|
|
128
138
|
}}
|
|
139
|
+
onDownloadClick={() => alert('Download clicked')}
|
|
140
|
+
onFetchSource={async () => ({
|
|
141
|
+
sourceUrl: sourceUrl,
|
|
142
|
+
thumbnailUrl: thumbnailUnlockedUrl,
|
|
143
|
+
})}
|
|
129
144
|
/>
|
|
130
145
|
</td>
|
|
131
146
|
)
|
|
132
147
|
)}
|
|
133
148
|
</tr>
|
|
134
|
-
<tr>
|
|
135
|
-
<td className="text-xs text-right font-medium text-black/40 pr-4 align-top pt-2">
|
|
136
|
-
Purchased
|
|
137
|
-
</td>
|
|
138
|
-
{VARIANTS.map(({ title, mimeType, detail, thumbnailUrl }) => (
|
|
139
|
-
<td key={mimeType} className="align-top">
|
|
140
|
-
<LockedAttachment.Visitor
|
|
141
|
-
title={title}
|
|
142
|
-
thumbnailUrl={thumbnailUrl}
|
|
143
|
-
mimeType={mimeType}
|
|
144
|
-
detail={detail}
|
|
145
|
-
amountText="AU$9.99"
|
|
146
|
-
paymentStatus="paid"
|
|
147
|
-
onUnlockClick={() => alert('Unlock clicked')}
|
|
148
|
-
onDownloadClick={() => alert('Download clicked')}
|
|
149
|
-
/>
|
|
150
|
-
</td>
|
|
151
|
-
))}
|
|
152
|
-
</tr>
|
|
153
149
|
<tr>
|
|
154
150
|
<td className="text-xs text-right font-medium text-black/40 pr-4 align-top pt-2">
|
|
155
151
|
Unlocked
|
|
@@ -171,15 +167,12 @@ export const Visitor: StoryFn = () => {
|
|
|
171
167
|
detail={detail}
|
|
172
168
|
amountText="AU$9.99"
|
|
173
169
|
paymentStatus="paid"
|
|
174
|
-
onUnlockClick={() =>
|
|
170
|
+
onUnlockClick={() => alert('Unlock clicked')}
|
|
175
171
|
onDownloadClick={() => alert('Download clicked')}
|
|
176
|
-
onFetchSource={async () => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
thumbnailUrl: thumbnailUnlockedUrl,
|
|
181
|
-
})
|
|
182
|
-
}}
|
|
172
|
+
onFetchSource={async () => ({
|
|
173
|
+
sourceUrl: sourceUrl,
|
|
174
|
+
thumbnailUrl: thumbnailUnlockedUrl,
|
|
175
|
+
})}
|
|
183
176
|
/>
|
|
184
177
|
</td>
|
|
185
178
|
)
|
|
@@ -190,7 +183,7 @@ export const Visitor: StoryFn = () => {
|
|
|
190
183
|
)
|
|
191
184
|
}
|
|
192
185
|
|
|
193
|
-
export const
|
|
186
|
+
export const Sent: StoryFn = () => (
|
|
194
187
|
<Table>
|
|
195
188
|
<TableHead variants={VARIANTS} />
|
|
196
189
|
<tbody>
|
|
@@ -213,10 +206,13 @@ export const Creator: StoryFn = () => (
|
|
|
213
206
|
thumbnailUrl={thumbnailUrl}
|
|
214
207
|
mimeType={mimeType}
|
|
215
208
|
detail={detail}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
209
|
+
onFetchSource={async () => {
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
211
|
+
return {
|
|
212
|
+
sourceUrl: sourceUrl,
|
|
213
|
+
thumbnailUrl: thumbnailUnlockedUrl,
|
|
214
|
+
}
|
|
215
|
+
}}
|
|
220
216
|
/>
|
|
221
217
|
</td>
|
|
222
218
|
)
|
|
@@ -243,34 +239,66 @@ export const Creator: StoryFn = () => (
|
|
|
243
239
|
<td className="text-xs text-right font-medium text-black/40 pr-4 align-top pt-2">
|
|
244
240
|
Sent
|
|
245
241
|
</td>
|
|
246
|
-
{VARIANTS.map(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
242
|
+
{VARIANTS.map(
|
|
243
|
+
({
|
|
244
|
+
title,
|
|
245
|
+
mimeType,
|
|
246
|
+
detail,
|
|
247
|
+
thumbnailUrl,
|
|
248
|
+
thumbnailUnlockedUrl,
|
|
249
|
+
sourceUrl,
|
|
250
|
+
}) => (
|
|
251
|
+
<td key={mimeType} className="align-top">
|
|
252
|
+
<LockedAttachment.Creator
|
|
253
|
+
title={title}
|
|
254
|
+
thumbnailUrl={thumbnailUrl}
|
|
255
|
+
mimeType={mimeType}
|
|
256
|
+
detail={detail}
|
|
257
|
+
amountText="AU$9.99"
|
|
258
|
+
onFetchSource={async () => {
|
|
259
|
+
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
260
|
+
return {
|
|
261
|
+
sourceUrl: sourceUrl,
|
|
262
|
+
thumbnailUrl: thumbnailUnlockedUrl,
|
|
263
|
+
}
|
|
264
|
+
}}
|
|
265
|
+
/>
|
|
266
|
+
</td>
|
|
267
|
+
)
|
|
268
|
+
)}
|
|
257
269
|
</tr>
|
|
258
270
|
<tr>
|
|
259
271
|
<td className="text-xs text-right font-medium text-black/40 pr-4 align-top pt-2">
|
|
260
272
|
Sold
|
|
261
273
|
</td>
|
|
262
|
-
{VARIANTS.map(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
{VARIANTS.map(
|
|
275
|
+
({
|
|
276
|
+
title,
|
|
277
|
+
mimeType,
|
|
278
|
+
detail,
|
|
279
|
+
thumbnailUrl,
|
|
280
|
+
thumbnailUnlockedUrl,
|
|
281
|
+
sourceUrl,
|
|
282
|
+
}) => (
|
|
283
|
+
<td key={mimeType} className="align-top">
|
|
284
|
+
<LockedAttachment.Creator
|
|
285
|
+
title={title}
|
|
286
|
+
thumbnailUrl={thumbnailUrl}
|
|
287
|
+
mimeType={mimeType}
|
|
288
|
+
detail={detail}
|
|
289
|
+
amountText="AU$9.99"
|
|
290
|
+
paymentStatus="paid"
|
|
291
|
+
onFetchSource={async () => {
|
|
292
|
+
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
293
|
+
return {
|
|
294
|
+
sourceUrl: sourceUrl,
|
|
295
|
+
thumbnailUrl: thumbnailUnlockedUrl,
|
|
296
|
+
}
|
|
297
|
+
}}
|
|
298
|
+
/>
|
|
299
|
+
</td>
|
|
300
|
+
)
|
|
301
|
+
)}
|
|
274
302
|
</tr>
|
|
275
303
|
</tbody>
|
|
276
304
|
</Table>
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
XIcon,
|
|
8
8
|
} from '@phosphor-icons/react'
|
|
9
9
|
import classNames from 'classnames'
|
|
10
|
-
import React, { useCallback, useState } from 'react'
|
|
10
|
+
import React, { useCallback, useRef, useState } from 'react'
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
LockedAttachmentBaseProps,
|
|
@@ -21,8 +21,10 @@ import CardThumbnail from './CardThumbnail'
|
|
|
21
21
|
export interface CreatorCardProps extends LockedAttachmentBaseProps {
|
|
22
22
|
placeholderTitle?: string
|
|
23
23
|
placeholderAmountText?: string
|
|
24
|
+
isUnlocking?: boolean
|
|
24
25
|
onDismiss?: () => void
|
|
25
|
-
onPreviewClick?: () =>
|
|
26
|
+
onPreviewClick?: () => void
|
|
27
|
+
onFetchSource?: () => Promise<LockedAttachmentSource | void>
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const CreatorCard: React.FC<CreatorCardProps> = ({
|
|
@@ -34,29 +36,56 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
|
|
|
34
36
|
placeholderTitle = 'Attachment title',
|
|
35
37
|
placeholderAmountText,
|
|
36
38
|
paymentStatus,
|
|
39
|
+
isUnlocking,
|
|
37
40
|
onDismiss,
|
|
38
41
|
onPreviewClick,
|
|
42
|
+
onFetchSource,
|
|
39
43
|
}) => {
|
|
40
44
|
const [source, setSource] = useState<LockedAttachmentSource | undefined>()
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const
|
|
45
|
+
const [isPreviewVisible, setIsPreviewVisible] = useState(false)
|
|
46
|
+
const [isLoadingPreview, setLoadingPreview] = useState(false)
|
|
47
|
+
const fetchingRef = useRef(false)
|
|
48
|
+
|
|
49
|
+
const effectiveSourceUrl = isPreviewVisible ? source?.sourceUrl : undefined
|
|
50
|
+
const effectiveThumbnailUrl = isPreviewVisible ? (source?.thumbnailUrl ?? thumbnailUrl) : thumbnailUrl
|
|
51
|
+
const isBusy = isLoadingPreview || isUnlocking
|
|
52
|
+
|
|
53
|
+
const handleToggle = useCallback(async () => {
|
|
54
|
+
onPreviewClick?.()
|
|
55
|
+
if (isPreviewVisible) {
|
|
56
|
+
setIsPreviewVisible(false)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
46
59
|
if (source) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
setSource(onPreviewClick())
|
|
60
|
+
setIsPreviewVisible(true)
|
|
61
|
+
return
|
|
50
62
|
}
|
|
51
|
-
|
|
63
|
+
if (!onFetchSource) return
|
|
64
|
+
if (fetchingRef.current) return
|
|
65
|
+
fetchingRef.current = true
|
|
66
|
+
setLoadingPreview(true)
|
|
67
|
+
try {
|
|
68
|
+
const result = await onFetchSource()
|
|
69
|
+
if (result) {
|
|
70
|
+
setSource(result)
|
|
71
|
+
setIsPreviewVisible(true)
|
|
72
|
+
}
|
|
73
|
+
} finally {
|
|
74
|
+
fetchingRef.current = false
|
|
75
|
+
setLoadingPreview(false)
|
|
76
|
+
}
|
|
77
|
+
}, [isPreviewVisible, source, onPreviewClick, onFetchSource])
|
|
78
|
+
|
|
79
|
+
const toggleHandler = onFetchSource || onPreviewClick ? handleToggle : undefined
|
|
52
80
|
|
|
53
81
|
return (
|
|
54
|
-
<div className="relative w-[280px] select-none overflow-hidden rounded-[24px] bg-
|
|
82
|
+
<div className="relative w-[280px] select-none overflow-hidden rounded-[24px] bg-[#121110] shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]">
|
|
55
83
|
<CardHeader
|
|
56
84
|
onDismiss={onDismiss}
|
|
57
|
-
onToggle={
|
|
58
|
-
isExpanded={
|
|
85
|
+
onToggle={isBusy ? undefined : toggleHandler}
|
|
86
|
+
isExpanded={isPreviewVisible}
|
|
59
87
|
paymentStatus={paymentStatus}
|
|
88
|
+
isLoading={isBusy}
|
|
60
89
|
/>
|
|
61
90
|
|
|
62
91
|
<CardThumbnail
|
|
@@ -64,12 +93,12 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
|
|
|
64
93
|
sourceUrl={effectiveSourceUrl}
|
|
65
94
|
thumbnailUrl={effectiveThumbnailUrl}
|
|
66
95
|
mimeType={mimeType}
|
|
67
|
-
onToggle={
|
|
96
|
+
onToggle={isBusy ? undefined : toggleHandler}
|
|
68
97
|
/>
|
|
69
98
|
|
|
70
|
-
<div className="px-4 pb-3 pt-3
|
|
99
|
+
<div className="px-4 pb-3 pt-3">
|
|
71
100
|
<p
|
|
72
|
-
className={classNames('mb-
|
|
101
|
+
className={classNames('mb-0.5 truncate text-base font-medium', {
|
|
73
102
|
'text-white/30': !title,
|
|
74
103
|
'text-white': !!title,
|
|
75
104
|
})}
|
|
@@ -90,11 +119,8 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
|
|
|
90
119
|
{paymentStatus === 'paid' ? (
|
|
91
120
|
<React.Fragment>
|
|
92
121
|
<span className="text-xs font-medium text-white/55">•</span>
|
|
93
|
-
<span className="text-xs font-medium text-[#
|
|
94
|
-
<CheckCircleIcon
|
|
95
|
-
className="size-4 text-[#4ade80]"
|
|
96
|
-
weight="bold"
|
|
97
|
-
/>
|
|
122
|
+
<span className="text-xs font-medium text-[#34c759]">Sold</span>
|
|
123
|
+
<CheckCircleIcon className="size-4 text-[#34c759]" weight="bold" />
|
|
98
124
|
</React.Fragment>
|
|
99
125
|
) : (
|
|
100
126
|
<React.Fragment>
|
|
@@ -120,6 +146,7 @@ interface CardHeaderProps {
|
|
|
120
146
|
onToggle?: () => void
|
|
121
147
|
isExpanded?: boolean
|
|
122
148
|
paymentStatus?: PaymentStatus
|
|
149
|
+
isLoading?: boolean
|
|
123
150
|
}
|
|
124
151
|
|
|
125
152
|
const CardHeader: React.FC<CardHeaderProps> = ({
|
|
@@ -127,6 +154,7 @@ const CardHeader: React.FC<CardHeaderProps> = ({
|
|
|
127
154
|
onToggle,
|
|
128
155
|
isExpanded,
|
|
129
156
|
paymentStatus,
|
|
157
|
+
isLoading,
|
|
130
158
|
}) => {
|
|
131
159
|
if (onDismiss) {
|
|
132
160
|
return (
|
|
@@ -160,7 +188,11 @@ const CardHeader: React.FC<CardHeaderProps> = ({
|
|
|
160
188
|
|
|
161
189
|
return (
|
|
162
190
|
<div className="absolute top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white left-3">
|
|
163
|
-
|
|
191
|
+
{isLoading ? (
|
|
192
|
+
<span className="size-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
|
193
|
+
) : (
|
|
194
|
+
<Icon className="size-4" weight="fill" />
|
|
195
|
+
)}
|
|
164
196
|
</div>
|
|
165
197
|
)
|
|
166
198
|
}
|
|
@@ -27,7 +27,7 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
|
|
|
27
27
|
type="button"
|
|
28
28
|
disabled={!onToggle}
|
|
29
29
|
className={classNames(
|
|
30
|
-
'relative block w-full overflow-hidden border-0 bg-
|
|
30
|
+
'relative block w-full overflow-hidden border-0 bg-white/10 p-0 text-left appearance-none',
|
|
31
31
|
{ 'cursor-pointer': !!onToggle, 'cursor-default': !onToggle }
|
|
32
32
|
)}
|
|
33
33
|
onClick={onToggle}
|
|
@@ -51,7 +51,7 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
|
|
|
51
51
|
) : (
|
|
52
52
|
<div className="aspect-video flex items-center justify-center">
|
|
53
53
|
{renderTypeIcon(mimeType, {
|
|
54
|
-
className: 'size-12 text-
|
|
54
|
+
className: 'size-12 text-white/20',
|
|
55
55
|
weight: 'regular',
|
|
56
56
|
})}
|
|
57
57
|
</div>
|
|
@@ -111,37 +111,30 @@ const VisitorCard: React.FC<VisitorCardProps> = ({
|
|
|
111
111
|
paymentStatus={paymentStatus}
|
|
112
112
|
/>
|
|
113
113
|
|
|
114
|
-
<div className="px-4 pb-3 pt-3
|
|
115
|
-
<p className="mb-
|
|
114
|
+
<div className="px-4 pb-3 pt-3">
|
|
115
|
+
<p className="mb-0.5 truncate text-base font-medium text-black">
|
|
116
116
|
{title}
|
|
117
117
|
</p>
|
|
118
118
|
<div className="flex items-center gap-1">
|
|
119
119
|
{renderTypeIcon(mimeType, {
|
|
120
|
-
className: 'size-5 shrink-0 text-
|
|
120
|
+
className: 'size-5 shrink-0 text-black/55',
|
|
121
121
|
weight: 'regular',
|
|
122
122
|
})}
|
|
123
123
|
|
|
124
124
|
{detail && (
|
|
125
|
-
<span className="text-xs font-medium text-
|
|
125
|
+
<span className="text-xs font-medium text-black/55">{detail}</span>
|
|
126
126
|
)}
|
|
127
127
|
|
|
128
128
|
{paymentStatus === 'paid' ? (
|
|
129
129
|
<React.Fragment>
|
|
130
|
-
<span className="text-xs font-medium text-
|
|
131
|
-
<span className="text-xs font-medium text-[#
|
|
132
|
-
|
|
133
|
-
</span>
|
|
134
|
-
<CheckCircleIcon
|
|
135
|
-
className="size-4 text-[#4ade80]"
|
|
136
|
-
weight="bold"
|
|
137
|
-
/>
|
|
130
|
+
<span className="text-xs font-medium text-black/55">•</span>
|
|
131
|
+
<span className="text-xs font-medium text-[#008236]">Purchased</span>
|
|
132
|
+
<CheckCircleIcon className="size-4 text-[#008236]" weight="bold" />
|
|
138
133
|
</React.Fragment>
|
|
139
134
|
) : amountText != null ? (
|
|
140
135
|
<React.Fragment>
|
|
141
|
-
<span className="text-xs font-medium text-
|
|
142
|
-
<span className="text-xs font-medium text-
|
|
143
|
-
{amountText}
|
|
144
|
-
</span>
|
|
136
|
+
<span className="text-xs font-medium text-black/55">•</span>
|
|
137
|
+
<span className="text-xs font-medium text-black/55">{amountText}</span>
|
|
145
138
|
</React.Fragment>
|
|
146
139
|
) : null}
|
|
147
140
|
</div>
|
|
@@ -26,10 +26,10 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
26
26
|
type="button"
|
|
27
27
|
onClick={onUnlockClicked}
|
|
28
28
|
disabled={isUnlocking}
|
|
29
|
-
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-
|
|
29
|
+
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 hover:bg-[#2a2928] disabled:opacity-70"
|
|
30
30
|
>
|
|
31
31
|
{isUnlocking ? (
|
|
32
|
-
<
|
|
32
|
+
<span className="size-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
|
33
33
|
) : (
|
|
34
34
|
<React.Fragment>
|
|
35
35
|
<LockSimpleIcon className="size-4" weight="fill" />
|
|
@@ -47,7 +47,7 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
47
47
|
target="_blank"
|
|
48
48
|
rel="noopener noreferrer"
|
|
49
49
|
onClick={onDownloadClicked}
|
|
50
|
-
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-
|
|
50
|
+
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 hover:bg-[#2a2928]"
|
|
51
51
|
>
|
|
52
52
|
<DownloadSimpleIcon className="size-4" weight="bold" />
|
|
53
53
|
Download
|
|
@@ -58,14 +58,5 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
58
58
|
return null
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const LoadingDots: React.FC = () => {
|
|
62
|
-
return (
|
|
63
|
-
<span className="flex items-center gap-1">
|
|
64
|
-
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.3s]" />
|
|
65
|
-
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.15s]" />
|
|
66
|
-
<span className="size-1 rounded-full bg-white animate-bounce" />
|
|
67
|
-
</span>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
61
|
|
|
71
62
|
export default CardActions
|
|
@@ -38,11 +38,11 @@ const CardThumbnail: React.FC<CardThumbnailProps> = ({
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
|
-
<div className="relative overflow-hidden bg-black/5">
|
|
41
|
+
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
42
42
|
<img
|
|
43
43
|
src={sourceType === 'document' ? thumbnailUrl : sourceUrl}
|
|
44
44
|
alt={title}
|
|
45
|
-
className={`
|
|
45
|
+
className={`absolute inset-0 h-full w-full object-contain transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}
|
|
46
46
|
draggable={false}
|
|
47
47
|
onLoad={() => setSourceReady(true)}
|
|
48
48
|
/>
|