Skip to content

Commit

Permalink
feat(Mattermost): Invite to Meeting (#10635)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dschoordsch authored Jan 8, 2025
1 parent be72f7a commit cf48ae2
Show file tree
Hide file tree
Showing 16 changed files with 240 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -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<InviteToMeetingModalQuery>(
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<Post> as Post)
handleClose()
}

return (
<Modal
title='Invite Channel to Join Activity'
commitButtonLabel='Share Invite'
handleCommit={handleStart}
handleClose={handleClose}
>
{teams ? (
<Select
label='Activity'
required={true}
options={activeMeetings ?? []}
value={selectedMeeting}
onChange={setSelectedMeeting}
/>
) : (
<LoadingSpinner />
)}
</Modal>
)
}

export default InviteToMeetingModal
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {lazy, Suspense} from 'react'
import {useSelector} from 'react-redux'

import {isInviteToMeetingModalVisible} from '../../selectors'

const InviteToMeetingModal = lazy(
() => import(/* webpackChunkName: 'CreateTaskModal' */ './InviteToMeetingModal')
)

const InviteToMeetingModalRoot = () => {
const isVisible = useSelector(isInviteToMeetingModalVisible)
if (!isVisible) {
return null
}
return (
<Suspense fallback={null}>
<InviteToMeetingModal />
</Suspense>
)
}

export default InviteToMeetingModalRoot
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ 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 {InviteToTeamModalQuery} from '../../__generated__/InviteToTeamModalQuery.graphql'
import {useCurrentChannel} from '../../hooks/useCurrentChannel'
import useMassInvitationToken from '../../hooks/useMassInvitationToken'
import LoadingSpinner from '../LoadingSpinner'
import Modal from '../Modal'
import useMassInvitationToken from './useMassInvitationToken'

