@material-ui/lab#Alert TypeScript Examples
The following examples show how to use
@material-ui/lab#Alert.
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: Beginning.tsx From abacus with GNU General Public License v2.0 | 6 votes |
Beginning = (): JSX.Element => {
const classes = useStyles()
return (
<div className={classes.root}>
<Typography variant='h4' gutterBottom>
Design and Document Your Experiment
</Typography>
<Typography variant='body2'>
We think one of the best ways to prevent a failed experiment is by documenting what you hope to learn.{/* */}
<br />
<br />
</Typography>
<Alert severity='info'>
<Link underline='always' href='https://github.com/Automattic/experimentation-platform/wiki' target='_blank'>
Our wiki is a great place to start
</Link>
, it will instruct you on creating a P2 post.
</Alert>
<Field
className={classes.p2EntryField}
component={TextField}
id='experiment.p2Url'
name='experiment.p2Url'
placeholder='https://your-p2-post-here'
label={`Your Post's URL`}
variant='outlined'
InputLabelProps={{
shrink: true,
}}
/>
</div>
)
}
Example #2
Source File: App.tsx From clarity with Apache License 2.0 | 6 votes |
Alerts = observer((props: AppProps) => {
if (props.errors.lastError == null) return null;
// Not using the `data-dismiss="alert"` to dismiss via Bootstrap JS
// becuase then it doesn't re-render when there's a new error.
return (
<div id="alert-message">
<Alert severity="error" onClose={() => props.errors.dismissLast()}>
<AlertTitle>Error!</AlertTitle>
{props.errors.lastError}
</Alert>
</div>
);
})
Example #3
Source File: BitriseArtifactsComponent.tsx From backstage with Apache License 2.0 | 6 votes |
BitriseArtifactsComponent = (
props: BitriseArtifactsComponentComponentProps,
) => {
const { value, loading, error } = useBitriseArtifacts(
props.build.appSlug,
props.build.buildSlug,
);
if (loading) {
return <Progress />;
} else if (error) {
return <Alert severity="error">{error.message}</Alert>;
} else if (!value || !value.length) {
return <Alert severity="info">No artifacts</Alert>;
}
return (
<List>
{value.map(row => (
<ListItem key={row.slug}>
<ListItemText primary={row.title} secondary={row.artifact_type} />
<ListItemSecondaryAction>
<BitriseDownloadArtifactComponent
appSlug={props.build.appSlug}
buildSlug={props.build.buildSlug}
artifactSlug={row.slug}
/>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
);
}
Example #4
Source File: snack.tsx From bitpay-browser-extension with MIT License | 6 votes |
Snack: React.FC<{ message: string; onClose: () => void }> = ({ message, onClose }) => (
<div className="snack">
<Snackbar style={position} open={!!message} autoHideDuration={6000} anchorOrigin={anchorOrigin} onClose={onClose}>
<Alert
style={menu}
severity="error"
action={
<IconButton aria-label="close" color="inherit" size="small" style={icon} onClick={onClose}>
<img src="/assets/icons/close.svg" alt="close" />
</IconButton>
}
>
<AlertTitle style={title}>Please Try Again!</AlertTitle>
{message}
</Alert>
</Snackbar>
</div>
)
Example #5
Source File: App.tsx From signer with Apache License 2.0 | 6 votes |
Alerts = observer((props: AppProps) => {
if (props.errors.lastError == null) return null;
// Not using the `data-dismiss="alert"` to dismiss via Bootstrap JS
// becuase then it doesn't re-render when there's a new error.
return (
<div id="alert-message">
<Alert severity="error" onClose={() => props.errors.dismissLast()}>
<AlertTitle>Error!</AlertTitle>
{props.errors.lastError}
</Alert>
</div>
);
})
Example #6
Source File: Message.tsx From clearflask with Apache License 2.0 | 6 votes |
render() {
return (
<Alert
className={classNames(this.props.className, this.props.classes.alert)}
variant={this.props.variant || 'outlined'}
style={this.props.innerStyle}
severity={this.props.severity}
action={this.props.action}
>
{this.props.message}
</Alert>
);
}
Example #7
Source File: AlertUploadSize.tsx From bee-dashboard with BSD 3-Clause "New" or "Revised" License | 6 votes |
export default function UploadSizeAlert(props: Props): ReactElement | null {
const classes = useStyles()
const totalSize = props.files.reduce((previous, current) => previous + current.size, 0)
const aboveLimit = totalSize >= LIMIT
return (
<Collapse in={aboveLimit}>
<div className={classes.root}>
<Alert severity="warning">
<AlertTitle>Warning</AlertTitle>
The files you are trying to upload are above the recommended size. The chunks may not be synchronised properly
over the network.
</Alert>
</div>
</Collapse>
)
}
Example #8
Source File: IndexScene.tsx From log4brains with Apache License 2.0 | 6 votes |
export function IndexScene({ projectName, markdown }: IndexSceneProps) {
const classes = useStyles();
const mode = React.useContext(Log4brainsModeContext);
const previewAlert =
mode === Log4brainsMode.preview ? (
<Alert
severity="warning"
className={classes.previewAlert}
classes={{ message: classes.previewAlertMessage }}
>
<Typography variant="h6">Preview mode</Typography>
<Typography variant="body2">
Hot Reload is enabled on all pages
</Typography>
</Alert>
) : null;
return (
<>
<Head>
<title>Architecture knowledge base of {projectName}</title>
<meta
name="description"
content={`This architecture knowledge base contains all the Architecture Decision Records (ADR) of the ${projectName} project`}
/>
</Head>
<TwoColContent rightColContent={previewAlert}>
<Markdown>{markdown}</Markdown>
</TwoColContent>
</>
);
}
Example #9
Source File: TermsAndConditions.tsx From twilio-voice-notification-app with Apache License 2.0 | 6 votes |
TermsAndConditions: React.FC = () => (
<Alert severity="info">
<strong>Reminder </strong>- Please note you have agreed to the{' '}
<Link href="https://www.twilio.com/legal/tos">
Twilio Terms and Service
</Link>{' '}
and{' '}
<Link href="https://www.twilio.com/legal/aup">
Twilio Acceptable Use Policy
</Link>
, as incorporated therein.
</Alert>
)
Example #10
Source File: DataPropagator.tsx From UsTaxes with GNU Affero General Public License v3.0 | 6 votes |
DataPropagator = (): ReactElement => {
const wholeState = useSelector((state: YearsTaxesState) => state)
const allYears = enumKeys(TaxYears)
const yearIndex = _.indexOf(allYears, wholeState.activeYear)
const dispatch = useDispatch()
const currentYear: Information = wholeState[wholeState.activeYear]
const priorYear: Information = wholeState[allYears[yearIndex - 1]]
const canPropagate =
yearIndex > 0 &&
currentYear.taxPayer.primaryPerson?.firstName === undefined &&
priorYear.taxPayer.primaryPerson?.firstName !== undefined
const onClick = () => {
if (canPropagate) {
dispatch(setInfo(priorYear))
}
}
if (canPropagate) {
return (
<div>
<Alert severity="info">
<p>
You have data from the prior tax year but no data for the current
tax year. Would you like to migrate your data from the prior year?
</p>
<Button onClick={onClick} variant="contained" color="primary">
Migrate
</Button>
</Alert>
</div>
)
}
return <></>
}
Example #11
Source File: HashLengthExtension.tsx From DamnVulnerableCryptoApp with MIT License | 5 votes |
HashLengthExtension = (props: IChallengeProps) => {
const layoutContext = useContext(LayoutContext);
const [message, setMessage] = useState("");
const [severity, setSeverity] = useState<Color>("success");
const [data, setData] = useState("");
useEffect(() => {
layoutContext.setLoading(true);
const authData = HashLengthExtensionService.getData();
authData.then(d => {
setData(d.data);
HashLengthExtensionService.check(d).then(f => {
props.setFlag(f.flag);
layoutContext.setLoading(false);
if (f.tampered) {
setMessage("DamnVulnerableCryptoApp sadly informs you that the data was manipulated");
setSeverity("error");
}
else {
setMessage("DamnVulnerableCryptoApp certifies that the data was not tampered");
setSeverity("success");
}
}).catch(err => {
layoutContext.setSnackErrorMessage("Some error message");
layoutContext.setLoading(false);
});
}).catch(err => {
layoutContext.setSnackErrorMessage("Some error message");
layoutContext.setLoading(false);
});
}, []);
const classes = useStyles();
return (
<Box >
<Box className={classes.header} textAlign="center">
<AccountBalanceIcon className={classes.iconSize} />
<Typography variant="h2">Integrity Checker</Typography>
</Box>
<Alert severity={severity} className={classes.alert}>
{message}
</Alert>
<Box className={classes.dataContainer}>
<Typography>Your Data:</Typography>
<Typography className={classes.data}>{data}</Typography>
</Box>
</Box>
);
}
Example #12
Source File: index.tsx From prism-frontend with MIT License | 5 votes |
function Notifier({ classes }: NotifierProps) {
const dispatch = useDispatch();
const notifications = useSelector(notificationsSelector);
const [topOffset, setTopOffset] = useState(65);
// make sure the notifications don't overlap the nav bar.
useEffect(() => {
const toolbar = document.getElementsByClassName('MuiToolbar-root')[0];
function handleResize() {
if (!toolbar) {
return;
}
setTopOffset(toolbar.clientHeight + 15);
}
// try make sure the toolbar is available to use on first run, not too dangerous if it doesn't work - default value is good for most screens.
setTimeout(handleResize, 500);
window.addEventListener('resize', handleResize);
// cleanup
return () => window.removeEventListener('resize', handleResize);
}, []);
const handleClose = (notification: Notification) => () => {
dispatch(removeNotification(notification.key));
};
return (
<div className={classes.notificationsContainer} style={{ top: topOffset }}>
{notifications.map(notification => {
return (
<Alert
variant="filled"
severity={notification.type}
key={notification.key}
onClose={handleClose(notification)}
className={classes.alert}
>
{notification.message}
</Alert>
);
})}
</div>
);
}
Example #13
Source File: index.tsx From aqualink-app with MIT License | 5 votes |
StatusSnackbar = ({
open,
message,
furtherActionLabel,
severity,
handleClose,
onFurtherActionTake,
}: StatusSnackbarProps) => {
const classes = useStyles(!!message);
return message ? (
<Snackbar
className={classes.snackbar}
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
<Alert
className={classes.alert}
variant="filled"
onClose={handleClose}
severity={severity}
classes={{ message: classes.alertMessage }}
>
{message}
{furtherActionLabel && onFurtherActionTake && (
<Button
size="small"
className={classes.button}
onClick={onFurtherActionTake}
>
{furtherActionLabel}
</Button>
)}
</Alert>
</Snackbar>
) : null;
}
Example #14
Source File: AlertDisplay.tsx From backstage with Apache License 2.0 | 5 votes |
/** @public */
export function AlertDisplay(props: AlertDisplayProps) {
const [messages, setMessages] = useState<Array<AlertMessage>>([]);
const alertApi = useApi(alertApiRef);
const { anchorOrigin = { vertical: 'top', horizontal: 'center' } } = props;
useEffect(() => {
const subscription = alertApi
.alert$()
.subscribe(message => setMessages(msgs => msgs.concat(message)));
return () => {
subscription.unsubscribe();
};
}, [alertApi]);
if (messages.length === 0) {
return null;
}
const [firstMessage] = messages;
const handleClose = () => {
setMessages(msgs => msgs.filter(msg => msg !== firstMessage));
};
return (
<Snackbar open anchorOrigin={anchorOrigin}>
<Alert
action={
<IconButton
color="inherit"
size="small"
onClick={handleClose}
data-testid="error-button-close"
>
<CloseIcon />
</IconButton>
}
severity={firstMessage.severity}
>
<span>
{firstMessage.message.toString()}
{messages.length > 1 && (
<em>{` (${messages.length - 1} older ${pluralize(
'message',
messages.length - 1,
)})`}</em>
)}
</span>
</Alert>
</Snackbar>
);
}
Example #15
Source File: UpgradeWrapper.tsx From clearflask with Apache License 2.0 | 5 votes |
UpgradeCover = (props: {
children: any,
overrideUpgradeMsg?: string,
}) => {
const classes = useStyles();
const [clicked, setClicked] = useState<boolean>();
const [hovered, setHovered] = useState<boolean>();
const showWarning = !!clicked || !!hovered;
return (
<div className={classes.outer}
onClick={e => setClicked(true)}
onMouseEnter={e => setHovered(true)}
onMouseLeave={e => setHovered(false)}
>
<Alert
className={classNames(
classes.warning,
!showWarning && classes.hidden,
)}
variant='outlined'
severity='info'
action={(
<Button component={Link} to='/dashboard/billing'>Plans</Button>
)}
>
{props.overrideUpgradeMsg || 'Plan upgrade required'}
</Alert>
<div
className={classNames(
classes.children,
showWarning && classes.partiallyHidden,
)}
>
{props.children}
</div>
</div>
);
}
Example #16
Source File: index.tsx From back-home-safe with GNU General Public License v3.0 | 5 votes |
Login = () => {
const { t } = useTranslation("login");
const [password, setPassword] = useState("");
const { unlockStore } = useData();
const [showPasswordError, setShowPasswordError] = useState(false);
const handleLogin = () => {
const success = unlockStore(password);
if (!success) {
setShowPasswordError(true);
setPassword("");
}
};
return (
<PageWrapper>
<Wrapper>
<Unlock />
<div>{t("message.please_input_password")}</div>
<InputWrapper
onSubmit={(e) => {
e.preventDefault();
handleLogin();
}}
>
<TextField
type="password"
autoComplete="current-password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
<ButtonWrapper>
<Button
variant="contained"
color="secondary"
disabled={isEmpty(password)}
type="submit"
>
{t("global:button.unlock")}
</Button>
</ButtonWrapper>
</InputWrapper>
<Button onClick={clearAllData}>{t("button.reset")}</Button>
</Wrapper>
<Snackbar
open={showPasswordError}
autoHideDuration={2000}
onClose={() => {
setShowPasswordError(false);
}}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
>
<Alert elevation={6} variant="filled" severity="error">
{t("message.wrong_password")}
</Alert>
</Snackbar>
</PageWrapper>
);
}
Example #17
Source File: SelectWorkflow.tsx From github-deploy-center with MIT License | 5 votes |
export function SelectWorkflow({
workflowId,
onChange,
FormControlProps = {},
}: {
workflowId: number
onChange: (workflowId: number) => void
FormControlProps?: FormControlProps
}) {
const workflows = useFetchWorkflows()
const { selectedApplication } = useAppState()
if (!selectedApplication) return null
if (workflows.error) {
return <Alert severity="error">Could not load workflows</Alert>
}
const workflowsSorted = (workflows.data ?? []).orderBy(
[
(workflow) => {
const containsName = workflow.name
.toLowerCase()
.includes(selectedApplication.name.toLowerCase().split(' ')[0])
const containsDeploy = workflow.name.toLowerCase().includes('deploy')
return containsDeploy && containsName
? WorkflowRelevance.NameAndDeploy
: containsName
? WorkflowRelevance.Name
: containsDeploy
? WorkflowRelevance.Deploy
: WorkflowRelevance.None
},
(w) => w.name,
],
['desc', 'asc']
)
return (
<FormControl variant="outlined" {...FormControlProps}>
<InputLabel id="workflow-select-label">Workflow</InputLabel>
{workflows.isLoading ? (
<CircularProgress />
) : workflows.data ? (
<Select
labelId="workflow-select-label"
id="workflow-select"
value={workflowId}
label="Workflow"
onChange={(e) => {
const workflowId =
typeof e.target.value === 'number'
? (e.target.value as number)
: 0
onChange(workflowId)
}}>
<MenuItem value={0}>
<em>None</em>
</MenuItem>
{workflowsSorted.map((workflow) => (
<MenuItem key={workflow.id} value={workflow.id}>
{workflow.name}
</MenuItem>
))}
</Select>
) : null}
</FormControl>
)
}
Example #18
Source File: AddTodo.tsx From max-todos with MIT License | 5 votes |
AddTodo: FC<{ addTodo: (text: string) => void }> = ({ addTodo }) => {
const [text, setText] = useState("");
const [open, setOpen] = useState(false);
const handleChange = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => setText(e.target.value);
const createTodo = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
addTodo(text);
setText("");
if (text.trim()) setOpen(true);
};
return (
<div>
<Container maxWidth="sm">
<form onSubmit={createTodo} className="add-todo">
<FormControl fullWidth={true}>
<TextField
label="I will do this"
variant="standard"
onChange={handleChange}
required={true}
value={text}
/>
<Button
variant="contained"
color="primary"
style={{ marginTop: 5 }}
type="submit"
>
<Add />
Add
</Button>
</FormControl>
</form>
</Container>
<Snackbar
open={open}
autoHideDuration={4000}
onClose={() => setOpen(false)}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert
// icon={<Check fontSize="inherit" />}
elevation={6}
variant="filled"
onClose={() => setOpen(false)}
severity="success"
>
Successfully added item!
</Alert>
</Snackbar>
</div>
);
}
Example #19
Source File: PageError.tsx From knboard with MIT License | 5 votes |
PageError = ({ children }: Props) => (
<Container>
<Alert severity="warning" variant="outlined">
{children}
</Alert>
</Container>
)
Example #20
Source File: styles.ts From twilio-voice-notification-app with Apache License 2.0 | 5 votes |
StyledAlert = withStyles(() => ({
root: {
marginTop: '1.5rem',
},
}))(Alert)
Example #21
Source File: HelpAndFeedback.tsx From UsTaxes with GNU Affero General Public License v3.0 | 5 votes |
HelpAndFeedback = (): ReactElement => {
const state = useSelector((state: USTState) => state)
const [anonymizedState, setAnonymizedState] = useState('')
const [copied, doSetCopied] = useState(false)
useEffect(() => {
setAnonymizedState(JSON.stringify(anonymize(state), undefined, 2))
}, [state])
const setCopied = (): void => {
doSetCopied(true)
setTimeout(() => doSetCopied(false), 5000)
}
const copiedAlert = (() => {
if (copied) {
return (
<Grid item>
<Alert severity="info">Copied to clipboard</Alert>
</Grid>
)
}
})()
const copyToClipboard = async () => {
await navigator.clipboard.writeText(anonymizedState)
setCopied()
}
return (
<>
<h2>Help and Feedback</h2>
<p>Did you notice something wrong?</p>
<p>
Please email <strong>[email protected]</strong> with any questions or
bugs. If your personal data might be helpful, please copy paste the
below into the body of the email. Your data below should be properly
anonymized.
</p>
<Grid container spacing={2} direction="column">
<Grid item>
<Button
variant="contained"
color="primary"
onClick={intentionallyFloat(copyToClipboard)}
>
Copy to clipboard
</Button>
</Grid>
{copiedAlert}
<Grid item>
<TextField
disabled
multiline
minRows={40}
maxRows={100}
fullWidth
value={JSON.stringify(anonymize(state), null, 2)}
/>
</Grid>
</Grid>{' '}
</>
)
}
Example #22
Source File: ExperimentResults.tsx From abacus with GNU General Public License v2.0 | 4 votes |
/**
* Render the latest analyses for the experiment for each metric assignment as a single condensed table, using only
* the experiment's default analysis strategy.
*/
export default function ExperimentResults({
analyses,
experiment,
metrics,
}: {
analyses: Analysis[]
experiment: ExperimentFull
metrics: Metric[]
debugMode?: boolean
}): JSX.Element {
const classes = useStyles()
const theme = useTheme()
const availableAnalysisStrategies = [
AnalysisStrategy.IttPure,
AnalysisStrategy.MittNoCrossovers,
AnalysisStrategy.MittNoSpammers,
AnalysisStrategy.MittNoSpammersNoCrossovers,
]
if (experiment.exposureEvents) {
availableAnalysisStrategies.push(AnalysisStrategy.PpNaive)
}
const [strategy, setStrategy] = useState<AnalysisStrategy>(() => Experiments.getDefaultAnalysisStrategy(experiment))
const onStrategyChange = (event: React.ChangeEvent<{ value: unknown }>) => {
setStrategy(event.target.value as AnalysisStrategy)
}
const baseVariationId = experiment.variations.find((v) => v.isDefault)?.variationId
const changeVariationId = experiment.variations.find((v) => !v.isDefault)?.variationId
// istanbul ignore next; Shouldn't occur.
if (!baseVariationId || !changeVariationId) {
throw new Error('Missing base or change variations.')
}
const variationDiffKey = `${changeVariationId}_${baseVariationId}`
const indexedMetrics = indexMetrics(metrics)
const analysesByMetricAssignmentId = _.groupBy(analyses, 'metricAssignmentId')
const allMetricAssignmentAnalysesData: MetricAssignmentAnalysesData[] = MetricAssignments.sort(
experiment.metricAssignments,
).map((metricAssignment) => {
const metricAssignmentAnalyses = analysesByMetricAssignmentId[metricAssignment.metricAssignmentId] || []
return {
metricAssignment,
metric: indexedMetrics[metricAssignment.metricId],
analysesByStrategyDateAsc: _.groupBy(
_.orderBy(metricAssignmentAnalyses, ['analysisDatetime'], ['asc']),
'analysisStrategy',
) as Record<AnalysisStrategy, Analysis[]>,
}
})
const metricAssignmentSummaryData = allMetricAssignmentAnalysesData.map(
({ metricAssignment, metric, analysesByStrategyDateAsc }) => ({
experiment,
strategy,
metricAssignment,
metric,
analysesByStrategyDateAsc,
recommendation: Recommendations.getAggregateMetricAssignmentRecommendation(
Object.values(analysesByStrategyDateAsc)
.map(_.last.bind(null))
.filter((x) => x)
.map((analysis) =>
Recommendations.getMetricAssignmentRecommendation(
experiment,
metric,
analysis as Analysis,
variationDiffKey,
),
),
strategy,
),
}),
)
// ### Result Summary Visualizations
const primaryMetricAssignmentAnalysesData = allMetricAssignmentAnalysesData.find(
({ metricAssignment: { isPrimary } }) => isPrimary,
) as MetricAssignmentAnalysesData
const primaryAnalyses = primaryMetricAssignmentAnalysesData.analysesByStrategyDateAsc[strategy] || []
const dates = primaryAnalyses.map(({ analysisDatetime }) => analysisDatetime.toISOString())
const plotlyDataParticipantGraph: Array<Partial<PlotData>> = [
..._.flatMap(experiment.variations, (variation, index) => {
const variationKey = `variation_${variation.variationId}`
return [
{
name: `${variation.name}`,
x: dates,
y: primaryAnalyses.map(({ participantStats: { [variationKey]: variationCount } }) => variationCount),
line: {
color: Visualizations.variantColors[index],
},
mode: 'lines+markers' as const,
type: 'scatter' as const,
},
]
}),
]
// ### Top Level Stats
const primaryMetricLatestAnalysesByStrategy = _.mapValues(
primaryMetricAssignmentAnalysesData.analysesByStrategyDateAsc,
_.last.bind(null),
)
const latestPrimaryMetricAnalysis = primaryMetricLatestAnalysesByStrategy[strategy]
// istanbul ignore next; trivial
const totalParticipants = latestPrimaryMetricAnalysis?.participantStats['total'] ?? 0
const primaryMetricRecommendation = Recommendations.getAggregateMetricAssignmentRecommendation(
Object.values(primaryMetricLatestAnalysesByStrategy)
.filter((x) => x)
.map((analysis) =>
Recommendations.getMetricAssignmentRecommendation(
experiment,
primaryMetricAssignmentAnalysesData.metric,
analysis as Analysis,
variationDiffKey,
),
),
strategy,
)
// We check if there are any analyses at all to show as we want to show what we can to the Experimenter:
const hasAnalyses = allMetricAssignmentAnalysesData.some(
(x) => Object.values(x.analysesByStrategyDateAsc).filter((y) => y).length > 0,
)
const experimentParticipantStats = Analyses.getExperimentParticipantStats(
experiment,
primaryMetricLatestAnalysesByStrategy,
)
const experimentHealthIndicators = [
...Analyses.getExperimentParticipantHealthIndicators(experimentParticipantStats),
...Analyses.getExperimentHealthIndicators(experiment),
]
const maxIndicationSeverity = experimentHealthIndicators
.map(({ indication: { severity } }) => severity)
.sort(
(severityA, severityB) =>
Analyses.healthIndicationSeverityOrder.indexOf(severityB) -
Analyses.healthIndicationSeverityOrder.indexOf(severityA),
)[0]
const maxIndicationSeverityMessage = {
[Analyses.HealthIndicationSeverity.Ok]: 'No issues detected',
[Analyses.HealthIndicationSeverity.Warning]: 'Potential issues',
[Analyses.HealthIndicationSeverity.Error]: 'Serious issues',
}
// ### Metric Assignments Table
const tableColumns = [
{
title: 'Metric (attribution window)',
render: ({ metric, metricAssignment }: { metric: Metric; metricAssignment: MetricAssignment }) => (
<>
<span className={classes.metricAssignmentNameLine}>
<Tooltip title={metric.description}>
<span>{metric.name}</span>
</Tooltip>
({AttributionWindowSecondsToHuman[metricAssignment.attributionWindowSeconds]})
</span>
{metricAssignment.isPrimary && (
<>
<br />
<Attribute name='primary' />
</>
)}
</>
),
cellStyle: {
fontFamily: theme.custom.fonts.monospace,
fontWeight: 600,
minWidth: 450,
},
},
{
title: 'Absolute change',
render: ({
metric,
strategy,
analysesByStrategyDateAsc,
recommendation,
}: {
metric: Metric
strategy: AnalysisStrategy
analysesByStrategyDateAsc: Record<AnalysisStrategy, Analysis[]>
recommendation: Recommendations.Recommendation
}) => {
const latestEstimates = _.last(analysesByStrategyDateAsc[strategy])?.metricEstimates
if (
!latestEstimates ||
recommendation.decision === Recommendations.Decision.ManualAnalysisRequired ||
recommendation.decision === Recommendations.Decision.MissingAnalysis
) {
return null
}
return (
<MetricValueInterval
intervalName={'the absolute change between variations'}
isDifference={true}
metricParameterType={metric.parameterType}
bottomValue={latestEstimates.diffs[variationDiffKey].bottom_95}
topValue={latestEstimates.diffs[variationDiffKey].top_95}
displayTooltipHint={false}
/>
)
},
cellStyle: {
fontFamily: theme.custom.fonts.monospace,
},
},
{
title: 'Relative change (lift)',
render: ({
strategy,
analysesByStrategyDateAsc,
recommendation,
}: {
metric: Metric
strategy: AnalysisStrategy
analysesByStrategyDateAsc: Record<AnalysisStrategy, Analysis[]>
recommendation: Recommendations.Recommendation
}) => {
const latestEstimates = _.last(analysesByStrategyDateAsc[strategy])?.metricEstimates
if (
!latestEstimates?.ratios[variationDiffKey]?.top_95 ||
recommendation.decision === Recommendations.Decision.ManualAnalysisRequired ||
recommendation.decision === Recommendations.Decision.MissingAnalysis
) {
return null
}
return (
<MetricValueInterval
intervalName={'the relative change between variations'}
metricParameterType={MetricParameterType.Conversion}
bottomValue={Analyses.ratioToDifferenceRatio(latestEstimates.ratios[variationDiffKey].bottom_95)}
topValue={Analyses.ratioToDifferenceRatio(latestEstimates.ratios[variationDiffKey].top_95)}
displayTooltipHint={false}
/>
)
},
cellStyle: {
fontFamily: theme.custom.fonts.monospace,
},
},
{
title: 'Analysis',
render: ({
experiment,
recommendation,
}: {
experiment: ExperimentFull
recommendation: Recommendations.Recommendation
}) => {
return <AnalysisDisplay {...{ experiment, analysis: recommendation }} />
},
cellStyle: {
fontFamily: theme.custom.fonts.monospace,
},
},
]
const DetailPanel = [
({
strategy,
analysesByStrategyDateAsc,
metricAssignment,
metric,
recommendation,
}: {
strategy: AnalysisStrategy
analysesByStrategyDateAsc: Record<AnalysisStrategy, Analysis[]>
metricAssignment: MetricAssignment
metric: Metric
recommendation: Recommendations.Recommendation
}) => {
let disabled = recommendation.decision === Recommendations.Decision.ManualAnalysisRequired
// istanbul ignore next; debug only
disabled = disabled && !isDebugMode()
return {
render: () => (
<MetricAssignmentResults
{...{
strategy,
analysesByStrategyDateAsc,
metricAssignment,
metric,
experiment,
recommendation,
variationDiffKey,
}}
/>
),
disabled,
}
},
]
return (
<div className='analysis-latest-results'>
<div className={classes.root}>
{hasAnalyses ? (
<>
{experiment.variations.length > 2 && (
<>
<Alert severity='error'>
<strong>A/B/n analysis is an ALPHA quality feature.</strong>
<br />
<br />
<strong>What's not working:</strong>
<ul>
<li> Metric Assignment Table (Absolute/Relative Change, Analysis.) </li>
<li> Summary text. </li>
<li> Recommendations. </li>
<li> Difference charts. </li>
<li> The health report. </li>
</ul>
<strong>What's working:</strong>
<ul>
<li> Participation stats and charts. </li>
<li> Analysis tables (when you open a metric assignment.)</li>
<li> Variation value charts. </li>
<li> Observed data. </li>
</ul>
</Alert>
<br />
</>
)}
<div className={classes.summary}>
<Paper className={classes.participantsPlotPaper}>
<Typography variant='h3' gutterBottom>
Participants by Variation
</Typography>
<Plot
layout={{
...Visualizations.plotlyLayoutDefault,
margin: {
l: theme.spacing(4),
r: theme.spacing(2),
t: 0,
b: theme.spacing(6),
},
}}
data={plotlyDataParticipantGraph}
className={classes.participantsPlot}
/>
</Paper>
<div className={classes.summaryColumn}>
<Paper className={classes.summaryStatsPaper}>
{latestPrimaryMetricAnalysis && (
<>
<div className={classes.summaryStatsPart}>
<Typography variant='h3' className={classes.summaryStatsStat} color='primary'>
{totalParticipants.toLocaleString('en', { useGrouping: true })}
</Typography>
<Typography variant='subtitle1'>
<strong>analyzed participants</strong> as at{' '}
{formatIsoDate(latestPrimaryMetricAnalysis.analysisDatetime)}
</Typography>
</div>
<div className={classes.summaryStatsPart}>
<Typography variant='h3' className={classes.summaryStatsStat} color='primary'>
<DeploymentRecommendation {...{ experiment, analysis: primaryMetricRecommendation }} />
</Typography>
<Typography variant='subtitle1'>
<strong>primary metric</strong> recommendation
</Typography>
</div>
</>
)}
</Paper>
<Paper
className={clsx(
classes.summaryHealthPaper,
classes[indicationSeverityClassSymbol(maxIndicationSeverity)],
)}
component='a'
// @ts-ignore: Component extensions aren't appearing in types.
href='#health-report'
>
<div className={classes.summaryStats}>
<Typography variant='h3' className={clsx(classes.summaryStatsStat)} color='primary'>
{maxIndicationSeverityMessage[maxIndicationSeverity]}
</Typography>
<Typography variant='subtitle1'>
see <strong>health report</strong>
</Typography>
</div>
</Paper>
</div>
</div>
<Typography variant='h3' className={classes.tableTitle}>
Metric Assignment Results
</Typography>
<MaterialTable
columns={tableColumns}
data={metricAssignmentSummaryData}
options={createStaticTableOptions(metricAssignmentSummaryData.length)}
onRowClick={(_event, rowData, togglePanel) => {
const { recommendation } = rowData as {
recommendation: Recommendations.Recommendation
}
let disabled = recommendation.decision === Recommendations.Decision.ManualAnalysisRequired
// istanbul ignore next; debug only
disabled = disabled && !isDebugMode()
// istanbul ignore else; trivial
if (togglePanel && !disabled) {
togglePanel()
}
}}
detailPanel={DetailPanel}
/>
<Typography variant='h3' className={classes.tableTitle}>
Health Report
</Typography>
<Paper id='health-report'>
<HealthIndicatorTable indicators={experimentHealthIndicators} />
</Paper>
</>
) : (
<Paper className={classes.noAnalysesPaper}>
<Typography variant='h3' gutterBottom>
{' '}
No Results{' '}
</Typography>
<Typography variant='body1'>No results are available at the moment, this can be due to:</Typography>
<ul>
<Typography component='li'>
<strong> An experiment being new. </strong> ExPlat can take 24-48 hours for results to process and
become available. Updates are usually released at 06:00 UTC daily.
</Typography>
<Typography component='li'>
<strong> No assignments occuring. </strong> Check the "Early Monitoring" section below to
ensure that assignments are occuring.
</Typography>
</ul>
</Paper>
)}
<div className={classes.accordions}>
{hasAnalyses && (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant='h5'>Advanced - Choose an Analysis Strategy</Typography>
</AccordionSummary>
<AccordionDetails className={classes.accordionDetails}>
<Typography variant='body1'>
Choosing a different analysis strategy is useful for checking the effect of different modelling
decisions on the results:
</Typography>
<ul>
<Typography variant='body1' component='li'>
<strong>All participants:</strong> All the participants are analysed based on their initial
variation assignment. Pure intention-to-treat.
</Typography>
<Typography variant='body1' component='li'>
<strong>Without crossovers:</strong> Same as all participants, but excluding participants that were
assigned to multiple experiment variations before or on the analysis date (aka crossovers). Modified
intention-to-treat.
</Typography>
<Typography variant='body1' component='li'>
<strong>Without spammers:</strong> Same as all participants, but excluding participants that were
flagged as spammers on the analysis date. Modified intention-to-treat.
</Typography>
<Typography variant='body1' component='li'>
<strong>Without crossovers and spammers:</strong> Same as all participants, but excluding both
spammers and crossovers. Modified intention-to-treat.
</Typography>
<Typography variant='body1' component='li'>
<strong>Exposed without crossovers and spammers:</strong> Only participants that triggered one of
the experiment's exposure events, excluding both spammers and crossovers. This analysis
strategy is only available if the experiment has exposure events, while the other four strategies
are used for every experiment. Naive per-protocol.
</Typography>
</ul>
<FormControl>
<InputLabel htmlFor='strategy-selector' id='strategy-selector-label'>
Analysis Strategy:
</InputLabel>
<Select
id='strategy-selector'
labelId='strategy-selector-label'
value={strategy}
onChange={onStrategyChange}
>
{availableAnalysisStrategies.map((strat) => (
<MenuItem key={strat} value={strat}>
{Analyses.AnalysisStrategyToHuman[strat]}
{strat === Experiments.getDefaultAnalysisStrategy(experiment) && ' (recommended)'}
</MenuItem>
))}
</Select>
<FormHelperText>Updates the page data.</FormHelperText>
</FormControl>
</AccordionDetails>
</Accordion>
)}
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant='h5'>Early Monitoring - Live Assignment Event Flow</Typography>
</AccordionSummary>
<AccordionDetails className={classes.accordionDetails}>
<Typography variant='body1'>
For early monitoring, you can run this query in Hue to retrieve unfiltered assignment counts from the
unprocessed tracks queue.
</Typography>
<Typography variant='body1'>
This query should only be used to monitor event flow. The best way to use it is to run it multiple times
and ensure that counts go up and are roughly distributed as expected. Counts may also go down as events
are moved to prod_events every day.
</Typography>
<pre className={classes.pre}>
<code>
{/* (Using a javasript string automatically replaces special characters with html entities.) */}
{`with tracks_counts as (
select
cast(json_extract(eventprops, '$.experiment_variation_id') as bigint) as experiment_variation_id,
count(distinct userid) as unique_users
from kafka_staging.etl_events
where
eventname = 'wpcom_experiment_variation_assigned' and
eventprops like '%"experiment_id":"${experiment.experimentId}"%'
group by cast(json_extract(eventprops, '$.experiment_variation_id') as bigint)
)
select
experiment_variations.name as variation_name,
unique_users
from tracks_counts
inner join wpcom.experiment_variations using (experiment_variation_id)`}
</code>
</pre>
</AccordionDetails>
</Accordion>
</div>
</div>
</div>
)
}
Example #23
Source File: Transfer.tsx From metamask-snap-polkadot with Apache License 2.0 | 4 votes |
Transfer: React.FC<ITransferProps> = ({network, onNewTransferCallback}) => {
const [recipient, setRecipient] = useState<string>("");
const [amount, setAmount] = useState<string | number>("");
const [alert, setAlert] = useState(false);
const [severity, setSeverity] = useState("success" as AlertSeverity);
const [message, setMessage] = useState("");
const [polkascanUrl, setPolkascanUrl] = useState("");
const handleRecipientChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setRecipient(event.target.value);
}, [setRecipient]);
const handleAmountChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setAmount(event.target.value);
}, [setAmount]);
const handleTransactionIncluded = useCallback((tx: TxEventArgument) => {
if (tx.txHash) {
// force new refresh after block information
onNewTransferCallback();
showAlert(
"success",
`Transaction ${tx.txHash} included in block`,
getPolkascanTxUrl(tx.txHash, network)
);
}
}, [network, onNewTransferCallback]);
const handleTransactionFinalized = useCallback((tx: TxEventArgument) => {
if (tx.txHash) {
showAlert(
"success",
`Transaction ${tx.txHash} finalized`,
getPolkascanTxUrl(tx.txHash, network)
);
}
}, [network]);
const showAlert = (severity: AlertSeverity, message: string, polkasacanUrl?: string) => {
setPolkascanUrl(polkasacanUrl ? polkasacanUrl : "");
setSeverity(severity);
setMessage(message);
setAlert(true);
};
const onSubmit = useCallback(async () => {
const provider = await getInjectedMetamaskExtension();
if(provider && provider.signer.signPayload) {
if (amount && recipient) {
const api = await provider.getMetamaskSnapApi();
const convertedAmount = BigInt(amount) * BigInt("1000000000");
const txPayload = await api.generateTransactionPayload(convertedAmount.toString(), recipient);
const signedTx = await provider.signer.signPayload(txPayload.payload);
let tx = await api.send(signedTx.signature, txPayload);
// subscribe to transaction events
const polkadotEventApi = await api.getEventApi();
polkadotEventApi.subscribeToTxStatus(tx.hash, handleTransactionIncluded, handleTransactionFinalized);
// clear fields
setAmount("");
setRecipient("");
// invoke provided callback to inform parent component that new tx is sent
onNewTransferCallback();
} else {
showAlert("error", "Please fill recipient and amount fields.");
}
}
}, [amount, handleTransactionFinalized, handleTransactionIncluded,
recipient, setAmount, setRecipient, onNewTransferCallback]);
return (
<Card>
<CardContent>
<CardHeader title="Transfer"/>
<Grid container alignItems="center" justify="space-between">
<Grid item xs={12}>
<TextField
onChange={handleRecipientChange} size="medium" fullWidth id="recipient" label="Recipient" variant="outlined" value={recipient}>
</TextField>
<Box m="0.5rem"/>
<TextField
InputProps={{startAdornment: <InputAdornment position="start">{`m${getCurrency(network)}`}</InputAdornment>}}
onChange={handleAmountChange} size="medium" fullWidth id="recipient" label="Amount" variant="outlined" value={amount}>
</TextField>
</Grid>
</Grid>
<Box m="0.5rem"/>
<Grid container item xs={12} justify="flex-end">
<Button onClick={onSubmit} color="secondary" variant="contained" size="large">SEND</Button>
</Grid>
<Snackbar
open={alert}
autoHideDuration={6000}
onClose={() => setAlert(false)}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}>
<Alert severity={severity} onClose={() => setAlert(false)}>
{`${message} `}
<Hidden xsUp={polkascanUrl === ""}>
<a href={polkascanUrl}>See details</a>
</Hidden>
</Alert>
</Snackbar>
</CardContent>
</Card>
);
}
Example #24
Source File: Analytics.tsx From backstage-plugin-opsgenie with MIT License | 4 votes |
Analytics = () => {
const configApi = useApi(configApiRef);
const opsgenieApi = useApi(opsgenieApiRef);
const from = moment().subtract(1, 'year').startOf('quarter');
const to = moment();
const { value: data, loading, error } = useAsync(async () => {
return Promise.all([
opsgenieApi.getIncidents({
limit: 100,
query: `createdAt < ${to.valueOf()} AND createdAt > ${from.valueOf()}`
}),
opsgenieApi.getTeams(),
])
});
if (loading) {
return <Progress />;
} else if (error) {
return <Alert severity="error">{error.message}</Alert>;
}
const context: Context = {
from: from,
to: to,
incidents: data![0].filter(incident => moment(incident.impactStartDate).isAfter(from)),
teams: data![1],
};
const businessHours = {
start: configApi.getOptionalNumber('opsgenie.analytics.businessHours.start') || DEFAULT_BUSINESS_HOURS_START,
end: configApi.getOptionalNumber('opsgenie.analytics.businessHours.end') || DEFAULT_BUSINESS_HOURS_END,
};
return (
<Grid container spacing={3}>
<Grid item xs={12}>
<InfoPanel
title="This graphs cover one year worth of incidents, from the current quarter to the same quarter last year."
message={
<ul>
<li>Incidents from {from.format('LL')} to now are used</li>
<li>Business hours are {businessHours.start} to {businessHours.end}</li>
<li>Responders are read from the <code>responders</code> incident extra property if defined, or from the "responders" section of an incident.</li>
</ul>
}
/>
</Grid>
<Grid item md={6} xs={12}>
<WeeklyIncidents context={context} />
</Grid>
<Grid item md={6} xs={12}>
<WeeklyIncidentsSeverity context={context} />
</Grid>
<Grid item md={6} xs={12}>
<WeeklyIncidentsResponders context={context} />
</Grid>
<Grid item md={6} xs={12}>
<MonthlyIncidentsResponders context={context} />
</Grid>
<Grid item md={6} xs={12}>
<QuarterlyIncidentsResponders context={context} />
</Grid>
<Grid item md={6} xs={12}>
<DailyIncidentsResponders context={context} />
</Grid>
<Grid item md={6} xs={12}>
<HourlyIncidents context={context} />
</Grid>
<Grid item md={6} xs={12}>
<DailyIncidents context={context} />
</Grid>
<Grid item md={6} xs={12}>
<WeeklyImpactResponders context={context} />
</Grid>
</Grid>
);
}
Example #25
Source File: Chart.tsx From aqualink-app with MIT License | 4 votes |
Chart = ({
datasets,
site,
pointId,
hideYAxisUnits,
pickerStartDate,
pickerEndDate,
startDate,
endDate,
surveysFiltered,
pickerErrored,
showDatePickers,
onStartDateChange,
onEndDateChange,
classes,
}: ChartProps) => {
const theme = useTheme();
const { hobo: hoboBottomTemperatureRange } =
useSelector(siteTimeSeriesDataRangeSelector)?.bottomTemperature || {};
const { minDate, maxDate } = hoboBottomTemperatureRange?.data?.[0] || {};
const isTimeSeriesDataRangeLoading = useSelector(
siteTimeSeriesDataRangeLoadingSelector
);
const isTimeSeriesDataLoading = useSelector(
siteTimeSeriesDataLoadingSelector
);
const surveys = filterSurveys(
useSelector(surveyListSelector),
"any",
surveysFiltered ? pointId || -1 : -1
);
const hasData = datasets.some(({ displayData }) => displayData);
const loading = isTimeSeriesDataLoading || isTimeSeriesDataRangeLoading;
const success = !pickerErrored && !loading && hasData;
const warning = !pickerErrored && !loading && !hasData;
const minDateLocal = displayTimeInLocalTimezone({
isoDate: minDate,
timeZone: site.timezone,
format: "MM/DD/YYYY",
displayTimezone: false,
});
const maxDateLocal = displayTimeInLocalTimezone({
isoDate: maxDate,
timeZone: site.timezone,
format: "MM/DD/YYYY",
displayTimezone: false,
});
const noDataMessage = () => (
<Box
margin="8px 0"
height="215px"
display="flex"
justifyContent="center"
alignItems="center"
textAlign="center"
color={theme.palette.primary.main}
>
<Typography variant="h2">No data to display</Typography>
</Box>
);
return (
<>
{loading && (
<Box
height="275px"
mt="8px"
mb="8px"
display="flex"
justifyContent="center"
alignItems="center"
>
<CircularProgress size="120px" thickness={1} />
</Box>
)}
{pickerErrored && (
<>
<Box mt="8px">
<Alert severity="error">
<Typography>Start Date should not be after End Date</Typography>
</Alert>
</Box>
{noDataMessage()}
</>
)}
{warning && (
<>
<Box mt="8px">
<Alert severity="warning">
<Typography>
{minDateLocal && maxDateLocal
? `No HOBO data available - data available from ${minDateLocal} to ${maxDateLocal}.`
: "No data available in this time range."}
</Typography>
</Alert>
</Box>
{noDataMessage()}
</>
)}
{success && (
<ChartWithTooltip
className={classes.chart}
datasets={datasets}
siteId={site.id}
hideYAxisUnits={hideYAxisUnits}
surveys={surveys}
temperatureThreshold={null}
maxMonthlyMean={null}
background
chartPeriod={findChartPeriod(startDate, endDate)}
timeZone={site.timezone}
startDate={convertToLocalTime(startDate, site.timezone)}
endDate={convertToLocalTime(endDate, site.timezone)}
showYearInTicks={moreThanOneYear(startDate, endDate)}
fill={false}
/>
)}
{!isTimeSeriesDataRangeLoading && showDatePickers && (
<Grid container justify="center">
<Grid
className={classes.datePickersWrapper}
item
xs={12}
container
justify="space-between"
spacing={1}
>
<Grid item>
<DatePicker
value={pickerStartDate}
dateName="START DATE"
dateNameTextVariant="subtitle1"
pickerSize="small"
autoOk={false}
onChange={onStartDateChange}
timeZone={site.timezone}
/>
</Grid>
<Grid item>
<DatePicker
value={pickerEndDate}
dateName="END DATE"
dateNameTextVariant="subtitle1"
pickerSize="small"
autoOk={false}
onChange={onEndDateChange}
timeZone={site.timezone}
/>
</Grid>
</Grid>
</Grid>
)}
</>
);
}
Example #26
Source File: EntityBazaarInfoContent.tsx From backstage with Apache License 2.0 | 4 votes |
EntityBazaarInfoContent = ({
bazaarProject,
fetchBazaarProject,
}: Props) => {
const classes = useStyles();
const bazaarApi = useApi(bazaarApiRef);
const identity = useApi(identityApiRef);
const [openEdit, setOpenEdit] = useState(false);
const [isMember, setIsMember] = useState(false);
const [openUnlink, setOpenUnlink] = useState(false);
const [members, fetchMembers] = useAsyncFn(async () => {
return bazaarProject
? await fetchProjectMembers(bazaarApi, bazaarProject)
: [];
});
const [userId, fetchUserId] = useAsyncFn(async () => {
return await (
await identity.getProfileInfo()
).displayName;
});
useEffect(() => {
fetchMembers();
fetchUserId();
}, [fetchMembers, fetchUserId]);
useEffect(() => {
if (members.value && userId.value) {
setIsMember(
members.value
?.map((member: Member) => member.userId)
.indexOf(userId.value) >= 0,
);
}
}, [bazaarProject, members, identity, userId.value]);
const handleMembersClick = async () => {
if (userId.value) {
if (!isMember) {
await bazaarApi.addMember(bazaarProject?.id!, userId.value);
} else {
await bazaarApi.deleteMember(bazaarProject!.id, userId.value);
}
setIsMember(!isMember);
fetchMembers();
}
};
const links: IconLinkVerticalProps[] = [
{
label: 'Entity page',
icon: <DashboardIcon />,
disabled: true,
},
{
label: 'Unlink project',
icon: <LinkOffIcon />,
disabled: false,
onClick: () => {
setOpenUnlink(true);
},
},
{
label: isMember ? 'Leave' : 'Join',
icon: isMember ? <ExitToAppIcon /> : <PersonAddIcon />,
href: '',
onClick: async () => {
handleMembersClick();
},
},
{
label: 'Community',
icon: <ChatIcon />,
href: bazaarProject?.community,
disabled: bazaarProject?.community === '' || !isMember,
},
];
const handleEditClose = () => {
setOpenEdit(false);
};
const handleUnlinkClose = () => {
setOpenUnlink(false);
};
const handleUnlinkSubmit = async () => {
const updateResponse = await bazaarApi.updateProject({
...bazaarProject,
entityRef: null,
});
if (updateResponse.status === 'ok') {
handleUnlinkClose();
fetchBazaarProject();
}
};
if (members.error) {
return <Alert severity="error">{members?.error?.message}</Alert>;
}
if (bazaarProject) {
return (
<div>
<EditProjectDialog
bazaarProject={bazaarProject!}
openEdit={openEdit}
handleEditClose={handleEditClose}
fetchBazaarProject={fetchBazaarProject}
/>
{openUnlink && (
<ConfirmationDialog
open={openUnlink}
handleClose={handleUnlinkClose}
message={[
'Are you sure you want to unlink ',
<b className={classes.wordBreak}>
{parseEntityRef(bazaarProject.entityRef!).name}
</b>,
' from ',
<b className={classes.wordBreak}>{bazaarProject.name}</b>,
' ?',
]}
type="unlink"
handleSubmit={handleUnlinkSubmit}
/>
)}
<CardHeader
title={<p className={classes.wordBreak}>{bazaarProject?.name!}</p>}
action={
<div>
<IconButton
onClick={() => {
setOpenEdit(true);
}}
>
<EditIcon />
</IconButton>
</div>
}
subheader={<HeaderIconLinkRow links={links} />}
/>
<Divider />
<CardContentFields
bazaarProject={bazaarProject}
members={members.value || []}
descriptionSize={10}
membersSize={2}
/>
</div>
);
}
return null;
}
Example #27
Source File: AuthContextProvider.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
AuthContextProvider: React.FC = ({ children }) => {
const [authUser, setAuthUser] = useState<AuthUser | null>(null);
const [token, setToken] = usePersistentState<string | null>('token', null);
const [org, setOrg] = usePersistentState<
Organization | OrganizationTag | null
>('organization', null);
const [showAllOrganizations, setShowAllOrganizations] = usePersistentState<
boolean
>('showAllOrganizations', false);
const [feedbackMessage, setFeedbackMessage] = useState<{
message: string;
type: AlertProps['severity'];
} | null>(null);
const cookies = useMemo(() => new Cookies(), []);
const logout = useCallback(async () => {
const shouldReload = !!token;
localStorage.clear();
await Auth.signOut();
cookies.remove('crossfeed-token', {
domain: process.env.REACT_APP_COOKIE_DOMAIN
});
if (shouldReload) {
// Refresh the page only if the token was previously defined
// (i.e. it is now invalid / has expired now).
window.location.reload();
}
}, [cookies, token]);
const handleError = useCallback(
async (e: Error) => {
if (e.message.includes('401')) {
// Unauthorized, log out user
await logout();
}
},
[logout]
);
const api = useApi(handleError);
const { apiGet, apiPost } = api;
const getProfile = useCallback(async () => {
const user: User = await apiGet<User>('/users/me');
setAuthUser({
...user,
isRegistered: user.firstName !== ''
});
}, [setAuthUser, apiGet]);
const setProfile = useCallback(
async (user: User) => {
setAuthUser({
...user,
isRegistered: user.firstName !== ''
});
},
[setAuthUser]
);
const refreshUser = useCallback(async () => {
if (!token && process.env.REACT_APP_USE_COGNITO) {
const session = await Auth.currentSession();
const { token } = await apiPost<{ token: string; user: User }>(
'/auth/callback',
{
body: {
token: session.getIdToken().getJwtToken()
}
}
);
setToken(token);
}
}, [apiPost, setToken, token]);
const extendedOrg = useMemo(() => {
return getExtendedOrg(org, authUser);
}, [org, authUser]);
const maximumRole = useMemo(() => {
return getMaximumRole(authUser);
}, [authUser]);
const touVersion = useMemo(() => {
return getTouVersion(maximumRole);
}, [maximumRole]);
const userMustSign = useMemo(() => {
return getUserMustSign(authUser, touVersion);
}, [authUser, touVersion]);
useEffect(() => {
refreshUser();
// eslint-disable-next-line
}, []);
useEffect(() => {
if (!token) {
setAuthUser(null);
} else {
getProfile();
}
}, [token, getProfile]);
return (
<AuthContext.Provider
value={{
user: authUser,
token,
setUser: setProfile,
refreshUser,
setOrganization: setOrg,
currentOrganization: extendedOrg,
showAllOrganizations: showAllOrganizations,
setShowAllOrganizations: setShowAllOrganizations,
login: setToken,
logout,
setLoading: () => {},
maximumRole,
touVersion,
userMustSign,
setFeedbackMessage,
...api
}}
>
{api.loading && (
<div className="cisa-crossfeed-loading">
<div></div>
<div></div>
</div>
)}
{feedbackMessage && (
<Snackbar
open={!!feedbackMessage}
autoHideDuration={5000}
onClose={() => setFeedbackMessage(null)}
>
<Alert
onClose={() => setFeedbackMessage(null)}
severity={feedbackMessage.type}
>
{feedbackMessage.message}
</Alert>
</Snackbar>
)}
{children}
</AuthContext.Provider>
);
}
Example #28
Source File: UserEdit.tsx From clearflask with Apache License 2.0 | 4 votes |
render() {
const userId = this.props.userId || this.state.createdUserId;
const isMe = !!this.props.loggedInUser && this.props.loggedInUser.userId === userId;
const isModOrAdminLoggedIn = this.props.server.isModOrAdminLoggedIn();
var content;
if (!userId) {
// Create form
if (!isModOrAdminLoggedIn) return null;
content = (
<div key='create-form' className={this.props.classes.section}>
<PanelTitle text={this.props.t('create-user')} />
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('avatar')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<AvatarDisplay user={{
name: this.state.displayName || '',
}} size={40} />
</Grid>
</Grid>
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('displayname')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<TextField
value={this.state.displayName || ''}
onChange={e => this.setState({ displayName: e.target.value })}
/>
<Button aria-label="Create" color='primary' style={{
visibility:
!this.state.displayName ? 'hidden' : undefined
}} onClick={async () => {
if (!this.state.displayName || !isModOrAdminLoggedIn) {
return;
}
const newUserAdmin = await (await this.props.server.dispatchAdmin()).userCreateAdmin({
projectId: this.props.server.getProjectId(),
userCreateAdmin: { name: this.state.displayName },
});
this.setState({
createdUserId: newUserAdmin.userId,
userAdmin: newUserAdmin,
displayName: undefined,
});
}}>{this.props.t('save')}</Button>
</Grid>
</Grid>
</div>
);
} else if (!isModOrAdminLoggedIn && !isMe) {
// View only
content = (
<div key='view-only' className={this.props.classes.section}>
<PanelTitle text={this.props.t('info')} />
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('avatar')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<AvatarDisplay user={this.props.user} size={40} />
</Grid>
</Grid>
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('displayname')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
{DisplayUserName(this.props.user)}
</Grid>
</Grid>
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('registered')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<TimeAgo date={this.props.user?.created || 0} />
</Grid>
</Grid>
</div>
);
} else {
// Edit form (for both self and by admin/mod)
var user: Client.UserMe | Admin.UserAdmin | undefined;
var balance: number | undefined;
if (this.props.loggedInUser?.userId === userId) {
user = this.props.loggedInUser;
balance = this.props.loggedInUserBalance;
} else {
user = this.state.userAdmin;
balance = this.state.userAdmin?.balance;
if (this.userAdminFetchedForUserId !== userId) {
this.userAdminFetchedForUserId = userId;
this.props.server.dispatchAdmin().then(d => d.userGetAdmin({
projectId: this.props.server.getProjectId(),
userId,
}))
.then(userAdmin => this.setState({
userAdmin,
userAdminStatus: Status.FULFILLED,
}))
.catch(e => this.setState({
userAdminStatus: Status.REJECTED,
}));
}
}
if (!user) {
return (<LoadingPage />);
}
const balanceAdjustmentHasError = !!this.state.balanceAdjustment && (!parseInt(this.state.balanceAdjustment) || !+this.state.balanceAdjustment || parseInt(this.state.balanceAdjustment) !== parseFloat(this.state.balanceAdjustment));
const browserPushControl = this.renderBrowserPushControl(isMe, user);
// const androidPushControl = this.renderMobilePushControl(MobileNotificationDevice.Android);
// const iosPushControl = this.renderMobilePushControl(MobileNotificationDevice.Ios);
const emailControl = this.renderEmailControl(isMe, user);
const isPushOrAnon = !user.email && !user.isExternal;
const categoriesWithSubscribe = (this.props.categories || []).filter(c => !!c.subscription);
content = (
<React.Fragment key='edit-user'>
<div className={this.props.classes.section}>
<PanelTitle text={this.props.t('account')} />
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('avatar')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<AvatarDisplay user={{
...user,
...(this.state.displayName !== undefined ? {
name: this.state.displayName,
} : {}),
...(this.state.email !== undefined ? {
email: this.state.email,
} : {}),
}} size={40} />
</Grid>
</Grid>
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('displayname')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
{!!user.isExternal ? (
<Tooltip title={this.props.t('cannot-be-changed')} placement='top-start'>
<Typography>{user.name || 'None'}</Typography>
</Tooltip>
) : (
<>
<TextField
id='displayName'
error={!user.name}
value={(this.state.displayName === undefined ? user.name : this.state.displayName) || ''}
onChange={e => this.setState({ displayName: e.target.value })}
/>
<Button aria-label={this.props.t('save')} color='primary' style={{
visibility:
!this.state.displayName
|| this.state.displayName === user.name
? 'hidden' : undefined
}} onClick={async () => {
if (!this.state.displayName
|| !user
|| this.state.displayName === user.name) {
return;
}
if (isModOrAdminLoggedIn) {
const newUserAdmin = await (await this.props.server.dispatchAdmin()).userUpdateAdmin({
projectId: this.props.server.getProjectId(),
userId: userId!,
userUpdateAdmin: { name: this.state.displayName },
});
this.setState({ displayName: undefined, userAdmin: newUserAdmin });
} else {
await (await this.props.server.dispatch()).userUpdate({
projectId: this.props.server.getProjectId(),
userId: userId!,
userUpdate: { name: this.state.displayName },
});
this.setState({ displayName: undefined });
}
}}>{this.props.t('save')}</Button>
</>
)}
</Grid>
</Grid>
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('email')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
{!!user.isExternal ? (
<Tooltip title={this.props.t('cannot-be-changed')} placement='top-start'>
<Typography>{user.email || this.props.t('none')}</Typography>
</Tooltip>
) : (
<>
<TextField
id='email'
value={(this.state.email === undefined ? user.email : this.state.email) || ''}
onChange={e => this.setState({ email: e.target.value })}
autoFocus={!!this.state.createdUserId}
/>
<Button aria-label={this.props.t('save')} color='primary' style={{
visibility:
!this.state.email
|| this.state.email === user.email
? 'hidden' : undefined
}} onClick={async () => {
if (!this.state.email
|| !user
|| this.state.email === user.email) {
return;
}
if (isModOrAdminLoggedIn) {
const newUserAdmin = await (await this.props.server.dispatchAdmin()).userUpdateAdmin({
projectId: this.props.server.getProjectId(),
userId: userId!,
userUpdateAdmin: { email: this.state.email },
});
this.setState({ email: undefined, userAdmin: newUserAdmin });
} else {
await (await this.props.server.dispatch()).userUpdate({
projectId: this.props.server.getProjectId(),
userId: userId!,
userUpdate: { email: this.state.email },
});
this.setState({ email: undefined });
}
}}>{this.props.t('save')}</Button>
</>
)}
</Grid>
</Grid>
{!user.isExternal && (
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('password-0')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<TextField
id='password'
value={this.state.password || ''}
onChange={e => this.setState({ password: e.target.value })}
type={this.state.revealPassword ? 'text' : 'password'}
disabled={!this.state.email && !user.email}
InputProps={{
endAdornment: (
<InputAdornment position='end'>
<IconButton
aria-label='Toggle password visibility'
onClick={() => this.setState({ revealPassword: !this.state.revealPassword })}
disabled={!this.state.email && !user.email}
>
{this.state.revealPassword ? <VisibilityIcon fontSize='small' /> : <VisibilityOffIcon fontSize='small' />}
</IconButton>
</InputAdornment>
),
}}
/>
<Button aria-label={this.props.t('save')} color='primary' style={{
visibility:
!this.state.password
|| this.state.password === user.name
? 'hidden' : undefined
}} onClick={async () => {
if (!this.state.password
|| !user) {
return;
}
if (isModOrAdminLoggedIn) {
const newUserAdmin = await (await this.props.server.dispatchAdmin()).userUpdateAdmin({
projectId: this.props.server.getProjectId(),
userId: userId!,
userUpdateAdmin: { password: this.state.password },
});
this.setState({ password: undefined, userAdmin: newUserAdmin });
} else {
await (await this.props.server.dispatch()).userUpdate({
projectId: this.props.server.getProjectId(),
userId: userId!,
userUpdate: { password: this.state.password },
});
this.setState({ password: undefined });
}
}}>{this.props.t('save')}</Button>
</Grid>
</Grid>
)}
{this.props.credits && (
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('balance')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<CreditView val={balance || 0} credits={this.props.credits} />
{isMe && !!this.props.credits?.creditPurchase?.redirectUrl && (
<Button
component={'a' as any}
className={this.props.classes.linkGetMore}
color='primary'
href={this.props.credits.creditPurchase.redirectUrl}
target='_blank'
underline='none'
rel='noopener nofollow'
>
{this.props.credits.creditPurchase.buttonTitle || 'Get more'}
</Button>
)}
{isModOrAdminLoggedIn && (
<>
<IconButton onClick={() => this.setState({ transactionCreateOpen: !this.state.transactionCreateOpen })}>
<EditIcon />
</IconButton>
<Collapse in={this.state.transactionCreateOpen}>
<div>
<TextField
label='Adjustment amount'
value={this.state.balanceAdjustment || ''}
error={balanceAdjustmentHasError}
helperText={balanceAdjustmentHasError ? 'Invalid number' : (
!this.state.balanceAdjustment ? undefined : (
<CreditView
val={+this.state.balanceAdjustment}
credits={this.props.credits}
/>
))}
onChange={e => this.setState({ balanceAdjustment: e.target.value })}
/>
<TextField
label='Transaction note'
value={this.state.balanceDescription || ''}
onChange={e => this.setState({ balanceDescription: e.target.value })}
/>
<Button aria-label="Save" color='primary' style={{
visibility:
(this.state.balanceAdjustment || 0) === 0
? 'hidden' : undefined
}} onClick={async () => {
if (this.state.balanceAdjustment === undefined
|| +this.state.balanceAdjustment === 0
|| !user) {
return;
}
const dispatcher = await this.props.server.dispatchAdmin();
const newUserAdmin = await dispatcher.userUpdateAdmin({
projectId: this.props.server.getProjectId(),
userId: userId!,
userUpdateAdmin: {
transactionCreate: {
amount: +this.state.balanceAdjustment,
summary: this.state.balanceDescription,
}
},
});
this.setState({
userAdmin: newUserAdmin,
transactionCreateOpen: false,
balanceAdjustment: undefined,
balanceDescription: undefined,
});
}}>Save</Button>
</div>
</Collapse>
</>
)}
</Grid>
</Grid>
)}
{isModOrAdminLoggedIn && (
<>
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('is-moderator')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<FormControlLabel
control={(
<Switch
color='default'
checked={!!user.isMod}
onChange={async (e, checked) => {
const dispatcher = await this.props.server.dispatchAdmin();
const newUserAdmin = await dispatcher.userUpdateAdmin({
projectId: this.props.server.getProjectId(),
userId: userId!,
userUpdateAdmin: { isMod: !user?.isMod },
});
this.setState({ password: undefined, userAdmin: newUserAdmin });
}}
/>
)}
label={(
<FormHelperText component='span'>
{user.isMod ? this.props.t('yes') : this.props.t('no')}
</FormHelperText>
)}
/>
</Grid>
</Grid>
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('user-id')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<Typography>{userId}</Typography>
</Grid>
</Grid>
</>
)}
{!!isMe && !this.props.suppressSignOut && (
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>
{this.props.t('sign-out-of-your-account')}
{!!isPushOrAnon && (
<Collapse in={!!this.state.signoutWarnNoEmail}>
<Alert
variant='outlined'
severity='warning'
>
{this.props.t('please-add-an-email-before')}
</Alert>
</Collapse>
)}
</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<Button
disabled={!!isPushOrAnon && !!this.state.signoutWarnNoEmail}
onClick={() => {
if (isPushOrAnon) {
this.setState({ signoutWarnNoEmail: true });
} else {
this.props.server.dispatch().then(d => d.userLogout({
projectId: this.props.server.getProjectId(),
}));
}
}}
>Sign out</Button>
</Grid>
</Grid>
)}
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('delete-account')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
<Button
onClick={() => this.setState({ deleteDialogOpen: true })}
>Delete</Button>
<Dialog
open={!!this.state.deleteDialogOpen}
onClose={() => this.setState({ deleteDialogOpen: false })}
>
<DialogTitle>Delete account?</DialogTitle>
<DialogContent>
<DialogContentText>{isMe
? 'By deleting your account, you will be signed out of your account and your account will be permanently deleted including all of your data.'
: 'Are you sure you want to permanently delete this user?'}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ deleteDialogOpen: false })}>Cancel</Button>
<Button style={{ color: this.props.theme.palette.error.main }} onClick={async () => {
if (isModOrAdminLoggedIn) {
await (await this.props.server.dispatchAdmin()).userDeleteAdmin({
projectId: this.props.server.getProjectId(),
userId: userId!,
});
} else {
await (await this.props.server.dispatch()).userDelete({
projectId: this.props.server.getProjectId(),
userId: userId!,
});
}
this.props.onDeleted?.();
this.setState({ deleteDialogOpen: false });
}}>{this.props.t('delete')}</Button>
</DialogActions>
</Dialog>
</Grid>
</Grid>
</div>
<div className={this.props.classes.section}>
<PanelTitle text={this.props.t('notifications')} />
{browserPushControl && (
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>{this.props.t('browser-desktop-messages')}</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>{browserPushControl}</Grid>
</Grid>
)}
{/* {androidPushControl && (
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>Android Push messages</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>{androidPushControl}</Grid>
</Grid>
)}
{iosPushControl && (
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}><Typography>Apple iOS Push messages</Typography></Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>{iosPushControl}</Grid>
</Grid>
)} */}
{emailControl && (
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}>
<Typography>
{this.props.t('email')}
{user.email !== undefined && (<Typography variant='caption'> ({truncateWithElipsis(20, user.email)})</Typography>)}
</Typography>
</Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>{emailControl}</Grid>
</Grid>
)}
{categoriesWithSubscribe.map(category => !!user && (
<Grid container alignItems='center' className={this.props.classes.item}>
<Grid item xs={12} sm={6}>
<Typography>{this.props.t('new-category', { category: category.name })}</Typography>
</Grid>
<Grid item xs={12} sm={6} className={this.props.classes.itemControls}>
{this.renderCategorySubscribeControl(category, isMe, user)}
</Grid>
</Grid>
))}
</div>
</React.Fragment>
);
}
return (
<div className={classNames(this.props.className, this.props.classes.settings)}>
{content}
</div>
);
}
Example #29
Source File: TimePickModal.tsx From back-home-safe with GNU General Public License v3.0 | 4 votes |
TimePickModal = ({
isModalOpen,
onCancel,
onConfirm,
date,
}: Props) => {
const { t } = useTranslation("confirm");
const { currentTime } = useTime();
const datePickerRef = useRef<DatePickerHandler>(null);
const [showPastDateError, setShowPastDateError] = useState(false);
const [showFutureDateError, setShowFutureDateError] = useState(false);
const initPicker = () => {
datePickerRef.current && datePickerRef.current.init();
};
const handleConfirm = () => {
const leaveDate = datePickerRef.current
? datePickerRef.current.getValue()
: Date();
const leaveDateDayJs = dayjs(leaveDate).startOf("minute");
if (leaveDateDayJs.isBefore(date.startOf("minute"))) {
setShowPastDateError(true);
} else if (leaveDateDayJs.isAfter(currentTime)) {
setShowFutureDateError(true);
} else {
onConfirm(leaveDateDayJs);
}
};
return (
<Modal
isOpen={isModalOpen}
onRequestClose={onCancel}
style={{
overlay: {
backgroundColor: "rgba(0, 0, 0, 0.5)",
},
content: {
outline: "none",
border: "0",
padding: "16px",
borderRadius: "8px",
width: "240px",
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
height: "280px",
overflow: "hidden",
},
}}
ariaHideApp={false}
onAfterOpen={initPicker}
>
<CrossWrapper>
<Cross src={crossBlack} onClick={onCancel} />
</CrossWrapper>
<Title>{t("message.when_you_left")}</Title>
<TimePickerWrapper>
<DatePicker ref={datePickerRef} />
</TimePickerWrapper>
<ModalConfirmButton onClick={handleConfirm}>
{t("global:button.confirm")}
</ModalConfirmButton>
<Snackbar
open={showPastDateError}
autoHideDuration={2000}
onClose={() => {
setShowPastDateError(false);
}}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
>
<Alert elevation={6} variant="filled" severity="error">
{t("message.leave_time_earlier_than_enter")}
</Alert>
</Snackbar>
<Snackbar
open={showFutureDateError}
autoHideDuration={2000}
onClose={() => {
setShowFutureDateError(false);
}}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
>
<Alert elevation={6} variant="filled" severity="error">
{t("message.future_leave_time")}
</Alert>
</Snackbar>
</Modal>
);
}