@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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
render() {
if (!this.props.account) {
return 'Need to login to see this page';
}
const status = this.props.accountStatus === Status.FULFILLED ? this.props.accountBillingStatus : this.props.accountStatus;
if (!this.props.accountBilling || status !== Status.FULFILLED) {
return (
<Loader skipFade status={status} />
);
}
var cardNumber, cardExpiry, cardStateIcon;
if (!!this.props.accountBilling?.payment) {
cardNumber = (
<>
<span className={this.props.classes.blurry}>5200 8282 8282 </span>
{this.props.accountBilling.payment.last4}
</>
);
var expiryColor;
if (new Date().getFullYear() % 100 >= this.props.accountBilling.payment.expiryYear % 100) {
if (new Date().getMonth() + 1 === this.props.accountBilling.payment.expiryMonth) {
expiryColor = this.props.theme.palette.warning.main;
} else if (new Date().getMonth() + 1 > this.props.accountBilling.payment.expiryMonth) {
expiryColor = this.props.theme.palette.error.main;
}
}
cardExpiry = (
<span style={expiryColor && { color: expiryColor }}>
{this.props.accountBilling.payment.expiryMonth}
/
{this.props.accountBilling.payment.expiryYear % 100}
</span>
);
} else {
cardNumber = (<span className={this.props.classes.blurry}>5200 8282 8282 8210</span>);
cardExpiry = (<span className={this.props.classes.blurry}>06 / 32</span>);
}
var hasAvailablePlansToSwitch: boolean = (this.props.accountBilling?.availablePlans || [])
.filter(p => p.basePlanId !== this.props.accountBilling?.plan.basePlanId)
.length > 0;
var cardState: 'active' | 'warn' | 'error' = 'active';
var paymentTitle, paymentDesc, showContactSupport, showSetPayment, setPaymentTitle, setPaymentAction, showCancelSubscription, showResumePlan, resumePlanDesc, planTitle, planDesc, showPlanChange, endOfTermChangeToPlanTitle, endOfTermChangeToPlanDesc, switchPlanTitle;
switch (this.props.account.subscriptionStatus) {
case Admin.SubscriptionStatus.Active:
if (this.props.accountBilling?.plan.basePlanId === TeammatePlanId) {
paymentTitle = 'No payment required';
paymentDesc = 'While you only access external projects, payments are made by the project owner. No payment is required from you at this time.';
cardState = 'active';
showSetPayment = false;
showCancelSubscription = false;
planTitle = 'You are not on a plan';
planDesc = 'While you only access external projects, you are not required to be on a plan. If you decide to create a project under your account, you will be able to choose a plan and your trial will begin.';
if (hasAvailablePlansToSwitch) {
showPlanChange = true;
switchPlanTitle = 'Choose plan'
}
} else {
paymentTitle = 'Automatic renewal is active';
paymentDesc = 'You will be automatically billed at the next cycle and your plan will be renewed.';
cardState = 'active';
showSetPayment = true;
setPaymentTitle = 'Update payment method';
showCancelSubscription = true;
planTitle = 'Your plan is active';
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan.`;
if (hasAvailablePlansToSwitch) {
planDesc += ' If you upgrade your plan, changes will reflect immediately. If you downgrade your plan, changes will take effect at the end of the term.';
showPlanChange = true;
}
}
break;
case Admin.SubscriptionStatus.ActiveTrial:
if (this.props.accountBilling?.payment) {
paymentTitle = 'Automatic renewal is active';
if (this.props.accountBilling?.billingPeriodEnd) {
paymentDesc = (
<>
Your first payment will be automatically billed at the end of the trial period in <TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />.
</>
);
} else {
paymentDesc = `Your first payment will be automatically billed at the end of the trial period.`;
}
cardState = 'active';
showSetPayment = true;
setPaymentTitle = 'Update payment method';
planTitle = 'Your plan is active';
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan.`;
if (hasAvailablePlansToSwitch) {
planDesc += ' If you switch plans now, your first payment at the end of your trial will reflect your new plan.';
showPlanChange = true;
}
} else {
paymentTitle = 'Automatic renewal requires a payment method';
paymentDesc = 'To continue using our service beyond the trial period, add a payment method to enable automatic renewal.';
cardState = 'warn';
showSetPayment = true;
setPaymentTitle = 'Add payment method';
planTitle = 'Your plan is active until your trial ends';
if (this.props.accountBilling?.billingPeriodEnd) {
planDesc = (
<>
You have full access to your {this.props.accountBilling.plan.title} plan until your trial expires in <TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />. Add a payment method to continue using our service beyond the trial period.
</>
);
} else {
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan until your trial expires. Add a payment method to continue using our service beyond the trial period.`;
}
if (hasAvailablePlansToSwitch) {
showPlanChange = true;
}
}
break;
case Admin.SubscriptionStatus.ActivePaymentRetry:
paymentTitle = 'Automatic renewal is having issues with your payment method';
paymentDesc = 'We are having issues charging your payment method. We will retry your payment method again soon and we may block your service if unsuccessful.';
cardState = 'error';
showSetPayment = true;
if (this.props.accountBilling?.payment) {
setPaymentTitle = 'Update payment method';
} else {
setPaymentTitle = 'Add payment method';
}
showCancelSubscription = true;
planTitle = 'Your plan is active';
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan; however, there is an issue with your payment. Please resolve it before you can change your plan.`;
break;
case Admin.SubscriptionStatus.ActiveNoRenewal:
paymentTitle = 'Automatic renewal is inactive';
paymentDesc = 'Resume automatic renewal to continue using our service beyond the next billing cycle.';
cardState = 'warn';
showSetPayment = true;
setPaymentTitle = 'Resume with new payment method';
setPaymentAction = 'Add and resume subscription';
showResumePlan = true;
resumePlanDesc = 'Your subscription will no longer be cancelled. You will be automatically billed for our service at the next billing cycle.';
if (this.props.accountBilling?.billingPeriodEnd) {
planTitle = (
<>
Your plan is active until <TimeAgo date={this.props.accountBilling?.billingPeriodEnd} />
</>
);
} else {
planTitle = 'Your plan is active until the end of the billing cycle';
}
planDesc = `You have full access to your ${this.props.accountBilling.plan.title} plan until it cancels. Please resume your payments to continue using our service beyond next billing cycle.`;
break;
case Admin.SubscriptionStatus.Limited:
paymentTitle = 'Automatic renewal is active';
paymentDesc = 'You will be automatically billed at the next cycle and your plan will be renewed.';
cardState = 'active';
showSetPayment = true;
setPaymentTitle = 'Update payment method';
showCancelSubscription = true;
planTitle = 'Your plan is limited';
planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan due to going over your plan limits. Please resolve all issues to continue using our service.`;
if (hasAvailablePlansToSwitch) {
planDesc += ' If you upgrade your plan, changes will reflect immediately. If you downgrade your plan, changes will take effect at the end of the term.';
showPlanChange = true;
}
break;
case Admin.SubscriptionStatus.NoPaymentMethod:
paymentTitle = 'Automatic renewal is inactive';
paymentDesc = 'Your trial has expired. To continue using our service, add a payment method to enable automatic renewal.';
cardState = 'error';
showSetPayment = true;
setPaymentTitle = 'Add payment method';
planTitle = 'Your trial plan has expired';
planDesc = `To continue using your ${this.props.accountBilling.plan.title} plan, please add a payment method.`;
break;
case Admin.SubscriptionStatus.Blocked:
paymentTitle = 'Payments are blocked';
paymentDesc = 'Contact support to reinstate your account.';
showContactSupport = true;
cardState = 'error';
planTitle = 'Your plan is inactive';
planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan due to a payment issue. Please resolve all issues to continue using our service.`;
break;
case Admin.SubscriptionStatus.Cancelled:
paymentTitle = 'Automatic renewal is inactive';
paymentDesc = 'Resume automatic renewal to continue using our service.';
cardState = 'error';
showSetPayment = true;
setPaymentTitle = 'Update payment method';
if (this.props.accountBilling?.payment) {
showResumePlan = true;
resumePlanDesc = 'Your subscription will no longer be cancelled. You will be automatically billed for our service starting now.';
}
planTitle = 'Your plan is cancelled';
planDesc = `You have limited access to your ${this.props.accountBilling.plan.title} plan since you cancelled your subscription. Please resume payment to continue using our service.`;
break;
}
if (this.props.accountBilling?.endOfTermChangeToPlan) {
endOfTermChangeToPlanTitle = `Pending plan change to ${this.props.accountBilling.endOfTermChangeToPlan.title}`;
endOfTermChangeToPlanDesc = `Your requested change of plans to ${this.props.accountBilling.endOfTermChangeToPlan.title} plan will take effect at the end of the term.`;
}
switch (cardState) {
case 'active':
cardStateIcon = (<ActiveIcon color='primary' />);
break;
case 'warn':
cardStateIcon = (<WarnIcon style={{ color: this.props.theme.palette.warning.main }} />);
break;
case 'error':
cardStateIcon = (<ErrorIcon color='error' />);
break;
}
const creditCard = (
<TourAnchor anchorId='settings-credit-card' placement='bottom'>
<CreditCard
className={this.props.classes.creditCard}
brand={cardStateIcon}
numberInput={cardNumber}
expiryInput={cardExpiry}
cvcInput={(<span className={this.props.classes.blurry}>642</span>)}
/>
</TourAnchor>
);
const paymentStripeAction: PaymentStripeAction | undefined = this.props.accountBilling?.paymentActionRequired?.actionType === 'stripe-next-action'
? this.props.accountBilling?.paymentActionRequired as PaymentStripeAction : undefined;
const paymentActionOnClose = () => {
this.setState({
paymentActionOpen: undefined,
paymentActionUrl: undefined,
paymentActionMessage: undefined,
paymentActionMessageSeverity: undefined,
});
if (this.refreshBillingAfterPaymentClose) {
ServerAdmin.get().dispatchAdmin().then(d => d.accountBillingAdmin({
refreshPayments: true,
}));
}
};
const paymentAction = paymentStripeAction ? (
<>
<Message
className={this.props.classes.paymentActionMessage}
message='One of your payments requires additional information'
severity='error'
action={(
<SubmitButton
isSubmitting={!!this.state.paymentActionOpen && !this.state.paymentActionUrl && !this.state.paymentActionMessage}
onClick={() => {
this.setState({ paymentActionOpen: true });
this.loadActionIframe(paymentStripeAction);
}}
>Open</SubmitButton>
)}
/>
<Dialog
open={!!this.state.paymentActionOpen}
onClose={paymentActionOnClose}
>
{this.state.paymentActionMessage ? (
<>
<DialogContent>
<Message
message={this.state.paymentActionMessage}
severity={this.state.paymentActionMessageSeverity || 'info'}
/>
</DialogContent>
<DialogActions>
<Button onClick={paymentActionOnClose}>Dismiss</Button>
</DialogActions>
</>
) : (this.state.paymentActionUrl ? (
<iframe
title='Complete outstanding payment action'
width={this.getFrameActionWidth()}
height={400}
src={this.state.paymentActionUrl}
/>
) : (
<div style={{
minWidth: this.getFrameActionWidth(),
minHeight: 400,
}}>
<LoadingPage />
</div>
))}
</Dialog>
</>
) : undefined;
const hasPayable = (this.props.accountBilling?.accountPayable || 0) > 0;
const hasReceivable = (this.props.accountBilling?.accountReceivable || 0) > 0;
const payment = (
<Section
title='Payment'
preview={(
<div className={this.props.classes.creditCardContainer}>
{creditCard}
<Box display='grid' gridTemplateAreas='"payTtl payAmt" "rcvTtl rcvAmt"' alignItems='center' gridGap='10px 10px'>
{hasPayable && (
<>
<Box gridArea='payTtl'><Typography component='div'>Credits:</Typography></Box>
<Box gridArea='payAmt' display='flex'>
<Typography component='div' variant='h6' color='textSecondary' style={{ alignSelf: 'flex-start' }}>{'$'}</Typography>
<Typography component='div' variant='h4' color={hasPayable ? 'primary' : undefined}>
{this.props.accountBilling?.accountPayable || 0}
</Typography>
</Box>
</>
)}
{(hasReceivable || !hasPayable) && (
<>
<Box gridArea='rcvTtl'><Typography component='div'>Overdue:</Typography></Box>
<Box gridArea='rcvAmt' display='flex'>
<Typography component='div' variant='h6' color='textSecondary' style={{ alignSelf: 'flex-start' }}>{'$'}</Typography>
<Typography component='div' variant='h4' color={hasReceivable ? 'error' : undefined}>
{this.props.accountBilling?.accountReceivable || 0}
</Typography>
</Box>
</>
)}
</Box>
</div>
)}
content={(
<div className={this.props.classes.actionContainer}>
<p><Typography variant='h6' color='textPrimary' component='div'>{paymentTitle}</Typography></p>
<Typography color='textSecondary'>{paymentDesc}</Typography>
<div className={this.props.classes.sectionButtons}>
{showContactSupport && (
<Button
disabled={this.state.isSubmitting || this.state.showAddPayment}
component={Link}
to='/contact/support'
>Contact support</Button>
)}
{showSetPayment && (
<TourAnchor anchorId='settings-add-payment-open' placement='bottom'>
{(next, isActive, anchorRef) => (
<SubmitButton
buttonRef={anchorRef}
isSubmitting={this.state.isSubmitting}
disabled={this.state.showAddPayment}
onClick={() => {
trackingBlock(() => {
ReactGA.event({
category: 'billing',
action: this.props.accountBilling?.payment ? 'click-payment-update-open' : 'click-payment-add-open',
label: this.props.accountBilling?.plan.basePlanId,
});
});
this.setState({ showAddPayment: true });
next();
}}
>
{setPaymentTitle}
</SubmitButton>
)}
</TourAnchor>
)}
{showCancelSubscription && (
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={this.state.showCancelSubscription}
style={{ color: this.props.theme.palette.error.main }}
onClick={() => this.setState({ showCancelSubscription: true })}
>
Cancel payments
</SubmitButton>
)}
{showResumePlan && (
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={this.state.showResumePlan}
color='primary'
onClick={() => this.setState({ showResumePlan: true })}
>
Resume payments
</SubmitButton>
)}
</div>
{paymentAction}
<Dialog
open={!!this.state.showAddPayment}
onClose={() => this.setState({ showAddPayment: undefined })}
>
<ElementsConsumer>
{({ elements, stripe }) => (
<TourAnchor anchorId='settings-add-payment-popup' placement='top'>
{(next, isActive, anchorRef) => (
<div ref={anchorRef}>
<DialogTitle>{setPaymentTitle || 'Add new payment method'}</DialogTitle>
<DialogContent className={this.props.classes.center}>
<StripeCreditCard onFilledChanged={(isFilled) => this.setState({ stripePaymentFilled: isFilled })} />
<Collapse in={!!this.state.stripePaymentError}>
<Message message={this.state.stripePaymentError} severity='error' />
</Collapse>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showAddPayment: undefined })}>
Cancel
</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={!this.state.stripePaymentFilled || !elements || !stripe}
color='primary'
onClick={async () => {
const success = await this.onPaymentSubmit(elements!, stripe!);
if (success) {
next();
tourSetGuideState('add-payment', TourDefinitionGuideState.Completed);
}
}}
>{setPaymentAction || 'Add'}</SubmitButton>
</DialogActions>
</div>
)}
</TourAnchor>
)}
</ElementsConsumer>
</Dialog>
<Dialog
open={!!this.state.showCancelSubscription}
onClose={() => this.setState({ showCancelSubscription: undefined })}
>
<DialogTitle>Stop subscription</DialogTitle>
<DialogContent className={this.props.classes.center}>
<DialogContentText>Stop automatic billing of your subscription. Any ongoing subscription will continue to work until it expires.</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showCancelSubscription: undefined })}>
Cancel
</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
style={{ color: this.props.theme.palette.error.main }}
onClick={() => {
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
accountUpdateAdmin: {
cancelEndOfTerm: true,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showCancelSubscription: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Stop subscription</SubmitButton>
</DialogActions>
</Dialog>
<Dialog
open={!!this.state.showResumePlan}
onClose={() => this.setState({ showResumePlan: undefined })}
>
<DialogTitle>Resume subscription</DialogTitle>
<DialogContent className={this.props.classes.center}>
<DialogContentText>{resumePlanDesc}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showResumePlan: undefined })}>
Cancel
</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
color='primary'
onClick={() => {
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
accountUpdateAdmin: {
resume: true,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showResumePlan: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Resume subscription</SubmitButton>
</DialogActions>
</Dialog>
</div>
)}
/>
);
const nextInvoicesCursor = this.state.invoices === undefined
? this.props.accountBilling?.invoices.cursor
: this.state.invoicesCursor;
const invoicesItems = [
...(this.props.accountBilling?.invoices.results || []),
...(this.state.invoices || []),
];
const invoices = invoicesItems.length <= 0 ? undefined : (
<Section
title='Invoices'
content={(
<>
<Table>
<TableHead>
<TableRow>
<TableCell key='due'>Due</TableCell>
<TableCell key='status'>Status</TableCell>
<TableCell key='amount'>Amount</TableCell>
<TableCell key='desc'>Description</TableCell>
<TableCell key='invoiceLink'>Invoice</TableCell>
</TableRow>
</TableHead>
<TableBody>
{invoicesItems.map((invoiceItem, index) => (
<TableRow key={index}>
<TableCell key='due'><Typography>{new Date(invoiceItem.date).toLocaleDateString()}</Typography></TableCell>
<TableCell key='status' align='center'><Typography>{invoiceItem.status}</Typography></TableCell>
<TableCell key='amount' align='right'><Typography>{invoiceItem.amount}</Typography></TableCell>
<TableCell key='desc'><Typography>{invoiceItem.description}</Typography></TableCell>
<TableCell key='invoiceLink'>
<Button onClick={() => this.onInvoiceClick(invoiceItem.invoiceId)}>View</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{nextInvoicesCursor && (
<Button
style={{ margin: 'auto', display: 'block' }}
onClick={() => ServerAdmin.get().dispatchAdmin()
.then(d => d.invoicesSearchAdmin({ cursor: nextInvoicesCursor }))
.then(results => this.setState({
invoices: [
...(this.state.invoices || []),
...results.results,
],
invoicesCursor: results.cursor,
}))}
>
Show more
</Button>
)}
</>
)}
/>
);
const plan = (
<Section
title='Plan'
preview={(
<div className={this.props.classes.planContainer}>
<TourAnchor anchorId='settings-billing-plan' placement='bottom' disablePortal>
<PricingPlan
selected
className={this.props.classes.plan}
plan={this.props.accountBilling.plan}
/>
</TourAnchor>
{(this.props.accountBilling?.trackedUsers !== undefined) && (
<Box display='grid' gridTemplateAreas='"mauLbl mauAmt"' alignItems='baseline' gridGap='10px 10px'>
<Box gridArea='mauLbl'><Typography component='div'>Tracked users:</Typography></Box>
<Box gridArea='mauAmt' display='flex'>
<Typography component='div' variant='h5'>
{this.props.accountBilling.trackedUsers}
</Typography>
</Box>
</Box>
)}
{(this.props.accountBilling?.postCount !== undefined) && (
<Box display='grid' gridTemplateAreas='"postCountLbl postCountAmt"' alignItems='baseline' gridGap='10px 10px'>
<Box gridArea='postCountLbl'><Typography component='div'>Post count:</Typography></Box>
<Box gridArea='postCountAmt' display='flex'>
<Typography component='div' variant='h5' color={
this.props.account.basePlanId === 'starter-unlimited'
&& this.props.accountBilling.postCount > StarterMaxPosts
? 'error' : undefined}>
{this.props.accountBilling.postCount}
</Typography>
</Box>
</Box>
)}
</div>
)}
content={(
<div className={this.props.classes.actionContainer}>
<p><Typography variant='h6' component='div' color='textPrimary'>{planTitle}</Typography></p>
<Typography color='textSecondary'>{planDesc}</Typography>
{(endOfTermChangeToPlanTitle || endOfTermChangeToPlanDesc) && (
<>
<p><Typography variant='h6' component='div' color='textPrimary' className={this.props.classes.sectionSpacing}>{endOfTermChangeToPlanTitle}</Typography></p>
<Typography color='textSecondary'>{endOfTermChangeToPlanDesc}</Typography>
</>
)}
{showPlanChange && (
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting || this.state.showPlanChange}
onClick={() => {
trackingBlock(() => {
ReactGA.event({
category: 'billing',
action: 'click-plan-switch-open',
label: this.props.accountBilling?.plan.basePlanId,
});
});
this.setState({ showPlanChange: true });
}}
>
{switchPlanTitle || 'Switch plan'}
</Button>
</div>
)}
{showPlanChange && (
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting || this.state.showPlanChange}
onClick={() => this.props.history.push('/coupon')}
>
Redeem coupon
</Button>
</div>
)}
{this.props.isSuperAdmin && (
<>
<Dialog
open={!!this.state.showFlatYearlyChange}
onClose={() => this.setState({ showFlatYearlyChange: undefined })}
scroll='body'
maxWidth='md'
>
<DialogTitle>Switch to yearly plan</DialogTitle>
<DialogContent>
<TextField
variant='outlined'
type='number'
label='Yearly flat price'
value={this.state.flatYearlyPrice !== undefined ? this.state.flatYearlyPrice : ''}
onChange={e => this.setState({ flatYearlyPrice: parseInt(e.target.value) >= 0 ? parseInt(e.target.value) : undefined })}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showFlatYearlyChange: undefined })}
>Cancel</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={this.state.flatYearlyPrice === undefined}
color='primary'
onClick={() => {
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateSuperAdmin({
accountUpdateSuperAdmin: {
changeToFlatPlanWithYearlyPrice: this.state.flatYearlyPrice || 0,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showFlatYearlyChange: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Change</SubmitButton>
</DialogActions>
</Dialog>
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting}
onClick={() => this.setState({ showFlatYearlyChange: true })}
>Flatten</Button>
</div>
</>
)}
{this.props.isSuperAdmin && (
<>
<Dialog
open={!!this.state.showAddonsChange}
onClose={() => this.setState({ showAddonsChange: undefined })}
scroll='body'
maxWidth='md'
>
<DialogTitle>Manage addons</DialogTitle>
<DialogContent className={this.props.classes.addonsContainer}>
<TextField
label='Extra projects'
variant='outlined'
type='number'
value={this.state.extraProjects !== undefined ? this.state.extraProjects : (this.props.account.addons?.[AddonExtraProject] || 0)}
onChange={e => this.setState({ extraProjects: parseInt(e.target.value) >= 0 ? parseInt(e.target.value) : undefined })}
/>
<FormControlLabel
control={(
<Switch
checked={this.state.whitelabel !== undefined ? this.state.whitelabel : !!this.props.account.addons?.[AddonWhitelabel]}
onChange={(e, checked) => this.setState({ whitelabel: !!checked })}
color='default'
/>
)}
label={(<FormHelperText>Whitelabel</FormHelperText>)}
/>
<FormControlLabel
control={(
<Switch
checked={this.state.privateProjects !== undefined ? this.state.privateProjects : !!this.props.account.addons?.[AddonPrivateProjects]}
onChange={(e, checked) => this.setState({ privateProjects: !!checked })}
color='default'
/>
)}
label={(<FormHelperText>Private projects</FormHelperText>)}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showAddonsChange: undefined })}
>Cancel</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={this.state.whitelabel === undefined
&& this.state.privateProjects === undefined
&& this.state.extraProjects === undefined}
color='primary'
onClick={() => {
if (this.state.whitelabel === undefined
&& this.state.privateProjects === undefined
&& this.state.extraProjects === undefined) return;
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateSuperAdmin({
accountUpdateSuperAdmin: {
addons: {
...(this.state.whitelabel === undefined ? {} : {
[AddonWhitelabel]: this.state.whitelabel ? 'true' : ''
}),
...(this.state.privateProjects === undefined ? {} : {
[AddonPrivateProjects]: this.state.privateProjects ? 'true' : ''
}),
...(this.state.extraProjects === undefined ? {} : {
[AddonExtraProject]: `${this.state.extraProjects}`
}),
},
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showAddonsChange: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Change</SubmitButton>
</DialogActions>
</Dialog>
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting}
onClick={() => this.setState({ showAddonsChange: true })}
>Addons</Button>
</div>
</>
)}
{this.props.isSuperAdmin && (
<>
<Dialog
open={!!this.state.showCreditAdjustment}
onClose={() => this.setState({ showCreditAdjustment: undefined })}
scroll='body'
maxWidth='md'
>
<DialogTitle>Credit adjustment</DialogTitle>
<DialogContent className={this.props.classes.addonsContainer}>
<TextField
label='Amount'
variant='outlined'
type='number'
value={this.state.creditAmount || 0}
onChange={e => this.setState({ creditAmount: parseInt(e.target.value) })}
/>
<TextField
label='Description'
variant='outlined'
value={this.state.creditDescription || ''}
onChange={e => this.setState({ creditDescription: e.target.value })}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showCreditAdjustment: undefined })}
>Cancel</Button>
<SubmitButton
isSubmitting={this.state.isSubmitting}
disabled={!this.props.account
|| !this.state.creditAmount
|| !this.state.creditDescription}
color='primary'
onClick={() => {
if (!this.props.account
|| !this.state.creditAmount
|| !this.state.creditDescription) return;
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountCreditAdjustmentSuperAdmin({
accountCreditAdjustment: {
accountId: this.props.account!.accountId,
amount: this.state.creditAmount!,
description: this.state.creditDescription!,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showCreditAdjustment: undefined, creditAmount: undefined, creditDescription: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
>Change</SubmitButton>
</DialogActions>
</Dialog>
<div className={this.props.classes.sectionButtons}>
<Button
disabled={this.state.isSubmitting}
onClick={() => this.setState({ showCreditAdjustment: true })}
>Credit</Button>
</div>
</>
)}
<BillingChangePlanDialog
open={!!this.state.showPlanChange}
onClose={() => this.setState({ showPlanChange: undefined })}
onSubmit={basePlanId => {
trackingBlock(() => {
ReactGA.event({
category: 'billing',
action: 'click-plan-switch-submit',
label: basePlanId,
});
});
this.setState({ isSubmitting: true });
ServerAdmin.get().dispatchAdmin().then(d => d.accountUpdateAdmin({
accountUpdateAdmin: {
basePlanId,
},
}).then(() => d.accountBillingAdmin({})))
.then(() => this.setState({ isSubmitting: false, showPlanChange: undefined }))
.catch(er => this.setState({ isSubmitting: false }));
}}
isSubmitting={!!this.state.isSubmitting}
/>
</div>
)}
/>
);
return (
<ProjectSettingsBase title='Billing'>
{plan}
{payment}
{invoices}
</ProjectSettingsBase>
);
}
Example #29
Source File: AddMembers.tsx From projectboard with MIT License | 4 votes |
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>
);
}