mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-02-05 00:06:36 -05:00
Improve input validation and data sanitization
The changes add consistent string trimming, better null handling, and improved validation logic across the business and client forms.
This commit is contained in:
@@ -123,29 +123,18 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
|
||||
// Update email configuration mutation
|
||||
const updateEmailConfig = api.businesses.updateEmailConfig.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success("Email configuration updated successfully");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Failed to update email configuration: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const createBusiness = api.businesses.create.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success("Business created successfully");
|
||||
router.push("/dashboard/businesses");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message || "Failed to create business");
|
||||
},
|
||||
});
|
||||
|
||||
const updateBusiness = api.businesses.update.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success("Business updated successfully");
|
||||
router.push("/dashboard/businesses");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message || "Failed to update business");
|
||||
},
|
||||
@@ -203,69 +192,87 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
newErrors.name = VALIDATION_MESSAGES.required;
|
||||
}
|
||||
// Nickname validation (optional, max 255 chars)
|
||||
if (formData.nickname && formData.nickname.length > 255) {
|
||||
if (formData.nickname && formData.nickname.trim().length > 255) {
|
||||
newErrors.nickname = "Nickname must be 255 characters or less";
|
||||
}
|
||||
|
||||
// Email validation
|
||||
if (formData.email && !isValidEmail(formData.email)) {
|
||||
if (formData.email.trim() && !isValidEmail(formData.email.trim())) {
|
||||
newErrors.email = VALIDATION_MESSAGES.email;
|
||||
}
|
||||
|
||||
// Phone validation (basic check for US format)
|
||||
if (formData.phone) {
|
||||
if (formData.phone.trim()) {
|
||||
const phoneDigits = formData.phone.replace(/\D/g, "");
|
||||
if (phoneDigits.length > 0 && phoneDigits.length < 10) {
|
||||
newErrors.phone = VALIDATION_MESSAGES.phone;
|
||||
}
|
||||
}
|
||||
|
||||
// Address validation if any address field is filled
|
||||
const hasAddressData =
|
||||
formData.addressLine1 ||
|
||||
formData.city ||
|
||||
formData.state ||
|
||||
formData.postalCode;
|
||||
// Address validation if any address field is filled (excluding country as it has a default)
|
||||
const hasAddressData = !!(
|
||||
formData.addressLine1.trim() ||
|
||||
formData.city.trim() ||
|
||||
formData.state.trim() ||
|
||||
formData.postalCode.trim()
|
||||
);
|
||||
|
||||
if (hasAddressData) {
|
||||
if (!formData.addressLine1)
|
||||
// Also check if country was explicitly changed from default
|
||||
const hasNonDefaultCountry =
|
||||
formData.country.trim() && formData.country.trim() !== "United States";
|
||||
const hasAnyAddressInput = hasAddressData || hasNonDefaultCountry;
|
||||
|
||||
// Only validate address if user has actually entered address data
|
||||
if (hasAnyAddressInput) {
|
||||
if (!formData.addressLine1.trim())
|
||||
newErrors.addressLine1 = VALIDATION_MESSAGES.required;
|
||||
if (!formData.city) newErrors.city = VALIDATION_MESSAGES.required;
|
||||
if (!formData.country) newErrors.country = VALIDATION_MESSAGES.required;
|
||||
if (!formData.city.trim()) newErrors.city = VALIDATION_MESSAGES.required;
|
||||
if (!formData.country.trim())
|
||||
newErrors.country = VALIDATION_MESSAGES.required;
|
||||
|
||||
if (formData.country === "United States") {
|
||||
if (!formData.state) newErrors.state = VALIDATION_MESSAGES.required;
|
||||
if (!formData.postalCode)
|
||||
// Only require US-specific fields if country is United States AND we have actual address data
|
||||
if (formData.country.trim() === "United States" && hasAddressData) {
|
||||
if (!formData.state.trim())
|
||||
newErrors.state = VALIDATION_MESSAGES.required;
|
||||
if (!formData.postalCode.trim())
|
||||
newErrors.postalCode = VALIDATION_MESSAGES.required;
|
||||
}
|
||||
}
|
||||
|
||||
// Email configuration validation
|
||||
// API Key validation
|
||||
if (formData.resendApiKey && !formData.resendApiKey.startsWith("re_")) {
|
||||
if (
|
||||
formData.resendApiKey.trim() &&
|
||||
!formData.resendApiKey.trim().startsWith("re_")
|
||||
) {
|
||||
newErrors.resendApiKey = "Resend API key should start with 're_'";
|
||||
}
|
||||
|
||||
// Domain validation
|
||||
if (formData.resendDomain) {
|
||||
if (formData.resendDomain.trim()) {
|
||||
const domainRegex =
|
||||
/^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.([a-zA-Z]{2,})+$/;
|
||||
if (!domainRegex.test(formData.resendDomain)) {
|
||||
if (!domainRegex.test(formData.resendDomain.trim())) {
|
||||
newErrors.resendDomain =
|
||||
"Please enter a valid domain (e.g., yourdomain.com)";
|
||||
}
|
||||
}
|
||||
|
||||
// If API key is provided, domain must also be provided
|
||||
if (formData.resendApiKey && !formData.resendDomain) {
|
||||
if (formData.resendApiKey.trim() && !formData.resendDomain.trim()) {
|
||||
newErrors.resendDomain = "Domain is required when API key is provided";
|
||||
}
|
||||
|
||||
// If domain is provided, API key must also be provided
|
||||
// If domain is provided, API key must also be provided (unless there's already one on the server)
|
||||
// In edit mode, if domain comes from server and API key field is empty, don't require new API key
|
||||
const userEnteredDomain =
|
||||
formData.resendDomain.trim() !== (emailConfig?.resendDomain ?? "");
|
||||
|
||||
if (
|
||||
formData.resendDomain &&
|
||||
!formData.resendApiKey &&
|
||||
!emailConfig?.hasApiKey
|
||||
formData.resendDomain.trim() &&
|
||||
!formData.resendApiKey.trim() &&
|
||||
!emailConfig?.hasApiKey &&
|
||||
(mode === "create" || userEnteredDomain)
|
||||
) {
|
||||
newErrors.resendApiKey = "API key is required when domain is provided";
|
||||
}
|
||||
@@ -289,7 +296,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
const dataToSubmit = {
|
||||
...formData,
|
||||
name: formData.name.trim(),
|
||||
nickname: formData.nickname.trim(),
|
||||
nickname: formData.nickname.trim() || undefined,
|
||||
website: formData.website ? formatWebsiteUrl(formData.website) : "",
|
||||
};
|
||||
|
||||
@@ -313,20 +320,24 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
|
||||
const newBusiness = await createBusiness.mutateAsync(businessData);
|
||||
|
||||
// Update email configuration separately if any email fields are provided
|
||||
if (
|
||||
newBusiness &&
|
||||
(formData.resendApiKey ||
|
||||
formData.resendDomain ||
|
||||
formData.emailFromName)
|
||||
) {
|
||||
// Update email configuration separately if any email fields have values
|
||||
const newApiKey = formData.resendApiKey.trim();
|
||||
const newDomain = formData.resendDomain.trim();
|
||||
const newFromName = formData.emailFromName.trim();
|
||||
|
||||
const hasEmailData = newApiKey || newDomain || newFromName;
|
||||
|
||||
if (newBusiness && hasEmailData) {
|
||||
await updateEmailConfig.mutateAsync({
|
||||
id: newBusiness.id,
|
||||
resendApiKey: formData.resendApiKey || undefined,
|
||||
resendDomain: formData.resendDomain || undefined,
|
||||
emailFromName: formData.emailFromName || undefined,
|
||||
resendApiKey: newApiKey || undefined,
|
||||
resendDomain: newDomain || undefined,
|
||||
emailFromName: newFromName || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
toast.success("Business created successfully");
|
||||
router.push("/dashboard/businesses");
|
||||
} else {
|
||||
// Update business data (excluding email config fields)
|
||||
const businessData = {
|
||||
@@ -350,19 +361,31 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
...businessData,
|
||||
});
|
||||
|
||||
// Update email configuration separately if any email fields are provided
|
||||
if (
|
||||
formData.resendApiKey ||
|
||||
formData.resendDomain ||
|
||||
formData.emailFromName
|
||||
) {
|
||||
// Only update email configuration if there are actual changes or new values
|
||||
const currentApiKey = emailConfig?.hasApiKey ? "EXISTING" : "";
|
||||
const currentDomain = emailConfig?.resendDomain ?? "";
|
||||
const currentFromName = emailConfig?.emailFromName ?? "";
|
||||
|
||||
const newApiKey = formData.resendApiKey.trim();
|
||||
const newDomain = formData.resendDomain.trim();
|
||||
const newFromName = formData.emailFromName.trim();
|
||||
|
||||
const hasEmailChanges =
|
||||
(newApiKey && newApiKey !== currentApiKey) ||
|
||||
newDomain !== currentDomain ||
|
||||
newFromName !== currentFromName;
|
||||
|
||||
if (hasEmailChanges) {
|
||||
await updateEmailConfig.mutateAsync({
|
||||
id: businessId!,
|
||||
resendApiKey: formData.resendApiKey || undefined,
|
||||
resendDomain: formData.resendDomain || undefined,
|
||||
emailFromName: formData.emailFromName || undefined,
|
||||
resendApiKey: newApiKey || undefined,
|
||||
resendDomain: newDomain || undefined,
|
||||
emailFromName: newFromName || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
toast.success("Business updated successfully");
|
||||
router.push("/dashboard/businesses");
|
||||
}
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -667,7 +690,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
<CardContent className="space-y-6">
|
||||
{/* Current Status */}
|
||||
{mode === "edit" && (
|
||||
<div className="flex items-center justify-between bg-gray-50 p-4">
|
||||
<div className="bg-muted/50 flex items-center justify-between p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">
|
||||
Current Status:
|
||||
@@ -688,7 +711,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
)}
|
||||
</div>
|
||||
{emailConfig?.resendDomain && (
|
||||
<span className="text-sm text-gray-600">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Domain: {emailConfig.resendDomain}
|
||||
</span>
|
||||
)}
|
||||
@@ -742,7 +765,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
}
|
||||
placeholder={
|
||||
mode === "edit" && emailConfig?.hasApiKey
|
||||
? "Enter new API key to update"
|
||||
? "••••••••••••••••••••••••••••••••"
|
||||
: "re_..."
|
||||
}
|
||||
className={
|
||||
|
||||
Reference in New Issue
Block a user