@material-ui/core#Collapse TypeScript Examples
The following examples show how to use
@material-ui/core#Collapse.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ExpandableElement.tsx From bee-dashboard with BSD 3-Clause "New" or "Revised" License | 6 votes |
export default function ExpandableElement({ children, expandable, defaultOpen }: Props): ReactElement | null {
const classes = useStyles()
const [open, setOpen] = useState<boolean>(Boolean(defaultOpen))
const handleClick = () => {
setOpen(!open)
}
return (
<div className={`${classes.root} ${classes.rootLevel2}`}>
<ListItem button onClick={handleClick} className={classes.header}>
{children}
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<div className={classes.contentLevel12}>{expandable}</div>
</Collapse>
</div>
)
}
Example #2
Source File: ProductInsightsCardList.tsx From backstage with Apache License 2.0 | 6 votes |
ProductInsightsCardList = ({
initialStates,
onSelectAsync,
}: ProductInsightsCardListProps) => {
if (!initialStates.length) {
return (
<Box display="flex" justifyContent="space-around" alignItems="center">
<CircularProgress />
</Box>
);
}
return (
<Collapse in timeout={1000}>
{initialStates.map(({ product, entity, duration }) => (
<Box
key={product.kind}
mb={6}
position="relative"
data-testid={`product-list-item-${product.kind}`}
>
<ProductInsightsCard
product={product}
onSelectAsync={onSelectAsync}
initialState={{ entity: entity, duration: duration }}
/>
</Box>
))}
</Collapse>
);
}
Example #3
Source File: NotificationButton.tsx From clearflask with Apache License 2.0 | 6 votes |
render() {
if (!this.props.isLoggedIn) return null;
return (
<Collapse
in={!!this.props.notifications?.length}
className={this.props.className}
>
<IconButton
aria-label='Notifications'
onClick={e => this.setState({ notificationAnchorEl: !!this.state.notificationAnchorEl ? undefined : e.currentTarget })}
>
<Badge
badgeContent={this.props.notifications ? this.props.notifications.length : 0}
color='secondary'
variant={this.props.showCount ? 'standard' : 'dot'}
max={999}
>
<NotificationsIcon fontSize='inherit' />
<NotificationPopup
server={this.props.server}
anchorEl={this.state.notificationAnchorEl}
onClose={() => this.setState({ notificationAnchorEl: undefined })}
/>
</Badge>
</IconButton>
</Collapse>
);
}
Example #4
Source File: alert-component.tsx From react-spring-messenger-project with MIT License | 6 votes |
AlertComponent: React.FunctionComponent<AlertComponentType> = () => {
const {alerts, setAlerts} = useAlertContext();
function closeAlert(id: string) {
const indexToDelete = alerts.findIndex((elt) => elt.id === id);
const alertsCopy = [...alerts];
const eltToDelete = alertsCopy[indexToDelete];
eltToDelete.isOpen = false;
alertsCopy[indexToDelete] = eltToDelete;
setAlerts(alertsCopy);
setTimeout(() => {
alertsCopy.splice(indexToDelete, 1)
setAlerts(alertsCopy);
}, 3000)
}
return (
<div style={{position: "absolute", bottom: "2%", left: "1%"}}>
{
alerts.map((value) => (
<div key={value.id} style={{margin: "5px"}}>
<Collapse in={value.isOpen}>
<Alert onClose={() => closeAlert(value.id)}
severity={value.alert}
variant={"standard"}>
{value.text}
</Alert>
</Collapse>
</div>
))
}
</div>
)
}
Example #5
Source File: ControlsSubtabChangelog.tsx From Teyvat.moe with GNU General Public License v3.0 | 6 votes |
ControlsSubtabChangelogVersion: FunctionComponent<ControlsSubtabChangelogVersionProps> = memo(
({ version, date, description }) => {
const classes = useStyles();
const [open, setOpen] = useState(false);
const toggleOpen = () => setOpen((value) => !value);
return (
<>
<ListItem disableGutters button onClick={toggleOpen}>
<ListItemText primary={version} secondary={date} />
{open ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</ListItem>
<Collapse in={open}>
<List dense disablePadding>
{_.map(description, (descriptionLine, index) => (
<ListItem
key={`${index}/${descriptionLine}`}
disableGutters
className={classes.subItem}
>
<ListItemText primary={descriptionLine} />
</ListItem>
))}
</List>
</Collapse>
</>
);
}
)
Example #6
Source File: TableExpandable.tsx From frontegg-react with MIT License | 6 votes |
TableExpandable: FC<TableExpandableProps<any>> = <T extends object>(props: TableExpandableProps<T>) => { const { isExpanded, renderExpandedComponent, row } = props; return ( <TableRow> <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={row.cells.length}> <Collapse in={isExpanded} timeout='auto' unmountOnExit> <Box margin='0 auto'>{renderExpandedComponent?.(row.original, row.index)}</Box> </Collapse> </TableCell> </TableRow> ); }
Example #7
Source File: PDF.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
PDF: React.FC<ContentProps> = (props:ContentProps) => {
assert(props.content.type === 'pdf')
const memberRef = useRef<Member>(new Member())
const member = memberRef.current
member.newProps = props
const refCanvas = useRef<HTMLCanvasElement>(null)
const refTextDiv = useRef<HTMLDivElement>(null)
const refAnnotationDiv = useRef<HTMLDivElement>(null)
const editing = useObserver(() => props.stores.contents.editing === props.content.id)
useEffect(()=>{
member.canvas = refCanvas.current
member.textDiv = refTextDiv.current
member.annotationDiv = refAnnotationDiv.current
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refCanvas.current])
useEffect(()=>{
member.updateProps()
})
return <div style={{overflow: 'hidden', pointerEvents: 'auto', userSelect: editing? 'text':'none'}}
onDoubleClick = {(ev) => { if (!editing) {
ev.stopPropagation()
ev.preventDefault()
props.stores.contents.setEditing(props.content.id)
} }} >
<canvas style={{ width:`${CANVAS_SCALE*100}%`, height:`${CANVAS_SCALE*100}%`,
transformOrigin:'top left', transform:`scale(${1/CANVAS_SCALE})`}} ref={refCanvas} />
<div ref={refTextDiv} style={{position:'absolute', left:0, top:0,
width:`${CANVAS_SCALE*100}%`, height:`${CANVAS_SCALE*100}%`,
transformOrigin:'top left', transform:`scale(${1/CANVAS_SCALE})`, lineHeight: 1,
overflow:'hidden'}} />
<div ref={refAnnotationDiv} />
<div style={{position:'absolute', top:0, left:0, width:'100%', height:40}}
onPointerEnter={()=>{member.showTop = true}} onPointerLeave={()=>{member.showTop = false}}>
<Observer>{()=>
<Collapse in={member.showTop} style={{position:'absolute', top:0, left:0, width:'100%'}}>
<Grid container alignItems="center">
<Grid item >
<IconButton size="small" color={member.pageNum>0?'primary':'default'} {...stopper}
onClick={(ev) => { ev.stopPropagation(); member.updateUrl(member.pageNum - 1) }}
onDoubleClick={(ev) => {ev.stopPropagation() }} >
<NavigateBeforeIcon />
</IconButton>
</Grid>
<Grid item xs={1}>
<TextField value={member.pageText} {...stopper}
inputProps={{min: 0, style: { textAlign: 'center' }}}
onChange={(ev)=> { member.pageText = ev.target.value}}
onBlur={(ev) => {
const num = Number(member.pageText)
if (num > 0) { member.updateUrl(num-1) }
}}
onKeyPress={(ev)=>{
if (ev.key === 'Enter'){
const num = Number(member.pageText)
if (num > 0) { member.updateUrl(num-1) }
}
}}
/>
</Grid>
<Grid item style={{fontSize:15}}>/ {member.numPages}</Grid>
<Grid item >
<IconButton size="small" color={member.pageNum<member.numPages-1?'primary':'default'} {...stopper}
onClick={(ev) => { ev.stopPropagation(); member.updateUrl(member.pageNum + 1) }}
onDoubleClick={(ev) => {ev.stopPropagation() }} >
<NavigateNextIcon />
</IconButton>
</Grid>
</Grid>
</Collapse>
}</Observer>
</div>
</div>
}
Example #8
Source File: Scores.tsx From dashboard with Apache License 2.0 | 5 votes |
Scores = ({ score, nested }: ScoreProps) => {
const [show, setShow] = useState(false)
const { palette } = useTheme()
let { op_name, operands, value, ref_id, description } = score
if (!op_name && !operands && !value) {
return <></>
}
const toggleShow = () => {
setShow((prev) => !prev)
}
const canToggle = operands && operands.length > 0
return (
<div style={{ paddingLeft: nested ? "10px" : "0px" }}>
<ListItem button={canToggle} onClick={toggleShow}>
<Grid container>
<Grid item xs={4}>
<Box paddingTop="0.75rem">
<Typography noWrap fontSize="1.25rem">
{score.value || "0.12341234"}
</Typography>
</Box>
</Grid>
<Grid item xs={7}>
<Box>
<Typography noWrap>{op_name}</Typography>
<Typography noWrap color={palette.grey[500]}>
{description}
</Typography>
</Box>
</Grid>
</Grid>
{canToggle && (
<ListItemSecondaryAction>
({operands?.length}){show ? <ExpandLess /> : <ExpandMore />}
</ListItemSecondaryAction>
)}
</ListItem>
{operands && (
<div>
<Collapse in={show}>
<List>
{operands.map((operand, index) => (
<Scores score={operand} key={`${index}-${ref_id}`} nested />
))}
</List>
</Collapse>
</div>
)}
</div>
)
}
Example #9
Source File: ExpandableList.tsx From bee-dashboard with BSD 3-Clause "New" or "Revised" License | 5 votes |
export default function ExpandableList({ children, label, level, defaultOpen, info }: Props): ReactElement | null {
const classes = useStyles()
const [open, setOpen] = useState<boolean>(Boolean(defaultOpen))
const handleClick = () => {
setOpen(!open)
}
let rootLevelClass = ''
let typographyVariant: 'h1' | 'h2' | 'h3' = 'h1'
let contentLevelClass = classes.contentLevel0
if (level === 1) {
rootLevelClass = classes.rootLevel1
typographyVariant = 'h2'
contentLevelClass = classes.contentLevel12
} else if (level === 2) {
rootLevelClass = classes.rootLevel2
typographyVariant = 'h3'
contentLevelClass = classes.contentLevel12
}
return (
<div className={`${classes.root} ${rootLevelClass}`}>
<ListItem button onClick={handleClick} className={classes.header}>
<ListItemText primary={<Typography variant={typographyVariant}>{label}</Typography>} />
<div style={{ display: 'flex' }}>
{!open && (
<Typography variant="body2" className={classes.infoText}>
{info}
</Typography>
)}
{open ? <ExpandLess /> : <ExpandMore />}
</div>
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<div className={contentLevelClass}>{children}</div>
</Collapse>
</div>
)
}
Example #10
Source File: CommentReply.tsx From clearflask with Apache License 2.0 | 5 votes |
render() {
return (
<Collapse
mountOnEnter
in={this.props.collapseIn !== false}
className={classNames(this.props.className, this.props.classes.addCommentFormOuter)}
>
<div className={this.props.classes.addCommentForm}>
<RichEditor
uploadImage={(file) => this.richEditorImageUploadRef.current!.uploadImage(file)}
variant='outlined'
size='small'
id='createComment'
className={this.props.classes.addCommentField}
label={this.props.inputLabel}
iAgreeInputIsSanitized
minInputHeight={60}
value={this.state.newCommentInput || ''}
onChange={e => this.setState({ newCommentInput: e.target.value })}
multiline
rowsMax={10}
InputProps={{
inputRef: this.inputRef,
// onBlurAndEmpty after a while, fixes issue where pasting causes blur.
onBlur: () => setTimeout(() => !this.state.newCommentInput && this.props.onBlurAndEmpty && this.props.onBlurAndEmpty(), 200),
}}
/>
<RichEditorImageUpload
ref={this.richEditorImageUploadRef}
server={this.props.server}
/>
<Collapse in={!!this.state.newCommentInput}>
<Button
color='primary'
variant='contained'
disableElevation
className={this.props.classes.addCommentSubmitButton}
disabled={!this.state.newCommentInput}
onClick={e => {
this.props.logIn().then(() => this.props.server.dispatch().then(d => d.commentCreate({
projectId: this.props.server.getProjectId(),
ideaId: this.props.ideaId,
commentCreate: {
content: this.state.newCommentInput!,
parentCommentId: this.props.mergedPostId === this.props.parentCommentId ? undefined : this.props.parentCommentId,
mergedPostId: this.props.mergedPostId,
},
}))).then(comment => {
this.setState({ newCommentInput: undefined })
this.props.onSubmitted && this.props.onSubmitted();
});
}}
>
Post
</Button>
</Collapse>
{!!this.props.focusOnIn && (
<ScrollAnchor scrollNow={this.props.collapseIn} />
)}
</div>
</Collapse>
);
}
Example #11
Source File: SwaggerView.tsx From dashboard with Apache License 2.0 | 5 votes |
SwaggerView = () => {
const [show, setShow] = useState(false)
const [host, setHost] = useState(getInitialHostAndPort().host)
const [port, setPort] = useState(getInitialHostAndPort().port)
const [endpoint, setEndpoint] = useState(DEFAULT_ENDPOINT)
const [url, setURL] = useState(formatURL(host, port, endpoint))
const updateURL = () => {
setURL(formatURL(host, port, endpoint))
}
const toggleShow = () => {
setShow((prev) => !prev)
}
return (
<Container data-name="debuggingTool">
<Box textAlign="right">
<Button onClick={toggleShow}>
endpoint settings {show ? <ExpandLess /> : <ExpandMore />}
</Button>
</Box>
<Collapse in={show}>
<Box paddingTop="2.5em">
<Grid container spacing={2}>
<Grid item xs={4}>
<TextInput
label="Host"
variant="outlined"
value={host}
onChange={(e) => setHost(e.target.value)}
/>
</Grid>
<Grid item xs={2}>
<TextInput
label="Port"
variant="outlined"
value={port}
onChange={(e) => setPort(e.target.value)}
/>
</Grid>
<Grid item xs={4}>
<TextInput
label="OpenAPI Schema"
variant="outlined"
value={endpoint}
onChange={(e) => setEndpoint(e.target.value)}
/>
</Grid>
<Grid item xs={2}>
<FullSizeButton onClick={updateURL} variant="contained">
Set
</FullSizeButton>
</Grid>
</Grid>
</Box>
</Collapse>
<SwaggerUIReact
url={url}
presets={[WrappedComponents]}
requestInterceptor={(r) => console.log("request:", r)}
responseInterceptor={(r) => console.log("response:", r)}
/>
</Container>
)
}
Example #12
Source File: PostEdit.tsx From clearflask with Apache License 2.0 | 5 votes |
PostSaveButton = (props: {
children?: any;
open?: boolean;
showNotify?: boolean;
isSubmitting?: boolean;
onSave: (doNotify: boolean) => void;
onCancel?: () => void;
}) => {
const classes = useStyles();
const [doNotify, setNotify] = useState<boolean>(!!props.showNotify);
return (
<>
{props.children}
<Collapse mountOnEnter in={!!props.open}>
<div className={classes.saveButtonActions}>
{props.showNotify && (
<FormControlLabel
disabled={props.isSubmitting}
control={(
<Checkbox
checked={doNotify}
onChange={(e, checked) => setNotify(!doNotify)}
color='default'
size='small'
/>
)}
label='Notify subscribers'
/>
)}
<div className={classes.grow} />
{!!props.onCancel && (
<Button
disabled={props.isSubmitting}
className={classes.saveButtonAction}
onClick={() => props.onCancel?.()}
>Cancel</Button>
)}
<SubmitButton
variant='contained'
disableElevation
className={classes.saveButtonAction}
isSubmitting={props.isSubmitting}
color='primary'
onClick={() => props.onSave(doNotify)}
>Save</SubmitButton>
</div>
</Collapse>
</>
);
}
Example #13
Source File: NavDrawerItem.tsx From firetable with Apache License 2.0 | 5 votes |
export default function NavDrawerItem({
section,
tables,
currentSection,
currentTable,
}: INavDrawerItemProps) {
const classes = useStyles();
const [open, setOpen] = useState(section === currentSection);
return (
<li>
<ListItem
button
classes={{
root: clsx(
classes.listItem,
!open && currentSection === section && classes.listItemSelected
),
}}
selected={!open && currentSection === section}
onClick={() => setOpen((o) => !o)}
>
<ListItemText
primary={section}
classes={{ primary: classes.listItemText }}
/>
<ArrowDropDownIcon
className={clsx(
classes.dropdownIcon,
open && classes.dropdownIconOpen
)}
/>
</ListItem>
<Collapse in={open}>
<List>
{tables.map((table) => (
<li key={table.collection}>
<ListItem
button
selected={table.collection === currentTable}
classes={{
root: clsx(classes.listItem, classes.childListItem),
selected: classes.listItemSelected,
}}
component={Link}
to={
table.isCollectionGroup
? `${routes.tableGroup}/${table.collection}`
: `${routes.table}/${table.collection.replace(
/\//g,
"~2F"
)}`
}
>
<ListItemText
primary={table.name}
classes={{ primary: classes.childListItemText }}
/>
</ListItem>
</li>
))}
</List>
</Collapse>
</li>
);
}
Example #14
Source File: MobileMenu.tsx From ra-enterprise-demo with MIT License | 5 votes |
SubMenu: FC<{
dense?: boolean;
handleToggle: () => void;
icon: ReactElement;
isOpen: boolean;
label: string;
sidebarIsOpen: boolean;
}> = ({
handleToggle,
sidebarIsOpen,
isOpen,
icon,
label,
children,
dense = false,
}) => {
const classes = useStyles();
const header = (
<MUIMenuItem
className={classes.subMenuItem}
dense={dense}
button
onClick={handleToggle}
>
<ListItemIcon className={classes.icon}>
{isOpen ? <ExpandMore /> : icon}
</ListItemIcon>
<Typography variant="inherit" color="textSecondary">
{label}
</Typography>
</MUIMenuItem>
);
return (
<>
{sidebarIsOpen || isOpen ? (
header
) : (
<Tooltip title={label} placement="right">
{header}
</Tooltip>
)}
<Collapse in={isOpen} timeout="auto" unmountOnExit>
<List
className={classes.subMenuContainer}
dense={dense}
component="div"
disablePadding
>
{children}
</List>
</Collapse>
</>
);
}
Example #15
Source File: Row.tsx From backstage with Apache License 2.0 | 5 votes |
export function Row({ baseVersion, releaseStat }: RowProps) {
const [open, setOpen] = useState(false);
const classes = useRowStyles();
return (
<>
<TableRow className={classes.root}>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowDownIcon /> : <ChevronRightIcon />}
</IconButton>
</TableCell>
<TableCell component="th" scope="row">
<Link to={releaseStat.htmlUrl} target="_blank">
{baseVersion}
{releaseStat.versions.length === 0 ? ' (prerelease)' : ''}
</Link>
</TableCell>
<TableCell>
{releaseStat.createdAt
? DateTime.fromISO(releaseStat.createdAt).toFormat('yyyy-MM-dd')
: '-'}
</TableCell>
<TableCell>{releaseStat.candidates.length}</TableCell>
<TableCell>{Math.max(0, releaseStat.versions.length - 1)}</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<RowCollapsed releaseStat={releaseStat} />
</Collapse>
</TableCell>
</TableRow>
</>
);
}
Example #16
Source File: Facets.tsx From cognitive-search-static-web-apps-sample-ui with MIT License | 5 votes |
render(): JSX.Element {
const state = this.props.state;
return (<FacetList component="nav">
{state.facets.map(facetState => {
var facetComponent: JSX.Element = null;
switch (facetState.facetType) {
case FacetTypeEnum.BooleanFacet:
facetComponent = (<BooleanFacet state={facetState.state as BooleanFacetState} inProgress={this.props.inProgress} />);
break;
case FacetTypeEnum.NumericFacet:
facetComponent = (<NumericFacet state={facetState.state as NumericFacetState} inProgress={this.props.inProgress} />);
break;
case FacetTypeEnum.DateFacet:
facetComponent = (<DateFacet state={facetState.state as DateFacetState} inProgress={this.props.inProgress} />);
break;
case FacetTypeEnum.StringFacet:
facetComponent = (<StringFacet state={facetState.state as StringFacetState} inProgress={this.props.inProgress} />);
break;
case FacetTypeEnum.StringCollectionFacet:
facetComponent = (<StringCollectionFacet state={facetState.state as StringCollectionFacetState} inProgress={this.props.inProgress} />);
break;
}
// Getting reference to a proper getHintText method in this a bit unusual and not very strongly typed way
const getHintTextFunc = facetComponent?.type.getHintText;
return (<div key={facetState.displayName}>
<FacetListItem disableRipple={true} button onClick={() => state.toggleExpand(facetState.fieldName)}>
<ListItemText
primary={facetState.displayName}
secondary={getHintTextFunc ? getHintTextFunc(facetState.state) : ''}
/>
{!!facetState.isExpanded ? <ExpandLess /> : <ExpandMore />}
</FacetListItem>
<Collapse in={facetState.isExpanded} timeout={200} unmountOnExit>
{facetComponent}
</Collapse>
</div>);
})}
</FacetList>);
}
Example #17
Source File: CostInsightsNavigation.tsx From backstage with Apache License 2.0 | 5 votes |
CostInsightsNavigation = React.memo(
({ alerts, products }: CostInsightsNavigationProps) => {
const classes = useStyles();
const { icons } = useConfig();
const [isOpen, setOpen] = useState(false);
const defaultNavigationItems = getDefaultNavigationItems(alerts);
const productNavigationItems: NavigationItem[] =
products?.map(product => ({
title: product.name,
navigation: product.kind,
icon: findAlways(icons, i => i.kind === product.kind).component,
})) ?? [];
useEffect(
function toggleProductMenuItems() {
if (products?.length) {
setOpen(true);
} else {
setOpen(false);
}
},
[products],
);
return (
<MenuList className={classes.menuList}>
{defaultNavigationItems.map(item => (
<NavigationMenuItem
key={`navigation-menu-item-${item.navigation}`}
navigation={item.navigation}
title={item.title}
icon={
item.navigation === DefaultNavigation.AlertInsightsHeader ? (
<Badge badgeContent={alerts} color="secondary">
{React.cloneElement(item.icon, {
className: classes.navigationIcon,
})}
</Badge>
) : (
React.cloneElement(item.icon, {
className: classes.navigationIcon,
})
)
}
/>
))}
<Collapse in={isOpen} timeout={850}>
{productNavigationItems.map((item: NavigationItem) => (
<NavigationMenuItem
key={`navigation-menu-item-${item.navigation}`}
navigation={item.navigation}
icon={React.cloneElement(item.icon, {
className: classes.navigationIcon,
})}
title={item.title}
/>
))}
</Collapse>
</MenuList>
);
},
)
Example #18
Source File: SideDrawerField.tsx From firetable with Apache License 2.0 | 5 votes |
export default function Color({
column,
control,
disabled,
}: ISideDrawerFieldProps) {
const classes = useStyles();
const fieldClasses = useFieldStyles();
const [showPicker, setShowPicker] = useState(false);
const toggleOpen = () => setShowPicker((s) => !s);
return (
<Controller
control={control}
name={column.key}
render={({ onChange, onBlur, value }) => (
<>
<Grid
container
alignItems="center"
spacing={1}
className={classes.root}
onClick={() => {
toggleOpen();
onBlur();
}}
component={ButtonBase}
focusRipple
disabled={disabled}
>
<Grid item>
<div
className={classes.colorIndicator}
style={{ backgroundColor: value?.hex }}
/>
</Grid>
<Grid item xs>
<Typography
variant="body1"
color={value?.hex ? "textPrimary" : "textSecondary"}
>
{value?.hex ?? "Choose a color…"}
</Typography>
</Grid>
</Grid>
<Collapse in={showPicker}>
<ChromePicker color={value?.rgb} onChangeComplete={onChange} />
</Collapse>
</>
)}
/>
);
}
Example #19
Source File: Form.tsx From aqualink-app with MIT License | 5 votes |
SurveyForm = ({ siteId, timeZone, changeTab }: SurveyFormProps) => {
const user = useSelector(userInfoSelector);
const surveyError = useSelector(surveyErrorSelector);
const dispatch = useDispatch();
const onSubmit = useCallback(
(
diveDateTime: string,
diveLocation: SurveyState["diveLocation"],
weatherConditions: SurveyData["weatherConditions"],
comments: string
) => {
const surveyData: SurveyData = {
site: siteId,
diveDate: diveDateTime,
diveLocation,
weatherConditions,
comments,
token: user?.token,
};
dispatch(
surveyAddRequest({
siteId: `${siteId}`,
surveyData,
changeTab,
})
);
},
[dispatch, changeTab, siteId, user]
);
return (
<>
<Grid item xs={12}>
<Collapse in={!!surveyError}>
<Alert
severity="error"
action={
<IconButton aria-label="close" color="inherit" size="small" />
}
>
There was an error creating the survey.
</Alert>
</Collapse>
</Grid>
<Form siteId={siteId} timeZone={timeZone} onSubmit={onSubmit} />
</>
);
}
Example #20
Source File: Header.tsx From storefront with MIT License | 5 votes |
Header: React.VFC = () => {
const styles = useStyles();
const { settings } = useSettings();
const [open, toggleOpen] = useToggle(false);
const { data: menu } = useMenuQuery({
variables: { location: MenuLocationEnum.PRIMARY_NAVIGATION },
});
const { data: { cart } = { cart: undefined } } = useCartQuery({
fetchPolicy: 'no-cache',
ssr: false,
});
return (
<AppBar color="default" position="relative">
<Toolbar
className={styles.toolbar}
sx={{
minHeight: { xs: 60, md: 110 },
mx: 'auto',
width: '100%',
}}
>
<Box sx={{ display: { md: 'none' }, flexGrow: 1 }}>
<IconButton aria-label="Menu" onClick={toggleOpen}>
<Menu />
</IconButton>
</Box>
<Link href="/" underline="none">
<Logo className={styles.logo} aria-label={settings.title} />
</Link>
<Box
sx={{
alignItems: 'center',
alignSelf: 'stretch',
display: 'flex',
flexGrow: 1,
justifyContent: 'flex-end',
}}
>
<Box sx={{ display: { xs: 'none', md: 'flex' }, height: '100%' }}>
<HeaderMenu menu={menu} />
</Box>
<IconButton href="/cart" color="inherit" aria-label="Cart">
<Badge badgeContent={cart?.contents?.itemCount}>
<Cart />
</Badge>
</IconButton>
</Box>
</Toolbar>
<Collapse unmountOnExit in={open} timeout="auto">
<HeaderMenu menu={menu} />
</Collapse>
</AppBar>
);
}
Example #21
Source File: AlertStatusSummary.tsx From backstage with Apache License 2.0 | 5 votes |
AlertStatusSummary = ({
open,
snoozed,
accepted,
dismissed,
}: AlertStatusSummaryProps) => {
const isSnoozedListDisplayed = !!snoozed.length;
const isAcceptedListDisplayed = !!accepted.length;
const isDismissedListDisplayed = !!dismissed.length;
return (
<Collapse in={open}>
{isAcceptedListDisplayed && (
<AlertGroup
title="Accepted"
alerts={accepted}
status={AlertStatus.Accepted}
icon={
<AcceptIcon
role="img"
aria-hidden={false}
aria-label={AlertStatus.Accepted}
/>
}
/>
)}
{isSnoozedListDisplayed && (
<AlertGroup
title="Snoozed"
alerts={snoozed}
status={AlertStatus.Snoozed}
icon={
<SnoozeIcon
role="img"
aria-hidden={false}
aria-label={AlertStatus.Snoozed}
/>
}
/>
)}
{isDismissedListDisplayed && (
<AlertGroup
title="Dismissed"
alerts={dismissed}
status={AlertStatus.Dismissed}
icon={
<DismissIcon
role="img"
aria-hidden={false}
aria-label={AlertStatus.Dismissed}
/>
}
/>
)}
</Collapse>
);
}
Example #22
Source File: index.tsx From aqualink-app with MIT License | 4 votes |
DeleteButton = ({
header,
content,
onConfirm,
onSuccess,
onError,
classes,
}: DeleteButtonProps) => {
const [open, setOpen] = useState<boolean>(false);
const [alertOpen, setAlertOpen] = useState<boolean>(false);
const [alertText, setAlertText] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
setAlertOpen(false);
setAlertText("");
};
const onDelete = async () => {
setLoading(true);
try {
await onConfirm();
onSuccess?.();
} catch (error) {
onError?.();
setAlertOpen(true);
setAlertText((error as any)?.message);
}
setLoading(false);
};
const dialogActions: Action[] = [
{
size: "small",
variant: "outlined",
color: "secondary",
text: "Cancel",
action: handleClose,
},
{
size: "small",
variant: "outlined",
color: "primary",
text: "Yes",
action: onDelete,
},
];
return (
<>
<IconButton onClick={handleClickOpen}>
<DeleteOutlineIcon color="primary" />
</IconButton>
<DeleteDialog
open={open}
onClose={handleClose}
header={header}
content={
<>
{content}
{loading && <LinearProgress />}
<Collapse className={classes.alert} in={alertOpen}>
<Alert
severity="error"
action={
<IconButton
color="inherit"
size="small"
onClick={() => {
setAlertOpen(false);
}}
>
<CloseIcon fontSize="inherit" />
</IconButton>
}
>
{alertText}
</Alert>
</Collapse>
</>
}
actions={dialogActions}
/>
</>
);
}
Example #23
Source File: AccountEnterPage.tsx From clearflask with Apache License 2.0 | 4 votes |
render() {
if (this.props.plansStatus === undefined) {
ServerAdmin.get().dispatchAdmin({ debounce: true, ssr: true }).then(d => d
.plansGet());
}
const isLoggedIn = this.props.accountStatus === Status.FULFILLED && !!this.props.account;
if (this.props.type === 'invitation') {
return this.renderInvitation(isLoggedIn);
}
if (this.props.type === 'coupon') {
return this.renderCoupon(isLoggedIn);
}
if (this.props.accountStatus === Status.FULFILLED && !!this.props.account
// Only redirect once submission is over (and redirectTo and accountWasCreated is set appropriately)
&& !this.state.isSubmitting) {
if (this.props.cfJwt && this.cfReturnUrl) {
windowIso.location.href = `${this.cfReturnUrl}?${SSO_TOKEN_PARAM_NAME}=${this.props.cfJwt}`;
return (<ErrorPage msg={this.props.t('redirecting-you-back')} variant='success' />);
}
return (<RedirectIso to={this.state.redirectTo
|| this.props.location.state?.[ADMIN_LOGIN_REDIRECT_TO]
|| (this.state.accountWasCreated
? '/dashboard/welcome' :
'/dashboard')} />);
}
const selectedPlanId = this.props.location.state?.[PRE_SELECTED_BASE_PLAN_ID]
|| (this.props.plans ? this.props.plans[0].basePlanId : undefined);
if (this.props.type === 'signup') {
if (!selectedPlanId && !this.props.plans) {
return <LoadingPage />
}
if (!selectedPlanId || !SIGNUP_PROD_ENABLED && isProd() && new URL(windowIso.location.href).searchParams.get('please') !== undefined) {
return <ErrorPage variant='warning' msg={(
<div style={{ display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', }} >
Direct sign ups are currently disabled. Instead,
<NavLink to='/contact/demo' className={this.props.classes.link}>schedule a demo</NavLink>
with us.
</div>
)} />
}
}
const isSingleCustomer = detectEnv() === Environment.PRODUCTION_SELF_HOST;
const isOauthEnabled = !isSingleCustomer;
const signUpOrLogIn = this.props.type === 'signup' ? this.props.t('sign-up-with') : this.props.t('log-in-with');
return (
<EnterTemplate
title={(
<>
{(this.props.type === 'signup' ? this.props.t('get-started-with') : this.props.t('welcome-back-to')) + ' '}
<span className={this.props.classes.titleClearFlask}>ClearFlask</span>
</>
)}
renderContent={submitButton => (
<>
{this.state.couponPlan && (
<Alert className={this.props.classes.alert} severity='info'>
Redeeming <span className={this.props.classes.bold}>{this.state.couponPlan.title}</span> plan.
</Alert>
)}
{this.props.invitation?.projectName && (
<Alert className={this.props.classes.alert} severity='info'>
Invitation to <span className={this.props.classes.bold}>{this.props.invitation?.projectName}</span>.
</Alert>
)}
{isOauthEnabled && (
<>
<Button
className={this.props.classes.oauthEnter}
variant='outlined'
fullWidth
size='large'
onClick={e => !!selectedPlanId && this.onOauth('google', selectedPlanId)}
disabled={this.state.isSubmitting}
>
<GoogleIcon />
{signUpOrLogIn} Google
</Button>
<Button
className={this.props.classes.oauthEnter}
variant='outlined'
fullWidth
size='large'
onClick={e => !!selectedPlanId && this.onOauth('github', selectedPlanId)}
disabled={this.state.isSubmitting}
>
<GithubIcon />
{signUpOrLogIn} GitHub
</Button>
{!isProd() && (
<Button
className={this.props.classes.oauthEnter}
variant='outlined'
fullWidth
size='large'
onClick={e => !!selectedPlanId && this.onOauth('bathtub', selectedPlanId)}
disabled={this.state.isSubmitting}
>
<BathtubIcon />
{signUpOrLogIn} Bathtub
</Button>
)}
</>
)}
<Collapse in={!this.state.useEmail}>
<Button
className={this.props.classes.oauthEnter}
variant='outlined'
fullWidth
size='large'
onClick={e => this.setState({ useEmail: true })}
disabled={this.state.isSubmitting}
>
<EmailIcon />
{signUpOrLogIn} {this.props.t('email')}
</Button>
</Collapse>
<Collapse in={this.state.useEmail}>
<div>
{isOauthEnabled && (
<Hr isInsidePaper length={120} margins={15}>{this.props.t('or')}</Hr>
)}
<Collapse in={this.props.type === 'signup'}>
<TextField
variant='outlined'
fullWidth
margin='normal'
placeholder={this.props.t('your-name-organization')}
required
value={this.state.name || ''}
onChange={e => this.setState({ name: e.target.value })}
disabled={this.state.isSubmitting}
/>
</Collapse>
<TextField
variant='outlined'
fullWidth
required
value={this.state.email || ''}
onChange={e => {
const newEmail = e.target.value;
this.setState({ email: newEmail });
if (this.props.type === 'signup') {
import(/* webpackChunkName: "emailDisposableList" */'../common/util/emailDisposableList')
.then(eu => this.setState({
emailIsFreeOrDisposable: eu.isDisposable(newEmail),
}));
}
}}
placeholder={this.props.type === 'login' ? this.props.t('email') : this.props.t('business-email')}
type='email'
margin='normal'
disabled={this.state.isSubmitting}
/>
<Collapse in={this.props.type === 'signup' && !!this.state.emailIsFreeOrDisposable}>
<Message severity='warning' message={(
<div style={{ display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', }} >
{this.props.t('cannot-use-a-disposable-email')} Is this a mistake?
<NavLink to='/contact/demo' className={this.props.classes.link}>Schedule a demo</NavLink>
with us.
</div>
)} />
</Collapse>
<TextField
variant='outlined'
fullWidth
required
value={this.state.pass || ''}
onChange={e => this.setState({ pass: e.target.value })}
placeholder={this.props.t('password')}
type={this.state.revealPassword ? 'text' : 'password'}
InputProps={{
endAdornment: (
<InputAdornment position='end'>
<IconButton
aria-label='Toggle password visibility'
onClick={() => this.setState({ revealPassword: !this.state.revealPassword })}
>
{this.state.revealPassword ? <VisibilityIcon fontSize='small' /> : <VisibilityOffIcon fontSize='small' />}
</IconButton>
</InputAdornment>
)
}}
margin='normal'
disabled={this.state.isSubmitting}
/>
{this.props.type === 'signup' && (
<AcceptTerms />
)}
{submitButton}
</div>
</Collapse>
</>
)}
submitTitle={this.props.type === 'signup' ? this.props.t('create-account') : this.props.t('continue')}
submitDisabled={
!this.state.email || !this.state.pass
|| this.props.type === 'signup' && (
!this.state.name
|| !!this.state.emailIsFreeOrDisposable)}
isSubmitting={this.state.isSubmitting}
onSubmit={this.props.type === 'signup' ? this.signUp.bind(this, selectedPlanId!) : this.onLogin.bind(this)}
footer={this.props.type === 'signup' ? {
text: this.props.t('have-an-account'),
actionText: this.props.t('log-in-here'),
linkTo: {
pathname: '/login',
state: this.props.location.state,
},
} : {
text: this.props.t('no-account'),
actionText: this.props.t('sign-up-here'),
linkTo: {
pathname: '/signup',
state: this.props.location.state,
},
}}
layout={this.props.type}
/>
);
}
Example #24
Source File: index.tsx From aqualink-app with MIT License | 4 votes |
SignInDialog = ({
open,
handleRegisterOpen,
handleSignInOpen,
classes,
}: SignInDialogProps) => {
const dispatch = useDispatch();
const user = useSelector(userInfoSelector);
const loading = useSelector(userLoadingSelector);
const error = useSelector(userErrorSelector);
const [errorAlertOpen, setErrorAlertOpen] = useState<boolean>(false);
const [passwordResetEmail, setPasswordResetEmail] = useState<string>("");
const { register, errors, handleSubmit } = useForm<SignInFormFields>({
reValidateMode: "onSubmit",
});
useEffect(() => {
if (user) {
handleSignInOpen(false);
}
if (error) {
setErrorAlertOpen(true);
}
}, [user, handleSignInOpen, error]);
const onSubmit = (
data: SignInFormFields,
event?: BaseSyntheticEvent<object, HTMLElement, HTMLElement>
) => {
if (event) {
event.preventDefault();
}
const registerInfo: UserSignInParams = {
email: data.emailAddress.toLowerCase(),
password: data.password,
};
dispatch(signInUser(registerInfo));
};
const onResetPassword = (
{ emailAddress }: SignInFormFields,
event?: BaseSyntheticEvent<object, HTMLElement, HTMLElement>
) => {
if (event) {
event.preventDefault();
}
dispatch(resetPassword({ email: emailAddress.toLowerCase() }));
setPasswordResetEmail(emailAddress.toLowerCase());
};
const clearUserError = () => dispatch(clearError());
return (
<Dialog
onEnter={() => {
clearUserError();
setPasswordResetEmail("");
}}
open={open}
maxWidth="xs"
>
<Card elevation={0}>
<CardHeader
className={classes.dialogHeader}
title={
<Grid container alignItems="center" justify="space-between">
<Grid item>
<Grid container>
<Typography variant="h4">Aqua</Typography>
<Typography
className={classes.dialogHeaderSecondPart}
variant="h4"
>
link
</Typography>
</Grid>
</Grid>
<Grid item>
<IconButton
className={classes.closeButton}
size="small"
onClick={() => handleSignInOpen(false)}
>
<CloseIcon />
</IconButton>
</Grid>
</Grid>
}
/>
{loading && <LinearProgress />}
{error && (
<Collapse in={errorAlertOpen}>
<Alert
severity="error"
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={() => {
setErrorAlertOpen(false);
}}
>
<CloseIcon fontSize="inherit" />
</IconButton>
}
>
{error}
</Alert>
</Collapse>
)}
<Collapse in={passwordResetEmail !== ""}>
<Alert
severity="success"
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={() => {
setPasswordResetEmail("");
}}
>
<CloseIcon fontSize="inherit" />
</IconButton>
}
>
{`Password reset email sent to ${passwordResetEmail}.`}
</Alert>
</Collapse>
<CardContent>
<Grid container justify="center" item xs={12}>
<Grid className={classes.dialogContentTitle} container item xs={10}>
<Grid item>
<Typography variant="h5" color="textSecondary">
Sign In
</Typography>
</Grid>
</Grid>
<Grid container item xs={10}>
<form className={classes.form} onSubmit={handleSubmit(onSubmit)}>
<Grid className={classes.textFieldWrapper} item xs={12}>
{/* TODO: ADD THIS WHEN WE ENABLE GOOGLE LOGIN */}
{/* <Typography className={classes.formText} variant="subtitle2">
or login with email address
</Typography> */}
<TextField
id="emailAddress"
name="emailAddress"
placeholder="Email Address"
helperText={
(errors.emailAddress &&
(errors.emailAddress.type === "validate"
? "Invalid email address"
: errors.emailAddress.message)) ||
""
}
label="Email Address"
inputRef={register({
required: "This is a required field",
validate: (value) => isEmail(value),
})}
error={!!errors.emailAddress}
inputProps={{ className: classes.textField }}
fullWidth
variant="outlined"
/>
</Grid>
<Grid className={classes.textFieldWrapper} item xs={12}>
<TextField
id="password"
name="password"
type="password"
placeholder="Password"
helperText={errors.password ? errors.password.message : ""}
label="Password"
inputRef={register({
required: "This is a required field",
})}
error={!!errors.password}
inputProps={{ className: classes.textField }}
fullWidth
variant="outlined"
/>
</Grid>
<Grid container item xs={12}>
<Button
className={classes.forgotPasswordButton}
onClick={handleSubmit(onResetPassword)}
>
<Typography variant="subtitle2" color="textSecondary">
Forgot your password?
</Typography>
</Button>
</Grid>
<Grid className={classes.button} item xs={12}>
<Button
fullWidth
type="submit"
color="primary"
variant="contained"
size="large"
>
SIGN IN
</Button>
</Grid>
<Grid container item xs={12}>
<Typography
className={classes.formText}
variant="subtitle1"
color="textSecondary"
>
Don't have an account?{" "}
<Button
onClick={() => {
handleRegisterOpen(true);
handleSignInOpen(false);
}}
color="primary"
>
SIGN UP
</Button>
</Typography>
</Grid>
</form>
</Grid>
</Grid>
</CardContent>
</Card>
</Dialog>
);
}
Example #25
Source File: BillingPage.tsx From clearflask with Apache License 2.0 | 4 votes |
render() {
if (!this.props.account) {
return 'Need to login to see this page';
}
const status = this.props.accountStatus === Status.FULFILLED ? this.props.accountBillingStatus : this.props.accountStatus;
if (!this.props.accountBilling || status !== Status.FULFILLED) {
return (
<Loader skipFade status={status} />
);
}
var cardNumber, cardExpiry, cardStateIcon;
if (!!this.props.accountBilling?.payment) {
cardNumber = (
<>
<span className={this.props.classes.blurry}>5200 8282 8282 </span>
{this.props.accountBilling.payment.last4}
</>
);
var expiryColor;
if (new Date().getFullYear() % 100 >= this.props.accountBilling.payment.expiryYear % 100) {
if (new Date().getMonth() + 1 === this.props.accountBilling.payment.expiryMonth) {
expiryColor = this.props.theme.palette.warning.main;
} else if (new Date().getMonth() + 1 > this.props.accountBilling.payment.expiryMonth) {
expiryColor = this.props.theme.palette.error.main;
}
}
cardExpiry = (
<span style={expiryColor && { color: expiryColor }}>
{this.props.accountBilling.payment.expiryMonth}
/
{this.props.accountBilling.payment.expiryYear % 100}
</span>
);
} else {
cardNumber = (<span className={this.props.classes.blurry}>5200 8282 8282 8210</span>);
cardExpiry = (<span className={this.props.classes.blurry}>06 / 32</span>);
}
var hasAvailablePlansToSwitch: boolean = (this.props.accountBilling?.availablePlans || [])
.filter(p => p.basePlanId !== this.props.accountBilling?.plan.basePlanId)
.length > 0;
var cardState: 'active' | 'warn' | 'error' = 'active';
var paymentTitle, paymentDesc, showContactSupport, showSetPayment, setPaymentTitle, setPaymentAction, showCancelSubscription, showResumePlan, resumePlanDesc, planTitle, planDesc, showPlanChange, endOfTermChangeToPlanTitle, endOfTermChangeToPlanDesc, switchPlanTitle;
switch (this.props.account.subscriptionStatus) {
case Admin.SubscriptionStatus.Active:
if (this.props.accountBilling?.plan.basePlanId === TeammatePlanId) {
paymentTitle = 'No payment required';
paymentDesc = 'While you only access external projects, payments are made by the project owner. No payment is required from you at this time.';
cardState = 'active';
showSetPayment = false;
showCancelSubscription = false;
planTitle = 'You are not on a plan';
planDesc = 'While you only access external projects, you are not required to be on a plan. If you decide to create a project under your account, you will be able to choose a plan and your trial will begin.';
if (hasAvailablePlansToSwitch) {
showPlanChange = true;
switchPlanTitle = 'Choose plan'
}
} else {
paymentTitle = 'Automatic renewal is active';
paymentDesc = 'You will be automatically billed at the next cycle and your plan will be renewed.';
cardState = 'active';
showSetPayment = true;
setPaymentTitle = 'Update payment method';
showCancelSubscription = true;
planTitle = 'Your plan is active';
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan.`;
if (hasAvailablePlansToSwitch) {
planDesc += ' If you upgrade your plan, changes will reflect immediately. If you downgrade your plan, changes will take effect at the end of the term.';
showPlanChange = true;
}
}
break;
case Admin.SubscriptionStatus.ActiveTrial:
if (this.props.accountBilling?.payment) {
paymentTitle = 'Automatic renewal is active';
if (this.props.accountBilling?.billingPeriodEnd) {
paymentDesc = (
<>
Your first payment will be automatically billed at the end of the trial period in <TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />.
</>
);
} else {
paymentDesc = `Your first payment will be automatically billed at the end of the trial period.`;
}
cardState = 'active';
showSetPayment = true;
setPaymentTitle = 'Update payment method';
planTitle = 'Your plan is active';
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan.`;
if (hasAvailablePlansToSwitch) {
planDesc += ' If you switch plans now, your first payment at the end of your trial will reflect your new plan.';
showPlanChange = true;
}
} else {
paymentTitle = 'Automatic renewal requires a payment method';
paymentDesc = 'To continue using our service beyond the trial period, add a payment method to enable automatic renewal.';
cardState = 'warn';
showSetPayment = true;
setPaymentTitle = 'Add payment method';
planTitle = 'Your plan is active until your trial ends';
if (this.props.accountBilling?.billingPeriodEnd) {
planDesc = (
<>
You have full access to your {this.props.accountBilling.plan.title} plan until your trial expires in <TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />. Add a payment method to continue using our service beyond the trial period.
</>
);
} else {
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan until your trial expires. Add a payment method to continue using our service beyond the trial period.`;
}
if (hasAvailablePlansToSwitch) {
showPlanChange = true;
}
}
break;
case Admin.SubscriptionStatus.ActivePaymentRetry:
paymentTitle = 'Automatic renewal is having issues with your payment method';
paymentDesc = 'We are having issues charging your payment method. We will retry your payment method again soon and we may block your service if unsuccessful.';
cardState = 'error';
showSetPayment = true;
if (this.props.accountBilling?.payment) {
setPaymentTitle = 'Update payment method';
} else {
setPaymentTitle = 'Add payment method';
}
showCancelSubscription = true;
planTitle = 'Your plan is active';
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan; however, there is an issue with your payment. Please resolve it before you can change your plan.`;
break;
case Admin.SubscriptionStatus.ActiveNoRenewal:
paymentTitle = 'Automatic renewal is inactive';
paymentDesc = 'Resume automatic renewal to continue using our service beyond the next billing cycle.';
cardState = 'warn';
showSetPayment = true;
setPaymentTitle = 'Resume with new payment method';
setPaymentAction = 'Add and resume subscription';
showResumePlan = true;
resumePlanDesc = 'Your subscription will no longer be cancelled. You will be automatically billed for our service at the next billing cycle.';
if (this.props.accountBilling?.billingPeriodEnd) {
planTitle = (
<>
Your plan is active until <TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />
</>
);
} else {
planTitle = 'Your plan is active until the end of the billing cycle';
}
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan until it cancels. Please resume your payments to continue using our service beyond next billing cycle.`;
break;
case Admin.SubscriptionStatus.Limited:
paymentTitle = 'Automatic renewal is active';
paymentDesc = 'You will be automatically billed at the next cycle and your plan will be renewed.';
cardState = 'active';
showSetPayment = true;
setPaymentTitle = 'Update payment method';
showCancelSubscription = true;
planTitle = 'Your plan is limited';
planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan due to going over your plan limits. Please resolve all issues to continue using our service.`;
if (hasAvailablePlansToSwitch) {
planDesc += ' If you upgrade your plan, changes will reflect immediately. If you downgrade your plan, changes will take effect at the end of the term.';
showPlanChange = true;
}
break;
case Admin.SubscriptionStatus.NoPaymentMethod:
paymentTitle = 'Automatic renewal is inactive';
paymentDesc = 'Your trial has expired. To continue using our service, add a payment method to enable automatic renewal.';
cardState = 'error';
showSetPayment = true;
setPaymentTitle = 'Add payment method';
planTitle = 'Your trial plan has expired';
planDesc = `To continue using your ${this.props.accountBilling.plan.title} plan, please add a payment method.`;
break;
case Admin.SubscriptionStatus.Blocked:
paymentTitle = 'Payments are blocked';
paymentDesc = 'Contact support to reinstate your account.';
showContactSupport = true;
cardState = 'error';
planTitle = 'Your plan is inactive';
planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan due to a payment issue. Please resolve all issues to continue using our service.`;
break;
case Admin.SubscriptionStatus.Cancelled:
paymentTitle = 'Automatic renewal is inactive';
paymentDesc = 'Resume automatic renewal to continue using our service.';
cardState = 'error';
showSetPayment = true;
setPaymentTitle = 'Update payment method';
if (this.props.accountBilling?.payment) {
showResumePlan = true;
resumePlanDesc = 'Your subscription will no longer be cancelled. You will be automatically billed for our service starting now.';
}
planTitle = 'Your plan is cancelled';
planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan since you cancelled your subscription. Please resume payment to continue using our service.`;
break;
}
if (this.props.accountBilling?.endOfTermChangeToPlan) {
endOfTermChangeToPlanTitle = `Pending plan change to ${this.props.accountBilling.endOfTermChangeToPlan.title}`;
endOfTermChangeToPlanDesc = `Your requested change of plans to ${this.props.accountBilling.endOfTermChangeToPlan.title} plan will take effect at the end of the term.`;
}
switch (cardState) {
case 'active':
cardStateIcon = (<ActiveIcon color='primary' />);
break;
case 'warn':
cardStateIcon = (<WarnIcon style={{ color: this.props.theme.palette.warning.main }} />);
break;
case 'error':
cardStateIcon = (<ErrorIcon color='error' />);
break;
}
const creditCard = (
<TourAnchor anchorId='settings-credit-card' placement='bottom'>
<CreditCard
className={this.props.classes.creditCard}
brand={cardStateIcon}
numberInput={cardNumber}
expiryInput={cardExpiry}
cvcInput={(<span className={this.props.classes.blurry}>642</span>)}
/>
</TourAnchor>
);
const paymentStripeAction: PaymentStripeAction | undefined = this.props.accountBilling?.paymentActionRequired?.actionType === 'stripe-next-action'
? this.props.accountBilling?.paymentActionRequired as PaymentStripeAction : undefined;
const paymentActionOnClose = () => {
this.setState({
paymentActionOpen: undefined,
paymentActionUrl: undefined,
paymentActionMessage: undefined,
paymentActionMessageSeverity: undefined,
});
if (this.refreshBillingAfterPaymentClose) {
ServerAdmin.get().dispatchAdmin().then(d => d.accountBillingAdmin({
refreshPayments: true,
}));
}
};
const paymentAction = paymentStripeAction ? (
<>
<Message
className={this.props.classes.paymentActionMessage}
message='One of your payments requires additional information'
severity='error'
action={(
<SubmitButton
isSubmitting={!!this.state.paymentActionOpen && !this.state.paymentActionUrl && !this.state.paymentActionMessage}
onClick={() => {
this.setState({ paymentActionOpen: true });
this.loadActionIframe(paymentStripeAction);
}}
>Open</SubmitButton>
)}
/>
<Dialog
open={!!this.state.paymentActionOpen}
onClose={paymentActionOnClose}
>
{this.state.paymentActionMessage ? (
<>
<DialogContent>
<Message
message={this.state.paymentActionMessage}
severity={this.state.paymentActionMessageSeverity || 'info'}
/>
</DialogContent>
<DialogActions>
<Button onClick={paymentActionOnClose}>Dismiss</Button>
</DialogActions>
</>
) : (this.state.paymentActionUrl ? (
<iframe
title='Complete outstanding payment action'
width={this.getFrameActionWidth()}
height={400}
src={this.state.paymentActionUrl}
/>
) : (
<div style={{
minWidth: this.getFrameActionWidth(),
minHeight: 400,
}}>
<LoadingPage />
</div>
))}
</Dialog>
</>
) : undefined;
const hasPayable = (this.props.accountBilling?.accountPayable || 0) > 0;
const hasReceivable = (this.props.accountBilling?.accountReceivable || 0) > 0;
const payment = (
<Section
title='Payment'
preview={(
<div className={this.props.classes.creditCardContainer}>
{creditCard}
<Box display='grid' gridTemplateAreas='"payTtl payAmt" "rcvTtl rcvAmt"' alignItems='center' gridGap='10px 10px'>
{hasPayable && (
<>
<Box gridArea='payTtl'><Typography component='div'>Credits:</Typography></Box>
<Box gridArea='payAmt' display='flex'>
<Typography component='div' variant='h6' color='textSecondary' style={{ alignSelf: 'flex-start' }}>{'$'}</Typography>
<Typography component='div' variant='h4' color={hasPayable ? 'primary' : undefined}>
{this.props.accountBilling?.accountPayable || 0}
</Typography>
</Box>
</>
)}
{(hasReceivable || !hasPayable) && (
<>
<Box gridArea='rcvTtl'><Typography component='div'>Overdue:</Typography></Box>
<Box gridArea='rcvAmt' display='flex'>
<Typography component='div' variant='h6' color='textSecondary' style={{ alignSelf: 'flex-start' }}>{'$'}</Typography>
<Typography component='div' variant='h4' color={hasReceivable ? 'error' : undefined}>
{this.props.accountBilling?.accountReceivable || 0}
</Typography>
</Box>
</>
)}
</Box>
</div>
)}
content={(
<div className={this.props.classes.actionContainer}>
<p><Typography variant='h6' color='textPrimary' component='div'>{paymentTitle}</Typography></p>
<Typography color='textSecondary'>{paymentDesc}</Typography>
<div className={this.props.classes.sectionButtons}>
{showContactSupport && (
<Button
disabled={this.state.isSubmitting || this.state.showAddPayment}
component={Link}
to='/contact/support'
>Contact support</Button>
)}
{showSetPayment && (
<TourAnchor anchorId='settings-add-payment-open' placement='bottom'>
{(next, isActive, anchorRef) => (
<SubmitButton
buttonRef={anchorRef}
isSubmitting={this.state.isSubmitting}
disabled={this.state.showAddPayment}
onClick={() => {
trackingBlock(() => {
ReactGA.event({
category: 'billing',
action: this.props.accountBilling?.payment ? 'click-payment-update-open' : 'click-payment-add-open',
label: this.props.accountBilling?.plan.basePlanId,
});
});
this.setState({ showAddPayment: true });
next();
}}
>
{setPaymentTitle}
</SubmitButton>
)}
</TourAnchor>
)}
{showCancelSubscription && (
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={this.state.showCancelSubscription}
style={{ color: this.props.theme.palette.error.main }}
onClick={() => this.setState({ showCancelSubscription: true })}
>
Cancel payments
</SubmitButton>
)}
{showResumePlan && (
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={this.state.showResumePlan}
color='primary'
onClick={() => this.setState({ showResumePlan: true })}
>
Resume payments
</SubmitButton>
)}
</div>
{paymentAction}
<Dialog
open={!!this.state.showAddPayment}
onClose={() => this.setState({ showAddPayment: undefined })}
>
<ElementsConsumer>
{({ elements, stripe }) => (
<TourAnchor anchorId='settings-add-payment-popup' placement='top'>
{(next, isActive, anchorRef) => (
<div ref={anchorRef}>
<DialogTitle>{setPaymentTitle || 'Add new payment method'}</DialogTitle>
<DialogContent className={this.props.classes.center}>
<StripeCreditCard onFilledChanged={(isFilled) => this.setState({ stripePaymentFilled: isFilled })} />
<Collapse in={!!this.state.stripePaymentError}>
<Message message={this.state.stripePaymentError} severity='error' />
</Collapse>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showAddPayment: undefined })}>
Cancel
</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={!this.state.stripePaymentFilled || !elements || !stripe}
color='primary'
onClick={async () => {
const success = await this.onPaymentSubmit(elements!, stripe!);
if (success) {
next();
tourSetGuideState('add-payment', TourDefinitionGuideState.Completed);
}
}}
>{setPaymentAction || 'Add'}</SubmitButton>
</DialogActions>
</div>
)}
</TourAnchor>
)}
</ElementsConsumer>
</Dialog>
<Dialog
open={!!this.state.showCancelSubscription}
onClose={() => this.setState({ showCancelSubscription: undefined })}
>
<DialogTitle>Stop subscription</DialogTitle>
<DialogContent className={this.props.classes.center}>
<DialogContentText>Stop automatic billing of your subscription. Any ongoing subscription will continue to work until it expires.</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showCancelSubscription: undefined })}>
Cancel
</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
style={{ color: this.props.theme.palette.error.main }}
onClick={() => {
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
accountUpdateAdmin: {
cancelEndOfTerm: true,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showCancelSubscription: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Stop subscription</SubmitButton>
</DialogActions>
</Dialog>
<Dialog
open={!!this.state.showResumePlan}
onClose={() => this.setState({ showResumePlan: undefined })}
>
<DialogTitle>Resume subscription</DialogTitle>
<DialogContent className={this.props.classes.center}>
<DialogContentText>{resumePlanDesc}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showResumePlan: undefined })}>
Cancel
</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
color='primary'
onClick={() => {
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
accountUpdateAdmin: {
resume: true,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showResumePlan: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Resume subscription</SubmitButton>
</DialogActions>
</Dialog>
</div>
)}
/>
);
const nextInvoicesCursor = this.state.invoices === undefined
? this.props.accountBilling?.invoices.cursor
: this.state.invoicesCursor;
const invoicesItems = [
...(this.props.accountBilling?.invoices.results || []),
...(this.state.invoices || []),
];
const invoices = invoicesItems.length <= 0 ? undefined : (
<Section
title='Invoices'
content={(
<>
<Table>
<TableHead>
<TableRow>
<TableCell key='due'>Due</TableCell>
<TableCell key='status'>Status</TableCell>
<TableCell key='amount'>Amount</TableCell>
<TableCell key='desc'>Description</TableCell>
<TableCell key='invoiceLink'>Invoice</TableCell>
</TableRow>
</TableHead>
<TableBody>
{invoicesItems.map((invoiceItem, index) => (
<TableRow key={index}>
<TableCell key='due'><Typography>{new Date(invoiceItem.date).toLocaleDateString()}</Typography></TableCell>
<TableCell key='status' align='center'><Typography>{invoiceItem.status}</Typography></TableCell>
<TableCell key='amount' align='right'><Typography>{invoiceItem.amount}</Typography></TableCell>
<TableCell key='desc'><Typography>{invoiceItem.description}</Typography></TableCell>
<TableCell key='invoiceLink'>
<Button onClick={() => this.onInvoiceClick(invoiceItem.invoiceId)}>View</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{nextInvoicesCursor && (
<Button
style={{ margin: 'auto', display: 'block' }}
onClick={() => ServerAdmin.get().dispatchAdmin()
.then(d => d.invoicesSearchAdmin({ cursor: nextInvoicesCursor }))
.then(results => this.setState({
invoices: [
...(this.state.invoices || []),
...results.results,
],
invoicesCursor: results.cursor,
}))}
>
Show more
</Button>
)}
</>
)}
/>
);
const plan = (
<Section
title='Plan'
preview={(
<div className={this.props.classes.planContainer}>
<TourAnchor anchorId='settings-billing-plan' placement='bottom' disablePortal>
<PricingPlan
selected
className={this.props.classes.plan}
plan={this.props.accountBilling.plan}
/>
</TourAnchor>
{(this.props.accountBilling?.trackedUsers !== undefined) && (
<Box display='grid' gridTemplateAreas='"mauLbl mauAmt"' alignItems='baseline' gridGap='10px 10px'>
<Box gridArea='mauLbl'><Typography component='div'>Tracked users:</Typography></Box>
<Box gridArea='mauAmt' display='flex'>
<Typography component='div' variant='h5'>
{this.props.accountBilling.trackedUsers}
</Typography>
</Box>
</Box>
)}
{(this.props.accountBilling?.postCount !== undefined) && (
<Box display='grid' gridTemplateAreas='"postCountLbl postCountAmt"' alignItems='baseline' gridGap='10px 10px'>
<Box gridArea='postCountLbl'><Typography component='div'>Post count:</Typography></Box>
<Box gridArea='postCountAmt' display='flex'>
<Typography component='div' variant='h5' color={
this.props.account.basePlanId === 'starter-unlimited'
&& this.props.accountBilling.postCount > StarterMaxPosts
? 'error' : undefined}>
{this.props.accountBilling.postCount}
</Typography>
</Box>
</Box>
)}
</div>
)}
content={(
<div className={this.props.classes.actionContainer}>
<p><Typography variant='h6' component='div' color='textPrimary'>{planTitle}</Typography></p>
<Typography color='textSecondary'>{planDesc}</Typography>
{(endOfTermChangeToPlanTitle || endOfTermChangeToPlanDesc) && (
<>
<p><Typography variant='h6' component='div' color='textPrimary' className={this.props.classes.sectionSpacing}>{endOfTermChangeToPlanTitle}</Typography></p>
<Typography color='textSecondary'>{endOfTermChangeToPlanDesc}</Typography>
</>
)}
{showPlanChange && (
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting || this.state.showPlanChange}
onClick={() => {
trackingBlock(() => {
ReactGA.event({
category: 'billing',
action: 'click-plan-switch-open',
label: this.props.accountBilling?.plan.basePlanId,
});
});
this.setState({ showPlanChange: true });
}}
>
{switchPlanTitle || 'Switch plan'}
</Button>
</div>
)}
{showPlanChange && (
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting || this.state.showPlanChange}
onClick={() => this.props.history.push('/coupon')}
>
Redeem coupon
</Button>
</div>
)}
{this.props.isSuperAdmin && (
<>
<Dialog
open={!!this.state.showFlatYearlyChange}
onClose={() => this.setState({ showFlatYearlyChange: undefined })}
scroll='body'
maxWidth='md'
>
<DialogTitle>Switch to yearly plan</DialogTitle>
<DialogContent>
<TextField
variant='outlined'
type='number'
label='Yearly flat price'
value={this.state.flatYearlyPrice !== undefined ? this.state.flatYearlyPrice : ''}
onChange={e => this.setState({ flatYearlyPrice: parseInt(e.target.value) >= 0 ? parseInt(e.target.value) : undefined })}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showFlatYearlyChange: undefined })}
>Cancel</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={this.state.flatYearlyPrice === undefined}
color='primary'
onClick={() => {
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateSuperAdmin({
accountUpdateSuperAdmin: {
changeToFlatPlanWithYearlyPrice: this.state.flatYearlyPrice || 0,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showFlatYearlyChange: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Change</SubmitButton>
</DialogActions>
</Dialog>
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting}
onClick={() => this.setState({ showFlatYearlyChange: true })}
>Flatten</Button>
</div>
</>
)}
{this.props.isSuperAdmin && (
<>
<Dialog
open={!!this.state.showAddonsChange}
onClose={() => this.setState({ showAddonsChange: undefined })}
scroll='body'
maxWidth='md'
>
<DialogTitle>Manage addons</DialogTitle>
<DialogContent className={this.props.classes.addonsContainer}>
<TextField
label='Extra projects'
variant='outlined'
type='number'
value={this.state.extraProjects !== undefined ? this.state.extraProjects : (this.props.account.addons?.[AddonExtraProject] || 0)}
onChange={e => this.setState({ extraProjects: parseInt(e.target.value) >= 0 ? parseInt(e.target.value) : undefined })}
/>
<FormControlLabel
control={(
<Switch
checked={this.state.whitelabel !== undefined ? this.state.whitelabel : !!this.props.account.addons?.[AddonWhitelabel]}
onChange={(e, checked) => this.setState({ whitelabel: !!checked })}
color='default'
/>
)}
label={(<FormHelperText>Whitelabel</FormHelperText>)}
/>
<FormControlLabel
control={(
<Switch
checked={this.state.privateProjects !== undefined ? this.state.privateProjects : !!this.props.account.addons?.[AddonPrivateProjects]}
onChange={(e, checked) => this.setState({ privateProjects: !!checked })}
color='default'
/>
)}
label={(<FormHelperText>Private projects</FormHelperText>)}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showAddonsChange: undefined })}
>Cancel</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={this.state.whitelabel === undefined
&& this.state.privateProjects === undefined
&& this.state.extraProjects === undefined}
color='primary'
onClick={() => {
if (this.state.whitelabel === undefined
&& this.state.privateProjects === undefined
&& this.state.extraProjects === undefined) return;
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateSuperAdmin({
accountUpdateSuperAdmin: {
addons: {
...(this.state.whitelabel === undefined ? {} : {
[AddonWhitelabel]: this.state.whitelabel ? 'true' : ''
}),
...(this.state.privateProjects === undefined ? {} : {
[AddonPrivateProjects]: this.state.privateProjects ? 'true' : ''
}),
...(this.state.extraProjects === undefined ? {} : {
[AddonExtraProject]: `${this.state.extraProjects}`
}),
},
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showAddonsChange: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Change</SubmitButton>
</DialogActions>
</Dialog>
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting}
onClick={() => this.setState({ showAddonsChange: true })}
>Addons</Button>
</div>
</>
)}
{this.props.isSuperAdmin && (
<>
<Dialog
open={!!this.state.showCreditAdjustment}
onClose={() => this.setState({ showCreditAdjustment: undefined })}
scroll='body'
maxWidth='md'
>
<DialogTitle>Credit adjustment</DialogTitle>
<DialogContent className={this.props.classes.addonsContainer}>
<TextField
label='Amount'
variant='outlined'
type='number'
value={this.state.creditAmount || 0}
onChange={e => this.setState({ creditAmount: parseInt(e.target.value) })}
/>
<TextField
label='Description'
variant='outlined'
value={this.state.creditDescription || ''}
onChange={e => this.setState({ creditDescription: e.target.value })}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showCreditAdjustment: undefined })}
>Cancel</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={!this.props.account
|| !this.state.creditAmount
|| !this.state.creditDescription}
color='primary'
onClick={() => {
if (!this.props.account
|| !this.state.creditAmount
|| !this.state.creditDescription) return;
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountCreditAdjustmentSuperAdmin({
accountCreditAdjustment: {
accountId: this.props.account!.accountId,
amount: this.state.creditAmount!,
description: this.state.creditDescription!,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showCreditAdjustment: undefined, creditAmount: undefined, creditDescription: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Change</SubmitButton>
</DialogActions>
</Dialog>
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting}
onClick={() => this.setState({ showCreditAdjustment: true })}
>Credit</Button>
</div>
</>
)}
<BillingChangePlanDialog
open={!!this.state.showPlanChange}
onClose={() => this.setState({ showPlanChange: undefined })}
onSubmit={basePlanId => {
trackingBlock(() => {
ReactGA.event({
category: 'billing',
action: 'click-plan-switch-submit',
label: basePlanId,
});
});
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
accountUpdateAdmin: {
basePlanId,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showPlanChange: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
isSubmitting={!!this.state.isSubmitting}
/>
</div>
)}
/>
);
return (
<ProjectSettingsBase title='Billing'>
{plan}
{payment}
{invoices}
</ProjectSettingsBase>
);
}
Example #26
Source File: LocationDetails.tsx From covid19testing-map with GNU General Public License v3.0 | 4 votes |
LocationDetails = ({ location, expanded, details }: DetailsProps) => {
function renderLocationTestingDetails(locationToRender: any): any {
if (locationToRender.location_specific_testing_criteria !== null) {
if (
locationToRender.location_specific_testing_criteria.substring(0, 4) !==
'http' &&
locationToRender.location_specific_testing_criteria.length > 3
) {
return (
<Grid key={1} item md={12} xs={12}>
<div style={{ marginTop: '10px' }}>
{location.location_specific_testing_criteria.split('\\n').map((i: any, key: number) => {
return <Typography key={key} paragraph variant="body2" component="p">{i}</Typography>;
})}
</div>
</Grid>
);
}
}
const trackLocationWebsiteClick = () => {
trackUiClick('Location Website', locationToRender.location_id + '|' + locationToRender.location_latitude + '|' + locationToRender.location_longitude);
};
// If there are specific instructions involving testing, then display those, and move on.
if (
locationToRender.location_specific_testing_criteria !== null &&
locationToRender.location_specific_testing_criteria.substring(0, 4) !==
'http'
) {
return (
<Grid key={2} item md={5} xs={12}>
<div style={{ marginTop: '10px' }}>
{location.location_specific_testing_criteria.split('\\n').map((i: any, key: number) => {
return <Typography key={key} paragraph variant="body1" component="p">{i}</Typography>;
})}
</div>
</Grid>
);
}
// Otherwise, figure out which URL to display
let urlToRender = '';
// If location does NOT require/apply testing criteria then we're done
if (
locationToRender.is_ordering_tests_only_for_those_who_meeting_criteria !== true &&
(locationToRender.reference_publisher_of_criteria === null ||
locationToRender.reference_publisher_of_criteria.length < 3)
) {
return (
<Grid key={3} item md={5} xs={12}>
<Typography style={{ marginTop: '10px' }}>
{'Published testing criteria that is specific to this location could not be found. '}
{'This is common when CDC guidelines are in effect, but we recommend calling ahead to confirm.'}
</Typography>
</Grid>
);
}
if (
locationToRender.location_specific_testing_criteria !== null &&
locationToRender.location_specific_testing_criteria.substring(0, 4) === 'http'
) {
urlToRender = locationToRender.location_specific_testing_criteria;
} else if (
locationToRender.location_contact_url_covid_info !== null &&
locationToRender.location_contact_url_covid_info.substring(0, 4) === 'http'
) {
urlToRender = locationToRender.location_contact_url_covid_info;
} else if (
locationToRender.location_contact_url_main !== null &&
locationToRender.location_contact_url_main.substring(0, 4) === 'http'
) {
urlToRender = locationToRender.location_contact_url_main;
} else {
urlToRender =
'https://www.cdc.gov/coronavirus/2019-ncov/symptoms-testing/index.html#';
}
return (
<Grid key={4} item md={5} xs={12}>
<Typography style={{ marginTop: '10px' }}>
{'Testing at this location is only offered to individuals that '}
<Link
onClick={trackLocationWebsiteClick}
href={urlToRender}
target="_blank"
rel="noopener"
>
meet specific criteria
</Link>
{
'. All others will be turned away.'
}
</Typography>
</Grid>
);
}
return (
<Collapse in={expanded} timeout="auto" unmountOnExit>
<Divider />
<CardContent>
<Grid container spacing={2}>
<Grid key={5} item md={12} xs={12}>
<div style={{ paddingTop: '20px' }}>
{location.additional_information_for_patients.split('\\n').map((i: any, key: number) => {
return <Typography key={key} paragraph variant="body1" component="p">{i}</Typography>;
})}
</div>
</Grid>
{renderLocationTestingDetails(location)}
<Divider orientation="horizontal" flexItem />
<Grid item md={12} xs={12}>
<Typography color="textPrimary" variant="caption" style={{ paddingTop: '20px',paddingBottom: '20px' }}>
{'\nSource: '}
<ReactGA.OutboundLink
eventLabel={'OutboundLink | Source | ' + location.location_address_locality + ' | ' + location.location_address_region + ' | ' + location.location_contact_url_main }
to={location.location_contact_url_main}
target="_blank"
>
{location.location_contact_url_main}
</ReactGA.OutboundLink>
</Typography>
</Grid>
</Grid>
</CardContent>
</Collapse>
);
}
Example #27
Source File: Settings.tsx From back-home-safe with GNU General Public License v3.0 | 4 votes |
Settings = () => {
const { t } = useTranslation("main_screen");
const { hasCameraSupport } = useCamera();
const { autoRemoveRecordDay, setAutoRemoveRecordDay } = useTravelRecord();
const { incognito, setIncognito, value } = useData();
const [languageOpen, setLanguageOpen] = useState(false);
const { language, setLanguage } = useI18n();
const handleLanguageClick = () => {
setLanguageOpen(!languageOpen);
};
const handleExportData = () => {
const dataStr =
"data:text/json;charset=utf-8," +
encodeURIComponent(JSON.stringify(value));
const downloadAnchorNode = document.createElement("a");
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", "export.json");
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
};
return (
<PageWrapper>
<Header name={t("setting.name")} />
<ContentWrapper>
<StyledList
subheader={
<ListSubheader>{t("setting.section.common")}</ListSubheader>
}
>
{hasCameraSupport ? (
<StyledLink to="/cameraSetting">
<ListItem button>
<ListItemText primary={t("setting.item.camera_setting")} />
</ListItem>
</StyledLink>
) : (
<ListItem button disabled>
<ListItemText primary={t("setting.item.camera_setting")} />
</ListItem>
)}
<StyledLink to="/confirmPageSetting">
<ListItem button>
<ListItemText primary={t("setting.item.confirm_page_setting")} />
</ListItem>
</StyledLink>
<ListItem>
<ListItemText primary={t("setting.item.auto_delete_record")} />
<ListItemSecondaryAction>
<Select
labelId="cameraId"
id="demo-simple-select"
value={autoRemoveRecordDay}
onChange={(e) => {
setAutoRemoveRecordDay(e.target.value as number);
}}
>
{range(1, 100).map((day) => (
<MenuItem value={day} key={day}>
{day}{" "}
{day === 1 ? t("setting.form.day") : t("setting.form.days")}
</MenuItem>
))}
</Select>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemText
primary={t("setting.item.incognito_mode.name")}
secondary={t("setting.item.incognito_mode.explanation")}
/>
<ListItemSecondaryAction>
<Switch
checked={incognito}
onChange={(e) => {
setIncognito(e.target.checked);
}}
color="primary"
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem button onClick={handleLanguageClick}>
<ListItemText primary={t("setting.item.language")} />
{languageOpen ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={languageOpen} timeout="auto" unmountOnExit>
<ListItem>
<RadioGroup
aria-label="language"
name="language"
value={language}
onChange={(event) => {
setLanguage(event.target.value as languageType);
}}
>
<FormControlLabel
value={languageType["ZH-HK"]}
control={<Radio />}
label="繁體中文"
/>
<FormControlLabel
value={languageType.EN}
control={<Radio />}
label="English"
/>
</RadioGroup>
</ListItem>
</Collapse>
</StyledList>
<Divider />
<StyledList
subheader={<ListSubheader>{t("setting.section.lab")}</ListSubheader>}
>
<StyledLink to="/qrGenerator">
<ListItem button>
<ListItemText primary={t("setting.item.qr_generator")} />
</ListItem>
</StyledLink>
<StyledLink to="/vaccinationQRReader">
<ListItem button>
<ListItemText primary={t("setting.item.vaccinationQRReader")} />
</ListItem>
</StyledLink>
<ListItem onClick={handleExportData}>
<ListItemText primary={t("setting.item.export_data")} />
</ListItem>
<ListItem button>
<ListItemText
primary={t("setting.item.reset")}
onClick={clearAllData}
/>
</ListItem>
</StyledList>
<Divider />
<StyledList
subheader={
<ListSubheader>
{t("setting.section.version")}: {__APP_VERSION__}
</ListSubheader>
}
>
<StyledExternalLink
href="https://gitlab.com/codogo-b/back-home-safe"
target="_blank"
>
<ListItem button>
<ListItemText primary={t("setting.item.about_us")} />
</ListItem>
</StyledExternalLink>
<StyledLink to="/disclaimer">
<ListItem button>
<ListItemText primary={t("setting.item.disclaimer")} />
</ListItem>
</StyledLink>
<StyledExternalLink
href="https://gitlab.com/codogo-b/back-home-safe/-/blob/master/CHANGELOG.md"
target="_blank"
>
<ListItem button>
<ListItemText primary={t("setting.item.change_log")} />
</ListItem>
</StyledExternalLink>
<StyledExternalLink
href="https://gitlab.com/codogo-b/back-home-safe/-/issues"
target="_blank"
>
<ListItem button>
<ListItemText primary={t("setting.item.report_issue")} />
</ListItem>
</StyledExternalLink>
</StyledList>
</ContentWrapper>
</PageWrapper>
);
}
Example #28
Source File: ProposalsList.tsx From homebase-app with MIT License | 4 votes |
ProposalsList: React.FC<Props> = ({
currentLevel,
proposals,
title,
showFooter,
rightItem,
}) => {
const [open, setopen] = useState(true);
return (
<TableContainer item>
<Grid container direction="column" wrap={"nowrap"}>
<TableHeader item container justifyContent="space-between">
<Grid item>
<Typography variant="body2" style={{fontWeight: "500"}} color="textPrimary">
{title}
</Typography>
</Grid>
{proposals.length ? (
<Grid item>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setopen(!open)}
>
{open ? (
<KeyboardArrowUpIcon htmlColor="#FFF" />
) : (
<KeyboardArrowDownIcon htmlColor="#FFF" />
)}
</IconButton>
</Grid>
) : null}
</TableHeader>
{proposals.length ? (
<Grid
item
container
wrap={"nowrap"}
component={Collapse}
in={open}
timeout="auto"
unmountOnExit
direction="column"
>
{proposals.map((p, i) => (
<Grid item key={`proposal-${i}`}>
<Link to={`proposal/${p.id}`}>
<ProposalItem
proposal={p}
status={p.getStatus(currentLevel).status}
>
{rightItem ? rightItem(p) : null}
</ProposalItem>
</Link>
</Grid>
))}
</Grid>
) : (
<ProposalsFooter
item
container
direction="column"
justifyContent="center"
>
<Grid item>
<Typography color="textPrimary" align="center">
No items
</Typography>
</Grid>
</ProposalsFooter>
)}
{showFooter && (
<ProposalsFooter
item
container
direction="column"
justifyContent="center"
>
<Grid item>
<Link to="proposals">
<Typography color="secondary" variant="body2" align="center">
View All Proposals
</Typography>
</Link>
</Grid>
</ProposalsFooter>
)}
</Grid>
</TableContainer>
);
}
Example #29
Source File: index.tsx From aqualink-app with MIT License | 4 votes |
RegisterDialog = ({
open,
handleRegisterOpen,
handleSignInOpen,
classes,
}: RegisterDialogProps) => {
const dispatch = useDispatch();
const user = useSelector(userInfoSelector);
const loading = useSelector(userLoadingSelector);
const error = useSelector(userErrorSelector);
const [errorAlertOpen, setErrorAlertOpen] = useState<boolean>(false);
const [readTerms, setReadTerms] = useState<boolean>(false);
const { register, errors, handleSubmit } = useForm<RegisterFormFields>({
reValidateMode: "onSubmit",
});
const onSubmit = (
data: RegisterFormFields,
event?: BaseSyntheticEvent<object, HTMLElement, HTMLElement>
) => {
if (event) {
event.preventDefault();
}
const registerInfo: UserRegisterParams = {
fullName: `${data.firstName} ${data.lastName}`,
organization: data.organization,
email: data.emailAddress.toLowerCase(),
password: data.password,
};
dispatch(createUser(registerInfo));
};
const clearUserError = () => dispatch(clearError());
useEffect(() => {
if (user) {
handleRegisterOpen(false);
}
if (error) {
setErrorAlertOpen(true);
}
}, [user, handleRegisterOpen, error]);
return (
<Dialog
onEnter={() => {
clearUserError();
setReadTerms(false);
}}
scroll="body"
open={open}
maxWidth="xs"
>
<Card>
<CardHeader
className={classes.dialogHeader}
title={
<Grid container alignItems="center" justify="space-between">
<Grid item>
<Grid container>
<Typography variant="h4">Aqua</Typography>
<Typography
className={classes.dialogHeaderSecondPart}
variant="h4"
>
link
</Typography>
</Grid>
</Grid>
<Grid item>
<IconButton
className={classes.closeButton}
size="small"
onClick={() => {
handleRegisterOpen(false);
}}
>
<CloseIcon />
</IconButton>
</Grid>
</Grid>
}
/>
{loading && <LinearProgress />}
{error && (
<Collapse in={errorAlertOpen}>
<Alert
severity="error"
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={() => {
setErrorAlertOpen(false);
}}
>
<CloseIcon fontSize="inherit" />
</IconButton>
}
>
{error}
</Alert>
</Collapse>
)}
<CardContent>
<Grid container justify="center" item xs={12}>
<Grid className={classes.dialogContentTitle} container item xs={10}>
<Grid item>
<Typography variant="h5" color="textSecondary">
Create an account
</Typography>
</Grid>
</Grid>
<Grid container item xs={10}>
<form className={classes.form} onSubmit={handleSubmit(onSubmit)}>
<Grid className={classes.textFieldWrapper} item xs={12}>
<TextField
id="firstName"
name="firstName"
placeholder="First Name"
helperText={
errors.firstName ? errors.firstName.message : ""
}
label="First Name"
inputRef={register({
required: "This is a required field",
})}
error={!!errors.firstName}
inputProps={{ className: classes.textField }}
fullWidth
variant="outlined"
/>
</Grid>
<Grid className={classes.textFieldWrapper} item xs={12}>
<TextField
id="lastName"
name="lastName"
placeholder="Last Name"
helperText={errors.lastName ? errors.lastName.message : ""}
label="Last Name"
inputRef={register({
required: "This is a required field",
})}
error={!!errors.lastName}
inputProps={{ className: classes.textField }}
fullWidth
variant="outlined"
/>
</Grid>
<Grid className={classes.textFieldWrapper} item xs={12}>
<TextField
id="organization"
name="organization"
placeholder="Organization"
helperText={
errors.organization ? errors.organization.message : ""
}
label="Organization"
inputRef={register({
required: "This is a required field",
})}
error={!!errors.organization}
inputProps={{ className: classes.textField }}
fullWidth
variant="outlined"
/>
</Grid>
<Grid className={classes.textFieldWrapper} item xs={12}>
<TextField
id="emailAddress"
name="emailAddress"
placeholder="Email Address"
helperText={
(errors.emailAddress &&
(errors.emailAddress.type === "validate"
? "Invalid email address"
: errors.emailAddress.message)) ||
""
}
label="Email Address"
inputRef={register({
required: "This is a required field",
validate: (value) => isEmail(value),
})}
error={!!errors.emailAddress}
inputProps={{ className: classes.textField }}
fullWidth
variant="outlined"
/>
</Grid>
<Grid className={classes.textFieldWrapper} item xs={12}>
<TextField
id="password"
name="password"
type="password"
placeholder="Password"
helperText={errors.password ? errors.password.message : ""}
label="Password"
inputRef={register({
required: "This is a required field",
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
})}
error={!!errors.password}
inputProps={{ className: classes.textField }}
fullWidth
variant="outlined"
/>
</Grid>
<Grid
container
justify="space-between"
alignItems="center"
item
xs={12}
>
<Grid item xs={1}>
<Checkbox
className={classes.termsCheckbox}
checked={readTerms}
onChange={() => setReadTerms(!readTerms)}
color="primary"
/>
</Grid>
<Grid item xs={10} sm={11}>
<Box>
<Typography
className={classes.formText}
variant="subtitle1"
color="textSecondary"
>
I have read the{" "}
<Link className={classes.termsLink} to="/terms">
Terms and Conditions
</Link>
</Typography>
</Box>
</Grid>
</Grid>
<Grid className={classes.button} item xs={12}>
<Button
fullWidth
type="submit"
color="primary"
variant="contained"
size="large"
disabled={!readTerms}
>
CREATE ACCOUNT
</Button>
</Grid>
<Grid container item xs={12}>
<Typography
className={classes.formText}
variant="subtitle1"
color="textSecondary"
>
Have an account?{" "}
<Button
onClick={() => {
handleRegisterOpen(false);
handleSignInOpen(true);
}}
color="primary"
>
SIGN IN
</Button>
</Typography>
</Grid>
</form>
</Grid>
</Grid>
</CardContent>
</Card>
</Dialog>
);
}