From cf48ae2934cd6ed29e4b11f62180a1b466b6a303 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Wed, 8 Jan 2025 11:26:56 +0100 Subject: [PATCH] feat(Mattermost): Invite to Meeting (#10635) --- .../InviteToMeetingModal.tsx | 152 ++++++++++++++++++ .../components/InviteToMeetingModal/index.tsx | 22 +++ .../InviteToTeamModal/InviteToTeamModal.tsx | 19 ++- .../LinkTeamModal/LinkTeamModal.tsx | 6 +- .../components/ModalRoot.tsx | 2 + .../components/Sidepanel/ActiveMeetings.tsx | 6 +- .../components/Sidepanel/LinkedTeams.tsx | 6 +- .../useMassInvitationToken.tsx | 8 +- packages/mattermost-plugin/index.tsx | 5 + packages/mattermost-plugin/package.json | 1 + .../mattermost-plugin/prod.webpack.config.js | 15 +- .../public/mattermost-plugin-commands.json | 6 +- packages/mattermost-plugin/reducers.ts | 9 ++ packages/mattermost-plugin/resolvers.ts | 2 +- packages/mattermost-plugin/selectors.ts | 3 + packages/mattermost-plugin/webpack.config.js | 15 +- 16 files changed, 240 insertions(+), 37 deletions(-) create mode 100644 packages/mattermost-plugin/components/InviteToMeetingModal/InviteToMeetingModal.tsx create mode 100644 packages/mattermost-plugin/components/InviteToMeetingModal/index.tsx rename packages/mattermost-plugin/{components/InviteToTeamModal => hooks}/useMassInvitationToken.tsx (86%) diff --git a/packages/mattermost-plugin/components/InviteToMeetingModal/InviteToMeetingModal.tsx b/packages/mattermost-plugin/components/InviteToMeetingModal/InviteToMeetingModal.tsx new file mode 100644 index 00000000000..4f4b0164c18 --- /dev/null +++ b/packages/mattermost-plugin/components/InviteToMeetingModal/InviteToMeetingModal.tsx @@ -0,0 +1,152 @@ +import graphql from 'babel-plugin-relay/macro' +import {useEffect, useMemo, useState} from 'react' +import {useDispatch, useSelector} from 'react-redux' +import {useLazyLoadQuery} from 'react-relay' + +import {closeInviteToMeetingModal} from '../../reducers' + +import Select from '../Select' + +import {Client4} from 'mattermost-redux/client' +import {getCurrentUser} from 'mattermost-redux/selectors/entities/common' +import {Post} from 'mattermost-redux/types/posts' +import {PALETTE} from '~/styles/paletteV3' +import {InviteToMeetingModalQuery} from '../../__generated__/InviteToMeetingModalQuery.graphql' +import {useCurrentChannel} from '../../hooks/useCurrentChannel' +import useMassInvitationToken from '../../hooks/useMassInvitationToken' +import LoadingSpinner from '../LoadingSpinner' +import Modal from '../Modal' + +const InviteToMeetingModal = () => { + const channel = useCurrentChannel() + const data = useLazyLoadQuery( + graphql` + query InviteToMeetingModalQuery($channel: ID!) { + config { + parabolUrl + } + linkedTeamIds(channel: $channel) + viewer { + teams { + id + name + activeMeetings { + id + teamId + name + meetingType + } + } + } + } + `, + { + channel: channel.id + } + ) + + const {viewer, config, linkedTeamIds} = data + const parabolUrl = config?.parabolUrl + const {teams} = viewer + const linkedTeams = useMemo( + () => viewer.teams.filter((team) => !linkedTeamIds || linkedTeamIds.includes(team.id)), + [viewer, linkedTeamIds] + ) + const activeMeetings = useMemo( + () => linkedTeams.flatMap((team) => team.activeMeetings), + [linkedTeams] + ) + + const [selectedMeeting, setSelectedMeeting] = useState<(typeof activeMeetings)[number]>() + + useEffect(() => { + if (!selectedMeeting && activeMeetings && activeMeetings.length > 0) { + setSelectedMeeting(activeMeetings[0]) + } + }, [activeMeetings, selectedMeeting]) + + const getToken = useMassInvitationToken({ + teamId: selectedMeeting?.teamId, + meetingId: selectedMeeting?.id + }) + + const currentUser = useSelector(getCurrentUser) + + const dispatch = useDispatch() + const handleClose = () => { + dispatch(closeInviteToMeetingModal()) + } + + const handleStart = async () => { + if (!selectedMeeting) { + return + } + const {name: meetingName, id: meetingId} = selectedMeeting + const token = await getToken() + if (!token) { + return + } + const team = linkedTeams.find((team) => team.id === selectedMeeting.teamId)! + const teamName = team.name + const inviteUrl = `${parabolUrl}/invitation-link/${token}` + const meetingUrl = `${parabolUrl}/meet/${meetingId}` + const {username, nickname, first_name, last_name} = currentUser + const userName = nickname || username || `${first_name} ${last_name}` + const props = { + attachments: [ + { + fallback: `${userName} invited you to join the meeting ${meetingName}`, + title: `${userName} invited you to join a meeting in [Parabol](${meetingUrl})`, + color: PALETTE.GRAPE_500, + fields: [ + { + short: true, + title: 'Team', + value: teamName + }, + { + short: true, + title: 'Meeting', + value: meetingName + }, + { + short: false, + value: ` +| [Join Meeting](${inviteUrl}) | +|:--------------------:| +||` + } + ] + } + ] + } + Client4.createPost({ + channel_id: channel.id, + props + } as Partial as Post) + handleClose() + } + + return ( + + {teams ? ( +