@weavy/uikit-react 11.0.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/.github/workflows/publish.yml +16 -0
- package/LICENSE.md +21 -0
- package/README.md +110 -0
- package/changelog.md +50 -0
- package/dist/cjs/index.js +39 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/types/client/WeavyClient.d.ts +16 -0
- package/dist/cjs/types/components/Attachment.d.ts +13 -0
- package/dist/cjs/types/components/Avatar.d.ts +11 -0
- package/dist/cjs/types/components/Chat.d.ts +4 -0
- package/dist/cjs/types/components/Conversation.d.ts +4 -0
- package/dist/cjs/types/components/ConversationBadge.d.ts +3 -0
- package/dist/cjs/types/components/ConversationForm.d.ts +7 -0
- package/dist/cjs/types/components/ConversationList.d.ts +3 -0
- package/dist/cjs/types/components/ConversationListItem.d.ts +4 -0
- package/dist/cjs/types/components/File.d.ts +9 -0
- package/dist/cjs/types/components/FileBrowser.d.ts +6 -0
- package/dist/cjs/types/components/Image.d.ts +16 -0
- package/dist/cjs/types/components/Meeting.d.ts +8 -0
- package/dist/cjs/types/components/MeetingCard.d.ts +6 -0
- package/dist/cjs/types/components/Meetings.d.ts +6 -0
- package/dist/cjs/types/components/Message.d.ts +4 -0
- package/dist/cjs/types/components/Messages.d.ts +9 -0
- package/dist/cjs/types/components/Messenger.d.ts +4 -0
- package/dist/cjs/types/components/NewConversation.d.ts +3 -0
- package/dist/cjs/types/components/Presence.d.ts +7 -0
- package/dist/cjs/types/components/Reactions.d.ts +13 -0
- package/dist/cjs/types/components/SearchUsers.d.ts +7 -0
- package/dist/cjs/types/components/SeenBy.d.ts +9 -0
- package/dist/cjs/types/components/Typing.d.ts +8 -0
- package/dist/cjs/types/contexts/MessengerContext.d.ts +8 -0
- package/dist/cjs/types/contexts/PreviewContext.d.ts +7 -0
- package/dist/cjs/types/contexts/UserContext.d.ts +8 -0
- package/dist/cjs/types/contexts/WeavyContext.d.ts +10 -0
- package/dist/cjs/types/hooks/useBadge.d.ts +1 -0
- package/dist/cjs/types/hooks/useChat.d.ts +1 -0
- package/dist/cjs/types/hooks/useConversation.d.ts +1 -0
- package/dist/cjs/types/hooks/useConversations.d.ts +1 -0
- package/dist/cjs/types/hooks/useDebounce.d.ts +2 -0
- package/dist/cjs/types/hooks/useEvents.d.ts +6 -0
- package/dist/cjs/types/hooks/useFileUploader.d.ts +1 -0
- package/dist/cjs/types/hooks/useMembers.d.ts +1 -0
- package/dist/cjs/types/hooks/useMessages.d.ts +1 -0
- package/dist/cjs/types/hooks/useMutateChat.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateConversation.d.ts +3 -0
- package/dist/cjs/types/hooks/useMutateConversationName.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateDeleteReaction.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateExternalBlobs.d.ts +3 -0
- package/dist/cjs/types/hooks/useMutateMeeting.d.ts +3 -0
- package/dist/cjs/types/hooks/useMutateMembers.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateMessage.d.ts +9 -0
- package/dist/cjs/types/hooks/useMutatePinned.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateReaction.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateRead.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateRemoveMembers.d.ts +4 -0
- package/dist/cjs/types/hooks/useMutateTyping.d.ts +3 -0
- package/dist/cjs/types/hooks/usePresence.d.ts +1 -0
- package/dist/cjs/types/hooks/usePreview.d.ts +4 -0
- package/dist/cjs/types/hooks/useReactions.d.ts +3 -0
- package/dist/cjs/types/hooks/useSearchUsers.d.ts +1 -0
- package/dist/cjs/types/hooks/useThrottle.d.ts +2 -0
- package/dist/cjs/types/hooks/useUser.d.ts +1 -0
- package/dist/cjs/types/index.d.ts +15 -0
- package/dist/cjs/types/types/Chat.d.ts +3 -0
- package/dist/cjs/types/types/Conversation.d.ts +4 -0
- package/dist/cjs/types/types/ConversationListItem.d.ts +4 -0
- package/dist/cjs/types/types/Message.d.ts +15 -0
- package/dist/cjs/types/types/Messenger.d.ts +3 -0
- package/dist/cjs/types/types/types.d.ts +150 -0
- package/dist/cjs/types/ui/Button.d.ts +4 -0
- package/dist/cjs/types/ui/Dropdown.d.ts +19 -0
- package/dist/cjs/types/ui/Icon.d.ts +10 -0
- package/dist/cjs/types/ui/Overlay.d.ts +12 -0
- package/dist/cjs/types/utils/fileUtilities.d.ts +5 -0
- package/dist/cjs/types/utils/styles.d.ts +17 -0
- package/dist/esm/index.js +39 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/types/client/WeavyClient.d.ts +16 -0
- package/dist/esm/types/components/Attachment.d.ts +13 -0
- package/dist/esm/types/components/Avatar.d.ts +11 -0
- package/dist/esm/types/components/Chat.d.ts +4 -0
- package/dist/esm/types/components/Conversation.d.ts +4 -0
- package/dist/esm/types/components/ConversationBadge.d.ts +3 -0
- package/dist/esm/types/components/ConversationForm.d.ts +7 -0
- package/dist/esm/types/components/ConversationList.d.ts +3 -0
- package/dist/esm/types/components/ConversationListItem.d.ts +4 -0
- package/dist/esm/types/components/File.d.ts +9 -0
- package/dist/esm/types/components/FileBrowser.d.ts +6 -0
- package/dist/esm/types/components/Image.d.ts +16 -0
- package/dist/esm/types/components/Meeting.d.ts +8 -0
- package/dist/esm/types/components/MeetingCard.d.ts +6 -0
- package/dist/esm/types/components/Meetings.d.ts +6 -0
- package/dist/esm/types/components/Message.d.ts +4 -0
- package/dist/esm/types/components/Messages.d.ts +9 -0
- package/dist/esm/types/components/Messenger.d.ts +4 -0
- package/dist/esm/types/components/NewConversation.d.ts +3 -0
- package/dist/esm/types/components/Presence.d.ts +7 -0
- package/dist/esm/types/components/Reactions.d.ts +13 -0
- package/dist/esm/types/components/SearchUsers.d.ts +7 -0
- package/dist/esm/types/components/SeenBy.d.ts +9 -0
- package/dist/esm/types/components/Typing.d.ts +8 -0
- package/dist/esm/types/contexts/MessengerContext.d.ts +8 -0
- package/dist/esm/types/contexts/PreviewContext.d.ts +7 -0
- package/dist/esm/types/contexts/UserContext.d.ts +8 -0
- package/dist/esm/types/contexts/WeavyContext.d.ts +10 -0
- package/dist/esm/types/hooks/useBadge.d.ts +1 -0
- package/dist/esm/types/hooks/useChat.d.ts +1 -0
- package/dist/esm/types/hooks/useConversation.d.ts +1 -0
- package/dist/esm/types/hooks/useConversations.d.ts +1 -0
- package/dist/esm/types/hooks/useDebounce.d.ts +2 -0
- package/dist/esm/types/hooks/useEvents.d.ts +6 -0
- package/dist/esm/types/hooks/useFileUploader.d.ts +1 -0
- package/dist/esm/types/hooks/useMembers.d.ts +1 -0
- package/dist/esm/types/hooks/useMessages.d.ts +1 -0
- package/dist/esm/types/hooks/useMutateChat.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateConversation.d.ts +3 -0
- package/dist/esm/types/hooks/useMutateConversationName.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateDeleteReaction.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateExternalBlobs.d.ts +3 -0
- package/dist/esm/types/hooks/useMutateMeeting.d.ts +3 -0
- package/dist/esm/types/hooks/useMutateMembers.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateMessage.d.ts +9 -0
- package/dist/esm/types/hooks/useMutatePinned.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateReaction.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateRead.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateRemoveMembers.d.ts +4 -0
- package/dist/esm/types/hooks/useMutateTyping.d.ts +3 -0
- package/dist/esm/types/hooks/usePresence.d.ts +1 -0
- package/dist/esm/types/hooks/usePreview.d.ts +4 -0
- package/dist/esm/types/hooks/useReactions.d.ts +3 -0
- package/dist/esm/types/hooks/useSearchUsers.d.ts +1 -0
- package/dist/esm/types/hooks/useThrottle.d.ts +2 -0
- package/dist/esm/types/hooks/useUser.d.ts +1 -0
- package/dist/esm/types/index.d.ts +15 -0
- package/dist/esm/types/types/Chat.d.ts +3 -0
- package/dist/esm/types/types/Conversation.d.ts +4 -0
- package/dist/esm/types/types/ConversationListItem.d.ts +4 -0
- package/dist/esm/types/types/Message.d.ts +15 -0
- package/dist/esm/types/types/Messenger.d.ts +3 -0
- package/dist/esm/types/types/types.d.ts +150 -0
- package/dist/esm/types/ui/Button.d.ts +4 -0
- package/dist/esm/types/ui/Dropdown.d.ts +19 -0
- package/dist/esm/types/ui/Icon.d.ts +10 -0
- package/dist/esm/types/ui/Overlay.d.ts +12 -0
- package/dist/esm/types/utils/fileUtilities.d.ts +5 -0
- package/dist/esm/types/utils/styles.d.ts +17 -0
- package/dist/index.d.ts +98 -0
- package/package.json +47 -0
- package/rollup.config.js +41 -0
- package/src/client/WeavyClient.ts +95 -0
- package/src/components/Attachment.tsx +33 -0
- package/src/components/Avatar.tsx +26 -0
- package/src/components/Chat.tsx +68 -0
- package/src/components/Conversation.tsx +220 -0
- package/src/components/ConversationBadge.tsx +44 -0
- package/src/components/ConversationForm.tsx +217 -0
- package/src/components/ConversationList.tsx +61 -0
- package/src/components/ConversationListItem.tsx +155 -0
- package/src/components/File.tsx +21 -0
- package/src/components/FileBrowser.tsx +86 -0
- package/src/components/Image.tsx +66 -0
- package/src/components/Meeting.tsx +21 -0
- package/src/components/MeetingCard.tsx +31 -0
- package/src/components/Meetings.tsx +58 -0
- package/src/components/Message.tsx +90 -0
- package/src/components/Messages.tsx +271 -0
- package/src/components/Messenger.tsx +34 -0
- package/src/components/NewConversation.tsx +50 -0
- package/src/components/Presence.tsx +15 -0
- package/src/components/Reactions.tsx +95 -0
- package/src/components/SearchUsers.tsx +90 -0
- package/src/components/SeenBy.tsx +26 -0
- package/src/components/Typing.tsx +131 -0
- package/src/contexts/MessengerContext.tsx +44 -0
- package/src/contexts/PreviewContext.tsx +105 -0
- package/src/contexts/UserContext.tsx +31 -0
- package/src/contexts/WeavyContext.tsx +66 -0
- package/src/hooks/useBadge.ts +32 -0
- package/src/hooks/useChat.ts +32 -0
- package/src/hooks/useConversation.ts +28 -0
- package/src/hooks/useConversations.ts +27 -0
- package/src/hooks/useDebounce.ts +22 -0
- package/src/hooks/useEvents.ts +43 -0
- package/src/hooks/useFileUploader.ts +35 -0
- package/src/hooks/useMembers.ts +27 -0
- package/src/hooks/useMessages.ts +42 -0
- package/src/hooks/useMessenger.ts +51 -0
- package/src/hooks/useMutateChat.ts +44 -0
- package/src/hooks/useMutateConversation.ts +40 -0
- package/src/hooks/useMutateConversationName.ts +41 -0
- package/src/hooks/useMutateDeleteReaction.ts +38 -0
- package/src/hooks/useMutateExternalBlobs.ts +39 -0
- package/src/hooks/useMutateMeeting.ts +39 -0
- package/src/hooks/useMutateMembers.ts +43 -0
- package/src/hooks/useMutateMessage.ts +116 -0
- package/src/hooks/useMutatePinned.ts +40 -0
- package/src/hooks/useMutateReaction.ts +38 -0
- package/src/hooks/useMutateRead.ts +40 -0
- package/src/hooks/useMutateRemoveMembers.ts +43 -0
- package/src/hooks/useMutateTyping.ts +34 -0
- package/src/hooks/usePresence.ts +32 -0
- package/src/hooks/usePreview.ts +21 -0
- package/src/hooks/useReactions.ts +53 -0
- package/src/hooks/useSearchUsers.ts +26 -0
- package/src/hooks/useThrottle.ts +13 -0
- package/src/hooks/useUser.ts +38 -0
- package/src/index.ts +33 -0
- package/src/types/Chat.ts +3 -0
- package/src/types/Conversation.ts +4 -0
- package/src/types/ConversationListItem.ts +4 -0
- package/src/types/Message.ts +16 -0
- package/src/types/Messenger.ts +3 -0
- package/src/types/emoji-toolkit.d.ts +1 -0
- package/src/types/types.ts +175 -0
- package/src/ui/Button.tsx +32 -0
- package/src/ui/Dropdown.tsx +58 -0
- package/src/ui/Icon.tsx +79 -0
- package/src/ui/Overlay.tsx +41 -0
- package/src/utils/fileUtilities.ts +230 -0
- package/src/utils/infiniteScroll.js +175 -0
- package/src/utils/scrollToBottom.js +75 -0
- package/src/utils/styles.ts +42 -0
- package/tsconfig.json +108 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useContext, useEffect, useState } from "react";
|
|
2
|
+
import { WeavyContext } from "../contexts/WeavyContext";
|
|
3
|
+
import useBadge from "../hooks/useBadge";
|
|
4
|
+
import { prefix as wy } from "../utils/styles";
|
|
5
|
+
|
|
6
|
+
const ConversationBadge = () => {
|
|
7
|
+
const { client } = useContext(WeavyContext);
|
|
8
|
+
const [badge, setBadge] = useState<number>(0);
|
|
9
|
+
|
|
10
|
+
if (!client) {
|
|
11
|
+
throw new Error('Weavy Badge component must be used within an WeavyProvider');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { isLoading, data } = useBadge();
|
|
15
|
+
|
|
16
|
+
const handleBadge = (data: BadgeType) => {
|
|
17
|
+
setBadge(data.private + data.rooms);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if(data){
|
|
22
|
+
setBadge(data.private + data.rooms);
|
|
23
|
+
}
|
|
24
|
+
}, [data])
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
client.subscribe(null, "conversation-badge", handleBadge);
|
|
28
|
+
|
|
29
|
+
return () => {
|
|
30
|
+
client.unsubscribe(null, "conversation-badge", handleBadge);
|
|
31
|
+
}
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
{!isLoading && badge > 0 &&
|
|
37
|
+
<span className={wy('badge badge-danger')}>{badge}</span>
|
|
38
|
+
}
|
|
39
|
+
</>
|
|
40
|
+
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default ConversationBadge;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useQueryClient } from 'react-query';
|
|
3
|
+
import useFileUploader from '../hooks/useFileUploader';
|
|
4
|
+
import File from './File';
|
|
5
|
+
import Icon from '../ui/Icon';
|
|
6
|
+
import Button from '../ui/Button';
|
|
7
|
+
import useMutateTyping from '../hooks/useMutateTyping';
|
|
8
|
+
import useThrottle from '../hooks/useThrottle';
|
|
9
|
+
import { flushSync } from 'react-dom';
|
|
10
|
+
import Meetings from './Meetings';
|
|
11
|
+
import Meeting from './Meeting';
|
|
12
|
+
import FileBrowser from './FileBrowser';
|
|
13
|
+
import { getIcon } from '../utils/fileUtilities';
|
|
14
|
+
import { prefix as wy } from "../utils/styles";
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
conversationId: number,
|
|
19
|
+
handleInsert: Function
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let uploadedFiles: any[] = [];
|
|
23
|
+
|
|
24
|
+
const ConversationForm = ({ conversationId, handleInsert }: Props) => {
|
|
25
|
+
|
|
26
|
+
const [text, setText] = useState<string>("");
|
|
27
|
+
const [files, setFiles] = useState<string>("");
|
|
28
|
+
const [fileCount, setFileCount] = useState<number>(0);
|
|
29
|
+
const [currentUploadCount, setCurrentUploadCount] = useState<number>(1);
|
|
30
|
+
const [working, setWorking] = useState<boolean>(false);
|
|
31
|
+
const queryClient = useQueryClient();
|
|
32
|
+
const [attachments, setAttachments] = useState<FileType[]>([]);
|
|
33
|
+
const [meetings, setMeetings] = useState<MeetingType[]>([]);
|
|
34
|
+
const [uploadError, setUploadError] = useState<string | boolean>(false);
|
|
35
|
+
const startTyping = useMutateTyping();
|
|
36
|
+
const textInput = useRef<HTMLTextAreaElement>(null);
|
|
37
|
+
|
|
38
|
+
let fileInput: HTMLInputElement | null;
|
|
39
|
+
|
|
40
|
+
const handleUploaded = (attachment: any) => {
|
|
41
|
+
// update attachment list
|
|
42
|
+
flushSync(() => {
|
|
43
|
+
if (attachment.status && attachment.status !== 200) {
|
|
44
|
+
setUploadError(attachment.detail);
|
|
45
|
+
} else {
|
|
46
|
+
setAttachments([...attachments, attachment[0]]);
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { mutateAsync: uploadFile, isSuccess: uploadSuccess } = useFileUploader(handleUploaded);
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
// set stored text and attachments if available
|
|
58
|
+
let textValue: any = queryClient.getQueryData(["form-text", conversationId]) || "";
|
|
59
|
+
setText(textValue);
|
|
60
|
+
|
|
61
|
+
let attachmentValue: any = queryClient.getQueryData(["form-attachments", conversationId]) || [];
|
|
62
|
+
setAttachments(attachmentValue);
|
|
63
|
+
}, [conversationId]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
// store attachments
|
|
67
|
+
queryClient.setQueryData(["form-attachments", conversationId], attachments);
|
|
68
|
+
}, [attachments]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
handleAutoGrow();
|
|
72
|
+
}, [text])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
const handleInsertMessage = (e: React.KeyboardEvent<EventTarget>) => {
|
|
77
|
+
|
|
78
|
+
if(e.type === 'keydown' && !(e.key === "Enter" && e.ctrlKey )) return;
|
|
79
|
+
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
e.stopPropagation();
|
|
82
|
+
|
|
83
|
+
if (text === "" && attachments.length === 0 && meetings.length === 0) return;
|
|
84
|
+
|
|
85
|
+
handleInsert(text, attachments, meetings);
|
|
86
|
+
setText("");
|
|
87
|
+
setFiles("");
|
|
88
|
+
setAttachments([]);
|
|
89
|
+
setMeetings([]);
|
|
90
|
+
|
|
91
|
+
// clear stored text and attachments
|
|
92
|
+
queryClient.setQueryData(["form-text", conversationId], "");
|
|
93
|
+
queryClient.setQueryData(["form-attachments", conversationId], []);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const handleChange = (e: any) => {
|
|
97
|
+
|
|
98
|
+
// store text
|
|
99
|
+
queryClient.setQueryData(["form-text", conversationId], e.target.value);
|
|
100
|
+
|
|
101
|
+
// set text value
|
|
102
|
+
setText(e.target.value);
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const handleFileUpload = async (e: any) => {
|
|
107
|
+
setUploadError(false);
|
|
108
|
+
setFiles(e.target.value);
|
|
109
|
+
setFileCount(e.target.files.length);
|
|
110
|
+
setWorking(true);
|
|
111
|
+
|
|
112
|
+
for (var i = 0; i < e.target.files.length; i++) {
|
|
113
|
+
setCurrentUploadCount(i + 1);
|
|
114
|
+
const file = e.target.files[i];
|
|
115
|
+
await uploadFile({ request: { file } });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setWorking(false)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const handleRemoveFile = (id: number, e: any) => {
|
|
122
|
+
setFiles("");
|
|
123
|
+
setAttachments(attachments.filter((a: FileType) => {
|
|
124
|
+
return a.id !== id
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const handleKeyPress = () => {
|
|
129
|
+
startTyping.mutate({ id: conversationId });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const openFileInput = (e: any) => {
|
|
133
|
+
fileInput?.click();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const handleAddMeeting = (data: any) => {
|
|
137
|
+
if (meetings.length > 0) return;
|
|
138
|
+
|
|
139
|
+
setMeetings([...meetings, data]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const handleExternalFileAdded = (blobs: FileType[]) => {
|
|
143
|
+
setAttachments([...attachments, ...blobs]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const handleRemoveMeeting = (id: number, e: any) => {
|
|
147
|
+
setMeetings(meetings.filter((a: MeetingType) => {
|
|
148
|
+
return a.id !== id
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const handleAutoGrow = () => {
|
|
153
|
+
if(textInput.current && textInput.current.parentNode){
|
|
154
|
+
const parent = textInput.current.parentNode as HTMLElement;
|
|
155
|
+
parent.dataset.replicatedValue = textInput.current.value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<form className={wy('message-form')}>
|
|
163
|
+
{uploadError &&
|
|
164
|
+
<div>{uploadError}</div>
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
{(working || attachments.length > 0 || meetings.length > 0) &&
|
|
168
|
+
<div>
|
|
169
|
+
{working &&
|
|
170
|
+
<div>Now uploading ({currentUploadCount} of {fileCount}) selected files</div>
|
|
171
|
+
}
|
|
172
|
+
<div className={wy('picker-list')}>
|
|
173
|
+
{attachments.map((a: FileType) => {
|
|
174
|
+
let { icon } = getIcon(a.name);
|
|
175
|
+
return (
|
|
176
|
+
<div key={a.id} className={wy('picker-list-item')}>
|
|
177
|
+
<File id={a.id} name={a.name} className={wy('picker-list-item-title')} icon={ icon } />
|
|
178
|
+
<Button.UI onClick={handleRemoveFile.bind(ConversationForm, a.id)}><Icon.UI name='close-circle' /></Button.UI>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
)
|
|
182
|
+
})}
|
|
183
|
+
|
|
184
|
+
{meetings.map((m: MeetingType) => {
|
|
185
|
+
return (
|
|
186
|
+
<div key={m.id} className={wy('picker-list-item')}>
|
|
187
|
+
<Meeting id={m.id} title={m.provider} className={wy('picker-list-item-title')} />
|
|
188
|
+
<Button.UI onClick={handleRemoveMeeting.bind(ConversationForm, m.id)}><Icon.UI name='close-circle' /></Button.UI>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
)
|
|
192
|
+
})}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
}
|
|
196
|
+
<div className={wy('message-editor-inputs')}>
|
|
197
|
+
|
|
198
|
+
<div className={wy('message-editor-buttons')}>
|
|
199
|
+
<input type="file" ref={input => fileInput = input} value={files} onChange={handleFileUpload} multiple hidden tabIndex={-1} />
|
|
200
|
+
<Button.UI title="Upload attachment" onClick={openFileInput}><Icon.UI name="attachment" /></Button.UI>
|
|
201
|
+
<Meetings onMeetingAdded={handleAddMeeting} />
|
|
202
|
+
<FileBrowser onFileAdded={handleExternalFileAdded} />
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className={wy('message-editor-text message-editor-grow')}>
|
|
206
|
+
<textarea rows={1} ref={textInput} className={wy('message-editor-textfield message-editor-textcontent')} value={text} onChange={handleChange} onKeyDown={handleInsertMessage} onKeyPress={useThrottle(handleKeyPress, 4000)}></textarea>
|
|
207
|
+
</div>
|
|
208
|
+
<div className={wy('message-editor-buttons')}>
|
|
209
|
+
<Button.UI type="button" onClick={handleInsertMessage} ><Icon.UI name="send"/></Button.UI>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</form>
|
|
213
|
+
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export default ConversationForm;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from 'react';
|
|
2
|
+
import { WeavyContext } from '../contexts/WeavyContext';
|
|
3
|
+
import useConversations from '../hooks/useConversations';
|
|
4
|
+
import ConversationListItem from './ConversationListItem';
|
|
5
|
+
import NewConversation from './NewConversation';
|
|
6
|
+
import Avatar from './Avatar';
|
|
7
|
+
import { UserContext } from '../contexts/UserContext';
|
|
8
|
+
import { prefix as wy } from "../utils/styles";
|
|
9
|
+
|
|
10
|
+
const ConversationList = () => {
|
|
11
|
+
const { client } = useContext(WeavyContext);
|
|
12
|
+
const { user } = useContext(UserContext);
|
|
13
|
+
const { data, isLoading, refetch } = useConversations();
|
|
14
|
+
|
|
15
|
+
if (!client) {
|
|
16
|
+
throw new Error('Weavy ConversationList component must be used within an WeavyProvider');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
|
|
21
|
+
client.subscribe(null, "app-inserted", handleAppInserted);
|
|
22
|
+
client.subscribe(null, "member-added", handleAppInserted);
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
client.unsubscribe(null, "app-inserted", handleAppInserted);
|
|
26
|
+
client.unsubscribe(null, "member-added", handleAppInserted);
|
|
27
|
+
}
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const handleAppInserted = (data: ConversationType) => {
|
|
31
|
+
refetch();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if (isLoading) {
|
|
36
|
+
return (
|
|
37
|
+
<div>Loading Conversation list...</div>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<header className={wy('appbars')}>
|
|
44
|
+
<nav className={wy('appbar')}>
|
|
45
|
+
<Avatar src={user.avatar_url} name={user.title} presence={user.presence} id={user.id} size={24} />
|
|
46
|
+
<div>Messenger</div>
|
|
47
|
+
<NewConversation />
|
|
48
|
+
</nav>
|
|
49
|
+
</header>
|
|
50
|
+
<div className={wy('conversations')}>
|
|
51
|
+
{data && data.data?.map((item) =>
|
|
52
|
+
<ConversationListItem key={item.id} refetchConversations={refetch} item={item} />
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
{/* <a className={wy('pager')}><svg className={wy('spinner spin')} width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle fill="none" cx="12" cy="12" r="11" stroke-linecap="butt" stroke-width="2"></circle></svg></a> */}
|
|
56
|
+
</div>
|
|
57
|
+
</>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default ConversationList;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useCallback, useContext, useEffect } from 'react';
|
|
2
|
+
import joypixels from 'emoji-toolkit';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import { MessengerContext } from '../contexts/MessengerContext';
|
|
5
|
+
import { WeavyContext } from '../contexts/WeavyContext';
|
|
6
|
+
import useMutatePinned from '../hooks/useMutatePinned';
|
|
7
|
+
import useMutateRead from '../hooks/useMutateRead';
|
|
8
|
+
import { ConversationListItemProps } from '../types/ConversationListItem';
|
|
9
|
+
import Dropdown from '../ui/Dropdown';
|
|
10
|
+
import Icon from '../ui/Icon';
|
|
11
|
+
import Button from '../ui/Button';
|
|
12
|
+
import Typing from './Typing';
|
|
13
|
+
import useMutateRemoveMembers from '../hooks/useMutateRemoveMembers';
|
|
14
|
+
import Avatar from './Avatar';
|
|
15
|
+
import { UserContext } from '../contexts/UserContext';
|
|
16
|
+
import { prefix as wy } from "../utils/styles";
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const ConversationListItem = ({ item, refetchConversations }: ConversationListItemProps) => {
|
|
20
|
+
|
|
21
|
+
const { client } = useContext(WeavyContext);
|
|
22
|
+
const { setSelectedConversationId, selectedConversationId } = useContext(MessengerContext);
|
|
23
|
+
const { user } = useContext(UserContext);
|
|
24
|
+
const readMutation = useMutateRead();
|
|
25
|
+
const pinMutation = useMutatePinned();
|
|
26
|
+
const removeMembers = useMutateRemoveMembers();
|
|
27
|
+
const date = dayjs.utc(item.last_message?.created_at).tz(dayjs.tz.guess());
|
|
28
|
+
|
|
29
|
+
const ChatRoom = "edb400ac-839b-45a7-b2a8-6a01820d1c44";
|
|
30
|
+
|
|
31
|
+
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>, id: any) => {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
setSelectedConversationId(id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const handleAppUpdated = useCallback((data: ConversationType) => {
|
|
37
|
+
if (data.id !== item.id) return;
|
|
38
|
+
refetchConversations();
|
|
39
|
+
|
|
40
|
+
}, [item.id])
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
client.subscribe(`a${item.id}`, "app-updated", handleAppUpdated);
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
client.unsubscribe(`a${item.id}`, "app-updated", handleAppUpdated);
|
|
47
|
+
}
|
|
48
|
+
}, [item.id])
|
|
49
|
+
|
|
50
|
+
const otherId = item.type !== ChatRoom ? item.member_ids.find((i) => { return i != user.id }) : null;
|
|
51
|
+
|
|
52
|
+
const handleUnread = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
readMutation.mutate({ id: item.id, read: false });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const handleRead = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
readMutation.mutate({ id: item.id, read: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const handlePin = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
pinMutation.mutate({ id: item.id, pin: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleUnpin = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
pinMutation.mutate({ id: item.id, pin: false });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handleLeaveConversation = () => {
|
|
73
|
+
removeMembers.mutate({ id: item.id, members: [user.id] });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// const handleStar = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
77
|
+
// e.preventDefault();
|
|
78
|
+
// console.log("Star: ", item.id)
|
|
79
|
+
// }
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className={wy('conversation' + (item.is_unread ? ' unread' : ''))} key={item.id}>
|
|
83
|
+
<a className={wy('conversation-link' + (selectedConversationId === item.id ? ' active': ''))} href="#" onClick={(e) => handleClick(e, item.id)}>
|
|
84
|
+
<Avatar src={item.avatar_url} id={otherId || -1} presence={item.type !== ChatRoom ? "away" : ""} name={item.display_name} />
|
|
85
|
+
|
|
86
|
+
<div className={wy('conversation-body')}>
|
|
87
|
+
<div className={wy('conversation-header')}>
|
|
88
|
+
<div className={wy('conversation-title')}>{item.display_name}</div>
|
|
89
|
+
{item.last_message &&
|
|
90
|
+
<time className={wy('conversation-time')} dateTime={item.last_message.created_at.toString()} title={date.format('LLLL')}>{date.fromNow()}</time>
|
|
91
|
+
}
|
|
92
|
+
</div>
|
|
93
|
+
<div className={wy('conversation-summary')}>
|
|
94
|
+
<Typing id={item.id} context="listitem">
|
|
95
|
+
|
|
96
|
+
{item.last_message?.html &&
|
|
97
|
+
<span className={wy('typing-hide')} dangerouslySetInnerHTML={{ __html: joypixels.shortnameToUnicode(item.last_message?.text) }}></span>
|
|
98
|
+
}
|
|
99
|
+
{!item.last_message?.html &&
|
|
100
|
+
<span className={wy('typing-hide')}>
|
|
101
|
+
{item.last_message?.attachment_ids?.length > 0 &&
|
|
102
|
+
<Icon.UI name="attachment" size={1} />
|
|
103
|
+
}
|
|
104
|
+
{item.last_message?.meeting_id &&
|
|
105
|
+
<Icon.UI name="zoom" size={1} />
|
|
106
|
+
}
|
|
107
|
+
</span>
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
</Typing>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</a>
|
|
114
|
+
|
|
115
|
+
<div className={wy('conversation-actions')}>
|
|
116
|
+
{item.is_pinned &&
|
|
117
|
+
<Button.UI onClick={handleUnpin}>
|
|
118
|
+
<Icon.UI name="pin" size={.75} />
|
|
119
|
+
</Button.UI>
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
<Dropdown.UI directionX='left'>
|
|
125
|
+
<>
|
|
126
|
+
{item.is_unread &&
|
|
127
|
+
<Dropdown.Item onClick={handleRead}>Mark as read</Dropdown.Item>
|
|
128
|
+
}
|
|
129
|
+
{!item.is_unread &&
|
|
130
|
+
<Dropdown.Item onClick={handleUnread}>Mark as unread</Dropdown.Item>
|
|
131
|
+
}
|
|
132
|
+
</>
|
|
133
|
+
<>
|
|
134
|
+
{item.is_pinned &&
|
|
135
|
+
<Dropdown.Item onClick={handleUnpin}>Unpin</Dropdown.Item>
|
|
136
|
+
}
|
|
137
|
+
{!item.is_pinned &&
|
|
138
|
+
<Dropdown.Item onClick={handlePin}>Pin</Dropdown.Item>
|
|
139
|
+
}
|
|
140
|
+
</>
|
|
141
|
+
{item.type === ChatRoom &&
|
|
142
|
+
<Dropdown.Item onClick={handleLeaveConversation}>Leave conversation</Dropdown.Item>
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
{/* <li><Button.UI onClick={handleStar}>Star</Button.UI></li> */}
|
|
146
|
+
</Dropdown.UI>
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default ConversationListItem;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Icon from "../ui/Icon";
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
id: number,
|
|
6
|
+
name: string
|
|
7
|
+
className?: string,
|
|
8
|
+
icon: string
|
|
9
|
+
}
|
|
10
|
+
const File = ({ id, name, className, icon }: Props) => {
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<Icon.UI name={icon} size={1} />
|
|
15
|
+
<div className={className}>{name}</div>
|
|
16
|
+
<input type="hidden" value={id} name="blob"/>
|
|
17
|
+
</>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default File;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from "react";
|
|
2
|
+
import { WeavyContext } from "../contexts/WeavyContext";
|
|
3
|
+
import Button from '../ui/Button';
|
|
4
|
+
import Icon from '../ui/Icon';
|
|
5
|
+
import useMutateExternalBlobs from '../hooks/useMutateExternalBlobs';
|
|
6
|
+
import { UserContext } from "../contexts/UserContext";
|
|
7
|
+
import { prefix as wy } from "../utils/styles";
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
onFileAdded: Function
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const FileBrowser = ({ onFileAdded }: Props) => {
|
|
14
|
+
|
|
15
|
+
const { options } = useContext(WeavyContext);
|
|
16
|
+
const { user } = useContext(UserContext);
|
|
17
|
+
|
|
18
|
+
const addExternalBlobs = useMutateExternalBlobs();
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
window.addEventListener("message", handleFilebrowserMessage);
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
window.removeEventListener("message", handleFilebrowserMessage);
|
|
26
|
+
}
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const handleFilebrowserMessage = async (e: any) => {
|
|
30
|
+
const messageData = e.data;
|
|
31
|
+
|
|
32
|
+
switch (messageData.name) {
|
|
33
|
+
case "addExternalBlobs":
|
|
34
|
+
var result = await addExternalBlobs.mutateAsync({ blobs: messageData.blobs });
|
|
35
|
+
onFileAdded(result);
|
|
36
|
+
closeFilebrowser();
|
|
37
|
+
break;
|
|
38
|
+
case "file-browser-close":
|
|
39
|
+
closeFilebrowser();
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const closeFilebrowser = () => {
|
|
45
|
+
let fb = document.getElementById("weavy-filebrowser");
|
|
46
|
+
if (fb) {
|
|
47
|
+
fb.style.display = "none";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const openFilebrowser = () => {
|
|
52
|
+
let fb = document.getElementById("weavy-filebrowser");
|
|
53
|
+
|
|
54
|
+
if (!fb) {
|
|
55
|
+
const origin = window.top?.document.location.origin;
|
|
56
|
+
const fileBrowserUrl = options?.filebrowserUrl;
|
|
57
|
+
const filebrowserSrc = fileBrowserUrl + "?origin=" + origin + "&v=X&t=" + Date.now().toString() + "&weavyId=-1";
|
|
58
|
+
|
|
59
|
+
let $filebrowserFrame = document.createElement("iframe");
|
|
60
|
+
$filebrowserFrame.id = "weavy-filebrowser";
|
|
61
|
+
$filebrowserFrame.name = "weavy-filebrowser";
|
|
62
|
+
$filebrowserFrame.src = filebrowserSrc;
|
|
63
|
+
$filebrowserFrame.className = wy('filebrowser-frame');
|
|
64
|
+
$filebrowserFrame.style.cssText = "position: absolute; top: 0; left: 0; height: 100%; width: 100%; background: rgba(1,1,1,.4); z-index: 10000; display:none"
|
|
65
|
+
|
|
66
|
+
window.top?.document.body.appendChild($filebrowserFrame);
|
|
67
|
+
|
|
68
|
+
$filebrowserFrame.addEventListener('load', () => {
|
|
69
|
+
$filebrowserFrame.style.display = "block";
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
} else {
|
|
73
|
+
fb.style.display = "block";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
{options?.enableCloudFiles &&
|
|
80
|
+
<Button.UI onClick={openFilebrowser} title="Add file from cloud"><Icon.UI name="cloud" /></Button.UI>
|
|
81
|
+
}
|
|
82
|
+
</>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default FileBrowser;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { prefix as wy } from "../utils/styles";
|
|
3
|
+
|
|
4
|
+
type ImageProps = {
|
|
5
|
+
src: string,
|
|
6
|
+
previewSrc: string,
|
|
7
|
+
width: number
|
|
8
|
+
height: number,
|
|
9
|
+
more?: number,
|
|
10
|
+
onClick: (e: any) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Image = ({src, previewSrc, width, height, more, onClick}: ImageProps) => {
|
|
14
|
+
let ratio = width / height;
|
|
15
|
+
let baseSize = 64;
|
|
16
|
+
let maxScale = 2;
|
|
17
|
+
let flexRatio = ratio.toPrecision(5);
|
|
18
|
+
let flexBasis = (ratio * baseSize).toPrecision(5) + "px";
|
|
19
|
+
let padding = (100 / ratio).toPrecision(5) + "%"
|
|
20
|
+
let intrinsicWidth = width + "px"
|
|
21
|
+
let maxWidth = (maxScale * width) + "px";
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<a href={src} className={wy('image')} onClick={(e) => onClick(e)} style={{
|
|
25
|
+
flexBasis: flexBasis,
|
|
26
|
+
flexGrow: flexRatio,
|
|
27
|
+
flexShrink: flexRatio,
|
|
28
|
+
width: intrinsicWidth,
|
|
29
|
+
maxWidth: maxWidth
|
|
30
|
+
}}>
|
|
31
|
+
<div className={wy('image-area')} style={{ paddingBottom: padding}}>
|
|
32
|
+
<img src={previewSrc} alt="" loading="lazy" decoding="async" />
|
|
33
|
+
{!!more && <span className={wy('more')}>+{more}</span>}
|
|
34
|
+
</div>
|
|
35
|
+
</a>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type ImageGridProps = {
|
|
40
|
+
children: React.ReactNode,
|
|
41
|
+
limit?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const ImageGrid = ({ children, limit = 3}: ImageGridProps) => {
|
|
45
|
+
|
|
46
|
+
let allImages = React.Children.toArray(children);
|
|
47
|
+
|
|
48
|
+
let more = allImages.length > limit ? allImages.length - limit : 0;
|
|
49
|
+
|
|
50
|
+
let firstImages = allImages.slice(0, limit);
|
|
51
|
+
|
|
52
|
+
let lastChild = firstImages[firstImages.length - 1];
|
|
53
|
+
|
|
54
|
+
if (React.isValidElement(lastChild)) {
|
|
55
|
+
// Set more property on last image
|
|
56
|
+
firstImages[firstImages.length - 1] = React.cloneElement(lastChild, { more });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className={wy('image-grid')}>
|
|
61
|
+
{firstImages}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default Image;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Icon from '../ui/Icon';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
id: number,
|
|
6
|
+
title: string
|
|
7
|
+
className?: string
|
|
8
|
+
}
|
|
9
|
+
const Meeting = ({ id, title, className }: Props) => {
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
{/* <div className={className}>{title}</div> */}
|
|
14
|
+
<Icon.UI name="zoom" />
|
|
15
|
+
<div className={className}>Zoom meeting</div>
|
|
16
|
+
<input type="hidden" value={id} name="meeting"/>
|
|
17
|
+
</>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default Meeting;
|