Skip to content

Commit

Permalink
Consistent date format in update birthday modal (#1115)
Browse files Browse the repository at this point in the history
* Consistent date format in update birthday modal

* Generic formatting of date in birthday modal
  • Loading branch information
batebobo authored and dimitur2204 committed Nov 21, 2022
1 parent e35b41c commit 56d9392
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 29 deletions.
46 changes: 39 additions & 7 deletions src/common/util/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,52 @@ export const dateFormatter = (value: Date | string | number, locale?: Locale) =>
return `${exact} (${relative})`
}

export const formatDateString = (dateString: string | Date) => {
const date = new Date(dateString)
const day = date.getDate().toString().padStart(2, '0')
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const year = date.getFullYear()

return `${day}.${month}.${year}`
export const formatDateString = (dateString: string | Date, locale?: string | undefined) => {
if (locale) {
return Intl.DateTimeFormat(locale.split('-')).format(new Date(dateString))
}
return new Date(dateString).toLocaleDateString()
}

export const getRelativeDate = (value: Date | string, locale?: Locale) => {
const date = new Date(value)
return formatRelative(date, new Date(), { locale })
}

/**
* Value format for storing and passing date strings. Should be used whenever we pass date strings between components.
*/
export const DATE_VALUE_FORMAT = 'yyyy-MM-dd'

/**
* Utility to return localized date format string.
* @param language Language to get the corresponding date format
* @returns Format string
*/
export const getDateFormat = (language: string) => {
const dateParts = Intl.DateTimeFormat(language.split('-')).formatToParts(new Date())

// Find the separator i.e. ".", "/", etc.
const literal = dateParts.find((part) => part.type === 'literal')

// Extract the locale date format
return dateParts
.filter((part) => part.type !== 'literal')
.map((part) => {
switch (part.type) {
case 'day':
return 'dd'
case 'month':
return 'MM'
case 'year':
return 'yyyy'
default:
return ''
}
})
.join(literal?.value ?? '')
}

/**
* Gets the duration in time from the specified date until now
* @param date Date to get the interval from
Expand Down
4 changes: 2 additions & 2 deletions src/components/auth/profile/PersonalInfoTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import UpdateBirthdateModal from './UpdateBirthdateModal'
import UpdateEmailModal from './UpdateEmailModal'
import UpdatePasswordModal from './UpdatePasswordModal'
import DisableAccountModal from './DisableAccountModal'
import { useTranslation } from 'next-i18next'
import { i18n, useTranslation } from 'next-i18next'

const PREFIX = 'PersonalInfoTab'

Expand Down Expand Up @@ -188,7 +188,7 @@ export default function PersonalInfoTab() {
<p className={classes.bold}>{t('profile:personalInfo.birthday')}</p>
<Typography sx={{ color: person?.birthday ? undefined : '#F22727' }}>
{person?.birthday
? formatDateString(person?.birthday)
? formatDateString(person?.birthday, i18n?.language)
: t('profile:personalInfo.noBirthday')}
</Typography>
<Box className={classes.editBox}>
Expand Down
32 changes: 12 additions & 20 deletions src/components/auth/profile/UpdateBirthdateModal.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import * as yup from 'yup'
import { styled } from '@mui/material/styles'
import { useState } from 'react'
import { Modal, Box, Grid, IconButton } from '@mui/material'
import { useMutation } from 'react-query'
import { AxiosError, AxiosResponse } from 'axios'
import { useTranslation } from 'next-i18next'
import CloseIcon from '@mui/icons-material/Close'
import { format, parse, isDate } from 'date-fns'

import GenericForm from 'components/common/form/GenericForm'
import SubmitButton from 'components/common/form/SubmitButton'
import { Person, UpdateUserAccount, UpdatePerson } from 'gql/person'
import { useMutation } from 'react-query'
import { AxiosError, AxiosResponse } from 'axios'
import { ApiErrors } from 'service/apiErrors'
import { updateCurrentPerson } from 'common/util/useCurrentPerson'

import { AlertStore } from 'stores/AlertStore'
import { useTranslation } from 'next-i18next'
import CloseIcon from '@mui/icons-material/Close'
import { format, parse, isDate } from 'date-fns'
import FormTextField from 'components/common/form/FormTextField'
import { useState } from 'react'
import FormDatePicker from 'components/common/form/FormDatePicker'
import { DATE_VALUE_FORMAT } from 'common/util/date'

const PREFIX = 'UpdateBirthdateModal'

Expand Down Expand Up @@ -42,12 +43,10 @@ const StyledModal = styled(Modal)(({ theme }) => ({
},
}))

const formatString = 'yyyy-MM-dd'

const parseDateString = (value: string, originalValue: string) => {
const parsedDate = isDate(originalValue)
? originalValue
: parse(originalValue, formatString, new Date())
: parse(originalValue, DATE_VALUE_FORMAT, new Date())

return parsedDate
}
Expand Down Expand Up @@ -80,7 +79,7 @@ function UpdateBirthdateModal({
const dateBefore18Years = new Date(new Date().setFullYear(new Date().getFullYear() - 18))

const initialValues: Pick<UpdateUserAccount, 'birthday'> = {
birthday: format(new Date(person.birthday ?? dateBefore18Years), formatString) || '',
birthday: format(new Date(person.birthday ?? dateBefore18Years), DATE_VALUE_FORMAT) || '',
}

const mutation = useMutation<AxiosResponse<Person>, AxiosError<ApiErrors>, UpdatePerson>({
Expand Down Expand Up @@ -120,14 +119,7 @@ function UpdateBirthdateModal({
validationSchema={validationSchema}>
<Grid container spacing={3}>
<Grid item xs={12} sm={8}>
<FormTextField
type="date"
name="birthday"
label={t('profile:birthdateModal.question')}
InputLabelProps={{
shrink: true,
}}
/>
<FormDatePicker name="birthday" label={t('profile:birthdateModal.question')} />
</Grid>
<Grid item xs={6}>
<SubmitButton fullWidth label="auth:cta.send" loading={loading} />
Expand Down
47 changes: 47 additions & 0 deletions src/components/common/form/FormDatePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { TextField } from '@mui/material'
import { LocalizationProvider, DatePicker } from '@mui/x-date-pickers'
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'
import { format } from 'date-fns'
import { useField, useFormikContext } from 'formik'
import { useTranslation } from 'next-i18next'

import { DATE_VALUE_FORMAT, getDateFormat } from 'common/util/date'

/**
* MUI date picker to be connected with Formik. Propagates updates to the passed Formik field name
* @param name - name of the Formik field to bind
* @param label - prompt text
* @returns
*/
export default function FormDatePicker({ name, label }: { name: string; label: string }) {
const [field] = useField(name)
const { setFieldValue } = useFormikContext()
const { i18n } = useTranslation()

const dateViewFormat = getDateFormat(i18n.language)
const mask = dateViewFormat.replace(new RegExp(/[^./]/g), '_')

const updateValue = (newValue: Date) => {
let formattedValue
try {
formattedValue = format(newValue, DATE_VALUE_FORMAT)
} catch {
formattedValue = field.value
} finally {
setFieldValue(name, formattedValue, true)
}
}

return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
mask={mask}
inputFormat={dateViewFormat}
label={label}
value={field.value}
onChange={(newValue) => updateValue(newValue)}
renderInput={(params) => <TextField size="small" {...params} />}
/>
</LocalizationProvider>
)
}

0 comments on commit 56d9392

Please sign in to comment.