Skip to content

Commit fd80e73

Browse files
committed
feat: hide steps depending on approval branch, show disabled mrf step
1 parent e390d43 commit fd80e73

File tree

8 files changed

+167
-28
lines changed

8 files changed

+167
-28
lines changed

packages/frontend/src/components/Editor/components/AddStepButton.tsx

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { IStep } from '@plumber/types'
1+
import { IStep, IStepApprovalBranch } from '@plumber/types'
22

3+
import { useMemo } from 'react'
34
import { BiPlus } from 'react-icons/bi'
45
import { Box, Divider, useDisclosure } from '@chakra-ui/react'
56
import { IconButton, TouchableTooltip } from '@opengovsg/design-system-react'
@@ -16,12 +17,39 @@ interface AddStepButtonProps {
1617
isLastStep: boolean
1718
showEmptyAction: boolean
1819
step: IStep
20+
approvalBranch: IStepApprovalBranch
1921
}
2022

2123
export function AddStepButton(props: AddStepButtonProps): JSX.Element {
22-
const { isHidden, isLastStep, step, isDisabled, showEmptyAction } = props
24+
const {
25+
isHidden,
26+
isLastStep,
27+
step,
28+
isDisabled,
29+
showEmptyAction,
30+
approvalBranch,
31+
} = props
2332
const { isOpen, onOpen, onClose } = useDisclosure()
2433

34+
const { dividerColor, iconBgColor } = useMemo(() => {
35+
if (approvalBranch === undefined) {
36+
return {
37+
dividerColor: 'base.divider.strong',
38+
iconBgColor: undefined,
39+
}
40+
}
41+
if (approvalBranch === 'approve') {
42+
return {
43+
dividerColor: 'green.500',
44+
iconBgColor: 'green.50',
45+
}
46+
}
47+
return {
48+
dividerColor: 'red.500',
49+
iconBgColor: 'red.50',
50+
}
51+
}, [approvalBranch])
52+
2553
const {
2654
cancelRef,
2755
isWarningOpen,
@@ -39,7 +67,8 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element {
3967
flexDir="column"
4068
alignItems="center"
4169
alignSelf="stretch"
42-
h={showEmptyAction ? undefined : isHidden ? 12 : 16}
70+
h="auto"
71+
w="100%"
4372
>
4473
{isHidden || (isDisabled && !isLastStep) ? (
4574
!isLastStep && (
@@ -65,7 +94,7 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element {
6594
)}
6695
{/* Top vertical line */}
6796
<Box h={6}>
68-
<Divider orientation="vertical" borderColor="base.divider.strong" />
97+
<Divider orientation="vertical" borderColor={dividerColor} />
6998
</Box>
7099
<TouchableTooltip
71100
label={isDisabled ? '' : 'Add step'}
@@ -81,6 +110,7 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element {
81110
variant={isLastStep ? 'outline' : 'clear'}
82111
size="xs"
83112
color="interaction.sub.default"
113+
bg={iconBgColor}
84114
borderRadius="full"
85115
pointerEvents={isDisabled ? 'none' : 'auto'}
86116
_hover={{
@@ -89,17 +119,14 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element {
89119
_active={{
90120
bg: 'interaction.muted.neutral.active',
91121
}}
92-
borderColor={isLastStep ? 'interaction.sub.default' : undefined}
122+
borderColor={isLastStep ? dividerColor : undefined}
93123
h={8}
94124
/>
95125
</TouchableTooltip>
96126
{/* Bottom vertical line */}
97127
{!isLastStep && (
98128
<Box h={6}>
99-
<Divider
100-
orientation="vertical"
101-
borderColor="base.divider.strong"
102-
/>
129+
<Divider orientation="vertical" borderColor={dividerColor} />
103130
</Box>
104131
)}
105132

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { IStep } from '@plumber/types'
2+
3+
import { useContext } from 'react'
4+
import { Flex } from '@chakra-ui/react'
5+
import { TouchableTooltip } from '@opengovsg/design-system-react'
6+
7+
import StepAppIcon from '@/components/FlowStep/components/StepAppIcon'
8+
import StepCaptionAndDemo from '@/components/FlowStep/components/StepCaptionAndDemo'
9+
import { flowStepStyles } from '@/components/FlowStep/styles'
10+
import { EditorContext } from '@/contexts/Editor'
11+
import { getFlowStepHeaderWidth } from '@/helpers/editor'
12+
import { useStepMetadata } from '@/hooks/useStepMetadata'
13+
14+
interface DisabledFlowStepProps {
15+
step: IStep
16+
tooltipText: string
17+
}
18+
19+
export function DisabledFlowStep(props: DisabledFlowStepProps) {
20+
const { step, tooltipText } = props
21+
22+
const { isMobile, isDrawerOpen } = useContext(EditorContext)
23+
24+
const { app, caption } = useStepMetadata(step)
25+
const headerWidth = getFlowStepHeaderWidth(isDrawerOpen, isMobile, false)
26+
27+
return (
28+
<Flex
29+
flexDir="row"
30+
alignItems="center"
31+
width="100%"
32+
display={isMobile ? 'block' : 'flex'}
33+
my={4}
34+
>
35+
<TouchableTooltip label={tooltipText} wrapperStyles={{ width: '100%' }}>
36+
<Flex
37+
data-test="flow-step"
38+
{...flowStepStyles.container}
39+
h="64px"
40+
w={headerWidth}
41+
>
42+
<Flex
43+
{...flowStepStyles.topHeader}
44+
opacity={0.4}
45+
pointerEvents="none"
46+
>
47+
<StepAppIcon app={app} step={step} />
48+
<StepCaptionAndDemo app={app} caption={caption} />
49+
</Flex>
50+
</Flex>
51+
</TouchableTooltip>
52+
</Flex>
53+
)
54+
}

packages/frontend/src/components/Editor/components/FlowStepWithAddButton.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { IStep } from '@plumber/types'
22

3+
import { useContext } from 'react'
4+
5+
import { MrfContext } from '@/contexts/MrfContext'
36
import { FlowStep } from '@/exports/components'
47
import { useStepMetadata } from '@/hooks/useStepMetadata'
58

69
import { ApproveReject } from '../../FlowStep/components/ApproveReject'
710

811
import { AddStepButton } from './AddStepButton'
12+
import { DisabledFlowStep } from './DisabledFlowStep'
913

1014
export default function FlowStepWithAddButton({
1115
step,
@@ -28,7 +32,11 @@ export default function FlowStepWithAddButton({
2832
showEmptyAction: boolean
2933
}
3034
}) {
31-
const { isApprovalStep } = useStepMetadata(step)
35+
const { approvalBranches, disabledMrfStepToDisplay } = useContext(MrfContext)
36+
const { isApprovalStep, approvalBranch } = useStepMetadata(step)
37+
const approvalBranchForAddStep = approvalBranch ?? approvalBranches[step.id]
38+
39+
const shouldShowDisabledMrfStep = isLastStep && disabledMrfStepToDisplay
3240

3341
return (
3442
<>
@@ -46,7 +54,16 @@ export default function FlowStepWithAddButton({
4654
isHidden={isHidden}
4755
isDisabled={isDisabled}
4856
showEmptyAction={showEmptyAction}
57+
approvalBranch={approvalBranchForAddStep}
4958
/>
59+
{shouldShowDisabledMrfStep && (
60+
<DisabledFlowStep
61+
step={disabledMrfStepToDisplay}
62+
tooltipText={
63+
'This step will only happen if the previous MRF step is approved'
64+
}
65+
/>
66+
)}
5067
</>
5168
)
5269
}

packages/frontend/src/components/FlowStep/components/ApproveReject.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback, useContext } from 'react'
22
import { Flex, Tab, TabList, TabProps, Tabs } from '@chakra-ui/react'
33

4+
import { EditorContext } from '@/contexts/Editor'
45
import { MrfContext } from '@/contexts/MrfContext'
56

67
const tabStyle = (primaryColor: string): TabProps => {
@@ -24,15 +25,18 @@ const tabStyle = (primaryColor: string): TabProps => {
2425
}
2526

2627
export function ApproveReject({ stepId }: { stepId: string }) {
28+
const { setCurrentStepId, onDrawerClose } = useContext(EditorContext)
2729
const { approvalBranches, setApprovalBranch } = useContext(MrfContext)
2830

2931
const isApproveBranch = approvalBranches[stepId] === 'approve'
3032

3133
const onChange = useCallback(
3234
(index: number) => {
3335
setApprovalBranch(stepId, index === 0 ? 'approve' : 'reject')
36+
setCurrentStepId(null)
37+
onDrawerClose()
3438
},
35-
[stepId, setApprovalBranch],
39+
[stepId, setApprovalBranch, setCurrentStepId, onDrawerClose],
3640
)
3741

3842
return (

packages/frontend/src/components/FlowStep/index.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ export default function FlowStep(
7777
substeps,
7878
shouldShowDragHandle,
7979
isDeletable,
80-
approvalBranch,
8180
} = useStepMetadata(step, true)
8281

8382
const {
@@ -233,9 +232,6 @@ export default function FlowStep(
233232
borderTopRadius={hasInfoBox ? 'none' : 'lg'}
234233
h={isNested ? '48px' : '64px'}
235234
w={headerWidth}
236-
// approval branch can be approve, reject or undefined
237-
// boxShadow specified in theme/foundations/shadows.ts
238-
boxShadow={isNested ? 'none' : approvalBranch ?? undefined}
239235
>
240236
<Flex {...flowStepStyles.topHeader} onClick={handleClick}>
241237
<StepAppIcon

packages/frontend/src/components/FlowStepGroup/index.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { NESTED_DRAG_HANDLE_WIDTH } from '@/components/SortableList/components/S
99
import { EditorContext } from '@/contexts/Editor'
1010
import { getFlowStepHeaderWidth, getToolboxIcon } from '@/helpers/editor'
1111
import { TOOLBOX_ACTIONS } from '@/helpers/toolbox'
12-
import { useStepMetadata } from '@/hooks/useStepMetadata'
1312

1413
import { MIN_FLOW_STEP_WIDTH } from '../Editor/constants'
1514

@@ -28,7 +27,6 @@ interface FlowStepGroupProps {
2827
export default function FlowStepGroup(props: FlowStepGroupProps) {
2928
const { groupedSteps, stepsBeforeGroup } = props
3029
const { isDrawerOpen, isMobile, onDrawerClose } = useContext(EditorContext)
31-
const { approvalBranch } = useStepMetadata(groupedSteps[0]?.[0])
3230

3331
const { stepGroupType, stepGroupCaption } = useMemo(() => {
3432
let stepGroupType: string | null = null
@@ -87,10 +85,6 @@ export default function FlowStepGroup(props: FlowStepGroupProps) {
8785
display={isMobile ? 'block' : 'flex'}
8886
w={getFlowStepHeaderWidth(isDrawerOpen, isMobile)}
8987
minW={MIN_FLOW_STEP_WIDTH}
90-
// approval branch can be approve, reject or undefined
91-
// boxShadow specified in theme/foundations/shadows.ts
92-
// we display for entire group instead of individual nested steps
93-
boxShadow={approvalBranch ?? undefined}
9488
>
9589
<Box {...flowStepGroupStyles.header} w="100%">
9690
<Flex

packages/frontend/src/contexts/MrfContext.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ interface MrfContextReturnValue {
1414
[stepId: string]: IStepApprovalBranch
1515
}
1616
setApprovalBranch: (stepId: string, branch: IStepApprovalBranch) => void
17+
disabledMrfStepToDisplay: IStep | null
1718
}
1819

1920
export const MrfContext = createContext<MrfContextReturnValue>({
2021
mrfSteps: [],
2122
mrfApprovalSteps: [],
2223
approvalBranches: {},
2324
setApprovalBranch: () => null,
25+
disabledMrfStepToDisplay: null,
2426
})
2527

2628
interface MrfContextProviderProps {
@@ -30,6 +32,9 @@ interface MrfContextProviderProps {
3032
export const MrfContextProvider = ({ children }: MrfContextProviderProps) => {
3133
const { flow } = useContext(EditorContext)
3234

35+
const [disabledMrfStepToDisplay, setDisabledMrfStepToDisplay] =
36+
useState<IStep | null>(null)
37+
3338
const mrfSteps = flow.steps.filter(
3439
(step) => step.appKey === FORMSG_APP_KEY && step.key === MRF_ACTION_KEY,
3540
)
@@ -52,6 +57,13 @@ export const MrfContextProvider = ({ children }: MrfContextProviderProps) => {
5257
...prev,
5358
[stepId]: branch,
5459
}))
60+
if (branch === 'reject') {
61+
const nextMrfStepId =
62+
mrfSteps[mrfSteps.findIndex((mrfStep) => mrfStep.id === stepId) + 1]
63+
setDisabledMrfStepToDisplay(nextMrfStepId ?? null)
64+
} else {
65+
setDisabledMrfStepToDisplay(null)
66+
}
5567
}
5668

5769
return (
@@ -61,6 +73,7 @@ export const MrfContextProvider = ({ children }: MrfContextProviderProps) => {
6173
mrfApprovalSteps,
6274
approvalBranches,
6375
setApprovalBranch,
76+
disabledMrfStepToDisplay,
6477
}}
6578
>
6679
{children}

packages/frontend/src/contexts/StepsToDisplay.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createContext, useContext, useMemo } from 'react'
66
import { extractBranchesWithSteps } from '@/helpers/toolbox'
77

88
import { EditorContext } from './Editor'
9+
import { MrfContext } from './MrfContext'
910

1011
export type StepToDisplayContextValue = {
1112
triggerStep: IStep | null
@@ -31,8 +32,38 @@ export function StepsToDisplayProvider({
3132
children,
3233
}: StepExecutionsProviderProps): JSX.Element {
3334
const { allApps, flow } = useContext(EditorContext)
35+
const { approvalBranches } = useContext(MrfContext)
3436

35-
const steps = flow.steps
37+
const allSteps = flow.steps
38+
39+
const stepsToDisplay = useMemo(() => {
40+
let firstRejectBranchStepId: string | null = null
41+
return allSteps.filter((step) => {
42+
if (
43+
firstRejectBranchStepId != null &&
44+
step.config?.approval?.stepId !== firstRejectBranchStepId
45+
) {
46+
return false
47+
}
48+
49+
if (!firstRejectBranchStepId && approvalBranches[step.id] === 'reject') {
50+
firstRejectBranchStepId = step.id
51+
return true
52+
}
53+
54+
if (!step.config?.approval) {
55+
return true
56+
}
57+
58+
const approvalConfigStepId = step.config?.approval?.stepId
59+
const approvalConfigBranch = step.config?.approval?.branch
60+
61+
if (approvalBranches[approvalConfigStepId] === approvalConfigBranch) {
62+
return true
63+
}
64+
return false
65+
})
66+
}, [allSteps, approvalBranches])
3667

3768
const appsWithActions: IApp[] = allApps.filter(
3869
(app: IApp) => !!app.actions?.length,
@@ -57,7 +88,7 @@ export function StepsToDisplayProvider({
5788
return [null, [], []]
5889
}
5990

60-
const groupStepIdx = steps.findIndex((step, index) => {
91+
const groupStepIdx = stepsToDisplay.findIndex((step, index) => {
6192
if (
6293
// We ignore the 1st step because it's either a trigger, or a
6394
// step-grouping action that is using a nested Editor to edit steps in
@@ -73,15 +104,18 @@ export function StepsToDisplayProvider({
73104

74105
let branchesWithSteps: IStep[][] = []
75106
if (groupStepIdx !== -1) {
76-
branchesWithSteps = extractBranchesWithSteps(steps.slice(groupStepIdx), 0)
107+
branchesWithSteps = extractBranchesWithSteps(
108+
stepsToDisplay.slice(groupStepIdx),
109+
0,
110+
)
77111
}
78112

79-
const triggerStep = steps[0]
113+
const triggerStep = stepsToDisplay[0]
80114

81115
return groupStepIdx === -1
82-
? [triggerStep, steps.slice(1), []]
83-
: [triggerStep, steps.slice(1, groupStepIdx), branchesWithSteps]
84-
}, [groupingActions, steps])
116+
? [triggerStep, stepsToDisplay.slice(1), []]
117+
: [triggerStep, stepsToDisplay.slice(1, groupStepIdx), branchesWithSteps]
118+
}, [groupingActions, stepsToDisplay])
85119

86120
return (
87121
<StepsToDisplayContext.Provider

0 commit comments

Comments
 (0)