Fixes
All checks were successful
ci / docker_image (push) Successful in 2m28s
ci / deploy (push) Successful in 24s

This commit is contained in:
Drake Marino 2025-06-19 17:14:18 -05:00
parent 8f41c04ca1
commit cbe002f4c4
17 changed files with 1052 additions and 109 deletions

640
package-lock.json generated
View File

@ -10,6 +10,7 @@
"dependencies": {
"@sveltejs/adapter-node": "^5.2.9",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/vite": "^4.1.10",
"autoprefixer": "^10.4.20",
"bcrypt": "^5.1.1",
"desm": "^1.3.1",
@ -40,7 +41,7 @@
"prettier-plugin-tailwindcss": "^0.6.9",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.9",
"tailwindcss": "^3.4.17",
"typescript": "^5.0.0",
"typescript-eslint": "^8.0.0",
"vite": "^6.0.0"
@ -763,6 +764,27 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@isaacs/fs-minipass/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@ -1329,6 +1351,360 @@
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
}
},
"node_modules/@tailwindcss/node": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz",
"integrity": "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"enhanced-resolve": "^5.18.1",
"jiti": "^2.4.2",
"lightningcss": "1.30.1",
"magic-string": "^0.30.17",
"source-map-js": "^1.2.1",
"tailwindcss": "4.1.10"
}
},
"node_modules/@tailwindcss/node/node_modules/jiti": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/@tailwindcss/node/node_modules/tailwindcss": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
"integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
"license": "MIT"
},
"node_modules/@tailwindcss/oxide": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz",
"integrity": "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.4",
"tar": "^7.4.3"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.10",
"@tailwindcss/oxide-darwin-arm64": "4.1.10",
"@tailwindcss/oxide-darwin-x64": "4.1.10",
"@tailwindcss/oxide-freebsd-x64": "4.1.10",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.10",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.10",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.10",
"@tailwindcss/oxide-linux-x64-musl": "4.1.10",
"@tailwindcss/oxide-wasm32-wasi": "4.1.10",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.10",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.10"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz",
"integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz",
"integrity": "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz",
"integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz",
"integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz",
"integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz",
"integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz",
"integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz",
"integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz",
"integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz",
"integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util",
"@emnapi/wasi-threads",
"tslib"
],
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@emnapi/wasi-threads": "^1.0.2",
"@napi-rs/wasm-runtime": "^0.2.10",
"@tybys/wasm-util": "^0.9.0",
"tslib": "^2.8.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz",
"integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz",
"integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide/node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/@tailwindcss/oxide/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/@tailwindcss/oxide/node_modules/minizlib": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@tailwindcss/oxide/node_modules/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"license": "MIT",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@tailwindcss/oxide/node_modules/tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"license": "ISC",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.0.1",
"mkdirp": "^3.0.1",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@tailwindcss/oxide/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/@tailwindcss/vite": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.10.tgz",
"integrity": "sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A==",
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.1.10",
"@tailwindcss/oxide": "4.1.10",
"tailwindcss": "4.1.10"
},
"peerDependencies": {
"vite": "^5.2.0 || ^6"
}
},
"node_modules/@tailwindcss/vite/node_modules/tailwindcss": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
"integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
"license": "MIT"
},
"node_modules/@types/bcrypt": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz",
@ -2449,9 +2825,9 @@
}
},
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@ -2543,6 +2919,19 @@
"node": ">= 0.8"
}
},
"node_modules/enhanced-resolve": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -3385,6 +3774,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@ -3783,6 +4178,234 @@
"node": ">= 0.8.0"
}
},
"node_modules/lightningcss": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-darwin-arm64": "1.30.1",
"lightningcss-darwin-x64": "1.30.1",
"lightningcss-freebsd-x64": "1.30.1",
"lightningcss-linux-arm-gnueabihf": "1.30.1",
"lightningcss-linux-arm64-gnu": "1.30.1",
"lightningcss-linux-arm64-musl": "1.30.1",
"lightningcss-linux-x64-gnu": "1.30.1",
"lightningcss-linux-x64-musl": "1.30.1",
"lightningcss-win32-arm64-msvc": "1.30.1",
"lightningcss-win32-x64-msvc": "1.30.1"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
"cpu": [
"arm"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@ -5823,6 +6446,15 @@
"node": ">=8.10.0"
}
},
"node_modules/tapable": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",

View File

@ -31,7 +31,7 @@
"prettier-plugin-tailwindcss": "^0.6.9",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.9",
"tailwindcss": "^3.4.17",
"typescript": "^5.0.0",
"typescript-eslint": "^8.0.0",
"vite": "^6.0.0"
@ -39,6 +39,7 @@
"dependencies": {
"@sveltejs/adapter-node": "^5.2.9",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/vite": "^4.1.10",
"autoprefixer": "^10.4.20",
"bcrypt": "^5.1.1",
"desm": "^1.3.1",

View File

@ -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;

View File

@ -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,

View File

@ -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 });
}

View File

@ -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() {

View File

@ -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>

View File

@ -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>

View File

@ -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`);
}
};

View File

@ -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>

View File

@ -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"
/>

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,4 @@
<script>
<script>editApplication
import { onMount } from 'svelte';
onMount(() => {

View File

@ -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

View File

@ -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>