Fixes
This commit is contained in:
+3
-3
@@ -20,12 +20,12 @@
|
||||
[data-theme='dark'] {
|
||||
--text-color: #f4f4f4;
|
||||
--bg-color: #0c0c0c;
|
||||
--hover-bg-color: #1a1c1c;
|
||||
--separator-line-color: #1a2029;
|
||||
--hover-bg-color: #1c1e1e;
|
||||
--separator-line-color: #2e343d;
|
||||
--low-emphasis-text-color: #999999;
|
||||
--primary-color: #1F96F3;
|
||||
--dull-primary-color: #1569ab;
|
||||
--elevated-bg-color: #151516;
|
||||
--elevated-bg-color: #1a1a1b;
|
||||
--bg-accent-color: #202020;
|
||||
--danger-color: #ff1d1f;
|
||||
--hyperlink-color: #3b82f6;
|
||||
|
||||
+142
-44
@@ -40,31 +40,30 @@ export async function updateUser(user: User): Promise<number> {
|
||||
}
|
||||
|
||||
// Construct the SQL query
|
||||
const response = await sql`UPDATE users
|
||||
SET username = ${user.username},
|
||||
${
|
||||
user.perms !== undefined
|
||||
? sql`perms
|
||||
=
|
||||
${user.perms},`
|
||||
: sql``
|
||||
}
|
||||
${
|
||||
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}
|
||||
RETURNING id;`;
|
||||
const response = await sql`
|
||||
UPDATE users
|
||||
SET username = ${user.username},
|
||||
${
|
||||
user.perms !== undefined
|
||||
? sql`perms = ${user.perms},`
|
||||
: sql``
|
||||
}
|
||||
${
|
||||
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}
|
||||
RETURNING id;`;
|
||||
|
||||
await saveAvatar(user);
|
||||
|
||||
@@ -220,7 +219,8 @@ export async function getUserWithCompanyAndApplications(
|
||||
application_data AS (SELECT id,
|
||||
posting_id AS "postingId",
|
||||
(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",
|
||||
candidate_statement AS "candidateStatement"
|
||||
FROM applications
|
||||
WHERE "user_id" = ${id})
|
||||
SELECT (SELECT row_to_json(company_data)
|
||||
@@ -236,7 +236,7 @@ export async function getUserWithCompanyAndApplications(
|
||||
if (!data) {
|
||||
error(404, 'User not found');
|
||||
}
|
||||
let user = data[0].user;
|
||||
const user = data[0].user;
|
||||
if (data[0].company) {
|
||||
user.company = data[0].company;
|
||||
}
|
||||
@@ -246,7 +246,7 @@ export async function getUserWithCompanyAndApplications(
|
||||
if (user.company) {
|
||||
user.company.createdAt = new Date(user.company.createdAt);
|
||||
}
|
||||
let applications = data[0].applications;
|
||||
const applications = data[0].applications;
|
||||
if (applications) {
|
||||
applications.forEach((application: { createdAt: string | number | Date }) => {
|
||||
application.createdAt = new Date(application.createdAt);
|
||||
@@ -509,8 +509,8 @@ export async function getCompanyEmployers(
|
||||
if (data[0].users) {
|
||||
data[0].users.forEach(
|
||||
(user: {
|
||||
company: { id: any };
|
||||
companyId: any;
|
||||
company: { id: unknown };
|
||||
companyId: unknown;
|
||||
createdAt: string | number | Date;
|
||||
lastSignIn: string | number | Date;
|
||||
}) => {
|
||||
@@ -576,8 +576,8 @@ export async function getCompanyEmployersAndRequests(
|
||||
if (data[0].employers) {
|
||||
data[0].employers.forEach(
|
||||
(user: {
|
||||
company: { id: any };
|
||||
companyId: any;
|
||||
company: { id: unknown };
|
||||
companyId: unknown;
|
||||
createdAt: string | number | Date;
|
||||
lastSignIn: string | number | Date;
|
||||
}) => {
|
||||
@@ -593,8 +593,8 @@ export async function getCompanyEmployersAndRequests(
|
||||
if (data[0].requests) {
|
||||
data[0].requests.forEach(
|
||||
(user: {
|
||||
company: { id: any };
|
||||
companyId: any;
|
||||
company: { id: unknown };
|
||||
companyId: unknown;
|
||||
createdAt: string | number | Date;
|
||||
lastSignIn: string | number | Date;
|
||||
}) => {
|
||||
@@ -751,7 +751,7 @@ export async function getPostingWithCompanyUser(id: number): Promise<Posting> {
|
||||
if (!data) {
|
||||
error(404, 'Posting not found');
|
||||
}
|
||||
let posting = <Posting>data[0].posting;
|
||||
const posting = <Posting>data[0].posting;
|
||||
posting.company = <Company>data[0].company;
|
||||
posting.employer = <User>data[0].user;
|
||||
|
||||
@@ -765,16 +765,107 @@ export async function getPostingWithCompanyUser(id: number): Promise<Posting> {
|
||||
return posting;
|
||||
}
|
||||
|
||||
export async function createApplication(application: Application): Promise<number> {
|
||||
const response = await sql`
|
||||
INSERT INTO applications (posting_id, user_id, candidate_statement, created_at)
|
||||
VALUES (${application.postingId}, ${application.userId}, ${application.candidateStatement}, NOW()) RETURNING id;
|
||||
|
||||
export async function getEditApplicationPageData(applicationId: number): Promise<{ posting: Posting; application: Application }> {
|
||||
const data = await sql`
|
||||
WITH application_data AS (
|
||||
SELECT id AS application_id,
|
||||
candidate_statement,
|
||||
posting_id
|
||||
FROM applications
|
||||
WHERE id = ${applicationId}
|
||||
),
|
||||
company_data AS (
|
||||
SELECT id,
|
||||
name,
|
||||
description,
|
||||
website,
|
||||
created_at AS "createdAt"
|
||||
FROM companies
|
||||
WHERE id = (
|
||||
SELECT company_id
|
||||
FROM postings
|
||||
WHERE id = (SELECT posting_id FROM application_data)
|
||||
)
|
||||
),
|
||||
user_data AS (
|
||||
SELECT username,
|
||||
email,
|
||||
phone,
|
||||
full_name AS "fullName"
|
||||
FROM users
|
||||
WHERE company_id = (
|
||||
SELECT company_id
|
||||
FROM postings
|
||||
WHERE id = (SELECT posting_id FROM application_data)
|
||||
)
|
||||
),
|
||||
posting_data AS (
|
||||
SELECT id,
|
||||
title,
|
||||
description,
|
||||
employer_id AS "employerId",
|
||||
address,
|
||||
employment_type AS "employmentType",
|
||||
wage,
|
||||
link,
|
||||
tag_ids AS "tagIds",
|
||||
created_at AT TIME ZONE 'UTC' AS "createdAt",
|
||||
updated_at AT TIME ZONE 'UTC' AS "updatedAt",
|
||||
flyer_link AS "flyerLink"
|
||||
FROM postings
|
||||
WHERE id = (SELECT posting_id FROM application_data)
|
||||
)
|
||||
SELECT
|
||||
(SELECT row_to_json(company_data) FROM company_data) AS company,
|
||||
(SELECT row_to_json(user_data) FROM user_data) AS user,
|
||||
(SELECT row_to_json(posting_data) FROM posting_data) AS posting,
|
||||
(SELECT row_to_json(application_data) FROM application_data) AS application;
|
||||
`;
|
||||
|
||||
sendEmployerNotificationEmail(application.postingId).catch((err) => {
|
||||
console.error('Failed to send employer notification email: ', err);
|
||||
});
|
||||
return response[0].id;
|
||||
if (!data || !data[0]?.posting) {
|
||||
error(404, 'Posting not found');
|
||||
}
|
||||
|
||||
const posting = <Posting>data[0].posting;
|
||||
posting.company = <Company>data[0].company;
|
||||
posting.employer = <User>data[0].user;
|
||||
|
||||
if (posting.createdAt) {
|
||||
posting.createdAt = new Date(posting.createdAt);
|
||||
}
|
||||
if (posting.updatedAt) {
|
||||
posting.updatedAt = new Date(posting.updatedAt);
|
||||
}
|
||||
|
||||
const application = <Application>{
|
||||
id: data[0].application.application_id,
|
||||
candidateStatement: data[0].application.candidate_statement
|
||||
};
|
||||
|
||||
return { posting, application };
|
||||
}
|
||||
|
||||
export async function createApplication(application: Application): Promise<number> {
|
||||
try {
|
||||
|
||||
const response = await sql`
|
||||
INSERT INTO applications (posting_id, user_id, candidate_statement, created_at)
|
||||
VALUES (${application.postingId}, ${application.userId}, ${application.candidateStatement}, NOW()) RETURNING id;
|
||||
`;
|
||||
sendEmployerNotificationEmail(application.postingId).catch((err) => {
|
||||
console.error('Failed to send employer notification email: ', err);
|
||||
});
|
||||
return response[0].id;
|
||||
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes('duplicate key value')) {
|
||||
error(400, 'You have already applied for this job');
|
||||
} else {
|
||||
error(500, 'Internal server error while creating application');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function deleteApplication(id: number): Promise<void> {
|
||||
@@ -789,7 +880,6 @@ export async function deleteApplicationWithUser(
|
||||
applicationId: number,
|
||||
userId: number
|
||||
): Promise<void> {
|
||||
console.log(applicationId, userId);
|
||||
await sql`
|
||||
DELETE
|
||||
FROM applications
|
||||
@@ -798,6 +888,14 @@ export async function deleteApplicationWithUser(
|
||||
`;
|
||||
}
|
||||
|
||||
export async function editApplication(application: Application): Promise<void> {
|
||||
await sql`
|
||||
UPDATE applications
|
||||
SET candidate_statement = ${application.candidateStatement}
|
||||
WHERE id = ${application.id};
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getApplications(postingId: number): Promise<Application[]> {
|
||||
const data = await sql`
|
||||
SELECT a.id,
|
||||
|
||||
@@ -13,8 +13,13 @@ export function setJWT(cookies: Cookies, user: User) {
|
||||
if (process.env.JWT_SECRET === undefined) {
|
||||
throw new Error('JWT_SECRET not defined');
|
||||
}
|
||||
if (process.env.BASE_URL === undefined) {
|
||||
throw new Error('BASE_URL not defined');
|
||||
}
|
||||
|
||||
const secure: boolean = process.env.BASE_URL?.includes('https://');
|
||||
|
||||
const maxAge = 60 * 60 * 24 * 30; // 30 days
|
||||
const JWT = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '30d' });
|
||||
cookies.set('jwt', JWT, { maxAge, path: '/', httpOnly: false });
|
||||
cookies.set('jwt', JWT, { maxAge, path: '/', httpOnly: secure, secure: secure });
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function telFormatter(initial: string) {
|
||||
return initial;
|
||||
}
|
||||
|
||||
export const getCookieValue = (name: String) =>
|
||||
export const getCookieValue = (name: string) =>
|
||||
document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || '';
|
||||
|
||||
export function updateUserState() {
|
||||
|
||||
@@ -22,8 +22,13 @@
|
||||
<p>This one is on our end...</p>
|
||||
<p>We are working to resolve this as fast as possible.</p>
|
||||
{/if}
|
||||
{#if $page.status !== 404 && $page.status !== 403 && $page.status !== 401 && $page.status !== 500}
|
||||
{#if $page.status === 400}
|
||||
<p>Invalid request!</p>
|
||||
<p>Make sure you have correctly inputted all information.</p>
|
||||
{/if}
|
||||
{#if $page.status !== 404 && $page.status !== 403 && $page.status !== 401 && $page.status !== 500 && $page.status !== 400}
|
||||
<p>An unexpected error has occurred.</p>
|
||||
<p>Please try again later</p>
|
||||
{/if}
|
||||
<p class="mt-8">Error: {$page.error?.message}</p>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import type { PageProps } from './$types';
|
||||
import type { Company } from '$lib/types';
|
||||
|
||||
let applicationToDelete: number = $state(0);
|
||||
let { data }: PageProps = $props();
|
||||
@@ -13,6 +14,25 @@
|
||||
if (window.location.search.includes('refresh')) {
|
||||
location.replace(location.pathname);
|
||||
}
|
||||
|
||||
const acc = document.getElementsByClassName('accordion');
|
||||
|
||||
for (let i = 0; i < acc.length; i++) {
|
||||
acc[i].addEventListener('click', function (this: HTMLElement) {
|
||||
this.classList.toggle('active');
|
||||
this.children[1].innerHTML = this.classList.contains('active')
|
||||
? 'arrow_drop_up'
|
||||
: 'arrow_drop_down';
|
||||
|
||||
/* Toggle between hiding and showing the active panel */
|
||||
let panel = this.nextElementSibling as HTMLElement;
|
||||
if (panel.style.display === 'flex') {
|
||||
panel.style.display = 'none';
|
||||
} else {
|
||||
panel.style.display = 'flex';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
||||
@@ -26,11 +46,16 @@
|
||||
`https://ui-avatars.com/api/?background=random&format=svg&name=${data.user.fullName ? encodeURIComponent(data.user.fullName) : encodeURIComponent(data.user.username)}`;
|
||||
}
|
||||
|
||||
function logoFallback(e: Event) {
|
||||
function logoFallback(e: Event, company: Company) {
|
||||
(e.target as HTMLImageElement).src =
|
||||
`https://ui-avatars.com/api/?background=random&format=svg&name=${encodeURIComponent(data.user.company!.name!)}`;
|
||||
`https://ui-avatars.com/api/?background=random&format=svg&name=${encodeURIComponent(company.name!)}`;
|
||||
}
|
||||
|
||||
// function logoFallback(e: Event) {
|
||||
// (e.target as HTMLImageElement).src =
|
||||
// `https://ui-avatars.com/api/?background=random&format=svg&name=${encodeURIComponent(data.user.company!.name!)}`;
|
||||
// }
|
||||
|
||||
function signOut() {
|
||||
document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||
window.location.href = '/signin';
|
||||
@@ -198,7 +223,7 @@
|
||||
class="mb-2 inline-block rounded"
|
||||
src="/uploads/logos/{data.user.company.id}.jpg?timestamp=${Date.now()}"
|
||||
alt="Company Logo"
|
||||
onerror={logoFallback}
|
||||
onerror={(e) => logoFallback(e, data.user.company!)}
|
||||
height="32"
|
||||
width="32"
|
||||
/>
|
||||
@@ -236,28 +261,43 @@
|
||||
<div class="elevated separator-borders m-2 inline-block h-min w-full rounded">
|
||||
<div class="p-3 font-semibold">Pending applications</div>
|
||||
{#each data.applications as application}
|
||||
<div class="top-border flex justify-between p-3">
|
||||
<button class="top-border flex justify-between p-2 accordion">
|
||||
<div class="inline-block">
|
||||
<div class="font-semibold">
|
||||
<div class="font-semibold text-left">
|
||||
Applied to: <a
|
||||
class="hover-hyperlink font-normal"
|
||||
href="/postings/{application.postingId}">{application.postingTitle}</a
|
||||
>
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
<div class="font-semibold text-left">
|
||||
Applied on: <span class="font-normal"
|
||||
>{application.createdAt.toLocaleDateString('en-US', dateFormatOptions)}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="material-symbols-outlined danger-color inline-block"
|
||||
onclick={() => {
|
||||
<div class="material-symbols-outlined align-top pt-3 pr-3">arrow_drop_down</div>
|
||||
</button>
|
||||
<div class="panel hidden p-2 justify-between">
|
||||
<div class="inline-block">
|
||||
|
||||
<!-- <h2>Candidate Statement</h2>-->
|
||||
<h3 class="low-emphasis-text">
|
||||
Why do you believe you are the best fit for this role?
|
||||
</h3>
|
||||
<p class="whitespace-pre-wrap">{application.candidateStatement}</p>
|
||||
</div>
|
||||
<div class="mt-3 mr-3">
|
||||
|
||||
<a href="account/editapplication?id={application.id}" class="mr-3 material-symbols-outlined hyperlink-color">edit</a>
|
||||
<button
|
||||
class="material-symbols-outlined danger-color inline-block"
|
||||
onclick={() => {
|
||||
applicationToDelete = application.id;
|
||||
openConfirm();
|
||||
}}
|
||||
>delete
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { type Actions, error, fail, redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { editApplication, getEditApplicationPageData } from '$lib/db/index.server';
|
||||
import { getUserId, getUserPerms } from '$lib/index.server';
|
||||
import { PERMISSIONS } from '$lib/consts';
|
||||
import type { Application } from '$lib/types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url, cookies }) => {
|
||||
if (!url.searchParams.has('id')) {
|
||||
error(400, 'Missing required parameter: id');
|
||||
}
|
||||
|
||||
// Permission check (apply perm)
|
||||
const id = parseInt(url.searchParams.get('id')!);
|
||||
const perms = getUserPerms(cookies);
|
||||
if (perms >= 0 && (perms & PERMISSIONS.APPLY_FOR_JOBS) > 0) {
|
||||
return await getEditApplicationPageData(id);
|
||||
}
|
||||
error(403, 'Unauthorized');
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
// Application submission
|
||||
submit: async ({ request, cookies, params, url }) => {
|
||||
// Permission check (apply perm)
|
||||
if (!((getUserPerms(cookies) & PERMISSIONS.APPLY_FOR_JOBS) > 0)) {
|
||||
return fail(403, { errorMessage: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const data = await request.formData();
|
||||
const candidateStatement = data.get('candidateStatement')?.toString().trim();
|
||||
|
||||
// Statement data validation
|
||||
if (!candidateStatement || candidateStatement === '') {
|
||||
return fail(400, { errorMessage: 'Candidate statement is required' });
|
||||
}
|
||||
|
||||
// Push to DB and redirect
|
||||
if (!url.searchParams.has('id')) {
|
||||
error(400, 'Missing required parameter: id');
|
||||
}
|
||||
const id: number = parseInt(url.searchParams.get('id')!);
|
||||
|
||||
await editApplication(<Application>{ id, candidateStatement });
|
||||
redirect(301, `/postings`);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import type { PageProps } from './$types';
|
||||
import { employmentTypeDisplayName } from '$lib/shared.svelte';
|
||||
import type { Posting } from '$lib/types';
|
||||
|
||||
const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
};
|
||||
|
||||
function logoFallback(e: Event, posting: Posting) {
|
||||
(e.target as HTMLImageElement).src =
|
||||
`https://ui-avatars.com/api/?background=random&format=svg&name=${encodeURIComponent(posting.company.name || 'COMPANY')}`;
|
||||
}
|
||||
|
||||
let { data, form }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="base-container">
|
||||
<div class="content flex">
|
||||
<div class="elevated separator-borders ml-4 mt-4 inline-block h-min w-1/2 rounded p-4">
|
||||
<div class="bottom-border elevated-bg flex justify-between pb-2">
|
||||
<div class="inline-block">
|
||||
<img
|
||||
alt="Company Logo"
|
||||
class="inline-block rounded"
|
||||
height="64"
|
||||
onerror={(e) => logoFallback(e, data.posting)}
|
||||
src="/uploads/logos/{data.posting?.company.id}.jpg"
|
||||
width="64"
|
||||
/>
|
||||
<div class="inline-block pl-2 align-top">
|
||||
<h1>{data.posting.title}</h1>
|
||||
<h2>Company: {data.posting.company.name}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-on-elevated details-height overflow-y-scroll">
|
||||
<h2 class="pt-2 font-semibold">Contact</h2>
|
||||
<p>{data.posting.employer?.fullName} ({data.posting.employer?.username})</p>
|
||||
<a class="hover-hyperlink" href="mailto:{data.posting.employer?.email}"
|
||||
>{data.posting.employer?.email}</a
|
||||
>
|
||||
<a class="hover-hyperlink" href="tel:{data.posting.employer?.phone}"
|
||||
>{data.posting.employer?.phone}</a
|
||||
>
|
||||
<h2 class="pt-2 font-semibold">Details</h2>
|
||||
{#if data.posting.employmentType}
|
||||
<p>{employmentTypeDisplayName(data.posting.employmentType)}</p>
|
||||
{/if}
|
||||
{#if data.posting.address}
|
||||
<a
|
||||
href="https://www.google.com/maps/search/?api=1&query={data.posting.address}"
|
||||
class="block w-max"
|
||||
>Address: <span class="hover-hyperlink">{data.posting.address}</span></a
|
||||
>
|
||||
{/if}
|
||||
{#if data.posting.wage}
|
||||
<p>Wage: {data.posting.wage}</p>
|
||||
{/if}
|
||||
{#if data.posting.createdAt}
|
||||
<p>Posted: {data.posting.createdAt.toLocaleDateString('en-US', dateFormatOptions)}</p>
|
||||
{/if}
|
||||
{#if data.posting.link}
|
||||
<a href={data.posting.link} class="block w-max"
|
||||
>More information: <span class="hyperlink-color hyperlink-underline"
|
||||
>{data.posting.link}</span
|
||||
></a
|
||||
>
|
||||
{/if}
|
||||
{#if data.posting.flyerLink}
|
||||
<a href={data.posting.flyerLink} class="block w-max"
|
||||
>Flyer: <span class="hyperlink-color hyperlink-underline">{data.posting.flyerLink}</span
|
||||
></a
|
||||
>
|
||||
{/if}
|
||||
<h2 class="pt-2 font-semibold">Job Description</h2>
|
||||
<p class="whitespace-pre-wrap">{data.posting.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elevated separator-borders m-4 inline-block h-min w-1/2 rounded">
|
||||
<div class="bottom-border flex place-content-between">
|
||||
<div class="p-3 font-semibold">Edit Application</div>
|
||||
</div>
|
||||
<form autocomplete="off" class="px-4" method="POST" use:enhance>
|
||||
<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>
|
||||
<textarea
|
||||
class="w-full rounded font-normal"
|
||||
id="candidateStatement"
|
||||
name="candidateStatement"
|
||||
placeholder="Answer here"
|
||||
required
|
||||
rows="4"
|
||||
>{data.application.candidateStatement}</textarea>
|
||||
</div>
|
||||
<p>
|
||||
Your account information and résumé (if supplied) will be submitted along with this
|
||||
application. If there is any other information you would like the employer to know, please
|
||||
add it in the box above.
|
||||
</p>
|
||||
|
||||
{#if form?.errorMessage}
|
||||
<div class="mb-2 text-red-500">{form.errorMessage}</div>
|
||||
{/if}
|
||||
<button
|
||||
class="dull-primary-bg-color mb-4 mt-6 rounded px-2 py-1"
|
||||
formaction="?/submit&id={data.application.id}"
|
||||
type="submit"
|
||||
>Save application
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -10,7 +10,7 @@
|
||||
let passwordVisible = $state(false);
|
||||
|
||||
function showPassword() {
|
||||
const password = document.querySelector('input[name="password"]');
|
||||
const password = document.querySelector('input[name="newPassword"]');
|
||||
if (password) {
|
||||
if (password.getAttribute('type') === 'password') {
|
||||
password.setAttribute('type', 'text');
|
||||
@@ -122,8 +122,8 @@
|
||||
New password (optional)
|
||||
<input
|
||||
class="w-full rounded font-normal"
|
||||
id="password"
|
||||
name="password"
|
||||
id="newPassword"
|
||||
name="newPassword"
|
||||
placeholder="New password"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
let passwordVisible = $state(false);
|
||||
|
||||
function showPassword() {
|
||||
const password = document.querySelector('input[name="password"]');
|
||||
const password = document.querySelector('input[name="newPassword"]');
|
||||
if (password) {
|
||||
if (password.getAttribute('type') === 'password') {
|
||||
password.setAttribute('type', 'text');
|
||||
@@ -102,8 +102,8 @@
|
||||
Password <span class="text-red-500">*</span>
|
||||
<input
|
||||
class="w-full rounded font-normal"
|
||||
id="password"
|
||||
name="password"
|
||||
id="newPassword"
|
||||
name="newPassword"
|
||||
placeholder="Password"
|
||||
required
|
||||
type="password"
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
{#if userState.perms >= 0 && ((userState.perms & PERMISSIONS.MANAGE_POSTINGS) > 0 || ((userState.perms & PERMISSIONS.SUBMIT_POSTINGS) > 0 && userState.companyId === details.company.id))}
|
||||
<a
|
||||
class="dull-primary-bg-color inline-block h-min rounded-md px-2.5 py-1 align-top"
|
||||
href="/postings/{details.id}/manage">Manage posting</a
|
||||
href="/postings/{details.id}/manage/applications/">Manage posting</a
|
||||
>
|
||||
{:else if (userState.perms & PERMISSIONS.APPLY_FOR_JOBS) > 0}
|
||||
<a
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
{#if userState.perms >= 0 && ((userState.perms & PERMISSIONS.MANAGE_POSTINGS) > 0 || ((userState.perms & PERMISSIONS.SUBMIT_POSTINGS) > 0 && userState.companyId === data.posting.company.id))}
|
||||
<a
|
||||
class="dull-primary-bg-color inline-block h-min rounded-md px-2.5 py-1 align-top"
|
||||
href="/postings/{data.posting.id}/manage">Manage posting</a
|
||||
href="/postings/{data.posting.id}/manage/applications/">Manage posting</a
|
||||
>
|
||||
{:else if (userState.perms & PERMISSIONS.APPLY_FOR_JOBS) > 0}
|
||||
<a
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script>editApplication
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -26,16 +26,16 @@
|
||||
|
||||
/* Toggle between hiding and showing the active panel */
|
||||
let panel = this.nextElementSibling as HTMLElement;
|
||||
if (panel.style.display === 'block') {
|
||||
if (panel.style.display === 'flex') {
|
||||
panel.style.display = 'none';
|
||||
} else {
|
||||
panel.style.display = 'block';
|
||||
panel.style.display = 'flex';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let { data, form }: PageProps = $props();
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="content">
|
||||
@@ -52,7 +52,7 @@
|
||||
<img
|
||||
class="m-2 inline-block rounded"
|
||||
src="/uploads/avatars/{application.user?.id || 'default'}.svg"
|
||||
alt="Company Logo"
|
||||
alt="User Avatar"
|
||||
height="32"
|
||||
width="32"
|
||||
onerror={(e) => logoFallback(e, application)}
|
||||
@@ -70,36 +70,34 @@
|
||||
</button>
|
||||
<div class="panel hidden p-2">
|
||||
<div class="flex justify-between">
|
||||
<div class="inline-block">
|
||||
<div class="inline-block pr-4 align-top">
|
||||
{#if application.user?.email}
|
||||
<p>Email: {application.user.email}</p>
|
||||
{/if}
|
||||
{#if application.user?.phone}
|
||||
<p>Phone: {application.user.phone}</p>
|
||||
{/if}
|
||||
{#if application.createdAt}
|
||||
<p>
|
||||
Applied: {application.createdAt.toLocaleDateString('en-US', dateFormatOptions)}
|
||||
</p>
|
||||
{/if}
|
||||
{#if application.user?.resume}
|
||||
<a
|
||||
class="hyperlink-underline hyperlink-color"
|
||||
href="/uploads/resumes/{application.user.id}.pdf"
|
||||
target="_blank"
|
||||
>
|
||||
Résumé
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="left-border inline-block pl-4">
|
||||
<h2>Candidate Statement</h2>
|
||||
<h3 class="low-emphasis-text">
|
||||
Why do you believe you are the best fit for this role?
|
||||
</h3>
|
||||
<p class="whitespace-pre-wrap">{application.candidateStatement}</p>
|
||||
</div>
|
||||
<div class="inline-block pr-4 min-w-max">
|
||||
{#if application.user?.email}
|
||||
<p>Email: {application.user.email}</p>
|
||||
{/if}
|
||||
{#if application.user?.phone}
|
||||
<p>Phone: {application.user.phone}</p>
|
||||
{/if}
|
||||
{#if application.createdAt}
|
||||
<p>
|
||||
Applied: {application.createdAt.toLocaleDateString('en-US', dateFormatOptions)}
|
||||
</p>
|
||||
{/if}
|
||||
{#if application.user?.resume}
|
||||
<a
|
||||
class="hyperlink-underline hyperlink-color"
|
||||
href="/uploads/resumes/{application.user.id}.pdf"
|
||||
target="_blank"
|
||||
>
|
||||
Résumé
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="left-border inline-block pl-4">
|
||||
<h2>Candidate Statement</h2>
|
||||
<h3 class="low-emphasis-text">
|
||||
Why do you believe you are the best fit for this role?
|
||||
</h3>
|
||||
<p class="whitespace-pre-wrap">{application.candidateStatement}</p>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<button
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<div class="mt-4 flex justify-between">
|
||||
<button
|
||||
class="danger-bg-color rounded px-2 py-1"
|
||||
formaction="?/delete&id={data.posting.id}"
|
||||
formaction="?/delete"
|
||||
type="submit"
|
||||
>Delete posting
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user