Skip to content

Commit 0307ef8

Browse files
committed
feat: enhance type safety and improve error handling in account-related routes and forms
1 parent 1542d77 commit 0307ef8

File tree

7 files changed

+103
-70
lines changed

7 files changed

+103
-70
lines changed

src/routes/(app)/app/accounts/+page.server.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { error } from '@sveltejs/kit';
22
import prisma from '$lib/prisma';
33

4-
export async function load({ locals, url, params }) {
4+
export async function load({ locals, url }) {
55
const org = locals.org;
66

77
const page = parseInt(url.searchParams.get('page') || '1');
@@ -13,15 +13,15 @@ export async function load({ locals, url, params }) {
1313

1414
try {
1515
// Build the where clause for filtering
16+
/** @type {import('@prisma/client').Prisma.AccountWhereInput} */
1617
const where = {organizationId: org.id};
1718

1819
// Add status filter
1920
const status = url.searchParams.get('status');
2021
if (status === 'open') {
21-
where.closedAt = null;
22-
where.active = true;
22+
where.isActive = true;
2323
} else if (status === 'closed') {
24-
where.closedAt = { not: null };
24+
where.isActive = false;
2525
}
2626

2727
// Fetch accounts with pagination

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<script>
22
import { page } from '$app/stores';
33
import { goto } from '$app/navigation';
4-
import { onMount } from 'svelte';
5-
import { Search, Plus, Eye, Edit, Trash2, ExternalLink, Phone, Mail, MapPin, Calendar, Users, TrendingUp, Building2, Globe, DollarSign, ChevronUp, ChevronDown, Filter } from '@lucide/svelte';
4+
import { Search, Plus, Eye, Edit, Phone, MapPin, Calendar, Users, TrendingUp, Building2, Globe, DollarSign, ChevronUp, ChevronDown, Filter } from '@lucide/svelte';
65
76
export let data;
87
@@ -12,11 +11,16 @@
1211
let isLoading = false;
1312
let statusFilter = $page.url.searchParams.get('status') || 'all';
1413
let searchQuery = $page.url.searchParams.get('q') || '';
14+
/** @type {NodeJS.Timeout | undefined} */
1515
let searchTimeout;
1616
17+
/**
18+
* @param {string} value
19+
*/
1720
function debounceSearch(value) {
1821
clearTimeout(searchTimeout);
1922
searchTimeout = setTimeout(() => {
23+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
2024
const params = new URLSearchParams($page.url.searchParams);
2125
if (value.trim()) {
2226
params.set('q', value.trim());
@@ -30,6 +34,7 @@
3034
3135
function updateQueryParams() {
3236
isLoading = true;
37+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
3338
const params = new URLSearchParams($page.url.searchParams);
3439
params.set('sort', sortField);
3540
params.set('order', sortOrder);
@@ -39,14 +44,21 @@
3944
goto(`?${params.toString()}`, { keepFocus: true });
4045
}
4146
47+
/**
48+
* @param {number} newPage
49+
*/
4250
function changePage(newPage) {
4351
if (newPage < 1 || newPage > pagination.totalPages) return;
4452
53+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
4554
const params = new URLSearchParams($page.url.searchParams);
4655
params.set('page', newPage.toString());
4756
goto(`?${params.toString()}`, { keepFocus: true });
4857
}
4958
59+
/**
60+
* @param {string} field
61+
*/
5062
function toggleSort(field) {
5163
if (sortField === field) {
5264
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
@@ -57,6 +69,9 @@
5769
updateQueryParams();
5870
}
5971
72+
/**
73+
* @param {number | null | undefined} amount
74+
*/
6075
function formatCurrency(amount) {
6176
if (!amount) return '-';
6277
return new Intl.NumberFormat('en-US', {
@@ -67,6 +82,9 @@
6782
}).format(amount);
6883
}
6984
85+
/**
86+
* @param {string | Date | null | undefined} date
87+
*/
7088
function formatDate(date) {
7189
if (!date) return '-';
7290
return new Intl.DateTimeFormat('en-US', {
@@ -107,7 +125,7 @@
107125
placeholder="Search accounts..."
108126
class="pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent min-w-[250px]"
109127
bind:value={searchQuery}
110-
oninput={(e) => debounceSearch(e.target.value)}
128+
oninput={(e) => debounceSearch(/** @type {HTMLInputElement} */ (e.target).value)}
111129
/>
112130
</div>
113131

@@ -242,7 +260,7 @@
242260
</td>
243261
</tr>
244262
{:else}
245-
{#each accounts as account}
263+
{#each accounts as account (account.id)}
246264
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors {account.closedAt ? 'opacity-60' : ''}">
247265
<td class="px-6 py-4">
248266
<div class="flex items-center gap-3">

src/routes/(app)/app/accounts/[accountId]/+page.server.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import prisma from '$lib/prisma';
33

44
/** @type {import('./$types').PageServerLoad} */
55
export async function load({ params, url, locals }) {
6-
const user = locals.user;
76
const org = locals.org;
87
try {
98
const accountId = params.accountId;
@@ -121,7 +120,10 @@ export async function load({ params, url, locals }) {
121120
};
122121
} catch (err) {
123122
console.error('Error loading account data:', err);
124-
throw error(err.status || 500, err.message || 'Error loading account data');
123+
const errorMessage = err instanceof Error ? err.message : 'Error loading account data';
124+
const statusCode = err && typeof err === 'object' && 'status' in err ?
125+
(typeof err.status === 'number' ? err.status : 500) : 500;
126+
throw error(statusCode, errorMessage);
125127
}
126128
}
127129

@@ -207,7 +209,7 @@ export const actions = {
207209
}
208210
},
209211

210-
reopenAccount: async ({ locals, params }) => {
212+
reopenAccount: async ({ request, locals, params }) => {
211213
try {
212214
const user = locals.user;
213215
const org = locals.org;
@@ -257,7 +259,7 @@ export const actions = {
257259
};
258260

259261
// Update the account to mark it as reopened
260-
const updatedAccount = await prisma.account.update({
262+
await prisma.account.update({
261263
where: { id: accountId },
262264
data: {
263265
closedAt: null,
@@ -289,7 +291,6 @@ export const actions = {
289291
},
290292

291293
comment: async ({ request, locals, params }) => {
292-
const user = locals.user;
293294
const org = locals.org;
294295
// Fallback: fetch account to get organizationId
295296
const account = await prisma.account.findUnique({

src/routes/(app)/app/accounts/[accountId]/+page.svelte

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
<script>
2-
import { page } from '$app/stores';
3-
import { goto } from '$app/navigation';
4-
import { onMount } from 'svelte';
5-
import { invalidateAll } from '$app/navigation';
62
import {
73
ArrowLeft,
84
Edit,
95
Lock,
106
Unlock,
11-
Trash2,
127
Users,
138
Target,
149
DollarSign,
@@ -18,9 +13,7 @@
1813
Phone,
1914
Mail,
2015
Globe,
21-
Building,
2216
MapPin,
23-
Calendar,
2417
MessageSquare,
2518
CheckSquare,
2619
FolderOpen,
@@ -31,16 +24,14 @@
3124
export let data;
3225
/** @type {any} */
3326
export let form;
34-
let users = Array.isArray(data.users) ? data.users : [];
3527
36-
const { account, contacts, opportunities = [], quotes, tasks, cases } = data;
28+
const { account, contacts, opportunities = [], tasks, cases } = data;
3729
let comments = data.comments;
3830
3931
// Form state
4032
let showCloseModal = false;
4133
let closureReason = '';
4234
let closeError = '';
43-
let isClosing = false;
4435
4536
// Comment functionality
4637
let newComment = '';
@@ -74,14 +65,17 @@
7465
const data = await res.json().catch(() => ({}));
7566
commentError = data?.error || data?.message || 'Failed to add comment.';
7667
}
77-
} catch (err) {
68+
} catch {
7869
commentError = 'Failed to add comment.';
7970
} finally {
8071
isSubmittingComment = false;
8172
}
8273
}
8374
84-
// Format date string
75+
/**
76+
* Format date string
77+
* @param {string | Date | null | undefined} dateStr
78+
*/
8579
function formatDate(dateStr) {
8680
if (!dateStr) return 'N/A';
8781
return new Date(dateStr).toLocaleDateString('en-US', {
@@ -91,7 +85,10 @@
9185
});
9286
}
9387
94-
// Format currency
88+
/**
89+
* Format currency
90+
* @param {number | null | undefined} value
91+
*/
9592
function formatCurrency(value) {
9693
if (!value) return '$0';
9794
return new Intl.NumberFormat('en-US', {
@@ -101,7 +98,10 @@
10198
}).format(value);
10299
}
103100
104-
// Badge color functions
101+
/**
102+
* Badge color functions
103+
* @param {string | null | undefined} stage
104+
*/
105105
function getStageBadgeColor(stage) {
106106
switch (stage?.toLowerCase()) {
107107
case 'prospecting': return 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400';
@@ -114,6 +114,9 @@
114114
}
115115
}
116116
117+
/**
118+
* @param {string | null | undefined} status
119+
*/
117120
function getCaseStatusBadgeColor(status) {
118121
switch (status?.toLowerCase()) {
119122
case 'open': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400';
@@ -123,19 +126,6 @@
123126
}
124127
}
125128
126-
function getQuoteStatusBadgeColor(status) {
127-
switch (status?.toLowerCase()) {
128-
case 'draft': return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300';
129-
case 'needs_review': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400';
130-
case 'in_review': return 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400';
131-
case 'approved': return 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400';
132-
case 'rejected': return 'bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400';
133-
case 'presented': return 'bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-400';
134-
case 'accepted': return 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900/20 dark:text-indigo-400';
135-
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300';
136-
}
137-
}
138-
139129
// Handle form submission errors
140130
$: {
141131
if (form?.success === false) {
@@ -431,7 +421,7 @@
431421
</tr>
432422
</thead>
433423
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
434-
{#each contacts as contact}
424+
{#each contacts as contact (contact.id)}
435425
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
436426
<td class="py-4 font-medium text-gray-900 dark:text-white">
437427
<a href="/app/contacts/{contact.id}" class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline">
@@ -501,7 +491,7 @@
501491
</tr>
502492
</thead>
503493
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
504-
{#each opportunities as opportunity}
494+
{#each opportunities as opportunity (opportunity.id)}
505495
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
506496
<td class="py-4 font-medium text-gray-900 dark:text-white">
507497
<a href="/app/opportunities/{opportunity.id}" class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline">
@@ -554,7 +544,7 @@
554544
</tr>
555545
</thead>
556546
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
557-
{#each tasks as task}
547+
{#each tasks as task (task.id)}
558548
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
559549
<td class="py-4 font-medium text-gray-900 dark:text-white">
560550
<a href="/app/tasks/{task.id}" class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline">
@@ -619,7 +609,7 @@
619609
</tr>
620610
</thead>
621611
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
622-
{#each cases as caseItem}
612+
{#each cases as caseItem (caseItem.id)}
623613
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
624614
<td class="py-4 font-medium text-gray-900 dark:text-white">
625615
<a href="/app/cases/{caseItem.id}" class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline">
@@ -688,7 +678,7 @@
688678
</div>
689679
{:else}
690680
<div class="space-y-4">
691-
{#each comments as comment}
681+
{#each comments as comment (comment.id)}
692682
<div class="bg-white dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 p-4">
693683
<div class="flex justify-between items-start mb-2">
694684
<div class="flex items-center space-x-2">
@@ -742,7 +732,7 @@
742732
<div>
743733
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Pipeline Value</p>
744734
<p class="text-2xl font-bold text-gray-900 dark:text-white">
745-
{formatCurrency(opportunities.reduce((sum, opp) => sum + (opp.amount || 0), 0))}
735+
{formatCurrency(opportunities.reduce(/** @param {number} sum @param {any} opp */ (sum, opp) => sum + (opp.amount || 0), 0))}
746736
</p>
747737
</div>
748738
<DollarSign class="w-8 h-8 text-yellow-500" />
@@ -752,7 +742,7 @@
752742
<div>
753743
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Open Cases</p>
754744
<p class="text-2xl font-bold text-gray-900 dark:text-white">
755-
{cases.filter(c => c.status !== 'CLOSED').length}
745+
{cases.filter(/** @param {any} c */ (c) => c.status !== 'CLOSED').length}
756746
</p>
757747
</div>
758748
<AlertTriangle class="w-8 h-8 text-red-500" />

src/routes/(app)/app/accounts/[accountId]/edit/+page.server.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ export const actions = {
1515
default: async ({ request, params, locals }) => {
1616
const org = locals.org;
1717
const form = await request.formData();
18-
const name = form.get('name');
19-
const industry = form.get('industry');
20-
const type = form.get('type');
21-
const website = form.get('website');
22-
const phone = form.get('phone');
18+
const name = form.get('name')?.toString();
19+
const industry = form.get('industry')?.toString() || null;
20+
const type = form.get('type')?.toString() || null;
21+
const website = form.get('website')?.toString() || null;
22+
const phone = form.get('phone')?.toString() || null;
2323

2424
if (!name) {
2525
return fail(400, { name, missing: true });

src/routes/(app)/app/accounts/new/+page.server.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { env } from '$env/dynamic/private';
2-
import { redirect } from '@sveltejs/kit';
3-
import prisma from '$lib/prisma';
41
import { fail } from '@sveltejs/kit';
2+
import prisma from '$lib/prisma';
53
import { validatePhoneNumber, formatPhoneForStorage } from '$lib/utils/phone.js';
64
import {
75
industries,
@@ -12,9 +10,7 @@ import {
1210
} from '$lib/data/index.js';
1311

1412
/** @type {import('./$types').PageServerLoad} */
15-
export async function load({ locals }) {
16-
const user = locals.user;
17-
const org = locals.org;
13+
export async function load() {
1814

1915
// Get data for dropdowns
2016
return {

0 commit comments

Comments
 (0)