Skip to content

Commit f1d7f21

Browse files
committed
add m365 configuration modal
1 parent d20f048 commit f1d7f21

File tree

11 files changed

+348
-20
lines changed

11 files changed

+348
-20
lines changed

packages/frontend/src/assets/microsoft-folder.svg

Lines changed: 14 additions & 0 deletions
Loading

packages/frontend/src/components/FlowStepConfigurationModal/ChooseAndAddConnection/AddConnection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import { getOpenerOrigin } from '@/helpers/window'
1818

1919
import Form from '../../Form'
2020
import { infoboxMdComponents } from '../../MarkdownRenderer/CustomMarkdownComponents'
21+
import { DEFAULT_ADD_CONNECTION_LABEL } from '../constants'
2122

2223
import ConnectionHeader from './ConnectionHeader'
23-
import { DEFAULT_ADD_CONNECTION_LABEL } from './constants'
2424

2525
type AddConnectionProps = {
2626
onSubmit: (response: Record<string, unknown>) => void

packages/frontend/src/components/FlowStepConfigurationModal/ChooseAndAddConnection/ChooseConnection.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import { EditorContext } from '@/contexts/Editor'
1111
import { REGISTER_CONNECTION } from '@/graphql/mutations/register-connection'
1212
import { TEST_CONNECTION } from '@/graphql/queries/test-connection'
1313

14+
import { DEFAULT_CHOOSE_CONNECTION_LABEL } from '../constants'
15+
1416
import ChooseConnectionDropdown from './ChooseConnectionDropdown'
1517
import ConnectionHeader from './ConnectionHeader'
16-
import { DEFAULT_CHOOSE_CONNECTION_LABEL } from './constants'
1718
import SetConnectionButton from './SetConnectionButton'
1819
import { ConnectionDropdownOption } from '.'
1920

packages/frontend/src/components/FlowStepConfigurationModal/ChooseAndAddConnection/ChooseConnectionDropdown.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { FormLabel } from '@opengovsg/design-system-react'
66

77
import { SingleSelect } from '@/components/SingleSelect'
88

9-
import { DEFAULT_ADD_CONNECTION_LABEL } from './constants'
9+
import { DEFAULT_ADD_CONNECTION_LABEL } from '../constants'
10+
1011
import { ConnectionDropdownOption } from '.'
1112

1213
interface ChooseConnectionDropdownProps {
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import type { IApp, IStep, ITestConnectionOutput } from '@plumber/types'
2+
3+
import { useCallback, useMemo, useState } from 'react'
4+
import { BiChevronLeft, BiQuestionMark, BiRightArrowAlt } from 'react-icons/bi'
5+
import { useMutation, useQuery } from '@apollo/client'
6+
import {
7+
Flex,
8+
Icon,
9+
Image,
10+
ModalBody,
11+
ModalHeader,
12+
Text,
13+
} from '@chakra-ui/react'
14+
import { Button, ModalCloseButton } from '@opengovsg/design-system-react'
15+
16+
import microsoftFolder from '@/assets/microsoft-folder.svg'
17+
import { REGISTER_CONNECTION } from '@/graphql/mutations/register-connection'
18+
import { TEST_CONNECTION } from '@/graphql/queries/test-connection'
19+
import useAuthentication from '@/hooks/useAuthentication'
20+
21+
import ConnectionHeader from './ConnectionHeader'
22+
23+
function ImageBox({
24+
imageUrl,
25+
boxSize,
26+
}: {
27+
imageUrl: string
28+
boxSize: number
29+
}) {
30+
return (
31+
<Image
32+
src={imageUrl}
33+
boxSize={boxSize}
34+
borderStyle="solid"
35+
fit="contain"
36+
fallback={
37+
<Icon
38+
boxSize={boxSize}
39+
as={BiQuestionMark}
40+
color="base.content.default"
41+
/>
42+
}
43+
/>
44+
)
45+
}
46+
47+
function SuccessfulConnectionHeader({ iconUrl }: { iconUrl: string }) {
48+
return (
49+
<Flex flexDir="column" alignItems="center" gap={4}>
50+
<Flex justifyContent="center" alignItems="center" gap={4} mt={2}>
51+
<ImageBox imageUrl={iconUrl} boxSize={14} />
52+
<Icon as={BiRightArrowAlt} boxSize={6} color="base.content.default" />
53+
<ImageBox imageUrl={microsoftFolder} boxSize={28} />
54+
</Flex>
55+
<Text textStyle="h4">Almost there! To start using your Excel files:</Text>
56+
</Flex>
57+
)
58+
}
59+
60+
function SuccessfulConnectionContent({
61+
userEmail,
62+
handleSubmit,
63+
}: {
64+
userEmail: string
65+
handleSubmit: () => void
66+
}) {
67+
return (
68+
<Flex flexDir="column" gap={3} mb={4}>
69+
<Text fontSize="sm" ml={4}>
70+
{`1. Find the `}
71+
<Text as="span" textDecoration="underline">
72+
{userEmail}
73+
</Text>
74+
{` folder we just created under 'Shared'`}
75+
</Text>
76+
<Text fontSize="sm" ml={4}>
77+
{`2. Place your Excel files in this folder to start using them`}
78+
</Text>
79+
80+
<Button isFullWidth onClick={handleSubmit}>
81+
Ok, got it!
82+
</Button>
83+
</Flex>
84+
)
85+
}
86+
87+
function ConfigurationConnectionContent({
88+
userEmail,
89+
onRegisterConnection,
90+
isLoading,
91+
}: {
92+
userEmail: string
93+
onRegisterConnection: () => void
94+
isLoading: boolean
95+
}) {
96+
return (
97+
<Flex flexDir="column" gap={3} mb={4}>
98+
<Text>How it works:</Text>
99+
<Text fontSize="sm" ml={4}>
100+
{`1. We'll create a folder named `}
101+
<Text as="span" textDecoration="underline">
102+
{userEmail}
103+
</Text>
104+
</Text>
105+
<Text fontSize="sm" ml={4}>
106+
{`2. You can use any Excel file in this folder in your workflow`}
107+
</Text>
108+
109+
<Button
110+
isFullWidth
111+
size="lg"
112+
onClick={onRegisterConnection}
113+
isLoading={isLoading}
114+
>
115+
Connect
116+
</Button>
117+
</Flex>
118+
)
119+
}
120+
121+
interface ConfigureExcelConnectionProps {
122+
selectedApp: IApp
123+
selectedConnectionId: string
124+
onBack: () => void
125+
handleSubmit: () => void
126+
mockStep?: IStep
127+
step?: IStep
128+
}
129+
130+
/**
131+
* This component is used to configure the Excel connection for a workflow step.
132+
* It should only appear if the connection is not yet registered.
133+
* Once the connection is registered, the component will display a success screen.
134+
*/
135+
export default function ConfigureExcelConnection(
136+
props: ConfigureExcelConnectionProps,
137+
) {
138+
const {
139+
selectedApp,
140+
selectedConnectionId,
141+
onBack,
142+
handleSubmit,
143+
mockStep,
144+
step,
145+
} = props
146+
const connectionModalLabel = selectedApp?.auth?.connectionModalLabel
147+
const [isRegistered, setIsRegistered] = useState(false) // track connection registration
148+
const { currentUser } = useAuthentication()
149+
150+
const { loading: testResultLoading, data: testConnectionData } = useQuery<{
151+
testConnection: ITestConnectionOutput
152+
}>(TEST_CONNECTION, {
153+
variables: {
154+
connectionId: selectedConnectionId,
155+
stepId: mockStep?.id,
156+
},
157+
skip: !selectedConnectionId || !isRegistered, // skip if not registered yet
158+
})
159+
160+
const [registerConnection, { loading: registerConnectionLoading }] =
161+
useMutation(REGISTER_CONNECTION)
162+
163+
const onRegisterConnection = useCallback(async () => {
164+
if (selectedConnectionId) {
165+
await registerConnection({
166+
variables: {
167+
input: {
168+
connectionId: selectedConnectionId,
169+
stepId: mockStep?.id,
170+
},
171+
},
172+
})
173+
setIsRegistered(true)
174+
}
175+
}, [mockStep, selectedConnectionId, registerConnection])
176+
177+
// Determine if the connection test was successful
178+
const isConnectionValid = useMemo(() => {
179+
if (testResultLoading || !testConnectionData?.testConnection) {
180+
return false
181+
}
182+
if (
183+
testConnectionData?.testConnection?.connectionVerified === false ||
184+
testConnectionData?.testConnection?.registrationVerified === false
185+
) {
186+
return false
187+
}
188+
return true
189+
}, [testConnectionData?.testConnection, testResultLoading])
190+
191+
return (
192+
<>
193+
<ModalHeader>
194+
{!isConnectionValid && (!step?.key || !step?.appKey) && (
195+
<Button
196+
variant="clear"
197+
colorScheme="secondary"
198+
size="xs"
199+
onClick={onBack}
200+
leftIcon={<BiChevronLeft />}
201+
ml={-4}
202+
>
203+
Back
204+
</Button>
205+
)}
206+
{isConnectionValid ? (
207+
<SuccessfulConnectionHeader iconUrl={selectedApp.iconUrl} />
208+
) : (
209+
<ConnectionHeader
210+
selectedApp={selectedApp}
211+
headerText={
212+
connectionModalLabel?.chooseConnectionLabel ??
213+
'Connect to M365 Excel'
214+
}
215+
/>
216+
)}
217+
</ModalHeader>
218+
<ModalCloseButton mt={2} size="xs" />
219+
220+
<ModalBody>
221+
{isConnectionValid ? (
222+
<SuccessfulConnectionContent
223+
userEmail={currentUser?.email ?? ''}
224+
handleSubmit={handleSubmit}
225+
/>
226+
) : (
227+
<ConfigurationConnectionContent
228+
userEmail={currentUser?.email ?? ''}
229+
onRegisterConnection={onRegisterConnection}
230+
isLoading={registerConnectionLoading}
231+
/>
232+
)}
233+
</ModalBody>
234+
</>
235+
)
236+
}

packages/frontend/src/components/FlowStepConfigurationModal/ChooseAndAddConnection/ConnectionHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Box, Flex, Icon, Image, Text } from '@chakra-ui/react'
55

66
import mainLogo from '@/assets/logo.svg'
77

8-
function AppLogoBox({ imageUrl }: { imageUrl: string }) {
8+
export function AppLogoBox({ imageUrl }: { imageUrl: string }) {
99
return (
1010
<Box
1111
boxSize={14}

packages/frontend/src/components/FlowStepConfigurationModal/ChooseAndAddConnection/constants.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

packages/frontend/src/components/FlowStepConfigurationModal/ChooseAndAddConnection/index.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import { useMutation, useQuery } from '@apollo/client'
77
import { GET_OR_CREATE_MOCK_STEP } from '@/graphql/mutations/get-or-create-mock-step'
88
import { GET_APP_CONNECTIONS } from '@/graphql/queries/get-app-connections'
99

10+
import { EXCEL_APP_KEY } from '../constants'
1011
import InvalidModalScreen from '../InvalidModalScreen'
1112
import { type ModalState } from '..'
1213

1314
import AddConnection from './AddConnection'
1415
import ChooseConnection from './ChooseConnection'
16+
import ConfigureExcelConnection from './ConfigureExcelConnection'
1517

1618
interface ChooseAndAddConnectionProps {
1719
onClose: () => void
@@ -258,6 +260,21 @@ export default function ChooseAndAddConnection(
258260
onBack={() => updateModalState({ currentScreen: 'choose-connection' })}
259261
/>
260262
)
263+
} else if (
264+
selectedApp?.key === EXCEL_APP_KEY &&
265+
selectedEvent &&
266+
currentScreen === 'configure-excel-connection'
267+
) {
268+
return (
269+
<ConfigureExcelConnection
270+
selectedApp={selectedApp as IApp}
271+
selectedConnectionId={selectedConnectionId}
272+
onBack={() => updateModalState({ currentScreen: 'choose-event' })}
273+
handleSubmit={handleSubmit}
274+
mockStep={mockStep ?? undefined}
275+
step={step}
276+
/>
277+
)
261278
} else {
262279
return <InvalidModalScreen />
263280
}

0 commit comments

Comments
 (0)