@material-ui/core#FormHelperText TypeScript Examples

The following examples show how to use @material-ui/core#FormHelperText. 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: PrioritizationControlsCredits.tsx    From clearflask with Apache License 2.0 6 votes vote down vote up
render() {
    return (
      <RadioGroup
        className={this.props.classes.extraControls}
        value={this.state.fundingType}
        onChange={this.handleChangeFundingType.bind(this)}
      >
        <FormControlLabel value='currency' control={<Radio color='primary' />}
          label={<FormHelperText component='span'>Currency</FormHelperText>} />
        <FormControlLabel value='time' control={<Radio color='primary' />}
          label={<FormHelperText component='span'>{this.props.forContentCreator ? 'Time' : 'Development time'}</FormHelperText>} />
        <FormControlLabel value={this.props.forContentCreator ? 'heart' : 'beer'} control={<Radio color='primary' />}
          label={<FormHelperText component='span'>Customize</FormHelperText>} />
      </RadioGroup>
    );
  }
Example #2
Source File: UserEdit.tsx    From clearflask with Apache License 2.0 6 votes vote down vote up
renderCategorySubscribeControl(category: Client.Category, isMe: boolean, user: Client.UserMe | Admin.UserAdmin) {
    if (!category.subscription) return null;

    const isSubscribed = user?.categorySubscriptions?.includes(category.categoryId);

    if (!isMe) {
      return user.browserPush ? this.props.t('subscribed') : this.props.t('not-subscribed');
    }

    return (
      <FormControlLabel
        control={(
          <Switch
            color='default'
            checked={!!isSubscribed}
            onChange={async (e, checked) => {
              const dispatcher = await this.props.server.dispatch();
              await dispatcher.categorySubscribe({
                projectId: this.props.server.getProjectId(),
                categoryId: category.categoryId,
                subscribe: !isSubscribed,
              });
            }}
          />
        )}
        label={(
          <FormHelperText component='span'>
            {isSubscribed ? this.props.t('subscribed') : this.props.t('not-subscribed')}
          </FormHelperText>
        )}
      />
    );
  }
Example #3
Source File: PrioritizationControlsExpressions.tsx    From clearflask with Apache License 2.0 6 votes vote down vote up
render() {
    return (
      <div className={this.props.classes.extraControls}>
        <FormControlLabel
          control={(
            <Switch
              color='primary'
              checked={!!this.state.expressionsLimitEmojis}
              onChange={this.handleChangeExpressionsLimitEmojis.bind(this)}
            />
          )}
          label={<FormHelperText component='span'>Limit available emojis</FormHelperText>}
        />
        <FormControlLabel
          control={(
            <Switch
              color='primary'
              checked={!!this.state.expressionsAllowMultiple}
              onChange={this.handleChangeExpressionsLimitSingle.bind(this)}
            />
          )}
          label={<FormHelperText component='span'>Allow selecting multiple</FormHelperText>}
        />
      </div>
    );
  }
Example #4
Source File: PrioritizationControlsVoting.tsx    From clearflask with Apache License 2.0 6 votes vote down vote up
render() {
    return (
      <div className={this.props.classes.extraControls}>
        <FormControlLabel
          control={(
            <Switch
              color='primary'
              checked={!!this.state.votingEnableDownvote}
              onChange={this.handleChangeEnableDownvote.bind(this)}
            />
          )}
          label={<FormHelperText component='span'>Downvoting</FormHelperText>}
        />
      </div>
    );
  }
Example #5
Source File: TemplateDemoControls.tsx    From clearflask with Apache License 2.0 6 votes vote down vote up
render() {
    return (
      <RadioGroup
        className={this.props.classes.extraControls}
        value={this.props.value}
        onChange={(e, val) => this.props.onChange(val)}
      >
        {Object.keys(demoOptions).map(option => (
          <FormControlLabel key={option} value={option} control={<Radio color='primary' />}
            label={<FormHelperText component='span'>{option}</FormHelperText>} />
        ))}
      </RadioGroup>
    );
  }
Example #6
Source File: branch_setup.tsx    From jupyter-extensions with Apache License 2.0 6 votes vote down vote up
render(): React.ReactElement {
    return (
      <FormControl
        className={classes(setupItemClass)}
        disabled={this.state.disabled}
        variant="outlined"
      >
        <FormHelperText className={setupHelperTextClass}>Branch</FormHelperText>
        <Select
          className={classes(setupItemInnerClass)}
          value={this.state.currBranch}
          onChange={event => this._onChange(event)}
        >
          {this._renderBranches()}
        </Select>
      </FormControl>
    );
  }
Example #7
Source File: path_setup.tsx    From jupyter-extensions with Apache License 2.0 6 votes vote down vote up
render(): React.ReactElement {
    return (
      <FormControl
        className={classes(setupItemClass)}
        disabled
        variant="outlined"
      >
        <FormHelperText className={setupHelperTextClass}>
          Repository
        </FormHelperText>
        <OutlinedInput
          className={classes(setupItemInnerClass)}
          value={this.state.path}
          style={{ color: 'var(--jp-ui-font-color0)' }}
        />
      </FormControl>
    );
  }
Example #8
Source File: Login.tsx    From cards-against-formality-pwa with BSD 2-Clause "Simplified" License 6 votes vote down vote up
function LoginProviders({ onProviderSelect }: any) {
  return <div className="login-providers-content">
    <Button className="button" onClick={() => onProviderSelect('anonymous')} variant="contained" color="secondary">Play Anonymously</Button>
    <FormHelperText color="secondary" className="sign-in-helper">
      Or Sign in to pick a permanent username.
      </FormHelperText>
    <Button className="button bottom" onClick={() => onProviderSelect('google')} variant="contained" color="primary">
      <img className="google-icon-svg" src={googleLogo} alt="google" />
      <div>Sign in with Google</div>
    </Button>
    <Button disabled={true} className="button" onClick={() => onProviderSelect('facebook')} variant="contained" color="primary">
      <FacebookIcon className="google-icon-svg" />
      <div>Continue with Facebook</div>
    </Button>
    <FormHelperText className="legal-helper">By Proceeding, you are agreeing to our terms of service and that you have read our privacy policy found <Link color="secondary" onClick={() => window.open('https://htmlpreview.github.io/?https://github.com/JordanPawlett/cards-against-formality-pwa/blob/master/public/privacy_policy.html')}>here</Link>.</FormHelperText>
  </div>
}
Example #9
Source File: index.tsx    From back-home-safe with GNU General Public License v3.0 6 votes vote down vote up
CameraSetting = () => {
  const { t } = useTranslation("camera_setting");
  const { preferredCameraId, setPreferredCameraId, cameraList } = useCamera();

  return (
    <PageWrapper>
      <Header backPath="/" name={t("name")} />
      <FormWrapper>
        <StyledFormControl>
          <InputLabel id="cameraId">{t("form.camera_choice.label")}</InputLabel>
          <Select
            labelId="cameraId"
            id="demo-simple-select"
            value={preferredCameraId}
            onChange={(e) => {
              setPreferredCameraId((e.target.value as string) || "AUTO");
            }}
          >
            <MenuItem value="AUTO">{t("form.camera_choice.auto")}</MenuItem>
            {cameraList.map(({ deviceId, label }) => (
              <MenuItem value={deviceId} key="deviceId">
                {isNil(label) || isEmpty(label) ? deviceId : label}
              </MenuItem>
            ))}
          </Select>
          <FormHelperText>{t("form.camera_choice.explain")}</FormHelperText>
        </StyledFormControl>
      </FormWrapper>
      <VideoContainer>
        <MediaStream suppressError />
      </VideoContainer>
    </PageWrapper>
  );
}
Example #10
Source File: PhoneInput.tsx    From glific-frontend with GNU Affero General Public License v3.0 6 votes vote down vote up
PhoneInput: React.SFC<InputProps> = ({
  enableSearch = true,
  form: { errors, setFieldValue },
  field,
  inputProps = {
    name: field.name,
    required: true,
    autoFocus: false,
  },
  ...props
}) => {
  const errorText = getIn(errors, field.name);
  const { placeholder } = props;

  return (
    <div className={styles.Input} data-testid="phoneInput">
      <FormControl>
        <ReactPhoneInput
          containerClass={styles.Container}
          inputClass={styles.PhoneNumber}
          data-testid="phoneNumber"
          placeholder={placeholder}
          enableSearch={enableSearch}
          country="in"
          autoFormat={false}
          inputProps={inputProps}
          {...field}
          value={field.value}
          onChange={(event) => {
            setFieldValue(field.name, event);
          }}
        />
        {errorText ? (
          <FormHelperText classes={{ root: styles.FormHelperText }}>{errorText}</FormHelperText>
        ) : null}
      </FormControl>
    </div>
  );
}
Example #11
Source File: TableProp.tsx    From clearflask with Apache License 2.0 5 votes vote down vote up
renderHeaderCell(key: string | number, name?: string, description?: string) {
    return (
      <TableCell key={key} align='center' style={{ fontWeight: 'normal', width: this.props.width }} size='small'>
        {name && (<InputLabel shrink={false}>{name}</InputLabel>)}
        {description && (<FormHelperText>{description}</FormHelperText>)}
      </TableCell>
    );
  }
Example #12
Source File: UserEdit.tsx    From clearflask with Apache License 2.0 5 votes vote down vote up
renderBrowserPushControl(isMe: boolean, user: Client.UserMe | Admin.UserAdmin): React.ReactNode | null {
    if (!this.props.config || !user || (!this.props.config.users.onboarding.notificationMethods.browserPush && !user.browserPush)) {
      return null;
    }

    if (!isMe) {
      return user.browserPush ? this.props.t('receiving') : this.props.t('not-receiving');
    }

    const browserPushStatus = WebNotification.getInstance().getStatus();
    var browserPushEnabled = !!user.browserPush;
    var browserPushControlDisabled;
    var browserPushLabel;
    if (user.browserPush) {
      browserPushControlDisabled = false;
      browserPushLabel = this.props.t('enabled');
    } else {
      switch (browserPushStatus) {
        case WebNotificationStatus.Unsupported:
          browserPushControlDisabled = true;
          browserPushLabel = 'Not supported by your browser';
          break;
        case WebNotificationStatus.Denied:
          browserPushControlDisabled = true;
          browserPushLabel = 'You have declined access to notifications';
          break;
        default:
        case WebNotificationStatus.Available:
        case WebNotificationStatus.Granted:
          browserPushControlDisabled = false;
          browserPushLabel = this.props.t('disabled');
          break;
      }
    }

    return (
      <FormControlLabel
        control={(
          <Switch
            color='default'
            disabled={browserPushControlDisabled}
            checked={browserPushEnabled}
            onChange={(e, checked) => {
              if (checked) {
                WebNotification.getInstance().askPermission()
                  .then(r => {
                    if (r.type === 'success') {
                      this.props.server.dispatch().then(d => d.userUpdate({
                        projectId: this.props.server.getProjectId(),
                        userId: user.userId,
                        userUpdate: { browserPushToken: r.token },
                      }));
                    } else if (r.type === 'error') {
                      if (r.userFacingMsg) {
                        this.props.enqueueSnackbar(r.userFacingMsg || 'Failed to setup browser notifications', { variant: 'error', preventDuplicate: true });
                      }
                      this.forceUpdate();
                    }
                  });
              } else {
                this.props.server.dispatch().then(d => d.userUpdate({
                  projectId: this.props.server.getProjectId(),
                  userId: user.userId,
                  userUpdate: { browserPushToken: '' },
                }));
              }
            }}
          />
        )}
        label={<FormHelperText component='span' error={browserPushControlDisabled}>{browserPushLabel}</FormHelperText>}
      />
    );
  }
