Files
rxminder/components/modals/EditReminderModal.tsx
2025-09-23 10:53:12 -07:00

258 lines
9.5 KiB
TypeScript

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 EditReminderModalProps {
isOpen: boolean;
onClose: () => void;
reminder: CustomReminder | null;
onUpdate: (reminder: CustomReminder) => Promise<void>;
}
const EditReminderModal: React.FC<EditReminderModalProps> = ({
isOpen,
onClose,
reminder,
onUpdate,
}) => {
const [title, setTitle] = useState('');
const [icon, setIcon] = useState('bell');
const [frequencyMinutes, setFrequencyMinutes] = useState(60);
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 = () =>
validateReminderInputs({
frequencyMinutes,
startTime,
endTime,
});
useEffect(() => {
if (isOpen && reminder) {
setTitle(reminder.title);
setIcon(reminder.icon);
setFrequencyMinutes(reminder.frequencyMinutes);
setStartTime(reminder.startTime);
setEndTime(reminder.endTime);
setIsSaving(false);
setErrors({});
setTimeout(() => titleInputRef.current?.focus(), 100);
}
}, [isOpen, reminder]);
useEffect(() => {
if (!isOpen) return;
setErrors(validate());
}, [isOpen, frequencyMinutes, startTime, endTime]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!title || !reminder || isSaving) return;
const validation = validate();
setErrors(validation);
if (Object.keys(validation).length > 0) {
return;
}
setIsSaving(true);
try {
await onUpdate({
...reminder,
title,
icon,
frequencyMinutes,
startTime,
endTime,
});
} catch (error) {
console.error('Failed to update reminder', error);
alert('There was an error updating your reminder. Please try again.');
} finally {
setIsSaving(false);
}
};
if (!isOpen) return null;
return (
<div
className='fixed inset-0 bg-black bg-opacity-50 dark:bg-opacity-70 z-50 flex justify-center items-center p-4'
role='dialog'
aria-modal='true'
aria-labelledby='edit-rem-title'
>
<div className='bg-white dark:bg-slate-800 rounded-lg shadow-xl w-full max-w-md'>
<div className='p-6 border-b border-slate-200 dark:border-slate-700'>
<h3
id='edit-rem-title'
className='text-xl font-semibold text-slate-800 dark:text-slate-100'
>
Edit Reminder
</h3>
</div>
<form onSubmit={handleSubmit}>
<div className='p-6 space-y-4 max-h-[70vh] overflow-y-auto'>
<div>
<label
htmlFor='rem-edit-title'
className='block text-sm font-medium text-slate-700 dark:text-slate-300'
>
Title
</label>
<input
type='text'
id='rem-edit-title'
value={title}
onChange={e => setTitle(e.target.value)}
required
ref={titleInputRef}
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 dark:placeholder-slate-400 dark:text-white'
/>
</div>
<div>
<label className='block text-sm font-medium text-slate-700 dark:text-slate-300'>
Icon
</label>
<div className='mt-2 flex flex-wrap gap-2'>
{Object.entries(reminderIcons).map(([key, IconComponent]) => (
<button
key={key}
type='button'
onClick={() => setIcon(key)}
className={`p-2 rounded-full transition-colors ${icon === key ? 'bg-indigo-600 text-white ring-2 ring-offset-2 ring-indigo-500 ring-offset-white dark:ring-offset-slate-800' : 'bg-slate-100 text-slate-600 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600'}`}
aria-label={`Select ${key} icon`}
>
<IconComponent className='w-6 h-6' />
</button>
))}
</div>
</div>
<div>
<label
htmlFor='rem-edit-frequency'
className='block text-sm font-medium text-slate-700 dark:text-slate-300'
>
Remind me every (minutes)
</label>
<input
type='number'
id='rem-edit-frequency'
value={frequencyMinutes}
onChange={e => {
const value = parseInt(e.target.value, 10);
setFrequencyMinutes(Number.isNaN(value) ? 0 : value);
}}
min={MIN_REMINDER_FREQUENCY_MINUTES}
max={MAX_REMINDER_FREQUENCY_MINUTES}
aria-invalid={Boolean(errors.frequency)}
aria-describedby='rem-edit-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-edit-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'>
<div>
<label
htmlFor='rem-edit-startTime'
className='block text-sm font-medium text-slate-700 dark:text-slate-300'
>
From
</label>
<input
type='time'
id='rem-edit-startTime'
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'
/>
</div>
<div>
<label
htmlFor='rem-edit-endTime'
className='block text-sm font-medium text-slate-700 dark:text-slate-300'
>
Until
</label>
<input
type='time'
id='rem-edit-endTime'
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'
/>
</div>
</div>
{errors.timeRange && (
<p
id='rem-edit-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
type='button'
onClick={onClose}
disabled={isSaving}
className='px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md shadow-sm hover:bg-slate-50 disabled:opacity-50 dark:bg-slate-700 dark:text-slate-200 dark:border-slate-600 dark:hover:bg-slate-600'
>
Cancel
</button>
<button
type='submit'
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 ? 'Saving...' : 'Save Changes'}
</button>
</div>
</form>
</div>
</div>
);
};
export default EditReminderModal;