diff --git a/package-lock.json b/package-lock.json index e9ae7f80..ec4555ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "svelte-stripe": "^1.1.4" }, "devDependencies": { + "@floating-ui/dom": "1.6.10", "@skeletonlabs/skeleton": "^2.0.0", "@skeletonlabs/tw-plugin": "^0.1.0", "@sveltejs/adapter-auto": "^2.0.0", @@ -430,6 +431,31 @@ "node": ">=14" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz", + "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==", + "dev": true, + "dependencies": { + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz", + "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", + "dev": true, + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz", + "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", + "dev": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", diff --git a/package.json b/package.json index 21441ddc..51debd24 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-node": "1.2.4", "@sveltejs/kit": "^1.5.0", + "@floating-ui/dom": "1.6.10", "@tailwindcss/forms": "^0.5.6", "@types/bcrypt": "^5.0.0", "@types/identicon.js": "^2.3.1", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6f610aea..3ab9e7dd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -121,8 +121,8 @@ model Picture { id Int @id @default(autoincrement()) title String? caption String? - data String? - isLocal Boolean @default(value: true) + data String? + isLocal Boolean @default(value: false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt Article Article[] @@ -165,6 +165,8 @@ model Article { model Team { id String @id @unique @default(uuid()) name String + teamLead Member? @relation("TeamLead", fields: [teamLeadId], references: [id]) + teamLeadId String? // Foreign key for team lead members Member[] maxMembers Int minMembers Int @@ -186,10 +188,10 @@ model Project { updatedAt DateTime @updatedAt description String logo Picture? @relation(fields: [pictureId], references: [id]) - budget Float + budget Float? docsLink String @default("") extraLinks Link[] - remainingFunds Float + remainingFunds Float? season Season @default(value: Fall) year Int articles Article[] @@ -200,6 +202,8 @@ model Project { Tags Tag[] Skills String[] projectType projectType @default(value: standard) + projectLead Member? @relation("ProjectLead", fields: [projectLeadId], references: [id]) + projectLeadId String? // Foreign key for project lead teams Team[] // possibility for no teams created if project does not require them } @@ -217,14 +221,16 @@ model Member { role Role @relation(fields: [roleId], references: [id]) roleId Int Articles Article[] - Projects Project[] BlogPost BlogPost[] Account Account? @relation(fields: [accountId], references: [id]) accountId Int? + Projects Project[] Teams Team[] Survey Survey? @relation(fields: [surveyId], references: [id]) surveyId Int? PasswordResetToken PasswordResetToken? + teamLead Team[] @relation("TeamLead") + projectLead Project[] @relation("ProjectLead") } model Sponsor { diff --git a/src/components/projectCard.svelte b/src/components/projectCard.svelte index 49438e31..931daa28 100644 --- a/src/components/projectCard.svelte +++ b/src/components/projectCard.svelte @@ -3,11 +3,19 @@ export let project: Project & { logo: Picture }; let hover = false; + + // Function to truncate description to 2 sentences + function truncateDescription(description: string): string { + const sentences = description.split('. ').filter((s) => s.trim() !== ''); + return sentences.length > 2 + ? `${sentences.slice(0, 2).join('. ')}...` + : description; + } - +
{ hover = true; }} @@ -15,27 +23,35 @@ hover = false; }} > - {#if !hover} - - {#if project.logo.isLocal} - + +
+

{project.title}

+
+ + +
+ {#if !hover} + + {#if project.logo.isLocal} + + {:else} + + {/if} {:else} -
{project.title}
- + +
+

{truncateDescription(project.description)}

+
+ {#each project.Skills as skill} + {skill} + {/each} +
+
{/if} - {:else} - -
-

{project.title}

-

{project.description}

- {#each project.Skills as skill} - {skill} - {/each} -
- {/if} -
+
+ + diff --git a/src/components/stripe/payments.svelte b/src/components/stripe/payments.svelte index f8c26207..ae2e43ab 100644 --- a/src/components/stripe/payments.svelte +++ b/src/components/stripe/payments.svelte @@ -12,7 +12,8 @@ let processing = false; export let userID: string; let thm: 'night' | 'stripe' | 'flat' | undefined = 'night'; - let duesSelection = '1'; + let duesSelection = ''; + let hide: boolean = false; const appearance = { theme: thm, @@ -22,17 +23,17 @@ onMount(async () => { stripe = await loadStripe(PUBLIC_STRIPE_KEY); - // create payment intent server side - clientSecret = await createPayment(); + // clientSecret = await createPayment(); }); async function createPayment() { + const response = await fetch('/create-payment-intent', { method: 'POST', headers: { 'content-type': 'application/json' }, - body: JSON.stringify({}) + body: JSON.stringify({ duesType: duesSelection }) }); const { clientSecret } = await response.json(); @@ -62,20 +63,44 @@ } } } + + function reloadPage(){ + location.reload(); + } + + async function test(){ + // create payment intent server side + clientSecret = await createPayment(); + hide = true; + } - {#if error}

Please try again

{error.message}

{/if} +{#if !hide} + +{:else} + {#if duesSelection == '1'} + Semesterly Dues + + + {/if} + {#if duesSelection == '2'} + Yearly Dues + + + {/if} +{/if} + {#if duesSelection} {#if clientSecret && stripe} diff --git a/src/components/toasts/failToast.ts b/src/components/toasts/failToast.ts new file mode 100644 index 00000000..b51fb653 --- /dev/null +++ b/src/components/toasts/failToast.ts @@ -0,0 +1,9 @@ +import { getToastStore, type ToastSettings } from '@skeletonlabs/skeleton'; + +export default (message: string) => { + const ts = { + message: message, + background: 'variant-filled-error' + } satisfies ToastSettings; + getToastStore().trigger(ts); +}; diff --git a/src/config.ts b/src/config.ts index 77d69dbf..f328df4b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -33,20 +33,28 @@ export default { * when roles in the future etc. Treasurser, come in to play, they can have a permission level to themselves, or even */ roles: { - officer: { - level: 10, - name: 'officer' + admin:{ + level: 5, + name: 'admin' }, - lead: { - level: 8, - name: 'lead' + president:{ + level: 5, + name: 'president' }, - committee: { - level: 6, - name: 'committee' + officers: { + level: 4, + name: 'officers' + }, + project_lead:{ + level: 3, + name: 'project lead' + }, + team_lead: { + level: 2, + name: ' team lead' }, member: { - level: 4, + level: 1, name: 'member' }, guest: { diff --git a/src/routes/(app)/dashboard/+page.server.ts b/src/routes/(app)/dashboard/+page.server.ts index ff15a236..b85c91ce 100644 --- a/src/routes/(app)/dashboard/+page.server.ts +++ b/src/routes/(app)/dashboard/+page.server.ts @@ -1,19 +1,57 @@ import { db } from '$lib/db'; import { z } from 'zod'; -import type { Actions, PageServerLoad } from './$types'; -import { superValidate } from 'sveltekit-superforms/server'; +import type { Actions, PageData, PageServerLoad } from './$types'; +import { setError, superValidate } from 'sveltekit-superforms/server'; import semesterYear from '../../../components/scripts/semesterYear'; -import config from '../../../config'; +import config from '../../../config.ts'; const updateDuesSchema = z.object({ email: z.string().email(), duesType: z.number() }); +const presidentSchema = z.object({ + presidentId: z.string().optional(), +}); + +const adminSchema = z.object({ + adminId: z.string().optional(), +}); + export const load: PageServerLoad = async ({ locals }) => { const form = await superValidate(updateDuesSchema); + const form1 = await superValidate(presidentSchema); + const form2 = await superValidate(adminSchema); const dateInfo = semesterYear(); + const currentPresident = await db.member.findFirst({ + where: { + role: { + name: 'president' + } + } + }); + + const currentAdmin = await db.member.findFirst({ + where: { + role: { + name: 'admin' + } + } + }); + + const members = await db.member.findMany({ + where: { + email: {not: locals.member?.email} + }, + select: { + id: true, + discordProfileName: true, + firstName: true, + lastName: true + } + }); + const availableProjects = await db.project.findMany({ where: { year: dateInfo.year, @@ -43,19 +81,13 @@ export const load: PageServerLoad = async ({ locals }) => { } } }, - Survey: true // Include the survey information + Survey: true, + role: true, } }); - // Log the DateUpdated value from the user's survey const surveyDateUpdated = user?.Survey?.DateUpdated; - // if (surveyDateUpdated) { - // console.log(`Survey Date Updated: ${surveyDateUpdated}`); - // } else { - // console.log('Survey Date Updated not found'); - // } - // Remove projects the user is already part of for (let i = 0; i < availableProjects.length; i++) { for (const element of user!.Projects) { if (availableProjects[i].id == element.id) { @@ -64,25 +96,25 @@ export const load: PageServerLoad = async ({ locals }) => { } } - return { user, form, availableProjects, surveyDateUpdated }; + return { user, form, availableProjects, surveyDateUpdated, form1, form2, currentPresident, members, currentAdmin}; }; export const actions: Actions = { - summerRole: async ({ request, locals }) => { + summerRole: async ({ request }) => { const form = await request.formData(); const id = form.get('id')?.toString(); if (id) { const currentYear = new Date().getFullYear(); - const august = new Date(currentYear, 7, 1); // August 1st - const dayOfWeek = august.getDay(); // Day of the week of August 1st - const firstDayOfFourthWeek = 22 + (7 - dayOfWeek) % 7; // Calculate the first day of the fourth week of August + const august = new Date(currentYear, 7, 1); + const dayOfWeek = august.getDay(); + const firstDayOfFourthWeek = 22 + (7 - dayOfWeek) % 7; await db.member.update({ where: { id: id }, data: { - membershipExpDate: new Date(currentYear, 7, firstDayOfFourthWeek), // Set the calculated date + membershipExpDate: new Date(currentYear, 7, firstDayOfFourthWeek), role: { connectOrCreate: { create: { @@ -98,7 +130,7 @@ export const actions: Actions = { }); } }, - + joinProject: async ({ request, locals }) => { const form = await request.formData(); const id = Number(form.get('projectID')); @@ -115,5 +147,117 @@ export const actions: Actions = { } } }); + }, + + changePresident: async ({ request }) => { + const formData = await request.formData(); + const form1 = await superValidate(formData, presidentSchema); + + if (!form1.valid) { + return setError(form1, 'presidentId', 'Invalid president selection.'); + } + + const newPresidentId = form1.data.presidentId; + + if (newPresidentId) { + const transaction = await db.$transaction(async (tx) => { + const currentPresident = await tx.member.findFirst({ + where: { + role: { + name: 'president' + } + } + }); + + if (currentPresident && currentPresident.id !== newPresidentId) { + await tx.member.update({ + where: { + id: currentPresident.id + }, + data: { + role: { + connect: { + name: 'member' + } + } + } + }); + } + + await tx.member.update({ + where: { + id: newPresidentId + }, + data: { + role: { + connect: { + name: 'president' + } + } + } + }); + }); + form1.message = 'OK'; + return { form1 }; + }else{ + form1.message = 'NO'; + return { form1 }; + } + }, + + changeAdmin: async ({ request }) => { + const formData = await request.formData(); + const form2 = await superValidate(formData, adminSchema); + + if (!form2.valid) { + return setError(form2, 'adminId', 'Invalid admin selection.'); + } + + const newAdminId = form2.data.adminId; + + if (newAdminId) { + const transaction = await db.$transaction(async (tx) => { + const currentAdmin = await tx.member.findFirst({ + where: { + role: { + name: 'admin' + } + } + }); + + if (currentAdmin && currentAdmin.id !== newAdminId) { + await tx.member.update({ + where: { + id: currentAdmin.id + }, + data: { + role: { + connect: { + name: 'member' + } + } + } + }); + } + + await tx.member.update({ + where: { + id: newAdminId + }, + data: { + role: { + connect: { + name: 'admin' + } + } + } + }); + }); + form2.message = 'OK'; + return { form2 }; + }else{ + form2.message = 'NO'; + return { form2 }; + } } }; diff --git a/src/routes/(app)/dashboard/+page.svelte b/src/routes/(app)/dashboard/+page.svelte index baf609f2..d39e725a 100644 --- a/src/routes/(app)/dashboard/+page.svelte +++ b/src/routes/(app)/dashboard/+page.svelte @@ -4,7 +4,7 @@ AppShell, type DrawerSettings, getDrawerStore, - modeCurrent + modeCurrent, } from '@skeletonlabs/skeleton'; import type { PageServerData } from './$types'; import { superForm } from 'sveltekit-superforms/client'; @@ -13,12 +13,18 @@ import RightSideBar from '../../../components/dashboard/rightSidebar/rightSideBar.svelte'; import Payments from '../../../components/stripe/payments.svelte'; import { enhance } from '$app/forms'; + import { Autocomplete, popup } from '@skeletonlabs/skeleton'; + import type { AutocompleteOption, PopupSettings } from '@skeletonlabs/skeleton'; + import successToast from '../../../components/toasts/successToast'; + import failToast from '../../../components/toasts/failToast'; export let data: PageServerData; - const { form, errors, constraints } = superForm(data.form, { + const { form, errors, constraints, message } = superForm(data.form, { clearOnSubmit: 'errors-and-message' }); + const drawerStore = getDrawerStore(); + const drawerSettingsLeft: DrawerSettings = { id: 'dashboard1', meta: { @@ -26,6 +32,7 @@ teams: data.user?.Teams } }; + const drawerSettingsRight: DrawerSettings = { id: 'dashboard2', position: 'right', @@ -55,8 +62,76 @@ break; } } + + let selectedMemberId: string | null = null; + let change: boolean | null = false; + let input: string = ''; + + let popupSettings: PopupSettings = { + event: 'focus-click', // Trigger popup on focus and click + target: 'popupAutocomplete', // The ID to target for the popup + placement: 'bottom' // Position the popup below the input + }; + + // Map members from the database for autocomplete options + const memberOptions: AutocompleteOption[] = data.members.map(member => ({ + label: `${member.firstName} ${member.lastName}`, + value: member.id, + keywords: `${member.firstName} ${member.lastName}, ${member.discordProfileName}`, + meta: {} + })); + + // Handle member selection from autocomplete + function onMemberSelection(event: CustomEvent): void { + input = event.detail.label; // Update input field with selected member's name + selectedMemberId = event.detail.value as string | null; // Store selected member ID + console.log(selectedMemberId); + } + + let selectedAdminId: string | null = null; + let changeAdmin: boolean | null = false; + let adminInput: string = ''; + + function onAdminSelection(event: CustomEvent): void { + adminInput = event.detail.label; // Update input field with selected admin's name + selectedAdminId = event.detail.value as string | null; // Store selected admin ID + // console.log(selectedAdminId); + } + + $: if ($message === 'OK') { + successToast('Configuration Updated Successfully!'); + }else if ($message === 'NO') { + failToast('Error 404, Member Not Found'); + } + + function isSummerPeriod() { + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); + + // Calculate the date of the fourth week in August (matching the original code) + const august = new Date(currentYear, 7, 1); + const dayOfWeek = august.getDay(); + const firstDayOfFourthWeek = 22 + (7 - dayOfWeek) % 7; + const fourthWeekInAugust = new Date(currentYear, 7, firstDayOfFourthWeek); + + // Set start date to May 1st + const startDate = new Date(currentYear, 4, 1); + + // console.log("Start: ", startDate); + // console.log("End: ", fourthWeekInAugust); + + return currentDate >= startDate && currentDate <= fourthWeekInAugust; + } + + + @@ -116,35 +191,147 @@ {/if}
- - {#if (data.user?.membershipExpDate.getTime() ?? 0) < new Date().getTime() && new Date().getMonth() <= 8 && new Date().getMonth() >= 4} + + {#if (data.user?.membershipExpDate.getTime() ?? 0) < new Date().getTime() && isSummerPeriod()} {#if data.user?.id}
{/if} + {:else} + {#if (data.user?.membershipExpDate.getTime() ?? 0) < new Date().getTime()} +
Looks like your dues are expired!
+
+
+ {#if data.user?.id} + + {/if} {:else} - {#if (data.user?.membershipExpDate.getTime() ?? 0) < new Date().getTime()} -
Looks like your dues are expired!
-
-
- {#if data.user?.id} - - {/if} - {:else} -
Your Dues Expire On {data.user?.membershipExpDate.toDateString()}
-
- Looks like you're all set! Check back in on discord after paying dues for membership status (it can take a second or two), and look out for announcements about updates to this site! -
- {/if} +
Your Dues Expire On {data.user?.membershipExpDate.toDateString()}
+
+ Looks like you're all set! Check back in on discord after paying dues for membership status (it can take a minute or two), and look out for announcements about updates to this site! +
+ {/if} {/if} - {#if !((data.user?.membershipExpDate.getTime() ?? 0) < new Date().getTime())} -
- {/if} + {#if (data.user?.membershipExpDate.getTime() ?? 0) > new Date().getTime()} +
+ {#if data.user?.role.permissionLevel > 1} +
+
+

+ {data.user?.role?.name?.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')} Dashboard +

+ + {#if data.user?.role.permissionLevel >= 5} +
+ {#if data.user?.role.name === "admin"} +
+
+ Configure President Position +
+ {#if data.currentPresident && !change} +

Current President: {data.currentPresident.firstName} {data.currentPresident.lastName}

+ + {/if} + + {#if !data.currentPresident || change} + +
+ +
+
change = false}> + + +
+ {/if} +
+ {/if} + {#if data.user?.role.name === "president"} +
+
Configure Admin Position
+ + {#if data.currentAdmin && !changeAdmin} +

Current Admin: {data.currentAdmin.firstName} {data.currentAdmin.lastName}

+ + {/if} + + {#if !data.currentAdmin || changeAdmin} + +
+ +
+
changeAdmin = false}> + + +
+ {/if} +
+ {/if} + + + + {/if} + + {#if data.user?.role.permissionLevel >= 4} +
+
+ Configure Projects +
+

Select or Create a Project

+ Create Project + Edit Project + + {/if} + + {#if data.user?.role.permissionLevel >= 3} +
+
+
+ Configure Teams & Team Leads +
+

Create, Edit, or Mange a Team

+ Create Team + Edit Team + {/if} + + {#if data.user?.role.permissionLevel >= 2} +
+
+
+ Configure Teams +
+

Appoint Members to a Team

+ Appoint to Team + {/if} +
+
+ {/if} {/if} diff --git a/src/routes/(app)/dashboard/admin/+page.server.ts b/src/routes/(app)/dashboard/admin/+page.server.ts deleted file mode 100644 index e18d4ff3..00000000 --- a/src/routes/(app)/dashboard/admin/+page.server.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals }) => { - if (locals.member.permissions.level <= 5) { - // lowwer than a committee member - throw redirect(302, '/'); - } - return {}; -}) satisfies PageServerLoad; diff --git a/src/routes/(app)/dashboard/admin/+page.svelte b/src/routes/(app)/dashboard/admin/+page.svelte deleted file mode 100644 index dbaddd9c..00000000 --- a/src/routes/(app)/dashboard/admin/+page.svelte +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/src/routes/(app)/dashboard/appoint-to-team/+page.server.ts b/src/routes/(app)/dashboard/appoint-to-team/+page.server.ts new file mode 100644 index 00000000..ac3cd815 --- /dev/null +++ b/src/routes/(app)/dashboard/appoint-to-team/+page.server.ts @@ -0,0 +1,85 @@ +import { z } from "zod"; +import type { Actions, PageServerLoad } from "./$types"; +import { superValidate } from "sveltekit-superforms/server"; +import { fail, redirect } from "@sveltejs/kit"; +import { db } from "$lib/db"; + +let pLevel = 0; + +const appointTeamSchema = z.object({ + members: z.string().array(), + teamId: z.string(), +}); + +export const load: PageServerLoad = async ({ parent, locals }) => { + const data = await parent(); + pLevel = data.member!.role.permissionLevel; + // check user permission level + if (!(pLevel > 1)) { + throw redirect(302, '/dashboard'); + } + + const teams = await db.team.findMany({ + where:{ + teamLead: { + firstName: locals.member.fname, + }, + }, + select:{ + id: true, + name: true, + teamLead: true, + Project: true, + } + }) + + const members = await db.member.findMany({ + where: { + email: {not: locals.member?.email} + }, + select: { + id: true, + discordProfileName: true, + firstName: true, + lastName: true + } + }); + + const form = await superValidate(appointTeamSchema); + return { form, teams, members }; +}; + +export const actions: Actions = { + default: async({ request }) => { + const form = await superValidate(request, appointTeamSchema); + // Validating forms + if (!form.valid) { + return fail(400, { form }); + } + console.log("Received Team ID:", form.data.teamId); // Verify received teamId + console.log("Received Members:", form.data.members); // Verify received members + + // Split the string of member IDs into an array + const memberIdsArray = form.data.members[0].split(','); + + // Map the array of member IDs to the format required by Prisma + const memberConnections = memberIdsArray.map((memberId) => ({ + id: memberId.trim(), + })); + + // Update the team with the connected members + await db.team.update({ + where: { + id: form.data.teamId, + }, + data: { + members: { + connect: memberConnections, + }, + }, + }); + + + throw redirect(302, '/dashboard'); + } +}; diff --git a/src/routes/(app)/dashboard/appoint-to-team/+page.svelte b/src/routes/(app)/dashboard/appoint-to-team/+page.svelte new file mode 100644 index 00000000..a265d24a --- /dev/null +++ b/src/routes/(app)/dashboard/appoint-to-team/+page.svelte @@ -0,0 +1,121 @@ + + + + +
+ +
+
+
+

Appoint Members to a Team

+ +
+ + +
+ Select Members + + +
+ +
+ + + + +
+
diff --git a/src/routes/(app)/dashboard/create-project/+page.server.ts b/src/routes/(app)/dashboard/create-project/+page.server.ts new file mode 100644 index 00000000..b8223245 --- /dev/null +++ b/src/routes/(app)/dashboard/create-project/+page.server.ts @@ -0,0 +1,134 @@ +import { z } from "zod"; +import type { Actions, PageServerLoad } from "./$types"; +import { setError, superValidate } from "sveltekit-superforms/server"; +import { fail, redirect } from "@sveltejs/kit"; +import { db } from "$lib/db"; + +let pLevel = 0; + +const createProSchema = z.object({ + title: z.string(), + description: z.string(), + docsLink: z.string(), + season: z.custom(), + year: z.string(), + logo: z.string(), + Skills: z.string().array(), + proLeadID: z.string() +}); + +export const load: PageServerLoad = async ({ parent, locals }) => { + const data = await parent(); + pLevel = data.member!.role.permissionLevel; + // check user permission level + if (!(pLevel > 3)) { + throw redirect(302, '/dashboard'); + } + + const members = await db.member.findMany({ + where: { + email: {not: locals.member?.email}, + }, + select: { + id: true, + discordProfileName: true, + firstName: true, + lastName: true + } + }); + + const form = await superValidate(createProSchema); + return { form, members }; +}; + +export const actions: Actions = { + default: async({ request }) => { + const form = await superValidate(request, createProSchema); + // Validating forms + if (!form.valid) { + return fail(400, { form }); + } + + const selectedyear = form.data.year; + const vaildTitle = form.data.title; + const vaildDocsLink = form.data.docsLink; + const vaildSeason = form.data.season; + const vaildLogo = form.data.logo; + const yearNum = parseInt(selectedyear); + + + if (vaildTitle === ''){ + return setError(form, 'title', 'Please Enter a Title'); + } + if (form.data.proLeadID === ''){ + return setError(form, 'proLeadID', "Please Select a Project Lead") + } + if (vaildLogo === ''){ + return setError(form, 'logo', 'Please Enter a Logo-Link'); + } + if (vaildDocsLink === ''){ + return setError(form, 'docsLink', 'Please Enter a Docs-Link'); + } + if (vaildSeason === ''){ + return setError(form, 'season', 'Please Enter a Season'); + } + if (selectedyear === '' || !yearNum) { + return setError(form, 'year', 'Please Enter a Valid Year'); + } + // console.log("raw skills: ", form.data.Skills); + const skillsArray = form.data.Skills[0].split(','); + // console.log("split skills: ", skillsArray); + + // Creating survey entry in the database + await db.project.create({ + data: { + title: form.data.title, + description: form.data.description, + projectLead: { + connect: { + id: form.data.proLeadID, + } + }, + logo: { + create: { + data: form.data.logo, + } + }, + docsLink: form.data.docsLink, + season: form.data.season, + year: yearNum, + Skills: skillsArray, + budget: 0, + remainingFunds: 0, + } + }); + + //update permisson level if insufficent + const lead = await db.member.findFirst({ + where:{ + id: form.data.proLeadID, + }, + select: { + role: true, + } + }) + + if (lead?.role.permissionLevel && lead?.role.permissionLevel < 3){ + await db.member.update({ + where: { + id: form.data.proLeadID, + }, + data: { + role: { + connect: { + name: "project lead", + } + } + } + }) + } + + + throw redirect(302, '/dashboard'); + } +}; diff --git a/src/routes/(app)/dashboard/create-project/+page.svelte b/src/routes/(app)/dashboard/create-project/+page.svelte new file mode 100644 index 00000000..81348e3f --- /dev/null +++ b/src/routes/(app)/dashboard/create-project/+page.svelte @@ -0,0 +1,182 @@ + + + + +
+ +
+
+
+

Create a Project

+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + + + +
+
+
+ \ No newline at end of file diff --git a/src/routes/(app)/dashboard/create-team/+page.server.ts b/src/routes/(app)/dashboard/create-team/+page.server.ts new file mode 100644 index 00000000..a9392b44 --- /dev/null +++ b/src/routes/(app)/dashboard/create-team/+page.server.ts @@ -0,0 +1,91 @@ +import { z } from "zod"; +import type { Actions, PageServerLoad } from "./$types"; +import { superValidate } from "sveltekit-superforms/server"; +import { fail, redirect } from "@sveltejs/kit"; +import { db } from "$lib/db"; + +let pLevel = 0; + +const createTeamSchema = z.object({ + projectId: z.number(), + name: z.string(), + teamLead: z.string(), +}); + +export const load: PageServerLoad = async ({ parent, locals }) => { + const data = await parent(); + pLevel = data.member!.role.permissionLevel; + // check user permission level + if (!(pLevel > 2)) { + throw redirect(302, '/dashboard'); + } + + const members = await db.member.findMany({ + where: { + email: {not: locals.member?.email} + }, + select: { + id: true, + discordProfileName: true, + firstName: true, + lastName: true + } + }); + + const projects = await db.project.findMany({ + select: { + id: true, + title: true, + season: true, + year: true, + projectType: true, + } + }); + + const form = await superValidate(createTeamSchema); + return { form, projects, members }; +}; + +export const actions: Actions = { + default: async({ request }) => { + const form = await superValidate(request, createTeamSchema); + // Validating forms + if (!form.valid) { + return fail(400, { form }); + } + + await db.team.create({ + data:{ + name: form.data.name, + teamLead: { + connect: { + id: form.data.teamLead, + } + }, + Project: { + connect: { + id: form.data.projectId, + } + }, + // to be changed later down the line + maxMembers: 0, + minMembers: 0, + } + }); + + //update member acess + await db.member.update({ + where:{ + id: form.data.teamLead, + }, + data:{ + role: { + connect: { + name: 'team lead', + } + } + } + }) + throw redirect(302, '/dashboard'); + } +}; diff --git a/src/routes/(app)/dashboard/create-team/+page.svelte b/src/routes/(app)/dashboard/create-team/+page.svelte new file mode 100644 index 00000000..babe4fd9 --- /dev/null +++ b/src/routes/(app)/dashboard/create-team/+page.svelte @@ -0,0 +1,143 @@ + + + + +
+ +
+
+
+

Create a Team

+ +
+ + +
+ + +
+ + + + +
+
+
+ \ No newline at end of file diff --git a/src/routes/(app)/dashboard/edit-projects/+page.server.ts b/src/routes/(app)/dashboard/edit-projects/+page.server.ts new file mode 100644 index 00000000..9dbb894a --- /dev/null +++ b/src/routes/(app)/dashboard/edit-projects/+page.server.ts @@ -0,0 +1,93 @@ +import { z } from "zod"; +import type { Actions, PageServerLoad } from "./$types"; +import { setError, superValidate } from "sveltekit-superforms/server"; +import { fail, redirect } from "@sveltejs/kit"; +import { db } from "$lib/db"; +import { prisma } from "$lib/server/prisma"; + +let pLevel = 0; + +const editProSchema = z.object({ + title: z.string().min(1, "Title is required."), + description: z.string().optional(), + docsLink: z.string().url("Invalid URL format."), + season: z.enum(["Fall", "Spring", "Summer"]), + year: z.string().regex(/^\d{4}$/, "Year must be a 4-digit number."), + logo: z.string().url("Invalid URL format."), + Skills: z.string().array().optional(), + id: z.number() +}); + +const projectSelectionSchema = z.object({ + id: z.number() +}) + +export const load: PageServerLoad = async ({ parent }) => { + const data = await parent(); + pLevel = data.member!.role.permissionLevel; + // check user permission level + if (!(pLevel > 3)) { + throw redirect(302, '/dashboard'); + } + + // Fetch all projects + const allProjects = await prisma.project.findMany({ + // You can include other relations if needed, like `member` or `tasks` + }); + + const form = await superValidate(editProSchema); + const selectionForm = await superValidate(projectSelectionSchema); + + console.log("On load Project id is: " + form.data.id); + + const currentProject = await db.project.findUnique({ + where: {id: form.data.id} + }) + + return { + form, selectionForm, currentProject, + member: { + Projects: allProjects // Pass all projects to the frontend + } + }; +}; + +// Handle form submission +export const actions: Actions = { + selectProject: async ({ request }) => { + console.log("Project select has been triggered!"); + // Parse the form data + const form = await superValidate(request, projectSelectionSchema); + + if (!form.valid) { + return fail(400, { form }); + } + + const projectId = form.data.id; + + // Find the project + const project = await db.project.findUnique({ + where: { id: projectId }, + }); + + console.log(project); + + if (!project) { + return setError(form, 'id', 'Project not found.'); + }else{ + return { form, currentProject: project }; + } + }, + + + updateProject: async ({ request }) => { + // Parse the form data + const form = await superValidate(request, editProSchema); + + // If form validation fails, return with errors + if (!form.valid) { + return fail(400, { form }); + } + } +}; + diff --git a/src/routes/(app)/dashboard/edit-projects/+page.svelte b/src/routes/(app)/dashboard/edit-projects/+page.svelte new file mode 100644 index 00000000..aaa91299 --- /dev/null +++ b/src/routes/(app)/dashboard/edit-projects/+page.svelte @@ -0,0 +1,185 @@ + + +
+ +
+
+

Edit a Project

+ {#if !data.currentProject} +
+

Select a Project

+ + +
+ +
+ + +
+ {/if} + + + {#if data.currentProject} +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + +
+ {/if} +
+
+ \ No newline at end of file diff --git a/src/routes/(app)/dashboard/lead/+page.server.ts b/src/routes/(app)/dashboard/lead/+page.server.ts deleted file mode 100644 index 421265a8..00000000 --- a/src/routes/(app)/dashboard/lead/+page.server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { redirect, type Actions } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; -import { z } from 'zod'; -import { superValidate } from 'sveltekit-superforms/server'; - -export const load = (async ({ locals }) => { - const form = superValidate(blogpostSchema); - if (locals.member.permissions.level < 8) { - throw redirect(302, '/'); - } - return { form }; -}) satisfies PageServerLoad; - -const blogpostSchema = z.object({ - title: z.string().max(32, 'Titles cannot be more than 32 characters!'), - blogpost: z.string(), - picture: z.string() // string that represents the url of the picture -}); -export const actions: Actions = { - blogPost: async ({ locals, request }) => { - const form = superValidate(request, blogpostSchema); - } -}; diff --git a/src/routes/(app)/dashboard/lead/+page.svelte b/src/routes/(app)/dashboard/lead/+page.svelte deleted file mode 100644 index 101d177d..00000000 --- a/src/routes/(app)/dashboard/lead/+page.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - -
-

Create new Blogpost

- -
-
- -
-