Skip to content

Commit

Permalink
[SALAD-23118] WebApp: Reusable modal component / GetNotifiedDemandCha…
Browse files Browse the repository at this point in the history
…ngesModal - added (#1232)

* ModalWithOverlay component - added

* ModalWithOverlay: unnecessary prop - removed

* temp

* GetNotifiedDemandChangesModal - added

* ModalWithOverlay import - simplified

* GetNotifiedDemandChangesModal appears on Get Notified Button click

* Demand Monitor UI: styling - improved

* ReviewAfterRedemption: styling - fixed

* ModalWithOverlay: styles - improved

* ReviewAfterRedemption: textarea area width increased
  • Loading branch information
vitto-moz authored Dec 3, 2024
1 parent 4469306 commit 852caef
Show file tree
Hide file tree
Showing 18 changed files with 387 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { WithStyles } from 'react-jss'
import withStyles from 'react-jss'
import type { CSSObjectWithLabel, GroupBase, StylesConfig } from 'react-select'
import Select from 'react-select'
import { DefaultTheme, type SaladTheme } from '../SaladTheme'
import { DefaultTheme, type SaladTheme } from '../../SaladTheme'

const styles = (theme: SaladTheme) => ({
container: {
Expand All @@ -18,7 +18,7 @@ interface Option {

export type DropdownStylesConfig = StylesConfig<Option | '', false, GroupBase<Option | ''>>

interface Props extends WithStyles<typeof styles> {
export interface Props extends WithStyles<typeof styles> {
customStyles?: DropdownStylesConfig
options?: Option[]
value?: string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { FC } from 'react'
import type { CSSObjectWithLabel } from 'react-select'
import { DefaultTheme } from '../../../SaladTheme'
import type { DropdownStylesConfig } from '../Dropdown'
import { Dropdown, type Props } from '../Dropdown'

const dropdownLightStyles: DropdownStylesConfig = {
control: (baseStyles: CSSObjectWithLabel) => ({
...baseStyles,
backgroundColor: DefaultTheme.lightGreen,
width: '230px',
borderRadius: 0,
}),
menu: (baseStyles: CSSObjectWithLabel) => ({
...baseStyles,
color: DefaultTheme.darkBlue,
backgroundColor: DefaultTheme.lightGreen,
}),
option: (baseStyles: CSSObjectWithLabel, state: { isSelected: boolean; isFocused: boolean }) => {
let backgroundColor = DefaultTheme.lightGreen
if (state.isSelected) {
backgroundColor = DefaultTheme.darkBlue
} else if (state.isFocused) {
backgroundColor = DefaultTheme.green
}

return {
...baseStyles,
backgroundColor,
color: state.isSelected ? DefaultTheme.lightGreen : DefaultTheme.darkBlue,
cursor: 'pointer',
}
},
singleValue: (baseStyles: CSSObjectWithLabel) => ({
...baseStyles,
transition: 'opacity 300ms',
color: DefaultTheme.darkBlue,
}),
}

export const DropdownLight: FC<Omit<Props, 'customStyles' | 'classes'>> = ({ options, value, onChange }) => {
return <Dropdown customStyles={dropdownLightStyles} options={options} value={value} onChange={onChange} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './DropdownLight'
2 changes: 2 additions & 0 deletions packages/web-app/src/components/Dropdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Dropdown'
export * from './DropdownLight'
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { X } from '@saladtechnologies/garden-icons'
import type CSS from 'csstype'
import type { FC } from 'react'
import { useRef } from 'react'
import type { WithStyles } from 'react-jss'
import withStyles from 'react-jss'
import { useDetectClickOutsideElement } from '../../hooks/useDetectClickOutsideElement'
import type { SaladTheme } from '../../SaladTheme'

const styles: (theme: SaladTheme) => Record<string, CSS.Properties> = (theme: SaladTheme) => ({
modalOverlay: {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: 1000000000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#00000070',
fontFamily: theme.fontMallory,
top: '0px',
left: '0px',
},
modalContainer: {
position: 'relative',
backgroundColor: theme.darkBlue,
overflow: 'hidden',
boxSizing: 'border-box',
},
closeIcon: {
position: 'absolute',
right: '16px',
top: '16px',
color: theme.lightGreen,
width: '16px',
height: '16px',
cursor: 'pointer',
},
})

interface Props extends WithStyles<typeof styles> {
onCloseClick: () => void
children: JSX.Element
}

const _ModalWithOverlay: FC<Props> = ({ classes, onCloseClick, children }) => {
const modalContainerRef = useRef(null)

useDetectClickOutsideElement(modalContainerRef, onCloseClick)

return (
<div className={classes.modalOverlay}>
<div className={classes.modalContainer} ref={modalContainerRef}>
{children}
<X className={classes.closeIcon} onClick={onCloseClick} />
</div>
</div>
)
}

export const ModalWithOverlay = withStyles(styles)(_ModalWithOverlay)
1 change: 1 addition & 0 deletions packages/web-app/src/components/ModalWithOverlay/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ModalWithOverlay'
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type { FunctionComponent } from 'react'
import type { WithStyles } from 'react-jss'
import withStyles from 'react-jss'
import { DefaultTheme } from '../../../../SaladTheme'
import { Dropdown } from '../../../../components/Dropdown'
import { customSetUpDropdownStyles, demandScenarios, mockedGpuNames } from '../../constants'
import { DropdownLight } from '../../../../components/Dropdown'
import { demandScenarios, mockedGpuNames } from '../../constants'

const styles: () => Record<string, CSS.Properties> = () => ({
container: {
Expand Down Expand Up @@ -38,11 +38,11 @@ const _DemandAlertsSetUp: FunctionComponent<Props> = ({ classes }) => {
<div className={classes.dropdownContainer}>
<div className={classes.dropdownContentContainer}>
<Text variant="baseS">GPU</Text>
<Dropdown customStyles={customSetUpDropdownStyles} options={mockedGpuNames} />
<DropdownLight options={mockedGpuNames} />
</div>
<div className={classes.dropdownContentContainer}>
<Text variant="baseS">Demand Scenario</Text>
<Dropdown customStyles={customSetUpDropdownStyles} options={demandScenarios} />
<DropdownLight options={demandScenarios} />
</div>
</div>
<Button onClick={() => {}} label="Add Alert" outlineColor={DefaultTheme.darkBlue} />
Expand Down
38 changes: 0 additions & 38 deletions packages/web-app/src/modules/demand-alerts-views/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import type { CSSObjectWithLabel } from "react-select";
import { DefaultTheme } from "../../SaladTheme";
import type { DropdownStylesConfig } from "../../components/Dropdown";

export const mockedGpuNames = [
{ label: 'NVIDIA RTX 4090', value: 'NVIDIA RTX 4090' },
{ label: 'NVIDIA RTX 4080', value: 'NVIDIA RTX 4080' },
Expand All @@ -13,37 +9,3 @@ export const demandScenarios = [
{ label: 'Moderate Demand', value: 'moderateDemand' },
{ label: 'Low Demand', value: 'lowDemand' },
]

export const customSetUpDropdownStyles: DropdownStylesConfig = {
control: (baseStyles: CSSObjectWithLabel) => ({
...baseStyles,
backgroundColor: DefaultTheme.lightGreen,
width: '230px',
borderRadius: 0,
}),
menu: (baseStyles: CSSObjectWithLabel) => ({
...baseStyles,
color: DefaultTheme.darkBlue,
backgroundColor: DefaultTheme.lightGreen,
}),
option: (baseStyles: CSSObjectWithLabel, state: { isSelected: boolean; isFocused: boolean }) => {
let backgroundColor = DefaultTheme.lightGreen
if (state.isSelected) {
backgroundColor = DefaultTheme.darkBlue
} else if (state.isFocused) {
backgroundColor = DefaultTheme.green
}

return {
...baseStyles,
backgroundColor,
color: state.isSelected ? DefaultTheme.lightGreen : DefaultTheme.darkBlue,
cursor: 'pointer',
}
},
singleValue: (baseStyles: CSSObjectWithLabel) => ({
...baseStyles,
transition: 'opacity 300ms',
color: DefaultTheme.darkBlue,
}),
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ export const styles = (theme: SaladTheme): Record<string, CSS.Properties> => ({
textShadow: '0px 0px 24px rgba(178, 213, 48, 0.7)',
},
descriptionHeader: {
fontFamily: 'Mallory',
fontFamily: theme.fontMallory,
color: theme.mediumGreen,
fontSize: '16px',
lineHeight: '24px',
paddingBottom: '0px',
},
description: {
fontFamily: 'Mallory',
fontFamily: theme.fontMallory,
color: theme.lightGreen,
fontSize: '16px',
lineHeight: '24px',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { Button, Text } from '@saladtechnologies/garden-components'
import { Bell } from '@saladtechnologies/garden-icons'
import type CSS from 'csstype'
import { type FunctionComponent } from 'react'
import { useEffect, useRef, useState, type FunctionComponent } from 'react'
import type { WithStyles } from 'react-jss'
import withStyles from 'react-jss'
import { useMediaQuery } from 'react-responsive'
import { mobileSize, Scrollbar } from '../../../components'
import type { SaladTheme } from '../../../SaladTheme'
import type { DemandedHardwarePerformance } from '../DemandMonitorStore'
import { DemandMonitorFAQ } from './DemandMonitorFAQ'
import { DemandMonitorTableContainer } from './DemandMonitorTable/DemandMonitorTableContainer'
import { DemandMonitorTable } from './DemandMonitorTable'
import { oneHourInMilliseconds } from './DemandMonitorTable/constants'
import { GetNotifiedDemandChangesModal } from './GetNotifiedDemandChangesModal/GetNotifiedDemandChangesModal'

const styles: (theme: SaladTheme) => Record<string, CSS.Properties> = (theme: SaladTheme) => ({
pageWrapper: {
Expand Down Expand Up @@ -50,7 +53,7 @@ const styles: (theme: SaladTheme) => Record<string, CSS.Properties> = (theme: Sa
},
},
description: {
fontFamily: 'Mallory',
fontFamily: theme.fontMallory,
color: theme.lightGreen,
fontSize: '16px',
lineHeight: '24px',
Expand All @@ -60,11 +63,47 @@ const styles: (theme: SaladTheme) => Record<string, CSS.Properties> = (theme: Sa
width: '100%',
},
})
interface Props extends WithStyles<typeof styles> {
export interface Props extends WithStyles<typeof styles> {
demandedHardwarePerformanceList?: DemandedHardwarePerformance[]
withGetNotifiedButton: boolean
fetchDemandedHardwarePerformanceList: () => void
onLoginClick: () => void
}

const _DemandMonitorPage: FunctionComponent<Props> = ({ classes, withGetNotifiedButton }) => {
const _DemandMonitorPage: FunctionComponent<Props> = ({
fetchDemandedHardwarePerformanceList,
onLoginClick,
withGetNotifiedButton,
demandedHardwarePerformanceList,
classes,
}) => {
const [isModalShown, setIsModalShown] = useState(false)

const updateTimerRef = useRef<NodeJS.Timeout | null>(null)

useEffect(() => {
fetchDemandedHardwarePerformanceList()
updateTimerRef.current = setInterval(fetchDemandedHardwarePerformanceList, oneHourInMilliseconds)

return () => {
if (updateTimerRef.current) {
clearInterval(updateTimerRef.current)
}
}
}, [fetchDemandedHardwarePerformanceList])

const handleModalCloseClick = () => {
setIsModalShown(false)
}

const handleModalContinueClick = () => {
setIsModalShown(false)
}

const handleGetNotifiedButtonClick = () => {
setIsModalShown(true)
}

const getPageContent = () => {
return (
<div className={classes.pageWrapper}>
Expand All @@ -78,16 +117,30 @@ const _DemandMonitorPage: FunctionComponent<Props> = ({ classes, withGetNotified
refreshed hourly.
</Text>
{withGetNotifiedButton && (
<Button width={148} leadingIcon={<Bell />} label="Get Notified" variant="secondary" />
<Button
width={148}
leadingIcon={<Bell />}
label="Get Notified"
variant="secondary"
onClick={handleGetNotifiedButtonClick}
/>
)}
</div>
<div className={classes.sectionWrapper}>
<DemandMonitorTableContainer />
<DemandMonitorTable demandedHardwarePerformanceList={demandedHardwarePerformanceList} />
</div>
<div className={classes.sectionWrapper}>
<DemandMonitorFAQ />
</div>
</div>
{isModalShown && (
<GetNotifiedDemandChangesModal
onLoginClick={onLoginClick}
onCloseClick={handleModalCloseClick}
onContinuesClick={handleModalContinueClick}
demandedHardwarePerformanceList={demandedHardwarePerformanceList}
/>
)}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ import type { FC } from 'react'
import { connect } from '../../../connect'
import { FeatureFlags, useFeatureManager } from '../../../FeatureManager'
import type { RootStore } from '../../../Store'
import { DemandMonitorPage } from './DemandMonitorPage'
import { DemandMonitorPage, type Props as DemandMonitorPageProps } from './DemandMonitorPage'

interface Props {
interface Props extends DemandMonitorPageProps {
isAuthenticated: boolean
}

export const _DemandMonitorPageContainer: FC<Props> = ({ isAuthenticated }) => {
export const _DemandMonitorPageContainer: FC<Props> = ({ isAuthenticated, ...props }: Props) => {
const featureManager = useFeatureManager()
const isDemandNotificationsFeatureFlagEnabled = featureManager.isEnabled(FeatureFlags.DemandNotifications)
const withGetNotifiedButton = isDemandNotificationsFeatureFlagEnabled && !isAuthenticated

return <DemandMonitorPage withGetNotifiedButton={withGetNotifiedButton} />
return <DemandMonitorPage {...props} withGetNotifiedButton={withGetNotifiedButton} />
}

const mapStoreToProps = (store: RootStore, props: Props): any => ({
...props,
const mapStoreToProps = (store: RootStore): any => ({
onLoginClick: store.auth.login,
fetchDemandedHardwarePerformanceList: store.demandMonitor.fetchDemandedHardwarePerformanceList,
isAuthenticated: store.auth.isAuthenticated,
demandedHardwarePerformanceList: store.demandMonitor.demandedHardwarePerformanceList,
})

export const DemandMonitorPageContainer = connect(mapStoreToProps, _DemandMonitorPageContainer)
Loading

0 comments on commit 852caef

Please sign in to comment.