Skip to content

Commit

Permalink
feat(share-summary): Share summary mutation (#8412)
Browse files Browse the repository at this point in the history
* feat(share-summary): Add share topic button to meeting summary

* renderLoader

* Fix email render

* Fix modal position

* Fix scroll issue

* Share topic mutation

* Fix merge conflict

* chore: add generic radix Dialog component

* Move to ui, remove px

* useDialog

* Some customization

* More customization

* SimpleModalDialog

* chore: add radix select

* chore(share-summary): integration dropdown

* Add slack connect button

* Run yarn again

* Add slack channel picker

* Defaults

* Update dialog composition

* useDialogState

* Remove simple modal

* fix type

* Update slack message

* Snackbar

* Update shadow

* Update copy

* Cursor pointer

* Add topic url

* More slack message data

* tweaks

* run yarn

* Change imports

* Tweak

* revert useModalPortal changes

* check if summary exists

* better error handling

* Handle slack limit

* Remove console log

* Fix types

* tweak

* Show topic name

* Single character

* View message button

* Do not make query unless share button is clicked

* Add loading indicator

* add isLoading to SelectTrigger
  • Loading branch information
igorlesnenko authored Jul 14, 2023
1 parent 97c9d53 commit d5bcc37
Show file tree
Hide file tree
Showing 17 changed files with 463 additions and 86 deletions.
1 change: 1 addition & 0 deletions codegen.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"SetMeetingSettingsPayload": "../types/SetMeetingSettingsPayload#SetMeetingSettingsPayloadSource",
"SetNotificationStatusPayload": "./types/SetNotificationStatusPayload#SetNotificationStatusPayloadSource",
"SetOrgUserRoleSuccess": "./types/SetOrgUserRoleSuccess#SetOrgUserRoleSuccessSource",
"ShareTopicSuccess": "./types/ShareTopicSuccess#ShareTopicSuccessSource",
"StartTeamPromptSuccess": "./types/StartTeamPromptSuccess#StartTeamPromptSuccessSource",
"StripeFailPaymentPayload": "./types/StripeFailPaymentPayload#StripeFailPaymentPayloadSource",
"Task": "../../database/types/Task#default",
Expand Down
6 changes: 3 additions & 3 deletions packages/client/components/PrivateRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ const ReviewRequestToJoinOrgRoot = lazy(
() => import(/* webpackChunkName: 'ReviewRequestToJoinOrgRoot' */ './ReviewRequestToJoinOrgRoot')
)

const ShareTopicRoot = lazy(
() => import(/* webpackChunkName: 'ShareTopicRoot' */ './ShareTopicRoot')
const ShareTopicRouterRoot = lazy(
() => import(/* webpackChunkName: 'ShareTopicRouterRoot' */ './ShareTopicRouterRoot')
)

const NewMeetingRoot = lazy(
Expand Down Expand Up @@ -85,7 +85,7 @@ const PrivateRoutes = () => {
path='/organization-join-request/:requestId'
component={ReviewRequestToJoinOrgRoot}
/>
<Route path='/new-summary/:meetingId/share/:stageId' component={ShareTopicRoot} />
<Route path='/new-summary/:meetingId/share/:stageId' component={ShareTopicRouterRoot} />
<Route path='/new-meeting/:teamId?' component={NewMeetingRoot} />
</Switch>
</>
Expand Down
81 changes: 68 additions & 13 deletions packages/client/components/ShareTopicModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {DialogContent} from '../ui/Dialog/DialogContent'
import {DialogTitle} from '../ui/Dialog/DialogTitle'
import {DialogDescription} from '../ui/Dialog/DialogDescription'
import {DialogActions} from '../ui/Dialog/DialogActions'
import useShareTopicMutation from '../mutations/useShareTopicMutation'
import {Select} from '../ui/Select/Select'
import {SelectItem} from '../ui/Select/SelectItem'
import {SelectTrigger} from '../ui/Select/SelectTrigger'
Expand All @@ -20,11 +21,13 @@ import SlackClientManager from '../utils/SlackClientManager'
import useMutationProps from '../hooks/useMutationProps'
import useAtmosphere from '../hooks/useAtmosphere'
import useSlackChannels from '../hooks/useSlackChannels'
import findStageById from '../utils/meetings/findStageById'

interface Props {
isOpen: boolean
onClose: () => void
stageId: string
meetingId: string
queryRef: PreloadedQuery<ShareTopicModalQuery>
}

Expand All @@ -39,11 +42,23 @@ const ShareTopicModalViewerFragment = graphql`
isActive
botAccessToken
slackUserId
slackTeamId
defaultTeamChannelId
}
}
}
}
phases {
phaseType
stages {
id
... on RetroDiscussStage {
reflectionGroup {
title
}
}
}
}
}
}
`
Expand All @@ -59,22 +74,28 @@ const query = graphql`
type Integration = 'slack'

const ShareTopicModal = (props: Props) => {
const {isOpen, onClose, queryRef} = props

const onShare = () => {
/* TODO */
}

const {isOpen, onClose, queryRef, meetingId, stageId} = props
const atmosphere = useAtmosphere()
const {submitting, submitMutation, onError, onCompleted} = useMutationProps()
const [commit, shareTopicSubmitting] = useShareTopicMutation()
const {
submitting: slackOAuthSubmitting,
submitMutation,
onError,
onCompleted
} = useMutationProps()
const data = usePreloadedQuery<ShareTopicModalQuery>(query, queryRef)
const viewer = useFragment<ShareTopicModal_viewer$key>(ShareTopicModalViewerFragment, data.viewer)
const {meeting} = viewer

const stage = findStageById(meeting?.phases, stageId)?.stage
const topicTitle = stage?.reflectionGroup?.title ?? ''

const slack = meeting?.viewerMeetingMember?.teamMember.integrations.slack ?? null
const isSlackConnected = slack?.isActive
const slackDefaultTeamChannelId = slack?.defaultTeamChannelId
const slackChannels = useSlackChannels(slack)
const channelsLoading = slackChannels.length === 0 && isSlackConnected
const isLoading = slackOAuthSubmitting || shareTopicSubmitting

const defaultSelectedIntegration = isSlackConnected ? 'slack' : ''
const [selectedIntegration, setSelectedIntegration] = React.useState<Integration | ''>(
Expand All @@ -96,7 +117,7 @@ const ShareTopicModal = (props: Props) => {
setSelectedIntegration('slack')
} else {
SlackClientManager.openOAuth(atmosphere, teamId, {
submitting,
submitting: slackOAuthSubmitting,
submitMutation,
onError,
onCompleted: () => {
Expand All @@ -112,6 +133,38 @@ const ShareTopicModal = (props: Props) => {
setSelectedChannel(channel)
}

const onShare = () => {
commit(
{
variables: {
stageId,
meetingId,
channelId: selectedChannel
}
},
{
onSuccess: () => {
const channel = slackChannels.find((channel) => channel.id === selectedChannel)
atmosphere.eventEmitter.emit('addSnackbar', {
key: `topicShared`,
autoDismiss: 5,
message: `"${topicTitle}" has been shared to ${channel?.name}`,
action: {
label: `View message`,
callback: () => {
const url = `https://app.slack.com/client/${
slack?.slackTeamId ?? ''
}/${selectedChannel}`
window.open(url, '_blank', 'noopener')?.focus()
}
}
})
onClose()
}
}
)
}

const comingSoonBadge = (
<div className='flex items-center justify-center rounded-full bg-slate-300 px-3 py-1'>
<div className='text-center text-xs font-semibold text-slate-600'>coming soon</div>
Expand All @@ -131,14 +184,16 @@ const ShareTopicModal = (props: Props) => {
<Dialog isOpen={isOpen} onClose={onClose}>
<DialogContent className='z-10'>
<DialogTitle>Share topic</DialogTitle>
<DialogDescription>Where would you like to share the topic?</DialogDescription>
<DialogDescription>
Where would you like to share the topic "{topicTitle}"?
</DialogDescription>

<fieldset className={fieldsetStyles}>
<label className={labelStyles}>Integration</label>
<Select
onValueChange={onIntegrationChange}
value={selectedIntegration}
disabled={submitting}
disabled={isLoading}
>
<SelectTrigger>
{selectedIntegration !== '' ? (
Expand Down Expand Up @@ -178,9 +233,9 @@ const ShareTopicModal = (props: Props) => {
<Select
onValueChange={onChannelChange}
value={selectedChannel}
disabled={submitting || selectedIntegration === ''}
disabled={isLoading || channelsLoading || selectedIntegration === ''}
>
<SelectTrigger>
<SelectTrigger isLoading={channelsLoading}>
<SelectValue />
</SelectTrigger>
<SelectContent position='item-aligned'>
Expand All @@ -199,7 +254,7 @@ const ShareTopicModal = (props: Props) => {
<SecondaryButton onClick={onClose} size='small'>
Cancel
</SecondaryButton>
<PrimaryButton size='small' onClick={onShare}>
<PrimaryButton size='small' onClick={onShare} disabled={isLoading || channelsLoading}>
Share
</PrimaryButton>
</DialogActions>
Expand Down
31 changes: 15 additions & 16 deletions packages/client/components/ShareTopicRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
import React, {Suspense, useCallback} from 'react'
import {useHistory, useLocation} from 'react-router'
import useRouter from '../hooks/useRouter'
import React, {Suspense} from 'react'
import ShareTopicModal from '~/components/ShareTopicModal'
import {renderLoader} from '../utils/relay/renderLoader'
import useQueryLoaderNow from '../hooks/useQueryLoaderNow'
import shareTopicModalQuery, {
ShareTopicModalQuery
} from '../__generated__/ShareTopicModalQuery.graphql'

const ShareTopicRoot = () => {
const {match} = useRouter<{stageId: string; meetingId: string}>()
const {params} = match
interface Props {
onClose: () => void
stageId: string
meetingId: string
}

const {meetingId, stageId} = params
const ShareTopicRoot = (props: Props) => {
const {stageId, meetingId, onClose} = props

const queryRef = useQueryLoaderNow<ShareTopicModalQuery>(
shareTopicModalQuery,
{meetingId},
'store-or-network'
)

const location = useLocation<{backgroundLocation?: Location}>()
const history = useHistory()

const onClose = useCallback(() => {
const state = location.state
history.replace(state?.backgroundLocation ?? `/new-summary/${meetingId}`)
}, [location])

return (
<Suspense fallback={renderLoader()}>
{queryRef && (
<ShareTopicModal stageId={stageId} isOpen={true} onClose={onClose} queryRef={queryRef} />
<ShareTopicModal
stageId={stageId}
meetingId={meetingId}
isOpen={true}
onClose={onClose}
queryRef={queryRef}
/>
)}
</Suspense>
)
Expand Down
46 changes: 46 additions & 0 deletions packages/client/components/ShareTopicRouterRoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, {Suspense, useCallback} from 'react'
import {useHistory, useLocation} from 'react-router'
import useRouter from '../hooks/useRouter'
import ShareTopicModal from '~/components/ShareTopicModal'
import {renderLoader} from '../utils/relay/renderLoader'
import useQueryLoaderNow from '../hooks/useQueryLoaderNow'
import shareTopicModalQuery, {
ShareTopicModalQuery
} from '../__generated__/ShareTopicModalQuery.graphql'

const ShareTopicRouterRoot = () => {
const {match} = useRouter<{stageId: string; meetingId: string}>()
const {params} = match

const {meetingId, stageId} = params

const queryRef = useQueryLoaderNow<ShareTopicModalQuery>(
shareTopicModalQuery,
{meetingId},
'store-or-network'
)

const location = useLocation<{backgroundLocation?: Location}>()
const history = useHistory()

const onClose = useCallback(() => {
const state = location.state
history.replace(state?.backgroundLocation ?? `/new-summary/${meetingId}`)
}, [location])

return (
<Suspense fallback={renderLoader()}>
{queryRef && (
<ShareTopicModal
stageId={stageId}
meetingId={meetingId}
isOpen={true}
onClose={onClose}
queryRef={queryRef}
/>
)}
</Suspense>
)
}

export default ShareTopicRouterRoot
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import {PALETTE} from 'parabol-client/styles/paletteV3'
import {FONT_FAMILY} from 'parabol-client/styles/typographyV2'
import React, {Suspense} from 'react'
import React from 'react'
import AnchorIfEmail from './AnchorIfEmail'
import makeAppURL from '../../../../../utils/makeAppURL'
import {renderLoader} from '../../../../../utils/relay/renderLoader'
import ShareTopicModal from '../../../../../components/ShareTopicModal'
import {useDialogState} from '../../../../../ui/Dialog/useDialogState'
import useQueryLoaderNow from '../../../../../hooks/useQueryLoaderNow'
import shareTopicModalQuery, {
ShareTopicModalQuery
} from '../../../../../__generated__/ShareTopicModalQuery.graphql'
import ShareTopicRoot from '../../../../../components/ShareTopicRoot'

interface Props {
isEmail: boolean
Expand Down Expand Up @@ -51,13 +46,6 @@ const ShareTopic = (props: Props) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const {isOpen, open, close} = useDialogState()

// eslint-disable-next-line react-hooks/rules-of-hooks
const queryRef = useQueryLoaderNow<ShareTopicModalQuery>(
shareTopicModalQuery,
{meetingId},
'network-only'
)

const onClick = () => {
if (isDemo) return
open()
Expand All @@ -68,11 +56,8 @@ const ShareTopic = (props: Props) => {
<span style={style} onClick={onClick}>
{label}
</span>
<Suspense fallback={renderLoader()}>
{queryRef && (
<ShareTopicModal stageId={stageId} isOpen={isOpen} onClose={close} queryRef={queryRef} />
)}
</Suspense>

{isOpen && <ShareTopicRoot onClose={close} stageId={stageId} meetingId={meetingId} />}
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const labelLookup = {
meetingEnd: 'Meeting End',
meetingStart: 'Meeting Start',
MEETING_STAGE_TIME_LIMIT_END: `Meeting ${MeetingLabels.TIME_LIMIT} Ended`,
MEETING_STAGE_TIME_LIMIT_START: `Meeting ${MeetingLabels.TIME_LIMIT} Started`
MEETING_STAGE_TIME_LIMIT_START: `Meeting ${MeetingLabels.TIME_LIMIT} Started`,
TOPIC_SHARED: `Topic Shared`
} as Record<SlackNotificationEventEnum, string>

const Row = styled('div')({
Expand Down
Loading

0 comments on commit d5bcc37

Please sign in to comment.