mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-27 11:02:16 +01:00
Implements client-side validation of constraint values before you can add them to a constraint. I've removed the extra server-side validation that used to happen for each specific constraint, because the surrounding form itself uses server side validation to check every constraint every time there's a change. This is what controls disabling the submit button etc. I wanna make the next PR a bit of a followup cleanup now that it's clearer what properties we do and don't need. <img width="371" alt="image" src="https://github.com/user-attachments/assets/7c98708f-fcbe-40ca-8590-bb0f5b2ad167" /> <img width="361" alt="image" src="https://github.com/user-attachments/assets/503d4841-d910-4e8e-b0ef-a3d725739534" />
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import Input from 'component/common/Input/Input';
|
|
import { parseDateValue, parseValidDate } from 'component/common/util';
|
|
|
|
import { useId, useMemo, useState } from 'react';
|
|
import { styled } from '@mui/material';
|
|
import TimezoneCountries from 'countries-and-timezones';
|
|
import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly';
|
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
|
import type { ConstraintValidatorOutput } from 'component/common/NewConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators';
|
|
|
|
interface IDateSingleValueProps {
|
|
setValue: (value: string) => void;
|
|
value?: string;
|
|
validator: (value: string) => ConstraintValidatorOutput;
|
|
}
|
|
|
|
const StyledInput = styled(Input)({
|
|
border: 'none',
|
|
'*': {
|
|
border: 'none',
|
|
padding: 0,
|
|
},
|
|
});
|
|
|
|
const Container = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexFlow: 'row wrap',
|
|
alignItems: 'center',
|
|
}));
|
|
|
|
export const ConstraintDateInput = ({
|
|
setValue,
|
|
value,
|
|
validator,
|
|
}: IDateSingleValueProps) => {
|
|
const [error, setError] = useState('');
|
|
const timezones = Object.values(
|
|
TimezoneCountries.getAllTimezones({ deprecated: false }) as {
|
|
[timezone: string]: { name: string; utcOffsetStr: string };
|
|
},
|
|
).map((timezone) => ({
|
|
key: timezone.name,
|
|
label: `${timezone.name}`,
|
|
utcOffset: timezone.utcOffsetStr,
|
|
}));
|
|
const { timeZone: localTimezoneName } =
|
|
Intl.DateTimeFormat().resolvedOptions();
|
|
const [pickedDate, setPickedDate] = useState(value || '');
|
|
const inputId = useId();
|
|
const helpId = useId();
|
|
|
|
const timezoneText = useMemo<string>(() => {
|
|
const localTimezone = timezones.find(
|
|
(t) => t.key === localTimezoneName,
|
|
);
|
|
if (localTimezone != null) {
|
|
return `${localTimezone.key} (UTC ${localTimezone.utcOffset})`;
|
|
} else {
|
|
return 'The time shown is in your local time zone according to your browser.';
|
|
}
|
|
}, [timezones, localTimezoneName]);
|
|
|
|
if (!value) return null;
|
|
|
|
return (
|
|
<Container>
|
|
<label htmlFor={inputId}>
|
|
<ScreenReaderOnly>Date</ScreenReaderOnly>
|
|
</label>
|
|
<StyledInput
|
|
id={inputId}
|
|
aria-describedby={helpId}
|
|
hiddenLabel
|
|
label=''
|
|
size='small'
|
|
type='datetime-local'
|
|
value={parseDateValue(pickedDate)}
|
|
onChange={(e) => {
|
|
setError('');
|
|
const parsedDate = parseValidDate(e.target.value);
|
|
const dateString = parsedDate?.toISOString();
|
|
if (!dateString) {
|
|
setError('Invalid date format');
|
|
} else {
|
|
setPickedDate(dateString);
|
|
|
|
const [isValid, errorMessage] = validator(dateString);
|
|
if (isValid) {
|
|
dateString && setValue(dateString);
|
|
} else {
|
|
setError(errorMessage);
|
|
}
|
|
}
|
|
}}
|
|
InputLabelProps={{
|
|
shrink: true,
|
|
}}
|
|
error={Boolean(error)}
|
|
errorText={error}
|
|
required
|
|
/>
|
|
<HelpIcon htmlTooltip tooltip={<p id={helpId}>{timezoneText}</p>} />
|
|
</Container>
|
|
);
|
|
};
|