diff --git a/src/app.css b/src/app.css index 22190f0..ef930f0 100644 --- a/src/app.css +++ b/src/app.css @@ -99,6 +99,15 @@ h1 { 'opsz' 20 } +.icon-48 { + font-size: 48px !important; + font-variation-settings: + 'FILL' 0, + 'wght' 400, + 'GRAD' 0, + 'opsz' 20 +} + input[type='search'], input[type='text'], input[type='password'], input[type='email'], input[type='tel'], input[type='number'], textarea, select { background-color: var(--bg-color); color: var(--text-color); @@ -429,3 +438,7 @@ h2 { /*width: 100%;*/ } +/*.drop_zone {*/ +/* border: 1px dashed var(--dull-primary-color);*/ +/*}*/ + diff --git a/src/lib/db/index.server.ts b/src/lib/db/index.server.ts index 61b89e4..b0862f7 100644 --- a/src/lib/db/index.server.ts +++ b/src/lib/db/index.server.ts @@ -11,6 +11,8 @@ import { type Application } from '$lib/types'; import { sendEmployerNotificationEmail } from '$lib/emailer.server'; +import fs from 'fs'; +import path from 'path'; export async function createUser(user: User): Promise { const password_hash: string = await bcrypt.hash(user.password!, 12); @@ -340,6 +342,7 @@ export async function getCompanyFullData( ), user_data AS ( SELECT + id, username, email, phone, @@ -491,7 +494,7 @@ export async function getCompanyEmployers( last_signin AT TIME ZONE 'UTC' AS "lastSignIn", company_id as "companyId" FROM users - WHERE "company_code" = (SELECT company_code FROM companies WHERE id = ${id})) + WHERE "company_id" = ${id}) SELECT ( SELECT row_to_json(company_data) @@ -530,6 +533,101 @@ export async function getCompanyEmployers( }; } +export async function getCompanyEmployersAndRequests( + id: number +): Promise<{ company: Company; employers: User[]; requests: User[] }> { + const data = await sql` + WITH company_data AS ( + SELECT + id, + name, + description, + website, + created_at AT TIME ZONE 'UTC' AS "createdAt", + company_code AS "companyCode" + FROM companies + WHERE id = ${id} + ), + employer_data AS (SELECT id, + username, + email, + phone, + full_name AS "fullName", + created_at AT TIME ZONE 'UTC' AS "createdAt", + last_signin AT TIME ZONE 'UTC' AS "lastSignIn", + company_id as "companyId" + FROM users + WHERE "company_id" = ${id}), + request_data AS (SELECT id, + username, + email, + phone, + full_name AS "fullName", + created_at AT TIME ZONE 'UTC' AS "createdAt", + last_signin AT TIME ZONE 'UTC' AS "lastSignIn", + company_id as "companyId" + FROM users + WHERE "company_code" = (SELECT company_code FROM companies WHERE id = ${id})) + SELECT + ( + SELECT row_to_json(company_data) + FROM company_data + ) AS company, + ( + SELECT json_agg(row_to_json(employer_data)) + FROM employer_data + ) AS employers, + ( + SELECT json_agg(row_to_json(request_data)) + FROM request_data + ) AS requests; + `; + + if (!data) { + error(404, 'Company not found'); + } + if (data[0].employers) { + data[0].employers.forEach( + (user: { + company: { id: any }; + companyId: any; + createdAt: string | number | Date; + lastSignIn: string | number | Date; + }) => { + user.company = { + id: user.companyId + }; + user.createdAt = new Date(user.createdAt); + user.lastSignIn = new Date(user.lastSignIn); + delete user.companyId; + } + ); + } + if (data[0].requests) { + data[0].requests.forEach( + (user: { + company: { id: any }; + companyId: any; + createdAt: string | number | Date; + lastSignIn: string | number | Date; + }) => { + user.company = { + id: user.companyId + }; + user.createdAt = new Date(user.createdAt); + user.lastSignIn = new Date(user.lastSignIn); + delete user.companyId; + } + ); + } + + return { + company: data[0].company, + employers: data[0].employers, + requests: data[0].requests + }; +} + export async function removeEmployerFromCompany(companyId: number, userId: number): Promise { await sql` UPDATE users @@ -748,7 +846,10 @@ export async function getApplications(postingId: number): Promise username: application.username, email: application.email, phone: application.phone, - fullName: application.fullName + fullName: application.fullName, + resume: fs.existsSync( + path.join(process.cwd(), 'static', 'uploads', 'resumes', `${application.userId}.pdf`) + ) }; delete application.userId; delete application.username; diff --git a/src/lib/types.ts b/src/lib/types.ts index 76435d9..ab25cf1 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -12,6 +12,7 @@ export interface User { company: Company | null; companyCode: string | null; companyId: number | null | undefined; + resume: boolean | null | undefined; } export interface Company { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 46b0a25..a634bef 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -36,7 +36,7 @@
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a83a7b5..60d4f14 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -18,5 +18,16 @@ We provide an accessible way for students to find internships and co-op opportunities, and for employers to find students to fill their positions. +

+ Create an account or sign in here. +

+

+ Or head over to our postings page here to view all job opportunities. +

diff --git a/src/routes/account/+page.server.ts b/src/routes/account/+page.server.ts index 9bbbc92..0d1772a 100644 --- a/src/routes/account/+page.server.ts +++ b/src/routes/account/+page.server.ts @@ -1,15 +1,16 @@ import type { PageServerLoad } from './$types'; import { deleteApplicationWithUser, getUserWithCompanyAndApplications } from '$lib/db/index.server'; import { getUserId } from '$lib/index.server'; -import { type Actions, fail } from '@sveltejs/kit'; +import { type Actions, fail, json, redirect } from '@sveltejs/kit'; import fs from 'fs'; import path from 'path'; +import { writeFileSync } from 'fs'; export const load: PageServerLoad = async ({ cookies }) => { const id = getUserId(cookies); const userData = await getUserWithCompanyAndApplications(id); const resumeExists = fs.existsSync( - path.join(process.cwd(), 'static', 'uploads', 'resume', `${id}.pdf`) + path.join(process.cwd(), 'static', 'uploads', 'resumes', `${id}.pdf`) ); return { @@ -19,12 +20,26 @@ export const load: PageServerLoad = async ({ cookies }) => { }; export const actions: Actions = { - delete: async ({ url, cookies }) => { + deleteApplication: async ({ url, cookies }) => { const id = parseInt(url.searchParams.get('id')!); try { await deleteApplicationWithUser(id, getUserId(cookies)); } catch (err) { return fail(500, { errorMessage: `Internal Server Error: ${err}` }); } + }, + uploadResume: async ({ request, cookies }) => { + const formData = await request.formData(); + const file = formData.get('resume') as File; + + if (!file) { + fail(400, { message: 'invalid' }); + } + writeFileSync( + `static/uploads/resumes/${getUserId(cookies)}.pdf`, + Buffer.from(await file.arrayBuffer()) + ); + + return { success: true }; } }; diff --git a/src/routes/account/+page.svelte b/src/routes/account/+page.svelte index da65a14..9d21943 100644 --- a/src/routes/account/+page.svelte +++ b/src/routes/account/+page.svelte @@ -3,6 +3,8 @@ import type { PageProps } from './$types'; let applicationToDelete: number = $state(0); + let { data, form }: PageProps = $props(); + let resumeState = $state(data.resumeExists); onMount(() => { if (!document.cookie.includes('jwt=')) { @@ -42,8 +44,78 @@ document.getElementById('deleteConfirmModal')!.style.display = 'none'; } - let resumeUpload; - let { data, form }: PageProps = $props(); + function openUpload() { + document.getElementById('uploadModal')!.style.display = 'block'; + } + + function closeUpload() { + document.getElementById('uploadModal')!.style.display = 'none'; + } + + let files: FileList | undefined | null = $state(); + let file: File | undefined | null = $state(); + + $effect(() => { + if (files) { + if (files[files.length - 1]?.type == 'application/pdf') { + file = files[files.length - 1]; + console.log(file.name); + } + } + }); + + function dropHandler(event: DragEvent) { + // Prevent default behavior (Prevent file from being opened) + event.preventDefault(); + + if (event.dataTransfer?.items) { + const items = event.dataTransfer.items; + // Use DataTransferItemList interface to access the file(s) + // If dropped items aren't files, reject them + if (items[0].kind === 'file') { + const upload = items[0].getAsFile(); + if (upload?.type !== 'application/pdf') { + console.error('Invalid file type'); + return; + } + file = upload; + } + } + } + + function dragOverHandler(event: DragEvent) { + event.preventDefault(); + } + + async function handleSubmit(event: SubmitEvent) { + event.preventDefault(); // Prevent default form submission + + if (!file) { + alert('Please select a file first'); + return; + } + + const formData = new FormData(); + formData.append('resume', file); // Manually append the file + + try { + const response = await fetch('?/uploadResume', { + method: 'POST', + body: formData + }); + + if (response.ok) { + const result = await response.json(); + console.log('File uploaded successfully:', result); + closeUpload(); + resumeState = true; + } else { + console.error('Upload failed'); + } + } catch (error) { + console.error('Error uploading file:', error); + } + }
@@ -142,19 +214,18 @@ {/if}
Résumé:
- {#if data.resumeExists} + {#if resumeState} - - {:else} - -
+ {/if} @@ -203,7 +274,7 @@ + diff --git a/src/routes/account/settings/+page.svelte b/src/routes/account/settings/+page.svelte index 366eec4..ee6ba6c 100644 --- a/src/routes/account/settings/+page.svelte +++ b/src/routes/account/settings/+page.svelte @@ -174,7 +174,9 @@