diff --git a/components/modals/AddReminderModal.tsx b/components/modals/AddReminderModal.tsx index fdf378a..e61fdd5 100644 --- a/components/modals/AddReminderModal.tsx +++ b/components/modals/AddReminderModal.tsx @@ -1,6 +1,11 @@ import React, { useState, useEffect, useRef } from 'react'; import { CustomReminder } from '../../types'; import { reminderIcons } from '../icons/Icons'; +import { + MIN_REMINDER_FREQUENCY_MINUTES, + MAX_REMINDER_FREQUENCY_MINUTES, + validateReminderInputs, +} from './reminderValidation'; interface AddReminderModalProps { isOpen: boolean; @@ -19,9 +24,21 @@ const AddReminderModal: React.FC = ({ const [startTime, setStartTime] = useState('09:00'); const [endTime, setEndTime] = useState('17:00'); const [isSaving, setIsSaving] = useState(false); + const [errors, setErrors] = useState<{ + frequency?: string; + timeRange?: string; + }>({}); const titleInputRef = useRef(null); + const validate = () => { + return validateReminderInputs({ + frequencyMinutes, + startTime, + endTime, + }); + }; + useEffect(() => { if (isOpen) { setTitle(''); @@ -30,14 +47,26 @@ const AddReminderModal: React.FC = ({ setStartTime('09:00'); setEndTime('17:00'); setIsSaving(false); + setErrors({}); setTimeout(() => titleInputRef.current?.focus(), 100); } }, [isOpen]); + useEffect(() => { + if (!isOpen) return; + setErrors(validate()); + }, [isOpen, frequencyMinutes, startTime, endTime]); + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!title || isSaving) return; + const validation = validate(); + setErrors(validation); + if (Object.keys(validation).length > 0) { + return; + } + setIsSaving(true); try { await onAdd({ @@ -125,11 +154,30 @@ const AddReminderModal: React.FC = ({ id='rem-frequency' value={frequencyMinutes} onChange={e => - setFrequencyMinutes(parseInt(e.target.value, 10)) + setFrequencyMinutes( + Number.isNaN(parseInt(e.target.value, 10)) + ? 0 + : parseInt(e.target.value, 10) + ) } - min='1' + min={MIN_REMINDER_FREQUENCY_MINUTES} + max={MAX_REMINDER_FREQUENCY_MINUTES} + aria-invalid={Boolean(errors.frequency)} + aria-describedby='rem-frequency-help' className='mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 dark:border-slate-600' /> +

+ {errors.frequency + ? errors.frequency + : `Minimum ${MIN_REMINDER_FREQUENCY_MINUTES} minutes, maximum ${MAX_REMINDER_FREQUENCY_MINUTES} minutes.`} +

@@ -146,6 +194,10 @@ const AddReminderModal: React.FC = ({ value={startTime} onChange={e => setStartTime(e.target.value)} required + aria-invalid={Boolean(errors.timeRange)} + aria-describedby={ + errors.timeRange ? 'rem-time-help' : undefined + } className='mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 dark:border-slate-600' />
@@ -162,10 +214,22 @@ const AddReminderModal: React.FC = ({ value={endTime} onChange={e => setEndTime(e.target.value)} required + aria-invalid={Boolean(errors.timeRange)} + aria-describedby={ + errors.timeRange ? 'rem-time-help' : undefined + } className='mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 dark:border-slate-600' /> + {errors.timeRange && ( +

+ {errors.timeRange} +

+ )}
@@ -148,6 +192,10 @@ const EditReminderModal: React.FC = ({ value={startTime} onChange={e => setStartTime(e.target.value)} required + aria-invalid={Boolean(errors.timeRange)} + aria-describedby={ + errors.timeRange ? 'rem-edit-time-help' : undefined + } className='mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 dark:border-slate-600' />
@@ -164,10 +212,22 @@ const EditReminderModal: React.FC = ({ value={endTime} onChange={e => setEndTime(e.target.value)} required + aria-invalid={Boolean(errors.timeRange)} + aria-describedby={ + errors.timeRange ? 'rem-edit-time-help' : undefined + } className='mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 dark:border-slate-600' /> + {errors.timeRange && ( +

+ {errors.timeRange} +

+ )}