Skip to content

Commit

Permalink
Removing duplicate code. Added a function to validate date and if inv…
Browse files Browse the repository at this point in the history
…alid, format the date.

I've written tests for the functions
  • Loading branch information
dr-bizz committed Jun 20, 2024
1 parent 75c4b1a commit 360fac1
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import CakeIcon from '@mui/icons-material/Cake';
import { FormikProps } from 'formik';
import { DateTime } from 'luxon';
Expand All @@ -8,9 +8,12 @@ import {
PersonCreateInput,
PersonUpdateInput,
} from 'src/graphql/types.generated';
import { useLocale } from 'src/hooks/useLocale';
import { validateAndFormatInvalidDate } from 'src/lib/intlFormat';
import { ModalSectionContainer } from '../ModalSectionContainer/ModalSectionContainer';
import { ModalSectionIcon } from '../ModalSectionIcon/ModalSectionIcon';
import { NewSocial } from '../PersonModal';
import { buildDate } from '../personModalHelper';

interface PersonBirthdayProps {
formikProps: FormikProps<(PersonUpdateInput | PersonCreateInput) & NewSocial>;
Expand All @@ -20,39 +23,54 @@ export const PersonBirthday: React.FC<PersonBirthdayProps> = ({
formikProps,
}) => {
const { t } = useTranslation();
const locale = useLocale();
const [birthdayDateIsInvalid, setBirthdayDateIsInvalid] = useState(false);
const [backupBirthdayDate, setBackupBirthdayDate] =
useState<DateTime<boolean> | null>(null);

const {
values: { birthdayDay, birthdayMonth, birthdayYear },
setFieldValue,
} = formikProps;

const handleDateChange = (date: DateTime | null) => {
setFieldValue('birthdayDay', date?.day || null);
setFieldValue('birthdayMonth', date?.month || null);
setFieldValue('birthdayYear', date?.year || null);
};
useEffect(() => {
if (typeof birthdayMonth !== 'number' || typeof birthdayDay !== 'number') {
return;
}

const date = validateAndFormatInvalidDate(
birthdayYear,
birthdayMonth,
birthdayDay,
locale,
);

const birthdayDate =
birthdayMonth && birthdayDay
? DateTime.local(birthdayYear ?? 1900, birthdayMonth, birthdayDay)
: null;
setBackupBirthdayDate(
date.formattedInvalidDate as unknown as DateTime<boolean>,
);
if (date.dateTime.invalidExplanation) {
setBirthdayDateIsInvalid(true);
}
}, [birthdayMonth, birthdayDay, birthdayYear]);

const invalidDate =
birthdayMonth === 0 && birthdayDay === 0
? DateTime.local(birthdayYear ?? 1900, birthdayMonth, birthdayDay)
.invalidExplanation !== ''
: false;
const handleDateChange = (date: DateTime | null) => {
setFieldValue('birthdayDay', date?.day ?? null);
setFieldValue('birthdayMonth', date?.month ?? null);
setFieldValue('birthdayYear', date?.year ?? null);
};

const backupBirthdayDate =
`${birthdayMonth}/${birthdayDay}/${birthdayYear}` as unknown as DateTime<boolean>;
const birthdayDate = useMemo(
() => buildDate(birthdayMonth, birthdayDay, birthdayYear),
[birthdayMonth, birthdayDay, birthdayYear],
);

return (
<ModalSectionContainer>
<ModalSectionIcon transform="translateY(-100%)" icon={<CakeIcon />} />
<CustomDateField
label={t('Birthday')}
invalidDate={invalidDate}
value={invalidDate ? backupBirthdayDate : birthdayDate}
invalidDate={birthdayDateIsInvalid}
value={birthdayDateIsInvalid ? backupBirthdayDate : birthdayDate}
onChange={(date) => handleDateChange(date)}
/>
</ModalSectionContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import BusinessIcon from '@mui/icons-material/Business';
import SchoolIcon from '@mui/icons-material/School';
import {
Expand All @@ -20,11 +20,14 @@ import {
PersonCreateInput,
PersonUpdateInput,
} from 'src/graphql/types.generated';
import { useLocale } from 'src/hooks/useLocale';
import { validateAndFormatInvalidDate } from 'src/lib/intlFormat';
import { RingIcon } from '../../../../../../RingIcon';
import { ModalSectionContainer } from '../ModalSectionContainer/ModalSectionContainer';
import { ModalSectionIcon } from '../ModalSectionIcon/ModalSectionIcon';
import { NewSocial } from '../PersonModal';
import { PersonSocial } from '../PersonSocials/PersonSocials';
import { buildDate } from '../personModalHelper';

const DeceasedLabel = styled(FormControlLabel)(() => ({
margin: 'none',
Expand All @@ -40,6 +43,11 @@ export const PersonShowMore: React.FC<PersonShowMoreProps> = ({
showDeceased = true,
}) => {
const { t } = useTranslation();
const locale = useLocale();
const [anniversaryDateIsInvalid, setAnniversaryDateIsInvalid] =
useState(false);
const [backupAnniversaryDate, setBackupAnniversaryDate] =
useState<DateTime<boolean> | null>(null);

const {
values: {
Expand All @@ -58,32 +66,39 @@ export const PersonShowMore: React.FC<PersonShowMoreProps> = ({
setFieldValue,
} = formikProps;

const handleDateChange = (date: DateTime | null) => {
setFieldValue('anniversaryDay', date?.day || null);
setFieldValue('anniversaryMonth', date?.month || null);
setFieldValue('anniversaryYear', date?.year || null);
};
useEffect(() => {
if (
typeof anniversaryMonth !== 'number' ||
typeof anniversaryDay !== 'number'
) {
return;

Check warning on line 74 in src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonShowMore/PersonShowMore.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonShowMore/PersonShowMore.tsx#L74

Added line #L74 was not covered by tests
}

const anniversaryDate =
anniversaryMonth && anniversaryDay
? DateTime.local(
anniversaryYear ?? 1900,
anniversaryMonth,
anniversaryDay,
)
: null;
const date = validateAndFormatInvalidDate(
anniversaryYear,
anniversaryMonth,
anniversaryDay,
locale,
);

const invalidAnniversaryDate =
anniversaryMonth === 0 && anniversaryDay === 0
? DateTime.local(
anniversaryYear ?? 1900,
anniversaryMonth,
anniversaryDay,
).invalidExplanation !== ''
: false;
setBackupAnniversaryDate(
date.formattedInvalidDate as unknown as DateTime<boolean>,
);
if (date.dateTime.invalidExplanation) {
setAnniversaryDateIsInvalid(true);
}
}, [anniversaryMonth, anniversaryDay, anniversaryYear]);

const backupAnniversaryDate =
`${anniversaryMonth}/${anniversaryDay}/${anniversaryYear}` as unknown as DateTime<boolean>;
const handleDateChange = (date: DateTime | null) => {
setFieldValue('anniversaryDay', date?.day ?? null);
setFieldValue('anniversaryMonth', date?.month ?? null);
setFieldValue('anniversaryYear', date?.year ?? null);
};

const anniversaryDate = useMemo(
() => buildDate(anniversaryMonth, anniversaryDay, anniversaryYear),
[anniversaryMonth, anniversaryDay, anniversaryYear],
);

return (
<>
Expand Down Expand Up @@ -164,9 +179,11 @@ export const PersonShowMore: React.FC<PersonShowMoreProps> = ({
<Grid item xs={12} sm={6}>
<CustomDateField
label={t('Anniversary')}
invalidDate={invalidAnniversaryDate}
invalidDate={anniversaryDateIsInvalid}
value={
invalidAnniversaryDate ? backupAnniversaryDate : anniversaryDate
anniversaryDateIsInvalid
? backupAnniversaryDate
: anniversaryDate
}
onChange={(date) => date && handleDateChange(date)}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DateTime } from 'luxon';
import { TFunction } from 'react-i18next';
import * as yup from 'yup';
import {
Expand Down Expand Up @@ -263,3 +264,7 @@ export const formatSubmittedFields = (
),
};
};

export const buildDate = (month, day, year) => {
return month && day ? DateTime.local(year ?? 1900, month, day) : null;
};
33 changes: 32 additions & 1 deletion src/lib/intlFormat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
monthYearFormat,
numberFormat,
percentageFormat,
validateAndFormatInvalidDate,
} from './intlFormat';

describe('intlFormat', () => {
Expand Down Expand Up @@ -186,14 +187,44 @@ describe('intlFormat', () => {
expect(date).toBeNull();
});

it('returns if month is null', () => {
it('handle an invalid date', () => {
const date = dateFromParts(2000, 0, 0, locale);

expect(date).toBe('0/0/2000 - Invalid Date, please fix.');
});

it('handle an invalid date without a year', () => {
const date = dateFromParts(null, 0, 0, locale);

expect(date).toBe('0/0/2020 - Invalid Date, please fix.');
});

it('handle an invalid date where we can not format the invalid date', () => {
const date = dateFromParts(0, 0, 2000, locale);

expect(date).toBe(
'Invalid Date - you specified 0 (of type number) as a month, which is invalid',
);
});
});

describe('validateAndFormatInvalidDate', () => {
const locale = 'en-US';
it('returns invalid date en-US formatted', () => {
const date = validateAndFormatInvalidDate(2000, 0, 0, locale);
expect(date.formattedInvalidDate).toBe('0/0/2000');
});

it('returns invalid date en-UK formatted', () => {
const date = validateAndFormatInvalidDate(2000, 0, 0, 'en-UK');
expect(date.formattedInvalidDate).toBe('0/00/2000');
});

it('returns invalid date de formatted', () => {
const date = validateAndFormatInvalidDate(2000, 0, 0, 'de');
expect(date.formattedInvalidDate).toBe('0.0.2000');
});
});
//this test often fails locally. It passes on github.
describe('dateTimeFormat', () => {
const locale = 'en-US';
Expand Down
53 changes: 44 additions & 9 deletions src/lib/intlFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,17 @@ export const dateFromParts = (
return null;
}

if (typeof year === 'number') {
const date = DateTime.local(year, month, day);
if (date.invalidReason || date.invalidExplanation) {
return `Invalid Date - ${date.invalidExplanation}`;
const date = validateAndFormatInvalidDate(year, month, day, locale);
if (date.dateTime.invalidExplanation) {
if (date.formattedInvalidDate) {
return `${date.formattedInvalidDate} - Invalid Date, please fix.`;
} else {
return `Invalid Date - ${date.dateTime.invalidExplanation}`;
}
return dateFormat(date, locale);
}
if (typeof year === 'number') {
return dateFormat(date.dateTime, locale);
} else {
const date = DateTime.local().set({ month, day });
if (date.invalidReason || date.invalidExplanation) {
return `Invalid Date - ${date.invalidExplanation}`;
}
return dayMonthFormat(day, month, locale);
}
};
Expand All @@ -120,6 +120,41 @@ export const dateTimeFormat = (
}).format(date.toJSDate());
};

export const validateAndFormatInvalidDate = (
year: number | null | undefined,
month: number,
day: number,
locale: string,
) => {
const yyyy = year ?? DateTime.local().year;
const date = DateTime.local(yyyy, month, day);
let formattedInvalidDate = '';
if (date.invalidExplanation && month === 0 && day === 0) {
const placeholderYear = 2024;
const placeholderMonth = 8;
const placeholderDay = 15;
const placeholderDate = new Date(
placeholderYear,
placeholderMonth - 1,
placeholderDay,
);
const formattedPlaceholderDate = dateFormatShort(
DateTime.fromISO(placeholderDate.toISOString()),
locale,
);

formattedInvalidDate = formattedPlaceholderDate
.replace(`${placeholderYear}`, `${yyyy}`)
.replace(`${placeholderMonth}`, `${month}`)
.replace(`${placeholderDay}`, `${day}`);
}

return {
formattedInvalidDate,
dateTime: date,
};
};

const intlFormat = {
numberFormat,
percentageFormat,
Expand Down

0 comments on commit 360fac1

Please sign in to comment.