Normalize RUC formatting to base-checkdigit across booking and admin views.
Accept dashed or digits-only RUC input on the backend, store a canonical dashed form, and display it consistently in booking and admin UIs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,11 +8,17 @@ import {
|
||||
} from '@heroicons/react/24/outline';
|
||||
import type { PaymentMethod, BookingResult } from '../_types';
|
||||
|
||||
export const rucPattern = /^\d{6,10}$/;
|
||||
// Paraguayan RUC: 5-8 digit base + "-" + 1 check digit (DV), e.g. 1234567-9 or 80012345-0
|
||||
export const rucPattern = /^\d{5,8}-\d$/;
|
||||
|
||||
/** Format RUC input: digits only, max 10. */
|
||||
/** Sanitize RUC input: digits and a single user-typed dash, max 10 chars. No dash is auto-inserted. */
|
||||
export function formatRuc(value: string): string {
|
||||
return value.replace(/\D/g, '').slice(0, 10);
|
||||
const cleaned = value.replace(/[^\d-]/g, '');
|
||||
const firstDash = cleaned.indexOf('-');
|
||||
const oneDash = firstDash === -1
|
||||
? cleaned
|
||||
: cleaned.slice(0, firstDash + 1) + cleaned.slice(firstDash + 1).replace(/-/g, '');
|
||||
return oneDash.slice(0, 10);
|
||||
}
|
||||
|
||||
/** Truncate a long invoice string for display. */
|
||||
|
||||
@@ -226,25 +226,10 @@ export function BookingFormStep({
|
||||
onBlur={handleRucBlur}
|
||||
placeholder={t('booking.form.rucPlaceholder')}
|
||||
error={errors.ruc}
|
||||
inputMode="numeric"
|
||||
maxLength={10}
|
||||
aria-label={t('booking.form.ruc')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('booking.form.preferredLanguage')}
|
||||
</label>
|
||||
<select
|
||||
value={formData.preferredLanguage}
|
||||
onChange={(e) => setFormData({ ...formData, preferredLanguage: e.target.value as 'en' | 'es' })}
|
||||
className="w-full px-4 py-3 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="es">Español</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ export interface BookingFormData {
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
preferredLanguage: 'en' | 'es';
|
||||
// Empty until the user explicitly picks a method (no default selection).
|
||||
paymentMethod: PaymentMethod | '';
|
||||
ruc: string;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useParams, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useLanguage } from '@/context/LanguageContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { eventsApi, ticketsApi, paymentOptionsApi, Event, PaymentOptionsConfig } from '@/lib/api';
|
||||
import { formatDateLong, formatTime } from '@/lib/utils';
|
||||
import { formatDateLong, formatTime, formatRucDisplay } from '@/lib/utils';
|
||||
import { isSafeExternalUrl } from '@/lib/safeRedirect';
|
||||
import toast from 'react-hot-toast';
|
||||
import type {
|
||||
@@ -56,7 +56,6 @@ export default function BookingPage() {
|
||||
lastName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
preferredLanguage: locale as 'en' | 'es',
|
||||
paymentMethod: '',
|
||||
ruc: '',
|
||||
});
|
||||
@@ -78,11 +77,10 @@ export default function BookingPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Validate RUC on blur (optional field: 6–10 digits)
|
||||
// Validate RUC on blur (optional field: base + "-" + check digit)
|
||||
const handleRucBlur = () => {
|
||||
if (!formData.ruc) return;
|
||||
const digits = formData.ruc.replace(/\D/g, '');
|
||||
if (digits.length > 0 && !rucPattern.test(digits)) {
|
||||
if (!rucPattern.test(formData.ruc)) {
|
||||
setErrors({ ...errors, ruc: t('booking.form.errors.rucInvalidFormat') });
|
||||
}
|
||||
};
|
||||
@@ -152,8 +150,7 @@ export default function BookingPage() {
|
||||
lastName: prev.lastName || lastName,
|
||||
email: prev.email || user.email || '',
|
||||
phone: prev.phone || user.phone || '',
|
||||
preferredLanguage: (user.languagePreference as 'en' | 'es') || prev.preferredLanguage,
|
||||
ruc: prev.ruc || user.rucNumber || '',
|
||||
ruc: prev.ruc || formatRucDisplay(user.rucNumber),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -197,12 +194,9 @@ export default function BookingPage() {
|
||||
newErrors.phone = t('booking.form.errors.phoneTooShort');
|
||||
}
|
||||
|
||||
// RUC validation (optional field - 6–10 digits if filled)
|
||||
if (formData.ruc.trim()) {
|
||||
const digits = formData.ruc.replace(/\D/g, '');
|
||||
if (!/^\d{6,10}$/.test(digits)) {
|
||||
newErrors.ruc = t('booking.form.errors.rucInvalidFormat');
|
||||
}
|
||||
// RUC validation (optional field - base + "-" + check digit if filled)
|
||||
if (formData.ruc.trim() && !rucPattern.test(formData.ruc.trim())) {
|
||||
newErrors.ruc = t('booking.form.errors.rucInvalidFormat');
|
||||
}
|
||||
|
||||
// Payment method must be explicitly chosen and currently enabled
|
||||
@@ -301,9 +295,9 @@ export default function BookingPage() {
|
||||
lastName: formData.lastName,
|
||||
email: formData.email,
|
||||
phone: formData.phone,
|
||||
preferredLanguage: formData.preferredLanguage,
|
||||
preferredLanguage: locale as 'en' | 'es',
|
||||
paymentMethod: formData.paymentMethod as PaymentMethod,
|
||||
...(formData.ruc.trim() && { ruc: formData.ruc.replace(/\D/g, '') }),
|
||||
...(formData.ruc.trim() && { ruc: formData.ruc.trim() }),
|
||||
// Include attendees array for multi-ticket bookings
|
||||
...(allAttendees.length > 1 && { attendees: allAttendees }),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user