Each form checks orgConfig.applications to verify the type is enabled, validates required fields, inserts into the applications table, and sends email notification via sendApplicationNotification. Adoption form supports optional pet pre-selection via ?pet=slug query param. Surrender form auto-creates a pet record from submitted data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
142 lines
5.5 KiB
TypeScript
142 lines
5.5 KiB
TypeScript
import type { PageServerLoad, Actions } from './$types';
|
|
import { fail, redirect } from '@sveltejs/kit';
|
|
import { db } from '$lib/server/db';
|
|
import { applications } from '$lib/server/schema';
|
|
import { sendApplicationNotification } from '$lib/server/email';
|
|
import { loginLimiter } from '$lib/server/rate-limit';
|
|
|
|
export const load: PageServerLoad = async ({ parent }) => {
|
|
const parentData = await parent();
|
|
|
|
if (!parentData.orgConfig.applications.fosterEnabled) {
|
|
throw redirect(302, '/');
|
|
}
|
|
|
|
return parentData;
|
|
};
|
|
|
|
export const actions: Actions = {
|
|
default: async ({ request, getClientAddress, locals }) => {
|
|
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? getClientAddress();
|
|
if (!loginLimiter.check(`apply-foster:${ip}`)) {
|
|
return fail(429, { error: 'Too many submissions. Please try again later.' });
|
|
}
|
|
|
|
const formData = await request.formData();
|
|
|
|
const firstName = formData.get('firstName')?.toString().trim() ?? '';
|
|
const lastName = formData.get('lastName')?.toString().trim() ?? '';
|
|
const applicantName = `${firstName} ${lastName}`.trim();
|
|
const email = formData.get('email')?.toString().trim() ?? '';
|
|
const phone = formData.get('phone')?.toString().trim() ?? '';
|
|
|
|
const data: Record<string, unknown> = {};
|
|
const fieldNames = [
|
|
'firstName', 'lastName',
|
|
'address1', 'address2', 'city', 'state', 'zip',
|
|
'ownRent', 'landlordInfo',
|
|
'householdMembers', 'currentAnimals', 'otherPropertyPets', 'previousPets',
|
|
'petsVaccinated', 'yardFenced', 'fenceHeight',
|
|
'whereDuringDay', 'whereSleepNight', 'hoursAlone', 'activityLevel',
|
|
'whyFoster', 'howHeard', 'agreeLeash',
|
|
'integratedBefore', 'twoWeekShutdown',
|
|
'felony', 'felonyDetails', 'animalNeglect',
|
|
'reference1Name', 'reference1Phone', 'reference1Relationship',
|
|
'reference2Name', 'reference2Phone', 'reference2Relationship',
|
|
'reference3Name', 'reference3Phone', 'reference3Relationship',
|
|
'vetName', 'vetClinic', 'vetPhone',
|
|
'landlordName', 'landlordPhone',
|
|
'acknowledge', 'signature'
|
|
];
|
|
|
|
for (const name of fieldNames) {
|
|
data[name] = formData.get(name)?.toString() ?? '';
|
|
}
|
|
|
|
data.petType = formData.getAll('petType').map((v) => v.toString());
|
|
data.traits = formData.getAll('traits').map((v) => v.toString());
|
|
|
|
for (let i = 1; i <= 20; i++) {
|
|
data[`agreement${i}`] = formData.get(`agreement${i}`)?.toString() ?? '';
|
|
}
|
|
|
|
const errors: string[] = [];
|
|
if (!firstName) errors.push('First name is required.');
|
|
if (!lastName) errors.push('Last name is required.');
|
|
if (!email) errors.push('Email is required.');
|
|
if (!phone) errors.push('Phone number is required.');
|
|
if (!data.address1) errors.push('Address is required.');
|
|
if (!data.city) errors.push('City is required.');
|
|
if (!data.state) errors.push('State is required.');
|
|
if (!data.zip) errors.push('ZIP code is required.');
|
|
if (!data.ownRent) errors.push('Own/rent is required.');
|
|
if (!data.householdMembers) errors.push('Household members is required.');
|
|
if (!data.currentAnimals) errors.push('Current animals is required.');
|
|
if (!data.previousPets) errors.push('Previous pets is required.');
|
|
if (!data.petsVaccinated) errors.push('Vaccination status is required.');
|
|
if (!data.yardFenced) errors.push('Yard fencing status is required.');
|
|
if (!data.whereDuringDay) errors.push('Where animal stays during the day is required.');
|
|
if (!data.whereSleepNight) errors.push('Where animal sleeps at night is required.');
|
|
if (!data.hoursAlone) errors.push('Hours alone is required.');
|
|
if (!data.activityLevel) errors.push('Activity level is required.');
|
|
if (!data.whyFoster) errors.push('Why you want to foster is required.');
|
|
if (!data.howHeard) errors.push('How you heard about us is required.');
|
|
if (!data.agreeLeash) errors.push('Leash agreement is required.');
|
|
if (!data.integratedBefore) errors.push('Integration experience is required.');
|
|
if (!data.twoWeekShutdown) errors.push('2-week shutdown response is required.');
|
|
if (!data.felony) errors.push('Felony question is required.');
|
|
if (!data.animalNeglect) errors.push('Animal neglect question is required.');
|
|
if (!data.reference1Name || !data.reference1Phone) errors.push('Reference 1 is required.');
|
|
if (!data.reference2Name || !data.reference2Phone) errors.push('Reference 2 is required.');
|
|
if (!data.reference3Name || !data.reference3Phone) errors.push('Reference 3 is required.');
|
|
if (!data.vetName || !data.vetClinic || !data.vetPhone) errors.push('Veterinarian information is required.');
|
|
if (!data.landlordName || !data.landlordPhone) errors.push('Landlord information is required (put N/A if you own).');
|
|
if (data.acknowledge !== 'yes') errors.push('You must acknowledge the foster agreement.');
|
|
if (!data.signature) errors.push('Signature is required.');
|
|
|
|
for (let i = 1; i <= 20; i++) {
|
|
if (!data[`agreement${i}`]) {
|
|
errors.push(`Agreement item ${i} is required.`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
return fail(400, {
|
|
error: errors.join(' '),
|
|
firstName,
|
|
lastName,
|
|
applicantName,
|
|
email,
|
|
phone,
|
|
...data
|
|
});
|
|
}
|
|
|
|
await db.insert(applications).values({
|
|
type: 'foster',
|
|
petId: null,
|
|
applicantName,
|
|
applicantEmail: email,
|
|
applicantPhone: phone || null,
|
|
formData: data
|
|
});
|
|
|
|
const orgConfig = locals.orgConfig;
|
|
if (orgConfig.orgInfo.email) {
|
|
try {
|
|
sendApplicationNotification(
|
|
orgConfig.orgInfo.name,
|
|
orgConfig.orgInfo.email,
|
|
applicantName,
|
|
'foster'
|
|
);
|
|
} catch {
|
|
// Email failure should not break form submission
|
|
}
|
|
}
|
|
|
|
return { success: true };
|
|
}
|
|
};
|