@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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
/** @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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
/**
 * 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>
            &nbsp;({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&apos;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&apos;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 &quot;Early Monitoring&quot; 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&apos;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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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'>&nbsp;({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 vote down vote up
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>
  );
}