@material-ui/core#ListSubheader TypeScript Examples
The following examples show how to use
@material-ui/core#ListSubheader.
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: SettingsPage.tsx From twitch-live-extension with BSD 3-Clause "New" or "Revised" License | 6 votes |
SettingsPage = () => {
const dispatch: AppDispatch = useDispatch();
const [user, setUser] = useState<ValidateTokenResponse>();
const { loading } = useSelector((state: RootState) => state.common);
useEffect(() => {
const getUserData = async () => {
const userData: ValidateTokenResponse = await dispatch(getUser());
setUser(userData);
};
getUserData();
}, [dispatch]);
return (
<div>
{user && (
<List subheader={<ListSubheader>Settings</ListSubheader>}>
<SettingsSwitchAccount user={user} />
<SettingsNotifications />
</List>
)}
{loading && !user && <CenteredCircularProgress />}
<Divider />
<SettingsFooter />
</div>
);
}
Example #2
Source File: Incidents.tsx From backstage with Apache License 2.0 | 6 votes |
Incidents = ({ serviceId, refreshIncidents }: Props) => {
const api = useApi(pagerDutyApiRef);
const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn(
async () => await api.getIncidentsByServiceId(serviceId),
);
useEffect(() => {
getIncidents();
}, [refreshIncidents, getIncidents]);
if (error) {
return (
<Alert severity="error">
Error encountered while fetching information. {error.message}
</Alert>
);
}
if (loading) {
return <Progress />;
}
if (!incidents?.length) {
return <IncidentsEmptyState />;
}
return (
<List dense subheader={<ListSubheader>INCIDENTS</ListSubheader>}>
{incidents!.map((incident, index) => (
<IncidentListItem key={incident.id + index} incident={incident} />
))}
</List>
);
}
Example #3
Source File: ChangeEvents.tsx From backstage with Apache License 2.0 | 6 votes |
ChangeEvents = ({ serviceId, refreshEvents }: Props) => {
const api = useApi(pagerDutyApiRef);
const [{ value: changeEvents, loading, error }, getChangeEvents] = useAsyncFn(
async () => await api.getChangeEventsByServiceId(serviceId),
);
useEffect(() => {
getChangeEvents();
}, [refreshEvents, getChangeEvents]);
if (error) {
return (
<Alert severity="error">
Error encountered while fetching information. {error.message}
</Alert>
);
}
if (loading) {
return <Progress />;
}
if (!changeEvents?.length) {
return <ChangeEventEmptyState />;
}
return (
<List dense subheader={<ListSubheader>CHANGE EVENTS</ListSubheader>}>
{changeEvents!.map((changeEvent, index) => (
<ChangeEventListItem
key={changeEvent.id + index}
changeEvent={changeEvent}
/>
))}
</List>
);
}
Example #4
Source File: MenuContents.tsx From firetable with Apache License 2.0 | 5 votes |
export default function MenuContents({ menuItems }: IMenuContentsProps) {
const classes = useStyles();
return (
<>
{menuItems.map((item, index) => {
if (item.type === "subheader")
return (
<>
{index !== 0 && <Divider />}
<ListSubheader
key={index}
className={classes.subheader}
disableGutters
disableSticky
>
{item.label}
</ListSubheader>
</>
);
let icon: JSX.Element = item.icon ?? <></>;
if (item.active && !!item.activeIcon) icon = item.activeIcon;
return (
<>
{index !== 0 && <Divider />}
<MenuItem
key={index}
onClick={item.onClick}
className={clsx(
classes.menuItem,
item.active && classes.menuItemActive,
item.color === "error" && classes.menuItemError
)}
disabled={item.disabled}
>
<ListItemIcon className={classes.menuItemIcon}>
{icon}
</ListItemIcon>
{item.active ? item.activeLabel : item.label}
</MenuItem>
</>
);
})}
</>
);
}
Example #5
Source File: TemplateDialog.tsx From prompts-ai with MIT License | 5 votes |
export default function TemplateDialog() {
const dispatch = useDispatch();
const classes = useStyles();
const templateDialogOpen = useSelector(selectTemplateDialogVisible);
const handleTemplateDialogClose = () => {
dispatch(toggleTemplateDialog(false));
};
const templateGroups = getTemplateGroups();
const handleLoadTemplate = (template: Template) => () => {
dispatch(loadTemplate(template.actionPayload))
dispatch(cleanExampleList());
handleTemplateDialogClose();
};
return <Dialog
open={templateDialogOpen}
onClose={handleTemplateDialogClose}
aria-labelledby="template-dialog-title"
>
<DialogTitle id="template-dialog-title">Load Template</DialogTitle>
<DialogContent
className={classes.templateDialog}
>
{templateGroups.map((templateGroup, ind) => (
<div key={ind}>
<List subheader={<ListSubheader className={classes.templateGroupHeader}>{templateGroup.name}</ListSubheader>}>
{templateGroup.templates.map(template => (
<ListItem key={template.id} button
onClick={handleLoadTemplate(template)}><ListItemText>{template.name}</ListItemText></ListItem>
))}
</List>
</div>
))}
</DialogContent>
<DialogActions>
<Button onClick={handleTemplateDialogClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>;
}
Example #6
Source File: Search.tsx From ra-enterprise-demo with MIT License | 5 votes |
CustomSearchPanel: FC<SearchPanelProps> = props => {
const listRef = useRef<HTMLUListElement>(
null
) as React.MutableRefObject<HTMLUListElement>;
const translate = useTranslate();
const classes = useCustomSearchPanelStyles(props);
useArrowKeysToNavigate(listRef);
const { data, onClose } = useSearchResults();
if (!data || data.length === 0) {
return (
<List data-testid="search-panel" dense {...props}>
<ListItem>
<ListItemText
primary={translate('ra.navigation.no_results')}
/>
</ListItem>
</List>
);
}
const groupedData = groupSearchResultsByResource(data, translate);
return (
<List
className={classes.root}
data-testid="search-panel"
dense
innerRef={listRef}
{...props}
>
{groupedData.map(group => (
<Fragment key={group.label}>
<ListSubheader
role="presentation"
className={classes.header}
disableSticky
>
<>
<Typography
className={classes.headerGroup}
variant="subtitle1"
>
{translate(group.label.toString(), {
_: group.label,
})}
</Typography>
<Typography
className={classes.headerCount}
variant="subtitle1"
>
{translate('ra-search.result', {
smart_count: group.data.length,
})}
</Typography>
</>
</ListSubheader>
{group.data.map(searchResultItem => (
<CustomSearchResultItem
key={searchResultItem.id}
data={searchResultItem}
onClose={onClose}
/>
))}
</Fragment>
))}
</List>
);
}
Example #7
Source File: Incidents.tsx From backstage with Apache License 2.0 | 5 votes |
Incidents = ({ readOnly, refreshIncidents, team }: Props) => {
const classes = useStyles();
const api = useApi(splunkOnCallApiRef);
const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn(
async () => {
// For some reason the changes applied to incidents (trigger-resolve-acknowledge)
// may take some time to actually be applied after receiving the response from the server.
// The timeout compensates for this latency.
await new Promise(resolve => setTimeout(resolve, 2000));
const allIncidents = await api.getIncidents();
const teams = await api.getTeams();
const teamSlug = teams.find(teamValue => teamValue.name === team)?.slug;
const filteredIncidents = teamSlug
? allIncidents.filter(incident =>
incident.pagedTeams?.includes(teamSlug),
)
: [];
return filteredIncidents;
},
);
useEffect(() => {
getIncidents();
}, [refreshIncidents, getIncidents]);
if (error) {
return (
<Alert severity="error">
Error encountered while fetching information. {error.message}
</Alert>
);
}
if (!loading && !incidents?.length) {
return <IncidentsEmptyState />;
}
return (
<List
className={classes.root}
dense
subheader={
<ListSubheader className={classes.subheader}>
CRITICAL INCIDENTS
</ListSubheader>
}
>
{loading ? (
<Progress className={classes.progress} />
) : (
incidents!.map((incident, index) => (
<IncidentListItem
onIncidentAction={() => getIncidents()}
key={index}
team={team}
incident={incident}
readOnly={readOnly}
/>
))
)}
</List>
);
}
Example #8
Source File: EscalationPolicy.tsx From backstage with Apache License 2.0 | 5 votes |
EscalationPolicy = ({ users, team }: Props) => {
const classes = useStyles();
const api = useApi(splunkOnCallApiRef);
const {
value: userNames,
loading,
error,
} = useAsync(async () => {
const oncalls = await api.getOnCallUsers();
const teamUsernames = oncalls
.filter(oncall => oncall.team?.name === team)
.flatMap(oncall => {
return oncall.oncallNow?.flatMap(oncallNow => {
return oncallNow.users?.flatMap(user => {
return user?.onCalluser?.username;
});
});
});
return teamUsernames;
});
if (error) {
return (
<Alert severity="error">
Error encountered while fetching information. {error.message}
</Alert>
);
}
if (!loading && !userNames?.length) {
return <EscalationUsersEmptyState />;
}
return (
<List
className={classes.root}
dense
subheader={
<ListSubheader className={classes.subheader}>ON CALL</ListSubheader>
}
>
{loading ? (
<Progress className={classes.progress} />
) : (
userNames &&
userNames.map(
(userName, index) =>
userName &&
userName in users && (
<EscalationUser key={index} user={users[userName]} />
),
)
)}
</List>
);
}
Example #9
Source File: EscalationPolicy.tsx From backstage with Apache License 2.0 | 5 votes |
EscalationPolicy = ({ policyId }: Props) => {
const api = useApi(pagerDutyApiRef);
const {
value: users,
loading,
error,
} = useAsync(async () => {
const oncalls = await api.getOnCallByPolicyId(policyId);
const usersItem = oncalls
.sort((a, b) => a.escalation_level - b.escalation_level)
.map(oncall => oncall.user);
return usersItem;
});
if (error) {
return (
<Alert severity="error">
Error encountered while fetching information. {error.message}
</Alert>
);
}
if (loading) {
return <Progress />;
}
if (!users?.length) {
return <EscalationUsersEmptyState />;
}
return (
<List dense subheader={<ListSubheader>ON CALL</ListSubheader>}>
{users!.map((user, index) => (
<EscalationUser key={index} user={user} />
))}
</List>
);
}
Example #10
Source File: BoundaryDropdown.tsx From prism-frontend with MIT License | 5 votes |
ClickableListSubheader = styled(ListSubheader)(({ theme }) => ({
// Override the default list subheader style to make it clickable
pointerEvents: 'inherit',
cursor: 'pointer',
'&:hover': {
backgroundColor: theme.palette.grey[100],
},
}))
Example #11
Source File: TransactionList.tsx From End-to-End-Web-Testing-with-Cypress with MIT License | 5 votes |
TransactionList: React.FC<TransactionListProps> = ({
header,
transactions,
isLoading,
showCreateButton,
loadNextPage,
pagination,
filterComponent,
}) => {
const classes = useStyles();
const showEmptyList = !isLoading && transactions?.length === 0;
const showSkeleton = isLoading && isEmpty(pagination);
return (
<Paper className={classes.paper}>
{filterComponent}
<ListSubheader component="div">{header}</ListSubheader>
{showSkeleton && <SkeletonList />}
{transactions.length > 0 && (
<TransactionInfiniteList
transactions={transactions}
loadNextPage={loadNextPage}
pagination={pagination}
/>
)}
{showEmptyList && (
<EmptyList entity="Transactions">
<Grid
container
direction="column"
justify="center"
alignItems="center"
style={{ width: "100%" }}
spacing={2}
>
<Grid item>
<TransferMoneyIllustration style={{ height: 200, width: 300, marginBottom: 30 }} />
</Grid>
<Grid item>
{showCreateButton && (
<Button
data-test="transaction-list-empty-create-transaction-button"
variant="contained"
color="primary"
component={RouterLink}
to="/transaction/new"
>
Create A Transaction
</Button>
)}
</Grid>
</Grid>
</EmptyList>
)}
</Paper>
);
}
Example #12
Source File: NavDrawer.tsx From akashlytics with GNU General Public License v3.0 | 5 votes |
export function NavDrawer({ isDrawerOpen, toggleDrawer }) {
const classes = useStyles();
const anchor = "left";
return (
<React.Fragment>
<SwipeableDrawer anchor={anchor} open={isDrawerOpen} onClose={toggleDrawer(false)} onOpen={toggleDrawer(true)}>
<div className={clsx(classes.list)} role="presentation" onClick={toggleDrawer(false)} onKeyDown={toggleDrawer(false)}>
<List
subheader={
<ListSubheader component={Link} to="/" id="nested-list-subheader" className={classes.listSubHeader}>
<img src="/images/akashlytics_logo_compact_small.png" alt="Akashlytics logo" className={clsx(classes.listSubHeaderLogo, "App-logo")} />
</ListSubheader>
}
>
<ListItem button component={Link} to="/">
<ListItemIcon>
<DashboardIcon />
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItem>
<ListItem button component={Link} to="/price-compare">
<ListItemIcon>
<AttachMoneyIcon />
</ListItemIcon>
<ListItemText primary="Compare price" />
</ListItem>
<ListItem button component={Link} to="/faq">
<ListItemIcon>
<HelpIcon />
</ListItemIcon>
<ListItemText primary="FAQ" />
</ListItem>
<ListItem button component={Link} to="/deploy">
<ListItemIcon>
<CloudUploadIcon />
</ListItemIcon>
<ListItemText primary="Deploy" />
</ListItem>
</List>
</div>
</SwipeableDrawer>
</React.Fragment>
);
}
Example #13
Source File: LayerDropdown.tsx From prism-frontend with MIT License | 4 votes |
function LayerDropdown({
type,
value,
setValue,
placeholder,
...rest
}: LayerSelectorProps) {
// this could be testable, needs to be constructed in a way that prevents it breaking whenever new layers are added. (don't put layer name in snapshot)
const { t } = useSafeTranslation();
const categories = menuList // we could memo this but it isn't impacting performance, for now
// 1. flatten to just the layer categories, don't need the big menus
.flatMap(menu => menu.layersCategories)
// 2. breakdown grouped layer back into flat list of layers if activate_all = false
.map(layerCategory => {
if (layerCategory.layers.some(f => f.group)) {
const layers = layerCategory.layers.map(layer => {
if (layer.group && !layer.group.activateAll) {
return layer.group.layers.map(layerKey => {
return LayerDefinitions[layerKey.id as LayerKey];
});
}
return layer;
});
return {
title: layerCategory.title,
layers: layers.flat(),
tables: layerCategory.tables,
};
}
return layerCategory;
})
// 3. get rid of layers within the categories which don't match the given type
.map(category => ({
...category,
layers: category.layers.filter(layer =>
layer.type === 'wms'
? layer.type === type &&
[undefined, 'polygon'].includes(layer.geometry)
: layer.type === type,
),
}))
// 4. filter categories which don't have any layers at the end of it all.
.filter(category => category.layers.length > 0);
const defaultValue = 'placeholder';
return (
<FormControl {...rest}>
<Select
defaultValue={defaultValue}
value={value}
onChange={e => {
setValue(e.target.value as LayerKey);
}}
>
{categories.reduce(
// map wouldn't work here because <Select> doesn't support <Fragment> with keys, so we need one array
(components, category) => [
...components,
<ListSubheader key={category.title}>
<Typography variant="body2" color="primary">
{t(category.title)}
</Typography>
</ListSubheader>,
...category.layers.map(layer => (
<MenuItem key={layer.id} value={layer.id}>
{t(layer.title || '')}
{getLayerGeometryIcon(layer)}
</MenuItem>
)),
],
(placeholder
? [
<MenuItem key={defaultValue} value={defaultValue} disabled>
{t(placeholder)}
</MenuItem>,
]
: []) as ReactElement[],
)}
</Select>
</FormControl>
);
}
Example #14
Source File: Menu.tsx From clarity with Apache License 2.0 | 4 votes |
MoreMenu = observer((props: Props) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const classes = useStyles();
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setTimeout(() => {
setAnchorEl(null);
}, 200);
};
return (
<div>
<IconButton
edge="end"
aria-controls="simple-menu"
aria-haspopup="true"
onClick={handleClick}
>
<MenuIcon />
</IconButton>
<Menu
id={'simple-menu'}
anchorEl={anchorEl}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={Boolean(anchorEl)}
onClose={handleClose}
>
<List
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="div" id="nested-list-subheader">
Accounts
</ListSubheader>
}
>
{props.authContainer.userAccounts.map((account, i) => {
return (
<ListItem
key={i}
button
dense={true}
onClick={() => {
props.authContainer.switchToAccount(account.name);
handleClose();
}}
>
{account.name ===
props.authContainer.selectedUserAccount?.name ? (
<CheckIcon fontSize={'small'} />
) : (
<Icon className={'fa fa-fw'} fontSize={'small'} />
)}
<ListItemText primary={account.name} />
</ListItem>
);
})}
<Divider light />
{props.authContainer.userAccounts.length > 0 && (
<ListItem
dense={true}
component={Link}
to={Pages.AccountManagement}
button
onClick={handleClose}
>
<SettingsIcon className={classes.menuIcon} />
<ListItemText primary="Key Management" />
</ListItem>
)}
{props.authContainer.selectedUserAccount && (
<ListItem
dense={true}
button
onClick={() => {
props.authContainer.downloadActiveKey();
handleClose();
}}
>
<CloudDownloadIcon className={classes.menuIcon} />
<ListItemText primary="Download Active Key" />
</ListItem>
)}
<ListItem
dense={true}
button
onClick={() => {
props.authContainer.lock();
handleClose();
}}
>
<LockIcon className={classes.menuIcon} />
<ListItemText primary="Lock" />
</ListItem>
</List>
</Menu>
</div>
);
})
Example #15
Source File: AccountManagementPage.tsx From signer with Apache License 2.0 | 4 votes |
render() {
return !this.props.accountManager.isUnLocked ||
!this.props.accountManager.userAccounts[0] ? (
<Redirect to={Pages.Home} />
) : (
<React.Fragment>
<DragDropContext onDragEnd={result => this.onDragEnd(result)}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<Observer>
{() => (
// TODO: fix this (deprecated RootRef)
<RootRef rootRef={provided.innerRef}>
<List>
{this.props.accountManager.userAccounts.map(
(item, index) => (
<Draggable
key={item.alias}
draggableId={item.alias}
index={index}
>
{(provided, snapshot) => (
<ListItem
innerRef={provided.innerRef}
ContainerProps={{
...provided.draggableProps,
...provided.dragHandleProps,
style: getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)
}}
>
<ListItemText primary={item.alias} />
<ListItemSecondaryAction>
<Tooltip title="Edit">
<IconButton
aria-label="Button will open a dialog to rename key"
edge={'end'}
onClick={() => {
this.handleClickOpen(item);
}}
>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton
edge={'end'}
onClick={() => {
this.handleClickRemove(item.alias);
}}
>
<DeleteIcon />
</IconButton>
</Tooltip>
<Tooltip title="View">
<IconButton
edge={'end'}
onClick={() => {
this.handleViewKey(item.alias);
}}
>
<VpnKeyIcon />
</IconButton>
</Tooltip>
<Tooltip title="Download">
<IconButton
edge={'end'}
onClick={() => {
this.handleDownloadKeys(item.alias);
}}
>
<GetApp />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
)}
</Draggable>
)
)}
{provided.placeholder}
</List>
</RootRef>
)}
</Observer>
)}
</Droppable>
</DragDropContext>
<Dialog
open={this.state.openDialog}
onClose={this.handleClose}
aria-label="Form to rename account - focus will be given to name input field"
aria-labelledby="form-dialog-title"
>
<form>
<DialogTitle id="form-dialog-title">Rename</DialogTitle>
<DialogContent>
<TextFieldWithFormState
autoFocus
fullWidth
label="Rename account"
placeholder="Account alias"
id="rename-account"
fieldState={this.renameAccountForm.name}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Cancel
</Button>
<Button
type="submit"
onClick={this.handleUpdateName}
color="primary"
disabled={this.renameAccountForm.submitDisabled}
>
Update
</Button>
</DialogActions>
</form>
</Dialog>
<Dialog
fullScreen
open={this.state.openKeyDialog}
onClose={this.handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Account Details</DialogTitle>
<DialogContent>
<List>
<ListSubheader>
<Typography variant={'h6'}>{this.state.alias}</Typography>
</ListSubheader>
<ListItem>
<IconButton
edge={'start'}
onClick={() => {
copy(this.state.publicKeyHex!);
this.setState({ copyStatus: true });
}}
>
<FilterNoneIcon />
</IconButton>
<ListItemText
primary={`Public Key: ${this.state.publicKeyHex}`}
style={{ overflowWrap: 'break-word' }}
/>
</ListItem>
<ListItem>
<IconButton
edge={'start'}
onClick={() => {
copy(this.state.accountHash!);
this.setState({ copyStatus: true });
}}
>
<FilterNoneIcon />
</IconButton>
<ListItemText
primary={`Account Hash: ${this.state.accountHash}`}
style={{ overflowWrap: 'break-word' }}
/>
</ListItem>
</List>
<Snackbar
open={this.state.copyStatus}
message="Copied!"
autoHideDuration={1500}
onClose={this.handleCopyMessage}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
Example #16
Source File: Menu.tsx From signer with Apache License 2.0 | 4 votes |
MoreMenu = observer((props: Props) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const classes = useStyles();
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setTimeout(() => {
setAnchorEl(null);
}, 200);
};
return (
<div>
<IconButton
edge="end"
aria-controls="simple-menu"
aria-haspopup="true"
onClick={handleClick}
style={{ color: '#C4C4C4' }}
>
<MenuIcon />
</IconButton>
<Menu
id={'simple-menu'}
anchorEl={anchorEl}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={Boolean(anchorEl)}
onClose={handleClose}
>
<List
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="div" id="nested-list-subheader">
{props.accountManager.userAccounts.length > 0
? 'Accounts'
: 'No Account'}
</ListSubheader>
}
>
{props.accountManager.userAccounts.map((account, i) => {
return (
<ListItem
key={i}
button
dense={true}
onClick={() => {
props.accountManager.switchToAccount(account.alias);
handleClose();
}}
>
{account.alias ===
props.accountManager.activeUserAccount?.alias ? (
<CheckIcon fontSize={'small'} />
) : (
<Icon className={'fa fa-fw'} fontSize={'small'} />
)}
<ListItemText primary={account.alias} />
</ListItem>
);
})}
<Divider light />
{props.accountManager.userAccounts.length > 0 && (
<ListItem
dense={true}
component={Link}
to={Pages.AccountManagement}
button
onClick={handleClose}
>
<SettingsIcon className={classes.menuIcon} />
<ListItemText primary="Key Management" />
</ListItem>
)}
<ListItem
dense={true}
component={Link}
to={Pages.ConnectedSites}
button
onClick={handleClose}
>
<WebIcon className={classes.menuIcon} />
<ListItemText primary="Connected Sites" />
</ListItem>
{props.accountManager.activeUserAccount && (
<ListItem
dense={true}
button
onClick={() => {
props.accountManager.downloadActiveKey();
handleClose();
}}
>
<CloudDownloadIcon className={classes.menuIcon} />
<ListItemText primary="Download Active Key" />
</ListItem>
)}
<ListItem
dense={true}
component={Link}
to={Pages.ConfigureTimeout}
button
onClick={handleClose}
>
<TimerIcon className={classes.menuIcon} />
<ListItemText primary="Timeout" />
<Typography variant="overline">
{props.accountManager.idleTimeoutMins} min
{props.accountManager.idleTimeoutMins === 1 ? '' : 's'}
</Typography>
</ListItem>
<ListItem
dense={true}
button
onClick={() => {
props.accountManager.lock();
handleClose();
}}
>
<LockIcon className={classes.menuIcon} />
<ListItemText primary="Lock" />
</ListItem>
</List>
</Menu>
</div>
);
})
Example #17
Source File: LogIn.tsx From clearflask with Apache License 2.0 | 4 votes |
render() {
if (!this.props.open && !this.props.inline) return null;
const onboarding = this.props.config?.users.onboarding || this.props.onboardBefore;
const notifOpts: Set<NotificationType> = new Set();
const oauthOpts: Array<Client.NotificationMethodsOauth> = onboarding?.notificationMethods.oauth || [];
if (onboarding) {
// if (onboarding.notificationMethods.mobilePush === true
// && (this.props.overrideMobileNotification || MobileNotification.getInstance()).canAskPermission()) {
// switch ((this.props.overrideMobileNotification || MobileNotification.getInstance()).getDevice()) {
// case Device.Android:
// notifOpts.add(NotificationType.Android);
// break;
// case Device.Ios:
// notifOpts.add(NotificationType.Ios);
// break;
// }
// }
if (onboarding.notificationMethods.browserPush === true
&& (this.props.overrideWebNotification || WebNotification.getInstance()).canAskPermission()) {
notifOpts.add(NotificationType.Browser);
}
if (onboarding.notificationMethods.anonymous
&& (onboarding.notificationMethods.anonymous.onlyShowIfPushNotAvailable !== true
|| (!notifOpts.has(NotificationType.Android) && !notifOpts.has(NotificationType.Ios) && !notifOpts.has(NotificationType.Browser)))) {
notifOpts.add(NotificationType.Silent)
}
if (onboarding.notificationMethods.email) {
notifOpts.add(NotificationType.Email);
}
if (onboarding.notificationMethods.sso) {
notifOpts.add(NotificationType.SSO);
}
if (oauthOpts.length > 0) {
notifOpts.add(NotificationType.OAuth);
}
}
const signupAllowed = notifOpts.size > 0;
const onlySingleOption = notifOpts.size === 1 && oauthOpts.length <= 1;
const singleColumnLayout = this.props.fullScreen || onlySingleOption;
const selectedNotificationType = (this.state.notificationType && notifOpts.has(this.state.notificationType))
? this.state.notificationType
: (onlySingleOption ? notifOpts.values().next().value : undefined);
const selectedOauthType = selectedNotificationType === NotificationType.OAuth && (this.state.oauthType
? this.state.oauthType
: oauthOpts[0]?.oauthId);
const emailValid = this.isEmailValid(this.state.email);
const emailAllowedDomain = this.isAllowedDomain(this.state.email);
const showDisplayNameInput = signupAllowed && !!onboarding?.accountFields && onboarding.accountFields.displayName !== Client.AccountFieldsDisplayNameEnum.None && selectedNotificationType !== NotificationType.SSO && selectedNotificationType !== NotificationType.OAuth;
const isDisplayNameRequired = showDisplayNameInput && onboarding?.accountFields?.displayName === Client.AccountFieldsDisplayNameEnum.Required;
const showPasswordInput = onboarding?.notificationMethods.email && onboarding.notificationMethods.email.password !== Client.EmailSignupPasswordEnum.None;
const isPasswordRequired = onboarding?.notificationMethods.email && onboarding.notificationMethods.email.password === Client.EmailSignupPasswordEnum.Required;
const showAccountFields = showPasswordInput || showDisplayNameInput;
const showEmailInput = selectedNotificationType === NotificationType.Email;
const showEmailInputInline = !showAccountFields;
const isSubmittable = selectedNotificationType
&& (selectedNotificationType !== NotificationType.SSO)
&& (selectedNotificationType !== NotificationType.Android || this.state.notificationDataAndroid)
&& (selectedNotificationType !== NotificationType.Ios || this.state.notificationDataIos)
&& (selectedNotificationType !== NotificationType.Browser || this.state.notificationDataBrowser)
&& (!isDisplayNameRequired || this.state.displayName)
&& (selectedNotificationType !== NotificationType.Email || (emailValid && emailAllowedDomain))
&& (!isPasswordRequired || this.state.pass);
const onlySingleOptionRequiresAllow = onlySingleOption &&
((selectedNotificationType === NotificationType.Android && !this.state.notificationDataAndroid)
|| (selectedNotificationType === NotificationType.Ios && !this.state.notificationDataIos)
|| (selectedNotificationType === NotificationType.Browser && !this.state.notificationDataBrowser));
const doSubmit = async (): Promise<string | undefined> => {
if (!!this.props.loggedInUser) {
this.props.onLoggedInAndClose(this.props.loggedInUser.userId);
return this.props.loggedInUser.userId;
}
this.setState({ isSubmitting: true });
try {
const userCreateResponse = await (await this.props.server.dispatch()).userCreate({
projectId: this.props.server.getProjectId(),
userCreate: {
name: showDisplayNameInput ? this.state.displayName : undefined,
email: showEmailInput ? this.state.email : undefined,
password: (showPasswordInput && this.state.pass) ? saltHashPassword(this.state.pass) : undefined,
iosPushToken: selectedNotificationType === NotificationType.Ios ? this.state.notificationDataIos : undefined,
androidPushToken: selectedNotificationType === NotificationType.Android ? this.state.notificationDataAndroid : undefined,
browserPushToken: selectedNotificationType === NotificationType.Browser ? this.state.notificationDataBrowser : undefined,
},
});
if (userCreateResponse.requiresEmailLogin) {
return new Promise(resolve => {
this.setState({
isSubmitting: false,
emailLoginDialog: resolve,
});
})
} else if (userCreateResponse.requiresEmailVerification) {
return new Promise(resolve => {
this.setState({
isSubmitting: false,
emailVerifyDialog: resolve,
});
})
} else {
this.setState({ isSubmitting: false });
if (userCreateResponse.user) {
this.props.onLoggedInAndClose(userCreateResponse.user.userId);
}
return userCreateResponse.user?.userId;
}
} catch (e) {
this.setState({ isSubmitting: false });
throw e;
}
};
if (this.props.externalSubmit && this.externalSubmitEnabled !== isSubmittable) {
this.externalSubmitEnabled = isSubmittable;
this.props.externalSubmit(isSubmittable ? doSubmit : undefined);
}
const emailInput = !notifOpts.has(NotificationType.Email) ? undefined : (
<TextField
classes={{
root: classNames(!!showEmailInputInline && this.props.classes.emailTextFieldInline),
}}
InputLabelProps={{
classes: {
root: classNames(!!showEmailInputInline && this.props.classes.emailInputLabelInline),
},
}}
InputProps={{
classes: {
notchedOutline: classNames(!!showEmailInputInline && this.props.classes.emailInputInline),
},
}}
inputRef={this.emailInputRef}
variant='outlined'
size='small'
fullWidth
required={!showEmailInputInline}
value={this.state.email || ''}
onChange={e => this.setState({ email: e.target.value })}
label='Email'
type='email'
error={!!this.state.email && (!emailValid || !emailAllowedDomain)}
helperText={(!!this.props.minimalistic || !!showEmailInputInline) ? undefined : (
<span className={this.props.classes.noWrap}>
{!this.state.email || emailAllowedDomain ? 'Where to send you updates' : `Allowed domains: ${onboarding?.notificationMethods.email?.allowedDomains?.join(', ')}`}
</span>
)}
margin='normal'
style={{ marginTop: showDisplayNameInput ? undefined : '0px' }}
disabled={this.state.isSubmitting}
/>
);
const dialogContent = (
<>
<DialogContent className={classNames(
this.props.className,
this.props.inline && this.props.classes.contentInline,
)}>
{!!this.props.actionTitle && typeof this.props.actionTitle !== 'string' && this.props.actionTitle}
<div>
<div
className={this.props.classes.content}
style={singleColumnLayout ? { flexDirection: 'column' } : undefined}
>
<List component='nav' className={this.props.classes.notificationList}>
{((!this.props.actionTitle && !this.props.minimalistic) || typeof this.props.actionTitle === 'string') && (
<ListSubheader className={this.props.classes.noWrap} component="div">{this.props.actionTitle !== undefined ? this.props.actionTitle : 'Create account'}</ListSubheader>
)}
<Collapse mountOnEnter in={notifOpts.has(NotificationType.SSO)}>
<ListItem
button={!onlySingleOption as any}
selected={!onlySingleOption && selectedNotificationType === NotificationType.SSO}
onClick={!onlySingleOption ? this.onClickSsoNotif.bind(this) : e => this.setState({ notificationType: NotificationType.SSO })}
disabled={this.state.isSubmitting}
>
<ListItemIcon>
{!onboarding?.notificationMethods.sso?.icon
? (<NewWindowIcon />)
: (<DynamicMuiIcon name={onboarding?.notificationMethods.sso?.icon} />)}
</ListItemIcon>
<ListItemText primary={onboarding?.notificationMethods.sso?.buttonTitle
|| this.props.config?.name
|| 'External'} />
</ListItem>
<Collapse mountOnEnter in={onlySingleOption}>
<Button color='primary' className={this.props.classes.allowButton} onClick={this.onClickSsoNotif.bind(this)}>Open</Button>
</Collapse>
</Collapse>
{oauthOpts.map(oauthOpt => (
<Collapse mountOnEnter in={notifOpts.has(NotificationType.OAuth)}>
<ListItem
button={!onlySingleOption as any}
selected={!onlySingleOption && selectedNotificationType === NotificationType.OAuth && selectedOauthType === oauthOpt.oauthId}
onClick={!onlySingleOption
? e => this.onClickOauthNotif(oauthOpt)
: e => this.setState({
notificationType: NotificationType.OAuth,
oauthType: oauthOpt.oauthId,
})}
disabled={this.state.isSubmitting}
>
<ListItemIcon>
{!oauthOpt.icon
? (<NewWindowIcon />)
: (<DynamicMuiIcon name={oauthOpt.icon} />)}
</ListItemIcon>
<ListItemText primary={oauthOpt.buttonTitle} />
</ListItem>
<Collapse mountOnEnter in={onlySingleOption}>
<Button color='primary' className={this.props.classes.allowButton} onClick={e => this.onClickOauthNotif(oauthOpt)}>Open</Button>
</Collapse>
</Collapse>
))}
<Collapse mountOnEnter in={notifOpts.has(NotificationType.Android) || notifOpts.has(NotificationType.Ios)}>
<ListItem
// https://github.com/mui-org/material-ui/pull/15049
button={!onlySingleOption as any}
selected={!onlySingleOption && (selectedNotificationType === NotificationType.Android || selectedNotificationType === NotificationType.Ios)}
onClick={!onlySingleOption ? this.onClickMobileNotif.bind(this) : undefined}
disabled={onlySingleOptionRequiresAllow || this.state.isSubmitting}
>
<ListItemIcon><MobilePushIcon /></ListItemIcon>
<ListItemText primary='Mobile Push' className={this.props.classes.noWrap} />
</ListItem>
<Collapse mountOnEnter in={onlySingleOptionRequiresAllow}>
<Button color='primary' className={this.props.classes.allowButton} onClick={this.onClickMobileNotif.bind(this)}>Allow</Button>
</Collapse>
</Collapse>
<Collapse mountOnEnter in={notifOpts.has(NotificationType.Browser)}>
<ListItem
button={!onlySingleOption as any}
selected={!onlySingleOption && selectedNotificationType === NotificationType.Browser}
onClick={!onlySingleOption ? this.onClickWebNotif.bind(this) : undefined}
disabled={onlySingleOptionRequiresAllow || this.state.isSubmitting}
>
<ListItemIcon><WebPushIcon /></ListItemIcon>
<ListItemText primary='Browser Push' className={this.props.classes.noWrap} />
</ListItem>
<Collapse mountOnEnter in={onlySingleOptionRequiresAllow}>
<Button color='primary' className={this.props.classes.allowButton} onClick={this.onClickWebNotif.bind(this)}>Allow</Button>
</Collapse>
</Collapse>
<Collapse mountOnEnter in={notifOpts.has(NotificationType.Email)}>
<ListItem
button={!onlySingleOption as any}
selected={!onlySingleOption && selectedNotificationType === NotificationType.Email}
onClick={!onlySingleOption ? e => {
this.setState({ notificationType: NotificationType.Email });
this.emailInputRef.current?.focus();
} : undefined}
disabled={this.state.isSubmitting}
>
<ListItemIcon><EmailIcon /></ListItemIcon>
<ListItemText className={this.props.classes.noWrap} primary={!showEmailInputInline ? 'Email' : emailInput} />
</ListItem>
</Collapse>
<Collapse mountOnEnter in={notifOpts.has(NotificationType.Silent)}>
<ListItem
button={!onlySingleOption as any}
selected={!onlySingleOption && selectedNotificationType === NotificationType.Silent}
onClick={!onlySingleOption ? e => this.setState({ notificationType: NotificationType.Silent }) : undefined}
disabled={this.state.isSubmitting}
>
<ListItemIcon><GuestIcon /></ListItemIcon>
<ListItemText primary={this.props.guestLabelOverride || 'Guest'} />
</ListItem>
</Collapse>
<Collapse mountOnEnter in={!signupAllowed}>
<ListItem
disabled={true}
>
<ListItemIcon><DisabledIcon /></ListItemIcon>
<ListItemText primary='Sign-up is not available' />
</ListItem>
</Collapse>
</List>
<div
className={this.props.classes.accountFieldsContainer}
style={{
maxWidth: showAccountFields ? '400px' : '0px',
maxHeight: showAccountFields ? '400px' : '0px',
}}
>
{!singleColumnLayout && (<Hr vertical isInsidePaper length='25%' />)}
<div>
{!this.props.minimalistic && (
<ListSubheader className={this.props.classes.noWrap} component="div">Your info</ListSubheader>
)}
{showDisplayNameInput && (
<TextField
variant='outlined'
size='small'
fullWidth
required={isDisplayNameRequired}
value={this.state.displayName || ''}
onChange={e => this.setState({ displayName: e.target.value })}
label='Name'
helperText={!!this.props.minimalistic ? undefined : (<span className={this.props.classes.noWrap}>How others see you</span>)}
margin='normal'
classes={{ root: this.props.classes.noWrap }}
style={{ marginTop: '0px' }}
disabled={this.state.isSubmitting}
/>
)}
<Collapse mountOnEnter in={showEmailInput} unmountOnExit>
<div>
{!showEmailInputInline && emailInput}
{showPasswordInput && (
<TextField
variant='outlined'
size='small'
fullWidth
required={isPasswordRequired}
value={this.state.pass || ''}
onChange={e => this.setState({ pass: e.target.value })}
label='Password'
helperText={!!this.props.minimalistic ? undefined : (
<span className={this.props.classes.noWrap}>
{isPasswordRequired
? 'Secure your account'
: 'Optionally secure your account'}
</span>
)}
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}
/>
)}
</div>
</Collapse>
</div>
</div>
</div>
</div>
{signupAllowed && onboarding?.terms?.documents?.length && (
<AcceptTerms overrideTerms={onboarding.terms.documents} />
)}
<Collapse mountOnEnter in={!!this.props.loggedInUser}>
<DialogContentText>You are logged in as <span className={this.props.classes.bold}>{this.props.loggedInUser?.name || this.props.loggedInUser?.email || 'Anonymous'}</span></DialogContentText>
</Collapse>
</DialogContent>
{!this.props.externalSubmit && (
<DialogActions>
{!!this.props.loggedInUser && !!this.props.onClose && (
<Button onClick={this.props.onClose.bind(this)}>Cancel</Button>
)}
{!!signupAllowed ? (
<SubmitButton
color='primary'
isSubmitting={this.state.isSubmitting}
disabled={!isSubmittable && !this.props.loggedInUser}
onClick={doSubmit}
>{this.props.actionSubmitTitle || 'Continue'}</SubmitButton>
) : (!!this.props.onClose ? (
<Button onClick={() => { this.props.onClose?.() }}>Back</Button>
) : null)}
</DialogActions>
)}
<Dialog
open={!!this.state.awaitExternalBind}
onClose={() => this.setState({ awaitExternalBind: undefined })}
maxWidth='xs'
{...this.props.forgotEmailDialogProps}
>
<DialogTitle>Awaiting confirmation...</DialogTitle>
<DialogContent>
{this.state.awaitExternalBind === 'recovery' ? (
<DialogContentText>We sent an email to <span className={this.props.classes.bold}>{this.state.email}</span>. Return to this page after clicking the confirmation link.</DialogContentText>
) : (
<DialogContentText>A popup was opened leading you to a sign-in page. After you complete sign-in, this dialog will automatically close.</DialogContentText>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ awaitExternalBind: undefined })}>Cancel</Button>
</DialogActions>
</Dialog>
<Dialog
open={!!this.state.emailLoginDialog}
onClose={() => {
this.state.emailLoginDialog?.();
this.setState({ emailLoginDialog: undefined })
}}
maxWidth='xs'
>
<DialogTitle>Login via Email</DialogTitle>
<DialogContent>
<DialogContentText>The email <span className={this.props.classes.bold}>{this.state.email}</span> is associated with an account.</DialogContentText>
<DialogContentText>Open the link from the email or copy the verification token here:</DialogContentText>
<DigitsInput
digits={6}
value={this.state.emailLoginToken}
disabled={this.state.isSubmitting}
onChange={(val, isComplete) => {
if (isComplete) {
this.setState({
emailLoginToken: val,
isSubmitting: true,
}, () => setTimeout(() => {
this.props.server.dispatch().then(d => d.userLogin({
projectId: this.props.server.getProjectId(),
userLogin: {
email: this.state.email!,
token: val.join(''),
},
})).then(user => {
this.state.emailLoginDialog?.(user.userId);
this.setState({
isSubmitting: false,
emailLoginDialog: undefined,
});
this.props.onLoggedInAndClose(user.userId);
}).catch(() => {
this.setState({ isSubmitting: false });
});
}, 1));
} else {
this.setState({ emailLoginToken: val });
}
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => {
this.state.emailLoginDialog?.();
this.setState({ emailLoginDialog: undefined })
}}>Cancel</Button>
</DialogActions>
</Dialog>
<Dialog
open={!!this.state.emailVerifyDialog}
onClose={() => {
this.state.emailVerifyDialog?.();
this.setState({ emailVerifyDialog: undefined });
}}
maxWidth='xs'
>
<DialogTitle>Verify your email</DialogTitle>
<DialogContent>
<DialogContentText>We sent a verification email to <span className={this.props.classes.bold}>{this.state.email}</span>. Please copy the verification token from the email here:</DialogContentText>
<DigitsInput
digits={6}
value={this.state.emailVerification}
disabled={this.state.isSubmitting}
onChange={(val, isComplete) => {
if (isComplete) {
this.setState({
emailVerification: val,
isSubmitting: true,
}, () => setTimeout(() => {
this.props.server.dispatch().then(d => d.userCreate({
projectId: this.props.server.getProjectId(),
userCreate: {
name: this.state.displayName,
email: this.state.email!,
emailVerification: val.join(''),
password: this.state.pass ? saltHashPassword(this.state.pass) : undefined,
},
})).then(userCreateResponse => {
if (userCreateResponse.requiresEmailVerification || !userCreateResponse.user) {
this.setState({ isSubmitting: false });
} else {
this.state.emailVerifyDialog?.(userCreateResponse.user.userId);
this.setState({
isSubmitting: false,
emailVerifyDialog: undefined,
});
this.props.onLoggedInAndClose(userCreateResponse.user.userId);
}
}).catch(() => {
this.setState({ isSubmitting: false });
});
}, 1));
} else {
this.setState({ emailVerification: val });
}
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => {
this.state.emailVerifyDialog?.();
this.setState({ emailVerifyDialog: undefined })
}}>Cancel</Button>
</DialogActions>
</Dialog>
</>
);
return this.props.inline ? (
<Collapse mountOnEnter in={!!this.props.open}>
{dialogContent}
</Collapse>
) : (
<Dialog
open={!!this.props.open}
onClose={this.props.onClose}
scroll='body'
PaperProps={{
style: {
width: 'fit-content',
marginLeft: 'auto',
marginRight: 'auto',
},
}}
{...this.props.DialogProps}
>
{dialogContent}
</Dialog>
);
}
Example #18
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 #19
Source File: AccountManagementPage.tsx From clarity with Apache License 2.0 | 4 votes |
AccountManagementPage = observer((props: Props) => {
const [openDialog, setOpenDialog] = React.useState(false);
const [openKeyDialog, setOpenKeyDialog] = React.useState(false);
const [
selectedAccount,
setSelectedAccount
] = React.useState<SignKeyPairWithAlias | null>(null);
const [name, setName] = React.useState('');
const [publicKey64, setPublicKey64] = React.useState('');
const [publicKeyHex, setPublicKeyHex] = React.useState('');
/* Note: 01 prefix denotes algorithm used in key generation */
const address = '01' + publicKeyHex;
const [copyStatus, setCopyStatus] = React.useState(false);
const handleClickOpen = (account: SignKeyPairWithAlias) => {
setOpenDialog(true);
setSelectedAccount(account);
setName(account.name);
};
const handleViewKey = async (accountName: string) => {
let publicKey64 = await props.authContainer.getSelectedAccountKey(
accountName
);
let publicKeyHex = await props.authContainer.getPublicKeyHex(accountName);
setName(accountName);
setPublicKey64(publicKey64);
setPublicKeyHex(publicKeyHex);
setOpenKeyDialog(true);
};
const handleCopyMessage = (event?: React.SyntheticEvent, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setCopyStatus(false);
};
const handleClose = () => {
setOpenDialog(false);
setOpenKeyDialog(false);
setSelectedAccount(null);
};
const handleUpdateName = () => {
if (selectedAccount) {
props.authContainer.renameUserAccount(selectedAccount.name, name);
handleClose();
}
};
const onDragEnd = (result: DropResult) => {
// dropped outside the list
if (!result.destination) {
return;
}
props.authContainer.reorderAccount(
result.source.index,
result.destination.index
);
};
const handleClickRemove = (name: string) => {
confirm(
<div className="text-danger">Remove account</div>,
'Are you sure you want to remove this account?'
).then(() => props.authContainer.removeUserAccount(name));
};
return (
<React.Fragment>
<DragDropContext onDragEnd={result => onDragEnd(result)}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<Observer>
{() => (
<RootRef rootRef={provided.innerRef}>
<List>
{props.authContainer.userAccounts.map((item, index) => (
<Draggable
key={item.name}
draggableId={item.name}
index={index}
>
{(provided, snapshot) => (
<ListItem
innerRef={provided.innerRef}
ContainerProps={{
...provided.draggableProps,
...provided.dragHandleProps,
style: getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)
}}
>
<ListItemText primary={item.name} />
<ListItemSecondaryAction>
<IconButton
edge={'end'}
onClick={() => {
handleClickOpen(item);
}}
>
<EditIcon />
</IconButton>
<IconButton
edge={'end'}
onClick={() => {
handleClickRemove(item.name);
}}
>
<DeleteIcon />
</IconButton>
<IconButton
edge={'end'}
onClick={() => {
handleViewKey(item.name);
}}
>
<VpnKeyIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)}
</Draggable>
))}
{provided.placeholder}
</List>
</RootRef>
)}
</Observer>
)}
</Droppable>
</DragDropContext>
<Dialog
open={openDialog}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Rename</DialogTitle>
<DialogContent>
<Input
autoFocus
margin="dense"
id="name"
type="text"
fullWidth
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleUpdateName} color="primary">
Update
</Button>
</DialogActions>
</Dialog>
<Dialog
fullScreen
open={openKeyDialog}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Account Details</DialogTitle>
<DialogContent>
<List>
<ListSubheader>
<Typography variant={'h6'}>{name}</Typography>
</ListSubheader>
<ListItem>
<IconButton
edge={'start'}
onClick={() => {
copy(address);
setCopyStatus(true);
}}
>
<FilterNoneIcon />
</IconButton>
<ListItemText
primary={'Address: ' + address}
style={{ overflowWrap: 'break-word' }}
/>
</ListItem>
<ListItem>
<IconButton
edge={'start'}
onClick={() => {
copy(publicKey64);
setCopyStatus(true);
}}
>
<FilterNoneIcon />
</IconButton>
<ListItemText
primary={'Public Key: ' + publicKey64}
style={{ overflowWrap: 'break-word' }}
/>
</ListItem>
</List>
<Snackbar
open={copyStatus}
message="Copied!"
autoHideDuration={1500}
onClose={handleCopyMessage}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
})
Example #20
Source File: SettingsPage.tsx From shadowsocks-electron with GNU General Public License v3.0 | 4 votes |
SettingsPage: React.FC = () => {
const styles = useStyles();
const { t } = useTranslation();
const dispatch = useTypedDispatch();
const [form] = Form.useForm();
const settings = useTypedSelector(state => state.settings);
const config = useTypedSelector(state => state.config);
// const [aclVisible, setAclVisible] = useState(false);
const inputFileRef = React.useRef<HTMLInputElement>(null);
const [DialogConfirm, showDialog, closeDialog] = useDialogConfirm();
const settingKeys = useRef(
['localPort', 'pacPort', 'gfwListUrl',
'httpProxy', 'autoLaunch', 'fixedMenu',
'darkMode', 'autoTheme', 'verbose', 'autoHide']
);
const cachedRef = useRef<any>(null);
const enqueueSnackbar = (message: SnackbarMessage, options: Notification) => {
dispatch(enqueueSnackbarAction(message, options))
};
useEffect(() => {
dispatch<any>(getStartupOnBoot());
}, [dispatch]);
/* dark mode */
useEffect(() => {
if (
(persistStore.get('darkMode') === 'true' && !settings.darkMode) ||
(persistStore.get('darkMode') === 'false' && !!settings.darkMode) ||
(persistStore.get('darkMode') === undefined && !!settings.darkMode)
) {
dispatchEvent({
type: 'theme:update',
payload: {
shouldUseDarkColors: !!settings.darkMode
}
});
}
}, [settings.darkMode]);
/* restoreFromFile */
useMemo(() => {
const obj = {};
if (cachedRef.current) {
settingKeys.current.forEach(key => {
if (cachedRef.current[key] !== (settings as any)[key]) {
if (key === 'httpProxy') {
Object.assign(obj, {
httpProxy: settings.httpProxy.enable,
httpProxyPort: settings.httpProxy.port,
});
} else {
Object.assign(obj, { [key]: (settings as any)[key] });
}
}
});
form.setFieldsValue(obj);
}
cachedRef.current = settingKeys.current.reduce(
(pre, cur) => Object.assign(pre, { [cur]: (settings as any)[cur] }),
{}
);
}, settingKeys.current.map(key => (settings as any)[key]));
const backupConfiguration = () => {
return backupConfigurationToFile({
config,
settings
});
};
const restoreConfiguration = () => {
dispatch<any>(restoreConfigurationFromFile({
success: t('the_recovery_is_successful'),
error: {
default: t('the_recovery_is_failed'),
404: t('user_canceled')
}
}));
}
const checkPortValid = (parsedValue: number) => {
if (!(parsedValue && parsedValue > 1024 && parsedValue <= 65535)) {
return Promise.reject(t("invalid_port_range"));
}
return Promise.resolve();
};
const checkPortSame = () => {
const localPort = +form.getFieldValue('localPort');
const pacPort = +form.getFieldValue('pacPort');
const httpPort = +form.getFieldValue('httpProxyPort');
const num = localPort ^ pacPort ^ httpPort;
if (num === localPort || num === pacPort || num === httpPort) {
return Promise.reject(t("the_same_port_is_not_allowed"));
}
return Promise.resolve();
};
const handleOpenLog = async () => {
await MessageChannel.invoke('main', 'service:desktop', {
action: 'openLogDir',
params: {}
});
};
const handleOpenProcessManager = async () => {
await MessageChannel.invoke('main', 'service:desktop', {
action: 'openProcessManager',
params: {}
});
};
const handleReset = () => {
dispatch({
type: CLEAR_STORE
} as any);
closeDialog();
MessageChannel.invoke('main', 'service:main', {
action: 'stopClient',
params: {}
});
enqueueSnackbar(t('cleared_all_data'), { variant: 'success' });
};
const handleAlertDialogOpen = () => {
showDialog(t('reset_all_data'), t('reset_all_data_tips'));
};
const handleAlertDialogClose = () => {
closeDialog();
};
const reGeneratePacFileWithFile = () => {
inputFileRef.current?.click();
}
const reGeneratePacFileWithUrl = () => {
reGeneratePacFile({
url: settings.gfwListUrl
});
}
const onGFWListFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e: any) => {
const text = e.target.result;
if (text) {
reGeneratePacFile({
text: text
});
}
};
reader.readAsText(file);
}
}
const reGeneratePacFile = (params: { url?: string, text?: string }) => {
dispatch<any>(setStatus('waiting', true));
MessageChannel.invoke('main', 'service:main', {
action: 'reGeneratePacFile',
params
}).then((rsp) => {
setTimeout(() => { dispatch<any>(setStatus('waiting', false)); }, 1e3);
if (rsp.code === 200) {
enqueueSnackbar(t('successful_operation'), { variant: 'success' });
} else {
enqueueSnackbar(t('failed_to_download_file'), { variant: 'error' });
}
});
}
const onLangChange = (e: React.ChangeEvent<{ name?: string | undefined, value: unknown; }>) => {
if (persistStore.get('lang') === e.target.value) return;
persistStore.set('lang', e.target.value as string);
MessageChannel.invoke('main', 'service:desktop', {
action: 'reloadMainWindow',
params: {}
});
MessageChannel.invoke('main', 'service:desktop', {
action: 'setLocale',
params: getFirstLanguage(e.target.value as string)
});
}
const onAutoThemeChange = (e: React.ChangeEvent<{ name?: string | undefined, checked: boolean; }>) => {
const checked = e.target.checked;
MessageChannel.invoke('main', 'service:theme', {
action: checked ? 'listenForUpdate' : 'unlistenForUpdate',
params: {}
}).then(rsp => {
if (rsp.code === 200) {
persistStore.set('autoTheme', checked ? 'true' : 'false');
}
});
MessageChannel.invoke('main', 'service:theme', {
action: 'getSystemThemeInfo',
params: {}
})
.then(rsp => {
if (rsp.code === 200) {
dispatchEvent({
type: 'theme:update',
payload: rsp.result
});
if (!checked) {
form.setFieldsValue({
darkMode: rsp.result?.shouldUseDarkColors
});
}
}
});
}
const checkPortField = (rule: any, value: any) => {
return Promise.all([checkPortSame(), checkPortValid(value)]);
};
const onFieldChange = (changedFields: { [key: string]: any }, allFields: { [key: string]: any }) => {
const keys = Object.keys(changedFields);
keys.forEach((key) => {
let value = changedFields[key];
form.validateFields([key]).then(() => {
switch (key) {
case 'httpProxy':
value = {
...settings.httpProxy,
enable: value
};
dispatch(setSetting<'httpProxy'>(key, value))
setHttpAndHttpsProxy({ ...value, type: 'http', proxyPort: settings.localPort });
return;
case 'httpProxyPort':
value = {
...settings.httpProxy,
port: value
};
dispatch(setSetting<'httpProxy'>('httpProxy', value))
setHttpAndHttpsProxy({ ...value, type: 'http', proxyPort: settings.localPort });
return;
case 'acl':
dispatch(setSetting<'acl'>(key, {
...settings.acl,
text: value
}));
return;
case 'autoLaunch':
dispatch<any>(setStartupOnBoot(value));
return;
case 'darkMode':
dispatchEvent({
type: 'theme:update',
payload: {
shouldUseDarkColors: value
}
});
break;
default:
break;
}
dispatch(setSetting<any>(key, value));
}).catch((reason: { errorFields: { errors: string[] }[] }) => {
enqueueSnackbar(reason?.errorFields?.map(item => item.errors.join()).join(), { variant: 'error' });
});
});
}
return (
<Container className={styles.container}>
<Form
form={form}
initialValues={
{
localPort: settings.localPort,
pacPort: settings.pacPort,
gfwListUrl: settings.gfwListUrl,
httpProxy: settings.httpProxy.enable,
httpProxyPort: settings.httpProxy.port,
autoLaunch: settings.autoLaunch,
fixedMenu: settings.fixedMenu,
darkMode: settings.darkMode,
autoTheme: settings.autoTheme,
verbose: settings.verbose,
autoHide: settings.autoHide,
}
}
onValuesChange={onFieldChange}
>
<Field
name="localPort"
rules={[
{ required: true, message: t('invalid_value') },
{ validator: checkPortField },
]}
normalize={(value: string) => +(value.trim())}
validateTrigger={false}
>
<TextField
className={styles.textField}
required
fullWidth
size="small"
type="number"
label={t('local_port')}
placeholder={t('local_port_tips')}
/>
</Field>
<Field
name="pacPort"
rules={[
{ required: true, message: t('invalid_value') },
{ validator: checkPortField }
]}
normalize={(value: string) => +(value.trim())}
validateTrigger={false}
>
<TextField
className={styles.textField}
required
fullWidth
type="number"
size="small"
label={t('pac_port')}
placeholder={t('pac_port_tips')}
/>
</Field>
<Field
name="gfwListUrl"
validateTrigger={false}
>
<TextField
className={styles.textField}
required
fullWidth
type="url"
size="small"
label={
<TextWithTooltip
text={t('gfwlist_url')}
icon={
<span>
<Tooltip arrow placement="top" title={t('recover_pac_file_with_link') as string}>
<RestorePage className={styles.cursorPointer} onClick={reGeneratePacFileWithUrl} />
</Tooltip>
<Tooltip arrow placement="top" title={t('recover_pac_file_with_file') as string}>
<NoteAdd className={styles.cursorPointer} onClick={reGeneratePacFileWithFile}/>
</Tooltip>
</span>
}
/>
}
placeholder={t('gfwlist_url_tips')}
/>
</Field>
<input onChange={onGFWListFileChange} ref={inputFileRef} type={'file'} multiple={false} style={{ display: 'none' }}></input>
<List className={styles.list}>
<ListItem>
<ListItemText
primary={t('http_proxy')}
/>
<ListItemSecondaryAction>
<Field name="httpProxy" valuePropName="checked">
<AdaptiveSwitch
edge="end"
/>
</Field>
</ListItemSecondaryAction>
</ListItem>
{
settings.httpProxy.enable && (
<ListItem>
<ListItemText
primary={t('http_proxy_port')}
secondary={t('restart_when_changed')}
/>
<ListItemSecondaryAction>
<Field
name="httpProxyPort"
rules={[
{ required: true, message: t('invalid_value') },
{ validator: checkPortField }
]}
normalize={(value: string) => +(value.trim())}
validateTrigger={false}
>
<TextField
className={`${styles.textField} ${styles.indentInput}`}
required
size="small"
type="number"
placeholder={t('http_proxy_port')}
/>
</Field>
</ListItemSecondaryAction>
</ListItem>
)
}
{/* <ListItem>
<ListItemText
primary={'ACL'}
// secondary="Not applicable to Linux"
/>
<ListItemSecondaryAction>
<AdaptiveSwitch
edge="end"
checked={settings.acl.enable}
onChange={e => handleSwitchValueChange("acl", e)}
/>
</ListItemSecondaryAction>
</ListItem>
{
settings.acl.enable && (
<ListItem>
<ListItemText
primary={t('acl_content')}
/>
<ListItemSecondaryAction>
<TextField
className={`${styles.textField} ${styles.indentInput}`}
style={{ width: '120px', textAlign: 'right' }}
required
size="small"
type="text"
placeholder={t('click_to_edit')}
onClick={() => setAclVisible(true)}
value={'*****'}
/>
</ListItemSecondaryAction>
</ListItem>
)
} */}
<ListItem>
<ListItemText
primary={t('launch_on_boot')}
secondary={t('not_applicable_to_linux_snap_application')}
/>
<ListItemSecondaryAction>
<Field name="autoLaunch" valuePropName="checked">
<AdaptiveSwitch
edge="end"
/>
</Field>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemText
primary={t('fixed_menu')}
/>
<ListItemSecondaryAction>
<Field name="fixedMenu" valuePropName="checked">
<AdaptiveSwitch
edge="end"
/>
</Field>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemText
primary={t('auto_hide')}
secondary={t('minimize_on_start')}
/>
<ListItemSecondaryAction>
<Field name="autoHide" valuePropName="checked">
<AdaptiveSwitch
edge="end"
/>
</Field>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemText
primary={t('autoTheme')}
secondary={t('autoThemeTips')}
/>
<ListItemSecondaryAction>
<Field name="autoTheme" valuePropName="checked">
<AdaptiveSwitch
edge="end"
onChange={onAutoThemeChange}
/>
</Field>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemText
primary={t('darkMode')}
/>
<ListItemSecondaryAction>
<Field name="darkMode" valuePropName="checked">
<AdaptiveSwitch
edge="end"
disabled={settings.autoTheme}
/>
</Field>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemText
primary={'Language'}
/>
<ListItemSecondaryAction>
<Select
value={getDefaultLang()}
onChange={onLangChange}
>
<MenuItem value={'en-US'}>English</MenuItem>
<MenuItem value={'zh-CN'}>中文简体</MenuItem>
</Select>
</ListItemSecondaryAction>
</ListItem>
<ListItem button onClick={backupConfiguration}>
<ListItemText primary={t('backup')} />
</ListItem>
<ListItem button onClick={() => restoreConfiguration()}>
<ListItemText primary={t('restore')} />
</ListItem>
<ListItem button onClick={handleAlertDialogOpen}>
<ListItemText primary={t('reset_data')} />
</ListItem>
<Divider className={styles.margin} />
<ListSubheader>{t('debugging')}</ListSubheader>
<ListItem>
<ListItemText
primary="Verbose"
secondary={t('verbose_output')}
/>
<ListItemSecondaryAction>
<Field name="verbose" valuePropName="checked">
<AdaptiveSwitch
edge="end"
/>
</Field>
</ListItemSecondaryAction>
</ListItem>
<ListItem button onClick={handleOpenLog}>
<ListItemText primary={t('open_log_dir')} />
</ListItem>
<ListItem button onClick={handleOpenProcessManager}>
<ListItemText primary={t('open_process_manager')} />
</ListItem>
</List>
</Form>
{/* dialog */}
{/* <EditAclDialog
open={aclVisible}
onClose={() => setAclVisible(false)}
children={undefined}
onTextChange={handleValueChange}
/> */}
<DialogConfirm onClose={handleAlertDialogClose} onConfirm={handleReset} />
</Container>
);
}
Example #21
Source File: ResponsiveDrawer.tsx From UsTaxes with GNU Affero General Public License v3.0 | 4 votes |
function ResponsiveDrawer(props: DrawerItemsProps): ReactElement {
const classes = useStyles({ isMobile })
const theme = useTheme()
const { sections, isOpen, setOpen } = props
const drawer = (
<>
{/* {isMobile && <Toolbar />} */}
{sections.map(({ title, items }) => (
<Fragment key={`section ${title}`}>
<List
subheader={<ListSubheader disableSticky>{title}</ListSubheader>}
className={classes.list}
>
{items.map((item) => (
<ListItem
button
classes={{}}
key={item.title}
component={NavLink}
selected={location.pathname === item.url}
to={item.url}
>
<ListItemText primary={`${item.title}`} />
</ListItem>
))}
</List>
<Divider />
</Fragment>
))}
<List className={classes.listSocial}>
<ListItem className={classes.listItemSocial}>
<Link to={Urls.help}>
<IconButton color="secondary" aria-label="help, support, feedback">
<HelpOutlineRounded />
</IconButton>
</Link>
<IconButton
color="secondary"
aria-label="github, opens in new tab"
component="a"
href={`https://github.com/ustaxes/UsTaxes`}
target="_blank"
rel="noreferrer noopener"
>
<GitHubIcon />
</IconButton>
</ListItem>
<ListItem className={classes.listItemSocial}>
<IconButton
color="secondary"
aria-label="twitter, opens in new tab"
component="a"
href={`https://www.twitter.com/ustaxesorg`}
target="_blank"
rel="noreferrer noopener"
>
<TwitterIcon />
</IconButton>
</ListItem>
<ListItem className={classes.listItemSocial}>
<Link to={Urls.settings}>
<IconButton color="secondary" aria-label="site user settings">
<Settings />
</IconButton>
</Link>
</ListItem>
</List>
</>
)
return (
<nav className={classes.drawer} aria-label="primary">
<SwipeableDrawer
variant={!isMobile ? 'persistent' : undefined}
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={isOpen}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
classes={{
root: classes.drawerContainer,
paper: classes.drawerPaper
}}
ModalProps={{
BackdropProps: {
classes: { root: classes.drawerBackdrop }
}
// Disabling for the time being due to scroll position persisting
// keepMounted: isMobile ? true : false // Better open performance on mobile.
}}
>
{drawer}
</SwipeableDrawer>
</nav>
)
}