mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-27 13:49:10 +02:00
feat: service accounts (UI - extract PAT form) (#2733)
https://linear.app/unleash/issue/2-540/extract-pat-form By refactoring the UI logic and extracting the PAT form we can use the same component on our service account creation form.
This commit is contained in:
parent
11f4435a9e
commit
4005bb8a8b
@ -1,4 +1,4 @@
|
|||||||
import { Alert, Button, styled, Typography } from '@mui/material';
|
import { Button, styled } from '@mui/material';
|
||||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
@ -7,13 +7,13 @@ import { FC, FormEvent, useEffect, useState } from 'react';
|
|||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { usePersonalAPITokens } from 'hooks/api/getters/usePersonalAPITokens/usePersonalAPITokens';
|
import { usePersonalAPITokens } from 'hooks/api/getters/usePersonalAPITokens/usePersonalAPITokens';
|
||||||
import { usePersonalAPITokensApi } from 'hooks/api/actions/usePersonalAPITokensApi/usePersonalAPITokensApi';
|
import { usePersonalAPITokensApi } from 'hooks/api/actions/usePersonalAPITokensApi/usePersonalAPITokensApi';
|
||||||
import Input from 'component/common/Input/Input';
|
|
||||||
import SelectMenu from 'component/common/select';
|
|
||||||
import { formatDateYMD } from 'utils/formatDate';
|
|
||||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { INewPersonalAPIToken } from 'interfaces/personalAPIToken';
|
import { INewPersonalAPIToken } from 'interfaces/personalAPIToken';
|
||||||
import { DateTimePicker } from 'component/common/DateTimePicker/DateTimePicker';
|
import {
|
||||||
|
calculateExpirationDate,
|
||||||
|
ExpirationOption,
|
||||||
|
IPersonalAPITokenFormErrors,
|
||||||
|
PersonalAPITokenForm,
|
||||||
|
} from './PersonalAPITokenForm/PersonalAPITokenForm';
|
||||||
|
|
||||||
const StyledForm = styled('form')(() => ({
|
const StyledForm = styled('form')(() => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -21,50 +21,6 @@ const StyledForm = styled('form')(() => ({
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledInputDescription = styled('p')(({ theme }) => ({
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
marginBottom: theme.spacing(1),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledInput = styled(Input)(({ theme }) => ({
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: theme.spacing(50),
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledExpirationPicker = styled('div')<{ custom?: boolean }>(
|
|
||||||
({ theme, custom }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: custom ? 'start' : 'center',
|
|
||||||
gap: theme.spacing(1.5),
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const StyledSelectMenu = styled(SelectMenu)(({ theme }) => ({
|
|
||||||
minWidth: theme.spacing(20),
|
|
||||||
marginRight: theme.spacing(0.5),
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
width: theme.spacing(50),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledDateTimePicker = styled(DateTimePicker)(({ theme }) => ({
|
|
||||||
width: theme.spacing(28),
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
width: theme.spacing(50),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
maxWidth: theme.spacing(50),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||||
marginTop: 'auto',
|
marginTop: 'auto',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -78,49 +34,7 @@ const StyledCancelButton = styled(Button)(({ theme }) => ({
|
|||||||
marginLeft: theme.spacing(3),
|
marginLeft: theme.spacing(3),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
enum ExpirationOption {
|
const DEFAULT_EXPIRATION = ExpirationOption['30DAYS'];
|
||||||
'7DAYS' = '7d',
|
|
||||||
'30DAYS' = '30d',
|
|
||||||
'60DAYS' = '60d',
|
|
||||||
NEVER = 'never',
|
|
||||||
CUSTOM = 'custom',
|
|
||||||
}
|
|
||||||
|
|
||||||
const expirationOptions = [
|
|
||||||
{
|
|
||||||
key: ExpirationOption['7DAYS'],
|
|
||||||
days: 7,
|
|
||||||
label: '7 days',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: ExpirationOption['30DAYS'],
|
|
||||||
days: 30,
|
|
||||||
label: '30 days',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: ExpirationOption['60DAYS'],
|
|
||||||
days: 60,
|
|
||||||
label: '60 days',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: ExpirationOption.NEVER,
|
|
||||||
label: 'Never',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: ExpirationOption.CUSTOM,
|
|
||||||
label: 'Custom',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
enum ErrorField {
|
|
||||||
DESCRIPTION = 'description',
|
|
||||||
EXPIRES_AT = 'expiresAt',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICreatePersonalAPITokenErrors {
|
|
||||||
[ErrorField.DESCRIPTION]?: string;
|
|
||||||
[ErrorField.EXPIRES_AT]?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICreatePersonalAPITokenProps {
|
interface ICreatePersonalAPITokenProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -137,50 +51,22 @@ export const CreatePersonalAPIToken: FC<ICreatePersonalAPITokenProps> = ({
|
|||||||
const { createPersonalAPIToken, loading } = usePersonalAPITokensApi();
|
const { createPersonalAPIToken, loading } = usePersonalAPITokensApi();
|
||||||
const { setToastApiError } = useToast();
|
const { setToastApiError } = useToast();
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const { locationSettings } = useLocationSettings();
|
|
||||||
|
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
const [expiration, setExpiration] = useState<ExpirationOption>(
|
const [expiration, setExpiration] =
|
||||||
ExpirationOption['30DAYS']
|
useState<ExpirationOption>(DEFAULT_EXPIRATION);
|
||||||
|
const [expiresAt, setExpiresAt] = useState(
|
||||||
|
calculateExpirationDate(DEFAULT_EXPIRATION)
|
||||||
);
|
);
|
||||||
const [errors, setErrors] = useState<ICreatePersonalAPITokenErrors>({});
|
const [errors, setErrors] = useState<IPersonalAPITokenFormErrors>({});
|
||||||
|
|
||||||
const clearError = (field: ErrorField) => {
|
|
||||||
setErrors(errors => ({ ...errors, [field]: undefined }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const setError = (field: ErrorField, error: string) => {
|
|
||||||
setErrors(errors => ({ ...errors, [field]: error }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateDate = () => {
|
|
||||||
const expiresAt = new Date();
|
|
||||||
const expirationOption = expirationOptions.find(
|
|
||||||
({ key }) => key === expiration
|
|
||||||
);
|
|
||||||
if (expiration === ExpirationOption.NEVER) {
|
|
||||||
expiresAt.setFullYear(expiresAt.getFullYear() + 1000);
|
|
||||||
} else if (expiration === ExpirationOption.CUSTOM) {
|
|
||||||
expiresAt.setMinutes(expiresAt.getMinutes() + 30);
|
|
||||||
} else if (expirationOption?.days) {
|
|
||||||
expiresAt.setDate(expiresAt.getDate() + expirationOption.days);
|
|
||||||
}
|
|
||||||
return expiresAt;
|
|
||||||
};
|
|
||||||
|
|
||||||
const [expiresAt, setExpiresAt] = useState(calculateDate());
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDescription('');
|
setDescription('');
|
||||||
|
setExpiration(DEFAULT_EXPIRATION);
|
||||||
|
setExpiresAt(calculateExpirationDate(DEFAULT_EXPIRATION));
|
||||||
setErrors({});
|
setErrors({});
|
||||||
setExpiration(ExpirationOption['30DAYS']);
|
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
clearError(ErrorField.EXPIRES_AT);
|
|
||||||
setExpiresAt(calculateDate());
|
|
||||||
}, [expiration]);
|
|
||||||
|
|
||||||
const getPersonalAPITokenPayload = () => {
|
const getPersonalAPITokenPayload = () => {
|
||||||
return {
|
return {
|
||||||
description,
|
description,
|
||||||
@ -220,22 +106,6 @@ export const CreatePersonalAPIToken: FC<ICreatePersonalAPITokenProps> = ({
|
|||||||
isDescriptionUnique(description) &&
|
isDescriptionUnique(description) &&
|
||||||
expiresAt > new Date();
|
expiresAt > new Date();
|
||||||
|
|
||||||
const onSetDescription = (description: string) => {
|
|
||||||
clearError(ErrorField.DESCRIPTION);
|
|
||||||
if (!isDescriptionUnique(description)) {
|
|
||||||
setError(
|
|
||||||
ErrorField.DESCRIPTION,
|
|
||||||
'A personal API token with that description already exists.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setDescription(description);
|
|
||||||
};
|
|
||||||
|
|
||||||
const customExpiration = expiration === ExpirationOption.CUSTOM;
|
|
||||||
|
|
||||||
const neverExpires =
|
|
||||||
expiresAt.getFullYear() > new Date().getFullYear() + 100;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarModal
|
<SidebarModal
|
||||||
open={open}
|
open={open}
|
||||||
@ -257,89 +127,16 @@ export const CreatePersonalAPIToken: FC<ICreatePersonalAPITokenProps> = ({
|
|||||||
>
|
>
|
||||||
<StyledForm onSubmit={handleSubmit}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<div>
|
<div>
|
||||||
<StyledInputDescription>
|
<PersonalAPITokenForm
|
||||||
Describe what this token will be used for
|
description={description}
|
||||||
</StyledInputDescription>
|
setDescription={setDescription}
|
||||||
<StyledInput
|
isDescriptionUnique={isDescriptionUnique}
|
||||||
autoFocus
|
expiration={expiration}
|
||||||
label="Description"
|
setExpiration={setExpiration}
|
||||||
error={Boolean(errors.description)}
|
expiresAt={expiresAt}
|
||||||
errorText={errors.description}
|
setExpiresAt={setExpiresAt}
|
||||||
value={description}
|
errors={errors}
|
||||||
onChange={e => onSetDescription(e.target.value)}
|
setErrors={setErrors}
|
||||||
required
|
|
||||||
/>
|
|
||||||
<StyledInputDescription>
|
|
||||||
Token expiration date
|
|
||||||
</StyledInputDescription>
|
|
||||||
<StyledExpirationPicker custom={customExpiration}>
|
|
||||||
<StyledSelectMenu
|
|
||||||
name="expiration"
|
|
||||||
id="expiration"
|
|
||||||
label="Token will expire in"
|
|
||||||
value={expiration}
|
|
||||||
onChange={e =>
|
|
||||||
setExpiration(
|
|
||||||
e.target.value as ExpirationOption
|
|
||||||
)
|
|
||||||
}
|
|
||||||
options={expirationOptions}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={customExpiration}
|
|
||||||
show={() => (
|
|
||||||
<StyledDateTimePicker
|
|
||||||
label="Date"
|
|
||||||
value={expiresAt}
|
|
||||||
onChange={date => {
|
|
||||||
clearError(ErrorField.EXPIRES_AT);
|
|
||||||
if (date < new Date()) {
|
|
||||||
setError(
|
|
||||||
ErrorField.EXPIRES_AT,
|
|
||||||
'Invalid date, must be in the future'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setExpiresAt(date);
|
|
||||||
}}
|
|
||||||
min={new Date()}
|
|
||||||
error={Boolean(errors.expiresAt)}
|
|
||||||
errorText={errors.expiresAt}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
elseShow={
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={neverExpires}
|
|
||||||
show={
|
|
||||||
<Typography variant="body2">
|
|
||||||
The token will{' '}
|
|
||||||
<strong>never</strong> expire!
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
elseShow={() => (
|
|
||||||
<Typography variant="body2">
|
|
||||||
Token will expire on{' '}
|
|
||||||
<strong>
|
|
||||||
{formatDateYMD(
|
|
||||||
expiresAt!,
|
|
||||||
locationSettings.locale
|
|
||||||
)}
|
|
||||||
</strong>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</StyledExpirationPicker>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={neverExpires}
|
|
||||||
show={
|
|
||||||
<StyledAlert severity="warning">
|
|
||||||
We strongly recommend that you set an
|
|
||||||
expiration date for your token to help keep
|
|
||||||
your information secure.
|
|
||||||
</StyledAlert>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -0,0 +1,254 @@
|
|||||||
|
import Input from 'component/common/Input/Input';
|
||||||
|
import SelectMenu from 'component/common/select';
|
||||||
|
import { formatDateYMD } from 'utils/formatDate';
|
||||||
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { DateTimePicker } from 'component/common/DateTimePicker/DateTimePicker';
|
||||||
|
import { Alert, styled, Typography } from '@mui/material';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: theme.spacing(50),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledExpirationPicker = styled('div')<{ custom?: boolean }>(
|
||||||
|
({ theme, custom }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: custom ? 'start' : 'center',
|
||||||
|
gap: theme.spacing(1.5),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const StyledSelectMenu = styled(SelectMenu)(({ theme }) => ({
|
||||||
|
minWidth: theme.spacing(20),
|
||||||
|
marginRight: theme.spacing(0.5),
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
width: theme.spacing(50),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDateTimePicker = styled(DateTimePicker)(({ theme }) => ({
|
||||||
|
width: theme.spacing(28),
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
width: theme.spacing(50),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
maxWidth: theme.spacing(50),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export enum ExpirationOption {
|
||||||
|
'7DAYS' = '7d',
|
||||||
|
'30DAYS' = '30d',
|
||||||
|
'60DAYS' = '60d',
|
||||||
|
NEVER = 'never',
|
||||||
|
CUSTOM = 'custom',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expirationOptions = [
|
||||||
|
{
|
||||||
|
key: ExpirationOption['7DAYS'],
|
||||||
|
days: 7,
|
||||||
|
label: '7 days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: ExpirationOption['30DAYS'],
|
||||||
|
days: 30,
|
||||||
|
label: '30 days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: ExpirationOption['60DAYS'],
|
||||||
|
days: 60,
|
||||||
|
label: '60 days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: ExpirationOption.NEVER,
|
||||||
|
label: 'Never',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: ExpirationOption.CUSTOM,
|
||||||
|
label: 'Custom',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const calculateExpirationDate = (expiration: ExpirationOption) => {
|
||||||
|
const expiresAt = new Date();
|
||||||
|
const expirationOption = expirationOptions.find(
|
||||||
|
({ key }) => key === expiration
|
||||||
|
);
|
||||||
|
if (expiration === ExpirationOption.NEVER) {
|
||||||
|
expiresAt.setFullYear(expiresAt.getFullYear() + 1000);
|
||||||
|
} else if (expiration === ExpirationOption.CUSTOM) {
|
||||||
|
expiresAt.setMinutes(expiresAt.getMinutes() + 30);
|
||||||
|
} else if (expirationOption?.days) {
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + expirationOption.days);
|
||||||
|
}
|
||||||
|
return expiresAt;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ErrorField {
|
||||||
|
DESCRIPTION = 'description',
|
||||||
|
EXPIRES_AT = 'expiresAt',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPersonalAPITokenFormErrors {
|
||||||
|
[ErrorField.DESCRIPTION]?: string;
|
||||||
|
[ErrorField.EXPIRES_AT]?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPersonalAPITokenFormProps {
|
||||||
|
description: string;
|
||||||
|
setDescription: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
isDescriptionUnique?: (description: string) => boolean;
|
||||||
|
expiration: ExpirationOption;
|
||||||
|
setExpiration: (expiration: ExpirationOption) => void;
|
||||||
|
expiresAt: Date;
|
||||||
|
setExpiresAt: React.Dispatch<React.SetStateAction<Date>>;
|
||||||
|
errors: IPersonalAPITokenFormErrors;
|
||||||
|
setErrors: React.Dispatch<
|
||||||
|
React.SetStateAction<IPersonalAPITokenFormErrors>
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PersonalAPITokenForm = ({
|
||||||
|
description,
|
||||||
|
setDescription,
|
||||||
|
isDescriptionUnique,
|
||||||
|
expiration,
|
||||||
|
setExpiration,
|
||||||
|
expiresAt,
|
||||||
|
setExpiresAt,
|
||||||
|
errors,
|
||||||
|
setErrors,
|
||||||
|
}: IPersonalAPITokenFormProps) => {
|
||||||
|
const { locationSettings } = useLocationSettings();
|
||||||
|
|
||||||
|
const clearError = (field: ErrorField) => {
|
||||||
|
setErrors(errors => ({ ...errors, [field]: undefined }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const setError = (field: ErrorField, error: string) => {
|
||||||
|
setErrors(errors => ({ ...errors, [field]: error }));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearError(ErrorField.EXPIRES_AT);
|
||||||
|
setExpiresAt(calculateExpirationDate(expiration));
|
||||||
|
}, [expiration]);
|
||||||
|
|
||||||
|
const onSetDescription = (description: string) => {
|
||||||
|
clearError(ErrorField.DESCRIPTION);
|
||||||
|
if (isDescriptionUnique && !isDescriptionUnique(description)) {
|
||||||
|
setError(
|
||||||
|
ErrorField.DESCRIPTION,
|
||||||
|
'A personal API token with that description already exists.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setDescription(description);
|
||||||
|
};
|
||||||
|
|
||||||
|
const customExpiration = expiration === ExpirationOption.CUSTOM;
|
||||||
|
|
||||||
|
const neverExpires =
|
||||||
|
expiresAt.getFullYear() > new Date().getFullYear() + 100;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledInputDescription>
|
||||||
|
Describe what this token will be used for
|
||||||
|
</StyledInputDescription>
|
||||||
|
<StyledInput
|
||||||
|
autoFocus
|
||||||
|
label="Description"
|
||||||
|
error={Boolean(errors.description)}
|
||||||
|
errorText={errors.description}
|
||||||
|
value={description}
|
||||||
|
onChange={e => onSetDescription(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<StyledInputDescription>
|
||||||
|
Token expiration date
|
||||||
|
</StyledInputDescription>
|
||||||
|
<StyledExpirationPicker custom={customExpiration}>
|
||||||
|
<StyledSelectMenu
|
||||||
|
name="expiration"
|
||||||
|
id="expiration"
|
||||||
|
label="Token will expire in"
|
||||||
|
value={expiration}
|
||||||
|
onChange={e =>
|
||||||
|
setExpiration(e.target.value as ExpirationOption)
|
||||||
|
}
|
||||||
|
options={expirationOptions}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={customExpiration}
|
||||||
|
show={() => (
|
||||||
|
<StyledDateTimePicker
|
||||||
|
label="Date"
|
||||||
|
value={expiresAt}
|
||||||
|
onChange={date => {
|
||||||
|
clearError(ErrorField.EXPIRES_AT);
|
||||||
|
if (date < new Date()) {
|
||||||
|
setError(
|
||||||
|
ErrorField.EXPIRES_AT,
|
||||||
|
'Invalid date, must be in the future'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setExpiresAt(date);
|
||||||
|
}}
|
||||||
|
min={new Date()}
|
||||||
|
error={Boolean(errors.expiresAt)}
|
||||||
|
errorText={errors.expiresAt}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
elseShow={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={neverExpires}
|
||||||
|
show={
|
||||||
|
<Typography variant="body2">
|
||||||
|
The token will <strong>never</strong>{' '}
|
||||||
|
expire!
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
elseShow={() => (
|
||||||
|
<Typography variant="body2">
|
||||||
|
Token will expire on{' '}
|
||||||
|
<strong>
|
||||||
|
{formatDateYMD(
|
||||||
|
expiresAt!,
|
||||||
|
locationSettings.locale
|
||||||
|
)}
|
||||||
|
</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledExpirationPicker>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={neverExpires}
|
||||||
|
show={
|
||||||
|
<StyledAlert severity="warning">
|
||||||
|
We strongly recommend that you set an expiration date
|
||||||
|
for your token to help keep your information secure.
|
||||||
|
</StyledAlert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user