@linktr.ee/messaging-react 1.26.1 → 1.28.0-rc-1776225927
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-D38dWn2X.js +318 -0
- package/dist/Creator-D38dWn2X.js.map +1 -0
- package/dist/MediaPlayer-DE9MC6k6.js +599 -0
- package/dist/MediaPlayer-DE9MC6k6.js.map +1 -0
- package/dist/Preview-DqAv16NS.js +87 -0
- package/dist/Preview-DqAv16NS.js.map +1 -0
- package/dist/Visitor-BG-9-3HU.js +199 -0
- package/dist/Visitor-BG-9-3HU.js.map +1 -0
- package/dist/dash.all.min-Duv4lvGS.js +18858 -0
- package/dist/dash.all.min-Duv4lvGS.js.map +1 -0
- package/dist/hls-Bogc7CBn.js +21710 -0
- package/dist/hls-Bogc7CBn.js.map +1 -0
- package/dist/index-Da-xN4Yq.js +16142 -0
- package/dist/index-Da-xN4Yq.js.map +1 -0
- package/dist/index-Dj9rqWcU.js +69 -0
- package/dist/index-Dj9rqWcU.js.map +1 -0
- package/dist/index.d.ts +74 -10
- package/dist/index.js +979 -934
- package/dist/index.js.map +1 -1
- package/dist/mixin-B6jYfIcp.js +808 -0
- package/dist/mixin-B6jYfIcp.js.map +1 -0
- package/dist/react-BxlQMOfz.js +419 -0
- package/dist/react-BxlQMOfz.js.map +1 -0
- package/dist/react-COAP-MIW.js +377 -0
- package/dist/react-COAP-MIW.js.map +1 -0
- package/dist/react-Cn4WlMcl.js +3108 -0
- package/dist/react-Cn4WlMcl.js.map +1 -0
- package/dist/react-CwTJArKY.js +459 -0
- package/dist/react-CwTJArKY.js.map +1 -0
- package/dist/react-DkfS_atT.js +373 -0
- package/dist/react-DkfS_atT.js.map +1 -0
- package/dist/react-Pea5fum1.js +286 -0
- package/dist/react-Pea5fum1.js.map +1 -0
- package/dist/react-RiBbsUDd.js +534 -0
- package/dist/react-RiBbsUDd.js.map +1 -0
- package/dist/react-dS1WBxxz.js +238 -0
- package/dist/react-dS1WBxxz.js.map +1 -0
- package/package.json +2 -1
- package/src/components/ChannelView.tsx +12 -2
- package/src/components/CustomMessage/CustomMessage.stories.tsx +173 -41
- package/src/components/CustomMessage/MessageTag.tsx +5 -0
- package/src/components/CustomMessage/index.tsx +43 -4
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +343 -0
- package/src/components/LockedAttachment/components/Creator.tsx +469 -0
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +359 -0
- package/src/components/LockedAttachment/components/Visitor.tsx +356 -0
- package/src/components/LockedAttachment/index.tsx +39 -0
- package/src/components/LockedAttachment/types.ts +17 -0
- package/src/components/LockedAttachment/utils/icons.ts +53 -0
- package/src/components/LockedAttachment/utils/mimeType.test.ts +119 -0
- package/src/components/LockedAttachment/utils/mimeType.ts +37 -0
- package/src/components/ParticipantPicker/index.tsx +8 -1
- package/src/hooks/useParticipants.ts +3 -2
- package/src/index.ts +4 -0
- package/src/stories/decorators/storyUser.tsx +37 -0
- package/src/stream-custom-data.ts +9 -3
- package/src/types.ts +20 -1
- package/src/utils/isDevBuild.ts +10 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { Suspense } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { CreatorCardProps } from './components/Creator'
|
|
4
|
+
import type { VisitorCardProps } from './components/Visitor'
|
|
5
|
+
|
|
6
|
+
const CreatorCardLazy = React.lazy(() => import('./components/Creator'))
|
|
7
|
+
const VisitorCardLazy = React.lazy(() => import('./components/Visitor'))
|
|
8
|
+
|
|
9
|
+
const LockedAttachmentFallback = () => (
|
|
10
|
+
<div
|
|
11
|
+
className="w-[280px] min-h-[200px] animate-pulse rounded-3xl bg-black/[0.06] shadow-[0px_0px_0px_1px_rgba(0,0,0,0.04),0px_1px_2px_0px_rgba(0,0,0,0.04)]"
|
|
12
|
+
aria-hidden
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
export type LockedAttachmentProps =
|
|
17
|
+
| ({ isCreator: true } & CreatorCardProps)
|
|
18
|
+
| ({ isCreator?: false } & VisitorCardProps)
|
|
19
|
+
|
|
20
|
+
const LockedAttachment = (props: LockedAttachmentProps) => {
|
|
21
|
+
if (props.isCreator) {
|
|
22
|
+
const { isCreator: _, ...rest } = props
|
|
23
|
+
return (
|
|
24
|
+
<Suspense fallback={<LockedAttachmentFallback />}>
|
|
25
|
+
<CreatorCardLazy {...rest} />
|
|
26
|
+
</Suspense>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
const { isCreator: _, ...rest } = props
|
|
30
|
+
return (
|
|
31
|
+
<Suspense fallback={<LockedAttachmentFallback />}>
|
|
32
|
+
<VisitorCardLazy {...rest} />
|
|
33
|
+
</Suspense>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default LockedAttachment
|
|
38
|
+
export type { CreatorCardProps, VisitorCardProps }
|
|
39
|
+
export type { PaymentStatus, LockedAttachmentSource } from './types'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PaymentStatus } from '../../stream-custom-data'
|
|
2
|
+
|
|
3
|
+
/** Shared fields for creator and visitor locked-attachment cards (internal). */
|
|
4
|
+
export interface LockedAttachmentBaseProps {
|
|
5
|
+
title?: string
|
|
6
|
+
mimeType?: string
|
|
7
|
+
/** Preview image. Video/image: pass blurred version. Audio/document: pass unblurred version. */
|
|
8
|
+
thumbnail?: string
|
|
9
|
+
/** Unlocked media URL. Undefined while locked or pending unlock. */
|
|
10
|
+
source?: string
|
|
11
|
+
detail?: string
|
|
12
|
+
amountText?: string
|
|
13
|
+
paymentStatus?: PaymentStatus
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type { PaymentStatus }
|
|
17
|
+
export type { LockedAttachmentSource } from '../../types'
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FileIcon,
|
|
3
|
+
FileCsvIcon,
|
|
4
|
+
FileDocIcon,
|
|
5
|
+
FileMdIcon,
|
|
6
|
+
FilePdfIcon,
|
|
7
|
+
FilePptIcon,
|
|
8
|
+
FileTextIcon,
|
|
9
|
+
FileXlsIcon,
|
|
10
|
+
FileZipIcon,
|
|
11
|
+
ImageIcon,
|
|
12
|
+
SpeakerHighIcon,
|
|
13
|
+
VideoCameraIcon,
|
|
14
|
+
IconProps,
|
|
15
|
+
} from '@phosphor-icons/react'
|
|
16
|
+
import React from 'react'
|
|
17
|
+
|
|
18
|
+
import { getDocumentIconType, getSourceType } from './mimeType'
|
|
19
|
+
import type { AttachmentSourceType } from './mimeType'
|
|
20
|
+
|
|
21
|
+
export const MEDIA_TYPE_ICON: Record<AttachmentSourceType, React.ElementType> =
|
|
22
|
+
{
|
|
23
|
+
video: VideoCameraIcon,
|
|
24
|
+
audio: SpeakerHighIcon,
|
|
25
|
+
image: ImageIcon,
|
|
26
|
+
document: FileIcon,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DOCUMENT_ICON_COMPONENT = {
|
|
30
|
+
pdf: FilePdfIcon,
|
|
31
|
+
doc: FileDocIcon,
|
|
32
|
+
xls: FileXlsIcon,
|
|
33
|
+
csv: FileCsvIcon,
|
|
34
|
+
ppt: FilePptIcon,
|
|
35
|
+
zip: FileZipIcon,
|
|
36
|
+
text: FileTextIcon,
|
|
37
|
+
markdown: FileMdIcon,
|
|
38
|
+
generic: FileIcon,
|
|
39
|
+
} as const
|
|
40
|
+
|
|
41
|
+
export function getTypeIcon(mimeType: string): React.ElementType {
|
|
42
|
+
const sourceType = getSourceType(mimeType)
|
|
43
|
+
if (sourceType !== 'document') return MEDIA_TYPE_ICON[sourceType]
|
|
44
|
+
return DOCUMENT_ICON_COMPONENT[getDocumentIconType(mimeType)]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Use instead of `<TypeIcon />` where TypeIcon = getTypeIcon(mime) to satisfy react-hooks/static-components. */
|
|
48
|
+
export function renderTypeIcon(
|
|
49
|
+
mimeType: string,
|
|
50
|
+
props: IconProps
|
|
51
|
+
): React.ReactElement {
|
|
52
|
+
return React.createElement(getTypeIcon(mimeType), props)
|
|
53
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { getDocumentIconType, getSourceType } from './mimeType'
|
|
2
|
+
|
|
3
|
+
describe('getSourceType', () => {
|
|
4
|
+
it('returns video for video/* types', () => {
|
|
5
|
+
expect(getSourceType('video/mp4')).toBe('video')
|
|
6
|
+
expect(getSourceType('video/webm')).toBe('video')
|
|
7
|
+
expect(getSourceType('video/quicktime')).toBe('video')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('returns audio for audio/* types', () => {
|
|
11
|
+
expect(getSourceType('audio/mpeg')).toBe('audio')
|
|
12
|
+
expect(getSourceType('audio/mp4')).toBe('audio')
|
|
13
|
+
expect(getSourceType('audio/ogg')).toBe('audio')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('returns image for image/* types', () => {
|
|
17
|
+
expect(getSourceType('image/jpeg')).toBe('image')
|
|
18
|
+
expect(getSourceType('image/png')).toBe('image')
|
|
19
|
+
expect(getSourceType('image/webp')).toBe('image')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('returns document for application/* types', () => {
|
|
23
|
+
expect(getSourceType('application/pdf')).toBe('document')
|
|
24
|
+
expect(getSourceType('application/msword')).toBe('document')
|
|
25
|
+
expect(getSourceType('application/zip')).toBe('document')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns document for text/* types', () => {
|
|
29
|
+
expect(getSourceType('text/plain')).toBe('document')
|
|
30
|
+
expect(getSourceType('text/csv')).toBe('document')
|
|
31
|
+
expect(getSourceType('text/markdown')).toBe('document')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('getDocumentIconType', () => {
|
|
36
|
+
it('returns pdf for application/pdf', () => {
|
|
37
|
+
expect(getDocumentIconType('application/pdf')).toBe('pdf')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('returns doc for Word types', () => {
|
|
41
|
+
expect(getDocumentIconType('application/msword')).toBe('doc')
|
|
42
|
+
expect(
|
|
43
|
+
getDocumentIconType(
|
|
44
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
45
|
+
)
|
|
46
|
+
).toBe('doc')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('returns xls for Excel types', () => {
|
|
50
|
+
expect(getDocumentIconType('application/vnd.ms-excel')).toBe('xls')
|
|
51
|
+
expect(
|
|
52
|
+
getDocumentIconType(
|
|
53
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
54
|
+
)
|
|
55
|
+
).toBe('xls')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('returns csv for text/csv', () => {
|
|
59
|
+
expect(getDocumentIconType('text/csv')).toBe('csv')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('returns ppt for PowerPoint types', () => {
|
|
63
|
+
expect(getDocumentIconType('application/vnd.ms-powerpoint')).toBe('ppt')
|
|
64
|
+
expect(
|
|
65
|
+
getDocumentIconType(
|
|
66
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
67
|
+
)
|
|
68
|
+
).toBe('ppt')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('returns zip for archive types', () => {
|
|
72
|
+
expect(getDocumentIconType('application/zip')).toBe('zip')
|
|
73
|
+
expect(getDocumentIconType('application/x-zip-compressed')).toBe('zip')
|
|
74
|
+
expect(getDocumentIconType('application/gzip')).toBe('zip')
|
|
75
|
+
expect(getDocumentIconType('application/x-gzip')).toBe('zip')
|
|
76
|
+
expect(getDocumentIconType('application/x-rar-compressed')).toBe('zip')
|
|
77
|
+
expect(getDocumentIconType('application/x-7z-compressed')).toBe('zip')
|
|
78
|
+
expect(getDocumentIconType('application/x-tar')).toBe('zip')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('returns text for plain text and rtf', () => {
|
|
82
|
+
expect(getDocumentIconType('text/plain')).toBe('text')
|
|
83
|
+
expect(getDocumentIconType('text/rtf')).toBe('text')
|
|
84
|
+
expect(getDocumentIconType('application/rtf')).toBe('text')
|
|
85
|
+
expect(getDocumentIconType('application/x-rtf')).toBe('text')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('returns markdown for markdown types', () => {
|
|
89
|
+
expect(getDocumentIconType('text/markdown')).toBe('markdown')
|
|
90
|
+
expect(getDocumentIconType('text/x-markdown')).toBe('markdown')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('returns xls for macro-enabled Excel', () => {
|
|
94
|
+
expect(
|
|
95
|
+
getDocumentIconType('application/vnd.ms-excel.sheet.macroEnabled.12')
|
|
96
|
+
).toBe('xls')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('returns ppt for macro-enabled PowerPoint', () => {
|
|
100
|
+
expect(
|
|
101
|
+
getDocumentIconType(
|
|
102
|
+
'application/vnd.ms-powerpoint.presentation.macroEnabled.12'
|
|
103
|
+
)
|
|
104
|
+
).toBe('ppt')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('returns generic for unknown types', () => {
|
|
108
|
+
expect(getDocumentIconType('application/octet-stream')).toBe('generic')
|
|
109
|
+
expect(getDocumentIconType('application/json')).toBe('generic')
|
|
110
|
+
expect(getDocumentIconType('text/html')).toBe('generic')
|
|
111
|
+
expect(getDocumentIconType('application/vnd.rar')).toBe('generic')
|
|
112
|
+
expect(getDocumentIconType('application/vnd.oasis.opendocument.text')).toBe(
|
|
113
|
+
'generic'
|
|
114
|
+
)
|
|
115
|
+
expect(
|
|
116
|
+
getDocumentIconType('application/vnd.oasis.opendocument.spreadsheet')
|
|
117
|
+
).toBe('generic')
|
|
118
|
+
})
|
|
119
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type AttachmentSourceType = 'image' | 'audio' | 'video' | 'document'
|
|
2
|
+
|
|
3
|
+
export type DocumentIconType =
|
|
4
|
+
| 'pdf'
|
|
5
|
+
| 'doc'
|
|
6
|
+
| 'xls'
|
|
7
|
+
| 'csv'
|
|
8
|
+
| 'ppt'
|
|
9
|
+
| 'zip'
|
|
10
|
+
| 'text'
|
|
11
|
+
| 'markdown'
|
|
12
|
+
| 'generic'
|
|
13
|
+
|
|
14
|
+
const DOCUMENT_ICON_PATTERNS: Array<[RegExp, DocumentIconType]> = [
|
|
15
|
+
[/pdf/, 'pdf'],
|
|
16
|
+
[/wordprocessingml|msword|\.doc/, 'doc'],
|
|
17
|
+
[/spreadsheetml|ms-excel|\.xls/, 'xls'],
|
|
18
|
+
[/csv/, 'csv'],
|
|
19
|
+
[/presentationml|ms-powerpoint|\.ppt/, 'ppt'],
|
|
20
|
+
[/zip|x-rar|x-7z|x-tar|x-gzip/, 'zip'],
|
|
21
|
+
[/plain|rtf/, 'text'],
|
|
22
|
+
[/markdown/, 'markdown'],
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
export function getSourceType(mimeType: string): AttachmentSourceType {
|
|
26
|
+
if (mimeType.startsWith('video/')) return 'video'
|
|
27
|
+
if (mimeType.startsWith('audio/')) return 'audio'
|
|
28
|
+
if (mimeType.startsWith('image/')) return 'image'
|
|
29
|
+
return 'document'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getDocumentIconType(mimeType: string): DocumentIconType {
|
|
33
|
+
const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) =>
|
|
34
|
+
pattern.test(mimeType)
|
|
35
|
+
)
|
|
36
|
+
return match ? match[1] : 'generic'
|
|
37
|
+
}
|
|
@@ -32,6 +32,12 @@ export const ParticipantPicker: React.FC<ParticipantPickerProps> = ({
|
|
|
32
32
|
// Track if we've already loaded participants to prevent repeated loading
|
|
33
33
|
const loadedRef = useRef(false)
|
|
34
34
|
|
|
35
|
+
// New source instance should be allowed to load again
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
loadedRef.current = false
|
|
38
|
+
}, [participantSource])
|
|
39
|
+
|
|
40
|
+
/* eslint-disable react-hooks/exhaustive-deps -- syncs with participantSource + debug; inner async uses participantSource.loadParticipants */
|
|
35
41
|
// Load participants initially - wait for participantSource to finish loading first
|
|
36
42
|
useEffect(() => {
|
|
37
43
|
// Wait for the participantSource to finish loading before we try to load participants
|
|
@@ -78,7 +84,8 @@ export const ParticipantPicker: React.FC<ParticipantPickerProps> = ({
|
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
loadInitialParticipants()
|
|
81
|
-
}, [participantSource
|
|
87
|
+
}, [participantSource, debug])
|
|
88
|
+
/* eslint-enable react-hooks/exhaustive-deps */
|
|
82
89
|
|
|
83
90
|
// Filter participants by search query and existing participants
|
|
84
91
|
const availableParticipants = participants
|
|
@@ -74,10 +74,11 @@ export const useParticipants = (
|
|
|
74
74
|
loadParticipants(true);
|
|
75
75
|
}, [loadParticipants]);
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
/* eslint-disable react-hooks/exhaustive-deps -- initial load only; `loadParticipants` changes whenever `loading` flips */
|
|
78
78
|
useEffect(() => {
|
|
79
79
|
loadParticipants(true);
|
|
80
|
-
}, [participantSource.loadParticipants]);
|
|
80
|
+
}, [participantSource.loadParticipants]);
|
|
81
|
+
/* eslint-enable react-hooks/exhaustive-deps */
|
|
81
82
|
|
|
82
83
|
return {
|
|
83
84
|
participants,
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { MessagingShell } from './components/MessagingShell'
|
|
|
6
6
|
export { ChannelList } from './components/ChannelList'
|
|
7
7
|
export { ChannelView } from './components/ChannelView'
|
|
8
8
|
export { default as ActionButton } from './components/ActionButton'
|
|
9
|
+
export { default as LockedAttachment } from './components/LockedAttachment'
|
|
9
10
|
export { ParticipantPicker } from './components/ParticipantPicker'
|
|
10
11
|
export { Avatar } from './components/Avatar'
|
|
11
12
|
export { FaqList } from './components/FaqList'
|
|
@@ -34,10 +35,13 @@ export type {
|
|
|
34
35
|
MessagingCapabilities,
|
|
35
36
|
ParticipantSource,
|
|
36
37
|
Participant,
|
|
38
|
+
LockedAttachmentSource,
|
|
37
39
|
} from './types'
|
|
38
40
|
export type { MessageMetadata } from './stream-custom-data'
|
|
39
41
|
export type { AvatarProps } from './components/Avatar'
|
|
40
42
|
export type { ActionButtonProps } from './components/ActionButton'
|
|
43
|
+
export type { LockedAttachmentProps } from './components/LockedAttachment'
|
|
44
|
+
export type { AttachmentSourceType } from './components/LockedAttachment/utils/mimeType'
|
|
41
45
|
export type { Faq, FaqListProps } from './components/FaqList'
|
|
42
46
|
export type { FaqListItemProps } from './components/FaqList/FaqListItem'
|
|
43
47
|
export type { VoteSelection } from './hooks/useMessageVote'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ArgTypes } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
export const storyUsers = {
|
|
4
|
+
creator: {
|
|
5
|
+
id: 'creator-user',
|
|
6
|
+
name: 'Creator',
|
|
7
|
+
image: 'https://i.pravatar.cc/150?img=1',
|
|
8
|
+
},
|
|
9
|
+
visitor: {
|
|
10
|
+
id: 'visitor-user',
|
|
11
|
+
name: 'Visitor',
|
|
12
|
+
image: 'https://i.pravatar.cc/150?img=5',
|
|
13
|
+
},
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
export type StoryRole = keyof typeof storyUsers
|
|
17
|
+
|
|
18
|
+
export type StoryUser = typeof storyUsers[keyof typeof storyUsers]
|
|
19
|
+
|
|
20
|
+
const DEFAULT_ROLE: StoryRole = 'creator'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Storybook control: pick viewer identity; `args.currentUser` is the full user object.
|
|
24
|
+
* Use with `key={currentUser.id}` on the story root so Stream client remounts when it changes.
|
|
25
|
+
*/
|
|
26
|
+
export const currentUserArgType: ArgTypes<{ currentUser?: StoryUser }> = {
|
|
27
|
+
currentUser: {
|
|
28
|
+
name: 'participant',
|
|
29
|
+
control: { type: 'inline-radio' as const },
|
|
30
|
+
options: ['creator', 'visitor'] as StoryRole[],
|
|
31
|
+
mapping: {
|
|
32
|
+
creator: storyUsers.creator,
|
|
33
|
+
visitor: storyUsers.visitor,
|
|
34
|
+
},
|
|
35
|
+
table: { defaultValue: { summary: DEFAULT_ROLE } },
|
|
36
|
+
},
|
|
37
|
+
} satisfies ArgTypes<{ currentUser?: StoryUser }>
|
|
@@ -24,6 +24,7 @@ export type MessageCustomType =
|
|
|
24
24
|
| 'MESSAGE_TIP'
|
|
25
25
|
| 'MESSAGE_PAID'
|
|
26
26
|
| 'MESSAGE_CHATBOT'
|
|
27
|
+
| 'MESSAGE_ATTACHMENT'
|
|
27
28
|
| AgeSafetySystemType
|
|
28
29
|
| DmAgentSystemType
|
|
29
30
|
|
|
@@ -31,12 +32,17 @@ export type MessageCustomType =
|
|
|
31
32
|
* Message metadata for paid messaging and chatbot flows.
|
|
32
33
|
* Used to identify message types and payment status.
|
|
33
34
|
*/
|
|
35
|
+
export type PaymentStatus = 'pending' | 'paid' | 'failed' | 'refunded'
|
|
36
|
+
|
|
34
37
|
export interface MessageMetadata {
|
|
35
38
|
custom_type?: MessageCustomType
|
|
36
|
-
amount_text?: string
|
|
37
|
-
payment_status?: string
|
|
38
|
-
payment_intent_id?: string
|
|
39
39
|
listing_id?: string
|
|
40
|
+
amount_text?: string
|
|
41
|
+
payment_status?: PaymentStatus
|
|
42
|
+
attachment_title?: string
|
|
43
|
+
attachment_mime_type?: string
|
|
44
|
+
attachment_thumbnail?: string
|
|
45
|
+
attachment_detail?: string
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
declare module 'stream-chat' {
|
package/src/types.ts
CHANGED
|
@@ -117,6 +117,10 @@ export interface ChannelListProps {
|
|
|
117
117
|
) => React.ReactNode
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
export interface LockedAttachmentSource {
|
|
121
|
+
source: string
|
|
122
|
+
}
|
|
123
|
+
|
|
120
124
|
/**
|
|
121
125
|
* ChannelView component props
|
|
122
126
|
*/
|
|
@@ -163,7 +167,7 @@ export interface ChannelViewProps {
|
|
|
163
167
|
* @example
|
|
164
168
|
* messageMetadata={{ custom_type: 'MESSAGE_PAID', listing_id: '...' }}
|
|
165
169
|
*/
|
|
166
|
-
messageMetadata?:
|
|
170
|
+
messageMetadata?: Partial<MessageMetadata>
|
|
167
171
|
|
|
168
172
|
/**
|
|
169
173
|
* Callback fired after a message is successfully sent.
|
|
@@ -231,6 +235,19 @@ export interface ChannelViewProps {
|
|
|
231
235
|
messageNode: React.ReactElement,
|
|
232
236
|
message: LocalMessage
|
|
233
237
|
) => React.ReactNode
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Called when the visitor clicks Unlock on a locked attachment message.
|
|
241
|
+
* Receives the message and channel. Show checkout, confirm payment, fetch
|
|
242
|
+
* the unlocked URL. `attachment_source` must NOT be stored on the Stream message metadata.
|
|
243
|
+
* The card shows a loading state for the full duration of the promise.
|
|
244
|
+
*/
|
|
245
|
+
onAttachmentUnlock?: (message: LocalMessage, channel: Channel) => Promise<LockedAttachmentSource>
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Called when the visitor clicks Download on an unlocked attachment message.
|
|
249
|
+
*/
|
|
250
|
+
onAttachmentDownload?: (message: LocalMessage, channel: Channel) => void
|
|
234
251
|
}
|
|
235
252
|
|
|
236
253
|
/**
|
|
@@ -254,6 +271,8 @@ export type ChannelViewPassthroughProps = Pick<
|
|
|
254
271
|
| 'customProfileContent'
|
|
255
272
|
| 'customChannelActions'
|
|
256
273
|
| 'renderMessage'
|
|
274
|
+
| 'onAttachmentUnlock'
|
|
275
|
+
| 'onAttachmentDownload'
|
|
257
276
|
>
|
|
258
277
|
|
|
259
278
|
/**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True in Vite dev builds (`import.meta.env.DEV`). Uses a type assertion so
|
|
3
|
+
* `tsc` does not rely on ambient `ImportMeta` merging (vite/client).
|
|
4
|
+
*/
|
|
5
|
+
export function isDevBuild(): boolean {
|
|
6
|
+
return (
|
|
7
|
+
typeof import.meta !== 'undefined' &&
|
|
8
|
+
(import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV === true
|
|
9
|
+
)
|
|
10
|
+
}
|