import cleanup
All checks were successful
ci / docker_image (push) Successful in 1m37s
ci / deploy (push) Successful in 16s

This commit is contained in:
Drake Marino 2025-03-29 18:13:02 -05:00
parent 1270fb3ae9
commit 77655c779d
45 changed files with 848 additions and 877 deletions

View File

@ -83,8 +83,7 @@ h1 {
.icon-16 { .icon-16 {
font-size: 16px !important; font-size: 16px !important;
font-variation-settings: font-variation-settings: 'FILL' 0,
'FILL' 0,
'wght' 400, 'wght' 400,
'GRAD' 0, 'GRAD' 0,
'opsz' 20 'opsz' 20
@ -92,8 +91,7 @@ h1 {
.icon-20 { .icon-20 {
font-size: 20px !important; font-size: 20px !important;
font-variation-settings: font-variation-settings: 'FILL' 0,
'FILL' 0,
'wght' 400, 'wght' 400,
'GRAD' 0, 'GRAD' 0,
'opsz' 20 'opsz' 20
@ -101,8 +99,7 @@ h1 {
.icon-48 { .icon-48 {
font-size: 48px !important; font-size: 48px !important;
font-variation-settings: font-variation-settings: 'FILL' 0,
'FILL' 0,
'wght' 400, 'wght' 400,
'GRAD' 0, 'GRAD' 0,
'opsz' 20 'opsz' 20
@ -300,8 +297,8 @@ input[type='checkbox']:focus {
width: 100%; /* Full width */ width: 100%; /* Full width */
height: 100%; /* Full height */ height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */ overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */ background-color: rgb(0, 0, 0); /* Fallback color */
background-color: rgba(0,0,0,0.6); /* Black w/ opacity */ background-color: rgba(0, 0, 0, 0.6); /* Black w/ opacity */
} }
.modal-always-display { .modal-always-display {
@ -312,8 +309,8 @@ input[type='checkbox']:focus {
width: 100%; /* Full width */ width: 100%; /* Full width */
height: 100%; /* Full height */ height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */ overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */ background-color: rgb(0, 0, 0); /* Fallback color */
background-color: rgba(0,0,0,0.6); /* Black w/ opacity */ background-color: rgba(0, 0, 0, 0.6); /* Black w/ opacity */
} }
/* Modal Content/Box */ /* Modal Content/Box */

View File

@ -1,11 +1,11 @@
<!doctype html> <!doctype html>
<!--suppress HtmlUnknownTarget --> <!--suppress HtmlUnknownTarget -->
<html lang="en" data-theme=""> <html data-theme="" lang="en">
<head> <head>
<title>FBLA 25</title> <title>FBLA 25</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link href="%sveltekit.assets%/favicon.png" rel="icon" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta content="width=device-width, initial-scale=1" name="viewport" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">

View File

@ -18,9 +18,10 @@ export async function createUser(user: User): Promise<number> {
const password_hash: string = await bcrypt.hash(user.password!, 12); const password_hash: string = await bcrypt.hash(user.password!, 12);
const response = await sql` const response = await sql`
INSERT INTO users (username, password_hash, perms, created_at, last_signin, active, email, phone, full_name, company_code) INSERT INTO users (username, password_hash, perms, created_at, last_signin, active, email, phone, full_name,
VALUES (${user.username}, ${password_hash}, ${user.perms}, NOW(), NOW(), ${user.active}, ${user.email}, ${user.phone}, ${user.fullName}, ${user.companyCode}) company_code)
RETURNING id; VALUES (${user.username}, ${password_hash}, ${user.perms}, NOW(), NOW(), ${user.active}, ${user.email},
${user.phone}, ${user.fullName}, ${user.companyCode}) RETURNING id;
`; `;
// TODO: handle custom image uploads // TODO: handle custom image uploads
@ -40,15 +41,28 @@ export async function updateUser(user: User): Promise<number> {
// Construct the SQL query // Construct the SQL query
const response = await sql`UPDATE users const response = await sql`UPDATE users
SET SET username = ${user.username},
username = ${user.username}, ${
${user.perms !== undefined ? sql`perms = ${user.perms},` : sql``} user.perms !== undefined
${user.active !== undefined ? sql`active = ${user.active},` : sql``} ? sql`perms
${password_hash !== null ? sql`password_hash = ${password_hash},` : sql``} =
email = ${user.email}, ${user.perms},`
phone = ${user.phone}, : sql``
full_name = ${user.fullName}, }
company_code = ${user.companyCode} ${
user.active !== undefined
? sql`active
=
${user.active},`
: sql``
} ${
password_hash !== null
? sql`password_hash
=
${password_hash},`
: sql``
}
email = ${user.email}, phone = ${user.phone}, full_name = ${user.fullName}, company_code = ${user.companyCode}
WHERE id = ${user.id} WHERE id = ${user.id}
RETURNING id;`; RETURNING id;`;
@ -115,10 +129,17 @@ export async function getCompanies(searchQuery: string | null = null): Promise<C
// should require MANAGE_USERS permission // should require MANAGE_USERS permission
export async function getUser(id: number): Promise<User> { export async function getUser(id: number): Promise<User> {
const [user] = await sql` const [user] = await sql`
SELECT id, username, perms, SELECT id,
username,
perms,
created_at AT TIME ZONE 'UTC' AS "createdAt", created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn", last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
active, email, phone, full_name AS "fullName", company_id, company_code active,
email,
phone,
full_name AS "fullName",
company_id,
company_code
FROM users FROM users
WHERE id = ${id}; WHERE id = ${id};
`; `;
@ -134,8 +155,7 @@ export async function getUser(id: number): Promise<User> {
export async function getUserWithCompany(id: number): Promise<User> { export async function getUserWithCompany(id: number): Promise<User> {
const [user] = await sql` const [user] = await sql`
SELECT SELECT u.id,
u.id,
u.username, u.username,
u.perms, u.perms,
u.email, u.email,
@ -149,12 +169,10 @@ export async function getUserWithCompany(id: number): Promise<User> {
c.description AS company_description, c.description AS company_description,
c.website AS company_website, c.website AS company_website,
c.created_at AS company_created_at c.created_at AS company_created_at
FROM FROM users u
users u
LEFT JOIN LEFT JOIN
companies c ON u.company_id = c.id companies c ON u.company_id = c.id
WHERE WHERE u.id = ${id};
u.id = ${id};
`; `;
if (!user) { if (!user) {
error(404, 'User not found'); error(404, 'User not found');
@ -181,19 +199,14 @@ export async function getUserWithCompanyAndApplications(
id: number id: number
): Promise<{ user: User; applications: Application[] }> { ): Promise<{ user: User; applications: Application[] }> {
const data = await sql` const data = await sql`
WITH company_data AS ( WITH company_data AS (SELECT id,
SELECT
id,
name, name,
description, description,
website, website,
created_at AT TIME ZONE 'UTC' AS "createdAt" created_at AT TIME ZONE 'UTC' AS "createdAt"
FROM companies FROM companies
WHERE id = (SELECT company_id FROM users WHERE id = ${id}) WHERE id = (SELECT company_id FROM users WHERE id = ${id})),
), user_data AS (SELECT id,
user_data AS (
SELECT
id,
username, username,
perms, perms,
email, email,
@ -203,26 +216,17 @@ export async function getUserWithCompanyAndApplications(
last_signin AT TIME ZONE 'UTC' AS "lastSignIn", last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
active active
FROM users FROM users
WHERE "id" = ${id} WHERE "id" = ${id}),
), application_data AS (SELECT id,
application_data AS (
SELECT
id,
posting_id AS "postingId", posting_id AS "postingId",
(SELECT title FROM postings WHERE id = posting_id) AS "postingTitle", (SELECT title FROM postings WHERE id = posting_id) AS "postingTitle",
created_at AT TIME ZONE 'UTC' AS "createdAt" created_at AT TIME ZONE 'UTC' AS "createdAt"
FROM applications FROM applications
WHERE "user_id" = ${id} WHERE "user_id" = ${id})
) SELECT (SELECT row_to_json(company_data)
SELECT FROM company_data) AS company,
( (SELECT row_to_json(user_data)
SELECT row_to_json(company_data) FROM user_data) AS user,
FROM company_data
) AS company,
(
SELECT row_to_json(user_data)
FROM user_data
) AS user,
( (
SELECT json_agg(row_to_json(application_data)) SELECT json_agg(row_to_json(application_data))
FROM application_data FROM application_data
@ -258,7 +262,8 @@ export async function getUserWithCompanyAndApplications(
// should require MANAGE_USERS permission // should require MANAGE_USERS permission
export async function deleteUser(id: number): Promise<void> { export async function deleteUser(id: number): Promise<void> {
await sql` await sql`
DELETE FROM users DELETE
FROM users
WHERE id = ${id}; WHERE id = ${id};
`; `;
} }
@ -283,8 +288,8 @@ export async function updateLastSignin(username: string): Promise<void> {
export async function createCompany(company: Company): Promise<number> { export async function createCompany(company: Company): Promise<number> {
const response = await sql` const response = await sql`
INSERT INTO companies (name, description, website, created_at, company_code) INSERT INTO companies (name, description, website, created_at, company_code)
VALUES (${company.name}, ${company.description}, ${company.website}, NOW(), generate_company_code(CAST(CURRVAL('companies_id_seq') AS INT))) VALUES (${company.name}, ${company.description}, ${company.website}, NOW(),
RETURNING id; generate_company_code(CAST(CURRVAL('companies_id_seq') AS INT))) RETURNING id;
`; `;
await saveLogo(company); await saveLogo(company);
@ -295,9 +300,10 @@ export async function createCompany(company: Company): Promise<number> {
export async function editCompany(company: Company): Promise<number> { export async function editCompany(company: Company): Promise<number> {
const response = await sql` const response = await sql`
UPDATE companies UPDATE companies
SET name = ${company.name}, description = ${company.description}, website = ${company.website} SET name = ${company.name},
WHERE id = ${company.id} description = ${company.description},
RETURNING id; website = ${company.website}
WHERE id = ${company.id} RETURNING id;
`; `;
await saveLogo(company); await saveLogo(company);
@ -307,7 +313,8 @@ export async function editCompany(company: Company): Promise<number> {
export async function deleteCompany(id: number): Promise<void> { export async function deleteCompany(id: number): Promise<void> {
await sql` await sql`
DELETE FROM companies DELETE
FROM companies
WHERE id = ${id}; WHERE id = ${id};
`; `;
@ -332,29 +339,21 @@ export async function getCompanyFullData(
id: number id: number
): Promise<{ company: Company; users: User[]; postings: Posting[] }> { ): Promise<{ company: Company; users: User[]; postings: Posting[] }> {
const data = await sql` const data = await sql`
WITH company_data AS ( WITH company_data AS (SELECT id,
SELECT
id,
name, name,
description, description,
website, website,
created_at AT TIME ZONE 'UTC' AS "createdAt" created_at AT TIME ZONE 'UTC' AS "createdAt"
FROM companies FROM companies
WHERE id = ${id} WHERE id = ${id}),
), user_data AS (SELECT id,
user_data AS (
SELECT
id,
username, username,
email, email,
phone, phone,
full_name AS "fullName" full_name AS "fullName"
FROM users FROM users
WHERE "company_id" = ${id} WHERE "company_id" = ${id}),
), posting_data AS (SELECT id,
posting_data AS (
SELECT
id,
title, title,
description, description,
employer_id AS "employerId", employer_id AS "employerId",
@ -367,21 +366,13 @@ export async function getCompanyFullData(
updated_at AT TIME ZONE 'UTC' AS "updatedAt", updated_at AT TIME ZONE 'UTC' AS "updatedAt",
flyer_link AS "flyerLink" flyer_link AS "flyerLink"
FROM postings FROM postings
WHERE "company_id" = ${id} WHERE "company_id" = ${id})
) SELECT (SELECT row_to_json(company_data)
SELECT FROM company_data) AS company,
( (SELECT json_agg(row_to_json(user_data))
SELECT row_to_json(company_data) FROM user_data) AS users,
FROM company_data (SELECT json_agg(row_to_json(posting_data))
) AS company, FROM posting_data) AS postings;
(
SELECT json_agg(row_to_json(user_data))
FROM user_data
) AS users,
(
SELECT json_agg(row_to_json(posting_data))
FROM posting_data
) AS postings;
`; `;
if (!data) { if (!data) {
@ -432,9 +423,11 @@ export async function createPosting(posting: Posting): Promise<number> {
} }
} }
const response = await sql` const response = await sql`
INSERT INTO postings (title, description, employer_id, address, employment_type, wage, link, tag_ids, created_at, updated_at, flyer_link, company_id) INSERT INTO postings (title, description, employer_id, address, employment_type, wage, link, tag_ids, created_at,
VALUES (${posting.title}, ${posting.description}, ${posting.employerId}, ${posting.address}, ${posting.employmentType}, ${posting.wage}, ${posting.link}, ${posting.tagIds}, NOW(), NOW(), ${posting.flyerLink}, ${posting.companyId}) updated_at, flyer_link, company_id)
RETURNING id; VALUES (${posting.title}, ${posting.description}, ${posting.employerId}, ${posting.address},
${posting.employmentType}, ${posting.wage}, ${posting.link}, ${posting.tagIds}, NOW(), NOW(),
${posting.flyerLink}, ${posting.companyId}) RETURNING id;
`; `;
return response[0].id; return response[0].id;
@ -457,9 +450,18 @@ export async function editPosting(posting: Posting): Promise<number> {
const response = await sql` const response = await sql`
UPDATE postings UPDATE postings
SET title = ${posting.title}, description = ${posting.description}, employer_id = ${posting.employerId}, address = ${posting.address}, employment_type = ${posting.employmentType}, wage = ${posting.wage}, link = ${posting.link}, tag_ids = ${posting.tagIds}, updated_at = NOW(), flyer_link = ${posting.flyerLink}, company_id = ${posting.companyId} SET title = ${posting.title},
WHERE id = ${posting.id} description = ${posting.description},
RETURNING id; employer_id = ${posting.employerId},
address = ${posting.address},
employment_type = ${posting.employmentType},
wage = ${posting.wage},
link = ${posting.link},
tag_ids = ${posting.tagIds},
updated_at = NOW(),
flyer_link = ${posting.flyerLink},
company_id = ${posting.companyId}
WHERE id = ${posting.id} RETURNING id;
`; `;
return response[0].id; return response[0].id;
@ -467,7 +469,8 @@ export async function editPosting(posting: Posting): Promise<number> {
export async function deletePosting(id: number): Promise<void> { export async function deletePosting(id: number): Promise<void> {
await sql` await sql`
DELETE FROM postings DELETE
FROM postings
WHERE id = ${id}; WHERE id = ${id};
`; `;
} }
@ -476,17 +479,14 @@ export async function getCompanyEmployers(
id: number id: number
): Promise<{ company: Company; users: User[] }> { ): Promise<{ company: Company; users: User[] }> {
const data = await sql` const data = await sql`
WITH company_data AS ( WITH company_data AS (SELECT id,
SELECT
id,
name, name,
description, description,
website, website,
created_at AT TIME ZONE 'UTC' AS "createdAt", created_at AT TIME ZONE 'UTC' AS "createdAt",
company_code AS "companyCode" company_code AS "companyCode"
FROM companies FROM companies
WHERE id = ${id} WHERE id = ${id}),
),
user_data AS (SELECT id, user_data AS (SELECT id,
username, username,
email, email,
@ -497,15 +497,10 @@ export async function getCompanyEmployers(
company_id as "companyId" company_id as "companyId"
FROM users FROM users
WHERE "company_id" = ${id}) WHERE "company_id" = ${id})
SELECT SELECT (SELECT row_to_json(company_data)
( FROM company_data) AS company,
SELECT row_to_json(company_data) (SELECT json_agg(row_to_json(user_data))
FROM company_data FROM user_data) AS users;
) AS company,
(
SELECT json_agg(row_to_json(user_data))
FROM user_data
) AS users;
`; `;
if (!data) { if (!data) {
@ -539,17 +534,14 @@ export async function getCompanyEmployersAndRequests(
id: number id: number
): Promise<{ company: Company; employers: User[]; requests: User[] }> { ): Promise<{ company: Company; employers: User[]; requests: User[] }> {
const data = await sql` const data = await sql`
WITH company_data AS ( WITH company_data AS (SELECT id,
SELECT
id,
name, name,
description, description,
website, website,
created_at AT TIME ZONE 'UTC' AS "createdAt", created_at AT TIME ZONE 'UTC' AS "createdAt",
company_code AS "companyCode" company_code AS "companyCode"
FROM companies FROM companies
WHERE id = ${id} WHERE id = ${id}),
),
employer_data AS (SELECT id, employer_data AS (SELECT id,
username, username,
email, email,
@ -570,19 +562,12 @@ export async function getCompanyEmployersAndRequests(
company_id as "companyId" company_id as "companyId"
FROM users FROM users
WHERE "company_code" = (SELECT company_code FROM companies WHERE id = ${id})) WHERE "company_code" = (SELECT company_code FROM companies WHERE id = ${id}))
SELECT SELECT (SELECT row_to_json(company_data)
( FROM company_data) AS company,
SELECT row_to_json(company_data) (SELECT json_agg(row_to_json(employer_data))
FROM company_data FROM employer_data) AS employers,
) AS company, (SELECT json_agg(row_to_json(request_data))
( FROM request_data) AS requests;
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) { if (!data) {
@ -726,28 +711,20 @@ export async function getPosting(id: number): Promise<Posting> {
export async function getPostingWithCompanyUser(id: number): Promise<Posting> { export async function getPostingWithCompanyUser(id: number): Promise<Posting> {
const data = await sql` const data = await sql`
WITH company_data AS ( WITH company_data AS (SELECT id,
SELECT
id,
name, name,
description, description,
website, website,
created_at AS "createdAt" created_at AS "createdAt"
FROM companies FROM companies
WHERE id = (SELECT company_id FROM postings WHERE id = ${id}) WHERE id = (SELECT company_id FROM postings WHERE id = ${id})),
), user_data AS (SELECT username,
user_data AS (
SELECT
username,
email, email,
phone, phone,
full_name AS "fullName" full_name AS "fullName"
FROM users FROM users
WHERE "company_id" = (SELECT company_id FROM postings WHERE id = ${id}) WHERE "company_id" = (SELECT company_id FROM postings WHERE id = ${id})),
), posting_data AS (SELECT id,
posting_data AS (
SELECT
id,
title, title,
description, description,
employer_id AS "employerId", employer_id AS "employerId",
@ -760,17 +737,11 @@ export async function getPostingWithCompanyUser(id: number): Promise<Posting> {
updated_at AT TIME ZONE 'UTC' AS "updatedAt", updated_at AT TIME ZONE 'UTC' AS "updatedAt",
flyer_link AS "flyerLink" flyer_link AS "flyerLink"
FROM postings FROM postings
WHERE id = ${id} WHERE id = ${id})
) SELECT (SELECT row_to_json(company_data)
SELECT FROM company_data) AS company,
( (SELECT row_to_json(user_data)
SELECT row_to_json(company_data) FROM user_data) AS user,
FROM company_data
) AS company,
(
SELECT row_to_json(user_data)
FROM user_data
) AS user,
( (
SELECT row_to_json(posting_data) SELECT row_to_json(posting_data)
FROM posting_data FROM posting_data
@ -797,8 +768,7 @@ export async function getPostingWithCompanyUser(id: number): Promise<Posting> {
export async function createApplication(application: Application): Promise<number> { export async function createApplication(application: Application): Promise<number> {
const response = await sql` const response = await sql`
INSERT INTO applications (posting_id, user_id, candidate_statement, created_at) INSERT INTO applications (posting_id, user_id, candidate_statement, created_at)
VALUES (${application.postingId}, ${application.userId}, ${application.candidateStatement}, NOW()) VALUES (${application.postingId}, ${application.userId}, ${application.candidateStatement}, NOW()) RETURNING id;
RETURNING id;
`; `;
sendEmployerNotificationEmail(application.postingId).catch((err) => { sendEmployerNotificationEmail(application.postingId).catch((err) => {
@ -808,8 +778,9 @@ export async function createApplication(application: Application): Promise<numbe
} }
export async function deleteApplication(id: number): Promise<void> { export async function deleteApplication(id: number): Promise<void> {
const response = await sql` await sql`
DELETE FROM applications DELETE
FROM applications
WHERE id = ${id}; WHERE id = ${id};
`; `;
} }
@ -819,16 +790,17 @@ export async function deleteApplicationWithUser(
userId: number userId: number
): Promise<void> { ): Promise<void> {
console.log(applicationId, userId); console.log(applicationId, userId);
const response = await sql` await sql`
DELETE FROM applications DELETE
WHERE id = ${applicationId} AND user_id = ${userId}; FROM applications
WHERE id = ${applicationId}
AND user_id = ${userId};
`; `;
} }
export async function getApplications(postingId: number): Promise<Application[]> { export async function getApplications(postingId: number): Promise<Application[]> {
const data = await sql` const data = await sql`
SELECT SELECT a.id,
a.id,
a.candidate_statement AS "candidateStatement", a.candidate_statement AS "candidateStatement",
a.created_at AS "createdAt", a.created_at AS "createdAt",
u.id AS "userId", u.id AS "userId",
@ -876,18 +848,13 @@ export async function getNotificationInfo(
postingId: number postingId: number
): Promise<{ title: string; emails: string[] }> { ): Promise<{ title: string; emails: string[] }> {
const data = await sql` const data = await sql`
WITH posting_data AS ( WITH posting_data AS (SELECT title, company_id
SELECT title, company_id
FROM postings FROM postings
WHERE id = ${postingId} WHERE id = ${postingId}),
), user_emails AS (SELECT email
user_emails AS (
SELECT email
FROM users FROM users
WHERE company_id = (SELECT company_id FROM posting_data) WHERE company_id = (SELECT company_id FROM posting_data))
) SELECT (SELECT title FROM posting_data) AS title,
SELECT
(SELECT title FROM posting_data) AS title,
(SELECT json_agg(email) FROM user_emails) AS emails; (SELECT json_agg(email) FROM user_emails) AS emails;
`; `;

View File

@ -2,11 +2,11 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
</script> </script>
<div style="padding-top: 32px" class="text-center"> <div class="text-center" style="padding-top: 32px">
<h1 class="text-9xl font-bold"> <h1 class="text-9xl font-bold">
{$page.status} {$page.status}
</h1> </h1>
<h1>Thats an error</h1> <h1>That's an error</h1>
{#if $page.status === 404} {#if $page.status === 404}
<p>We cant seem to find the page you are looking for.</p> <p>We cant seem to find the page you are looking for.</p>
<p>The address may be mistyped, or the page may have moved or been deleted.</p> <p>The address may be mistyped, or the page may have moved or been deleted.</p>

View File

@ -35,19 +35,19 @@
</script> </script>
<link <link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..40,400,0,0&display=block&icon_names=account_circle,arrow_drop_down,arrow_drop_up,calendar_today,call,check,close,cloud_upload,dark_mode,delete,description,edit,group,info,light_mode,login,mail,open_in_new,person,search,sell,store,upload,visibility,visibility_off,work" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..40,400,0,0&display=block&icon_names=account_circle,arrow_drop_down,arrow_drop_up,calendar_today,call,check,close,cloud_upload,dark_mode,delete,description,edit,group,info,light_mode,login,mail,open_in_new,person,search,sell,store,upload,visibility,visibility_off,work"
rel="stylesheet"
/> />
<div class="flex min-h-screen flex-col"> <div class="flex min-h-screen flex-col">
<div class="bottom-border bg-color sticky top-0 z-50 flex h-14 justify-between p-3 align-middle"> <div class="bottom-border bg-color sticky top-0 z-50 flex h-14 justify-between p-3 align-middle">
<nav class="pt-1"> <nav class="pt-1">
<a href="/" class="hover-bg-color mr-1 rounded-md px-2 pb-2 pt-1.5"> <a class="hover-bg-color mr-1 rounded-md px-2 pb-2 pt-1.5" href="/">
<img <img
class="inline-block"
src="/mdevtriangle.svg"
alt="MarinoDev Logo" alt="MarinoDev Logo"
class="inline-block"
height="24" height="24"
src="/mdevtriangle.svg"
width="24" width="24"
/> />
<div class="inline-block text-sm">Home</div> <div class="inline-block text-sm">Home</div>
@ -76,7 +76,7 @@
{/if} {/if}
</nav> </nav>
<div> <div>
<button onclick={toggleTheme} class=""> <button class="" onclick={toggleTheme}>
<span class="material-symbols-outlined hover-bg-color rounded-full p-1"> <span class="material-symbols-outlined hover-bg-color rounded-full p-1">
{currentTheme === 'light' ? 'light_mode' : 'dark_mode'} {currentTheme === 'light' ? 'light_mode' : 'dark_mode'}
</span> </span>
@ -103,13 +103,13 @@
<div class="inline-block text-left align-top"> <div class="inline-block text-left align-top">
<div class="inline-block pr-3"> <div class="inline-block pr-3">
<p class="font-semibold">Drake Marino:</p> <p class="font-semibold">Drake Marino:</p>
<a href="mailto:drake@marinodev.com" class="hyperlink-color hyperlink-underline" <a class="hyperlink-color hyperlink-underline" href="mailto:drake@marinodev.com"
>drake@marinodev.com</a >drake@marinodev.com</a
> >
</div> </div>
<div class="inline-block"> <div class="inline-block">
<p class="font-semibold">Chetan Malkan:</p> <p class="font-semibold">Chetan Malkan:</p>
<a href="mailto:chetan@marinodev.com" class="hyperlink-color hyperlink-underline" <a class="hyperlink-color hyperlink-underline" href="mailto:chetan@marinodev.com"
>chetan@marinodev.com</a >chetan@marinodev.com</a
> >
</div> </div>
@ -121,8 +121,9 @@
<div class="inline-block text-right align-top"> <div class="inline-block text-right align-top">
<div class="font-semibold">Source Code:</div> <div class="font-semibold">Source Code:</div>
<a <a
class="hyperlink-color hyperlink-underline"
href="https://git.marinodev.com/MarinoDev/FBLA25" href="https://git.marinodev.com/MarinoDev/FBLA25"
class="hyperlink-color hyperlink-underline">https://git.marinodev.com/MarinoDev/FBLA25</a >https://git.marinodev.com/MarinoDev/FBLA25</a
> >
</div> </div>
</footer> </footer>

View File

@ -10,7 +10,7 @@
<div class="base-container"> <div class="base-container">
<div class="content pt-16"> <div class="content pt-16">
<img class="mx-auto" src="/mdevtriangle.svg" alt="MarinoDev Logo" height="256" width="256" /> <img alt="MarinoDev Logo" class="mx-auto" height="256" src="/mdevtriangle.svg" width="256" />
<h1 class="text-center text-8xl font-semibold">CareerConnect</h1> <h1 class="text-center text-8xl font-semibold">CareerConnect</h1>
<h2 class="text-center text-3xl italic">Connecting Students with Opportunities</h2> <h2 class="text-center text-3xl italic">Connecting Students with Opportunities</h2>
<h2 class="pt-8 text-center">We are a platform that connects students with employers.</h2> <h2 class="pt-8 text-center">We are a platform that connects students with employers.</h2>

View File

@ -1,5 +1,5 @@
<h1>About</h1> <h1>About</h1>
<p> <p>
This is my submission for the 2025 FBLA Website Coding & Development event. It was built using This is my submission for the 2025 FBLA Website Coding & Development event. It was built using
<a href="https://svelte.dev/docs/kit/introduction" class="text-blue-600">SvelteKit</a>. <a class="text-blue-600" href="https://svelte.dev/docs/kit/introduction">SvelteKit</a>.
</p> </p>

View File

@ -1,7 +1,7 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { deleteApplicationWithUser, getUserWithCompanyAndApplications } from '$lib/db/index.server'; import { deleteApplicationWithUser, getUserWithCompanyAndApplications } from '$lib/db/index.server';
import { getUserId } from '$lib/index.server'; import { getUserId } from '$lib/index.server';
import { type Actions, fail, json, redirect } from '@sveltejs/kit'; import { type Actions, fail } from '@sveltejs/kit';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';

View File

@ -3,7 +3,7 @@
import type { PageProps } from './$types'; import type { PageProps } from './$types';
let applicationToDelete: number = $state(0); let applicationToDelete: number = $state(0);
let { data, form }: PageProps = $props(); let { data }: PageProps = $props();
let resumeState = $state(data.resumeExists); let resumeState = $state(data.resumeExists);
onMount(() => { onMount(() => {
@ -65,13 +65,10 @@
}); });
function dropHandler(event: DragEvent) { function dropHandler(event: DragEvent) {
// Prevent default behavior (Prevent file from being opened)
event.preventDefault(); event.preventDefault();
if (event.dataTransfer?.items) { if (event.dataTransfer?.items) {
const items = 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') { if (items[0].kind === 'file') {
const upload = items[0].getAsFile(); const upload = items[0].getAsFile();
if (upload?.type !== 'application/pdf') { if (upload?.type !== 'application/pdf') {
@ -88,7 +85,7 @@
} }
async function handleSubmit(event: SubmitEvent) { async function handleSubmit(event: SubmitEvent) {
event.preventDefault(); // Prevent default form submission event.preventDefault();
if (!file) { if (!file) {
alert('Please select a file first'); alert('Please select a file first');
@ -96,7 +93,7 @@
} }
const formData = new FormData(); const formData = new FormData();
formData.append('resume', file); // Manually append the file formData.append('resume', file);
try { try {
const response = await fetch('?/uploadResume', { const response = await fetch('?/uploadResume', {
@ -123,14 +120,14 @@
<div class="elevated separator-borders m-2 inline-block h-min min-w-max rounded align-top"> <div class="elevated separator-borders m-2 inline-block h-min min-w-max rounded align-top">
<div class="inline-block p-4"> <div class="inline-block p-4">
<img <img
id="avatar" alt="User avatar"
class="mb-2 inline-block rounded-lg" class="mb-2 inline-block rounded-lg"
height="240"
id="avatar"
onerror={avatarFallback}
src="/uploads/avatars/{data.user.id src="/uploads/avatars/{data.user.id
? data.user.id ? data.user.id
: 'default'}.svg?timestamp=${Date.now()}" : 'default'}.svg?timestamp=${Date.now()}"
onerror={avatarFallback}
alt="User avatar"
height="240"
width="240" width="240"
/> />
{#if data.user.fullName} {#if data.user.fullName}
@ -169,8 +166,8 @@
>Edit account</a >Edit account</a
> >
<button class="danger-border-color m-2 rounded-md border px-2.5 py-1" onclick={signOut} <button class="danger-border-color m-2 rounded-md border px-2.5 py-1" onclick={signOut}
>Sign out</button >Sign out
> </button>
</div> </div>
</div> </div>
<div class="p-3"> <div class="p-3">
@ -217,17 +214,17 @@
{#if resumeState} {#if resumeState}
<a class="pb-2" href="/uploads/resumes/{data.user.id}.pdf" target="_blank"> <a class="pb-2" href="/uploads/resumes/{data.user.id}.pdf" target="_blank">
<button class="dull-primary-bg-color rounded-md px-2.5 py-1" <button class="dull-primary-bg-color rounded-md px-2.5 py-1"
><span class="material-symbols-outlined align-middle">open_in_new</span> View résumé</button ><span class="material-symbols-outlined align-middle">open_in_new</span> View résumé
> </button>
</a> </a>
<button class="mb-2 ml-1 rounded-md border px-2.5 py-1" onclick={openUpload} <button class="mb-2 ml-1 rounded-md border px-2.5 py-1" onclick={openUpload}
><span class="material-symbols-outlined align-middle">upload</span> Upload new version</button ><span class="material-symbols-outlined align-middle">upload</span> Upload new version
> </button>
{:else} {:else}
<div class="">No résumé submitted.</div> <div class="">No résumé submitted.</div>
<button class="dull-primary-bg-color mb-1 rounded-md px-2.5 py-1" onclick={openUpload} <button class="dull-primary-bg-color mb-1 rounded-md px-2.5 py-1" onclick={openUpload}
><span class="material-symbols-outlined align-middle">upload</span> Upload</button ><span class="material-symbols-outlined align-middle">upload</span> Upload
> </button>
{/if} {/if}
</div> </div>
<div class="top-border pt-2 font-semibold"> <div class="top-border pt-2 font-semibold">
@ -258,8 +255,9 @@
onclick={() => { onclick={() => {
applicationToDelete = application.id; applicationToDelete = application.id;
openConfirm(); openConfirm();
}}>delete</button }}
> >delete
</button>
</div> </div>
{/each} {/each}
</div> </div>
@ -267,7 +265,7 @@
</div> </div>
</div> </div>
</div> </div>
<form id="deleteConfirmModal" method="POST" class="modal"> <form class="modal" id="deleteConfirmModal" method="POST">
<div class="modal-content"> <div class="modal-content">
<div class="mb-2 inline-flex w-full justify-between"> <div class="mb-2 inline-flex w-full justify-between">
<h2 class="font-semibold">Are you sure?</h2> <h2 class="font-semibold">Are you sure?</h2>
@ -278,18 +276,20 @@
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
<button <button
class="danger-bg-color rounded px-2 py-1" class="danger-bg-color rounded px-2 py-1"
formaction="?/deleteApplication&id={applicationToDelete}"
type="submit" type="submit"
formaction="?/deleteApplication&id={applicationToDelete}">Delete application</button >Delete application
> </button>
<button <button
class="separator-borders bg-color rounded px-2 py-1" class="separator-borders bg-color rounded px-2 py-1"
onclick={closeConfirm}
type="button" type="button"
onclick={closeConfirm}>Cancel</button >Cancel
> </button>
</div> </div>
</div> </div>
</form> </form>
<form id="uploadModal" method="POST" class="modal" onsubmit={handleSubmit}> <form class="modal" id="uploadModal" method="POST" onsubmit={handleSubmit}>
<div class="modal-content"> <div class="modal-content">
<div class="mb-2 inline-flex w-full justify-between"> <div class="mb-2 inline-flex w-full justify-between">
<h2 class="font-semibold">Résumé Upload</h2> <h2 class="font-semibold">Résumé Upload</h2>
@ -297,22 +297,19 @@
</div> </div>
<div> <div>
<div <div
role="region"
class="dull-primary-border-color rounded-lg border-2 border-dashed" class="dull-primary-border-color rounded-lg border-2 border-dashed"
ondrop={dropHandler}
ondragover={dragOverHandler} ondragover={dragOverHandler}
ondrop={dropHandler}
role="region"
> >
<label for="resume" class="cursor-pointer p-4"> <label class="cursor-pointer p-4" for="resume">
<div class="text-center"> <div class="text-center">
<span class="material-symbols-outlined icon-48">cloud_upload</span> <span class="material-symbols-outlined icon-48">cloud_upload</span>
<h3>Drag & drop your résumé here</h3> <h3>Drag & drop your résumé here</h3>
<p class=""> <p class="">
or <span class="hyperlink-color hyperlink-underline">click here to browse.</span> or <span class="hyperlink-color hyperlink-underline">click here to browse.</span>
</p> </p>
<!-- <label class="dull-primary-bg-color cursor-pointer rounded px-2 py-1" for="resume"--> <input accept=".pdf" bind:files class="hidden" id="resume" type="file" />
<!-- >Select a file</label-->
<!-- >-->
<input bind:files type="file" id="resume" accept=".pdf" class="hidden" />
</div> </div>
</label> </label>
</div> </div>
@ -322,9 +319,10 @@
<button class="dull-primary-bg-color rounded px-2 py-1" type="submit">Submit</button> <button class="dull-primary-bg-color rounded px-2 py-1" type="submit">Submit</button>
<button <button
class="separator-borders bg-color rounded px-2 py-1" class="separator-borders bg-color rounded px-2 py-1"
onclick={closeUpload}
type="button" type="button"
onclick={closeUpload}>Cancel</button >Cancel
> </button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,10 +1,8 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { deleteUser, getUser, getUserWithCompany, updateUser } from '$lib/db/index.server'; import { deleteUser, getUserWithCompany, updateUser } from '$lib/db/index.server';
import { type Actions, fail, redirect } from '@sveltejs/kit'; import { type Actions, fail, redirect } from '@sveltejs/kit';
import { PERMISSIONS } from '$lib/consts';
import { getUserId } from '$lib/index.server'; import { getUserId } from '$lib/index.server';
import type { User } from '$lib/types'; import type { User } from '$lib/types';
import path from 'path';
export const load: PageServerLoad = async ({ cookies }) => { export const load: PageServerLoad = async ({ cookies }) => {
const id = getUserId(cookies); const id = getUserId(cookies);

View File

@ -3,6 +3,7 @@
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
import type { PageProps } from './$types'; import type { PageProps } from './$types';
import { telFormatter } from '$lib/shared.svelte'; import { telFormatter } from '$lib/shared.svelte';
let permsAccordions: boolean[] = [false, false, false]; let permsAccordions: boolean[] = [false, false, false];
onMount(() => { onMount(() => {
@ -91,63 +92,63 @@
<div class="bottom-border flex place-content-between"> <div class="bottom-border flex place-content-between">
<div class="p-3 font-semibold">Edit Account</div> <div class="p-3 font-semibold">Edit Account</div>
</div> </div>
<form method="POST" class="px-4" autocomplete="off" use:enhance> <form autocomplete="off" class="px-4" method="POST" use:enhance>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Username <span class="danger-color">*</span> Username <span class="danger-color">*</span>
<input <input
type="text"
name="username"
id="username"
value={data.user?.username}
placeholder="Username"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="username"
name="username"
placeholder="Username"
type="text"
value={data.user?.username}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Full name <span class="danger-color">*</span> Full name <span class="danger-color">*</span>
<input <input
type="text"
name="fullName"
id="fullName"
value={data.user?.fullName}
placeholder="Full name"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="fullName"
name="fullName"
placeholder="Full name"
required required
type="text"
value={data.user?.fullName}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Email <span class="danger-color">*</span> Email <span class="danger-color">*</span>
<input <input
type="email"
name="email"
id="email"
value={data.user?.email}
placeholder="Email"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="email"
name="email"
placeholder="Email"
required required
type="email"
value={data.user?.email}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Phone (optional) Phone (optional)
<input <input
type="tel"
name="phone"
id="phone"
value={data.user?.phone}
placeholder="Phone"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="phone"
name="phone"
pattern="([0-9]\{3}) [0-9]\{3}-[0-9]\{3}" pattern="([0-9]\{3}) [0-9]\{3}-[0-9]\{3}"
placeholder="Phone"
type="tel"
value={data.user?.phone}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Company code (optional) Company code (optional)
<input <input
type="text"
name="companyCode"
id="companyCode"
placeholder="Company code"
value={data.user?.companyCode}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="companyCode"
name="companyCode"
placeholder="Company code"
type="text"
value={data.user?.companyCode}
/> />
</div> </div>
<p class="low-emphasis-text pb-4"> <p class="low-emphasis-text pb-4">
@ -161,43 +162,46 @@
<div class="flex justify-between"> <div class="flex justify-between">
<button <button
class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1" class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1"
formaction="?/submit"
type="submit" type="submit"
formaction="?/submit">Update account</button >Update account
> </button>
<button <button
class="danger-bg-color mb-4 mt-2 rounded px-2 py-1" class="danger-bg-color mb-4 mt-2 rounded px-2 py-1"
onclick={openConfirm}
type="button" type="button"
onclick={openConfirm}>Delete account</button >Delete account
> </button>
</div> </div>
<div id="deleteConfirmModal" class="modal"> <div class="modal" id="deleteConfirmModal">
<div class="modal-content"> <div class="modal-content">
<div class="mb-2 inline-flex w-full justify-between"> <div class="mb-2 inline-flex w-full justify-between">
<h2 class="font-semibold">Are you sure?</h2> <h2 class="font-semibold">Are you sure?</h2>
<button class="material-symbols-outlined" onclick={closeConfirm} type="button" <button class="material-symbols-outlined" onclick={closeConfirm} type="button"
>close</button >close
> </button>
</div> </div>
<p>This will permanently delete your account. This action cannot be undone.</p> <p>This will permanently delete your account. This action cannot be undone.</p>
<p>Please type "I understand" into the box below to confirm</p> <p>Please type "I understand" into the box below to confirm</p>
<input <input
type="text"
name="confirm"
id="confirm"
placeholder="I understand"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="confirm"
name="confirm"
pattern="I understand" pattern="I understand"
placeholder="I understand"
required required
type="text"
/> />
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
<button class="danger-bg-color rounded px-2 py-1" type="submit" formaction="?/delete" <button class="danger-bg-color rounded px-2 py-1" formaction="?/delete" type="submit"
>Delete account</button >Delete account
> </button>
<button <button
class="separator-borders bg-color rounded px-2 py-1" class="separator-borders bg-color rounded px-2 py-1"
onclick={closeConfirm}
type="button" type="button"
onclick={closeConfirm}>Cancel</button >Cancel
> </button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { getCompanies, getUsers } from '$lib/db/index.server'; import { getCompanies } from '$lib/db/index.server';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { getUserPerms } from '$lib/index.server'; import { getUserPerms } from '$lib/index.server';

View File

@ -21,11 +21,11 @@
<form action="" class="flex p-4"> <form action="" class="flex p-4">
<div class="search-bar"> <div class="search-bar">
<input <input
type="search"
name="searchCompanies"
id="searchCompanies"
placeholder="Search Companies"
class="search-cancel" class="search-cancel"
id="searchCompanies"
name="searchCompanies"
placeholder="Search Companies"
type="search"
/> />
<button><span class="material-symbols-outlined">search</span></button> <button><span class="material-symbols-outlined">search</span></button>
</div> </div>

View File

@ -1,5 +1,5 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { getPostings, getUsers } from '$lib/db/index.server'; import { getPostings } from '$lib/db/index.server';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { getUserPerms } from '$lib/index.server'; import { getUserPerms } from '$lib/index.server';

View File

@ -23,11 +23,11 @@
<form action="" class="flex p-4"> <form action="" class="flex p-4">
<div class="search-bar"> <div class="search-bar">
<input <input
type="search"
name="searchPostings"
id="searchPostings"
placeholder="Search Postings"
class="search-cancel" class="search-cancel"
id="searchPostings"
name="searchPostings"
placeholder="Search Postings"
type="search"
/> />
<button><span class="material-symbols-outlined">search</span></button> <button><span class="material-symbols-outlined">search</span></button>
</div> </div>

View File

@ -21,11 +21,11 @@
<form action="" class="flex p-4"> <form action="" class="flex p-4">
<div class="search-bar"> <div class="search-bar">
<input <input
type="search"
name="searchTags"
id="searchTags"
placeholder="Search Tags"
class="search-cancel" class="search-cancel"
id="searchTags"
name="searchTags"
placeholder="Search Tags"
type="search"
/> />
<button><span class="material-symbols-outlined">search</span></button> <button><span class="material-symbols-outlined">search</span></button>
</div> </div>

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { userPerms, employerPerms, adminPerms } from '$lib/shared.svelte'; import { userPerms, employerPerms, adminPerms } from '$lib/shared.svelte';
let { data } = $props(); let { data } = $props();
const dateFormatOptions: Intl.DateTimeFormatOptions = { const dateFormatOptions: Intl.DateTimeFormatOptions = {
@ -29,11 +30,11 @@
<form action="" class="flex p-4"> <form action="" class="flex p-4">
<div class="search-bar"> <div class="search-bar">
<input <input
type="search"
name="searchUsers"
id="searchUsers"
placeholder="Search Users"
class="search-cancel" class="search-cancel"
id="searchUsers"
name="searchUsers"
placeholder="Search Users"
type="search"
/> />
<button><span class="material-symbols-outlined">search</span></button> <button><span class="material-symbols-outlined">search</span></button>
</div> </div>

View File

@ -27,11 +27,11 @@
<div class="elevated separator-borders m-2 inline-block h-min min-w-max rounded align-top"> <div class="elevated separator-borders m-2 inline-block h-min min-w-max rounded align-top">
<div class="inline-block p-4"> <div class="inline-block p-4">
<img <img
class="mb-2 inline-block rounded-lg"
src="/uploads/avatars/{data.user.id}.svg?timestamp=${Date.now()}"
alt="User avatar" alt="User avatar"
onerror={avatarFallback} class="mb-2 inline-block rounded-lg"
height="240" height="240"
onerror={avatarFallback}
src="/uploads/avatars/{data.user.id}.svg?timestamp=${Date.now()}"
width="240" width="240"
/> />
{#if data.user.fullName} {#if data.user.fullName}

View File

@ -4,6 +4,7 @@
import type { PageProps } from './$types'; import type { PageProps } from './$types';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import { userPerms, employerPerms, adminPerms, telFormatter } from '$lib/shared.svelte'; import { userPerms, employerPerms, adminPerms, telFormatter } from '$lib/shared.svelte';
let permsAccordions: boolean[] = [false, false, false]; let permsAccordions: boolean[] = [false, false, false];
let passwordVisible = $state(false); let passwordVisible = $state(false);
@ -104,32 +105,32 @@
Edit User {data.user.username}{data.user.fullName ? ` (${data.user.fullName})` : ''} Edit User {data.user.username}{data.user.fullName ? ` (${data.user.fullName})` : ''}
</div> </div>
</div> </div>
<form method="POST" class="px-4" autocomplete="off" use:enhance> <form autocomplete="off" class="px-4" method="POST" use:enhance>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Username <span class="text-red-500">*</span> Username <span class="text-red-500">*</span>
<input <input
type="text"
name="username"
id="username"
value={data.user?.username}
placeholder="Username"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="username"
name="username"
placeholder="Username"
required required
type="text"
value={data.user?.username}
/> />
</div> </div>
<div class="relative pt-4 text-sm font-semibold"> <div class="relative pt-4 text-sm font-semibold">
New password (optional) New password (optional)
<input <input
type="password"
name="password"
id="password"
placeholder="New password"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="password"
name="password"
placeholder="New password"
type="password"
/> />
<button <button
type="button"
onclick={showPassword}
class="absolute right-2.5 -translate-y-1/2 transform pt-12" class="absolute right-2.5 -translate-y-1/2 transform pt-12"
onclick={showPassword}
type="button"
> >
<span class="material-symbols-outlined" <span class="material-symbols-outlined"
>{passwordVisible ? 'visibility' : 'visibility_off'}</span >{passwordVisible ? 'visibility' : 'visibility_off'}</span
@ -139,48 +140,48 @@
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Full name <span class="danger-color">*</span> Full name <span class="danger-color">*</span>
<input <input
type="text"
name="fullName"
id="fullName"
value={data.user?.fullName}
placeholder="Full name"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="fullName"
name="fullName"
placeholder="Full name"
required required
type="text"
value={data.user?.fullName}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Email <span class="danger-color">*</span> Email <span class="danger-color">*</span>
<input <input
type="email"
name="email"
id="email"
value={data.user?.email}
placeholder="Email"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="email"
name="email"
placeholder="Email"
required required
type="email"
value={data.user?.email}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Phone (optional) Phone (optional)
<input <input
type="tel"
name="phone"
id="phone"
value={data.user?.phone}
placeholder="Phone"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="phone"
name="phone"
pattern="([0-9]\{3}) [0-9]\{3}-[0-9]\{3}" pattern="([0-9]\{3}) [0-9]\{3}-[0-9]\{3}"
placeholder="Phone"
type="tel"
value={data.user?.phone}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Company code (optional) Company code (optional)
<input <input
type="text"
name="companyCode"
id="companyCode"
placeholder="Company code"
value={data.user?.companyCode}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="companyCode"
name="companyCode"
placeholder="Company code"
type="text"
value={data.user?.companyCode}
/> />
</div> </div>
<p class="low-emphasis-text"> <p class="low-emphasis-text">
@ -195,12 +196,12 @@
<span class="flex place-items-center"> <span class="flex place-items-center">
<span class="ml-1 mr-3" <span class="ml-1 mr-3"
><input ><input
type="checkbox"
name="userPerms"
id="userPerms"
class="select-all"
checked={(perms & userPerms) === userPerms} checked={(perms & userPerms) === userPerms}
class="select-all"
id="userPerms"
indeterminate={(perms & userPerms) !== userPerms && (perms & userPerms) > 0} indeterminate={(perms & userPerms) !== userPerms && (perms & userPerms) > 0}
name="userPerms"
type="checkbox"
/></span /></span
>User Permissions >User Permissions
</span> </span>
@ -211,25 +212,25 @@
<div class="panel hidden p-2"> <div class="panel hidden p-2">
<div> <div>
<div class="mb-1"> <div class="mb-1">
<label for="view" class="flex place-items-center"> <label class="flex place-items-center" for="view">
<input <input
type="checkbox"
name="view"
id="view"
class="permCheckbox mx-1"
checked={(perms & PERMISSIONS.VIEW) > 0} checked={(perms & PERMISSIONS.VIEW) > 0}
class="permCheckbox mx-1"
id="view"
name="view"
type="checkbox"
/> />
<span class="ml-2">View access</span></label <span class="ml-2">View access</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="apply" class="flex place-items-center"> <label class="flex place-items-center" for="apply">
<input <input
type="checkbox"
name="apply"
id="apply"
class="permCheckbox mx-1"
checked={(perms & PERMISSIONS.APPLY_FOR_JOBS) > 0} checked={(perms & PERMISSIONS.APPLY_FOR_JOBS) > 0}
class="permCheckbox mx-1"
id="apply"
name="apply"
type="checkbox"
/> />
<span class="ml-2">Apply for jobs</span></label <span class="ml-2">Apply for jobs</span></label
> >
@ -245,13 +246,13 @@
<span class="flex place-items-center"> <span class="flex place-items-center">
<span class="ml-1 mr-3" <span class="ml-1 mr-3"
><input ><input
type="checkbox"
name="companyPerms"
id="companyPerms"
class="select-all"
checked={(perms & employerPerms) === employerPerms} checked={(perms & employerPerms) === employerPerms}
class="select-all"
id="companyPerms"
indeterminate={(perms & employerPerms) !== employerPerms && indeterminate={(perms & employerPerms) !== employerPerms &&
(perms & employerPerms) > 0} (perms & employerPerms) > 0}
name="companyPerms"
type="checkbox"
/></span /></span
>Company Permissions >Company Permissions
</span> </span>
@ -262,25 +263,25 @@
<div class="panel hidden p-2"> <div class="panel hidden p-2">
<div> <div>
<div class="mb-1"> <div class="mb-1">
<label for="submitPostings" class="flex place-items-center"> <label class="flex place-items-center" for="submitPostings">
<input <input
type="checkbox"
name="submitPostings"
id="submitPostings"
checked={(perms & PERMISSIONS.SUBMIT_POSTINGS) >= 0} checked={(perms & PERMISSIONS.SUBMIT_POSTINGS) >= 0}
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="submitPostings"
name="submitPostings"
type="checkbox"
/> />
<span class="ml-2">Submit postings</span></label <span class="ml-2">Submit postings</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="manageEmployers" class="flex place-items-center"> <label class="flex place-items-center" for="manageEmployers">
<input <input
type="checkbox"
name="manageEmployers"
id="manageEmployers"
checked={(perms & PERMISSIONS.MANAGE_EMPLOYERS) > 0} checked={(perms & PERMISSIONS.MANAGE_EMPLOYERS) > 0}
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="manageEmployers"
name="manageEmployers"
type="checkbox"
/> />
<span class="ml-2">Manage employers (within their company)</span></label <span class="ml-2">Manage employers (within their company)</span></label
> >
@ -296,12 +297,12 @@
<span class="flex place-items-center"> <span class="flex place-items-center">
<span class="ml-1 mr-3" <span class="ml-1 mr-3"
><input ><input
type="checkbox"
name="adminPerms"
id="adminPerms"
class="select-all"
checked={(perms & adminPerms) === adminPerms} checked={(perms & adminPerms) === adminPerms}
class="select-all"
id="adminPerms"
indeterminate={(perms & adminPerms) !== adminPerms && (perms & adminPerms) > 0} indeterminate={(perms & adminPerms) !== adminPerms && (perms & adminPerms) > 0}
name="adminPerms"
type="checkbox"
/></span /></span
>Admin Permissions >Admin Permissions
</span> </span>
@ -312,49 +313,49 @@
<div class="panel hidden p-2"> <div class="panel hidden p-2">
<div> <div>
<div class="mb-1"> <div class="mb-1">
<label for="manageTags" class="flex place-items-center"> <label class="flex place-items-center" for="manageTags">
<input <input
type="checkbox"
name="manageTags"
id="manageTags"
checked={(perms & PERMISSIONS.MANAGE_TAGS) > 0} checked={(perms & PERMISSIONS.MANAGE_TAGS) > 0}
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="manageTags"
name="manageTags"
type="checkbox"
/> />
<span class="ml-2">Manage tags</span></label <span class="ml-2">Manage tags</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="managePostings" class="flex place-items-center"> <label class="flex place-items-center" for="managePostings">
<input <input
type="checkbox"
name="managePostings"
id="managePostings"
checked={(perms & PERMISSIONS.MANAGE_POSTINGS) > 0} checked={(perms & PERMISSIONS.MANAGE_POSTINGS) > 0}
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="managePostings"
name="managePostings"
type="checkbox"
/> />
<span class="ml-2">Manage postings</span></label <span class="ml-2">Manage postings</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="manageUsers" class="flex place-items-center"> <label class="flex place-items-center" for="manageUsers">
<input <input
type="checkbox"
name="manageUsers"
id="manageUsers"
checked={(perms & PERMISSIONS.MANAGE_USERS) > 0} checked={(perms & PERMISSIONS.MANAGE_USERS) > 0}
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="manageUsers"
name="manageUsers"
type="checkbox"
/> />
<span class="ml-2">Manage users</span></label <span class="ml-2">Manage users</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="manageCompanies" class="flex place-items-center"> <label class="flex place-items-center" for="manageCompanies">
<input <input
type="checkbox"
name="manageCompanies"
id="manageCompanies"
checked={(perms & PERMISSIONS.MANAGE_COMPANIES) > 0} checked={(perms & PERMISSIONS.MANAGE_COMPANIES) > 0}
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="manageCompanies"
name="manageCompanies"
type="checkbox"
/> />
<span class="ml-2">Manage companies</span></label <span class="ml-2">Manage companies</span></label
> >
@ -362,13 +363,13 @@
</div> </div>
</div> </div>
</div> </div>
<label for="accountActive" class="flex place-items-center p-2"> <label class="flex place-items-center p-2" for="accountActive">
<input <input
type="checkbox"
name="accountActive"
id="accountActive"
checked={data.user?.active} checked={data.user?.active}
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="accountActive"
name="accountActive"
type="checkbox"
/> />
<span class="ml-2">Account active</span></label <span class="ml-2">Account active</span></label
> >
@ -379,23 +380,25 @@
<div class="flex justify-between"> <div class="flex justify-between">
<button <button
class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1" class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1"
formaction="?/submit"
type="submit" type="submit"
formaction="?/submit">Save user</button >Save user
> </button>
<button <button
class="danger-bg-color mb-4 mt-2 rounded px-2 py-1" class="danger-bg-color mb-4 mt-2 rounded px-2 py-1"
onclick={openConfirm}
type="button" type="button"
onclick={openConfirm}>Delete user</button >Delete user
> </button>
</div> </div>
</form> </form>
<form id="deleteConfirmModal" class="modal" method="POST" use:enhance> <form class="modal" id="deleteConfirmModal" method="POST" use:enhance>
<div class="modal-content"> <div class="modal-content">
<div class="mb-2 inline-flex w-full justify-between"> <div class="mb-2 inline-flex w-full justify-between">
<h2 class="font-semibold">Are you sure?</h2> <h2 class="font-semibold">Are you sure?</h2>
<button class="material-symbols-outlined" onclick={closeConfirm} type="button" <button class="material-symbols-outlined" onclick={closeConfirm} type="button"
>close</button >close
> </button>
</div> </div>
<p> <p>
This will permanently delete user <span class="font-semibold">{data.user?.username}.</span This will permanently delete user <span class="font-semibold">{data.user?.username}.</span
@ -403,23 +406,24 @@
</p> </p>
<p>Please type "I understand" into the box below to confirm</p> <p>Please type "I understand" into the box below to confirm</p>
<input <input
type="text"
name="confirm"
id="confirm"
placeholder="I understand"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="confirm"
name="confirm"
pattern="I understand" pattern="I understand"
placeholder="I understand"
required required
type="text"
/> />
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
<button class="danger-bg-color rounded px-2 py-1" type="submit" formaction="?/delete" <button class="danger-bg-color rounded px-2 py-1" formaction="?/delete" type="submit"
>Delete user</button >Delete user
> </button>
<button <button
class="separator-borders bg-color rounded px-2 py-1" class="separator-borders bg-color rounded px-2 py-1"
onclick={closeConfirm}
type="button" type="button"
onclick={closeConfirm}>Cancel</button >Cancel
> </button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,12 +1,12 @@
import { type Actions, error, fail, redirect } from '@sveltejs/kit'; import { type Actions, error, fail, redirect } from '@sveltejs/kit';
import { createUser, getUsers } from '$lib/db/index.server'; import { createUser } from '$lib/db/index.server';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import { getUserPerms } from '$lib/index.server'; import { getUserPerms } from '$lib/index.server';
import type { User } from '$lib/types'; import type { User } from '$lib/types';
import { employerPerms, userPerms } from '$lib/shared.svelte'; import { employerPerms, userPerms } from '$lib/shared.svelte';
import type { PageServerLoad } from '../../../../../.svelte-kit/types/src/routes/admin/users/$types'; import type { PageServerLoad } from '../../../../../.svelte-kit/types/src/routes/admin/users/$types';
export const load: PageServerLoad = async ({ cookies, url }) => { export const load: PageServerLoad = async ({ cookies }) => {
const perms = getUserPerms(cookies); const perms = getUserPerms(cookies);
if (!(perms >= 0 && (perms & PERMISSIONS.MANAGE_USERS) > 0)) { if (!(perms >= 0 && (perms & PERMISSIONS.MANAGE_USERS) > 0)) {
error(403, 'Unauthorized'); error(403, 'Unauthorized');

View File

@ -3,6 +3,7 @@
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
import type { PageProps } from './$types'; import type { PageProps } from './$types';
import { telFormatter } from '$lib/shared.svelte'; import { telFormatter } from '$lib/shared.svelte';
let permsAccordions: boolean[] = [false, false, false]; let permsAccordions: boolean[] = [false, false, false];
let passwordVisible = $state(false); let passwordVisible = $state(false);
@ -85,32 +86,32 @@
<div class="bottom-border flex place-content-between"> <div class="bottom-border flex place-content-between">
<div class="p-3 font-semibold">Create new user</div> <div class="p-3 font-semibold">Create new user</div>
</div> </div>
<form method="POST" class="px-4" autocomplete="off" use:enhance> <form autocomplete="off" class="px-4" method="POST" use:enhance>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Username <span class="text-red-500">*</span> Username <span class="text-red-500">*</span>
<input <input
type="text"
name="username"
id="username"
placeholder="Username"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="username"
name="username"
placeholder="Username"
required required
type="text"
/> />
</div> </div>
<div class="relative mt-4 text-sm font-semibold"> <div class="relative mt-4 text-sm font-semibold">
Password <span class="text-red-500">*</span> Password <span class="text-red-500">*</span>
<input <input
type="password"
name="password"
id="password"
placeholder="Password"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="password"
name="password"
placeholder="Password"
required required
type="password"
/> />
<button <button
type="button"
onclick={showPassword}
class="absolute right-2.5 -translate-y-1/2 transform pt-12" class="absolute right-2.5 -translate-y-1/2 transform pt-12"
onclick={showPassword}
type="button"
> >
<span class="material-symbols-outlined" <span class="material-symbols-outlined"
>{passwordVisible ? 'visibility' : 'visibility_off'}</span >{passwordVisible ? 'visibility' : 'visibility_off'}</span
@ -120,44 +121,44 @@
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Full name <span class="danger-color">*</span> Full name <span class="danger-color">*</span>
<input <input
type="text"
name="fullName"
id="fullName"
placeholder="Full Name"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="fullName"
name="fullName"
placeholder="Full Name"
required required
type="text"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Email <span class="danger-color">*</span> Email <span class="danger-color">*</span>
<input <input
type="email"
name="email"
id="email"
placeholder="Email"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="email"
name="email"
placeholder="Email"
required required
type="email"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Phone (optional) Phone (optional)
<input <input
type="tel"
name="phone"
id="phone"
placeholder="Phone"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="phone"
name="phone"
pattern="([0-9]\{3}) [0-9]\{3}-[0-9]\{3}" pattern="([0-9]\{3}) [0-9]\{3}-[0-9]\{3}"
placeholder="Phone"
type="tel"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Company code (optional) Company code (optional)
<input <input
type="text"
name="companyCode"
id="companyCode"
placeholder="Company code"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="companyCode"
name="companyCode"
placeholder="Company code"
type="text"
/> />
</div> </div>
<p class="low-emphasis-text"> <p class="low-emphasis-text">
@ -172,11 +173,11 @@
<span class="flex place-items-center"> <span class="flex place-items-center">
<span class="ml-1 mr-3" <span class="ml-1 mr-3"
><input ><input
type="checkbox"
name="userPerms"
id="userPerms"
class="select-all" class="select-all"
id="userPerms"
indeterminate={true} indeterminate={true}
name="userPerms"
type="checkbox"
/></span /></span
>User Permissions >User Permissions
</span> </span>
@ -187,20 +188,20 @@
<div class="panel hidden p-2"> <div class="panel hidden p-2">
<div> <div>
<div class="mb-1"> <div class="mb-1">
<label for="view" class="flex place-items-center"> <label class="flex place-items-center" for="view">
<input <input
type="checkbox"
name="view"
id="view"
checked={true} checked={true}
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="view"
name="view"
type="checkbox"
/> />
<span class="ml-2">View access</span></label <span class="ml-2">View access</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="apply" class="flex place-items-center"> <label class="flex place-items-center" for="apply">
<input type="checkbox" name="apply" id="apply" class="permCheckbox mx-1" /> <input class="permCheckbox mx-1" id="apply" name="apply" type="checkbox" />
<span class="ml-2">Apply for jobs</span></label <span class="ml-2">Apply for jobs</span></label
> >
</div> </div>
@ -215,10 +216,10 @@
<span class="flex place-items-center"> <span class="flex place-items-center">
<span class="ml-1 mr-3" <span class="ml-1 mr-3"
><input ><input
type="checkbox"
name="companyPerms"
id="companyPerms"
class="select-all" class="select-all"
id="companyPerms"
name="companyPerms"
type="checkbox"
/></span /></span
>Company Permissions >Company Permissions
</span> </span>
@ -229,23 +230,23 @@
<div class="panel hidden p-2"> <div class="panel hidden p-2">
<div> <div>
<div class="mb-1"> <div class="mb-1">
<label for="submitPostings" class="flex place-items-center"> <label class="flex place-items-center" for="submitPostings">
<input <input
type="checkbox"
name="submitPostings"
id="submitPostings"
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="submitPostings"
name="submitPostings"
type="checkbox"
/> />
<span class="ml-2">Submit postings</span></label <span class="ml-2">Submit postings</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="manageEmployers" class="flex place-items-center"> <label class="flex place-items-center" for="manageEmployers">
<input <input
type="checkbox"
name="manageEmployers"
id="manageEmployers"
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="manageEmployers"
name="manageEmployers"
type="checkbox"
/> />
<span class="ml-2">Manage employers (within their company)</span></label <span class="ml-2">Manage employers (within their company)</span></label
> >
@ -260,7 +261,7 @@
> >
<span class="flex place-items-center"> <span class="flex place-items-center">
<span class="ml-1 mr-3" <span class="ml-1 mr-3"
><input type="checkbox" name="adminPerms" id="adminPerms" class="select-all" /></span ><input class="select-all" id="adminPerms" name="adminPerms" type="checkbox" /></span
>Admin Permissions >Admin Permissions
</span> </span>
<span class="material-symbols-outlined" <span class="material-symbols-outlined"
@ -270,45 +271,45 @@
<div class="panel hidden p-2"> <div class="panel hidden p-2">
<div> <div>
<div class="mb-1"> <div class="mb-1">
<label for="manageTags" class="flex place-items-center"> <label class="flex place-items-center" for="manageTags">
<input <input
type="checkbox"
name="manageTags"
id="manageTags"
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="manageTags"
name="manageTags"
type="checkbox"
/> />
<span class="ml-2">Manage tags</span></label <span class="ml-2">Manage tags</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="managePostings" class="flex place-items-center"> <label class="flex place-items-center" for="managePostings">
<input <input
type="checkbox"
name="managePostings"
id="managePostings"
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="managePostings"
name="managePostings"
type="checkbox"
/> />
<span class="ml-2">Manage postings</span></label <span class="ml-2">Manage postings</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="manageUsers" class="flex place-items-center"> <label class="flex place-items-center" for="manageUsers">
<input <input
type="checkbox"
name="manageUsers"
id="manageUsers"
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="manageUsers"
name="manageUsers"
type="checkbox"
/> />
<span class="ml-2">Manage users</span></label <span class="ml-2">Manage users</span></label
> >
</div> </div>
<div class="mb-1"> <div class="mb-1">
<label for="manageCompanies" class="flex place-items-center"> <label class="flex place-items-center" for="manageCompanies">
<input <input
type="checkbox"
name="manageCompanies"
id="manageCompanies"
class="permCheckbox mx-1" class="permCheckbox mx-1"
id="manageCompanies"
name="manageCompanies"
type="checkbox"
/> />
<span class="ml-2">Manage companies</span></label <span class="ml-2">Manage companies</span></label
> >
@ -316,13 +317,13 @@
</div> </div>
</div> </div>
</div> </div>
<label for="accountActive" class="flex place-items-center p-2"> <label class="flex place-items-center p-2" for="accountActive">
<input <input
type="checkbox"
name="accountActive"
id="accountActive"
class="permCheckbox mx-1"
checked checked
class="permCheckbox mx-1"
id="accountActive"
name="accountActive"
type="checkbox"
/> />
<span class="ml-2">Account active</span></label <span class="ml-2">Account active</span></label
> >
@ -332,9 +333,10 @@
{/if} {/if}
<button <button
class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1" class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1"
formaction="?/submit"
type="submit" type="submit"
formaction="?/submit">Create user</button >Create user
> </button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { getPosting, getPostingWithCompanyUser } from '$lib/db/index.server'; import { getPostingWithCompanyUser } from '$lib/db/index.server';
import { error, json } from '@sveltejs/kit'; import { error, json } from '@sveltejs/kit';
export async function GET({ url }) { export async function GET({ url }) {

View File

@ -23,11 +23,11 @@
<div class="bottom-border mb-4 flex justify-between"> <div class="bottom-border mb-4 flex justify-between">
<div class="flex"> <div class="flex">
<img <img
class="mb-2 inline-block h-32 rounded-lg"
src="/uploads/logos/{data.company.id}.jpg?timestamp=${Date.now()}"
alt="Company Logo" alt="Company Logo"
onerror={logoFallback} class="mb-2 inline-block h-32 rounded-lg"
height="128" height="128"
onerror={logoFallback}
src="/uploads/logos/{data.company.id}.jpg?timestamp=${Date.now()}"
width="128" width="128"
/> />
<div class="inline-block h-min pl-4"> <div class="inline-block h-min pl-4">

View File

@ -7,17 +7,17 @@
<div class="bottom-border h-10 pt-2 text-center"> <div class="bottom-border h-10 pt-2 text-center">
<a <a
href={page.url.pathname.endsWith('employers') ? '.' : ''}
class="p-2 {page.url.pathname.endsWith('edit') class="p-2 {page.url.pathname.endsWith('edit')
? 'primary-underline font-bold' ? 'primary-underline font-bold'
: 'low-emphasis-text low-emphasis-text-button'}" : 'low-emphasis-text low-emphasis-text-button'}"
href={page.url.pathname.endsWith('employers') ? '.' : ''}
><span class="material-symbols-outlined align-bottom">store</span> Details</a ><span class="material-symbols-outlined align-bottom">store</span> Details</a
> >
<a <a
href={page.url.pathname.endsWith('edit') ? 'edit/employers' : ''}
class="p-2 {page.url.pathname.endsWith('employers') class="p-2 {page.url.pathname.endsWith('employers')
? 'primary-underline font-bold' ? 'primary-underline font-bold'
: 'low-emphasis-text low-emphasis-text-button'}" : 'low-emphasis-text low-emphasis-text-button'}"
href={page.url.pathname.endsWith('edit') ? 'edit/employers' : ''}
><span class="material-symbols-outlined align-bottom">group</span> Employers</a ><span class="material-symbols-outlined align-bottom">group</span> Employers</a
> >
</div> </div>

View File

@ -1,7 +1,7 @@
import { type Actions, error, fail, redirect } from '@sveltejs/kit'; import { type Actions, error, fail, redirect } from '@sveltejs/kit';
import { deleteCompany, editCompany, getCompany } from '$lib/db/index.server'; import { deleteCompany, editCompany, getCompany } from '$lib/db/index.server';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import { getUserCompanyId, getUserPerms } from '$lib/index.server'; import { getUserPerms } from '$lib/index.server';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import type { Company } from '$lib/types'; import type { Company } from '$lib/types';

View File

@ -22,11 +22,11 @@
<div class="content"> <div class="content">
<div class="m-4"> <div class="m-4">
<img <img
class="mb-2 inline-block rounded-lg"
src="/uploads/logos/{data.company.id}.jpg?timestamp=${Date.now()}"
alt="User avatar" alt="User avatar"
onerror={logoFallback} class="mb-2 inline-block rounded-lg"
height="80" height="80"
onerror={logoFallback}
src="/uploads/logos/{data.company.id}.jpg?timestamp=${Date.now()}"
width="80" width="80"
/> />
<div class="inline-block pl-4 align-top"> <div class="inline-block pl-4 align-top">
@ -38,38 +38,38 @@
<div class="bottom-border flex place-content-between"> <div class="bottom-border flex place-content-between">
<div class="p-3 font-semibold">Edit Company {data.company.name}</div> <div class="p-3 font-semibold">Edit Company {data.company.name}</div>
</div> </div>
<form method="POST" class="px-4" autocomplete="off" use:enhance> <form autocomplete="off" class="px-4" method="POST" use:enhance>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Name <span class="text-red-500">*</span> Name <span class="text-red-500">*</span>
<input <input
type="text"
name="name"
id="name"
placeholder="Name"
value={data.company?.name}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="name"
name="name"
placeholder="Name"
required required
type="text"
value={data.company?.name}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Description <span class="text-red-500">*</span> Description <span class="text-red-500">*</span>
<textarea <textarea
name="description" class="w-full rounded font-normal"
id="description" id="description"
rows="4" name="description"
placeholder="Description" placeholder="Description"
class="w-full rounded font-normal">{data.company?.description}</textarea rows="4">{data.company?.description}</textarea
> >
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Website <span class="text-red-500">*</span> Website <span class="text-red-500">*</span>
<input <input
type="text"
name="website"
id="website"
placeholder="Website"
value={data.company?.website}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="website"
name="website"
placeholder="Website"
type="text"
value={data.company?.website}
/> />
</div> </div>
@ -79,23 +79,25 @@
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
<button <button
class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1" class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1"
formaction="?/submit"
type="submit" type="submit"
formaction="?/submit">Save company</button >Save company
> </button>
<button <button
class="danger-bg-color mb-4 mt-2 rounded px-2 py-1" class="danger-bg-color mb-4 mt-2 rounded px-2 py-1"
onclick={openConfirm}
type="button" type="button"
onclick={openConfirm}>Delete company</button >Delete company
> </button>
</div> </div>
</form> </form>
<form id="deleteConfirmModal" class="modal" method="POST" use:enhance> <form class="modal" id="deleteConfirmModal" method="POST" use:enhance>
<div class="modal-content"> <div class="modal-content">
<div class="mb-2 inline-flex w-full justify-between"> <div class="mb-2 inline-flex w-full justify-between">
<h2 class="font-semibold">Are you sure?</h2> <h2 class="font-semibold">Are you sure?</h2>
<button class="material-symbols-outlined" onclick={closeConfirm} type="button" <button class="material-symbols-outlined" onclick={closeConfirm} type="button"
>close</button >close
> </button>
</div> </div>
<p> <p>
This will permanently delete company <span class="font-semibold" This will permanently delete company <span class="font-semibold"
@ -104,23 +106,24 @@
</p> </p>
<p>Please type "I understand" into the box below to confirm</p> <p>Please type "I understand" into the box below to confirm</p>
<input <input
type="text"
name="confirm"
id="confirm"
placeholder="I understand"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="confirm"
name="confirm"
pattern="I understand" pattern="I understand"
placeholder="I understand"
required required
type="text"
/> />
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
<button class="danger-bg-color rounded px-2 py-1" type="submit" formaction="?/delete" <button class="danger-bg-color rounded px-2 py-1" formaction="?/delete" type="submit"
>Delete company</button >Delete company
> </button>
<button <button
class="separator-borders bg-color rounded px-2 py-1" class="separator-borders bg-color rounded px-2 py-1"
onclick={closeConfirm}
type="button" type="button"
onclick={closeConfirm}>Cancel</button >Cancel
> </button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,10 +1,6 @@
import { type Actions, error, fail, redirect } from '@sveltejs/kit'; import { type Actions, error, fail } from '@sveltejs/kit';
import { import {
addEmployerToCompany, addEmployerToCompany,
deleteCompany,
editCompany,
getCompany,
getCompanyEmployers,
getCompanyEmployersAndRequests, getCompanyEmployersAndRequests,
removeEmployerFromCompany removeEmployerFromCompany
} from '$lib/db/index.server'; } from '$lib/db/index.server';

View File

@ -20,11 +20,11 @@
<div class="content"> <div class="content">
<div class="m-4"> <div class="m-4">
<img <img
class="mb-2 inline-block rounded-lg"
src="/uploads/logos/{data.company.id}.jpg?timestamp=${Date.now()}"
alt="User avatar" alt="User avatar"
onerror={logoFallback} class="mb-2 inline-block rounded-lg"
height="80" height="80"
onerror={logoFallback}
src="/uploads/logos/{data.company.id}.jpg?timestamp=${Date.now()}"
width="80" width="80"
/> />
<div class="inline-block pl-4 align-top"> <div class="inline-block pl-4 align-top">
@ -66,13 +66,14 @@
>{user.lastSignIn?.toLocaleDateString('en-US', dateFormatOptions) || >{user.lastSignIn?.toLocaleDateString('en-US', dateFormatOptions) ||
'unknown'}</td 'unknown'}</td
> >
<td class="material-symbols-outlined hover-bg-color danger-color m-1 rounded" <td class="material-symbols-outlined hover-bg-color danger-color m-1 rounded">
><button <button
onclick={() => { onclick={() => {
idToRemove = user.id; idToRemove = user.id;
}}>close</button }}
></td >close
> </button>
</td>
</tr> </tr>
{/if} {/if}
{/each} {/each}
@ -91,23 +92,26 @@
class="material-symbols-outlined" class="material-symbols-outlined"
onclick={() => { onclick={() => {
idToRemove = null; idToRemove = null;
}}>close</button }}
> >close
</button>
</div> </div>
<p>This will remove this employer from the company.</p> <p>This will remove this employer from the company.</p>
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
<button <button
class="danger-bg-color rounded px-2 py-1" class="danger-bg-color rounded px-2 py-1"
type="submit" type="submit"
formaction="?/removeEmployer&userId={idToRemove}">Remove</button formaction="?/removeEmployer&userId={idToRemove}"
> >Remove
</button>
<button <button
class="separator-borders bg-color rounded px-2 py-1" class="separator-borders bg-color rounded px-2 py-1"
type="button" type="button"
onclick={() => { onclick={() => {
idToRemove = null; idToRemove = null;
}}>Cancel</button }}
> >Cancel
</button>
</div> </div>
</div> </div>
</form> </form>
@ -147,18 +151,20 @@
>{user.lastSignIn?.toLocaleDateString('en-US', dateFormatOptions) || >{user.lastSignIn?.toLocaleDateString('en-US', dateFormatOptions) ||
'unknown'}</td 'unknown'}</td
> >
<td class="material-symbols-outlined" <td class="material-symbols-outlined">
><form method="POST" class="flex"> <form method="POST" class="flex">
<button <button
class="hover-bg-color m-1 rounded text-green-600" class="hover-bg-color m-1 rounded text-green-600"
formaction="?/addEmployer&userId={user.id}">check</button formaction="?/addEmployer&userId={user.id}"
> >check
</button>
<button <button
class="hover-bg-color danger-color m-1 rounded" class="hover-bg-color danger-color m-1 rounded"
formaction="?/removeEmployer&userId={user.id}">close</button formaction="?/removeEmployer&userId={user.id}"
> >close
</form></td </button>
> </form>
</td>
</tr> </tr>
{/if} {/if}
{/each} {/each}

View File

@ -1,7 +1,7 @@
import { type Actions, fail, redirect } from '@sveltejs/kit'; import { type Actions, fail, redirect } from '@sveltejs/kit';
import { createCompany } from '$lib/db/index.server'; import { createCompany } from '$lib/db/index.server';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import { getUserCompanyId, getUserPerms } from '$lib/index.server'; import { getUserPerms } from '$lib/index.server';
import type { Company } from '$lib/types'; import type { Company } from '$lib/types';
export const actions: Actions = { export const actions: Actions = {

View File

@ -11,38 +11,38 @@
<div class="bottom-border flex place-content-between"> <div class="bottom-border flex place-content-between">
<div class="p-3 font-semibold">Create new company</div> <div class="p-3 font-semibold">Create new company</div>
</div> </div>
<form method="POST" class="px-4" autocomplete="off" use:enhance> <form autocomplete="off" class="px-4" method="POST" use:enhance>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Name <span class="text-red-500">*</span> Name <span class="text-red-500">*</span>
<input <input
type="text"
name="name"
id="name"
placeholder="Name"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="name"
name="name"
placeholder="Name"
required required
type="text"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Description <span class="text-red-500">*</span> Description <span class="text-red-500">*</span>
<textarea <textarea
name="description"
id="description"
rows="4"
placeholder="Description"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="description"
name="description"
placeholder="Description"
required required
rows="4"
></textarea> ></textarea>
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Website <span class="text-red-500">*</span> Website <span class="text-red-500">*</span>
<input <input
type="text"
name="website"
id="website"
placeholder="Website"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="website"
name="website"
placeholder="Website"
required required
type="text"
/> />
</div> </div>
@ -51,9 +51,10 @@
{/if} {/if}
<button <button
class="dull-primary-bg-color mb-4 mt-6 rounded px-2 py-1" class="dull-primary-bg-color mb-4 mt-6 rounded px-2 py-1"
formaction="?/submit"
type="submit" type="submit"
formaction="?/submit">Create company</button >Create company
> </button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -15,7 +15,7 @@
</p> </p>
<p> <p>
If you are your company admin, first create your account (without inputting a code). Reach out If you are your company admin, first create your account (without inputting a code). Reach out
to a CareerConnect admin to elevate your account privaleges. Then, go to the company page, and to a CareerConnect admin to elevate your account privileges. Then, go to the company page, and
use the button in the top right to create a new company. Once created, you will be able to see use the button in the top right to create a new company. Once created, you will be able to see
the company code, which you can then give to your employees. the company code, which you can then give to your employees.
</p> </p>

View File

@ -4,19 +4,23 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { employmentTypeDisplayName, userState } from '$lib/shared.svelte'; import { employmentTypeDisplayName, userState } from '$lib/shared.svelte';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
let details: Posting | undefined = $state<Posting>(); let details: Posting | undefined = $state<Posting>();
// Formating for all dates on the page
const dateFormatOptions: Intl.DateTimeFormatOptions = { const dateFormatOptions: Intl.DateTimeFormatOptions = {
year: 'numeric', year: 'numeric',
month: 'short', month: 'short',
day: 'numeric' day: 'numeric'
}; };
// If logo isn't found on the server, use a placeholder
function logoFallback(e: Event, posting: Posting | undefined) { function logoFallback(e: Event, posting: Posting | undefined) {
(e.target as HTMLImageElement).src = (e.target as HTMLImageElement).src =
`https://ui-avatars.com/api/?background=random&format=svg&name=${encodeURIComponent(posting?.company.name || 'COMPANY')}`; `https://ui-avatars.com/api/?background=random&format=svg&name=${encodeURIComponent(posting?.company.name || 'COMPANY')}`;
} }
// Fetch the detail pane content of a posting
async function fetchDetails(id: number) { async function fetchDetails(id: number) {
const response = await fetch(`/api/posting?id=${id}`); const response = await fetch(`/api/posting?id=${id}`);
details = await response.json(); details = await response.json();
@ -30,6 +34,7 @@
let { data }: PageProps = $props(); let { data }: PageProps = $props();
// Initial fetch of the first posting
onMount(async () => { onMount(async () => {
await fetchDetails(data.postings[0].id); await fetchDetails(data.postings[0].id);
}); });
@ -48,22 +53,14 @@
<form action="" class="flex p-4"> <form action="" class="flex p-4">
<div class="search-bar"> <div class="search-bar">
<input <input
type="search"
name="searchQuery"
id="searchQuery"
placeholder="Search Postings"
class="search-cancel" class="search-cancel"
id="searchQuery"
name="searchQuery"
placeholder="Search Postings"
type="search"
/> />
<button><span class="material-symbols-outlined">search</span></button> <button><span class="material-symbols-outlined">search</span></button>
</div> </div>
<!-- <button class="hover-bg-color mx-2 rounded py-2 pl-3 pr-2 text-sm"-->
<!-- >Filter<span class="material-symbols-outlined icon-20 align-middle">arrow_drop_down</span-->
<!-- ></button-->
<!-- >-->
<!-- <button class="hover-bg-color rounded py-2 pl-3 pr-2 text-sm"-->
<!-- >Sort<span class="material-symbols-outlined icon-20 align-middle">arrow_drop_down</span-->
<!-- ></button-->
<!-- >-->
</form> </form>
<div class="flex"> <div class="flex">
<div class="right-border inline-block w-1/3"> <div class="right-border inline-block w-1/3">

View File

@ -1,5 +1,5 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { getPostingWithCompanyUser, getPostings } from '$lib/db/index.server'; import { getPostingWithCompanyUser } from '$lib/db/index.server';
export const load: PageServerLoad = async ({ params }) => { export const load: PageServerLoad = async ({ params }) => {
return { return {

View File

@ -24,16 +24,16 @@
<div class="bottom-border elevated-bg flex justify-between pb-2"> <div class="bottom-border elevated-bg flex justify-between pb-2">
<div class="inline-block"> <div class="inline-block">
<img <img
class="inline-block rounded"
src="/uploads/logos/{data.posting.company.id}.jpg"
alt="Company Logo" alt="Company Logo"
class="inline-block rounded"
height="64" height="64"
width="64"
onerror={(e) => logoFallback(e, data.posting)} onerror={(e) => logoFallback(e, data.posting)}
src="/uploads/logos/{data.posting.company.id}.jpg"
width="64"
/> />
<div class="inline-block pl-2 align-top"> <div class="inline-block pl-2 align-top">
<h1>{data.posting.title}</h1> <h1>{data.posting.title}</h1>
<a href="/companies/{data.posting.company.id}" class="hover-hyperlink text-xl" <a class="hover-hyperlink text-xl" href="/companies/{data.posting.company.id}"
>{data.posting.company.name}</a >{data.posting.company.name}</a
> >
</div> </div>

View File

@ -1,11 +1,7 @@
import { type Actions, error, fail, redirect } from '@sveltejs/kit'; import { type Actions, error, fail, redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { import { createApplication, getPostingWithCompanyUser } from '$lib/db/index.server';
createApplication, import { getUserId, getUserPerms } from '$lib/index.server';
getPostingWithCompanyUser,
getUserWithCompany
} from '$lib/db/index.server';
import { getUserCompanyId, getUserId, getUserPerms } from '$lib/index.server';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import type { Application } from '$lib/types'; import type { Application } from '$lib/types';

View File

@ -24,12 +24,12 @@
<div class="bottom-border elevated-bg flex justify-between pb-2"> <div class="bottom-border elevated-bg flex justify-between pb-2">
<div class="inline-block"> <div class="inline-block">
<img <img
class="inline-block rounded"
src="/uploads/logos/{data.posting?.company.id}.jpg"
alt="Company Logo" alt="Company Logo"
class="inline-block rounded"
height="64" height="64"
width="64"
onerror={(e) => logoFallback(e, data.posting)} onerror={(e) => logoFallback(e, data.posting)}
src="/uploads/logos/{data.posting?.company.id}.jpg"
width="64"
/> />
<div class="inline-block pl-2 align-top"> <div class="inline-block pl-2 align-top">
<h1>{data.posting.title}</h1> <h1>{data.posting.title}</h1>
@ -84,16 +84,16 @@
<div class="bottom-border flex place-content-between"> <div class="bottom-border flex place-content-between">
<div class="p-3 font-semibold">Apply</div> <div class="p-3 font-semibold">Apply</div>
</div> </div>
<form method="POST" class="px-4" autocomplete="off" use:enhance> <form autocomplete="off" class="px-4" method="POST" use:enhance>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Why do you believe you are the best fit for this role? <span class="text-red-500">*</span> Why do you believe you are the best fit for this role? <span class="text-red-500">*</span>
<textarea <textarea
name="candidateStatement"
id="candidateStatement"
rows="4"
placeholder="Answer here"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="candidateStatement"
name="candidateStatement"
placeholder="Answer here"
required required
rows="4"
></textarea> ></textarea>
</div> </div>
<p> <p>
@ -107,9 +107,10 @@
{/if} {/if}
<button <button
class="dull-primary-bg-color mb-4 mt-6 rounded px-2 py-1" class="dull-primary-bg-color mb-4 mt-6 rounded px-2 py-1"
formaction="?/submit"
type="submit" type="submit"
formaction="?/submit">Submit application</button >Submit application
> </button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -7,18 +7,17 @@
<div class="bottom-border h-10 pt-2 text-center"> <div class="bottom-border h-10 pt-2 text-center">
<a <a
href="applications"
class="p-2 {page.url.pathname.endsWith('applications') class="p-2 {page.url.pathname.endsWith('applications')
? 'primary-underline font-bold' ? 'primary-underline font-bold'
: 'low-emphasis-text low-emphasis-text-button'}" : 'low-emphasis-text low-emphasis-text-button'}"
href="applications"
><span class="material-symbols-outlined align-bottom">description</span> Applications</a ><span class="material-symbols-outlined align-bottom">description</span> Applications</a
> >
<a <a
href="edit"
class="p-2 {page.url.pathname.endsWith('edit') class="p-2 {page.url.pathname.endsWith('edit')
? 'primary-underline font-bold' ? 'primary-underline font-bold'
: 'low-emphasis-text low-emphasis-text-button'}" : 'low-emphasis-text low-emphasis-text-button'}"
><span class="material-symbols-outlined align-bottom">work</span> Details</a href="edit"><span class="material-symbols-outlined align-bottom">work</span> Details</a
> >
</div> </div>

View File

@ -1,11 +1,7 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { getUserCompanyId, getUserPerms } from '$lib/index.server'; import { getUserCompanyId, getUserPerms } from '$lib/index.server';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import { import { deleteApplication, getApplications } from '$lib/db/index.server';
deleteApplication,
getApplications,
getPostingWithCompanyUser
} from '$lib/db/index.server';
import { type Actions, error } from '@sveltejs/kit'; import { type Actions, error } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ params, cookies }) => { export const load: PageServerLoad = async ({ params, cookies }) => {

View File

@ -1,8 +1,6 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$app/forms';
import type { PageProps } from './$types'; import type { PageProps } from './$types';
import { employmentTypeDisplayName } from '$lib/shared.svelte'; import type { Application } from '$lib/types';
import type { Application, Posting } from '$lib/types';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
const dateFormatOptions: Intl.DateTimeFormatOptions = { const dateFormatOptions: Intl.DateTimeFormatOptions = {
@ -20,7 +18,7 @@
const acc = document.getElementsByClassName('accordion'); const acc = document.getElementsByClassName('accordion');
for (let i = 0; i < acc.length; i++) { for (let i = 0; i < acc.length; i++) {
acc[i].addEventListener('click', function (this: HTMLElement, event: Event) { acc[i].addEventListener('click', function (this: HTMLElement) {
this.classList.toggle('active'); this.classList.toggle('active');
this.children[1].innerHTML = this.classList.contains('active') this.children[1].innerHTML = this.classList.contains('active')
? 'arrow_drop_up' ? 'arrow_drop_up'

View File

@ -1,5 +1,5 @@
import { type Actions, error, fail, redirect } from '@sveltejs/kit'; import { type Actions, error, fail, redirect } from '@sveltejs/kit';
import { deletePosting, editPosting, getPosting, getPostings, getUser } from '$lib/db/index.server'; import { deletePosting, editPosting, getPosting } from '$lib/db/index.server';
import { PERMISSIONS } from '$lib/consts'; import { PERMISSIONS } from '$lib/consts';
import { getUserCompanyId, getUserId, getUserPerms } from '$lib/index.server'; import { getUserCompanyId, getUserId, getUserPerms } from '$lib/index.server';
import type { Posting } from '$lib/types'; import type { Posting } from '$lib/types';

View File

@ -20,7 +20,7 @@
<div class="bottom-border flex place-content-between"> <div class="bottom-border flex place-content-between">
<div class="p-3 font-semibold">Edit {data.posting.title}</div> <div class="p-3 font-semibold">Edit {data.posting.title}</div>
</div> </div>
<form method="POST" class="px-4" autocomplete="off" use:enhance> <form autocomplete="off" class="px-4" method="POST" use:enhance>
{#if !userState.companyId} {#if !userState.companyId}
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Company ID <span class="text-red-500">*</span> Company ID <span class="text-red-500">*</span>
@ -38,45 +38,45 @@
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Title <span class="text-red-500">*</span> Title <span class="text-red-500">*</span>
<input <input
type="text"
name="title"
id="title"
placeholder="Title"
value={data.posting.title}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="title"
name="title"
placeholder="Title"
required required
type="text"
value={data.posting.title}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Description <span class="text-red-500">*</span> Description <span class="text-red-500">*</span>
<textarea <textarea
name="description"
id="description"
rows="4"
placeholder="Description"
class="w-full rounded font-normal" class="w-full rounded font-normal"
required>{data.posting.description}</textarea id="description"
name="description"
placeholder="Description"
required
rows="4">{data.posting.description}</textarea
> >
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Address <span class="text-red-500">*</span> Address <span class="text-red-500">*</span>
<input <input
type="text"
name="address"
id="address"
placeholder="Address"
value={data.posting.address}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="address"
name="address"
placeholder="Address"
required required
type="text"
value={data.posting.address}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
<label for="employmentType">Employment type <span class="text-red-500">*</span></label> <label for="employmentType">Employment type <span class="text-red-500">*</span></label>
<select <select
name="employmentType"
id="employmentType"
value={data.posting.employmentType}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="employmentType"
name="employmentType"
value={data.posting.employmentType}
> >
<option value="full_time">Full time</option> <option value="full_time">Full time</option>
<option value="part_time">Part time</option> <option value="part_time">Part time</option>
@ -86,34 +86,34 @@
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Wage (optional) Wage (optional)
<input <input
type="text"
name="wage"
id="wage"
placeholder="Wage"
value={data.posting.wage}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="wage"
name="wage"
placeholder="Wage"
type="text"
value={data.posting.wage}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Link to external posting information (optional) Link to external posting information (optional)
<input <input
type="text"
name="link"
id="link"
placeholder="Link"
value={data.posting.link}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="link"
name="link"
placeholder="Link"
type="text"
value={data.posting.link}
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Link to flyer (optional) Link to flyer (optional)
<input <input
type="text"
name="flyerLink"
id="flyerLink"
placeholder="Flyer link"
value={data.posting.flyerLink}
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="flyerLink"
name="flyerLink"
placeholder="Flyer link"
type="text"
value={data.posting.flyerLink}
/> />
</div> </div>
@ -123,20 +123,22 @@
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
<button <button
class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1" class="dull-primary-bg-color mb-4 mt-2 rounded px-2 py-1"
formaction="?/submit"
type="submit" type="submit"
formaction="?/submit">Save posting</button >Save posting
> </button>
<button <button
class="danger-bg-color mb-4 mt-2 rounded px-2 py-1" class="danger-bg-color mb-4 mt-2 rounded px-2 py-1"
onclick={openConfirm}
type="button" type="button"
onclick={openConfirm}>Delete posting</button >Delete posting
> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<form id="deleteConfirmModal" method="POST" class="modal"> <form class="modal" id="deleteConfirmModal" method="POST">
<div class="modal-content"> <div class="modal-content">
<div class="mb-2 inline-flex w-full justify-between"> <div class="mb-2 inline-flex w-full justify-between">
<h2 class="font-semibold">Are you sure?</h2> <h2 class="font-semibold">Are you sure?</h2>
@ -147,14 +149,16 @@
<div class="mt-4 flex justify-between"> <div class="mt-4 flex justify-between">
<button <button
class="danger-bg-color rounded px-2 py-1" class="danger-bg-color rounded px-2 py-1"
formaction="?/delete&id={data.posting.id}"
type="submit" type="submit"
formaction="?/delete&id={data.posting.id}">Delete posting</button >Delete posting
> </button>
<button <button
class="separator-borders bg-color rounded px-2 py-1" class="separator-borders bg-color rounded px-2 py-1"
onclick={closeConfirm}
type="button" type="button"
onclick={closeConfirm}>Cancel</button >Cancel
> </button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -12,7 +12,7 @@
<div class="bottom-border flex place-content-between"> <div class="bottom-border flex place-content-between">
<div class="p-3 font-semibold">Create new posting</div> <div class="p-3 font-semibold">Create new posting</div>
</div> </div>
<form method="POST" class="px-4" autocomplete="off" use:enhance> <form autocomplete="off" class="px-4" method="POST" use:enhance>
{#if !userState.companyId} {#if !userState.companyId}
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Company ID <span class="text-red-500">*</span> Company ID <span class="text-red-500">*</span>
@ -29,39 +29,39 @@
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Title <span class="text-red-500">*</span> Title <span class="text-red-500">*</span>
<input <input
type="text"
name="title"
id="title"
placeholder="Title"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="title"
name="title"
placeholder="Title"
required required
type="text"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Description <span class="text-red-500">*</span> Description <span class="text-red-500">*</span>
<textarea <textarea
name="description"
id="description"
rows="4"
placeholder="Description"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="description"
name="description"
placeholder="Description"
required required
rows="4"
></textarea> ></textarea>
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Address <span class="text-red-500">*</span> Address <span class="text-red-500">*</span>
<input <input
type="text"
name="address"
id="address"
placeholder="Address"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="address"
name="address"
placeholder="Address"
required required
type="text"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
<label for="employmentType">Employment type <span class="text-red-500">*</span></label> <label for="employmentType">Employment type <span class="text-red-500">*</span></label>
<select name="employmentType" id="employmentType" class="w-full rounded font-normal"> <select class="w-full rounded font-normal" id="employmentType" name="employmentType">
<option value="full_time">Full time</option> <option value="full_time">Full time</option>
<option value="part_time">Part time</option> <option value="part_time">Part time</option>
<option value="internship">Internship</option> <option value="internship">Internship</option>
@ -70,31 +70,31 @@
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Wage (optional) Wage (optional)
<input <input
type="text"
name="wage"
id="wage"
placeholder="Wage"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="wage"
name="wage"
placeholder="Wage"
type="text"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Link to external posting information (optional) Link to external posting information (optional)
<input <input
type="text"
name="link"
id="link"
placeholder="Link"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="link"
name="link"
placeholder="Link"
type="text"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Link to flyer (optional) Link to flyer (optional)
<input <input
type="text"
name="flyerLink"
id="flyerLink"
placeholder="Flyer link"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="flyerLink"
name="flyerLink"
placeholder="Flyer link"
type="text"
/> />
</div> </div>
@ -103,9 +103,10 @@
{/if} {/if}
<button <button
class="dull-primary-bg-color mb-4 mt-6 rounded px-2 py-1" class="dull-primary-bg-color mb-4 mt-6 rounded px-2 py-1"
formaction="?/submit"
type="submit" type="submit"
formaction="?/submit">Create posting</button >Create posting
> </button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -1,6 +1,5 @@
import * as dotenv from 'dotenv'; import * as dotenv from 'dotenv';
import { type Actions, type Cookies, fail, redirect } from '@sveltejs/kit'; import { type Actions, fail, redirect } from '@sveltejs/kit';
import jwt from 'jsonwebtoken';
import { createUser } from '$lib/db/index.server'; import { createUser } from '$lib/db/index.server';
import { setJWT } from '$lib/shared.server'; import { setJWT } from '$lib/shared.server';

View File

@ -21,85 +21,85 @@
<div class="elevated separator-borders bg content mb-4 rounded-md p-8"> <div class="elevated separator-borders bg content mb-4 rounded-md p-8">
<h1 class="text-weight-semibold mb-4 text-center">Register</h1> <h1 class="text-weight-semibold mb-4 text-center">Register</h1>
<p>Create your account. Its free and only takes a minute!</p> <p>Create your account. Its free and only takes a minute!</p>
<form method="POST" class="arrange-vertically" use:enhance> <form class="arrange-vertically" method="POST" use:enhance>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Username <span class="text-red-500">*</span> Username <span class="text-red-500">*</span>
<input <input
type="text"
name="username"
id="username"
placeholder="Username"
class="input-field w-full font-normal" class="input-field w-full font-normal"
id="username"
name="username"
placeholder="Username"
required required
type="text"
/> />
</div> </div>
<div class="relative mt-4 text-sm font-semibold"> <div class="relative mt-4 text-sm font-semibold">
Password <span class="text-red-500">*</span> Password <span class="text-red-500">*</span>
<input <input
type="password"
class="input-field w-full font-normal" class="input-field w-full font-normal"
placeholder="Password"
name="password" name="password"
placeholder="Password"
required required
type="password"
/> />
</div> </div>
<div class="relative mt-4 text-sm font-semibold"> <div class="relative mt-4 text-sm font-semibold">
Confirm password <span class="text-red-500">*</span> Confirm password <span class="text-red-500">*</span>
<input <input
type="password"
class="input-field w-full font-normal" class="input-field w-full font-normal"
placeholder="Password"
name="confirmPassword" name="confirmPassword"
placeholder="Password"
required required
type="password"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Full name <span class="text-red-500">*</span> Full name <span class="text-red-500">*</span>
<input <input
type="text"
name="fullName"
id="fullName"
placeholder="Full name"
class="input-field w-full font-normal" class="input-field w-full font-normal"
id="fullName"
name="fullName"
placeholder="Full name"
required required
type="text"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Email <span class="text-red-500">*</span> Email <span class="text-red-500">*</span>
<input <input
type="text"
name="email"
id="email"
placeholder="Email"
class="input-field w-full font-normal" class="input-field w-full font-normal"
id="email"
name="email"
placeholder="Email"
required required
type="text"
/> />
</div> </div>
<div class="mt-4 text-sm font-semibold"> <div class="mt-4 text-sm font-semibold">
Phone (optional) Phone (optional)
<input <input
type="tel"
name="phone"
id="phone"
placeholder="Phone"
class="w-full rounded font-normal" class="w-full rounded font-normal"
id="phone"
name="phone"
pattern="([0-9]\{3}) [0-9]\{3}-[0-9]\{3}" pattern="([0-9]\{3}) [0-9]\{3}-[0-9]\{3}"
placeholder="Phone"
type="tel"
/> />
</div> </div>
<div class="relative mt-4 text-sm font-semibold"> <div class="relative mt-4 text-sm font-semibold">
<label for="companyCode"> Company code (optional) </label> <label for="companyCode"> Company code (optional) </label>
<div class="relative"> <div class="relative">
<input <input
type="text"
name="companyCode"
id="companyCode"
placeholder="Company code"
class="input-field w-full pr-10 font-normal" class="input-field w-full pr-10 font-normal"
id="companyCode"
name="companyCode"
placeholder="Company code"
type="text"
/> />
<a <a
type="button"
href="/info#company-codes"
class="hyperlink-color tooltip absolute inset-y-0 right-2 flex items-center" class="hyperlink-color tooltip absolute inset-y-0 right-2 flex items-center"
href="/info#company-codes"
type="button"
> >
<span class="material-symbols-outlined">info</span><span <span class="material-symbols-outlined">info</span><span
class="tooltip-text font-sans text-sm font-normal">About company codes</span class="tooltip-text font-sans text-sm font-normal">About company codes</span
@ -114,10 +114,11 @@
<button <button
class="primary-bg-color mt-8 w-full rounded px-2 py-2" class="primary-bg-color mt-8 w-full rounded px-2 py-2"
formaction="?/register"
type="submit" type="submit"
formaction="?/register">Create account</button >Create account
> </button>
<a href="/signin" class="low-emphasis-text-button mt-2">I already have an account.</a> <a class="low-emphasis-text-button mt-2" href="/signin">I already have an account.</a>
</form> </form>
</div> </div>
</div> </div>

View File

@ -31,30 +31,30 @@
<div class="signin-container place-items-center pt-8"> <div class="signin-container place-items-center pt-8">
<div class="separator-borders elevated content rounded-md p-8"> <div class="separator-borders elevated content rounded-md p-8">
<h1 class="text-weight-semibold mb-4 text-center">Welcome Back!</h1> <h1 class="text-weight-semibold mb-4 text-center">Welcome Back!</h1>
<form method="POST" class="arrange-vertically" use:enhance> <form class="arrange-vertically" method="POST" use:enhance>
<div class="bg-color my-2 rounded"> <div class="bg-color my-2 rounded">
<input <input
class="input-field w-full" class="input-field w-full"
type="text"
placeholder="Username"
name="username" name="username"
placeholder="Username"
required required
type="text"
/> />
</div> </div>
<div class="relative w-full"> <div class="relative w-full">
<div class="bg-color mt-4 rounded"> <div class="bg-color mt-4 rounded">
<input <input
type={passwordVisible ? 'text' : 'password'}
class="input-field w-full pr-10" class="input-field w-full pr-10"
placeholder="Password"
name="password" name="password"
placeholder="Password"
required required
type={passwordVisible ? 'text' : 'password'}
/> />
</div> </div>
<button <button
type="button"
onclick={showPassword}
class="absolute right-2.5 top-6 -translate-y-1/2 transform" class="absolute right-2.5 top-6 -translate-y-1/2 transform"
onclick={showPassword}
type="button"
> >
<span class="material-symbols-outlined" <span class="material-symbols-outlined"
>{passwordVisible ? 'visibility' : 'visibility_off'}</span >{passwordVisible ? 'visibility' : 'visibility_off'}</span
@ -68,10 +68,11 @@
<button <button
class="primary-bg-color mt-8 w-full rounded px-2 py-2" class="primary-bg-color mt-8 w-full rounded px-2 py-2"
formaction="?/signin"
type="submit" type="submit"
formaction="?/signin">Sign In</button >Sign In
> </button>
<a href="/register" class="low-emphasis-text-button mt-2" <a class="low-emphasis-text-button mt-2" href="/register"
>Don't have an account? Register here.</a >Don't have an account? Register here.</a
> >
</form> </form>