feat(reminders): validate frequency and time range
This commit is contained in:
@@ -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<AddReminderModalProps> = ({
|
||||
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<HTMLInputElement>(null);
|
||||
|
||||
const validate = () => {
|
||||
return validateReminderInputs({
|
||||
frequencyMinutes,
|
||||
startTime,
|
||||
endTime,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setTitle('');
|
||||
@@ -30,14 +47,26 @@ const AddReminderModal: React.FC<AddReminderModalProps> = ({
|
||||
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<AddReminderModalProps> = ({
|
||||
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'
|
||||
/>
|
||||
<p
|
||||
id='rem-frequency-help'
|
||||
className={`mt-1 text-sm ${
|
||||
errors.frequency
|
||||
? 'text-red-600 dark:text-red-400'
|
||||
: 'text-slate-500 dark:text-slate-400'
|
||||
}`}
|
||||
>
|
||||
{errors.frequency
|
||||
? errors.frequency
|
||||
: `Minimum ${MIN_REMINDER_FREQUENCY_MINUTES} minutes, maximum ${MAX_REMINDER_FREQUENCY_MINUTES} minutes.`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
@@ -146,6 +194,10 @@ const AddReminderModal: React.FC<AddReminderModalProps> = ({
|
||||
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'
|
||||
/>
|
||||
</div>
|
||||
@@ -162,10 +214,22 @@ const AddReminderModal: React.FC<AddReminderModalProps> = ({
|
||||
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'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{errors.timeRange && (
|
||||
<p
|
||||
id='rem-time-help'
|
||||
className='text-sm text-red-600 dark:text-red-400'
|
||||
>
|
||||
{errors.timeRange}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className='px-6 py-4 bg-slate-50 dark:bg-slate-700/50 flex justify-end space-x-3 rounded-b-lg border-t border-slate-200 dark:border-slate-700'>
|
||||
<button
|
||||
@@ -178,7 +242,9 @@ const AddReminderModal: React.FC<AddReminderModalProps> = ({
|
||||
</button>
|
||||
<button
|
||||
type='submit'
|
||||
disabled={isSaving}
|
||||
disabled={
|
||||
isSaving || Boolean(errors.frequency || errors.timeRange)
|
||||
}
|
||||
className='px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed flex items-center dark:focus:ring-offset-slate-800'
|
||||
>
|
||||
{isSaving ? 'Adding...' : 'Add Reminder'}
|
||||
|
||||
Reference in New Issue
Block a user