Example #13
Source File: SwarmSelect.tsx    From bee-dashboard with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
export function SwarmSelect({ defaultValue, formik, name, options, onChange, label }: Props): ReactElement {
  const classes = useStyles()

  if (formik) {
    return (
      <>
        {label && <FormHelperText>{label}</FormHelperText>}
        <Field
          required
          component={Select}
          name={name}
          fullWidth
          variant="outlined"
          defaultValue={defaultValue || ''}
          className={classes.select}
          placeholder={label}
          MenuProps={{ MenuListProps: { disablePadding: true }, PaperProps: { square: true } }}
        >
          {options.map((x, i) => (
            <MenuItem key={i} value={x.value} className={classes.option}>
              {x.label}
            </MenuItem>
          ))}
        </Field>
      </>
    )
  }

  return (
    <>
      {label && <FormHelperText>{label}</FormHelperText>}
      <SimpleSelect
        required
        name={name}
        fullWidth
        variant="outlined"
        className={classes.select}
        defaultValue={defaultValue || ''}
        onChange={onChange}
        placeholder={label}
        MenuProps={{ MenuListProps: { disablePadding: true }, PaperProps: { square: true } }}
      >
        {options.map((x, i) => (
          <MenuItem key={i} value={x.value} className={classes.option}>
            {x.label}
          </MenuItem>
        ))}
      </SimpleSelect>
    </>
  )
}
Example #14
Source File: Dropdown.tsx    From glific-frontend with GNU Affero General Public License v3.0 5 votes vote down vote up
Dropdown: React.SFC<DropdownProps> = (props) => {
  const { options, placeholder, field, helperText, disabled, form, fieldValue, fieldChange } =
    props;

  const { onChange, value, ...rest } = field;

  let optionsList = null;
  if (options) {
    optionsList = options.map((option: any) => (
      <MenuItem value={option.id} key={option.id}>
        {option.label ? option.label : option.name}
      </MenuItem>
    ));
  }

  return (
    <div className={styles.Dropdown} data-testid="dropdown">
      <FormControl
        variant="outlined"
        fullWidth
        error={form && form.errors[field.name] && form.touched[field.name]}
      >
        {placeholder ? (
          <InputLabel id="simple-select-outlined-label" data-testid="inputLabel">
            {placeholder}
          </InputLabel>
        ) : null}
        <Select
          onChange={(event) => {
            onChange(event);
            if (fieldChange) {
              fieldChange(event);
            }
          }}
          MenuProps={{
            classes: {
              paper: styles.Paper,
            },
          }}
          value={fieldValue !== undefined ? fieldValue : value}
          {...rest}
          label={placeholder !== '' ? placeholder : undefined}
          fullWidth
          disabled={disabled}
        >
          {optionsList}
        </Select>
        {form && form.errors[field.name] && form.touched[field.name] ? (
          <FormHelperText>{form.errors[field.name]}</FormHelperText>
        ) : null}
        {helperText ? (
          <FormHelperText className={styles.HelperText}>{helperText}</FormHelperText>
        ) : null}
      </FormControl>
    </div>
  );
}
Example #15
Source File: SQFormRadioButtonGroup.tsx    From SQForm with MIT License 5 votes vote down vote up
function SQFormRadioButtonGroup({
  name,
  onChange,
  shouldDisplayInRow = false,
  size = 'auto',
  groupLabel,
  children,
}: SQFormRadioButtonGroupProps): React.ReactElement {
  const {
    fieldState: {isFieldError, isFieldRequired},
    formikField: {field},
    fieldHelpers: {handleChange, handleBlur, HelperTextComponent},
  } = useForm<
    RadioButtonInputItemProps['value'],
    React.ChangeEvent<HTMLInputElement>
  >({
    name,
    onChange,
  });

  const childrenToRadioGroupItems = () => {
    return children.map((radioOption) => {
      const {label, value, isDisabled, InputProps} = radioOption;
      return (
        <SQFormRadioButtonGroupItem
          label={label}
          value={value}
          isDisabled={isDisabled}
          isRowDisplay={shouldDisplayInRow}
          InputProps={InputProps}
          key={`SQFormRadioButtonGroupItem_${value}`}
        />
      );
    });
  };

  return (
    <Grid item sm={size}>
      <FormControl
        component="fieldset"
        required={isFieldRequired}
        error={isFieldError}
        onBlur={handleBlur}
      >
        <FormLabel
          component="legend"
          classes={{
            root: 'MuiInputLabel-root',
            asterisk: 'MuiInputLabel-asterisk',
          }}
        >
          {groupLabel}
        </FormLabel>
        <RadioGroup
          value={field.value}
          row={shouldDisplayInRow}
          aria-label={`SQFormRadioButtonGroup_${name}`}
          name={name}
          onChange={handleChange}
        >
          {childrenToRadioGroupItems()}
        </RadioGroup>
        <FormHelperText>{HelperTextComponent}</FormHelperText>
      </FormControl>
    </Grid>
  );
}
Example #16
Source File: RadioInput.tsx    From glific-frontend with GNU Affero General Public License v3.0 5 votes vote down vote up
RadioInput: React.SFC<RadioInputProps> = ({
  labelYes = 'Yes',
  labelNo = 'No',
  row = true,
  field,
  form: { touched, errors, setFieldValue, values },
  radioTitle,
  handleChange,
}) => {
  const selectedValue = values[field.name];

  const isChecked = (value: any) => selectedValue === value;

  const handleRadioChange = (value: boolean) => {
    setFieldValue(field.name, value);
    if (handleChange) {
      handleChange(value);
    }
  };

  let radioGroupLabel: any;
  if (radioTitle) {
    radioGroupLabel = <FormLabel component="legend">{radioTitle}</FormLabel>;
  }

  return (
    <FormControl component="fieldset">
      {radioGroupLabel}
      <RadioGroup row={row} name="radio-buttons">
        <FormControlLabel
          value={1}
          control={
            <Radio
              color="primary"
              onClick={() => handleRadioChange(true)}
              checked={isChecked(true)}
            />
          }
          label={labelYes}
          className={styles.Label}
        />
        <FormControlLabel
          value={0}
          control={
            <Radio
              color="primary"
              onClick={() => handleRadioChange(false)}
              checked={isChecked(false)}
            />
          }
          label={labelNo}
          className={styles.Label}
        />
      </RadioGroup>
      {errors[field.name] && touched[field.name] ? (
        <FormHelperText className={styles.DangerText}>{errors[field.name]}</FormHelperText>
      ) : null}
    </FormControl>
  );
}
Example #17
Source File: CreateRoom.tsx    From cards-against-formality-pwa with BSD 2-Clause "Simplified" License 5 votes vote down vote up
function DeckSelector({ decks, onChange }: { decks: any[], onChange: (decks: string[]) => void }) {
  const [deckOptions, setDeckOptions] = useState<{ name: string; _id: string, value?: boolean }[]>([]);
  const [isExpanded, setIsExpanded] = useState(false);
  const [isAllSelected, setIsAllSelected] = useState(false);
  const toggleSelectAll = useCallback(() => {
    setDeckOptions(prevDeck => {
      prevDeck.forEach(deck => deck.value = !isAllSelected);
      return [...prevDeck];
    });

    setIsAllSelected(!isAllSelected);
  }, [isAllSelected])

  useEffect(() => {
    if (decks) {
      setDeckOptions(decks.map(deck => {
        return { value: deck.name.includes('Base'), ...deck }
      }));
    }
  }, [decks]);

  useEffect(() => {
    onChange(deckOptions.filter(deck => deck.value).map(deck => deck._id));
  }, [deckOptions, onChange]);


  function _onChange(e: React.ChangeEvent<HTMLInputElement>) {
    setDeckOptions(prevDeck => {
      const deck = prevDeck.find(deck => deck._id === e.target.name);
      if (deck) {
        deck.value = e.target.checked;
      }
      return [...prevDeck];
    });
  }

  if (!decks?.length) {
    return null;
  }

  return <ExpansionPanel expanded={isExpanded} onChange={() => { setIsExpanded(prev => !prev) }}>
    <ExpansionPanelSummary
      expandIcon={<ExpandMoreIcon />}
      aria-controls="panel1bh-content"
      id="panel1bh-header"
    >
      <Typography>Available Decks!</Typography>
    </ExpansionPanelSummary>
    <ExpansionPanelDetails>
      <FormControl required component="fieldset" error={!deckOptions.some(deck => deck.value)}>
        <FormControlLabel
          control={<Checkbox checked={isAllSelected} onChange={toggleSelectAll} name="Select all" />}
          label="Select all"
        />
        <Divider />
        <FormLabel component="legend">Select which decks you would like to play with</FormLabel>
        <FormGroup className="deck-checkbox-group">
          {deckOptions.map(deck => {
            return <FormControlLabel
              key={deck._id}
              control={<Checkbox checked={deck.value} onChange={_onChange} name={deck._id} />}
              label={deck.name}
            />
          })}
        </FormGroup>
        <FormHelperText>You must select at least one</FormHelperText>
      </FormControl>
    </ExpansionPanelDetails>
  </ExpansionPanel>
}
Example #18
Source File: Recaptcha.tsx    From firebase-react-typescript-project-template with MIT License 5 votes vote down vote up
Recaptcha = () => {
  const classes = useStyles();

  const recaptchaVerified = useBooleanState(false);
  const recaptchaError = useBooleanState(false);

  const recaptcha = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    const setupRecaptcha = async () => {
      (window as WindowWithRecaptcha).recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
        recaptcha.current,
        {
          size: "normal",
          callback: () => {
            recaptchaVerified.setTrue();
            recaptchaError.setFalse();
          },
          "expired-callback": () => {
            recaptchaVerified.setFalse();
            recaptchaError.setTrue();
          },
        }
      );
      (window as WindowWithRecaptcha).recaptchaWidgetId = await (window as WindowWithRecaptcha).recaptchaVerifier?.render();
    };
    setupRecaptcha();
    // eslint-disable-next-line
  }, []);

  return (
    <FormControl
      error={!recaptchaVerified.state && recaptchaError.state}
      fullWidth
      className={classes.recaptcha}
    >
      <div ref={recaptcha} />
      {recaptchaError.state && (
        <FormHelperText id="name-error-text">
          Please verify you are a human
        </FormHelperText>
      )}
    </FormControl>
  );
}
Example #19
Source File: QuickReplyTemplate.tsx    From glific-frontend with GNU Affero General Public License v3.0 5 votes vote down vote up
QuickReplyTemplate: React.SFC<QuickReplyTemplateProps> = (props) => {
  const {
    index,
    inputFields,
    form: { touched, errors },
    onAddClick,
    onRemoveClick,
    onInputChange,
    translation,
  } = props;

  const isError = (key: string) =>
    !!(
      errors.templateButtons &&
      touched.templateButtons &&
      errors.templateButtons[index] &&
      errors.templateButtons[index][key]
    );

  const handleInputChange = (event: any, key: string) => {
    const { value } = event.target;
    const payload = { key };
    onInputChange(value, payload);
  };

  const name = 'Enter button text(20 char.)';
  const defaultValue = inputFields && inputFields[index]?.value;
  const endAdornmentIcon = inputFields.length > 1 && (
    <CrossIcon className={styles.RemoveIcon} title="Remove" onClick={onRemoveClick} />
  );

  return (
    <>
      {translation && <div className={styles.Translation}>{translation}</div>}
      <div className={styles.WrapperBackground}>
        <div className={styles.QuickReplyWrapper}>
          <FormControl fullWidth error={isError('value')} className={styles.FormControl}>
            <TextField
              placeholder={name}
              variant="outlined"
              onChange={(e: any) => handleInputChange(e, 'value')}
              value={defaultValue}
              className={styles.TextField}
              InputProps={{
                endAdornment: endAdornmentIcon,
              }}
              error={isError('value')}
            />
            {errors.templateButtons && touched.templateButtons && touched.templateButtons[index] ? (
              <FormHelperText>{errors.templateButtons[index]?.value}</FormHelperText>
            ) : null}
          </FormControl>
        </div>
        <div>
          {inputFields.length === index + 1 && inputFields.length !== 3 ? (
            <Button
              color="primary"
              onClick={onAddClick}
              className={styles.AddButton}
              startIcon={<AddIcon className={styles.AddIcon} />}
            >
              Add quick reply
            </Button>
          ) : null}
        </div>
      </div>
    </>
  );
}
Example #20
Source File: Input.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
Input: React.SFC<InputProps> = ({ textArea = false, disabled = false, ...props }) => {
  const {
    field,
    form,
    helperText,
    type,
    togglePassword,
    endAdornmentCallback,
    emojiPicker,
    placeholder,
    editor,
    rows,
    endAdornment,
    inputProp,
    translation,
  } = props;

  let fieldType = type;
  let fieldEndAdorment = null;
  if (type === 'password') {
    // we should change the type to text if user has clicked on show password
    if (togglePassword) {
      fieldType = 'text';
    }
    fieldEndAdorment = (
      <InputAdornment position="end">
        <IconButton
          aria-label="toggle password visibility"
          data-testid="passwordToggle"
          onClick={endAdornmentCallback}
          edge="end"
        >
          {togglePassword ? (
            <Visibility classes={{ root: styles.Visibility }} />
          ) : (
            <VisibilityOff classes={{ root: styles.Visibility }} />
          )}
        </IconButton>
      </InputAdornment>
    );
  } else if (emojiPicker) {
    fieldEndAdorment = emojiPicker;
  } else if (type === 'otp') {
    fieldType = 'text';
    fieldEndAdorment = (
      <InputAdornment position="end">
        <IconButton
          aria-label="resend otp"
          data-testid="resendOtp"
          onClick={endAdornmentCallback}
          edge="end"
        >
          <p className={styles.Resend}>resend</p>{' '}
          <RefreshIcon classes={{ root: styles.ResendButton }} />
        </IconButton>
      </InputAdornment>
    );
  }

  let showError = false;
  if (form && form.errors[field.name] && form.touched[field.name]) {
    showError = true;
  }

  return (
    <>
      {translation && <div className={styles.Translation}>{translation}</div>}
      <div className={styles.Input} data-testid="input">
        <FormControl fullWidth error={showError}>
          <InputLabel variant="outlined" className={styles.Label} data-testid="inputLabel">
            {placeholder}
          </InputLabel>
          <OutlinedInput
            data-testid="outlinedInput"
            inputComponent={editor ? editor.inputComponent : undefined}
            inputProps={editor ? editor.inputProps : inputProp}
            type={fieldType}
            classes={{ multiline: styles.Multiline }}
            disabled={disabled}
            error={showError}
            multiline={textArea}
            rows={rows}
            className={styles.OutlineInput}
            label={placeholder}
            fullWidth
            {...field}
            endAdornment={endAdornment || fieldEndAdorment}
          />
          {form && form.errors[field.name] && form.touched[field.name] ? (
            <FormHelperText className={styles.DangerText}>{form.errors[field.name]}</FormHelperText>
          ) : null}
          {helperText && (
            <div id="helper-text" className={styles.HelperText}>
              {helperText}
            </div>
          )}
        </FormControl>
      </div>
    </>
  );
}
Example #21
Source File: AddToMessageTemplate.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
AddToMessageTemplate: React.SFC<AddToMessageTemplateProps> = ({
  id,
  message,
  changeDisplay,
}) => {
  const [messageTemplate, setMessageTemplate] = useState<string | null>('');
  const [required, setRequired] = useState(false);
  const { t } = useTranslation();

  const [saveTemplate] = useMutation(SAVE_MESSAGE_TEMPLATE_MUTATION, {
    onCompleted: () => {
      setNotification(t('Message has been successfully added to speed sends.'));
    },
    refetchQueries: [
      {
        query: FILTER_TEMPLATES,
        variables: setVariables({ term: '' }),
      },
    ],
  });

  const onChange = (event: any) => {
    setMessageTemplate(event.target.value);
    if (required) {
      setRequired(false);
    }
  };

  const textField = (
    <div className={styles.DialogContainer} data-testid="templateContainer">
      <FormControl fullWidth error={required}>
        <InputLabel variant="outlined">Enter title</InputLabel>
        <OutlinedInput
          error={required}
          classes={{
            notchedOutline: styles.InputBorder,
          }}
          className={styles.Label}
          label={t('Enter title')}
          fullWidth
          data-testid="templateInput"
          onChange={onChange}
        />
        {required ? <FormHelperText>{t('Required')}</FormHelperText> : null}
      </FormControl>
      <div className={styles.Message}>{WhatsAppToJsx(message)}</div>
    </div>
  );

  const handleCloseButton = () => {
    changeDisplay(false);
    setMessageTemplate(null);
  };

  const handleOKButton = () => {
    if (messageTemplate === '') {
      setRequired(true);
    } else {
      saveTemplate({
        variables: {
          messageId: id,
          templateInput: {
            label: messageTemplate,
            shortcode: messageTemplate,
            languageId: '2',
          },
        },
      });
      changeDisplay(false);
      setMessageTemplate(null);
    }
  };

  return (
    <div>
      <DialogBox
        handleCancel={handleCloseButton}
        handleOk={handleOKButton}
        title={t('Add message to speed sends')}
        buttonOk={t('Save')}
      >
        {textField}
      </DialogBox>
    </div>
  );
}
Example #22
Source File: AutoComplete.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
AutoComplete: React.SFC<AutocompleteProps> = ({
  options,
  optionLabel,
  additionalOptionLabel,
  field,
  icon,
  chipIcon,
  form: { touched, errors, setFieldValue },
  textFieldProps,
  helperText,
  questionText,
  multiple = true,
  disabled = false,
  freeSolo = false,
  autoSelect = false,
  getOptions,
  asyncValues,
  roleSelection,
  onChange,
  asyncSearch = false,
  helpLink,
  noOptionsText = 'No options available',
  openOptions,
  disableClearable = false,
  listBoxProps,
  classes = {},
  renderTags = true,
  selectedOptionsIds = [],
  selectTextAsOption = false,
  onInputChange = () => null,
  valueElementName = 'id',
}) => {
  const errorText = getIn(errors, field.name);
  const touchedVal = getIn(touched, field.name);
  const hasError = touchedVal && errorText !== undefined;
  const [searchTerm, setSearchTerm] = useState('');
  const [optionValue, setOptionValue] = useState([]);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    if (options.length > 0) {
      setOptionValue(options);
    }
  }, [options]);

  useEffect(() => {
    if (getOptions && getOptions()) {
      const optionList = getOptions();
      if (optionList.length > 0) setOptionValue(optionList);
    }
  }, [open, getOptions]);

  const getValue = (() => {
    if (multiple && asyncSearch) return asyncValues.value;
    if (multiple) {
      if (optionValue.length > 0 && field.value) {
        return optionValue.filter((option: any) =>
          field.value.map((value: any) => value.id).includes(option.id)
        );
      }
      return [];
    }
    return field.value;
  })();

  const getLabel = (option: any) => {
    if (option[optionLabel]) {
      return option[optionLabel];
    }
    if (additionalOptionLabel) {
      return option[additionalOptionLabel];
    }
    return option;
  };

  /**
   *
   * @param value Callback value
   * @param getTagProps Render tag props
   *
   */
  const getRenderTags = (value: Array<any>, getTagProps: any) => {
    let tagsToRender = value;

    /**
     * when renderTags is true,
     * default selected options along with newly selected options will be visible
     * else,
     * only post selected options will be visible

     */
    if (!renderTags) {
      tagsToRender = value.filter((option: any) => !selectedOptionsIds.includes(option.id));
    }

    return tagsToRender.map((option: any, index: number) => {
      const props = getTagProps({ index });

      /**
       * If disableClearable is true, removing onDelete event
       * deleteIcon component will be disabled, when onDelete is absent
       */
      if (disableClearable) {
        delete props.onDelete;
      }

      return (
        <Chip
          data-testid="searchChip"
          style={{ backgroundColor: '#e2f1ea' }}
          className={styles.Chip}
          icon={chipIcon}
          label={getLabel(option)}
          {...props}
          deleteIcon={<DeleteIcon className={styles.DeleteIcon} data-testid="deleteIcon" />}
        />
      );
    });
  };

  const getOptionDisabled = (option: any) => selectedOptionsIds.includes(option.id);

  return (
    <div className={styles.Input}>
      <FormControl fullWidth error={hasError}>
        {questionText ? <div className={styles.QuestionText}>{questionText}</div> : null}
        <Autocomplete
          classes={classes}
          multiple={multiple}
          data-testid="autocomplete-element"
          options={optionValue}
          freeSolo={freeSolo}
          autoSelect={autoSelect}
          disableClearable={disableClearable}
          getOptionLabel={(option: any) => (option[optionLabel] ? option[optionLabel] : option)}
          getOptionDisabled={getOptionDisabled}
          getOptionSelected={(option, value) => {
            if (value) {
              return option[valueElementName] === value[valueElementName];
            }
            return false;
          }}
          onChange={(event, value: any) => {
            if (roleSelection) {
              roleSelection(value);
            }
            if (asyncSearch && value.length > 0) {
              const filterValues = asyncValues.value.filter(
                (val: any) => val.id !== value[value.length - 1].id
              );
              if (filterValues.length === value.length - 2) {
                asyncValues.setValue(filterValues);
              } else {
                asyncValues.setValue([...value]);
              }
              setSearchTerm('');
              onChange('');
            } else if (asyncSearch && value.length === 0) {
              asyncValues.setValue([]);
            }
            if (onChange) {
              onChange(value);
            }
            setFieldValue(field.name, value);
          }}
          onInputChange={onInputChange}
          inputValue={asyncSearch ? searchTerm : undefined}
          value={getValue}
          disabled={disabled}
          disableCloseOnSelect={multiple}
          renderTags={getRenderTags}
          renderOption={(option: any, { selected }) => (
            <>
              {multiple ? (
                <Checkbox
                  icon={icon}
                  checked={
                    asyncSearch
                      ? asyncValues.value.map((value: any) => value.id).includes(option.id)
                      : selected
                  }
                  color="primary"
                />
              ) : (
                ''
              )}
              {getLabel(option)}
            </>
          )}
          renderInput={(params: any) => {
            const { inputProps } = params;
            inputProps.value = selectTextAsOption ? field.value : inputProps.value;
            const asyncChange = asyncSearch
              ? {
                  onChange: (event: any) => {
                    setSearchTerm(event.target.value);
                    return onChange(event.target.value);
                  },
                }
              : null;
            return (
              <TextField
                {...params}
                inputProps={inputProps}
                {...asyncChange}
                error={hasError}
                helperText={hasError ? errorText : ''}
                {...textFieldProps}
                data-testid="AutocompleteInput"
              />
            );
          }}
          open={openOptions || open}
          onOpen={() => {
            setOpen(true);
          }}
          onClose={() => {
            setOpen(false);
          }}
          noOptionsText={noOptionsText}
          ListboxProps={listBoxProps}
        />
        {helperText ? (
          <FormHelperText className={styles.HelperText}>{helperText}</FormHelperText>
        ) : null}

        {helpLink ? (
          <div
            className={styles.HelpLink}
            onKeyDown={() => helpLink.handleClick()}
            onClick={() => helpLink.handleClick()}
            role="button"
            data-testid="helpButton"
            tabIndex={0}
          >
            {helpLink.label}
          </div>
        ) : null}
      </FormControl>
    </div>
  );
}
Example #23
Source File: InteractiveOptions.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
InteractiveOptions: React.SFC<InteractiveOptionsProps> = ({
  isAddButtonChecked,
  templateType,
  inputFields,
  form,
  onAddClick,
  onRemoveClick,
  onTemplateTypeChange,
  onInputChange,
  onGlobalButtonInputChange,
  onListItemAddClick,
  onListItemRemoveClick,
  disabled = false,
  translation,
  disabledType,
}) => {
  const { values, errors, touched, setFieldValue } = form;

  const handleAddClick = (helper: any, type: string) => {
    const obj = type === LIST ? { title: '', options: [] } : { value: '' };
    helper.push(obj);
    onAddClick(true, type);
  };

  const handleRemoveClick = (helper: any, idx: number) => {
    helper.remove(idx);
    onRemoveClick(idx);
  };

  const getButtons = (index: number, arrayHelpers: any) => {
    let template: any = null;
    if (templateType === LIST) {
      template = (
        <ListReplyTemplate
          translation={translation && translation.items[index]}
          key={index}
          index={index}
          inputFields={inputFields}
          form={form}
          onListAddClick={() => handleAddClick(arrayHelpers, LIST)}
          onListRemoveClick={() => handleRemoveClick(arrayHelpers, index)}
          onListItemAddClick={(options: Array<any>) => onListItemAddClick(index, options)}
          onListItemRemoveClick={(itemIndex: number) => onListItemRemoveClick(index, itemIndex)}
          onInputChange={(value: string, payload: any) =>
            onInputChange(LIST, index, value, payload, setFieldValue)
          }
        />
      );
    }

    if (templateType === QUICK_REPLY) {
      template = (
        <QuickReplyTemplate
          translation={translation && translation[index]}
          key={index}
          index={index}
          inputFields={inputFields}
          form={form}
          onInputChange={(value: string, payload: any) =>
            onInputChange(QUICK_REPLY, index, value, payload, setFieldValue)
          }
          onAddClick={() => handleAddClick(arrayHelpers, QUICK_REPLY)}
          onRemoveClick={() => handleRemoveClick(arrayHelpers, index)}
        />
      );
    }
    return template;
  };

  const radioTemplateType = (
    <div>
      <RadioGroup
        aria-label="template-type"
        name="template-type"
        row
        value={templateType}
        onChange={(event) => onTemplateTypeChange(event.target.value)}
      >
        <div className={styles.RadioLabelWrapper}>
          <FormControlLabel
            value={QUICK_REPLY}
            control={
              <Radio
                disabled={disabledType}
                color="primary"
                checkedIcon={<ApprovedIcon className={styles.CheckedIcon} />}
                size="small"
              />
            }
            className={templateType === QUICK_REPLY ? styles.SelectedLabel : ''}
            classes={{ root: styles.RadioLabel }}
            label="Reply buttons"
          />
        </div>
        <div className={styles.RadioLabelWrapper}>
          <FormControlLabel
            value={LIST}
            control={
              <Radio
                disabled={disabledType}
                color="primary"
                checkedIcon={<ApprovedIcon className={styles.CheckedIcon} />}
                size="small"
              />
            }
            className={templateType === LIST ? styles.SelectedLabel : ''}
            classes={{ root: styles.RadioLabel }}
            label="List message"
          />
        </div>
      </RadioGroup>
      {templateType && templateType === LIST && (
        <div className={styles.GlobalButton}>
          {translation && <div className={styles.Translation}>{translation.globalButton}</div>}
          <FormControl
            fullWidth
            error={!!(errors.globalButton && touched.globalButton)}
            className={styles.FormControl}
          >
            <TextField
              placeholder="List header"
              variant="outlined"
              label="List header*"
              className={styles.TextField}
              onChange={(e: any) => {
                setFieldValue('globalButton', e.target.value);
                onGlobalButtonInputChange(e.target.value);
              }}
              value={values.globalButton}
              error={!!errors.globalButton && touched.globalButton}
            />
            {errors.globalButton && touched.globalButton && (
              <FormHelperText>{errors.globalButton}</FormHelperText>
            )}
          </FormControl>
        </div>
      )}

      {templateType && (
        <div className={templateType === QUICK_REPLY ? styles.TemplateFields : ''}>
          <FieldArray
            name="templateButtons"
            render={(arrayHelpers) =>
              values.templateButtons.map((row: any, index: any) => getButtons(index, arrayHelpers))
            }
          />
        </div>
      )}
    </div>
  );

  return <div>{isAddButtonChecked && !disabled && radioTemplateType}</div>;
}
Example #24
Source File: ListReplyTemplate.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
ListReplyTemplate: React.SFC<ListReplyTemplateProps> = (props) => {
  const {
    index,
    inputFields,
    form: { touched, errors, values },
    onListAddClick,
    onListRemoveClick,
    onListItemAddClick,
    onListItemRemoveClick,
    onInputChange,
    translation,
  } = props;

  const { t } = useTranslation();

  const isError = (key: string, itemIdx: number) => {
    const error =
      errors.templateButtons &&
      touched.templateButtons &&
      errors.templateButtons[index] &&
      touched.templateButtons[index] &&
      errors.templateButtons[index].options &&
      touched.templateButtons[index].options &&
      errors.templateButtons[index].options[itemIdx] &&
      errors.templateButtons[index].options[itemIdx][key];

    return !!error;
  };

  const isListTitleError = (() => {
    const error =
      errors.templateButtons &&
      touched.templateButtons &&
      errors.templateButtons[index] &&
      touched.templateButtons[index] &&
      errors.templateButtons[index].title;

    return !!error;
  })();

  const sectionLabel = `Enter list ${index + 1} title*`;

  const { templateButtons } = values;
  const { options } = templateButtons[index];

  if (!options) {
    return null;
  }

  const showDeleteIcon = inputFields[index]?.options && inputFields[index]?.options.length > 1;
  const defaultTitle = inputFields[index]?.title;

  const isAddMoreOptionAllowed = inputFields.reduce((result: number, field: any) => {
    const { options: optn } = field;
    return result + (optn ? optn.length : 0);
  }, 0);

  const handleAddListItem = (helper: any) => {
    helper.push({ title: '', description: '' });
    onListItemAddClick(options);
  };

  const handleRemoveListItem = (helper: any, idx: number) => {
    helper.remove(idx);
    onListItemRemoveClick(idx);
  };

  const handleInputChange = (
    event: any,
    key: string,
    itemIndex: number | null = null,
    isOption: boolean = false
  ) => {
    const { value } = event.target;
    const payload = { key, itemIndex, isOption };
    onInputChange(value, payload);
  };

  return (
    <div className={styles.WrapperBackground} key={index.toString()}>
      <div className={styles.Section}>
        <div>List {index + 1}</div>
        <div>
          {inputFields.length > 1 && (
            <DeleteIcon
              title="Remove"
              className={styles.ListDeleteIcon}
              onClick={onListRemoveClick}
            />
          )}
        </div>
      </div>

      <div className={styles.ListReplyWrapper}>
        {translation && <div className={styles.Translation}>{translation.title}</div>}
        <FormControl fullWidth error={isListTitleError} className={styles.FormControl}>
          <TextField
            label={sectionLabel}
            placeholder={t(`List ${index + 1} title (Max 24 char.)`)}
            variant="outlined"
            onChange={(e: any) => handleInputChange(e, 'title')}
            className={styles.TextField}
            error={isListTitleError}
            value={defaultTitle}
          />
          {errors.templateButtons && touched.templateButtons && touched.templateButtons[index] ? (
            <FormHelperText>{errors.templateButtons[index]?.title}</FormHelperText>
          ) : null}
        </FormControl>

        <div>
          <FieldArray
            name={`templateButtons[${index}].options`}
            render={(arrayHelpers) =>
              options.map((itemRow: any, itemIndex: any) => (
                // disabling eslint for this as we have no other unique way to define a key
                // eslint-disable-next-line react/no-array-index-key
                <div key={itemIndex}>
                  <div className={styles.ListReplyItemWrapper}>
                    <div className={styles.ListReplyItemContent}>
                      <div className={styles.TextFieldWrapper}>
                        {translation?.options && translation.options.length > itemIndex && (
                          <div className={styles.Translation}>
                            {translation.options[itemIndex].title}
                          </div>
                        )}
                        <FormControl
                          fullWidth
                          error={isError('title', itemIndex)}
                          className={styles.FormControl}
                        >
                          <TextField
                            placeholder={`Title ${itemIndex + 1} (Max 24 char.)`}
                            variant="outlined"
                            label={`Enter list item ${itemIndex + 1} title*`}
                            onChange={(e: any) => handleInputChange(e, 'title', itemIndex, true)}
                            className={styles.TextField}
                            error={isError('title', itemIndex)}
                            value={itemRow.title}
                            InputProps={{
                              endAdornment: itemIndex !== 0 && showDeleteIcon && (
                                <CrossIcon
                                  title="Remove"
                                  className={styles.ListDeleteIcon}
                                  onClick={() => handleRemoveListItem(arrayHelpers, itemIndex)}
                                />
                              ),
                            }}
                          />
                          {isError('title', itemIndex) && (
                            <FormHelperText className={styles.HelperText}>
                              {errors.templateButtons[index].options[itemIndex].title}
                            </FormHelperText>
                          )}
                        </FormControl>
                      </div>

                      <div className={styles.TextFieldWrapper}>
                        {translation?.options &&
                          translation.options.length > itemIndex &&
                          translation.options[itemIndex].description && (
                            <div className={styles.Translation}>
                              {translation.options[itemIndex].description}
                            </div>
                          )}
                        <FormControl
                          fullWidth
                          error={isError('description', itemIndex)}
                          className={styles.FormControl}
                        >
                          <TextField
                            placeholder={`Description ${itemIndex + 1} (Max 60 char.)`}
                            variant="outlined"
                            label={`Enter list item ${itemIndex + 1} description`}
                            onChange={(e: any) =>
                              handleInputChange(e, 'description', itemIndex, true)
                            }
                            className={styles.TextField}
                            error={isError('description', itemIndex)}
                            value={itemRow.description}
                          />
                          {isError('description', itemIndex) ? (
                            <FormHelperText>
                              {errors.templateButtons[index].options[itemIndex].description}
                            </FormHelperText>
                          ) : null}
                        </FormControl>
                      </div>
                    </div>
                  </div>
                  <div className={styles.ActionButtons}>
                    {isAddMoreOptionAllowed < 10 &&
                      inputFields.length === index + 1 &&
                      options.length === itemIndex + 1 && (
                        <Button
                          color="primary"
                          className={styles.AddButton}
                          onClick={onListAddClick}
                          startIcon={<AddIcon className={styles.AddIcon} />}
                        >
                          {t('Add another list')}
                        </Button>
                      )}
                    {isAddMoreOptionAllowed < 10 && options.length === itemIndex + 1 && (
                      <Button
                        color="primary"
                        className={styles.AddButton}
                        onClick={() => handleAddListItem(arrayHelpers)}
                        startIcon={<AddIcon className={styles.AddIcon} />}
                      >
                        {t('Add another list item')}
                      </Button>
                    )}
                  </div>
                </div>
              ))
            }
          />
        </div>
      </div>
    </div>
  );
}
Example #25
Source File: TemplateOptions.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
TemplateOptions: React.SFC<TemplateOptionsProps> = ({
  isAddButtonChecked,
  templateType,
  inputFields,
  form: { touched, errors, values },
  onAddClick,
  onRemoveClick,
  onTemplateTypeChange,
  onInputChange,
  disabled = false,
}) => {
  const buttonTitle = 'Button Title';
  const buttonValue = 'Button Value';
  const buttonTitles: any = {
    CALL_TO_ACTION: 'Call to action',
    QUICK_REPLY: 'Quick Reply',
  };

  const handleAddClick = (helper: any, type: boolean) => {
    const obj = type ? { type: '', value: '', title: '' } : { value: '' };
    helper.push(obj);
    onAddClick();
  };

  const handleRemoveClick = (helper: any, idx: number) => {
    helper.remove(idx);
    onRemoveClick(idx);
  };

  const addButton = (helper: any, type: boolean = false) => {
    const title = templateType ? buttonTitles[templateType] : '';
    const buttonClass =
      templateType === QUICK_REPLY ? styles.QuickReplyAddButton : styles.CallToActionAddButton;
    return (
      <Button
        className={buttonClass}
        variant="outlined"
        color="primary"
        onClick={() => handleAddClick(helper, type)}
      >
        Add {title}
      </Button>
    );
  };

  const getButtons = (row: any, index: number, arrayHelpers: any) => {
    const { type, title, value }: any = row;
    let template: any = null;

    const isError = (key: string) =>
      !!(
        errors.templateButtons &&
        touched.templateButtons &&
        errors.templateButtons[index] &&
        errors.templateButtons[index][key]
      );

    if (templateType === CALL_TO_ACTION) {
      template = (
        <div className={styles.CallToActionContainer} key={index.toString()}>
          <div className={styles.CallToActionWrapper}>
            <div>
              <div className={styles.RadioStyles}>
                <FormControl fullWidth error={isError('type')} className={styles.FormControl}>
                  <RadioGroup
                    aria-label="action-radio-buttons"
                    name="action-radio-buttons"
                    row
                    value={type}
                    onChange={(e: any) => onInputChange(e, row, index, 'type')}
                    className={styles.RadioGroup}
                  >
                    <FormControlLabel
                      value="phone_number"
                      control={
                        <Radio
                          color="primary"
                          disabled={
                            disabled ||
                            (index === 0 &&
                              inputFields.length > 1 &&
                              inputFields[0].type !== 'phone_number') ||
                            (index > 0 &&
                              inputFields[0].type &&
                              inputFields[0].type === 'phone_number')
                          }
                        />
                      }
                      label="Phone number"
                    />
                    <FormControlLabel
                      value="url"
                      control={
                        <Radio
                          color="primary"
                          disabled={
                            disabled ||
                            (index === 0 &&
                              inputFields.length > 1 &&
                              inputFields[0].type !== 'url') ||
                            (index > 0 && inputFields[0].type && inputFields[0].type === 'url')
                          }
                        />
                      }
                      label="URL"
                    />
                  </RadioGroup>
                  {errors.templateButtons &&
                  touched.templateButtons &&
                  touched.templateButtons[index] ? (
                    <FormHelperText>{errors.templateButtons[index]?.type}</FormHelperText>
                  ) : null}
                </FormControl>
              </div>
              <div>
                {inputFields.length > 1 ? (
                  <DeleteIcon onClick={() => handleRemoveClick(arrayHelpers, index)} />
                ) : null}
              </div>
            </div>
            <div className={styles.TextFieldWrapper}>
              <FormControl fullWidth error={isError('title')} className={styles.FormControl}>
                <TextField
                  disabled={disabled}
                  title={title}
                  defaultValue={value}
                  placeholder={buttonTitle}
                  variant="outlined"
                  label={buttonTitle}
                  onBlur={(e: any) => onInputChange(e, row, index, 'title')}
                  className={styles.TextField}
                  error={isError('title')}
                />
                {errors.templateButtons &&
                touched.templateButtons &&
                touched.templateButtons[index] ? (
                  <FormHelperText>{errors.templateButtons[index]?.title}</FormHelperText>
                ) : null}
              </FormControl>
            </div>
            <div className={styles.TextFieldWrapper}>
              <FormControl fullWidth error={isError('value')} className={styles.FormControl}>
                <TextField
                  title={value}
                  defaultValue={value}
                  disabled={disabled}
                  placeholder={buttonValue}
                  variant="outlined"
                  label={buttonValue}
                  onBlur={(e: any) => onInputChange(e, row, index, 'value')}
                  className={styles.TextField}
                  error={isError('value')}
                />
                {errors.templateButtons &&
                touched.templateButtons &&
                touched.templateButtons[index] ? (
                  <FormHelperText>{errors.templateButtons[index]?.value}</FormHelperText>
                ) : null}
              </FormControl>
            </div>
          </div>
          <div>
            {inputFields.length === index + 1 && inputFields.length !== 2
              ? addButton(arrayHelpers, true)
              : null}
          </div>
        </div>
      );
    }

    if (templateType === QUICK_REPLY) {
      template = (
        <div className={styles.QuickReplyContainer} key={index.toString()}>
          <div className={styles.QuickReplyWrapper}>
            <FormControl fullWidth error={isError('value')} className={styles.FormControl}>
              <TextField
                disabled={disabled}
                defaultValue={value}
                title={title}
                placeholder={`Quick reply ${index + 1} title`}
                label={`Quick reply ${index + 1} title`}
                variant="outlined"
                onBlur={(e: any) => onInputChange(e, row, index, 'value')}
                className={styles.TextField}
                error={isError('value')}
                InputProps={{
                  endAdornment: inputFields.length > 1 && !disabled && (
                    <CrossIcon
                      className={styles.RemoveIcon}
                      title="Remove"
                      onClick={() => handleRemoveClick(arrayHelpers, index)}
                    />
                  ),
                }}
              />
              {errors.templateButtons &&
              touched.templateButtons &&
              touched.templateButtons[index] ? (
                <FormHelperText>{errors.templateButtons[index]?.value}</FormHelperText>
              ) : null}
            </FormControl>
          </div>
          <div>
            {inputFields.length === index + 1 && inputFields.length !== 3
              ? addButton(arrayHelpers)
              : null}
          </div>
        </div>
      );
    }
    return template;
  };

  const radioTemplateType = (
    <div>
      <RadioGroup
        aria-label="template-type"
        name="template-type"
        row
        value={templateType}
        onChange={(event) => onTemplateTypeChange(event.target.value)}
      >
        <div className={styles.RadioLabelWrapper}>
          <FormControlLabel
            value={CALL_TO_ACTION}
            control={<Radio color="primary" disabled={disabled} />}
            label="Call to actions"
            classes={{ root: styles.RadioLabel }}
          />
          <Tooltip title={GUPSHUP_CALL_TO_ACTION} placement="right" tooltipClass={styles.Tooltip}>
            <InfoIcon />
          </Tooltip>
        </div>
        <div className={styles.RadioLabelWrapper}>
          <FormControlLabel
            value={QUICK_REPLY}
            control={<Radio color="primary" disabled={disabled} />}
            label="Quick replies"
            className={styles.RadioLabel}
          />
          <Tooltip title={GUPSHUP_QUICK_REPLY} placement="right" tooltipClass={styles.Tooltip}>
            <InfoIcon />
          </Tooltip>
        </div>
      </RadioGroup>

      {templateType ? (
        <div
          className={
            templateType === QUICK_REPLY
              ? styles.QuickTemplateFields
              : styles.CallToActionTemplateFields
          }
        >
          <FieldArray
            name="templateButtons"
            render={(arrayHelpers) =>
              values.templateButtons.map((row: any, index: any) =>
                getButtons(row, index, arrayHelpers)
              )
            }
          />
        </div>
      ) : null}
    </div>
  );

  return <div>{isAddButtonChecked && radioTemplateType}</div>;
}
Example #26
Source File: StationSelect.tsx    From metro-fare with MIT License 4 votes vote down vote up
StationSelect = ({
  title,
  value,
  onChange,
}: {
  title: string;
  value: string;
  onChange: Function;
}) => {
  const { t: translate, i18n } = useTranslation();
  const [lineType, setLineType] = useState<LineType>(LineType.MRT_BLUE);
  const lineElementId = `${title}-line-native-required`;
  const selectElementId = `${title}-native-required`;
  const isStationAvailable = (station: Station) => station.lineType === lineType && !station.isNotAvailable
  const stationsName = STATIONS.filter(isStationAvailable);

  const handleLineTypeSelectChange = (value: string) => {
    setLineType(value as LineType);
    onChange("");
  };

  useEffect(() => {
    if (Object.values(BTS_SILOM_STATION_ID).find((btsId) => btsId === value)) {
      setLineType(LineType.BTS_SILOM);
    } else if (Object.values(BTS_SUKHUMVIT_STATION_ID).find((btsId) => btsId === value)) {
      setLineType(LineType.BTS_SUKHUMVIT);
    } else if (Object.values(ARL_STATION_ID).find((arlId) => arlId === value)) {
      setLineType(LineType.ARL);
    } else if (Object.values(BRT_STATION_ID).find((brtId) => brtId === value)) {
      setLineType(LineType.BRT);
    } else if (value.length !== 0) {
      setLineType(LineType.MRT_BLUE);
    }
  }, [value]);

  return (
    <section>
      <FormControl className="line-type-select" required variant="standard">
        <InputLabel htmlFor={lineElementId}>
          {translate("lineType.line")}
        </InputLabel>
        <Select
          native
          onChange={(e: any) => handleLineTypeSelectChange(e.target.value)}
          name={"Line"}
          value={lineType}
          inputProps={{
            id: lineElementId,
          }}
          variant="standard">
          <option value={"MRT_BLUE"}>{translate("lineType.mrtBlue")}</option>
          <option value={"BTS_SILOM"}>{translate("lineType.btsSilom")}</option>
          <option value={"BTS_SUKHUMVIT"}>{translate("lineType.btsSukhumvit")}</option>
          <option value={"ARL"}>{translate("lineType.arl")}</option>
          <option value={"BRT"}>{translate("lineType.brt")}</option>
        </Select>
        <FormHelperText>{translate("common.required")}</FormHelperText>
      </FormControl>
      <FormControl className="station-select" required variant="standard">
        <InputLabel htmlFor={selectElementId}>{title}</InputLabel>
        <Select
          native
          onChange={(e) => onChange(e.target.value)}
          name={title}
          value={value}
          inputProps={{
            id: selectElementId,
          }}
          variant="standard">
          <option value="" disabled></option>
          {stationsName.map((station: Station) => {
            const label = `(${station.id}) ${getStationName(station, i18n.language)}`;
            return (
              <option key={station.id} value={station.id}>
                {label}
              </option>
            );
          })}
        </Select>
        <FormHelperText>{translate("common.required")}</FormHelperText>
      </FormControl>
    </section>
  );
}
Example #27
Source File: Content.tsx    From Demae with MIT License 4 votes vote down vote up
Form = ({ provider }: { provider: ProviderDraft }) => {
	const classes = useStyles()
	const [setProcessing] = useProcessing()
	const [setMessage] = useSnackbar()
	const [thumbnail, setThumbnail] = useState<File | undefined>()
	const [cover, setCover] = useState<File | undefined>()
	const [name] = useTextField(provider.name)
	const [caption] = useTextField(provider.caption)
	const [description] = useTextField(provider.description)
	const providerCapabilities = provider.capabilities || []
	const [capabilities, setCapabilities] = useState<{ [key in Capability]: boolean }>({
		"download": providerCapabilities.includes("download"),
		"instore": providerCapabilities.includes("instore"),
		"online": providerCapabilities.includes("online"),
		"pickup": providerCapabilities.includes("pickup")
	})

	const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
		setCapabilities({ ...capabilities, [event.target.name]: event.target.checked });
	};

	const capabilitiesError = Object.values(capabilities).filter((v) => v).length < 1;

	const uploadThumbnail = (file: File): Promise<StorageFile | undefined> => {
		const ref = firebase.storage().ref(provider.documentReference.path + "/thumbnail.jpg")
		return new Promise((resolve, reject) => {
			ref.put(file).then(async (snapshot) => {
				if (snapshot.state === "success") {
					const storageFile = new StorageFile()
					if (snapshot.metadata.contentType) {
						storageFile.mimeType = snapshot.metadata.contentType
					}
					storageFile.path = ref.fullPath
					resolve(storageFile)
				} else {
					reject(undefined)
				}
			})
		})
	}

	const uploadCover = (file: File): Promise<StorageFile | undefined> => {
		const ref = firebase.storage().ref(provider.documentReference.path + "/cover.jpg")
		return new Promise((resolve, reject) => {
			ref.put(file).then(async (snapshot) => {
				if (snapshot.state === "success") {
					const storageFile = new StorageFile()
					if (snapshot.metadata.contentType) {
						storageFile.mimeType = snapshot.metadata.contentType
					}
					storageFile.path = ref.fullPath
					resolve(storageFile)
				} else {
					reject(undefined)
				}
			})
		})
	}

	const [isEditing, setEdit] = useEdit(async (event) => {
		event.preventDefault()
		if (!provider) return
		setProcessing(true)
		if (thumbnail) {
			const thumbnailImage = await uploadThumbnail(thumbnail)
			if (thumbnailImage) {
				provider.thumbnailImage = thumbnailImage
			}
		}
		if (cover) {
			const coverImage = await uploadCover(cover)
			if (coverImage) {
				provider.coverImage = coverImage
			}
		}
		try {
			provider.name = name.value as string
			provider.caption = caption.value as string
			provider.description = description.value as string
			provider.capabilities = Object.keys(capabilities).filter(value => capabilities[value]) as Capability[]
			await provider.save()
		} catch (error) {
			console.log(error)
		}
		setProcessing(false)
		setMessage("success", "Change your provider informations.")
		setEdit(false)
	})

	useContentToolbar(() => {
		if (!provider) return <></>
		if (isEditing) {
			return (
				<Box display="flex" flexGrow={1} justifyContent="space-between" paddingX={1}>
					<Button variant="outlined" color="primary" size="small" onClick={() => setEdit(false)}>Cancel</Button>
					<Button variant="contained" color="primary" size="small" type="submit" disabled={capabilitiesError}
					>Save</Button>
				</Box>
			)
		}
		return (
			<Box display="flex" flexGrow={1} justifyContent="space-between" paddingX={1}>
				<Box display="flex" flexGrow={1} justifyContent="flex-end">
					<Button variant="outlined" color="primary" size="small" onClick={() => setEdit(true)}>Edit</Button>
				</Box>
			</Box>
		)
	})

	if (isEditing) {
		return (
			<Container maxWidth="sm">
				<Box padding={2}>
					<Typography variant="h1" gutterBottom>Shop</Typography>
					<Paper>
						<Box display="flex" position="relative" flexGrow={1}>
							<Box display="flex" flexGrow={1} height={300}>
								<DndCard
									url={provider?.coverImageURL()}
									onDrop={(files) => {
										const file = files[0] as File
										setCover(file)
									}} />
							</Box>
							<Box display="flex" position="absolute" zIndex={1050} flexGrow={1} width={120} height={120} border={2} borderColor="white" borderRadius="50%" bottom={-16} left={16} style={{ overflow: "hidden" }}>
								<DndCard
									url={provider?.thumbnailImageURL()}
									onDrop={(files) => {
										const file = files[0] as File
										setThumbnail(file)
									}} />
							</Box>
						</Box>
						<Box padding={2} paddingTop={5}>
							<Box paddingBottom={2}>
								<Typography variant="subtitle1" gutterBottom>Name</Typography>
								<TextField variant="outlined" margin="dense" required {...name} fullWidth />
							</Box>
							<Box paddingBottom={2}>
								<Typography variant="subtitle1" gutterBottom>Caption</Typography>
								<TextField variant="outlined" margin="dense" required {...caption} fullWidth />
							</Box>
							<Box paddingBottom={2}>
								<Typography variant="subtitle1" gutterBottom>Description</Typography>
								<TextField variant="outlined" margin="dense" required fullWidth multiline rows={8} {...description} />
							</Box>
						</Box>

						<Box paddingX={2} paddingBottom={1}>
							<Typography variant="subtitle1" gutterBottom>Sales method</Typography>
							<FormControl required error={capabilitiesError} component="fieldset">
								<FormLabel component="legend">Please select at least one selling method</FormLabel>
								<FormGroup>
									<FormControlLabel
										control={<Checkbox checked={capabilities.download} onChange={handleChange} name="download" />}
										label="Download"
									/>
									<FormControlLabel
										control={<Checkbox checked={capabilities.instore} onChange={handleChange} name="instore" />}
										label="In-Store Sales"
									/>
									<FormControlLabel
										control={<Checkbox checked={capabilities.online} onChange={handleChange} name="online" />}
										label="Online Sales"
									/>
									<FormControlLabel
										control={<Checkbox checked={capabilities.pickup} onChange={handleChange} name="pickup" />}
										label="Pickup"
									/>
								</FormGroup>
								<FormHelperText>You can choose multiple sales methods.</FormHelperText>
							</FormControl>
						</Box>
					</Paper>
					<Box padding={1}>
						<Typography variant="body2" color="textSecondary" gutterBottom>ID: {provider.id}</Typography>
					</Box>
				</Box>
			</Container>
		)
	}

	return (
		<Container maxWidth="sm">
			<Box padding={2}>
				<Typography variant="h1" gutterBottom>Shop</Typography>
				<Paper>
					<Box display="flex" position="relative" flexGrow={1}>
						<Box display="flex" flexGrow={1} height={300}>
							<Avatar variant="square" src={provider.coverImageURL()} style={{
								minHeight: "300px",
								width: "100%"
							}}>
								<ImageIcon />
							</Avatar>
						</Box>
						<Box display="flex" position="absolute" zIndex={1050} flexGrow={1} width={120} height={120} border={2} borderColor="white" borderRadius="50%" bottom={-16} left={16} style={{ overflow: "hidden" }}>
							<Avatar variant="square" src={provider.thumbnailImageURL()} style={{
								minHeight: "64px",
								height: "100%",
								width: "100%"
							}}>
								<ImageIcon />
							</Avatar>
						</Box>
					</Box>

					<Box padding={2} paddingTop={5}>
						<Box paddingBottom={2}>
							<Typography variant="subtitle1" gutterBottom>Name</Typography>
							<Typography variant="body1" gutterBottom>{provider.name}</Typography>
						</Box>
						<Box paddingBottom={2}>
							<Typography variant="subtitle1" gutterBottom>Caption</Typography>
							<Typography variant="body1" gutterBottom>{provider.caption}</Typography>
						</Box>
						<Box paddingBottom={2}>
							<Typography variant="subtitle1" gutterBottom>Description</Typography>
							<Typography variant="body1" gutterBottom>{provider.description}</Typography>
						</Box>
					</Box>

					<Box paddingX={2} paddingBottom={1}>
						<Typography variant="subtitle1" gutterBottom>Sales method</Typography>
						<FormControl disabled component="fieldset">
							<FormGroup>
								<FormControlLabel
									control={<Checkbox checked={capabilities.download} onChange={handleChange} name="download" />}
									label="Download"
								/>
								<FormControlLabel
									control={<Checkbox checked={capabilities.instore} onChange={handleChange} name="instore" />}
									label="In-Store Sales"
								/>
								<FormControlLabel
									control={<Checkbox checked={capabilities.online} onChange={handleChange} name="online" />}
									label="Online Sales"
								/>
								<FormControlLabel
									control={<Checkbox checked={capabilities.pickup} onChange={handleChange} name="pickup" />}
									label="Pickup"
								/>
							</FormGroup>
						</FormControl>
					</Box>
				</Paper>
				<Box padding={1}>
					<Typography variant="body2" color="textSecondary" gutterBottom>ID: {provider.id}</Typography>
				</Box>
			</Box>
		</Container>
	)
}
Example #28
Source File: BillingPage.tsx    From clearflask with Apache License 2.0 4 votes vote down vote up
render() {
    if (!this.props.account) {
      return 'Need to login to see this page';
    }

    const status = this.props.accountStatus === Status.FULFILLED ? this.props.accountBillingStatus : this.props.accountStatus;
    if (!this.props.accountBilling || status !== Status.FULFILLED) {
      return (
        <Loader skipFade status={status} />
      );
    }

    var cardNumber, cardExpiry, cardStateIcon;
    if (!!this.props.accountBilling?.payment) {
      cardNumber = (
        <>
          <span className={this.props.classes.blurry}>5200&nbsp;8282&nbsp;8282&nbsp;</span>
          {this.props.accountBilling.payment.last4}
        </>
      );
      var expiryColor;
      if (new Date().getFullYear() % 100 >= this.props.accountBilling.payment.expiryYear % 100) {
        if (new Date().getMonth() + 1 === this.props.accountBilling.payment.expiryMonth) {
          expiryColor = this.props.theme.palette.warning.main;
        } else if (new Date().getMonth() + 1 > this.props.accountBilling.payment.expiryMonth) {
          expiryColor = this.props.theme.palette.error.main;
        }
      }
      cardExpiry = (
        <span style={expiryColor && { color: expiryColor }}>
          {this.props.accountBilling.payment.expiryMonth}
          &nbsp;/&nbsp;
          {this.props.accountBilling.payment.expiryYear % 100}
        </span>
      );
    } else {
      cardNumber = (<span className={this.props.classes.blurry}>5200&nbsp;8282&nbsp;8282&nbsp;8210</span>);
      cardExpiry = (<span className={this.props.classes.blurry}>06 / 32</span>);
    }
    var hasAvailablePlansToSwitch: boolean = (this.props.accountBilling?.availablePlans || [])
      .filter(p => p.basePlanId !== this.props.accountBilling?.plan.basePlanId)
      .length > 0;
    var cardState: 'active' | 'warn' | 'error' = 'active';
    var paymentTitle, paymentDesc, showContactSupport, showSetPayment, setPaymentTitle, setPaymentAction, showCancelSubscription, showResumePlan, resumePlanDesc, planTitle, planDesc, showPlanChange, endOfTermChangeToPlanTitle, endOfTermChangeToPlanDesc, switchPlanTitle;
    switch (this.props.account.subscriptionStatus) {
      case Admin.SubscriptionStatus.Active:
        if (this.props.accountBilling?.plan.basePlanId === TeammatePlanId) {
          paymentTitle = 'No payment required';
          paymentDesc = 'While you only access external projects, payments are made by the project owner. No payment is required from you at this time.';
          cardState = 'active';
          showSetPayment = false;
          showCancelSubscription = false;
          planTitle = 'You are not on a plan';
          planDesc = 'While you only access external projects, you are not required to be on a plan. If you decide to create a project under your account, you will be able to choose a plan and your trial will begin.';
          if (hasAvailablePlansToSwitch) {
            showPlanChange = true;
            switchPlanTitle = 'Choose plan'
          }
        } else {
          paymentTitle = 'Automatic renewal is active';
          paymentDesc = 'You will be automatically billed at the next cycle and your plan will be renewed.';
          cardState = 'active';
          showSetPayment = true;
          setPaymentTitle = 'Update payment method';
          showCancelSubscription = true;
          planTitle = 'Your plan is active';
          planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan.`;
          if (hasAvailablePlansToSwitch) {
            planDesc += ' If you upgrade your plan, changes will reflect immediately. If you downgrade your plan, changes will take effect at the end of the term.';
            showPlanChange = true;
          }
        }
        break;
      case Admin.SubscriptionStatus.ActiveTrial:
        if (this.props.accountBilling?.payment) {
          paymentTitle = 'Automatic renewal is active';
          if (this.props.accountBilling?.billingPeriodEnd) {
            paymentDesc = (
              <>
                Your first payment will be automatically billed at the end of the trial period in&nbsp;<TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />.
              </>
            );
          } else {
            paymentDesc = `Your first payment will be automatically billed at the end of the trial period.`;
          }
          cardState = 'active';
          showSetPayment = true;
          setPaymentTitle = 'Update payment method';
          planTitle = 'Your plan is active';
          planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan.`;
          if (hasAvailablePlansToSwitch) {
            planDesc += ' If you switch plans now, your first payment at the end of your trial will reflect your new plan.';
            showPlanChange = true;
          }
        } else {
          paymentTitle = 'Automatic renewal requires a payment method';
          paymentDesc = 'To continue using our service beyond the trial period, add a payment method to enable automatic renewal.';
          cardState = 'warn';
          showSetPayment = true;
          setPaymentTitle = 'Add payment method';
          planTitle = 'Your plan is active until your trial ends';
          if (this.props.accountBilling?.billingPeriodEnd) {
            planDesc = (
              <>
                You have full access to your {this.props.accountBilling.plan.title} plan until your trial expires in&nbsp;<TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />. Add a payment method to continue using our service beyond the trial period.
              </>
            );
          } else {
            planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan until your trial expires. Add a payment method to continue using our service beyond the trial period.`;
          }
          if (hasAvailablePlansToSwitch) {
            showPlanChange = true;
          }
        }
        break;
      case Admin.SubscriptionStatus.ActivePaymentRetry:
        paymentTitle = 'Automatic renewal is having issues with your payment method';
        paymentDesc = 'We are having issues charging your payment method. We will retry your payment method again soon and we may block your service if unsuccessful.';
        cardState = 'error';
        showSetPayment = true;
        if (this.props.accountBilling?.payment) {
          setPaymentTitle = 'Update payment method';
        } else {
          setPaymentTitle = 'Add payment method';
        }
        showCancelSubscription = true;
        planTitle = 'Your plan is active';
        planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan; however, there is an issue with your payment. Please resolve it before you can change your plan.`;
        break;
      case Admin.SubscriptionStatus.ActiveNoRenewal:
        paymentTitle = 'Automatic renewal is inactive';
        paymentDesc = 'Resume automatic renewal to continue using our service beyond the next billing cycle.';
        cardState = 'warn';
        showSetPayment = true;
        setPaymentTitle = 'Resume with new payment method';
        setPaymentAction = 'Add and resume subscription';
        showResumePlan = true;
        resumePlanDesc = 'Your subscription will no longer be cancelled. You will be automatically billed for our service at the next billing cycle.';
        if (this.props.accountBilling?.billingPeriodEnd) {
          planTitle = (
            <>
              Your plan is active until&nbsp;<TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />
            </>
          );
        } else {
          planTitle = 'Your plan is active until the end of the billing cycle';
        }
        planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan until it cancels. Please resume your payments to continue using our service beyond next billing cycle.`;
        break;
      case Admin.SubscriptionStatus.Limited:
        paymentTitle = 'Automatic renewal is active';
        paymentDesc = 'You will be automatically billed at the next cycle and your plan will be renewed.';
        cardState = 'active';
        showSetPayment = true;
        setPaymentTitle = 'Update payment method';
        showCancelSubscription = true;
        planTitle = 'Your plan is limited';
        planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan due to going over your plan limits. Please resolve all issues to continue using our service.`;
        if (hasAvailablePlansToSwitch) {
          planDesc += ' If you upgrade your plan, changes will reflect immediately. If you downgrade your plan, changes will take effect at the end of the term.';
          showPlanChange = true;
        }
        break;
      case Admin.SubscriptionStatus.NoPaymentMethod:
        paymentTitle = 'Automatic renewal is inactive';
        paymentDesc = 'Your trial has expired. To continue using our service, add a payment method to enable automatic renewal.';
        cardState = 'error';
        showSetPayment = true;
        setPaymentTitle = 'Add payment method';
        planTitle = 'Your trial plan has expired';
        planDesc = `To continue using your ${this.props.accountBilling.plan.title} plan, please add a payment method.`;
        break;
      case Admin.SubscriptionStatus.Blocked:
        paymentTitle = 'Payments are blocked';
        paymentDesc = 'Contact support to reinstate your account.';
        showContactSupport = true;
        cardState = 'error';
        planTitle = 'Your plan is inactive';
        planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan due to a payment issue. Please resolve all issues to continue using our service.`;
        break;
      case Admin.SubscriptionStatus.Cancelled:
        paymentTitle = 'Automatic renewal is inactive';
        paymentDesc = 'Resume automatic renewal to continue using our service.';
        cardState = 'error';
        showSetPayment = true;
        setPaymentTitle = 'Update payment method';
        if (this.props.accountBilling?.payment) {
          showResumePlan = true;
          resumePlanDesc = 'Your subscription will no longer be cancelled. You will be automatically billed for our service starting now.';
        }
        planTitle = 'Your plan is cancelled';
        planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan since you cancelled your subscription. Please resume payment to continue using our service.`;
        break;
    }
    if (this.props.accountBilling?.endOfTermChangeToPlan) {
      endOfTermChangeToPlanTitle = `Pending plan change to ${this.props.accountBilling.endOfTermChangeToPlan.title}`;
      endOfTermChangeToPlanDesc = `Your requested change of plans to ${this.props.accountBilling.endOfTermChangeToPlan.title} plan will take effect at the end of the term.`;
    }
    switch (cardState) {
      case 'active':
        cardStateIcon = (<ActiveIcon color='primary' />);
        break;
      case 'warn':
        cardStateIcon = (<WarnIcon style={{ color: this.props.theme.palette.warning.main }} />);
        break;
      case 'error':
        cardStateIcon = (<ErrorIcon color='error' />);
        break;
    }
    const creditCard = (
      <TourAnchor anchorId='settings-credit-card' placement='bottom'>
        <CreditCard
          className={this.props.classes.creditCard}
          brand={cardStateIcon}
          numberInput={cardNumber}
          expiryInput={cardExpiry}
          cvcInput={(<span className={this.props.classes.blurry}>642</span>)}
        />
      </TourAnchor>
    );

    const paymentStripeAction: PaymentStripeAction | undefined = this.props.accountBilling?.paymentActionRequired?.actionType === 'stripe-next-action'
      ? this.props.accountBilling?.paymentActionRequired as PaymentStripeAction : undefined;
    const paymentActionOnClose = () => {
      this.setState({
        paymentActionOpen: undefined,
        paymentActionUrl: undefined,
        paymentActionMessage: undefined,
        paymentActionMessageSeverity: undefined,
      });
      if (this.refreshBillingAfterPaymentClose) {
        ServerAdmin.get().dispatchAdmin().then(d => d.accountBillingAdmin({
          refreshPayments: true,
        }));
      }
    };
    const paymentAction = paymentStripeAction ? (
      <>
        <Message
          className={this.props.classes.paymentActionMessage}
          message='One of your payments requires additional information'
          severity='error'
          action={(
            <SubmitButton
              isSubmitting={!!this.state.paymentActionOpen && !this.state.paymentActionUrl && !this.state.paymentActionMessage}
              onClick={() => {
                this.setState({ paymentActionOpen: true });
                this.loadActionIframe(paymentStripeAction);
              }}
            >Open</SubmitButton>
          )}
        />
        <Dialog
          open={!!this.state.paymentActionOpen}
          onClose={paymentActionOnClose}
        >
          {this.state.paymentActionMessage ? (
            <>
              <DialogContent>
                <Message
                  message={this.state.paymentActionMessage}
                  severity={this.state.paymentActionMessageSeverity || 'info'}
                />
              </DialogContent>
              <DialogActions>
                <Button onClick={paymentActionOnClose}>Dismiss</Button>
              </DialogActions>
            </>
          ) : (this.state.paymentActionUrl ? (
            <iframe
              title='Complete outstanding payment action'
              width={this.getFrameActionWidth()}
              height={400}
              src={this.state.paymentActionUrl}
            />
          ) : (
            <div style={{
              minWidth: this.getFrameActionWidth(),
              minHeight: 400,
            }}>
              <LoadingPage />
            </div>
          ))}
        </Dialog>
      </>
    ) : undefined;

    const hasPayable = (this.props.accountBilling?.accountPayable || 0) > 0;
    const hasReceivable = (this.props.accountBilling?.accountReceivable || 0) > 0;
    const payment = (
      <Section
        title='Payment'
        preview={(
          <div className={this.props.classes.creditCardContainer}>
            {creditCard}
            <Box display='grid' gridTemplateAreas='"payTtl payAmt" "rcvTtl rcvAmt"' alignItems='center' gridGap='10px 10px'>
              {hasPayable && (
                <>
                  <Box gridArea='payTtl'><Typography component='div'>Credits:</Typography></Box>
                  <Box gridArea='payAmt' display='flex'>
                    <Typography component='div' variant='h6' color='textSecondary' style={{ alignSelf: 'flex-start' }}>{'$'}</Typography>
                    <Typography component='div' variant='h4' color={hasPayable ? 'primary' : undefined}>
                      {this.props.accountBilling?.accountPayable || 0}
                    </Typography>
                  </Box>
                </>
              )}
              {(hasReceivable || !hasPayable) && (
                <>
                  <Box gridArea='rcvTtl'><Typography component='div'>Overdue:</Typography></Box>
                  <Box gridArea='rcvAmt' display='flex'>
                    <Typography component='div' variant='h6' color='textSecondary' style={{ alignSelf: 'flex-start' }}>{'$'}</Typography>
                    <Typography component='div' variant='h4' color={hasReceivable ? 'error' : undefined}>
                      {this.props.accountBilling?.accountReceivable || 0}
                    </Typography>
                  </Box>
                </>
              )}
            </Box>
          </div>
        )}
        content={(
          <div className={this.props.classes.actionContainer}>
            <p><Typography variant='h6' color='textPrimary' component='div'>{paymentTitle}</Typography></p>
            <Typography color='textSecondary'>{paymentDesc}</Typography>
            <div className={this.props.classes.sectionButtons}>
              {showContactSupport && (
                <Button
                  disabled={this.state.isSubmitting || this.state.showAddPayment}
                  component={Link}
                  to='/contact/support'
                >Contact support</Button>
              )}
              {showSetPayment && (
                <TourAnchor anchorId='settings-add-payment-open' placement='bottom'>
                  {(next, isActive, anchorRef) => (
                    <SubmitButton
                      buttonRef={anchorRef}
                      isSubmitting={this.state.isSubmitting}
                      disabled={this.state.showAddPayment}
                      onClick={() => {
                        trackingBlock(() => {
                          ReactGA.event({
                            category: 'billing',
                            action: this.props.accountBilling?.payment ? 'click-payment-update-open' : 'click-payment-add-open',
                            label: this.props.accountBilling?.plan.basePlanId,
                          });
                        });
                        this.setState({ showAddPayment: true });
                        next();
                      }}
                    >
                      {setPaymentTitle}
                    </SubmitButton>
                  )}
                </TourAnchor>
              )}
              {showCancelSubscription && (
                <SubmitButton
                  isSubmitting={this.state.isSubmitting}
                  disabled={this.state.showCancelSubscription}
                  style={{ color: this.props.theme.palette.error.main }}
                  onClick={() => this.setState({ showCancelSubscription: true })}
                >
                  Cancel payments
                </SubmitButton>
              )}
              {showResumePlan && (
                <SubmitButton
                  isSubmitting={this.state.isSubmitting}
                  disabled={this.state.showResumePlan}
                  color='primary'
                  onClick={() => this.setState({ showResumePlan: true })}
                >
                  Resume payments
                </SubmitButton>
              )}
            </div>
            {paymentAction}
            <Dialog
              open={!!this.state.showAddPayment}
              onClose={() => this.setState({ showAddPayment: undefined })}
            >
              <ElementsConsumer>
                {({ elements, stripe }) => (
                  <TourAnchor anchorId='settings-add-payment-popup' placement='top'>
                    {(next, isActive, anchorRef) => (
                      <div ref={anchorRef}>
                        <DialogTitle>{setPaymentTitle || 'Add new payment method'}</DialogTitle>
                        <DialogContent className={this.props.classes.center}>
                          <StripeCreditCard onFilledChanged={(isFilled) => this.setState({ stripePaymentFilled: isFilled })} />
                          <Collapse in={!!this.state.stripePaymentError}>
                            <Message message={this.state.stripePaymentError} severity='error' />
                          </Collapse>
                        </DialogContent>
                        <DialogActions>
                          <Button onClick={() => this.setState({ showAddPayment: undefined })}>
                            Cancel
                          </Button>
                          <SubmitButton
                            isSubmitting={this.state.isSubmitting}
                            disabled={!this.state.stripePaymentFilled || !elements || !stripe}
                            color='primary'
                            onClick={async () => {
                              const success = await this.onPaymentSubmit(elements!, stripe!);
                              if (success) {
                                next();
                                tourSetGuideState('add-payment', TourDefinitionGuideState.Completed);
                              }
                            }}
                          >{setPaymentAction || 'Add'}</SubmitButton>
                        </DialogActions>
                      </div>
                    )}
                  </TourAnchor>
                )}
              </ElementsConsumer>
            </Dialog>
            <Dialog
              open={!!this.state.showCancelSubscription}
              onClose={() => this.setState({ showCancelSubscription: undefined })}
            >
              <DialogTitle>Stop subscription</DialogTitle>
              <DialogContent className={this.props.classes.center}>
                <DialogContentText>Stop automatic billing of your subscription. Any ongoing subscription will continue to work until it expires.</DialogContentText>
              </DialogContent>
              <DialogActions>
                <Button onClick={() => this.setState({ showCancelSubscription: undefined })}>
                  Cancel
                </Button>
                <SubmitButton
                  isSubmitting={this.state.isSubmitting}
                  style={{ color: this.props.theme.palette.error.main }}
                  onClick={() => {
                    this.setState({ isSubmitting: true });
                    ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
                      accountUpdateAdmin: {
                        cancelEndOfTerm: true,
                      },
                    }).then(() => d.accountBillingAdmin({})))
                      .then(() => this.setState({ isSubmitting: false, showCancelSubscription: undefined }))
                      .catch(er => this.setState({ isSubmitting: false }));
                  }}
                >Stop subscription</SubmitButton>
              </DialogActions>
            </Dialog>
            <Dialog
              open={!!this.state.showResumePlan}
              onClose={() => this.setState({ showResumePlan: undefined })}
            >
              <DialogTitle>Resume subscription</DialogTitle>
              <DialogContent className={this.props.classes.center}>
                <DialogContentText>{resumePlanDesc}</DialogContentText>
              </DialogContent>
              <DialogActions>
                <Button onClick={() => this.setState({ showResumePlan: undefined })}>
                  Cancel
                </Button>
                <SubmitButton
                  isSubmitting={this.state.isSubmitting}
                  color='primary'
                  onClick={() => {
                    this.setState({ isSubmitting: true });
                    ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
                      accountUpdateAdmin: {
                        resume: true,
                      },
                    }).then(() => d.accountBillingAdmin({})))
                      .then(() => this.setState({ isSubmitting: false, showResumePlan: undefined }))
                      .catch(er => this.setState({ isSubmitting: false }));
                  }}
                >Resume subscription</SubmitButton>
              </DialogActions>
            </Dialog>
          </div>
        )}
      />
    );

    const nextInvoicesCursor = this.state.invoices === undefined
      ? this.props.accountBilling?.invoices.cursor
      : this.state.invoicesCursor;
    const invoicesItems = [
      ...(this.props.accountBilling?.invoices.results || []),
      ...(this.state.invoices || []),
    ];
    const invoices = invoicesItems.length <= 0 ? undefined : (
      <Section
        title='Invoices'
        content={(
          <>
            <Table>
              <TableHead>
                <TableRow>
                  <TableCell key='due'>Due</TableCell>
                  <TableCell key='status'>Status</TableCell>
                  <TableCell key='amount'>Amount</TableCell>
                  <TableCell key='desc'>Description</TableCell>
                  <TableCell key='invoiceLink'>Invoice</TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                {invoicesItems.map((invoiceItem, index) => (
                  <TableRow key={index}>
                    <TableCell key='due'><Typography>{new Date(invoiceItem.date).toLocaleDateString()}</Typography></TableCell>
                    <TableCell key='status' align='center'><Typography>{invoiceItem.status}</Typography></TableCell>
                    <TableCell key='amount' align='right'><Typography>{invoiceItem.amount}</Typography></TableCell>
                    <TableCell key='desc'><Typography>{invoiceItem.description}</Typography></TableCell>
                    <TableCell key='invoiceLink'>
                      <Button onClick={() => this.onInvoiceClick(invoiceItem.invoiceId)}>View</Button>
                    </TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
            {nextInvoicesCursor && (
              <Button
                style={{ margin: 'auto', display: 'block' }}
                onClick={() => ServerAdmin.get().dispatchAdmin()
                  .then(d => d.invoicesSearchAdmin({ cursor: nextInvoicesCursor }))
                  .then(results => this.setState({
                    invoices: [
                      ...(this.state.invoices || []),
                      ...results.results,
                    ],
                    invoicesCursor: results.cursor,
                  }))}
              >
                Show more
              </Button>
            )}
          </>
        )}
      />
    );

    const plan = (
      <Section
        title='Plan'
        preview={(
          <div className={this.props.classes.planContainer}>
            <TourAnchor anchorId='settings-billing-plan' placement='bottom' disablePortal>
              <PricingPlan
                selected
                className={this.props.classes.plan}
                plan={this.props.accountBilling.plan}
              />
            </TourAnchor>
            {(this.props.accountBilling?.trackedUsers !== undefined) && (
              <Box display='grid' gridTemplateAreas='"mauLbl mauAmt"' alignItems='baseline' gridGap='10px 10px'>
                <Box gridArea='mauLbl'><Typography component='div'>Tracked users:</Typography></Box>
                <Box gridArea='mauAmt' display='flex'>
                  <Typography component='div' variant='h5'>
                    {this.props.accountBilling.trackedUsers}
                  </Typography>
                </Box>
              </Box>
            )}
            {(this.props.accountBilling?.postCount !== undefined) && (
              <Box display='grid' gridTemplateAreas='"postCountLbl postCountAmt"' alignItems='baseline' gridGap='10px 10px'>
                <Box gridArea='postCountLbl'><Typography component='div'>Post count:</Typography></Box>
                <Box gridArea='postCountAmt' display='flex'>
                  <Typography component='div' variant='h5' color={
                    this.props.account.basePlanId === 'starter-unlimited'
                      && this.props.accountBilling.postCount > StarterMaxPosts
                      ? 'error' : undefined}>
                    {this.props.accountBilling.postCount}
                  </Typography>
                </Box>
              </Box>
            )}
          </div>
        )}
        content={(
          <div className={this.props.classes.actionContainer}>
            <p><Typography variant='h6' component='div' color='textPrimary'>{planTitle}</Typography></p>
            <Typography color='textSecondary'>{planDesc}</Typography>
            {(endOfTermChangeToPlanTitle || endOfTermChangeToPlanDesc) && (
              <>
                <p><Typography variant='h6' component='div' color='textPrimary' className={this.props.classes.sectionSpacing}>{endOfTermChangeToPlanTitle}</Typography></p>
                <Typography color='textSecondary'>{endOfTermChangeToPlanDesc}</Typography>
              </>
            )}
            {showPlanChange && (
              <div className={this.props.classes.sectionButtons}>
                <Button
                  disabled={this.state.isSubmitting || this.state.showPlanChange}
                  onClick={() => {
                    trackingBlock(() => {
                      ReactGA.event({
                        category: 'billing',
                        action: 'click-plan-switch-open',
                        label: this.props.accountBilling?.plan.basePlanId,
                      });
                    });

                    this.setState({ showPlanChange: true });
                  }}
                >
                  {switchPlanTitle || 'Switch plan'}
                </Button>
              </div>
            )}
            {showPlanChange && (
              <div className={this.props.classes.sectionButtons}>
                <Button
                  disabled={this.state.isSubmitting || this.state.showPlanChange}
                  onClick={() => this.props.history.push('/coupon')}
                >
                  Redeem coupon
                </Button>
              </div>
            )}
            {this.props.isSuperAdmin && (
              <>
                <Dialog
                  open={!!this.state.showFlatYearlyChange}
                  onClose={() => this.setState({ showFlatYearlyChange: undefined })}
                  scroll='body'
                  maxWidth='md'
                >
                  <DialogTitle>Switch to yearly plan</DialogTitle>
                  <DialogContent>
                    <TextField
                      variant='outlined'
                      type='number'
                      label='Yearly flat price'
                      value={this.state.flatYearlyPrice !== undefined ? this.state.flatYearlyPrice : ''}
                      onChange={e => this.setState({ flatYearlyPrice: parseInt(e.target.value) >= 0 ? parseInt(e.target.value) : undefined })}
                    />
                  </DialogContent>
                  <DialogActions>
                    <Button onClick={() => this.setState({ showFlatYearlyChange: undefined })}
                    >Cancel</Button>
                    <SubmitButton
                      isSubmitting={this.state.isSubmitting}
                      disabled={this.state.flatYearlyPrice === undefined}
                      color='primary'
                      onClick={() => {

                        this.setState({ isSubmitting: true });
                        ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateSuperAdmin({
                          accountUpdateSuperAdmin: {
                            changeToFlatPlanWithYearlyPrice: this.state.flatYearlyPrice || 0,
                          },
                        }).then(() => d.accountBillingAdmin({})))
                          .then(() => this.setState({ isSubmitting: false, showFlatYearlyChange: undefined }))
                          .catch(er => this.setState({ isSubmitting: false }));
                      }}
                    >Change</SubmitButton>
                  </DialogActions>
                </Dialog>
                <div className={this.props.classes.sectionButtons}>
                  <Button
                    disabled={this.state.isSubmitting}
                    onClick={() => this.setState({ showFlatYearlyChange: true })}
                  >Flatten</Button>
                </div>
              </>
            )}
            {this.props.isSuperAdmin && (
              <>
                <Dialog
                  open={!!this.state.showAddonsChange}
                  onClose={() => this.setState({ showAddonsChange: undefined })}
                  scroll='body'
                  maxWidth='md'
                >
                  <DialogTitle>Manage addons</DialogTitle>
                  <DialogContent className={this.props.classes.addonsContainer}>
                    <TextField
                      label='Extra projects'
                      variant='outlined'
                      type='number'
                      value={this.state.extraProjects !== undefined ? this.state.extraProjects : (this.props.account.addons?.[AddonExtraProject] || 0)}
                      onChange={e => this.setState({ extraProjects: parseInt(e.target.value) >= 0 ? parseInt(e.target.value) : undefined })}
                    />
                    <FormControlLabel
                      control={(
                        <Switch
                          checked={this.state.whitelabel !== undefined ? this.state.whitelabel : !!this.props.account.addons?.[AddonWhitelabel]}
                          onChange={(e, checked) => this.setState({ whitelabel: !!checked })}
                          color='default'
                        />
                      )}
                      label={(<FormHelperText>Whitelabel</FormHelperText>)}
                    />
                    <FormControlLabel
                      control={(
                        <Switch
                          checked={this.state.privateProjects !== undefined ? this.state.privateProjects : !!this.props.account.addons?.[AddonPrivateProjects]}
                          onChange={(e, checked) => this.setState({ privateProjects: !!checked })}
                          color='default'
                        />
                      )}
                      label={(<FormHelperText>Private projects</FormHelperText>)}
                    />
                  </DialogContent>
                  <DialogActions>
                    <Button onClick={() => this.setState({ showAddonsChange: undefined })}
                    >Cancel</Button>
                    <SubmitButton
                      isSubmitting={this.state.isSubmitting}
                      disabled={this.state.whitelabel === undefined
                        && this.state.privateProjects === undefined
                        && this.state.extraProjects === undefined}
                      color='primary'
                      onClick={() => {
                        if (this.state.whitelabel === undefined
                          && this.state.privateProjects === undefined
                          && this.state.extraProjects === undefined) return;

                        this.setState({ isSubmitting: true });
                        ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateSuperAdmin({
                          accountUpdateSuperAdmin: {
                            addons: {
                              ...(this.state.whitelabel === undefined ? {} : {
                                [AddonWhitelabel]: this.state.whitelabel ? 'true' : ''
                              }),
                              ...(this.state.privateProjects === undefined ? {} : {
                                [AddonPrivateProjects]: this.state.privateProjects ? 'true' : ''
                              }),
                              ...(this.state.extraProjects === undefined ? {} : {
                                [AddonExtraProject]: `${this.state.extraProjects}`
                              }),
                            },
                          },
                        }).then(() => d.accountBillingAdmin({})))
                          .then(() => this.setState({ isSubmitting: false, showAddonsChange: undefined }))
                          .catch(er => this.setState({ isSubmitting: false }));
                      }}
                    >Change</SubmitButton>
                  </DialogActions>
                </Dialog>
                <div className={this.props.classes.sectionButtons}>
                  <Button
                    disabled={this.state.isSubmitting}
                    onClick={() => this.setState({ showAddonsChange: true })}
                  >Addons</Button>
                </div>
              </>
            )}
            {this.props.isSuperAdmin && (
              <>
                <Dialog
                  open={!!this.state.showCreditAdjustment}
                  onClose={() => this.setState({ showCreditAdjustment: undefined })}
                  scroll='body'
                  maxWidth='md'
                >
                  <DialogTitle>Credit adjustment</DialogTitle>
                  <DialogContent className={this.props.classes.addonsContainer}>
                    <TextField
                      label='Amount'
                      variant='outlined'
                      type='number'
                      value={this.state.creditAmount || 0}
                      onChange={e => this.setState({ creditAmount: parseInt(e.target.value) })}
                    />
                    <TextField
                      label='Description'
                      variant='outlined'
                      value={this.state.creditDescription || ''}
                      onChange={e => this.setState({ creditDescription: e.target.value })}
                    />
                  </DialogContent>
                  <DialogActions>
                    <Button onClick={() => this.setState({ showCreditAdjustment: undefined })}
                    >Cancel</Button>
                    <SubmitButton
                      isSubmitting={this.state.isSubmitting}
                      disabled={!this.props.account
                        || !this.state.creditAmount
                        || !this.state.creditDescription}
                      color='primary'
                      onClick={() => {
                        if (!this.props.account
                          || !this.state.creditAmount
                          || !this.state.creditDescription) return;

                        this.setState({ isSubmitting: true });
                        ServerAdmin.get().dispatchAdmin().then(d => d.accountCreditAdjustmentSuperAdmin({
                          accountCreditAdjustment: {
                            accountId: this.props.account!.accountId,
                            amount: this.state.creditAmount!,
                            description: this.state.creditDescription!,
                          },
                        }).then(() => d.accountBillingAdmin({})))
                          .then(() => this.setState({ isSubmitting: false, showCreditAdjustment: undefined, creditAmount: undefined, creditDescription: undefined }))
                          .catch(er => this.setState({ isSubmitting: false }));
                      }}
                    >Change</SubmitButton>
                  </DialogActions>
                </Dialog>
                <div className={this.props.classes.sectionButtons}>
                  <Button
                    disabled={this.state.isSubmitting}
                    onClick={() => this.setState({ showCreditAdjustment: true })}
                  >Credit</Button>
                </div>
              </>
            )}
            <BillingChangePlanDialog
              open={!!this.state.showPlanChange}
              onClose={() => this.setState({ showPlanChange: undefined })}
              onSubmit={basePlanId => {
                trackingBlock(() => {
                  ReactGA.event({
                    category: 'billing',
                    action: 'click-plan-switch-submit',
                    label: basePlanId,
                  });
                });

                this.setState({ isSubmitting: true });
                ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
                  accountUpdateAdmin: {
                    basePlanId,
                  },
                }).then(() => d.accountBillingAdmin({})))
                  .then(() => this.setState({ isSubmitting: false, showPlanChange: undefined }))
                  .catch(er => this.setState({ isSubmitting: false }));
              }}
              isSubmitting={!!this.state.isSubmitting}
            />
          </div>
        )}
      />
    );

    return (
      <ProjectSettingsBase title='Billing'>
        {plan}
        {payment}
        {invoices}
      </ProjectSettingsBase>
    );
  }
