Skip to content

Commit f9f42fd

Browse files
committed
feat: enhance lead editing with validation for required fields and owner ID, and update form data handling
1 parent bd7b143 commit f9f42fd

File tree

2 files changed

+99
-46
lines changed

2 files changed

+99
-46
lines changed

src/routes/(app)/app/leads/[lead_id]/edit/+page.server.js

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { error, redirect } from '@sveltejs/kit';
2-
import { PrismaClient } from '@prisma/client';
2+
import { PrismaClient, LeadStatus, LeadSource } from '@prisma/client';
33

44
const prisma = new PrismaClient();
55

@@ -20,7 +20,10 @@ export async function load({ params, locals }) {
2020
}
2121

2222
const users = await prisma.userOrganization.findMany({
23-
where: { organizationId: org.id }
23+
where: { organizationId: org.id },
24+
include: {
25+
user: true
26+
}
2427
});
2528

2629
return {
@@ -37,6 +40,49 @@ export const actions = {
3740
const org = locals.org;
3841

3942
const leadEmail = formData.get('email');
43+
const ownerId = formData.get('ownerId');
44+
const firstName = formData.get('firstName');
45+
const lastName = formData.get('lastName');
46+
47+
// Validate required fields
48+
if (!firstName || typeof firstName !== 'string' || firstName.trim() === '') {
49+
return {
50+
success: false,
51+
error: 'First name is required.'
52+
};
53+
}
54+
55+
if (!lastName || typeof lastName !== 'string' || lastName.trim() === '') {
56+
return {
57+
success: false,
58+
error: 'Last name is required.'
59+
};
60+
}
61+
62+
if (!ownerId || typeof ownerId !== 'string') {
63+
return {
64+
success: false,
65+
error: 'Owner ID is required.'
66+
};
67+
}
68+
69+
// Validate owner ID - ensure the user belongs to the organization
70+
const ownerValidation = await prisma.userOrganization.findUnique({
71+
where: {
72+
userId_organizationId: {
73+
userId: ownerId,
74+
organizationId: org.id,
75+
},
76+
},
77+
select: { id: true }
78+
});
79+
80+
if (!ownerValidation) {
81+
return {
82+
success: false,
83+
error: 'Invalid owner selected. User is not part of this organization.'
84+
};
85+
}
4086

4187
// Check if leadEmail is a non-empty string before proceeding
4288
if (typeof leadEmail === 'string' && leadEmail.trim() !== '') {
@@ -73,26 +119,49 @@ export const actions = {
73119
}
74120
}
75121

76-
const updatedLead = {
77-
firstName: formData.get('firstName'),
78-
lastName: formData.get('lastName'),
79-
email: formData.get('email'),
80-
phone: formData.get('phone'),
81-
company: formData.get('company'),
82-
title: formData.get('title'),
83-
status: formData.get('status'),
84-
leadSource: formData.get('leadSource') || null,
85-
industry: formData.get('industry') || null,
86-
rating: formData.get('rating') || null,
87-
description: formData.get('description') || null,
88-
ownerId: formData.get('ownerId'),
89-
organizationId: org.id // Always set from session
90-
};
122+
// Get and validate form data
123+
const statusValue = formData.get('status')?.toString() || 'NEW';
124+
const leadSourceValue = formData.get('leadSource')?.toString();
125+
126+
// Simple string validation - Prisma will validate the enum at runtime
127+
const validStatuses = ['NEW', 'PENDING', 'CONTACTED', 'QUALIFIED', 'UNQUALIFIED', 'CONVERTED'];
128+
const validSources = ['WEB', 'PHONE_INQUIRY', 'PARTNER_REFERRAL', 'COLD_CALL', 'TRADE_SHOW', 'EMPLOYEE_REFERRAL', 'ADVERTISEMENT', 'OTHER'];
91129

130+
if (!validStatuses.includes(statusValue)) {
131+
return {
132+
success: false,
133+
error: 'Invalid lead status provided.'
134+
};
135+
}
136+
137+
if (leadSourceValue && !validSources.includes(leadSourceValue)) {
138+
return {
139+
success: false,
140+
error: 'Invalid lead source provided.'
141+
};
142+
}
143+
92144
try {
145+
// Use the correct Prisma update method with proper typing
93146
await prisma.lead.update({
94147
where: { id: lead_id },
95-
data: updatedLead
148+
data: {
149+
firstName: firstName.trim(),
150+
lastName: lastName.trim(),
151+
email: formData.get('email')?.toString() || null,
152+
phone: formData.get('phone')?.toString() || null,
153+
company: formData.get('company')?.toString() || null,
154+
title: formData.get('title')?.toString() || null,
155+
industry: formData.get('industry')?.toString() || null,
156+
rating: formData.get('rating')?.toString() || null,
157+
description: formData.get('description')?.toString() || null,
158+
ownerId: ownerId,
159+
organizationId: org.id,
160+
// @ts-ignore - Bypassing TypeScript enum checking for validated enum values
161+
status: statusValue,
162+
// @ts-ignore - Bypassing TypeScript enum checking for validated enum values
163+
leadSource: leadSourceValue || null
164+
}
96165
});
97166

98167
return { success: true };

src/routes/(app)/app/leads/[lead_id]/edit/+page.svelte

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
<script>
1+
<script lang="ts">
22
import { enhance } from '$app/forms';
33
import { goto } from '$app/navigation';
44
import { fly } from 'svelte/transition';
5-
import { ArrowLeft, Save, X, User, Building, Mail, Phone, Calendar, Star, Target, DollarSign, AlertCircle } from '@lucide/svelte';
5+
import { ArrowLeft, Save, X, User, Building, Mail, Phone, Calendar, Star, Target, AlertCircle } from '@lucide/svelte';
66
77
export let data;
88
@@ -12,28 +12,28 @@
1212
let errorMessage = '';
1313
1414
// Form validation
15-
let errors = {};
15+
let errors: Record<string, string> = {};
1616
17-
function validateForm(formData) {
17+
function validateForm(formData: FormData) {
1818
errors = {};
1919
20-
if (!formData.get('firstName')?.trim()) {
20+
if (!formData.get('firstName')?.toString()?.trim()) {
2121
errors.firstName = 'First name is required';
2222
}
2323
24-
if (!formData.get('lastName')?.trim()) {
24+
if (!formData.get('lastName')?.toString()?.trim()) {
2525
errors.lastName = 'Last name is required';
2626
}
2727
28-
const email = formData.get('email')?.trim();
28+
const email = formData.get('email')?.toString()?.trim();
2929
if (email && !isValidEmail(email)) {
3030
errors.email = 'Please enter a valid email address';
3131
}
3232
3333
return Object.keys(errors).length === 0;
3434
}
3535
36-
function isValidEmail(email) {
36+
function isValidEmail(email: string) {
3737
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
3838
}
3939
@@ -159,7 +159,7 @@
159159
goto(`/app/leads/${lead.id}`);
160160
}, 1500);
161161
} else if (result.data?.error) {
162-
errorMessage = result.data.error;
162+
errorMessage = result.data.error as string;
163163
}
164164
} else {
165165
errorMessage = 'An unexpected error occurred';
@@ -356,35 +356,19 @@
356356
</div>
357357
</div>
358358

359-
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
359+
<div class="mb-6">
360360
<div>
361361
<label for="ownerId" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Lead Owner</label>
362362
<select
363363
id="ownerId"
364364
name="ownerId"
365365
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition-all"
366366
>
367-
{#each users as user}
368-
<option value={user.id} selected={lead.ownerId === user.id}>{user.name}</option>
367+
{#each users as userOrg}
368+
<option value={userOrg.user.id} selected={lead.ownerId === userOrg.user.id}>{userOrg.user.name}</option>
369369
{/each}
370370
</select>
371371
</div>
372-
373-
<div>
374-
<label for="annualRevenue" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
375-
<DollarSign class="w-4 h-4 inline mr-1" />
376-
Annual Revenue (Optional)
377-
</label>
378-
<input
379-
id="annualRevenue"
380-
name="annualRevenue"
381-
type="number"
382-
step="0.01"
383-
value={lead.annualRevenue || ''}
384-
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 transition-all"
385-
placeholder="Enter annual revenue"
386-
/>
387-
</div>
388372
</div>
389373

390374
<div>

0 commit comments

Comments
 (0)