Skip to content
This repository was archived by the owner on Nov 29, 2025. It is now read-only.

Commit a30250b

Browse files
authored
Merge pull request #16 from MicroPyramid/dev
Dev
2 parents a130e4d + 165c949 commit a30250b

File tree

43 files changed

+1583
-1088
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1583
-1088
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@
4646
"dependencies": {
4747
"@fortawesome/free-brands-svg-icons": "^6.7.2",
4848
"@fortawesome/free-solid-svg-icons": "^6.7.2",
49+
"@lucide/svelte": "^0.511.0",
4950
"@prisma/client": "6.5.0",
5051
"axios": "^1.8.4",
5152
"date-fns": "^4.1.0",
5253
"flowbite-svelte-blocks": "^1.1.4",
5354
"flowbite-svelte-icons": "^2.1.1",
5455
"marked": "^15.0.8",
5556
"svelte-fa": "^4.0.3",
56-
"uuid": "^11.1.0"
57+
"uuid": "^11.1.0",
58+
"zod": "^3.25.28"
5759
}
5860
}

pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- AlterTable
2+
ALTER TABLE "Comment" ADD COLUMN "taskId" TEXT;
3+
4+
-- AddForeignKey
5+
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "Task"("id") ON DELETE SET NULL ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ model User {
3030
contacts Contact[]
3131
leads Lead[]
3232
opportunities Opportunity[]
33-
tasks Task[]
34-
events Event[]
33+
tasks Task[] // Tasks created by user
34+
events Event[] // Events created by user
3535
ownedTasks Task[] @relation("TaskOwner")
3636
ownedEvents Event[] @relation("EventOwner")
3737
cases Case[]
@@ -281,6 +281,7 @@ model Task {
281281
caseId String?
282282
organization Organization @relation(fields: [organizationId], references: [id])
283283
organizationId String
284+
comments Comment[] @relation("TaskComments")
284285
}
285286

286287
model Event {
@@ -404,6 +405,8 @@ model Comment {
404405
accountId String?
405406
contact Contact? @relation(fields: [contactId], references: [id])
406407
contactId String?
408+
task Task? @relation("TaskComments", fields: [taskId], references: [id])
409+
taskId String?
407410
}
408411

409412
model Quote {

src/routes/(app)/Sidebar.svelte

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { afterNavigate } from '$app/navigation';
33
import { page } from '$app/stores';
44
import Fa from 'svelte-fa';
5-
import { faPieChart } from '@fortawesome/free-solid-svg-icons';
5+
import { faPieChart, faQuestion } from '@fortawesome/free-solid-svg-icons';
66
import {
77
Sidebar,
88
SidebarDropdownItem,
@@ -134,6 +134,20 @@
134134
class={`${mainSidebarUrl === '/app/invoices/new' ? 'bg-gray-100 font-semibold dark:bg-gray-700' : ''}`}
135135
/>
136136
</SidebarDropdownWrapper> -->
137+
138+
<SidebarItem
139+
label="Support"
140+
href="/app/support"
141+
class={`${mainSidebarUrl === '/app/support' ? 'flex p-2 items-center rounded-lg bg-gray-100 font-semibold dark:bg-gray-700' : ''}`}
142+
spanClass="ml-3"
143+
>
144+
<svelte:fragment slot="icon">
145+
<Fa
146+
icon={faQuestion}
147+
class={`${iconClass} ${mainSidebarUrl === '/app/support' ? 'text-gray-900 dark:text-white' : ''}`}
148+
/>
149+
</svelte:fragment>
150+
</SidebarItem>
137151
</SidebarGroup>
138152
139153
</nav>

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

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

4-
export async function load({ locals, url }) {
5-
4+
export async function load({ locals, url, params }) {
5+
const org = locals.org;
6+
67
const page = parseInt(url.searchParams.get('page') || '1');
78
const limit = parseInt(url.searchParams.get('limit') || '10');
89
const sort = url.searchParams.get('sort') || 'name';
@@ -12,7 +13,7 @@ export async function load({ locals, url }) {
1213

1314
try {
1415
// Build the where clause for filtering
15-
const where = {};
16+
const where = {organizationId: org.id};
1617

1718
// Add status filter
1819
const status = url.searchParams.get('status');

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

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import { error, fail } from '@sveltejs/kit';
22
import prisma from '$lib/prisma';
33

44
/** @type {import('./$types').PageServerLoad} */
5-
export async function load({ params, url }) {
5+
export async function load({ params, url, locals }) {
6+
const user = locals.user;
7+
const org = locals.org;
68
try {
79
const accountId = params.accountId;
810

911
// Fetch account details
1012
const account = await prisma.account.findUnique({
1113
where: {
12-
id: accountId
14+
id: accountId,
15+
organizationId: org.id
1316
}
1417
});
1518

@@ -127,10 +130,7 @@ export const actions = {
127130
closeAccount: async ({ params, request, locals }) => {
128131
try {
129132
const user = locals.user;
130-
131-
if (!user) {
132-
return fail(401, { success: false, message: 'Unauthorized' });
133-
}
133+
const org = locals.org;
134134

135135
const { accountId } = params;
136136
const formData = await request.formData();
@@ -142,7 +142,7 @@ export const actions = {
142142

143143
// Fetch the account to verify it exists
144144
const account = await prisma.account.findUnique({
145-
where: { id: accountId },
145+
where: { id: accountId, organizationId: org.id },
146146
select: {
147147
id: true,
148148
closedAt: true,
@@ -169,8 +169,7 @@ export const actions = {
169169

170170
const hasPermission =
171171
user.id === account.ownerId ||
172-
userOrg?.role === 'ADMIN' ||
173-
userOrg?.role === 'SALES_MANAGER';
172+
userOrg?.role === 'ADMIN';
174173

175174
if (!hasPermission) {
176175
return fail(403, { success: false, message: 'Permission denied. Only account owners, sales managers, or admins can close accounts.' });
@@ -210,16 +209,13 @@ export const actions = {
210209
reopenAccount: async ({ params, request, locals }) => {
211210
try {
212211
const user = locals.user;
213-
214-
if (!user) {
215-
return fail(401, { success: false, message: 'Unauthorized' });
216-
}
212+
const org = locals.org;
217213

218214
const { accountId } = params;
219215

220216
// Fetch the account to verify it exists
221217
const account = await prisma.account.findUnique({
222-
where: { id: accountId },
218+
where: { id: accountId, organizationId: org.id },
223219
select: {
224220
id: true,
225221
closedAt: true,
@@ -247,8 +243,7 @@ export const actions = {
247243

248244
const hasPermission =
249245
user.id === account.ownerId ||
250-
userOrg?.role === 'ADMIN' ||
251-
userOrg?.role === 'SALES_MANAGER';
246+
userOrg?.role === 'ADMIN';
252247

253248
if (!hasPermission) {
254249
return fail(403, { success: false, message: 'Permission denied. Only account owners, sales managers, or admins can reopen accounts.' });
@@ -294,9 +289,8 @@ export const actions = {
294289
addContact: async ({ params, request, locals }) => {
295290
try {
296291
const user = locals.user;
297-
if (!user) {
298-
return fail(401, { success: false, message: 'Unauthorized' });
299-
}
292+
const org = locals.org;
293+
300294
const { accountId } = params;
301295
let data;
302296
// Support both JSON and form submissions
@@ -312,6 +306,14 @@ export const actions = {
312306
if (!firstName || !lastName) {
313307
return fail(400, { success: false, message: 'First and last name are required.' });
314308
}
309+
310+
// check if the account exists and belongs to the organization
311+
const account = await prisma.account.findUnique({
312+
where: { id: accountId, organizationId: org.id }
313+
});
314+
if (!account) {
315+
return fail(404, { success: false, message: 'Account not found or does not belong to this organization.' });
316+
}
315317
// Create the contact
316318
const contact = await prisma.contact.create({
317319
data: {
@@ -321,7 +323,7 @@ export const actions = {
321323
phone: data.phone?.toString() || null,
322324
title: data.title?.toString() || null,
323325
ownerId: user.id,
324-
organizationId: (await prisma.account.findUnique({ where: { id: accountId }, select: { organizationId: true } })).organizationId
326+
organizationId: org.id,
325327
}
326328
});
327329
// Link contact to account
@@ -342,13 +344,9 @@ export const actions = {
342344

343345
addOpportunity: async ({ params, request, locals }) => {
344346
try {
345-
// @ts-ignore
346347
const user = locals.user;
347-
// @ts-ignore
348348
const org = locals.org;
349-
if (!user || !org) {
350-
return fail(401, { success: false, message: 'Unauthorized' });
351-
}
349+
352350
const { accountId } = params;
353351
const formData = await request.formData();
354352
const name = formData.get('name')?.toString().trim();
@@ -363,6 +361,15 @@ export const actions = {
363361
if (!name) {
364362
return fail(400, { success: false, message: 'Opportunity name is required.' });
365363
}
364+
365+
// chek if the account exists and belongs to the organization
366+
const account = await prisma.account.findUnique({
367+
where: { id: accountId, organizationId: org.id }
368+
});
369+
if (!account) {
370+
return fail(404, { success: false, message: 'Account not found or does not belong to this organization.' });
371+
}
372+
366373
// Create the opportunity
367374
await prisma.opportunity.create({
368375
data: {
@@ -385,9 +392,10 @@ export const actions = {
385392

386393
comment: async ({ request, params, locals }) => {
387394
const user = locals.user;
395+
const org = locals.org;
388396
// Fallback: fetch account to get organizationId
389397
const account = await prisma.account.findUnique({
390-
where: { id: params.accountId },
398+
where: { id: params.accountId, organizationId: org.id },
391399
select: { organizationId: true, ownerId: true }
392400
});
393401
if (!account) {
@@ -414,9 +422,7 @@ export const actions = {
414422
try {
415423
const user = locals.user;
416424
const org = locals.org;
417-
if (!user || !org) {
418-
return fail(401, { success: false, message: 'Unauthorized' });
419-
}
425+
420426
const { accountId } = params;
421427
const formData = await request.formData();
422428
const subject = formData.get('subject')?.toString().trim();
@@ -427,9 +433,17 @@ export const actions = {
427433
if (!subject) {
428434
return fail(400, { success: false, message: 'Subject is required.' });
429435
}
436+
437+
// Check if the account exists and belongs to the organization
438+
const account = await prisma.account.findUnique({
439+
where: { id: accountId, organizationId: org.id }
440+
});
441+
if (!account) {
442+
return fail(404, { success: false, message: 'Account not found or does not belong to this organization.' });
443+
}
430444
// If no ownerId is provided, default to current user
431445
// if (!ownerId) ownerId = user.id;
432-
console.log(user.id, org.id);
446+
// console.log(user.id, org.id);
433447
const task = await prisma.task.create({
434448
data: {
435449
subject,

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

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

4-
export async function load({ params }) {
4+
export async function load({ params, locals }) {
5+
const org = locals.org;
56
try {
67
const accountId = params.accountId;
78

89
const account = await prisma.account.findUnique({
9-
where: { id: accountId },
10+
where: { id: accountId, organizationId: org.id },
1011
select: {
1112
id: true,
1213
name: true,
@@ -72,13 +73,14 @@ export async function load({ params }) {
7273
}
7374

7475
export const actions = {
75-
default: async ({ params }) => {
76+
default: async ({ params, locals }) => {
7677
try {
7778
const accountId = params.accountId;
79+
const org = locals.org;
7880

7981
// Check if account exists first
8082
const account = await prisma.account.findUnique({
81-
where: { id: accountId },
83+
where: { id: accountId, organizationId: org.id },
8284
include: {
8385
_count: {
8486
select: {

0 commit comments

Comments
 (0)