fix(trips): keep the day-count field empty when cleared and validate it (#1204) (#1207)

This commit is contained in:
Maurice
2026-06-16 16:20:17 +02:00
committed by GitHub
parent 25324108cb
commit a1ad512064
22 changed files with 56 additions and 3 deletions
@@ -288,4 +288,26 @@ describe('TripFormModal', () => {
await user.click(submitBtn.closest('button')!); await user.click(submitBtn.closest('button')!);
await waitFor(() => expect(screen.getByText('Saving...')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('Saving...')).toBeInTheDocument());
}); });
it('FE-COMP-TRIPFORM-029: clearing the day count leaves the field empty (no snap to 1)', () => {
render(<TripFormModal {...defaultProps} trip={null} />);
const dayInput = document.querySelector('input[max="365"]') as HTMLInputElement;
expect(dayInput).toBeInTheDocument();
expect(dayInput.value).toBe('7');
fireEvent.change(dayInput, { target: { value: '' } });
expect(dayInput.value).toBe('');
});
it('FE-COMP-TRIPFORM-030: empty day count blocks submit with an error', async () => {
const user = userEvent.setup();
const onSave = vi.fn();
render(<TripFormModal {...defaultProps} trip={null} onSave={onSave} />);
await user.type(screen.getByPlaceholderText(/Summer in Japan/i), 'No-date Trip');
const dayInput = document.querySelector('input[max="365"]') as HTMLInputElement;
fireEvent.change(dayInput, { target: { value: '' } });
const submitBtn = screen.getAllByText('Create New Trip').find(el => el.closest('button'))!;
await user.click(submitBtn.closest('button')!);
await screen.findByText('Number of days is required');
expect(onSave).not.toHaveBeenCalled();
});
}); });
+14 -3
View File
@@ -40,7 +40,7 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
start_date: '', start_date: '',
end_date: '', end_date: '',
reminder_days: 0 as number, reminder_days: 0 as number,
day_count: 7, day_count: 7 as number | '',
}) })
const [customReminder, setCustomReminder] = useState(false) const [customReminder, setCustomReminder] = useState(false)
const [error, setError] = useState('') const [error, setError] = useState('')
@@ -100,6 +100,12 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
if (formData.start_date && formData.end_date && new Date(formData.end_date) < new Date(formData.start_date)) { if (formData.start_date && formData.end_date && new Date(formData.end_date) < new Date(formData.start_date)) {
setError(t('dashboard.endDateError')); return setError(t('dashboard.endDateError')); return
} }
if (!formData.start_date && !formData.end_date) {
const dc = Number(formData.day_count)
if (formData.day_count === '' || !Number.isInteger(dc) || dc < 1 || dc > 365) {
setError(t('dashboard.dayCountRequired')); return
}
}
setIsLoading(true) setIsLoading(true)
try { try {
const result = await onSave({ const result = await onSave({
@@ -108,7 +114,7 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
start_date: formData.start_date || null, start_date: formData.start_date || null,
end_date: formData.end_date || null, end_date: formData.end_date || null,
reminder_days: formData.reminder_days, reminder_days: formData.reminder_days,
...(!formData.start_date && !formData.end_date ? { day_count: formData.day_count } : {}), ...(!formData.start_date && !formData.end_date ? { day_count: Number(formData.day_count) } : {}),
}) })
const createdTrip = result ? result.trip : undefined const createdTrip = result ? result.trip : undefined
// Add selected members for newly created trips // Add selected members for newly created trips
@@ -320,7 +326,12 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
{t('dashboard.dayCount')} {t('dashboard.dayCount')}
</label> </label>
<input type="number" min={1} max={365} value={formData.day_count} <input type="number" min={1} max={365} value={formData.day_count}
onChange={e => update('day_count', Math.max(1, Math.min(365, Number(e.target.value) || 1)))} onChange={e => {
const raw = e.target.value
if (raw === '') { update('day_count', ''); return }
const n = Math.floor(Number(raw))
if (Number.isFinite(n)) update('day_count', Math.min(365, Math.max(1, n)))
}}
className={inputCls} /> className={inputCls} />
<p className="text-xs text-slate-400 mt-1.5">{t('dashboard.dayCountHint')}</p> <p className="text-xs text-slate-400 mt-1.5">{t('dashboard.dayCountHint')}</p>
</div> </div>
+1
View File
@@ -163,5 +163,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'تبديل العملات', 'dashboard.aria.swapCurrencies': 'تبديل العملات',
'dashboard.aria.addTimezone': 'إضافة منطقة زمنية', 'dashboard.aria.addTimezone': 'إضافة منطقة زمنية',
'dashboard.aria.removeTimezone': 'إزالة {city}', 'dashboard.aria.removeTimezone': 'إزالة {city}',
'dashboard.dayCountRequired': 'عدد الأيام مطلوب',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -163,5 +163,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Trocar moedas', 'dashboard.aria.swapCurrencies': 'Trocar moedas',
'dashboard.aria.addTimezone': 'Adicionar fuso horário', 'dashboard.aria.addTimezone': 'Adicionar fuso horário',
'dashboard.aria.removeTimezone': 'Remover {city}', 'dashboard.aria.removeTimezone': 'Remover {city}',
'dashboard.dayCountRequired': 'O número de dias é obrigatório',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -163,5 +163,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Prohodit měny', 'dashboard.aria.swapCurrencies': 'Prohodit měny',
'dashboard.aria.addTimezone': 'Přidat časové pásmo', 'dashboard.aria.addTimezone': 'Přidat časové pásmo',
'dashboard.aria.removeTimezone': 'Odebrat {city}', 'dashboard.aria.removeTimezone': 'Odebrat {city}',
'dashboard.dayCountRequired': 'Počet dní je povinný',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -164,5 +164,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Währungen tauschen', 'dashboard.aria.swapCurrencies': 'Währungen tauschen',
'dashboard.aria.addTimezone': 'Zeitzone hinzufügen', 'dashboard.aria.addTimezone': 'Zeitzone hinzufügen',
'dashboard.aria.removeTimezone': '{city} entfernen', 'dashboard.aria.removeTimezone': '{city} entfernen',
'dashboard.dayCountRequired': 'Anzahl der Tage ist erforderlich',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -163,5 +163,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Swap currencies', 'dashboard.aria.swapCurrencies': 'Swap currencies',
'dashboard.aria.addTimezone': 'Add timezone', 'dashboard.aria.addTimezone': 'Add timezone',
'dashboard.aria.removeTimezone': 'Remove {city}', 'dashboard.aria.removeTimezone': 'Remove {city}',
'dashboard.dayCountRequired': 'Number of days is required',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -164,5 +164,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Intercambiar monedas', 'dashboard.aria.swapCurrencies': 'Intercambiar monedas',
'dashboard.aria.addTimezone': 'Añadir zona horaria', 'dashboard.aria.addTimezone': 'Añadir zona horaria',
'dashboard.aria.removeTimezone': 'Eliminar {city}', 'dashboard.aria.removeTimezone': 'Eliminar {city}',
'dashboard.dayCountRequired': 'El número de días es obligatorio',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -167,5 +167,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Inverser les devises', 'dashboard.aria.swapCurrencies': 'Inverser les devises',
'dashboard.aria.addTimezone': 'Ajouter un fuseau horaire', 'dashboard.aria.addTimezone': 'Ajouter un fuseau horaire',
'dashboard.aria.removeTimezone': 'Supprimer {city}', 'dashboard.aria.removeTimezone': 'Supprimer {city}',
'dashboard.dayCountRequired': 'Le nombre de jours est requis',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -166,5 +166,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Swap currencies', // en-fallback 'dashboard.aria.swapCurrencies': 'Swap currencies', // en-fallback
'dashboard.aria.addTimezone': 'Add timezone', // en-fallback 'dashboard.aria.addTimezone': 'Add timezone', // en-fallback
'dashboard.aria.removeTimezone': 'Remove {city}', // en-fallback 'dashboard.aria.removeTimezone': 'Remove {city}', // en-fallback
'dashboard.dayCountRequired': 'Ο αριθμός ημερών είναι υποχρεωτικός',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -164,5 +164,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Pénznemek cseréje', 'dashboard.aria.swapCurrencies': 'Pénznemek cseréje',
'dashboard.aria.addTimezone': 'Időzóna hozzáadása', 'dashboard.aria.addTimezone': 'Időzóna hozzáadása',
'dashboard.aria.removeTimezone': '{city} eltávolítása', 'dashboard.aria.removeTimezone': '{city} eltávolítása',
'dashboard.dayCountRequired': 'A napok száma kötelező',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -163,5 +163,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Tukar mata uang', 'dashboard.aria.swapCurrencies': 'Tukar mata uang',
'dashboard.aria.addTimezone': 'Tambah zona waktu', 'dashboard.aria.addTimezone': 'Tambah zona waktu',
'dashboard.aria.removeTimezone': 'Hapus {city}', 'dashboard.aria.removeTimezone': 'Hapus {city}',
'dashboard.dayCountRequired': 'Jumlah hari wajib diisi',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -167,5 +167,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Inverti valute', 'dashboard.aria.swapCurrencies': 'Inverti valute',
'dashboard.aria.addTimezone': 'Aggiungi fuso orario', 'dashboard.aria.addTimezone': 'Aggiungi fuso orario',
'dashboard.aria.removeTimezone': 'Rimuovi {city}', 'dashboard.aria.removeTimezone': 'Rimuovi {city}',
'dashboard.dayCountRequired': 'Il numero di giorni è obbligatorio',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -162,5 +162,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': '通貨を入れ替え', 'dashboard.aria.swapCurrencies': '通貨を入れ替え',
'dashboard.aria.addTimezone': 'タイムゾーンを追加', 'dashboard.aria.addTimezone': 'タイムゾーンを追加',
'dashboard.aria.removeTimezone': '{city}を削除', 'dashboard.aria.removeTimezone': '{city}を削除',
'dashboard.dayCountRequired': '日数は必須です',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -162,5 +162,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': '통화 바꾸기', 'dashboard.aria.swapCurrencies': '통화 바꾸기',
'dashboard.aria.addTimezone': '시간대 추가', 'dashboard.aria.addTimezone': '시간대 추가',
'dashboard.aria.removeTimezone': '{city} 제거', 'dashboard.aria.removeTimezone': '{city} 제거',
'dashboard.dayCountRequired': '일수는 필수입니다',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -163,5 +163,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': "Valuta's omwisselen", 'dashboard.aria.swapCurrencies': "Valuta's omwisselen",
'dashboard.aria.addTimezone': 'Tijdzone toevoegen', 'dashboard.aria.addTimezone': 'Tijdzone toevoegen',
'dashboard.aria.removeTimezone': '{city} verwijderen', 'dashboard.aria.removeTimezone': '{city} verwijderen',
'dashboard.dayCountRequired': 'Aantal dagen is verplicht',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -163,5 +163,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Zamień waluty', 'dashboard.aria.swapCurrencies': 'Zamień waluty',
'dashboard.aria.addTimezone': 'Dodaj strefę czasową', 'dashboard.aria.addTimezone': 'Dodaj strefę czasową',
'dashboard.aria.removeTimezone': 'Usuń {city}', 'dashboard.aria.removeTimezone': 'Usuń {city}',
'dashboard.dayCountRequired': 'Liczba dni jest wymagana',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -163,5 +163,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Поменять валюты', 'dashboard.aria.swapCurrencies': 'Поменять валюты',
'dashboard.aria.addTimezone': 'Добавить часовой пояс', 'dashboard.aria.addTimezone': 'Добавить часовой пояс',
'dashboard.aria.removeTimezone': 'Удалить {city}', 'dashboard.aria.removeTimezone': 'Удалить {city}',
'dashboard.dayCountRequired': 'Количество дней обязательно',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -162,5 +162,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Para birimlerini değiştir', 'dashboard.aria.swapCurrencies': 'Para birimlerini değiştir',
'dashboard.aria.addTimezone': 'Saat dilimi ekle', 'dashboard.aria.addTimezone': 'Saat dilimi ekle',
'dashboard.aria.removeTimezone': '{city} kaldır', 'dashboard.aria.removeTimezone': '{city} kaldır',
'dashboard.dayCountRequired': 'Gün sayısı gereklidir',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -164,5 +164,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': 'Поміняти валюти', 'dashboard.aria.swapCurrencies': 'Поміняти валюти',
'dashboard.aria.addTimezone': 'Додати часовий пояс', 'dashboard.aria.addTimezone': 'Додати часовий пояс',
'dashboard.aria.removeTimezone': 'Вилучити {city}', 'dashboard.aria.removeTimezone': 'Вилучити {city}',
'dashboard.dayCountRequired': 'Вкажіть кількість днів',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -161,5 +161,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': '交換貨幣', 'dashboard.aria.swapCurrencies': '交換貨幣',
'dashboard.aria.addTimezone': '新增時區', 'dashboard.aria.addTimezone': '新增時區',
'dashboard.aria.removeTimezone': '移除 {city}', 'dashboard.aria.removeTimezone': '移除 {city}',
'dashboard.dayCountRequired': '天數為必填項',
}; };
export default dashboard; export default dashboard;
+1
View File
@@ -161,5 +161,6 @@ const dashboard: TranslationStrings = {
'dashboard.aria.swapCurrencies': '交换货币', 'dashboard.aria.swapCurrencies': '交换货币',
'dashboard.aria.addTimezone': '添加时区', 'dashboard.aria.addTimezone': '添加时区',
'dashboard.aria.removeTimezone': '移除 {city}', 'dashboard.aria.removeTimezone': '移除 {city}',
'dashboard.dayCountRequired': '天数为必填项',
}; };
export default dashboard; export default dashboard;