Skip to content

Commit 830c24b

Browse files
committed
feat: enhance type safety with JSDoc annotations and improve form handling in leads and profile components
1 parent 0e3805a commit 830c24b

File tree

4 files changed

+77
-46
lines changed

4 files changed

+77
-46
lines changed

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

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,17 @@
5151
let showConfirmModal = false;
5252
5353
// Function to get the full name of a lead
54+
/**
55+
* @param {any} lead
56+
*/
5457
function getFullName(lead) {
5558
return `${lead.firstName} ${lead.lastName}`.trim();
5659
}
5760
5861
// Function to format date
62+
/**
63+
* @param {string | Date | null | undefined} dateString
64+
*/
5965
function formatDate(dateString) {
6066
if (!dateString) return 'N/A';
6167
return new Date(dateString).toLocaleDateString('en-US', {
@@ -68,6 +74,9 @@
6874
}
6975
7076
// Function to format date (short)
77+
/**
78+
* @param {string | Date | null | undefined} dateString
79+
*/
7180
function formatDateShort(dateString) {
7281
if (!dateString) return 'N/A';
7382
return new Date(dateString).toLocaleDateString('en-US', {
@@ -78,6 +87,9 @@
7887
}
7988
8089
// Function to map lead status to colors
90+
/**
91+
* @param {string} status
92+
*/
8193
function getStatusColor(status) {
8294
switch (status) {
8395
case 'NEW':
@@ -98,12 +110,18 @@
98110
}
99111
100112
// Function to get lead source display name
113+
/**
114+
* @param {string | null | undefined} source
115+
*/
101116
function getLeadSourceDisplay(source) {
102117
if (!source) return 'Unknown';
103-
return source.replace('_', ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase());
118+
return source.replace('_', ' ').toLowerCase().replace(/\b\w/g, (/** @type {string} */ l) => l.toUpperCase());
104119
}
105120
106121
// Function to get initials for avatar
122+
/**
123+
* @param {any} lead
124+
*/
107125
function getInitials(lead) {
108126
const first = lead.firstName?.[0] || '';
109127
const last = lead.lastName?.[0] || '';
@@ -144,12 +162,17 @@
144162
function confirmConversion() {
145163
showConfirmModal = false;
146164
// Submit the form programmatically
147-
document.getElementById('convertForm').requestSubmit();
165+
const form = document.getElementById('convertForm');
166+
if (form) {
167+
// Use dispatchEvent as a cross-browser solution
168+
const event = new Event('submit', { cancelable: true, bubbles: true });
169+
form.dispatchEvent(event);
170+
}
148171
}
149172
150173
const enhanceConvertForm = () => {
151174
isConverting = true;
152-
return async ({ update }) => {
175+
return async (/** @type {{ update: any }} */ { update }) => {
153176
await update({ reset: false });
154177
// Note: If conversion is successful, the server will redirect automatically
155178
// This will only execute if there's an error
@@ -159,7 +182,7 @@
159182
160183
const enhanceCommentForm = () => {
161184
isSubmittingComment = true;
162-
return async ({ update }) => {
185+
return async (/** @type {{ update: any }} */ { update }) => {
163186
await update({ reset: false });
164187
// Reset the loading state after update
165188
isSubmittingComment = false;
@@ -455,19 +478,6 @@
455478
</div>
456479
{/if}
457480
458-
<!-- Annual Revenue -->
459-
{#if lead.annualRevenue}
460-
<div class="space-y-2">
461-
<div class="flex items-center gap-2">
462-
<DollarSign class="w-4 h-4 text-gray-400 dark:text-gray-500" />
463-
<span class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">Annual Revenue</span>
464-
</div>
465-
<p class="text-sm text-gray-900 dark:text-gray-100 font-medium bg-gray-50 dark:bg-gray-700 px-3 py-2 rounded-lg">
466-
${lead.annualRevenue.toLocaleString()}
467-
</p>
468-
</div>
469-
{/if}
470-
471481
<!-- Lead Owner -->
472482
<div class="space-y-2">
473483
<div class="flex items-center gap-2">
@@ -491,19 +501,6 @@
491501
</div>
492502
</div>
493503
494-
<!-- Address -->
495-
{#if lead.address}
496-
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700">
497-
<div class="flex items-center gap-2 mb-3">
498-
<Location class="w-5 h-5 text-gray-400 dark:text-gray-500" />
499-
<span class="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">Address</span>
500-
</div>
501-
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-xl">
502-
<p class="text-sm text-gray-900 dark:text-gray-100 whitespace-pre-line leading-relaxed">{lead.address}</p>
503-
</div>
504-
</div>
505-
{/if}
506-
507504
<!-- Description -->
508505
{#if lead.description}
509506
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700">
@@ -684,14 +681,14 @@
684681
<div class="flex justify-between items-center">
685682
<span class="text-sm text-gray-600 dark:text-gray-300">Days Since Created</span>
686683
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">
687-
{Math.floor((new Date() - new Date(lead.createdAt)) / (1000 * 60 * 60 * 24))}
684+
{Math.floor((new Date().getTime() - new Date(lead.createdAt).getTime()) / (1000 * 60 * 60 * 24))}
688685
</span>
689686
</div>
690687
{#if lead.convertedAt}
691688
<div class="flex justify-between items-center">
692689
<span class="text-sm text-gray-600 dark:text-gray-300">Days to Convert</span>
693690
<span class="text-sm font-semibold text-green-600 dark:text-green-400">
694-
{Math.floor((new Date(lead.convertedAt) - new Date(lead.createdAt)) / (1000 * 60 * 60 * 24))}
691+
{Math.floor((new Date(lead.convertedAt).getTime() - new Date(lead.createdAt).getTime()) / (1000 * 60 * 60 * 24))}
695692
</span>
696693
</div>
697694
{/if}

src/routes/(app)/app/leads/new/+page.svelte

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
3636
/**
3737
* Object holding the form fields.
38+
* @type {Record<string, string>}
3839
*/
3940
let formData = {
4041
lead_title: '',
@@ -73,18 +74,25 @@
7374
7475
/**
7576
* Object to store field errors.
77+
* @type {Record<string, string>}
7678
*/
7779
let errors = {};
7880
7981
/**
8082
* Handles changes to form inputs
83+
* @param {Event} event
8184
*/
8285
function handleChange(event) {
83-
const { name, value } = event.target;
84-
formData[name] = value;
85-
// Clear error when user starts typing
86-
if (errors[name]) {
87-
errors[name] = '';
86+
const target = event.target;
87+
if (!target || !('name' in target && 'value' in target)) return;
88+
const name = target.name;
89+
const value = target.value;
90+
if (typeof name === 'string' && typeof value === 'string') {
91+
formData[name] = value;
92+
// Clear error when user starts typing
93+
if (errors[name]) {
94+
errors[name] = '';
95+
}
8896
}
8997
}
9098
@@ -139,7 +147,7 @@
139147
}
140148
141149
// Validate probability range
142-
if (formData.probability && (formData.probability < 0 || formData.probability > 100)) {
150+
if (formData.probability && (Number(formData.probability) < 0 || Number(formData.probability) > 100)) {
143151
errors.probability = 'Probability must be between 0 and 100';
144152
isValid = false;
145153
}
@@ -190,6 +198,10 @@
190198
errors = {};
191199
}
192200
201+
/**
202+
* @param {string} message
203+
* @param {'success' | 'error'} type
204+
*/
193205
function showNotification(message, type = 'success') {
194206
toastMessage = message;
195207
toastType = type;
@@ -284,7 +296,10 @@
284296
resetForm();
285297
setTimeout(() => goto('/app/leads/open'), 1500);
286298
} else if (result.type === 'failure') {
287-
showNotification(result.data?.error || 'Failed to create lead', 'error');
299+
const errorMessage = result.data && typeof result.data === 'object' && 'error' in result.data
300+
? String(result.data.error)
301+
: 'Failed to create lead';
302+
showNotification(errorMessage, 'error');
288303
}
289304
};
290305
}} class="space-y-6">

src/routes/(app)/app/leads/open/+page.svelte

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,17 @@
7777
];
7878
7979
// Function to get the full name of a lead
80+
/**
81+
* @param {any} lead
82+
*/
8083
function getFullName(lead) {
8184
return `${lead.firstName} ${lead.lastName}`.trim();
8285
}
8386
8487
// Function to map lead status to colors and icons
88+
/**
89+
* @param {string} status
90+
*/
8591
function getStatusConfig(status) {
8692
switch (status) {
8793
case 'NEW':
@@ -102,6 +108,9 @@
102108
}
103109
104110
// Function to get rating config
111+
/**
112+
* @param {string} rating
113+
*/
105114
function getRatingConfig(rating) {
106115
switch (rating) {
107116
case 'Hot':
@@ -116,6 +125,9 @@
116125
}
117126
118127
// Replace fixed date formatting with relative time
128+
/**
129+
* @param {string | Date | null | undefined} dateString
130+
*/
119131
function formatDate(dateString) {
120132
if (!dateString) return '-';
121133
return formatDistanceToNow(new Date(dateString), { addSuffix: true });
@@ -134,8 +146,12 @@
134146
135147
return matchesSearch && matchesStatus && matchesSource && matchesRating;
136148
}).sort((a, b) => {
137-
const aValue = a[sortBy];
138-
const bValue = b[sortBy];
149+
const getFieldValue = (/** @type {any} */ obj, /** @type {string} */ field) => {
150+
return obj[field];
151+
};
152+
153+
const aValue = getFieldValue(a, sortBy);
154+
const bValue = getFieldValue(b, sortBy);
139155
140156
if (sortOrder === 'asc') {
141157
return aValue > bValue ? 1 : -1;
@@ -145,6 +161,9 @@
145161
});
146162
147163
// Function to toggle sort order
164+
/**
165+
* @param {string} field
166+
*/
148167
function toggleSort(field) {
149168
if (sortBy === field) {
150169
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
@@ -320,7 +339,7 @@
320339
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
321340
{#each filteredLeads as lead, i}
322341
{@const statusConfig = getStatusConfig(lead.status)}
323-
{@const ratingConfig = getRatingConfig(lead.rating)}
342+
{@const ratingConfig = getRatingConfig(lead.rating || '')}
324343
<tr
325344
class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150"
326345
in:fly={{y: 20, duration: 300, delay: i * 50}}
@@ -392,7 +411,7 @@
392411
</td>
393412
<td class="px-4 py-4">
394413
<div class="flex items-center gap-2">
395-
{#snippet statusIcon(config)}
414+
{#snippet statusIcon(/** @type {any} */ config)}
396415
{@const StatusIcon = config.icon}
397416
<StatusIcon class="w-4 h-4 {config.color.split(' ')[1]} flex-shrink-0" />
398417
{/snippet}
@@ -440,7 +459,7 @@
440459
<div class="xl:hidden divide-y divide-gray-200 dark:divide-gray-700">
441460
{#each filteredLeads as lead, i}
442461
{@const statusConfig = getStatusConfig(lead.status)}
443-
{@const ratingConfig = getRatingConfig(lead.rating)}
462+
{@const ratingConfig = getRatingConfig(lead.rating || '')}
444463
<div
445464
class="p-6 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150"
446465
in:fly={{y: 20, duration: 300, delay: i * 50}}
@@ -461,7 +480,7 @@
461480
</div>
462481
</div>
463482
<div class="flex items-center gap-2">
464-
{#snippet statusIcon(config)}
483+
{#snippet statusIcon(/** @type {any} */ config)}
465484
{@const StatusIcon = config.icon}
466485
<StatusIcon class="w-4 h-4 {config.color.split(' ')[1]}" />
467486
{/snippet}

src/routes/(app)/app/profile/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@
251251
</button>
252252
<button
253253
type="submit"
254-
disabled={isSubmitting || phoneError}
254+
disabled={isSubmitting || !!phoneError}
255255
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800 transition-colors disabled:opacity-50"
256256
>
257257
{#if isSubmitting}

0 commit comments

Comments
 (0)