mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-08 15:05:22 +02:00
Issue #81: Date inputs should respect users date format
This commit is contained in:
parent
bcf9d119f6
commit
5ab2374ebb
4 changed files with 146 additions and 96 deletions
|
@ -92,7 +92,7 @@ type ProfileViewProps = {
|
|||
function ProfileForm({ title, onSubmit, defaultValues }: ProfileViewProps) {
|
||||
const [currentQuestion, setCurrentQuestion] = useState<
|
||||
'birthday' | 'household' | 'residence' | 'goals'
|
||||
>('birthday')
|
||||
>('residence')
|
||||
|
||||
const {
|
||||
control,
|
||||
|
@ -122,98 +122,13 @@ function ProfileForm({ title, onSubmit, defaultValues }: ProfileViewProps) {
|
|||
to building plans or receiving advice from our advisors.
|
||||
</p>
|
||||
<div className="mt-5 space-y-3">
|
||||
<Question
|
||||
open={currentQuestion === 'birthday'}
|
||||
valid={!errors.dob}
|
||||
icon={RiCakeLine}
|
||||
label={<>When’s your birthday?</>}
|
||||
onClick={() => setCurrentQuestion('birthday')}
|
||||
next={() => setCurrentQuestion('household')}
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="dob"
|
||||
rules={{
|
||||
validate: (d) =>
|
||||
BrowserUtil.validateFormDate(d, {
|
||||
minDate: DateTime.now().minus({ years: 100 }).toISODate(),
|
||||
required: true,
|
||||
}),
|
||||
}}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<DatePicker
|
||||
popperPlacement="bottom"
|
||||
className="mt-2"
|
||||
minCalendarDate={DateTime.now().minus({ years: 100 }).toISODate()}
|
||||
error={error?.message}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Tooltip
|
||||
placement="bottom-start"
|
||||
content={
|
||||
<>
|
||||
We use your age to personalize plans to your context instead of
|
||||
showing years when referring to future events. “Retire at
|
||||
45” sounds better than “Retire in 2043”.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="flex items-center mt-4 text-base text-gray-50 cursor-default">
|
||||
<RiQuestionLine className="w-5 h-5 mr-2 text-gray-100" />
|
||||
Why do we need your age?
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Question>
|
||||
|
||||
<Question
|
||||
open={currentQuestion === 'household'}
|
||||
valid={!errors.household}
|
||||
icon={RiHome5Line}
|
||||
label="Which best describes your household?"
|
||||
onClick={() => setCurrentQuestion('household')}
|
||||
back={() => setCurrentQuestion('birthday')}
|
||||
next={() => setCurrentQuestion('residence')}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="household"
|
||||
rules={{ required: true }}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{Object.entries({
|
||||
single: 'Single income, no dependents',
|
||||
singleWithDependents:
|
||||
'Single income, at least one dependent',
|
||||
dual: 'Dual income, no dependents',
|
||||
dualWithDependents: 'Dual income, at least one dependent',
|
||||
retired: 'Retired or financially independent',
|
||||
}).map(([value, label]) => (
|
||||
<Checkbox
|
||||
key={value}
|
||||
label={label}
|
||||
checked={field.value === value}
|
||||
onChange={(checked) => {
|
||||
if (checked) field.onChange(value)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Question>
|
||||
|
||||
<Question
|
||||
open={currentQuestion === 'residence'}
|
||||
valid={!errors.country}
|
||||
icon={RiMapPin2Line}
|
||||
label="Where are you based?"
|
||||
onClick={() => setCurrentQuestion('residence')}
|
||||
back={() => setCurrentQuestion('household')}
|
||||
next={() => setCurrentQuestion('goals')}
|
||||
next={() => setCurrentQuestion('birthday')}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<Controller
|
||||
|
@ -253,6 +168,94 @@ function ProfileForm({ title, onSubmit, defaultValues }: ProfileViewProps) {
|
|||
</div>
|
||||
</Tooltip>
|
||||
</Question>
|
||||
<Question
|
||||
open={currentQuestion === 'birthday'}
|
||||
valid={!errors.dob}
|
||||
icon={RiCakeLine}
|
||||
label={<>When’s your birthday?</>}
|
||||
onClick={() => setCurrentQuestion('birthday')}
|
||||
next={() => setCurrentQuestion('household')}
|
||||
back={() => setCurrentQuestion('residence')}
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="dob"
|
||||
rules={{
|
||||
validate: (d) =>
|
||||
BrowserUtil.validateFormDate(d, {
|
||||
minDate: DateTime.now().minus({ years: 100 }).toISODate(),
|
||||
required: true,
|
||||
}),
|
||||
}}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<DatePicker
|
||||
popperPlacement="bottom"
|
||||
className="mt-2"
|
||||
minCalendarDate={DateTime.now().minus({ years: 100 }).toISODate()}
|
||||
error={error?.message}
|
||||
placeholder={BrowserUtil.getDateFormatByCountryCode(
|
||||
country
|
||||
).toUpperCase()}
|
||||
dateFormat={BrowserUtil.getDateFormatByCountryCode(country)}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Tooltip
|
||||
placement="bottom-start"
|
||||
content={
|
||||
<>
|
||||
We use your age to personalize plans to your context instead of
|
||||
showing years when referring to future events. “Retire at
|
||||
45” sounds better than “Retire in 2043”.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="flex items-center mt-4 text-base text-gray-50 cursor-default">
|
||||
<RiQuestionLine className="w-5 h-5 mr-2 text-gray-100" />
|
||||
Why do we need your age?
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Question>
|
||||
|
||||
<Question
|
||||
open={currentQuestion === 'household'}
|
||||
valid={!errors.household}
|
||||
icon={RiHome5Line}
|
||||
label="Which best describes your household?"
|
||||
onClick={() => setCurrentQuestion('household')}
|
||||
back={() => setCurrentQuestion('birthday')}
|
||||
next={() => setCurrentQuestion('goals')}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="household"
|
||||
rules={{ required: true }}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{Object.entries({
|
||||
single: 'Single income, no dependents',
|
||||
singleWithDependents:
|
||||
'Single income, at least one dependent',
|
||||
dual: 'Dual income, no dependents',
|
||||
dualWithDependents: 'Dual income, at least one dependent',
|
||||
retired: 'Retired or financially independent',
|
||||
}).map(([value, label]) => (
|
||||
<Checkbox
|
||||
key={value}
|
||||
label={label}
|
||||
checked={field.value === value}
|
||||
onChange={(checked) => {
|
||||
if (checked) field.onChange(value)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Question>
|
||||
|
||||
<Question
|
||||
open={currentQuestion === 'goals'}
|
||||
|
|
|
@ -2,3 +2,4 @@ export * from './image-loaders'
|
|||
export * from './browser-utils'
|
||||
export * from './account-utils'
|
||||
export * from './form-utils'
|
||||
export * from './profile-utils'
|
||||
|
|
16
libs/client/shared/src/utils/profile-utils.ts
Normal file
16
libs/client/shared/src/utils/profile-utils.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
export function getDateFormatByCountryCode(countryCode: string): string {
|
||||
let dateFormat = ''
|
||||
switch (countryCode.toLowerCase()) {
|
||||
case 'in':
|
||||
dateFormat = 'dd / MM / yyyy'
|
||||
break
|
||||
case 'us':
|
||||
dateFormat = 'yyyy / MM / dd'
|
||||
break
|
||||
default:
|
||||
dateFormat = 'dd / MM / yyyy'
|
||||
break
|
||||
}
|
||||
|
||||
return dateFormat
|
||||
}
|
|
@ -11,7 +11,7 @@ import { usePopper } from 'react-popper'
|
|||
import { DatePickerCalendar } from './DatePickerCalendar'
|
||||
import { MAX_SUPPORTED_DATE, MIN_SUPPORTED_DATE } from './utils'
|
||||
|
||||
const INPUT_DATE_FORMAT = 'MM / dd / yyyy'
|
||||
const DEFAULT_INPUT_DATE_FORMAT = 'MM / dd / yyyy'
|
||||
|
||||
export interface DatePickerProps {
|
||||
name: string
|
||||
|
@ -25,11 +25,41 @@ export interface DatePickerProps {
|
|||
maxCalendarDate?: string
|
||||
popperPlacement?: PopperJs.Placement
|
||||
popperStrategy?: PopperJs.PositioningStrategy
|
||||
dateFormat?: string
|
||||
}
|
||||
|
||||
function toFormattedStr(date: string | null) {
|
||||
function toFormattedStr(date: string | null, dateFormat: string): string {
|
||||
if (!date) return ''
|
||||
return DateTime.fromISO(date).toFormat(INPUT_DATE_FORMAT)
|
||||
return DateTime.fromISO(date).toFormat(dateFormat)
|
||||
}
|
||||
|
||||
// function to get mask from the given format
|
||||
function getMaskArray(dateFormat: String): string[] {
|
||||
return dateFormat
|
||||
.split('/')
|
||||
.map((keyword) => keyword.trim())
|
||||
.join('')
|
||||
.split('')
|
||||
.map((keyword) => keyword.toUpperCase())
|
||||
}
|
||||
|
||||
function getDateFormatPattern(dateFormat: string): string {
|
||||
const dateComponents = dateFormat.split('/')
|
||||
const pattern = dateComponents
|
||||
.map((component) => {
|
||||
if (component.includes('d')) {
|
||||
return ' ## ' // Placeholder for day
|
||||
} else if (component.includes('m') || component.includes('M')) {
|
||||
return ' ## ' // Placeholder for month
|
||||
} else if (component.includes('y')) {
|
||||
return ' #### ' // Placeholder for year
|
||||
}
|
||||
return component // Keep non-date components unchanged
|
||||
})
|
||||
.join('/')
|
||||
.trim()
|
||||
|
||||
return pattern
|
||||
}
|
||||
|
||||
function DatePicker(
|
||||
|
@ -45,6 +75,7 @@ function DatePicker(
|
|||
maxCalendarDate = MAX_SUPPORTED_DATE.toISODate(),
|
||||
popperPlacement = 'auto',
|
||||
popperStrategy = 'fixed',
|
||||
dateFormat = DEFAULT_INPUT_DATE_FORMAT,
|
||||
}: DatePickerProps,
|
||||
ref: Ref<HTMLInputElement>
|
||||
): JSX.Element {
|
||||
|
@ -79,15 +110,14 @@ function DatePicker(
|
|||
setCalendarValue('')
|
||||
onChange(null)
|
||||
} else {
|
||||
const inputDate = DateTime.fromFormat(date.formattedValue, INPUT_DATE_FORMAT)
|
||||
|
||||
const inputDate = DateTime.fromFormat(date.formattedValue, dateFormat)
|
||||
if (inputDate.isValid) {
|
||||
setCalendarValue(inputDate.toISODate())
|
||||
onChange(inputDate.toISODate())
|
||||
}
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
[onChange, dateFormat]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -97,10 +127,10 @@ function DatePicker(
|
|||
name={name}
|
||||
customInput={Input} // passes all props below to <Input /> - https://github.com/s-yadav/react-number-format#custom-inputs
|
||||
getInputRef={ref}
|
||||
format="## / ## / ####"
|
||||
format={getDateFormatPattern(dateFormat)}
|
||||
placeholder={placeholder}
|
||||
mask={['M', 'M', 'D', 'D', 'Y', 'Y', 'Y', 'Y']}
|
||||
value={toFormattedStr(value)}
|
||||
mask={getMaskArray(dateFormat)}
|
||||
value={toFormattedStr(value, dateFormat)}
|
||||
error={error}
|
||||
label={label}
|
||||
onValueChange={handleInputValueChange}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue