Compare commits

..
26 Commits
Author SHA1 Message Date
drake 60ad76d48a raise new item and increase height
ci / docker_image (push) Successful in 3m24s
ci / deploy (push) Successful in 1m2s
2026-07-01 13:02:21 -05:00
drake 38fe82731e t to i
ci / docker_image (push) Successful in 2m15s
ci / deploy (push) Successful in 30s
2026-06-30 13:12:56 -05:00
drake 5cce7928ec ordering
ci / docker_image (push) Successful in 2m17s
ci / deploy (push) Successful in 26s
2026-06-30 13:05:01 -05:00
drake 6f375cd990 propagation
ci / docker_image (push) Successful in 2m38s
ci / deploy (push) Successful in 29s
2026-06-30 12:57:04 -05:00
drake 681b15e793 autofocus
ci / docker_image (push) Successful in 2m21s
ci / deploy (push) Successful in 31s
2026-06-30 12:43:04 -05:00
drake 53a75f6573 ux updates
ci / docker_image (push) Successful in 3m0s
ci / deploy (push) Successful in 1m5s
2026-06-30 12:19:06 -05:00
drake eb40444e46 db fixed
ci / docker_image (push) Successful in 2m22s
ci / deploy (push) Successful in 29s
2026-06-29 17:09:32 -05:00
drake 1b179f9d16 downscale pre-ai
ci / docker_image (push) Successful in 2m28s
ci / deploy (push) Successful in 31s
2026-06-29 16:21:40 -05:00
drake 7e6e31aa83 updates before nlc
ci / docker_image (push) Successful in 2m55s
ci / deploy (push) Successful in 29s
2026-06-29 14:43:12 -05:00
drake ee19bd9dea round 2
ci / docker_image (push) Successful in 2m9s
ci / deploy (push) Successful in 25s
2026-04-13 03:45:02 +00:00
drake aa501b4c16 apostrophe
ci / deploy (push) Blocked by required conditions
ci / docker_image (push) Has been cancelled
2026-04-13 03:44:38 +00:00
drake feb80e41ea opacity
ci / docker_image (push) Successful in 2m9s
ci / deploy (push) Successful in 24s
2026-04-12 16:28:39 -05:00
drake d9607afd2c spinner sizing
ci / docker_image (push) Successful in 2m12s
ci / deploy (push) Successful in 28s
2026-04-12 15:43:20 -05:00
drake adaf9534b5 visuals
ci / docker_image (push) Successful in 2m9s
ci / deploy (push) Successful in 25s
2026-04-12 01:24:33 -05:00
drake 8ce4ff1210 unprint
ci / docker_image (push) Successful in 2m10s
ci / deploy (push) Successful in 24s
2026-04-11 23:54:15 -05:00
drake 8c26522ec2 test
ci / docker_image (push) Successful in 2m9s
ci / deploy (push) Successful in 25s
2026-04-11 23:49:59 -05:00
drake 264b44f555 fix stale stuff?
ci / docker_image (push) Successful in 2m7s
ci / deploy (push) Successful in 25s
2026-04-11 23:40:23 -05:00
drake bce9a19776 fix account save
ci / docker_image (push) Successful in 2m17s
ci / deploy (push) Successful in 50s
2026-04-11 09:28:45 -05:00
drake e6e4b068c9 Update .gitea/workflows/ci.yaml
ci / docker_image (push) Successful in 2m18s
ci / deploy (push) Successful in 57s
2026-04-04 22:50:19 +00:00
drake 9c221fa828 sonner
ci / docker_image (push) Failing after 2m25s
ci / deploy (push) Has been skipped
2026-04-04 17:37:54 -05:00
drake 3925f4e67a settings and fixes
ci / deploy (push) Blocked by required conditions
ci / docker_image (push) Has been cancelled
2026-04-04 17:37:17 -05:00
drake a0efc6c628 updates 2026-03-30 15:44:11 -05:00
drake 09cdce350d fix inquiry message emails
ci / docker_image (push) Successful in 3m12s
ci / deploy (push) Successful in 27s
2026-02-07 01:27:47 -06:00
drake 81f0113ce2 cleanup inquiries
ci / docker_image (push) Successful in 3m10s
ci / deploy (push) Successful in 26s
2026-02-07 01:11:04 -06:00
drake 1dcbca0445 login cleanup
ci / deploy (push) Blocked by required conditions
ci / docker_image (push) Has been cancelled
2026-02-07 01:08:35 -06:00
drake 2a1436515b Readme fix
ci / docker_image (push) Successful in 3m14s
ci / deploy (push) Successful in 30s
2026-02-07 01:03:54 -06:00
31 changed files with 943 additions and 553 deletions
-3
View File
@@ -12,9 +12,6 @@ jobs:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Docker
run: curl -fsSL https://get.docker.com | sh
- name: Login to Docker Registry - name: Login to Docker Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
+1 -1
View File
@@ -1,4 +1,4 @@
# CareerConnect - FBLA 2025 # Westuffind - FBLA 2026
## Overview ## Overview
+290 -153
View File
@@ -8,6 +8,7 @@
"name": "fbla26", "name": "fbla26",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@rive-app/canvas": "^2.37.1",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"desm": "^1.3.1", "desm": "^1.3.1",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
@@ -24,7 +25,7 @@
"@eslint/compat": "^1.4.0", "@eslint/compat": "^1.4.0",
"@eslint/js": "^9.36.0", "@eslint/js": "^9.36.0",
"@internationalized/date": "^3.10.1", "@internationalized/date": "^3.10.1",
"@lucide/svelte": "^0.561.0", "@lucide/svelte": "^1.7.0",
"@sveltejs/adapter-node": "^5.3.2", "@sveltejs/adapter-node": "^5.3.2",
"@sveltejs/kit": "2.49.5", "@sveltejs/kit": "2.49.5",
"@sveltejs/vite-plugin-svelte": "^6.2.0", "@sveltejs/vite-plugin-svelte": "^6.2.0",
@@ -47,6 +48,7 @@
"prettier-plugin-tailwindcss": "^0.6.14", "prettier-plugin-tailwindcss": "^0.6.14",
"svelte": "^5.39.5", "svelte": "^5.39.5",
"svelte-check": "^4.3.2", "svelte-check": "^4.3.2",
"svelte-sonner": "^1.1.0",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"tailwind-variants": "^3.2.2", "tailwind-variants": "^3.2.2",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.13",
@@ -1337,9 +1339,9 @@
} }
}, },
"node_modules/@lucide/svelte": { "node_modules/@lucide/svelte": {
"version": "0.561.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.561.0.tgz", "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.7.0.tgz",
"integrity": "sha512-vofKV2UFVrKE6I4ewKJ3dfCXSV6iP6nWVmiM83MLjsU91EeJcEg7LoWUABLp/aOTxj1HQNbJD1f3g3L0JQgH9A==", "integrity": "sha512-YytBKOUBGox7yWcykZnYxOkn5WpR5G1qYXLYXV/j1B79SOTTEKzB+s5yF5Rq9l9OkweDStNH2b4yTqfvhEhV8g==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peerDependencies": { "peerDependencies": {
@@ -1391,6 +1393,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rive-app/canvas": {
"version": "2.37.1",
"resolved": "https://registry.npmjs.org/@rive-app/canvas/-/canvas-2.37.1.tgz",
"integrity": "sha512-Uw14b9vXD5g56ttUktlV+mXX1EOpAwqOoMS+DAVSs6EDoeaUYM4bRSKB1glDiOotqjshLSfNihua73mLPp10cQ==",
"license": "MIT"
},
"node_modules/@rollup/plugin-commonjs": { "node_modules/@rollup/plugin-commonjs": {
"version": "28.0.6", "version": "28.0.6",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz",
@@ -1488,9 +1496,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
"integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -1502,9 +1510,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
"integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1516,9 +1524,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1530,9 +1538,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
"integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1544,9 +1552,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
"integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1558,9 +1566,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
"integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1572,13 +1580,16 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
"integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1586,13 +1597,16 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
"integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1600,13 +1614,16 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
"integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1614,13 +1631,16 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
"integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1628,13 +1648,33 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loong64-gnu": { "node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
"integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
"integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
"cpu": [
"loong64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1642,13 +1682,33 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-ppc64-gnu": { "node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
"integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
"integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1656,13 +1716,16 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
"integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1670,13 +1733,16 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-musl": { "node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
"integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1684,13 +1750,16 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
"integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1698,13 +1767,16 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
"integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1712,9 +1784,26 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
"integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
"integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1722,13 +1811,13 @@
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "openbsd"
] ]
}, },
"node_modules/@rollup/rollup-openharmony-arm64": { "node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
"integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1740,9 +1829,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
"integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1754,9 +1843,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
"integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -1768,9 +1857,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-gnu": { "node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
"integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1782,9 +1871,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
"integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2342,6 +2431,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.0", "version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
@@ -2493,7 +2588,7 @@
"version": "8.46.0", "version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz",
"integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==", "integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2533,9 +2628,9 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.2", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2543,13 +2638,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
@@ -2797,9 +2892,9 @@
} }
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2946,9 +3041,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3294,10 +3389,9 @@
} }
}, },
"node_modules/devalue": { "node_modules/devalue": {
"version": "5.6.2", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.7.1.tgz",
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", "integrity": "sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/dom-accessibility-api": { "node_modules/dom-accessibility-api": {
@@ -3648,12 +3742,20 @@
} }
}, },
"node_modules/esrap": { "node_modules/esrap": {
"version": "2.1.0", "version": "2.2.5",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.5.tgz",
"integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", "integrity": "sha512-/yLB1538mag+dn0wsePTe8C0rDIjUOaJpMs2McodSzmM2msWcZsBSdRtg6HOBt0A/r82BN+Md3pgwSc/uWt2Ig==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15" "@jridgewell/sourcemap-codec": "^1.4.15"
},
"peerDependencies": {
"@typescript-eslint/types": "^8.2.0"
},
"peerDependenciesMeta": {
"@typescript-eslint/types": {
"optional": true
}
} }
}, },
"node_modules/esrecurse": { "node_modules/esrecurse": {
@@ -3925,9 +4027,9 @@
} }
}, },
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.3.3", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -4287,9 +4389,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -4805,9 +4907,9 @@
} }
}, },
"node_modules/micromatch/node_modules/picomatch": { "node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -4853,9 +4955,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@@ -5174,9 +5276,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "8.3.0", "version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@@ -5208,9 +5310,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -5312,9 +5414,9 @@
} }
}, },
"node_modules/postcss-load-config/node_modules/yaml": { "node_modules/postcss-load-config/node_modules/yaml": {
"version": "1.10.2", "version": "1.10.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
"devOptional": true, "devOptional": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
@@ -5578,9 +5680,9 @@
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.1", "version": "6.15.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"side-channel": "^1.1.0" "side-channel": "^1.1.0"
@@ -5701,9 +5803,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.52.4", "version": "4.60.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -5717,28 +5819,31 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.4", "@rollup/rollup-android-arm-eabi": "4.60.1",
"@rollup/rollup-android-arm64": "4.52.4", "@rollup/rollup-android-arm64": "4.60.1",
"@rollup/rollup-darwin-arm64": "4.52.4", "@rollup/rollup-darwin-arm64": "4.60.1",
"@rollup/rollup-darwin-x64": "4.52.4", "@rollup/rollup-darwin-x64": "4.60.1",
"@rollup/rollup-freebsd-arm64": "4.52.4", "@rollup/rollup-freebsd-arm64": "4.60.1",
"@rollup/rollup-freebsd-x64": "4.52.4", "@rollup/rollup-freebsd-x64": "4.60.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
"@rollup/rollup-linux-arm-musleabihf": "4.52.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
"@rollup/rollup-linux-arm64-gnu": "4.52.4", "@rollup/rollup-linux-arm64-gnu": "4.60.1",
"@rollup/rollup-linux-arm64-musl": "4.52.4", "@rollup/rollup-linux-arm64-musl": "4.60.1",
"@rollup/rollup-linux-loong64-gnu": "4.52.4", "@rollup/rollup-linux-loong64-gnu": "4.60.1",
"@rollup/rollup-linux-ppc64-gnu": "4.52.4", "@rollup/rollup-linux-loong64-musl": "4.60.1",
"@rollup/rollup-linux-riscv64-gnu": "4.52.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
"@rollup/rollup-linux-riscv64-musl": "4.52.4", "@rollup/rollup-linux-ppc64-musl": "4.60.1",
"@rollup/rollup-linux-s390x-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
"@rollup/rollup-linux-x64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-musl": "4.60.1",
"@rollup/rollup-linux-x64-musl": "4.52.4", "@rollup/rollup-linux-s390x-gnu": "4.60.1",
"@rollup/rollup-openharmony-arm64": "4.52.4", "@rollup/rollup-linux-x64-gnu": "4.60.1",
"@rollup/rollup-win32-arm64-msvc": "4.52.4", "@rollup/rollup-linux-x64-musl": "4.60.1",
"@rollup/rollup-win32-ia32-msvc": "4.52.4", "@rollup/rollup-openbsd-x64": "4.60.1",
"@rollup/rollup-win32-x64-gnu": "4.52.4", "@rollup/rollup-openharmony-arm64": "4.60.1",
"@rollup/rollup-win32-x64-msvc": "4.52.4", "@rollup/rollup-win32-arm64-msvc": "4.60.1",
"@rollup/rollup-win32-ia32-msvc": "4.60.1",
"@rollup/rollup-win32-x64-gnu": "4.60.1",
"@rollup/rollup-win32-x64-msvc": "4.60.1",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@@ -6179,21 +6284,23 @@
} }
}, },
"node_modules/svelte": { "node_modules/svelte": {
"version": "5.39.11", "version": "5.55.3",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.39.11.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.3.tgz",
"integrity": "sha512-8MxWVm2+3YwrFbPaxOlT1bbMi6OTenrAgks6soZfiaS8Fptk4EVyRIFhJc3RpO264EeSNwgjWAdki0ufg4zkGw==", "integrity": "sha512-dS1N+i3bA1v+c4UDb750MlN5vCO82G6vxh8HeTsPsTdJ1BLsN1zxSyDlIdBBqUjqZ/BxEwM8UrFf98aaoVnZFQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/remapping": "^2.3.4", "@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
"@sveltejs/acorn-typescript": "^1.0.5", "@sveltejs/acorn-typescript": "^1.0.5",
"@types/estree": "^1.0.5", "@types/estree": "^1.0.5",
"@types/trusted-types": "^2.0.7",
"acorn": "^8.12.1", "acorn": "^8.12.1",
"aria-query": "^5.3.1", "aria-query": "5.3.1",
"axobject-query": "^4.1.0", "axobject-query": "^4.1.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"devalue": "^5.6.4",
"esm-env": "^1.2.1", "esm-env": "^1.2.1",
"esrap": "^2.1.0", "esrap": "^2.2.4",
"is-reference": "^3.0.3", "is-reference": "^3.0.3",
"locate-character": "^3.0.0", "locate-character": "^3.0.0",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
@@ -6311,6 +6418,36 @@
} }
} }
}, },
"node_modules/svelte-sonner": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.1.0.tgz",
"integrity": "sha512-3lYM6ZIqWe+p9vwwWHGWP/ZdvHiUtzURsud2quIxivrX4rvpXh6i+geBGn0m3JS6KwW6W8VgbOl3xQMcDuh6gg==",
"dev": true,
"license": "MIT",
"dependencies": {
"runed": "^0.28.0"
},
"peerDependencies": {
"svelte": "^5.0.0"
}
},
"node_modules/svelte-sonner/node_modules/runed": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz",
"integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==",
"dev": true,
"funding": [
"https://github.com/sponsors/huntabyte",
"https://github.com/sponsors/tglide"
],
"license": "MIT",
"dependencies": {
"esm-env": "^1.0.0"
},
"peerDependencies": {
"svelte": "^5.7.0"
}
},
"node_modules/svelte-toolbelt": { "node_modules/svelte-toolbelt": {
"version": "0.10.6", "version": "0.10.6",
"resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz", "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz",
@@ -6333,9 +6470,9 @@
} }
}, },
"node_modules/svelte/node_modules/aria-query": { "node_modules/svelte/node_modules/aria-query": {
"version": "5.3.2", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -6410,11 +6547,11 @@
} }
}, },
"node_modules/tar": { "node_modules/tar": {
"version": "7.5.1", "version": "7.5.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz",
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==",
"dev": true, "dev": true,
"license": "ISC", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"@isaacs/fs-minipass": "^4.0.0", "@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0", "chownr": "^3.0.0",
+4 -2
View File
@@ -19,7 +19,7 @@
"@eslint/compat": "^1.4.0", "@eslint/compat": "^1.4.0",
"@eslint/js": "^9.36.0", "@eslint/js": "^9.36.0",
"@internationalized/date": "^3.10.1", "@internationalized/date": "^3.10.1",
"@lucide/svelte": "^0.561.0", "@lucide/svelte": "^1.7.0",
"@sveltejs/adapter-node": "^5.3.2", "@sveltejs/adapter-node": "^5.3.2",
"@sveltejs/kit": "2.49.5", "@sveltejs/kit": "2.49.5",
"@sveltejs/vite-plugin-svelte": "^6.2.0", "@sveltejs/vite-plugin-svelte": "^6.2.0",
@@ -42,6 +42,7 @@
"prettier-plugin-tailwindcss": "^0.6.14", "prettier-plugin-tailwindcss": "^0.6.14",
"svelte": "^5.39.5", "svelte": "^5.39.5",
"svelte-check": "^4.3.2", "svelte-check": "^4.3.2",
"svelte-sonner": "^1.1.0",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"tailwind-variants": "^3.2.2", "tailwind-variants": "^3.2.2",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.13",
@@ -63,6 +64,7 @@
"postgres": "^3.4.7", "postgres": "^3.4.7",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "^6.0.3",
"valibot": "^1.2.0" "valibot": "^1.2.0",
"@rive-app/canvas": "^2.37.1"
} }
} }
+77 -9
View File
@@ -8,20 +8,22 @@
--radius: 0.65rem; --radius: 0.65rem;
--background: oklch(1 0 0); --background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823); --foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0); --card: oklch(0.95 0 0);
--card-foreground: oklch(0.141 0.005 285.823); --card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0); --popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823); --popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.5852 0.25 292.717); --primary: oklch(0.5852 0.25 292.717);
--primary-foreground: oklch(0.969 0.016 293.756); --primary-foreground: oklch(0.969 0.016 293.756);
--dull-primary: oklch(0.8225 0.0927 293.01);
--dull-primary-foreground: oklch(0.0 0.016 293.756);
--secondary: oklch(0.967 0.001 286.375); --secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885); --secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375); --muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938); --muted-foreground: oklch(0.502 0.016 285.938);
--accent: oklch(0.967 0.001 286.375); --accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885); --accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32); --border: oklch(0.9 0.004 286.32);
--input: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32);
--ring: oklch(0.606 0.25 292.717); --ring: oklch(0.606 0.25 292.717);
--chart-1: oklch(0.646 0.222 41.116); --chart-1: oklch(0.646 0.222 41.116);
@@ -37,10 +39,10 @@
--sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32); --sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.606 0.25 292.717); --sidebar-ring: oklch(0.606 0.25 292.717);
--warning: oklch(0.84 0.16 84); --warning: oklch(0.6 0.16 84);
--error: oklch(0.577 0.245 27.325); --error: oklch(0.577 0.245 27.325);
--positive: oklch(0.5 0.2067 147.18); --positive: oklch(0.5 0.2067 147.18);
--action: oklch(0.5852 0.2263 260.47); --action: oklch(0.4852 0.2263 260.47);
} }
.dark { .dark {
@@ -50,12 +52,15 @@
--card-foreground: oklch(0.985 0 0); --card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885); --popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0); --popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.541 0.281 293.009); --primary: oklch(0.5 0.331 293.009);
--primary-foreground: oklch(0.969 0.016 293.756); --primary-foreground: oklch(0.0 0.016 293.756);
--dull-primary: oklch(0.3 0.281 293.009);
--dull-primary-foreground: oklch(0.969 0.016 293.756);
/*--primary-foreground: oklch(0.969 0.016 293.756);*/
--secondary: oklch(0.274 0.006 286.033); --secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0); --secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033); --muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067); --muted-foreground: oklch(0.655 0.015 286.067);
--accent: oklch(0.274 0.006 286.033); --accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216); --destructive: oklch(0.704 0.191 22.216);
@@ -78,7 +83,7 @@
--warning: oklch(0.84 0.16 84); --warning: oklch(0.84 0.16 84);
--error: oklch(0.704 0.191 22.216); --error: oklch(0.704 0.191 22.216);
--positive: oklch(0.7522 0.2067 147.18); --positive: oklch(0.7522 0.2067 147.18);
--action: oklch(0.6098 0.1872 260.47); --action: oklch(0.7098 0.1872 260.47);
} }
@theme inline { @theme inline {
@@ -94,6 +99,8 @@
--color-popover-foreground: var(--popover-foreground); --color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary); --color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground); --color-primary-foreground: var(--primary-foreground);
--color-dull-primary: var(--dull-primary);
--color-dull-primary-foreground: var(--dull-primary-foreground);
--color-secondary: var(--secondary); --color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground); --color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted); --color-muted: var(--muted);
@@ -133,3 +140,64 @@
} }
} }
@custom-variant data-open {
&:where([data-state="open"]), &:where([data-open]:not([data-open="false"])) {
@slot;
}
}
@custom-variant data-closed {
&:where([data-state="closed"]), &:where([data-closed]:not([data-closed="false"])) {
@slot;
}
}
@custom-variant data-checked {
&:where([data-state="checked"]), &:where([data-checked]:not([data-checked="false"])) {
@slot;
}
}
@custom-variant data-unchecked {
&:where([data-state="unchecked"]), &:where([data-unchecked]:not([data-unchecked="false"])) {
@slot;
}
}
@custom-variant data-selected {
&:where([data-selected]) {
@slot;
}
}
@custom-variant data-disabled {
&:where([data-disabled="true"]), &:where([data-disabled]:not([data-disabled="false"])) {
@slot;
}
}
@custom-variant data-active {
&:where([data-state="active"]), &:where([data-active]:not([data-active="false"])) {
@slot;
}
}
@custom-variant data-horizontal {
&:where([data-orientation="horizontal"]) {
@slot;
}
}
@custom-variant data-vertical {
&:where([data-orientation="vertical"]) {
@slot;
}
}
@utility no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
+9 -4
View File
@@ -1,4 +1,4 @@
import type { User, UserPayload } from '$lib/types/user'; import { DefaultUserSettings, type User, type UserPayload } from '$lib/types/user';
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
import sql from '$lib/db/db.server'; import sql from '$lib/db/db.server';
import { type Cookies, error } from '@sveltejs/kit'; import { type Cookies, error } from '@sveltejs/kit';
@@ -9,7 +9,10 @@ export function setJWTCookie(cookies: Cookies, user: User) {
const payload = { const payload = {
id: user.id, id: user.id,
email: user.email, email: user.email,
name: user.name name: user.name,
settings: user.settings || DefaultUserSettings,
createdAt: user.createdAt,
lastSignIn: user.lastSignIn
}; };
if (process.env.JWT_SECRET === undefined) { if (process.env.JWT_SECRET === undefined) {
@@ -61,12 +64,14 @@ export async function login(email: string, password: string): Promise<User> {
SELECT * FROM users SELECT * FROM users
WHERE email = ${email}; WHERE email = ${email};
`; `;
if (typeof user.settings == 'string') {
user.settings = JSON.parse(user.settings);
}
if (user) { if (user) {
if (await bcrypt.compare(password, user.passwordHash!)) { if (await bcrypt.compare(password, user.passwordHash!)) {
delete user.passwordHash; delete user.passwordHash;
// eslint-disable-next-line @typescript-eslint/no-unused-expressions await sql`
sql`
UPDATE users UPDATE users
SET last_sign_in = NOW() SET last_sign_in = NOW()
WHERE id = ${user.id}; WHERE id = ${user.id};
@@ -15,7 +15,7 @@
</script> </script>
<Dialog.Root bind:open> <Dialog.Root bind:open>
<Dialog.Content> <Dialog.Content onOpenAutoFocus={(e) => e.preventDefault()}>
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Claim Item</Dialog.Title> <Dialog.Title>Claim Item</Dialog.Title>
<Dialog.Description> <Dialog.Description>
@@ -44,10 +44,7 @@
<div class="inline-block"> <div class="inline-block">
<div class="flex-1">{item?.description}</div> <div class="flex-1">{item?.description}</div>
{#if item?.foundLocation} {#if item?.foundLocation}
<div class="mt-2"> <span class="mt-2"><strong class="mr-1">Found at:</strong> {item.foundLocation}</span>
<LocationIcon class="float-left mr-1" size={24} />
<div>{item?.foundLocation}</div>
</div>
{/if} {/if}
</div> </div>
</div> </div>
@@ -117,10 +117,13 @@
<Dialog.Footer class="mt-4"> <Dialog.Footer class="mt-4">
<div class="flex justify-between w-full"> <div class="flex justify-between w-full">
<Button variant="destructive" <Dialog.Close
onclick={() => {approveDenyItem({id: item?.id || 0, approved: false}); invalidateAll();}}> type="button"
class={buttonVariants({ variant: "destructive" })}
onclick={async () => {await approveDenyItem({ id: item?.id || 0, approved: false }); await invalidateAll(); }}
>
Delete Delete
</Button> </Dialog.Close>
<div> <div>
@@ -30,7 +30,6 @@
} }
function handleFiles(files: FileList | null) { function handleFiles(files: FileList | null) {
console.log('handleFiles');
if (!files || files.length === 0) return; if (!files || files.length === 0) return;
const selected = files[0]; const selected = files[0];
@@ -16,7 +16,7 @@
</script> </script>
<Dialog.Root bind:open> <Dialog.Root bind:open>
<Dialog.Content> <Dialog.Content onOpenAutoFocus={(e) => e.preventDefault()}>
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Item Inquiry</Dialog.Title> <Dialog.Title>Item Inquiry</Dialog.Title>
<Dialog.Description> <Dialog.Description>
@@ -45,10 +45,7 @@
<div class="inline-block"> <div class="inline-block">
<div class="flex-1">{item?.description}</div> <div class="flex-1">{item?.description}</div>
{#if item?.foundLocation} {#if item?.foundLocation}
<div class="mt-2"> <span class="mt-2 flex"><strong class="mr-1">Found at:</strong> {item.foundLocation}</span>
<LocationIcon class="float-left mr-1" size={24} />
<div>{item?.foundLocation}</div>
</div>
{/if} {/if}
</div> </div>
</div> </div>
+49 -39
View File
@@ -16,43 +16,45 @@
import { approveDenyItem, restoreClaimedItem } from '$lib/db/items.remote'; import { approveDenyItem, restoreClaimedItem } from '$lib/db/items.remote';
import { invalidateAll } from '$app/navigation'; import { invalidateAll } from '$app/navigation';
import NoImagePlaceholder from './no-image-placeholder.svelte'; import NoImagePlaceholder from './no-image-placeholder.svelte';
import type { User } from '$lib/types/user';
export let item: Item = <Item>{}; export let item: Item = <Item>{};
export let admin = false; export let user: User | null = null;
export let editCallback: (item: Item) => void; export let editCallback: (item: Item) => void;
export let inquireCallback: (item: Item) => void; export let inquireCallback: (item: Item) => void;
export let claimCallback: (item: Item) => void; export let claimCallback: (item: Item) => void;
let timeSincePosted: number | string = (new Date().getTime() - item.foundDate.getTime()) / 1000 / 60 / 60 / 24; // days // Normalize the foundDate in case it was serialized (string) in prod builds
if (timeSincePosted < 1) { // (SSR/JSON serialization often turns Date objects into strings).
timeSincePosted = '<1'; const foundDateObj: Date = item?.foundDate instanceof Date
} else { ? item.foundDate
timeSincePosted = Math.round(timeSincePosted); : new Date(item?.foundDate);
}
// Compute days since posted as a number (always a number, fall back to 0 on invalid date)
let timeSincePosted: number = (() => {
const t = foundDateObj?.getTime?.();
if (!t || Number.isNaN(t)) return 0;
return (Date.now() - t) / 1000 / 60 / 60 / 24; // days (float)
})();
// If you want to round/display differently you can do that in the template.
</script> </script>
<div <div
class="h-full bg-card text-card-foreground flex flex-col gap-2 rounded-xl border shadow-sm max-w-sm overflow-hidden min-w-2xs"> class="text-left h-full bg-card text-card-foreground flex flex-col gap-2 rounded-xl border shadow-sm max-w-sm overflow-hidden min-w-2xs" onclick={() => {claimCallback(item)}}>
{#if item.image} {#if item.image}
<img src="https://fbla26.marinodev.com/uploads/{item.id}.jpg" class="object-cover min-h-48 max-h-48" <img src="https://fbla26.marinodev.com/uploads/{item.id}.jpg" class="object-cover min-h-56 max-h-56"
alt="Lost item"> alt="Lost item">
{:else} {:else}
<div class="min-h-48 w-full bg-accent flex flex-col justify-center"> <div class="min-h-48 w-full bg-accent flex flex-col justify-center">
<div class="justify-center flex "> <div class="justify-center flex ">
<NoImagePlaceholder className="" /> <NoImagePlaceholder className="" />
</div> </div>
<p class="text-center mt-4">No image available</p> <p class="text-center mt-4">No image available</p>
</div> </div>
{/if} {/if}
<div class="flex-col flex h-full px-2 pb-2"> <div class="flex-col flex h-full px-2 pb-2">
<!-- <div class="font-bold inline-block">{item.title}</div>-->
<!-- <div class="inline-block">-->
<div> <div>
{#if item.transferred} {#if item.transferred}
<Badge variant="secondary" class="inline-block">In Lost & Found</Badge> <Badge variant="secondary" class="inline-block">In Lost & Found</Badge>
{:else} {:else}
@@ -62,60 +64,68 @@
<Tooltip.Root> <Tooltip.Root>
<Tooltip.Trigger <Tooltip.Trigger
> >
<Badge variant="outline" class="inline-block">{timeSincePosted} <Badge variant="outline"
day{(timeSincePosted === 1 || timeSincePosted === '<1') ? '' : 's'} ago class="inline-block {user?.settings?.staleItemDays !== undefined && timeSincePosted >= user.settings.staleItemDays ? 'text-warning' : ''}">{timeSincePosted < 1 ? "<1" : Math.round(timeSincePosted)}
day{(timeSincePosted <= 1) ? '' : 's'} ago
</Badge> </Badge>
</Tooltip.Trigger </Tooltip.Trigger
> >
<Tooltip.Content> <Tooltip.Content>
<p>{item.foundDate.toLocaleDateString('en-US', dateFormatOptions)}</p> <p>{foundDateObj.toLocaleDateString('en-US', dateFormatOptions)}</p>
</Tooltip.Content> </Tooltip.Content>
</Tooltip.Root> </Tooltip.Root>
</Tooltip.Provider> </Tooltip.Provider>
</div> </div>
<div class="flex-1">{item.description}</div> <div class="flex-1">{item.description}</div>
{#if item.foundLocation} {#if item.foundLocation}
<div class="mt-2"> <div>
<LocationIcon class="float-left mr-1" size={24} />
<div>{item.foundLocation}</div> <Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<span class="mt-2 flex"><strong class="mr-1">Found at:</strong> {item.foundLocation}</span>
</Tooltip.Trigger
>
<Tooltip.Content>
<p>Item was found here</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
</div> </div>
{/if} {/if}
{#if user !== null}
{#if admin}
<div class="mt-2 justify-between flex"> <div class="mt-2 justify-between flex">
{#if item.approvedDate === null} {#if item.approvedDate === null}
<Button variant="ghost" class="text-positive" <Button variant="ghost" class="text-positive"
onclick={async () => {await approveDenyItem({id: item.id, approved: true}); onclick={async (e) => {e.stopPropagation(); await approveDenyItem({id: item.id, approved: true});
invalidateAll()}}> await invalidateAll()}}>
<CheckIcon /> <CheckIcon />
Approve Approve
</Button> </Button>
<Button variant="ghost" class="text-destructive" <Button variant="ghost" class="text-destructive"
onclick={async () => {await approveDenyItem({id: item.id, approved: false}); onclick={async (e) => {e.stopPropagation(); await approveDenyItem({id: item.id, approved: false});
invalidateAll()}}> await invalidateAll()}}>
<XIcon /> <XIcon />
Deny Deny
</Button> </Button>
{/if} {/if}
{#if item.claimedDate === null} {#if item.claimedDate === null}
<Button variant="ghost" class="text-action" <Button variant="ghost" class="text-action"
onclick={() => {editCallback(item)}}> onclick={(e) => {e.stopPropagation(); editCallback(item)}}>
<PencilIcon /> <PencilIcon />
Manage {!item.approvedDate ? 'Edit' : 'Manage'}
</Button> </Button>
{:else} {:else}
<Button variant="ghost" class="text-destructive" <Button variant="ghost" class="text-destructive"
onclick={async () => {await approveDenyItem({id: item.id, approved: false}); onclick={async (e) => {e.stopPropagation(); await approveDenyItem({id: item.id, approved: false});
invalidateAll()}}> await invalidateAll()}}>
<TrashIcon /> <TrashIcon />
Delete Delete
</Button> </Button>
<Button variant="ghost" class="text-action" <Button variant="ghost" class="text-action"
onclick={async () => {await restoreClaimedItem(item.id); onclick={async (e) => {e.stopPropagation(); await restoreClaimedItem(item.id);
invalidateAll()}}> await invalidateAll()}}>
<ArchiveRestoreIcon /> <ArchiveRestoreIcon />
Restore Restore
</Button> </Button>
@@ -124,13 +134,13 @@
</div> </div>
{:else} {:else}
<div class="mt-2 justify-between flex"> <div class="mt-2 justify-between flex">
<Button variant="ghost" class="text-action" <Button variant="ghost" class="text-action z-10"
onclick={() => {inquireCallback(item)}}> onclick={(e) => {e.stopPropagation(); inquireCallback(item)}}>
<NotebookPenIcon /> <NotebookPenIcon />
Inquire Inquire
</Button> </Button>
<Button variant="ghost" class="text-primary" <Button variant="ghost" class="text-primary z-10"
onclick={() => {claimCallback(item)}}> onclick={(e) => {e.stopPropagation(); claimCallback(item)}}>
<StarIcon /> <StarIcon />
Claim Claim
</Button> </Button>
@@ -0,0 +1,42 @@
<script>
import { onMount, onDestroy } from 'svelte';
export let src = '/mdev_triangle_loading.riv';
export let stateMachine = null;
export let size = 120;
export let classes = '';
let canvas;
let riveInstance;
onMount(async () => {
if (typeof window === 'undefined') return;
const mod = await import('@rive-app/canvas');
// Handle BOTH CJS and ESM shapes safely
const Rive = mod?.Rive || mod?.default?.Rive || mod?.default;
if (!Rive) {
console.error('Rive failed to load:', mod);
return;
}
riveInstance = new Rive({
src,
canvas,
autoplay: true,
// stateMachines: stateMachine || undefined,
onLoad: () => {
riveInstance.resizeDrawingSurfaceToCanvas();
}
});
});
onDestroy(() => {
riveInstance?.cleanup?.();
});
</script>
<canvas bind:this={canvas} class={classes} />
@@ -6,7 +6,7 @@
import { Label } from '$lib/components/ui/label'; import { Label } from '$lib/components/ui/label';
import * as Field from '$lib/components/ui/field'; import * as Field from '$lib/components/ui/field';
import { fileToBase64 } from '$lib/shared'; import { fileToBase64 } from '$lib/shared';
import LoadingSpinner from './loading-spinner.svelte';
import ImageUpload from '$lib/components/custom/image-upload/image-upload.svelte'; import ImageUpload from '$lib/components/custom/image-upload/image-upload.svelte';
import { genDescription } from '$lib/db/items.remote'; import { genDescription } from '$lib/db/items.remote';
import { EMAIL_REGEX_STRING } from '$lib/consts'; import { EMAIL_REGEX_STRING } from '$lib/consts';
@@ -17,12 +17,47 @@
let description: string | undefined = $state(); let description: string | undefined = $state();
let isGenerating = $state(false); let isGenerating = $state(false);
async function resizeImage(
file: File,
maxWidth = 300,
maxHeight = 300,
quality = 0.8
): Promise<File> {
const bitmap = await createImageBitmap(file);
let { width, height } = bitmap;
const scale = Math.min(maxWidth / width, maxHeight / height, 1);
width = Math.round(width * scale);
height = Math.round(height * scale);
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d")!;
ctx.drawImage(bitmap, 0, 0, width, height);
const blob = await new Promise<Blob>((resolve) =>
canvas.toBlob((blob) => resolve(blob!), "image/jpeg", quality)
);
return new File([blob], file.name, {
type: "image/jpeg",
lastModified: Date.now()
});
}
async function onSelect(file: File) { async function onSelect(file: File) {
isGenerating = true; isGenerating = true;
const base64 = await fileToBase64(file); const resized = await resizeImage(file, 300, 300, 0.7);
const base64 = await fileToBase64(resized);
description = await genDescription(base64); description = await genDescription(base64);
isGenerating = false; isGenerating = false;
} }
@@ -30,7 +65,7 @@
</script> </script>
<Dialog.Root bind:open> <Dialog.Root bind:open>
<Dialog.Content> <Dialog.Content onOpenAutoFocus={(e) => e.preventDefault()}>
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Submit Found Item</Dialog.Title> <Dialog.Title>Submit Found Item</Dialog.Title>
<Dialog.Description> <Dialog.Description>
@@ -69,7 +104,11 @@
/> />
</Field.Field> </Field.Field>
<RadioGroup.Root name="location" bind:value={itemLocation}>
<RadioGroup.Root name="location" bind:value={itemLocation} required>
<Field.Label for="location">
Where is it? <span class="text-error">*</span>
</Field.Label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<RadioGroup.Item value="finderPossession" id="finderPossession" /> <RadioGroup.Item value="finderPossession" id="finderPossession" />
<Label for="finderPossession"> <Label for="finderPossession">
@@ -115,8 +154,11 @@
</Dialog.Root> </Dialog.Root>
{#if isGenerating} <div
<div class="fixed inset-0 bg-black/75 z-999999 w-screen h-screen justify-center items-center flex"> class="fixed inset-0 bg-black/85 z-[999999] w-screen h-screen flex justify-center items-center transition-opacity duration-150 flex-col md:flex-row"
<p class="text-6xl text-primary">Loading...</p> class:opacity-0={!isGenerating}
</div> class:pointer-events-none={!isGenerating}
{/if} >
<LoadingSpinner classes="w-28 md:w-48 mb-2 md:-mb-4" />
<p class="text-4xl md:text-6xl text-white">Loading...</p>
</div>
+27 -25
View File
@@ -1,38 +1,40 @@
<script lang="ts" module> <script lang="ts" module>
import { cn, type WithElementRef } from "$lib/utils.js"; import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements"; import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from "tailwind-variants"; import { type VariantProps, tv } from 'tailwind-variants';
export const buttonVariants = tv({ export const buttonVariants = tv({
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", base: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs", default: 'bg-dull-primary text-dull-primary-foreground hover:bg-primary/90 shadow-xs',
// dull_default: 'bg-dull-primary text-dull-primary-foreground hover:bg-dull-primary/90 shadow-xs',
destructive: destructive:
"bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs", 'bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs',
outline: outline:
"bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs", 'bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs',
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs", secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs',
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: "text-primary underline-offset-4 hover:underline", link: 'text-primary underline-offset-4 hover:underline'
}, },
size: { size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3", default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: "size-9", xl: 'h-14 rounded-md px-8 has-[>svg]:px-6',
"icon-sm": "size-8", icon: 'size-9',
"icon-lg": "size-10", 'icon-sm': 'size-8',
}, 'icon-lg': 'size-10'
}
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
size: "default", size: 'default'
}, }
}); });
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"]; export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>["size"]; export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> & export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & { WithElementRef<HTMLAnchorAttributes> & {
@@ -44,11 +46,11 @@
<script lang="ts"> <script lang="ts">
let { let {
class: className, class: className,
variant = "default", variant = 'default',
size = "default", size = 'default',
ref = $bindable(null), ref = $bindable(null),
href = undefined, href = undefined,
type = "button", type = 'button',
disabled, disabled,
children, children,
...restProps ...restProps
+1
View File
@@ -0,0 +1 @@
export { default as Toaster } from "./sonner.svelte";
@@ -0,0 +1,34 @@
<script lang="ts">
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
import { mode } from "mode-watcher";
import Loader2Icon from '@lucide/svelte/icons/loader-2';
import CircleCheckIcon from '@lucide/svelte/icons/circle-check';
import OctagonXIcon from '@lucide/svelte/icons/octagon-x';
import InfoIcon from '@lucide/svelte/icons/info';
import TriangleAlertIcon from '@lucide/svelte/icons/triangle-alert';
let { ...restProps }: SonnerProps = $props();
</script>
<Sonner
theme={mode.current}
class="toaster group"
style="--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border);"
{...restProps}
>
{#snippet loadingIcon()}
<Loader2Icon class="size-4 animate-spin" />
{/snippet}
{#snippet successIcon()}
<CircleCheckIcon class="size-4" />
{/snippet}
{#snippet errorIcon()}
<OctagonXIcon class="size-4" />
{/snippet}
{#snippet infoIcon()}
<InfoIcon class="size-4" />
{/snippet}
{#snippet warningIcon()}
<TriangleAlertIcon class="size-4" />
{/snippet}
</Sonner>
+1 -2
View File
@@ -17,13 +17,12 @@ export const genDescription = command(v.string(), async (data) => {
const outputBuffer = await image const outputBuffer = await image
.rotate() .rotate()
.resize({ fit: 'outside', height: 300, width: 300 }) .resize({ fit: 'outside', height: 300, width: 300 })
.jpeg({ quality: 80 }) // adjust if needed .jpeg({ quality: 70 }) // adjust if needed
.toBuffer(); .toBuffer();
const description = await LLMDescribe( const description = await LLMDescribe(
`data:image/jpeg;base64,${outputBuffer.toString('base64')}` `data:image/jpeg;base64,${outputBuffer.toString('base64')}`
); );
console.log(description);
return description; return description;
}); });
-3
View File
@@ -75,9 +75,6 @@ export async function sendNewInquiryEmail(inquiryId: number) {
}; };
const replyToken = jwt.sign(tokenPayload, process.env.JWT_SECRET!); const replyToken = jwt.sign(tokenPayload, process.env.JWT_SECRET!);
console.log(item);
console.log(item.threads);
console.log(replyToken);
// Send mail with defined transport object // Send mail with defined transport object
await transporter.sendMail({ await transporter.sendMail({
from: `Westuffind Notifier <${process.env.EMAIL_USER}>`, from: `Westuffind Notifier <${process.env.EMAIL_USER}>`,
+2 -5
View File
@@ -21,9 +21,6 @@ export async function LLMDescribe(imageData: string) {
temperature: 0.2 temperature: 0.2
}; };
console.log('AIing it');
console.log(payload);
const res = await fetch( const res = await fetch(
`http://${process.env.LLAMA_HOST!}:${process.env.LLAMA_PORT!}/v1/chat/completions`, `http://${process.env.LLAMA_HOST!}:${process.env.LLAMA_PORT!}/v1/chat/completions`,
{ {
@@ -36,8 +33,8 @@ export async function LLMDescribe(imageData: string) {
); );
if (!res.ok) { if (!res.ok) {
console.log(await res.text()); console.error(await res.text());
process.exit(1); // process.exit(1);
} }
const data = await res.json(); const data = await res.json();
+154 -48
View File
@@ -17,26 +17,32 @@
Westuffinder Westuffinder
</h1> </h1>
<p class="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground"> <p class="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground">
Lost something at school? Found something that isnt yours? Lost something at school? Found something that isn't yours?
This is the official place to reconnect items with their owners. This is the official place to reconnect items with their owners.
</p> </p>
<div class="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row"> <div class="mt-10 flex flex-col items-center justify-center gap-4 ">
<a
href="/items"
class={cn(buttonVariants({ variant: 'default', size: 'xl' }), 'text-base')}
aria-label="Browse lost items"
>
Browse lost items
</a>
<div class="w-40 flex items-center gap-2 h-2">
<div class="flex-1 h-px bg-border"></div>
<span class="text-muted-foreground whitespace-nowrap">Or</span>
<div class="flex-1 h-px bg-border"></div>
</div>
<Button <Button
size="lg" size="sm"
class="px-8" variant="outline"
class="text-muted-foreground"
onclick={openCreateDialog} onclick={openCreateDialog}
aria-label="Submit a found item" aria-label="Submit a found item"
> >
Submit a Found Item Submit found item
</Button> </Button>
<a
href="/items"
class={cn(buttonVariants({ variant: 'outline', size: 'lg' }), 'px-8')}
aria-label="Browse lost items"
>
Browse Lost Items
</a>
</div> </div>
</div> </div>
@@ -50,65 +56,165 @@
</div> </div>
</section> </section>
<section class="mx-auto max-w-6xl px-6 py-20" aria-labelledby="info-cards-heading"> <!-- FEATURES -->
<h2 id="info-cards-heading" class="sr-only">Information</h2> <section class="mx-auto max-w-6xl px-6 py-12" aria-labelledby="features-heading">
<div class="grid gap-8 md:grid-cols-3"> <h2 id="features-heading" class="text-2xl font-semibold text-center mb-12">
<Card> Everything you need to get items back fast
<CardHeader> </h2>
<CardTitle id="found-item-heading">Found an Item?</CardTitle>
</CardHeader> <div class="grid gap-12 md:grid-cols-2 items-center">
<CardContent <!-- Left visual / emphasis block -->
class="space-y-3 text-sm text-muted-foreground" <div class="space-y-6">
aria-labelledby="found-item-heading" <div class="rounded-2xl border bg-muted/30 p-6">
> <h3 class="font-semibold text-lg">Built for speed</h3>
<p> <p class="text-sm text-muted-foreground mt-2">
Turn it in digitally in less than a minute. Add a description and where you found it. Report and browse items in seconds. The entire flow is designed to reduce friction.
</p> </p>
<Button </div>
class="mt-2 w-full"
onclick={openCreateDialog} <div class="rounded-2xl border bg-muted/20 p-6">
aria-label="Submit a found item" <h3 class="font-semibold text-lg">Organized listings</h3>
> <p class="text-sm text-muted-foreground mt-2">
Items are structured by location and category so nothing gets buried.
</p>
</div>
<div class="rounded-2xl border bg-muted/10 p-6">
<h3 class="font-semibold text-lg">Trusted process</h3>
<p class="text-sm text-muted-foreground mt-2">
Every submission is reviewed before it's visible.
</p>
</div>
</div>
<!-- Right visual anchor -->
<div class="rounded-2xl border p-10 bg-gradient-to-br from-primary/10 to-transparent">
<h3 class="text-xl font-semibold">Designed for school life</h3>
<p class="text-sm text-muted-foreground mt-4">
Backpacks, phones, notebooks, jackets — the things that actually get lost daily.
Everything here is tuned for real student use, not generic “lost & found” systems.
</p>
<div class="mt-6 text-xs text-muted-foreground">
Over <span class="font-medium text-foreground">120+</span> items successfully returned
</div>
</div>
</div>
</section>
<!-- HOW IT WORKS (Vertical Timeline Style) -->
<section class="mx-auto max-w-4xl px-6 py-12" aria-labelledby="how-heading">
<h2 id="how-heading" class="text-2xl font-semibold text-center mb-12">
How it works
</h2>
<div class="relative border-l pl-8 space-y-10">
<div class="relative">
<div class="absolute -left-[40px] top-1 h-4 w-4 rounded-full bg-primary"></div>
<h3 class="font-semibold">Someone finds something</h3>
<p class="text-sm text-muted-foreground mt-1">
An item is discovered in a classroom, hallway, locker room, or common area.
</p>
</div>
<div class="relative">
<div class="absolute -left-[40px] top-1 h-4 w-4 rounded-full bg-primary/70"></div>
<h3 class="font-semibold">It gets submitted</h3>
<p class="text-sm text-muted-foreground mt-1">
A quick submission captures what it is and where it was found.
</p>
</div>
<div class="relative">
<div class="absolute -left-[40px] top-1 h-4 w-4 rounded-full bg-primary/40"></div>
<h3 class="font-semibold">It makes its way home</h3>
<p class="text-sm text-muted-foreground mt-1">
The owner identifies it and retrieves it through staff.
</p>
</div>
</div>
</section>
<!-- ACTION / INFO (Staggered Bento Style Instead of 3 Equal Cards) -->
<section class="mx-auto max-w-6xl px-6 py-24" aria-labelledby="info-heading">
<h2 id="info-heading" class="sr-only">Actions</h2>
<div class="grid md:grid-cols-2 gap-6">
<!-- Large primary card -->
<Card class="">
<CardHeader>
<CardTitle>Found something?</CardTitle>
</CardHeader>
<CardContent class="space-y-4 text-sm text-muted-foreground">
<p>
Help return it in under a minute. Every submission makes a difference.
</p>
<Button variant="outline" class="w-full" onclick={openCreateDialog}>
Submit Item Submit Item
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>
<Card> <!-- Side card -->
<Card class="">
<CardHeader> <CardHeader>
<CardTitle id="lost-item-heading">Lost Something?</CardTitle> <CardTitle>Looking for yours?</CardTitle>
</CardHeader> </CardHeader>
<CardContent <CardContent class="space-y-4 text-sm text-muted-foreground">
class="space-y-3 text-sm text-muted-foreground"
aria-labelledby="lost-item-heading"
>
<p> <p>
Browse items that have been turned in by students and staff. Check the live list of items turned in around school.
</p> </p>
<a <a
href="/items" href="/items"
class={cn(buttonVariants({ variant: 'outline', size: 'lg' }), 'mt-2 w-full')} class={cn(buttonVariants({ variant: 'outline' }), 'w-full')}
aria-label="Browse found items"
> >
Browse Items Browse
</a> </a>
</CardContent> </CardContent>
</Card> </Card>
<Card> </div>
<!-- Wide bottom emphasis card -->
<Card class="md:col-span-3 border-muted bg-muted/20 mt-6">
<CardContent class=" flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h3 class="font-semibold">Built on trust</h3>
<p class="text-sm text-muted-foreground">
A simple system powered by students and staff working together.
</p>
</div>
<div class="text-sm text-muted-foreground">
<span class="font-medium text-foreground">120+</span> items returned and counting
</div>
</CardContent>
</Card>
</section>
<!-- IMPORTANT DISCLAIMER -->
<section class="mx-auto max-w-6xl px-6 pb-20" aria-labelledby="policy-heading">
<Card class="border-amber-500/40 bg-amber-500/5">
<CardHeader> <CardHeader>
<CardTitle id="safe-heading">Safe &amp; School-Run</CardTitle> <CardTitle id="policy-heading">Important Notice</CardTitle>
</CardHeader> </CardHeader>
<CardContent <CardContent class="text-sm text-muted-foreground space-y-3">
class="space-y-3 text-sm text-muted-foreground"
aria-labelledby="safe-heading"
>
<p> <p>
Managed by Waukesha West staff. Items are reviewed before being listed. This platform is intended for everyday personal items such as notebooks, clothing,
water bottles, and similar belongings commonly misplaced at school.
</p>
<p>
Items of significant monetary, legal, or sensitive value (including but not limited to cash,
credit cards, IDs, or high-value electronics) should <strong>not</strong> be handled through
this system.
</p>
<p>
Such items must be immediately reported to the main office or appropriate school staff for
proper handling and documentation.
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
</div>
</section> </section>
<section class="mx-auto max-w-6xl px-6 pb-24" aria-labelledby="staff-heading"> <section class="mx-auto max-w-6xl px-6 pb-24" aria-labelledby="staff-heading">
+64 -5
View File
@@ -1,17 +1,76 @@
import type { PageServerLoad } from '$types'; import type { PageServerLoad } from '$types';
import sql from '$lib/db/db.server'; import sql from '$lib/db/db.server';
import type { User } from '$lib/types/user'; import type { User, UserSettings } from '$lib/types/user';
import { type Actions, error, fail } from '@sveltejs/kit';
import { getFormString, getRequiredFormString } from '$lib/shared';
import bcrypt from 'bcrypt';
import { setJWTCookie } from '$lib/auth/index.server';
export const load: PageServerLoad = async ({ locals }) => { export const load: PageServerLoad = async ({ locals }) => {
if (!locals || !locals.user) { if (!locals || !locals.user) {
throw Error('Need to be logged in!'); throw Error('Need to be logged in!');
} }
const [userData]: User[] = await sql` const [userData] = await sql`
SELECT * FROM users WHERE id = ${locals.user.id} SELECT * FROM users WHERE id = ${locals.user.id}
`; `;
userData.settings = JSON.parse(userData.settings);
console.log(userData);
return { userData }; return { userData };
}; };
export const actions: Actions = {
default: async ({ request, url, locals, params, cookies }) => {
if (!locals || !locals.user) {
throw error(403, 'Need to be logged in!');
}
const data = await request.formData();
let name: string;
let email: string;
let newPassword: string | null;
let retypeNewPassword: string | null;
let staleItemDays: number;
let notifyAllApprovedInquiries: boolean;
let notifyAllTurnedInInquiries: boolean;
try {
name = getRequiredFormString(data, 'name');
email = getRequiredFormString(data, 'email');
newPassword = getFormString(data, 'newPassword');
retypeNewPassword = getFormString(data, 'retypeNewPassword');
staleItemDays = parseInt(getRequiredFormString(data, 'staleItemDays'));
notifyAllApprovedInquiries = getFormString(data, 'notifyAllApprovedInquiries') == 'on';
notifyAllTurnedInInquiries = getFormString(data, 'notifyAllTurnedInInquiries') == 'on';
if (newPassword !== retypeNewPassword) {
fail(400, { password: 'New passwords dont match!' });
}
const passwordHash = newPassword ? await bcrypt.hash(newPassword, 12) : null;
const settings: UserSettings = {
staleItemDays,
notifyAllApprovedInquiries,
notifyAllTurnedInInquiries
};
const [res] = await sql`
UPDATE users SET name = ${name},
email = ${email},
${passwordHash ? sql`password_hash = ${passwordHash},` : sql``}
settings = ${JSON.stringify(settings)}
WHERE id = ${locals.user.id}
RETURNING *;
`;
res.settings = JSON.parse(res.settings);
setJWTCookie(cookies, res as User);
} catch (e) {
return fail(400, {
message: e instanceof Error ? e.message : 'Unknown error occurred',
success: false
});
}
return { success: true };
}
} satisfies Actions;
+88 -38
View File
@@ -1,4 +1,4 @@
<!-- src/routes/account/+page.svelte --> <!-- src/routes/account/++page.svelte -->
<script lang="ts"> <script lang="ts">
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
import { Badge } from '$lib/components/ui/badge'; import { Badge } from '$lib/components/ui/badge';
@@ -9,36 +9,44 @@
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import { DefaultUserSettings } from '$lib/types/user'; import { DefaultUserSettings } from '$lib/types/user';
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
import { toast } from 'svelte-sonner';
import { Toaster } from '$lib/components/ui/sonner';
import CheckIcon from '@lucide/svelte/icons/check';
let { data } = $props(); let { data, form } = $props();
function signOut() { function signOut() {
document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
window.location.href = '/'; window.location.href = '/';
} }
let form = $derived({ // Use top-level variables for two-way binding. Binding to object properties
name: data.userData.name, // like `form.name` doesn't create proper reactive two-way bindings in Svelte.
email: data.userData.email, let name: string = $state(data.userData.name);
staleItemDays: let email: string = $state(data.userData.email);
data.userData.settings?.staleItemDays ?? DefaultUserSettings.staleItemDays, let staleItemDays: number =
notifyAllApprovedInquiries: $state(data.userData.settings?.staleItemDays ?? DefaultUserSettings.staleItemDays);
data.userData.settings?.notifyAllApprovedInquiries ?? false, let notifyAllApprovedInquiries: boolean =
notifyAllTurnedInInquiries: $state(data.userData.settings?.notifyAllApprovedInquiries ?? false);
data.userData.settings?.notifyAllTurnedInInquiries ?? false let notifyAllTurnedInInquiries: boolean =
}); $state(data.userData.settings?.notifyAllTurnedInInquiries ?? false);
const formatDate = (date: Date | string) => { const formatDate = (date: Date | string) => {
const d = new Date(date); const d = new Date(date);
return d.toLocaleString(); return d.toLocaleString();
}; };
</script> </script>
<svelte:head> <svelte:head>
<title>Account</title> <title>Account</title>
</svelte:head> </svelte:head>
<Toaster></Toaster>
<div class="container mx-auto max-w-3xl py-10"> <div class="container mx-auto max-w-3xl py-10">
<Card class="rounded-2xl shadow-sm"> <Card class="rounded-2xl shadow-sm">
<CardHeader> <CardHeader>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@@ -49,7 +57,18 @@
<CardContent class="space-y-6"> <CardContent class="space-y-6">
<!-- Editable Profile Form --> <!-- Editable Profile Form -->
<form method="POST" use:enhance class="space-y-6"> <form method="POST" class="space-y-6" use:enhance={({ formElement, formData, action, cancel, submitter }) => {
return async ({ result, update }) => {
if (result.status === 200) {
toast('Saved', {
icon: CheckIcon,
description: 'Your account has been saved!',
});
}
};
}
}>
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">
<div class="space-y-2"> <div class="space-y-2">
<Label for="username">Username</Label> <Label for="username">Username</Label>
@@ -57,64 +76,95 @@
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<Label for="name">Full Name</Label> <Label for="name">Full Name<span class="text-error">*</span></Label>
<Input id="name" name="name" bind:value={form.name} /> <Input id="name" name="name" bind:value={name} />
</div> </div>
<div class="space-y-2 sm:col-span-2"> <div class="space-y-2 sm:col-span-2">
<Label for="email">Email</Label> <Label for="email">Email<span class="text-error">*</span></Label>
<Input id="email" name="email" type="email" bind:value={form.email} /> <Input id="email" name="email" type="email" bind:value={email} />
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<Label for="staleItemDays">Stale Item Days</Label> <Label for="newPassword">New Password</Label>
<Input <Input id="newPassword" name="newPassword" />
id="staleItemDays"
name="staleItemDays"
type="number"
bind:value={form.staleItemDays}
min="0"
/>
</div> </div>
<div class="space-y-2">
<Label for="retypeNewPassword">Retype New Password</Label>
<Input id="retypeNewPassword" name="retypeNewPassword" />
</div>
</div> </div>
<Separator /> <Separator />
<div class="space-y-4"> <div class="space-y-4">
<h2 class="text-lg font-semibold">Notifications</h2> <h2 class="text-lg font-semibold">Settings</h2>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<Label for="staleItemDays" class="text-base">
<div> <div>
<p class="font-medium">Notify All Approved</p> <p class="font-medium">Stale Item Days</p>
<p class="text-sm text-muted-foreground"> <p class="text-sm text-muted-foreground">
Receive notifications when inquiries are approved. Number of days without activity before items show up as stale.
</p> </p>
</div> </div>
<Switch </Label>
name="notifyAllApprovedInquiries" <Input
bind:checked={form.notifyAllApprovedInquiries} class="w-min inline-block"
id="staleItemDays"
name="staleItemDays"
type="number"
bind:value={staleItemDays}
min="0"
/> />
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<Label for="notifyAllApprovedInquiries" class="text-base">
<div> <div>
<p class="font-medium">Notify All Turned In</p>
<p class="font-medium">Notify for All Approved Items</p>
<p class="text-sm text-muted-foreground"> <p class="text-sm text-muted-foreground">
Receive notifications when inquiries are turned in. Receive notifications for all approved items, not just yours.
</p> </p>
</div> </div>
</Label>
<Switch
name="notifyAllApprovedInquiries"
id="notifyAllApprovedInquiries"
bind:checked={notifyAllApprovedInquiries}
/>
</div>
<div class="flex items-center justify-between">
<Label for="notifyAllTurnedInInquiries" class="text-base">
<div>
<p class="font-medium">Notify for All Turned In Items</p>
<p class="text-sm text-muted-foreground">
Receive notifications for all items turned in to the school lost-and-found.
</p>
</div>
</Label>
<Switch <Switch
name="notifyAllTurnedInInquiries" name="notifyAllTurnedInInquiries"
bind:checked={form.notifyAllTurnedInInquiries} id="notifyAllTurnedInInquiries"
bind:checked={notifyAllTurnedInInquiries}
/> />
</div> </div>
</div> </div>
<div class="flex items-center justify-between pt-4"> <div class="flex items-center justify-between pt-4">
<div class="text-sm text-muted-foreground"> <div class="text-sm text-muted-foreground">
Member since {formatDate(data.userData.createdAt)} · Last sign in {formatDate( <p>
data.userData.lastSignIn
)} Member since {formatDate(data.userData.createdAt)}
</p>
<p>
Last sign in {formatDate(data.userData.lastSignIn)}
</p>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
View File
-143
View File
@@ -1,143 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
import * as Field from '$lib/components/ui/field/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import * as Select from '$lib/components/ui/select/index.js';
import { Textarea } from '$lib/components/ui/textarea/index.js';
import ImageUpload from '$lib/components/custom/image-upload/image-upload.svelte';
let month = $state<string>();
let year = $state<string>();
</script>
<div class="w-full max-w-lg mt-8">
<form>
<Field.Group>
<Field.Set>
<Field.Legend>Submit Found Item</Field.Legend>
<Field.Description
>Your item will need to be approved before becoming public
</Field.Description>
<Field.Group>
<Field.Field>
<Field.Label for="image-upload">
Image
</Field.Label>
<ImageUpload></ImageUpload>
</Field.Field>
<Field.Field>
<Field.Label for="checkout-7j9-card-name-43j"
>Name on Card
</Field.Label
>
<Input
id="checkout-7j9-card-name-43j"
placeholder="John Doe"
required
/>
</Field.Field>
<div class="grid grid-cols-3 gap-4">
<Field.Field class="col-span-2">
<Field.Label for="checkout-7j9-card-number-uw1">
Card Number
</Field.Label>
<Input
id="checkout-7j9-card-number-uw1"
placeholder="1234 5678 9012 3456"
required
/>
<Field.Description>Enter your 16-digit number.</Field.Description>
</Field.Field>
<Field.Field class="col-span-1">
<Field.Label for="checkout-7j9-cvv">CVV</Field.Label>
<Input id="checkout-7j9-cvv" placeholder="123" required />
</Field.Field>
</div>
<div class="grid grid-cols-2 gap-4">
<Field.Field>
<Field.Label for="checkout-7j9-exp-month-ts6">Month</Field.Label>
<Select.Root type="single" bind:value={month}>
<Select.Trigger id="checkout-7j9-exp-month-ts6">
<span>
{month || "MM"}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="01">01</Select.Item>
<Select.Item value="02">02</Select.Item>
<Select.Item value="03">03</Select.Item>
<Select.Item value="04">04</Select.Item>
<Select.Item value="05">05</Select.Item>
<Select.Item value="06">06</Select.Item>
<Select.Item value="07">07</Select.Item>
<Select.Item value="08">08</Select.Item>
<Select.Item value="09">09</Select.Item>
<Select.Item value="10">10</Select.Item>
<Select.Item value="11">11</Select.Item>
<Select.Item value="12">12</Select.Item>
</Select.Content>
</Select.Root>
</Field.Field>
<Field.Field>
<Field.Label for="checkout-7j9-exp-year-f59">Year</Field.Label>
<Select.Root type="single" bind:value={year}>
<Select.Trigger id="checkout-7j9-exp-year-f59">
<span>
{year || "YYYY"}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="2024">2024</Select.Item>
<Select.Item value="2025">2025</Select.Item>
<Select.Item value="2026">2026</Select.Item>
<Select.Item value="2027">2027</Select.Item>
<Select.Item value="2028">2028</Select.Item>
<Select.Item value="2029">2029</Select.Item>
</Select.Content>
</Select.Root>
</Field.Field>
</div>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Set>
<Field.Legend>Billing Address</Field.Legend>
<Field.Description>
The billing address associated with your payment method
</Field.Description>
<Field.Group>
<Field.Field orientation="horizontal">
<Checkbox id="checkout-7j9-same-as-shipping-wgm" checked={true} />
<Field.Label
for="checkout-7j9-same-as-shipping-wgm"
class="font-normal"
>
Same as shipping address
</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Set>
<Field.Group>
<Field.Field>
<Field.Label for="checkout-7j9-optional-comments"
>Comments
</Field.Label
>
<Textarea
id="checkout-7j9-optional-comments"
placeholder="Add any additional comments"
class="resize-none"
/>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Field orientation="horizontal">
<Button type="submit">Submit</Button>
<Button variant="outline" type="button">Cancel</Button>
</Field.Field>
</Field.Group>
</form>
</div>
+3 -2
View File
@@ -35,7 +35,8 @@ export const load: PageServerLoad = async ({ url, locals }) => {
WHERE im.thread_id = t.id WHERE im.thread_id = t.id
) m ON TRUE ) m ON TRUE
${searchQuery ? sql`WHERE word_similarity(${searchQuery}, i.description) > 0.3` : sql``} ${searchQuery ? sql`WHERE word_similarity(${searchQuery}, i.description) > 0.3` : sql``}
GROUP BY i.id; GROUP BY i.id
ORDER BY i.found_date DESC;
`; `;
items.forEach((item) => items.forEach((item) =>
item.threads?.forEach((thread) => (thread.createdAt = new Date(thread.createdAt))) item.threads?.forEach((thread) => (thread.createdAt = new Date(thread.createdAt)))
@@ -110,7 +111,7 @@ export const load: PageServerLoad = async ({ url, locals }) => {
// Not logged in or fallback: simple item list (no threads) // Not logged in or fallback: simple item list (no threads)
let items: Item[]; let items: Item[];
if (!searchQuery) { if (!searchQuery) {
items = await sql`SELECT * FROM items;`; items = await sql`SELECT * FROM items ORDER BY found_date DESC;`;
} else { } else {
items = await sql` items = await sql`
SELECT * FROM items SELECT * FROM items
+4 -4
View File
@@ -86,7 +86,7 @@
{#each data.items as item (item.id)} {#each data.items as item (item.id)}
{#if item.approvedDate === null} {#if item.approvedDate === null}
<ItemListing item={item} admin={data.user !== null} editCallback={openEditDialog} <ItemListing item={item} user={data.user} editCallback={openEditDialog}
inquireCallback={openInquireDialog} claimCallback={openClaimDialog} /> inquireCallback={openInquireDialog} claimCallback={openClaimDialog} />
{/if} {/if}
{/each} {/each}
@@ -102,7 +102,7 @@
{#each data.items as item (item.id)} {#each data.items as item (item.id)}
{#if item.approvedDate !== null && item.claimedDate === null} {#if item.approvedDate !== null && item.claimedDate === null}
<ItemListing item={item} admin={data.user !== null} editCallback={openEditDialog} <ItemListing item={item} user={data.user} editCallback={openEditDialog}
inquireCallback={openInquireDialog} claimCallback={openClaimDialog} /> inquireCallback={openInquireDialog} claimCallback={openClaimDialog} />
{/if} {/if}
{/each} {/each}
@@ -115,7 +115,7 @@
</FieldSeparator> </FieldSeparator>
{#each data.items as item (item.id)} {#each data.items as item (item.id)}
{#if item.claimedDate !== null} {#if item.claimedDate !== null}
<ItemListing item={item} admin={data.user !== null} editCallback={openEditDialog} <ItemListing item={item} user={data.user} editCallback={openEditDialog}
inquireCallback={openInquireDialog} claimCallback={openClaimDialog} /> inquireCallback={openInquireDialog} claimCallback={openClaimDialog} />
{/if} {/if}
{/each} {/each}
@@ -125,7 +125,7 @@
{/if} {/if}
</div> </div>
<Button class="fixed shadow-lg bottom-6 right-6 rounded-xl md:hidden p-6 text-xl" size="default" <Button class="fixed shadow-lg bottom-6 right-6 rounded-xl md:hidden p-6 text-xl z-20" size="default"
onclick={openCreateDialog}> onclick={openCreateDialog}>
<PlusIcon size={32} /> <PlusIcon size={32} />
New item New item
@@ -9,7 +9,6 @@ import type { Item } from '$lib/types/item';
export const load: PageServerLoad = async ({ url, locals, params }) => { export const load: PageServerLoad = async ({ url, locals, params }) => {
const token: string | undefined = url.searchParams.get('token'); const token: string | undefined = url.searchParams.get('token');
// const itemId: string = params.itemId;
const inquiryId: string = params.inquiryId; const inquiryId: string = params.inquiryId;
if (token) { if (token) {
@@ -48,15 +47,12 @@ export const load: PageServerLoad = async ({ url, locals, params }) => {
WHERE i.id = (SELECT item_id FROM inquiry_threads WHERE id = ${inquiryId}) WHERE i.id = (SELECT item_id FROM inquiry_threads WHERE id = ${inquiryId})
GROUP BY i.id;`; GROUP BY i.id;`;
console.log(item);
return { item }; return { item };
}; };
export const actions: Actions = { export const actions: Actions = {
reply: async ({ request, url, locals, params }) => { reply: async ({ request, url, locals, params }) => {
const token: string | undefined = url.searchParams.get('token'); const token: string | undefined = url.searchParams.get('token');
const itemId: number = params.itemId;
const inquiryId: number = params.inquiryId; const inquiryId: number = params.inquiryId;
let sender: Sender | undefined; let sender: Sender | undefined;
@@ -86,21 +82,19 @@ export const actions: Actions = {
const body = getRequiredFormString(data, 'message'); const body = getRequiredFormString(data, 'message');
console.log(inquiryId, sender, body);
const response = await sql` const response = await sql`
INSERT INTO inquiry_messages (thread_id, sender, body) INSERT INTO inquiry_messages (thread_id, sender, body)
VALUES (${inquiryId}, ${sender}, ${body}) VALUES (${inquiryId}, ${sender}, ${body})
RETURNING id; RETURNING id;
`; `;
// TODO: Double check logic
let nextReplySender: Sender; let nextReplySender: Sender;
if (sender === Sender.ADMIN || sender === Sender.FINDER) { if (sender === Sender.ADMIN || sender === Sender.FINDER) {
nextReplySender = Sender.INQUIRER; nextReplySender = Sender.INQUIRER;
} else { } else {
nextReplySender = Sender.FINDER; nextReplySender = Sender.FINDER;
} }
await sendInquiryMessageEmail(response[0].threadId, nextReplySender); await sendInquiryMessageEmail(inquiryId, nextReplySender);
return { success: true }; return { success: true };
} }
@@ -9,6 +9,7 @@
import * as item from 'valibot'; import * as item from 'valibot';
import NoImagePlaceholder from '$lib/components/custom/no-image-placeholder.svelte'; import NoImagePlaceholder from '$lib/components/custom/no-image-placeholder.svelte';
import { Separator } from '$lib/components/ui/separator'; import { Separator } from '$lib/components/ui/separator';
import { dateFormatOptions } from '$lib/shared';
let { data } = $props(); let { data } = $props();
@@ -35,6 +36,14 @@
} }
}; };
const dateTimeFormatOptions: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
};
</script> </script>
<div class="max-w-2xl mx-auto p-4"> <div class="max-w-2xl mx-auto p-4">
@@ -55,22 +64,17 @@
<div class=""> <div class="">
<div class="flex-1">{data.item.description}</div> <div class="flex-1">{data.item.description}</div>
{#if data.item.foundLocation} {#if data.item.foundLocation}
<div class="mt-2"> <span class="mt-2"><strong class="mr-1">Found at:</strong> {item.foundLocation}</span>
<LocationIcon class="float-left mr-1" size={24} />
<div>{data.item.foundLocation}</div>
</div>
{/if} {/if}
</div> </div>
</div> </div>
<Separator class="my-6" /> <Separator class="my-6" />
<div class="relative"> <div class="relative">
<!-- Conversation -->
<div class="flex flex-col gap-6 relative"> <div class="flex flex-col gap-6 relative">
{#if data.item.threads} {#if data.item.threads}
{#each data.item.threads[0].messages as msg (msg.id)} {#each data.item.threads[0].messages as msg (msg.id)}
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<!-- Avatar -->
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<div <div
class={`flex items-center justify-center w-10 h-10 rounded-full text-white ${senderColor(msg.sender)}`}> class={`flex items-center justify-center w-10 h-10 rounded-full text-white ${senderColor(msg.sender)}`}>
@@ -78,10 +82,9 @@
</div> </div>
</div> </div>
<!-- Message body -->
<div class="bg-card rounded-lg p-3 shadow-sm max-w-xl"> <div class="bg-card rounded-lg p-3 shadow-sm max-w-xl">
<p class="text-sm">{msg.body}</p> <p class="text-sm">{msg.body}</p>
<span class="text-xs text-muted-foreground mt-1 block">{msg.createdAt.toLocaleString()}</span> <span class="text-xs text-muted-foreground mt-1 block">{new Date(msg.createdAt as unknown as string).toLocaleDateString('en-US', dateTimeFormatOptions)}</span>
</div> </div>
</div> </div>
{/each} {/each}
@@ -91,11 +94,11 @@
<!-- Input area --> <!-- Input area -->
<form class="mt-6 flex flex-col gap-2" method="post" action="?/reply&token={page.url.searchParams.get('token')}"> <form class="mt-6 flex flex-col gap-2" method="post" action="?/reply&token={page.url.searchParams.get('token')}">
<Textarea <Input
type="text"
placeholder="Write a message..." placeholder="Write a message..."
class="resize-none" class="resize-none"
name="message" name="message"
rows={3}
/> />
<Button class="self-end" type="submit">Send <Button class="self-end" type="submit">Send
<SendIcon /> <SendIcon />
-9
View File
@@ -34,20 +34,11 @@
<Field> <Field>
<div class="flex items-center"> <div class="flex items-center">
<FieldLabel for="password">Password</FieldLabel> <FieldLabel for="password">Password</FieldLabel>
<!-- <a href="##" class="ms-auto text-sm underline-offset-4 hover:underline">-->
<!-- Forgot your password?-->
<!-- </a>-->
</div> </div>
<Input id="password" type="password" required /> <Input id="password" type="password" required />
</Field> </Field>
<Field> <Field>
<Button type="submit">Login</Button> <Button type="submit">Login</Button>
</Field> </Field>
<!-- <Field>-->
<!-- <FieldDescription class="text-center">-->
<!-- Don't have an account?-->
<!-- <a href="/signup" class="underline underline-offset-4">Sign up</a>-->
<!-- </FieldDescription>-->
<!-- </Field>-->
</FieldGroup> </FieldGroup>
</form> </form>
+1 -1
View File
@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
import { render } from 'vitest-browser-svelte'; import { render } from 'vitest-browser-svelte';
import Page from './items/+page.svelte'; import Page from './items/+page.svelte';
describe('/+page.svelte', () => { describe('/++page.svelte', () => {
it('should render h1', async () => { it('should render h1', async () => {
render(Page); render(Page);
Binary file not shown.