diff --git a/src/app.css b/src/app.css index 4011771..3240407 100644 --- a/src/app.css +++ b/src/app.css @@ -12,7 +12,7 @@ --card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(1 0 0); --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.606 0.25 292.717); + --primary: oklch(0.5852 0.25 292.717); --primary-foreground: oklch(0.969 0.016 293.756); --secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.21 0.006 285.885); @@ -40,6 +40,7 @@ --warning: oklch(0.84 0.16 84); --error: oklch(0.577 0.245 27.325); --positive: oklch(0.5 0.2067 147.18); + --edit: oklch(0.5852 0.2263 260.47); } .dark { @@ -77,6 +78,7 @@ --warning: oklch(0.84 0.16 84); --error: oklch(0.704 0.191 22.216); --positive: oklch(0.7522 0.2067 147.18); + --edit: oklch(0.6098 0.1872 260.47); } @theme inline { @@ -118,6 +120,7 @@ --color-warning: var(--warning); --color-error: var(--error); --color-positive: var(--positive); + --color-edit: var(--edit); } @layer base { diff --git a/src/lib/components/custom/item-listing.svelte b/src/lib/components/custom/item-listing.svelte index 340ebb7..6cf5273 100644 --- a/src/lib/components/custom/item-listing.svelte +++ b/src/lib/components/custom/item-listing.svelte @@ -5,11 +5,13 @@ import LocationIcon from '@lucide/svelte/icons/map-pinned'; import CheckIcon from '@lucide/svelte/icons/check'; import XIcon from '@lucide/svelte/icons/x'; + import PencilIcon from '@lucide/svelte/icons/pencil'; import { Button } from '$lib/components/ui/button'; import * as Tooltip from '$lib/components/ui/tooltip'; import { dateFormatOptions } from '$lib/shared'; import { approveDenyItem } from '$lib/db/items.remote'; import { invalidateAll } from '$app/navigation'; + import NoImagePlaceholder from './no-image-placeholder.svelte'; export let item: Item = { id: 2, @@ -18,7 +20,8 @@ approvedDate: new Date(), description: 'A matte black water bottle with a black lid and a "BKLYN BENTO" logo on the side.', transferred: true, - foundLocation: 'By the tennis courts.' + foundLocation: 'By the tennis courts.', + image: true }; export let admin = false; @@ -28,11 +31,24 @@ } else { timeSincePosted = Math.round(timeSincePosted); } +
- + {#if item.image} + Lost item + {:else} +
+ +
+ + +
+

No image available

+
+ {/if}
@@ -69,22 +85,32 @@
{/if} - {#if admin && item.approvedDate === null} + {#if admin}
- - + + {/if} + +
{/if} +
diff --git a/src/lib/components/custom/no-image-placeholder.svelte b/src/lib/components/custom/no-image-placeholder.svelte new file mode 100644 index 0000000..a718487 --- /dev/null +++ b/src/lib/components/custom/no-image-placeholder.svelte @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/components/custom/submit-item-dialog.svelte b/src/lib/components/custom/submit-item-dialog.svelte new file mode 100644 index 0000000..2b543be --- /dev/null +++ b/src/lib/components/custom/submit-item-dialog.svelte @@ -0,0 +1,117 @@ + + + + + + Submit Found Item + + Your item will need to be approved before becoming public. + + + +
+ + + + + + Description * + + + + + + + Where did you find it? + + + + + +
+ + +
+ +
+ + +
+
+ + + + Your Email + + + +
+ + + + Cancel + + + +
+
+
+ + +{#if isGenerating} +
+

Loading...

+
+{/if} diff --git a/src/lib/types/item.d.ts b/src/lib/types/item.d.ts index 61224b3..42ac61d 100644 --- a/src/lib/types/item.d.ts +++ b/src/lib/types/item.d.ts @@ -10,4 +10,6 @@ export interface Item { transferred: boolean; // to L&F location keywords?: string[]; foundLocation: string; + deleted: boolean; + image: boolean; } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 9254310..07bc47b 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -22,7 +22,11 @@
- Admin Log in +
+ + Browse Lost Items + +
+
+ + + + + +
+
+ + + Found an Item? + + +

+ Turn it in digitally in less than a minute. Add a description and where you found it. +

+ +
+
+ + + + Lost Something? + + +

+ Browse items that have been turned in by students and staff. +

+ Browse Items +
+
+ + + + Safe & School-Run + + +

+ Managed by Waukesha West staff. Items are reviewed before being listed. +

+
+
+
+
+ +
+
+

+ Staff only: + + Admin sign in + +

+
+
+ + diff --git a/src/routes/items/+page.server.ts b/src/routes/items/+page.server.ts index ae7f6c4..345736e 100644 --- a/src/routes/items/+page.server.ts +++ b/src/routes/items/+page.server.ts @@ -30,11 +30,11 @@ export const actions: Actions = { location = getFormString(data, 'location'); const file = data.get('image'); - if (file === null) - return fail(400, { - message: `Missing required field image.`, - success: false - }); + // if (file === null) + // return fail(400, { + // message: `Missing required field image.`, + // success: false + // }); if (!email && location !== 'turnedIn') { fail(400, { @@ -43,27 +43,28 @@ export const actions: Actions = { }); } - if (!(file instanceof File)) { - return fail(400, { message: 'No file uploaded or file is invalid', success: false }); - } + let outputBuffer: Buffer | undefined; + if (file) { + if (!(file instanceof File)) { + return fail(400, { message: 'No file uploaded or file is invalid', success: false }); + } - // Convert File → Buffer - const inputBuffer = Buffer.from(await file.arrayBuffer()); + // Convert File → Buffer + const inputBuffer = Buffer.from(await file.arrayBuffer()); - // Detect format (Sharp does this internally) - const image = sharp(inputBuffer); - const metadata = await image.metadata(); + // Detect format (Sharp does this internally) + const image = sharp(inputBuffer); + const metadata = await image.metadata(); - let outputBuffer: Buffer; - - if (metadata.format === 'jpeg') { - // Already JPG → keep as-is - outputBuffer = inputBuffer; - } else { - // Convert to JPG - outputBuffer = await image - .jpeg({ quality: 90 }) // adjust if needed - .toBuffer(); + if (metadata.format === 'jpeg') { + // Already JPG → keep as-is + outputBuffer = inputBuffer; + } else { + // Convert to JPG + outputBuffer = await image + .jpeg({ quality: 90 }) // adjust if needed + .toBuffer(); + } } const response = await sql` @@ -71,7 +72,8 @@ export const actions: Actions = { emails, description, transferred, - found_location + found_location, + image ) VALUES ( ${ location === 'turnedIn' @@ -89,19 +91,22 @@ export const actions: Actions = { }, ${description}, ${location === 'turnedIn'}, - ${foundLocation} + ${foundLocation}, + ${file instanceof File} ) RETURNING id; `; - try { - // It's a good idea to validate the filename to prevent path traversal attacks - const savePath = path.join('uploads', `${response[0]['id']}.jpg`); + if (outputBuffer) { + try { + // It's a good idea to validate the filename to prevent path traversal attacks + const savePath = path.join('uploads', `${response[0]['id']}.jpg`); - writeFileSync(savePath, outputBuffer); - } catch (err) { - console.error('File upload failed:', err); - return fail(500, { message: 'Internal server error during file upload', success: false }); + writeFileSync(savePath, outputBuffer); + } catch (err) { + console.error('File upload failed:', err); + return fail(500, { message: 'Internal server error during file upload', success: false }); + } } } catch (e) { return fail(400, { diff --git a/src/routes/items/+page.svelte b/src/routes/items/+page.svelte index 9fc5100..0c973cc 100644 --- a/src/routes/items/+page.svelte +++ b/src/routes/items/+page.svelte @@ -11,88 +11,26 @@ import { Label } from '$lib/components/ui/label'; import ItemListing from '$lib/components/custom/item-listing.svelte'; import { FieldSeparator } from '$lib/components/ui/field'; + import SubmitItemDialog from '$lib/components/custom/submit-item-dialog.svelte'; + // import { type Item } from '$lib/types/item'; - let itemLocation: string | undefined = $state(''); - let foundLocation: string | undefined = $state(); - let description: string | undefined = $state(); - let isGenerating = $state(false); + let createDialogOpen: boolean = $state(false); - async function onSelect() { - isGenerating = true; - description = await genDescription(); - isGenerating = false; + function openCreateDialog() { + createDialogOpen = true; } - let { data }: PageProps = $props();
-

Found Items

+

Found Items

- - Post an item - - - - Submit Found Item - - Your item will need to be approved before becoming public. - - -
- - - - - Description* - - - - - - Where did you find it? - - - - -
- - -
-
- - -
-
- - - Your Email - - - - -
- - - Cancel - - - -
-
-
+
@@ -101,7 +39,7 @@ {#if data.user && data.items.some( (item) => item.approvedDate === null )} - + Pending items {/if} @@ -127,13 +65,9 @@ {/each}
- + + -{#if isGenerating} -
-

Loading...

-
-{/if}