381 lines
12 KiB
Svelte
381 lines
12 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import type { PageProps } from './$types';
|
|
import type { Company } from '$lib/types';
|
|
import { MediaQuery } from 'svelte/reactivity';
|
|
|
|
let applicationToDelete: number = $state(0);
|
|
let { data }: PageProps = $props();
|
|
let resumeState = $state(data.resumeExists);
|
|
|
|
onMount(() => {
|
|
if (!document.cookie.includes('jwt=')) {
|
|
window.location.href = '/signin';
|
|
}
|
|
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 = {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
};
|
|
|
|
function avatarFallback(e: Event) {
|
|
(e.target as HTMLImageElement).src =
|
|
`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, company: Company) {
|
|
(e.target as HTMLImageElement).src =
|
|
`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';
|
|
}
|
|
|
|
function openConfirm() {
|
|
document.getElementById('deleteConfirmModal')!.style.display = 'block';
|
|
}
|
|
|
|
function closeConfirm() {
|
|
document.getElementById('deleteConfirmModal')!.style.display = 'none';
|
|
}
|
|
|
|
function openUpload() {
|
|
document.getElementById('uploadModal')!.style.display = 'block';
|
|
}
|
|
|
|
function closeUpload() {
|
|
document.getElementById('uploadModal')!.style.display = 'none';
|
|
}
|
|
|
|
let files: FileList | undefined | null = $state();
|
|
let file: File | undefined | null = $state();
|
|
|
|
$effect(() => {
|
|
if (files) {
|
|
if (files[files.length - 1]?.type == 'application/pdf') {
|
|
file = files[files.length - 1];
|
|
console.log(file.name);
|
|
}
|
|
}
|
|
});
|
|
|
|
function dropHandler(event: DragEvent) {
|
|
event.preventDefault();
|
|
|
|
if (event.dataTransfer?.items) {
|
|
const items = event.dataTransfer.items;
|
|
if (items[0].kind === 'file') {
|
|
const upload = items[0].getAsFile();
|
|
if (upload?.type !== 'application/pdf') {
|
|
console.error('Invalid file type');
|
|
return;
|
|
}
|
|
file = upload;
|
|
}
|
|
}
|
|
}
|
|
|
|
function dragOverHandler(event: DragEvent) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
async function handleSubmit(event: SubmitEvent) {
|
|
event.preventDefault();
|
|
|
|
if (!file) {
|
|
alert('Please select a file first');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('resume', file);
|
|
|
|
try {
|
|
const response = await fetch('?/uploadResume', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
console.log('File uploaded successfully:', result);
|
|
closeUpload();
|
|
resumeState = true;
|
|
} else {
|
|
console.error('Upload failed');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error uploading file:', error);
|
|
}
|
|
}
|
|
|
|
const largeScreen = new MediaQuery('min-width: 640px');
|
|
</script>
|
|
|
|
<div class="base-container">
|
|
<div class="content my-2 flex">
|
|
{#if largeScreen.current}
|
|
<div class="elevated separator-borders mr-2 inline-block h-min min-w-max rounded align-top">
|
|
<div class="inline-block p-4">
|
|
<img
|
|
alt="User avatar"
|
|
class="mb-2 inline-block rounded-lg"
|
|
height="240"
|
|
id="avatar"
|
|
onerror={avatarFallback}
|
|
src="/uploads/avatars/{data.user.id
|
|
? data.user.id
|
|
: 'default'}.svg?timestamp=${Date.now()}"
|
|
width="240"
|
|
/>
|
|
{#if data.user.fullName}
|
|
<h2 class="text-center font-semibold">{data.user.fullName}</h2>
|
|
<h3 class="text-center">{data.user.username}</h3>
|
|
{/if}
|
|
{#if !data.user.fullName}
|
|
<h2 class="text-center font-semibold">{data.user.username}</h2>
|
|
{/if}
|
|
</div>
|
|
{#if data.user.email}
|
|
<div class="top-border p-3">
|
|
<span class="material-symbols-outlined align-middle">mail</span>
|
|
<a class="hover-hyperlink" href="mailto:{data.user.email}">{data.user.email}</a>
|
|
</div>
|
|
{/if}
|
|
{#if data.user.phone}
|
|
<div class="top-border p-3">
|
|
<span class="material-symbols-outlined align-middle">call</span>
|
|
<a class="hover-hyperlink" href="tel:{data.user.phone}">{data.user.phone}</a>
|
|
</div>
|
|
{/if}
|
|
{#if data.user.createdAt}
|
|
<div class="top-border p-3">
|
|
<span class="material-symbols-outlined align-middle">calendar_today</span>
|
|
Joined {data.user.createdAt.toLocaleDateString('en-US', dateFormatOptions)}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
<div class="inline-block w-full">
|
|
<div class="elevated separator-borders h-min w-full rounded">
|
|
<div class="bottom-border flex place-content-between">
|
|
<div class="p-3 font-semibold">User Details</div>
|
|
<div class="flex">
|
|
<a class="dull-primary-bg-color my-2 rounded-md px-2.5 py-1" href="/account/settings"
|
|
>Edit account</a
|
|
>
|
|
<button class="danger-border-color m-2 rounded-md border px-2.5 py-1" onclick={signOut}
|
|
>Sign out
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="p-3">
|
|
<div class="font-semibold">
|
|
ID: <span class="font-normal">{data.user.id}</span>
|
|
</div>
|
|
<div class="font-semibold">
|
|
Username: <span class="font-normal">{data.user.username}</span>
|
|
</div>
|
|
<div class="font-semibold">
|
|
Full Name: <span class="font-normal">{data.user.fullName}</span>
|
|
</div>
|
|
<div class="font-semibold">
|
|
Account active: <span class="material-symbols-outlined align-middle"
|
|
>{data.user.active ? 'check' : 'cancel'}</span
|
|
>
|
|
</div>
|
|
<div class="font-semibold">
|
|
Last sign-in: <span class="font-normal"
|
|
>{data.user.lastSignIn?.toLocaleDateString('en-US', dateFormatOptions)}</span
|
|
>
|
|
</div>
|
|
<!--{#if !data.user.company?.id}-->
|
|
<!-- <div class="pb-2 font-semibold">-->
|
|
<!-- Employer company: <span class="font-normal">N/A</span>-->
|
|
<!-- </div>-->
|
|
<!--{/if}-->
|
|
{#if data.user.company?.id}
|
|
<div class="font-semibold">
|
|
Employer company:
|
|
<div class="top-border mt-2 p-3 align-top">
|
|
<img
|
|
id="logo"
|
|
class="mb-2 inline-block rounded"
|
|
src="/uploads/logos/{data.user.company.id}.jpg?timestamp=${Date.now()}"
|
|
alt="Company Logo"
|
|
onerror={(e) => logoFallback(e, data.user.company!)}
|
|
height="32"
|
|
width="32"
|
|
/>
|
|
<div class="inline-block pl-2 align-top">
|
|
<div>{data.user.company.name}</div>
|
|
<div class="max-char-length font-normal">{data.user.company.description}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
<div>
|
|
<div class="font-semibold">Résumé:</div>
|
|
{#if resumeState}
|
|
<a class="pb-2" href="/uploads/resumes/{data.user.id}.pdf" target="_blank">
|
|
<button class="dull-primary-bg-color my-1 mr-2 rounded-md px-2.5 py-1"
|
|
><span class="material-symbols-outlined align-middle">open_in_new</span> View résumé
|
|
</button>
|
|
</a>
|
|
<button class="my-1 mb-2 rounded-md border px-2.5 py-1" onclick={openUpload}
|
|
><span class="material-symbols-outlined align-middle">upload</span> Upload new version
|
|
</button>
|
|
{:else}
|
|
<!-- <div class="">No résumé submitted.</div>-->
|
|
<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>
|
|
{/if}
|
|
</div>
|
|
<div class="top-border pt-2 font-semibold">
|
|
Permissions: <span class="font-normal">{data.user.perms}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{#if data.applications}
|
|
<div class="elevated separator-borders m-2 ml-0 inline-block h-min w-full rounded">
|
|
<div class="p-3 font-semibold">Pending applications</div>
|
|
{#each data.applications as application}
|
|
<button class="top-border accordion flex justify-between p-2">
|
|
<div class="inline-block">
|
|
<div class="text-left font-semibold">
|
|
Applied to: <a
|
|
class="hover-hyperlink font-normal"
|
|
href="/postings/{application.postingId}">{application.postingTitle}</a
|
|
>
|
|
</div>
|
|
<div class="text-left font-semibold">
|
|
Applied on: <span class="font-normal"
|
|
>{application.createdAt.toLocaleDateString('en-US', dateFormatOptions)}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="material-symbols-outlined pr-3 pt-3 align-top">arrow_drop_down</div>
|
|
</button>
|
|
<div class="panel hidden justify-between p-2">
|
|
<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="mr-3 mt-3 min-w-max">
|
|
<a
|
|
href="account/editapplication?id={application.id}"
|
|
class="material-symbols-outlined hyperlink-color mr-3">edit</a
|
|
>
|
|
<button
|
|
class="material-symbols-outlined danger-color inline-block"
|
|
onclick={() => {
|
|
applicationToDelete = application.id;
|
|
openConfirm();
|
|
}}
|
|
>delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<form class="modal" id="deleteConfirmModal" method="POST">
|
|
<div class="modal-content">
|
|
<div class="mb-2 inline-flex w-full justify-between">
|
|
<h2 class="font-semibold">Are you sure?</h2>
|
|
<button class="material-symbols-outlined" onclick={closeConfirm} type="button">close</button>
|
|
</div>
|
|
<p>This will permanently delete this application. This action cannot be undone.</p>
|
|
|
|
<div class="mt-4 flex justify-between">
|
|
<button
|
|
class="danger-bg-color rounded px-2 py-1"
|
|
formaction="?/deleteApplication&id={applicationToDelete}"
|
|
type="submit"
|
|
>Delete application
|
|
</button>
|
|
<button
|
|
class="separator-borders bg-color rounded px-2 py-1"
|
|
onclick={closeConfirm}
|
|
type="button"
|
|
>Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<form class="modal" id="uploadModal" method="POST" onsubmit={handleSubmit}>
|
|
<div class="modal-content">
|
|
<div class="mb-2 inline-flex w-full justify-between">
|
|
<h2 class="font-semibold">Résumé Upload</h2>
|
|
<button class="material-symbols-outlined" onclick={closeUpload} type="button">close</button>
|
|
</div>
|
|
<div>
|
|
<div
|
|
class="dull-primary-border-color rounded-lg border-2 border-dashed"
|
|
ondragover={dragOverHandler}
|
|
ondrop={dropHandler}
|
|
role="region"
|
|
>
|
|
<label class="cursor-pointer p-4" for="resume">
|
|
<div class="text-center">
|
|
<span class="material-symbols-outlined icon-48">cloud_upload</span>
|
|
<h3>Drag & drop your résumé here</h3>
|
|
<p class="">
|
|
or <span class="hyperlink-color hyperlink-underline">click here to browse.</span>
|
|
</p>
|
|
<input accept=".pdf" bind:files class="hidden" id="resume" type="file" />
|
|
</div>
|
|
</label>
|
|
</div>
|
|
<div class="mt-2">{file?.name}</div>
|
|
</div>
|
|
<div class="mt-4 flex justify-between">
|
|
<button class="dull-primary-bg-color rounded px-2 py-1" type="submit">Submit</button>
|
|
<button
|
|
class="separator-borders bg-color rounded px-2 py-1"
|
|
onclick={closeUpload}
|
|
type="button"
|
|
>Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|