const InviteToTeamModal = () => {
const data = useLazyLoadQuery<InviteToTeamModalQuery>(
Expand Down Expand Up @@ -60,29 +61,31 @@ const InviteToTeamModal = () => {
if (!selectedTeam) {
return
}
const {name: teamName} = selectedTeam
const {id: teamId, name: teamName} = selectedTeam
const token = await getToken()
if (!token) {
return
}
const url = `${parabolUrl}/invitation-link/${token}`
const inviteUrl = `${parabolUrl}/invitation-link/${token}`
const teamUrl = `${parabolUrl}/team/${teamId}`
const {username, nickname, first_name, last_name} = currentUser
const userName = nickname || username || `${first_name} ${last_name}`
const props = {
attachments: [
{
fallback: `Join our team ${teamName}, [Join Team](${url})`,
title: `${userName} invited you to join a team in [Parabol](${teamUrl})`,
fallback: `${userName} invited you to join a team ${teamName} in Parabol`,
color: PALETTE.GRAPE_500,
fields: [
{short: true, value: teamName},
{
short: false,
value: `
| [Join Team](${url}) |
| [Join Team](${inviteUrl}) |
|:--------------------:|
||`
}
],
title: `${userName} invited you to join a team in Parabol`
]
}
]
}
Expand All @@ -95,7 +98,7 @@ const InviteToTeamModal = () => {

return (
<Modal
title='Invite to Parabol Team'
title='Invite Channel to Join Parabol Team'
commitButtonLabel='Share Invite'
handleCommit={handleStart}
handleClose={handleClose}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const LinkTeamModal = () => {
const data = useLazyLoadQuery<LinkTeamModalQuery>(
graphql`
query LinkTeamModalQuery($channel: ID!) {
linkedTeamIds(channel: $channel)
viewer {
linkedTeamIds(channel: $channel)
teams {
id
name
Expand All @@ -32,8 +32,8 @@ const LinkTeamModal = () => {
channel: channel.id
}
)
const viewer = data.viewer
const unlinkedTeams = viewer.teams.filter((team) => !viewer.linkedTeamIds?.includes(team.id))
const {viewer, linkedTeamIds} = data
const unlinkedTeams = viewer.teams.filter((team) => !linkedTeamIds?.includes(team.id))
const linkTeam = useLinkTeam()

const [selectedTeam, setSelectedTeam] = React.useState<(typeof data.viewer.teams)[number]>()
Expand Down
2 changes: 2 additions & 0 deletions packages/mattermost-plugin/components/ModalRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CreateTaskModalRoot from './CreateTaskModal'
import InviteToMeetingModalRoot from './InviteToMeetingModal'
import InviteToTeamModalRoot from './InviteToTeamModal'
import LinkTeamModal from './LinkTeamModal'
import PushReflectionModalRoot from './PushReflection'
Expand All @@ -9,6 +10,7 @@ const ModalRoot = () => {
<>
<CreateTaskModalRoot />
<InviteToTeamModalRoot />
<InviteToMeetingModalRoot />
<LinkTeamModal />
<PushReflectionModalRoot />
<StartActivityModalRoot />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ const ActiveMeetings = () => {
config {
parabolUrl
}
linkedTeamIds(channel: $channel)
viewer {
linkedTeamIds(channel: $channel)
teams {
id
activeMeetings {
Expand All @@ -33,9 +33,9 @@ const ActiveMeetings = () => {
channel: channel.id
}
)
const viewer = data.viewer
const {viewer, linkedTeamIds} = data
const linkedTeams = viewer.teams.filter(
(team) => !viewer.linkedTeamIds || viewer.linkedTeamIds.includes(team.id)
(team) => !linkedTeamIds || linkedTeamIds.includes(team.id)
)
const isLoading = false
const error = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const LinkedTeams = () => {
config {
parabolUrl
}
linkedTeamIds(channel: $channel)
viewer {
linkedTeamIds(channel: $channel)
teams {
id
...TeamRow_team
Expand All @@ -28,9 +28,9 @@ const LinkedTeams = () => {
channel: channel.id
}
)
const viewer = data.viewer
const {viewer, linkedTeamIds} = data
const linkedTeams = viewer.teams.filter(
(team) => !viewer.linkedTeamIds || viewer.linkedTeamIds.includes(team.id)
(team) => !linkedTeamIds || linkedTeamIds.includes(team.id)
)

const dispatch = useDispatch()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import graphql from 'babel-plugin-relay/macro'
import {useLazyLoadQuery, useMutation} from 'react-relay'

import {useCallback} from 'react'
import {Threshold} from '../../../client/types/constEnums'
import {useMassInvitationTokenMutation} from '../../__generated__/useMassInvitationTokenMutation.graphql'
import {useMassInvitationTokenQuery} from '../../__generated__/useMassInvitationTokenQuery.graphql'
import {mutationResult} from '../../utils/mutationResult'
import {Threshold} from '../../client/types/constEnums'
import {useMassInvitationTokenMutation} from '../__generated__/useMassInvitationTokenMutation.graphql'
import {useMassInvitationTokenQuery} from '../__generated__/useMassInvitationTokenQuery.graphql'
import {mutationResult} from '../utils/mutationResult'

const FIVE_MINUTES = 1000 * 60 * 5
const acceptableLifeLeft = Threshold.MASS_INVITATION_TOKEN_LIFESPAN - FIVE_MINUTES
Expand Down
5 changes: 5 additions & 0 deletions packages/mattermost-plugin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import PanelTitle from './components/Sidepanel/PanelTitle'
import manifest from './manifest'
import rootReducer, {
openCreateTaskModal,
openInviteToMeetingModal,
openInviteToTeamModal,
openPushPostAsReflection,
openStartActivityModal
Expand Down Expand Up @@ -59,6 +60,10 @@ export const init = async (registry: PluginRegistry, store: Store<GlobalState, A
store.dispatch(openInviteToTeamModal())
})

registry.registerWebSocketEventHandler(`custom_${manifest.id}_share`, () => {
store.dispatch(openInviteToMeetingModal())
})

registry.registerPostDropdownMenuAction(
<div>
<span className='MenuItem__icon'>
Expand Down
1 change: 1 addition & 0 deletions packages/mattermost-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"ts-node-dev": "^1.0.0-pre.44"
},
"dependencies": {
"parabol-client": "8.16.0",
"@mattermost/compass-icons": "0.1.47",
"@reduxjs/toolkit": "1.9.7",
"@tiptap/core": "^2.9.1",
Expand Down
Loading

0 comments on commit cf48ae2

Please sign in to comment.