Example #29
Source File: AddMembers.tsx    From projectboard with MIT License 4 votes vote down vote up
AddMembers: React.FC<Props> = () => {
    const [users, setUsers] = useState([]);
    const { getAccessTokenSilently } = useAuth0();
    const isMobile = useMediaQuery('(max-width:600px)');
    const match = useRouteMatch<MatchParams>();
    const [error, setError] = useState(false);

    // Add Member API Call States
    const [addMemberLoading, setAddMemberLoading] = useState(false);

    const onAddMember = async (userId: string) => {
        try {
            setAddMemberLoading(true);
            showWarning('', 'Adding Member...');
            const token = await getAccessTokenSilently();
            await axios({
                url: `${baseURL}${endpoints.projects}/${match.params.projectId}${endpoints.members}/${userId}`,
                method: 'POST',
                headers: {
                    Authorization: `Bearer ${token}`
                }
            });
            showInfo('', 'Member Added Successfully.');
        } catch (e) {
            showError(e?.response?.data?.message, 'Error Adding Member.');
        } finally {
            setAddMemberLoading(false);
        }
    };

    return (
        <div>
            <Formik
                initialValues={{
                    searchTerm: ''
                }}
                validateOnChange={true}
                validateOnBlur={false}
                validateOnMount={false}
                validationSchema={ValidationSchema}
                onSubmit={(values, { setSubmitting, resetForm }) => {
                    setTimeout(async () => {
                        try {
                            setError(false);
                            const token = await getAccessTokenSilently();
                            const { data } = await axios({
                                url: `${baseURL}${endpoints.users}?keyword=${encodeURIComponent(
                                    values.searchTerm
                                )}`,
                                method: 'GET',
                                headers: {
                                    Authorization: `Bearer ${token}`
                                }
                            });
                            setUsers(data.users);
                            resetForm();
                        } catch (e) {
                            setError(e?.response?.data?.message);
                        } finally {
                            setSubmitting(false);
                        }
                    });
                }}
            >
                {({ values, errors, handleChange, handleSubmit, isSubmitting }) => (
                    <form onSubmit={handleSubmit}>
                        <div className="flex items-stretch">
                            <input
                                id="searchTerm"
                                type="text"
                                value={values.searchTerm}
                                onChange={handleChange}
                                className="flex-grow-0 w-full p-4 text-lg border-b border-gray-200 focus:outline-none mr-4"
                                placeholder="Enter username or email..."
                            />
                            <Button variant="outlined" color="primary" type="submit" disabled={isSubmitting}>
                                <SearchIcon />
                            </Button>
                        </div>
                        {errors.searchTerm && (
                            <FormHelperText>
                                <span className="text-red-600">{errors.searchTerm}</span>
                            </FormHelperText>
                        )}
                        {error && <p className="text-red-600 mt-2">{error}</p>}
                    </form>
                )}
            </Formik>
            <div>
                {users.length !== 0 && <UsersColHeader isMobile={isMobile} />}
                {React.Children.toArray(
                    users.map((user: User) => (
                        <UserRow
                            onAddMember={onAddMember}
                            disableAddButton={addMemberLoading}
                            isMobile={isMobile}
                            user={user}
                        />
                    ))
                )}
            </div>
        </div>
    );
}