pages
ci / docker_image (push) Successful in 2m57s
ci / deploy (push) Successful in 28s

This commit is contained in:
2026-02-04 18:49:35 -06:00
parent 8ea632a14f
commit 5cd3af719d
9 changed files with 433 additions and 124 deletions
+38 -12
View File
@@ -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 = <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);
}
</script>
<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-2-3xs">
<img src="https://fbla26.marinodev.com/uploads/{item.id}.jpg" alt="" class="object-cover max-h-48">
{#if item.image}
<img src="https://fbla26.marinodev.com/uploads/{item.id}.jpg" class="object-cover min-h-48 max-h-48"
alt="Lost item">
{:else}
<div class="min-h-48 w-full bg-accent flex flex-col justify-center">
<div class="justify-center flex ">
<NoImagePlaceholder className="" />
</div>
<p class="text-center mt-4">No image available</p>
</div>
{/if}
<div class="flex-col flex h-full px-2 pb-2">
<!-- <div class="font-bold inline-block">{item.title}</div>-->
@@ -69,22 +85,32 @@
</div>
{/if}
{#if admin && item.approvedDate === null}
{#if admin}
<div class="mt-2 justify-between flex">
<Button variant="ghost" class="text-positive"
onclick={async () => {await approveDenyItem({id: item.id, approved: true});
{#if item.approvedDate === null}
<Button variant="ghost" class="text-positive"
onclick={async () => {await approveDenyItem({id: item.id, approved: true});
invalidateAll()}}>
<CheckIcon />
Approve
</Button>
<Button variant="ghost" class="text-destructive"
onclick={async () => {await approveDenyItem({id: item.id, approved: false});
<CheckIcon />
Approve
</Button>
<Button variant="ghost" class="text-destructive"
onclick={async () => {await approveDenyItem({id: item.id, approved: false});
invalidateAll()}}>
<XIcon />
Deny
<XIcon />
Deny
</Button>
{/if}
<Button variant="ghost" class="text-edit"
onclick={async () => {}}>
<PencilIcon />
Edit
</Button>
</div>
{/if}
</div>
</div>
@@ -0,0 +1,122 @@
<script>
export let className = '';
</script>
<svg
version="1.1"
id="svg2"
viewBox="0 0 99.999999 99.999999"
height="100"
width="100"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
class={className}>
<defs
id="defs4">
<linearGradient
id="linearGradient967">
<stop
id="stop963"
offset="0"
style="stop-color:#c2c2c2;stop-opacity:1" />
<stop
id="stop965"
offset="1"
style="stop-color:#9f9f9f;stop-opacity:1" />
</linearGradient>
<linearGradient
gradientTransform="translate(-45.254833,0.35355338)"
gradientUnits="userSpaceOnUse"
y2="108.77648"
x2="658.45801"
y1="6.5995569"
x1="660.06653"
id="linearGradient969"
xlink:href="#linearGradient967" />
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-399.13437,-122.79051)"
id="layer1">
<g
transform="matrix(0.60784825,0,0,0.67101051,134.74354,126.08684)"
id="g1015">
<rect
style="fill:#9f9f9f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.2995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1002"
width="82.489967"
height="90.470001"
x="398.75827"
y="178.74706"
ry="8.3970251"
transform="rotate(-16.342822)" />
<g
id="g1000"
transform="rotate(16.320529,538.13563,-184.89727)">
<rect
ry="4.5961938"
y="1.6498091"
x="547.18585"
height="115.96551"
width="107.83378"
id="rect961"
style="fill:url(#linearGradient969);fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:5.398;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
<g
style="stroke:#ffffff;stroke-width:13.0708;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(0.17265471,0,0,0.17265471,512.49324,-6.3296456)"
id="g875">
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:13.0708;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect3338"
width="491.10556"
height="449.99814"
x="270"
y="107.36227" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:13.0708;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect3342"
width="491.10559"
height="209.99976"
x="270"
y="107.36227" />
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:13.0708;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 300,317.36255 38.46147,-53.53818 60.53097,-45.16084 15.88277,18.57394 13.61285,-38.68356 8.20133,-2.98188 13.3106,-28.2093 180,179.99979"
id="path3344" />
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#b3b3b3;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:13.0708;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 180,60 c 4.09311,16.474688 7.71219,33.067277 10.85156,49.75 2.38256,12.66097 4.48857,25.37408 6.31641,38.12695 l -22.06445,-7.16015 -46.11133,-29.41602 5.32422,46.42578 -1.61524,24.78711 10.05274,30.37695 73.18554,-11.75585 L 300,180 252.19922,102.56641 242.5,117.5 215.375,95.375 Z"
transform="translate(270,107.36227)"
id="path3390-0" />
<path
id="path3358"
d="m 419.99999,347.36252 81.89918,-74.42959 18.50574,-9.68009 23.6512,-44.18894 25.94388,-21.70121 179.99999,179.99979"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:13.0708;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#b3b3b3;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:13.0708;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 569.99999,197.36269 35.9388,80.91289 v 30.11038 30.11038 l 22.45864,19.46652 c 6.52453,-6.45031 14.14893,-11.78526 22.44431,-15.70477 14.8245,-7.00447 31.33823,-9.35959 47.17057,-13.6217 6.42776,-1.73037 12.90672,-3.85419 18.21343,-7.87277 1.35174,-1.02362 2.61592,-2.16281 3.77424,-3.40107 h -30 l -40.52149,-40.55006 -29.85645,-48.91972 -10.25307,8.83886 z"
id="path3386" />
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:13.0708;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 419.99999,557.36227 c -0.41699,-9.60089 -8.81759,-17.60878 17.1252,-30.66806 31.8318,-16.02389 125.895,-35.88836 152.1537,-59.98434 19.42709,-17.82687 -70.4154,-37.66945 -55.0191,-59.07323 6.981,-9.70528 59.037,-19.96947 82.1463,-30.27386 21.90569,-9.76799 15.14129,-19.80328 31.4046,-29.97507 15.7092,-9.82558 68.3499,-19.77358 72.18929,-30.02516 -10.41359,10.52188 -68.83379,20.40327 -89.99999,30.00026 -22.3377,10.128 -21.4689,19.93018 -49.4313,29.48367 -30.1245,10.29239 -89.142,20.55268 -102.7077,30.51626 -28.4133,20.86858 46.863,42.59995 16.2024,59.99993 C 452.54309,490.92554 344.7219,510.65712 300,527.3626 c -30.9039,11.54369 -28.4079,17.74799 -30,29.99967"
id="path3370" />
</g>
</g>
</g>
</g>
</svg>
@@ -0,0 +1,117 @@
<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog';
import { Button, buttonVariants } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import * as RadioGroup from '$lib/components/ui/radio-group';
import { Label } from '$lib/components/ui/label';
import * as Field from '$lib/components/ui/field';
import ImageUpload from '$lib/components/custom/image-upload/image-upload.svelte';
import { genDescription } from '$lib/db/items.remote';
import { EMAIL_REGEX_STRING } from '$lib/consts';
let itemLocation: string | undefined = $state('');
let foundLocation: string | undefined = $state();
let description: string | undefined = $state();
let isGenerating = $state(false);
async function onSelect() {
isGenerating = true;
description = await genDescription();
isGenerating = false;
}
let { open = $bindable() }: { open: boolean } = $props();
</script>
<Dialog.Root bind:open>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Submit Found Item</Dialog.Title>
<Dialog.Description>
Your item will need to be approved before becoming public.
</Dialog.Description>
</Dialog.Header>
<form method="post" action="?/create" enctype="multipart/form-data">
<Field.Group>
<ImageUpload onSelect={onSelect} required />
<Field.Field>
<Field.Label for="description">
Description <span class="text-error">*</span>
</Field.Label>
<Input
id="description"
name="description"
bind:value={description}
placeholder="A red leather book bag..."
maxlength={200}
required
/>
</Field.Field>
<Field.Field>
<Field.Label for="foundLocation">
Where did you find it?
</Field.Label>
<Input
id="foundLocation"
name="foundLocation"
bind:value={foundLocation}
placeholder="By the tennis courts."
required
/>
</Field.Field>
<RadioGroup.Root name="location" bind:value={itemLocation}>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="finderPossession" id="finderPossession" />
<Label for="finderPossession">
I still have the item.
</Label>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="turnedIn" id="turnedIn" />
<Label for="turnedIn">
I turned the item in to the school lost and found.
</Label>
</div>
</RadioGroup.Root>
<Field.Field
class={itemLocation !== "finderPossession" ? "hidden pointer-events-none opacity-50" : ""}
>
<Field.Label for="email">
Your Email
</Field.Label>
<Input
id="email"
name="email"
placeholder="name@domain.com"
pattern={EMAIL_REGEX_STRING}
required={itemLocation === "finderPossession"}
/>
</Field.Field>
</Field.Group>
<Dialog.Footer class="mt-4">
<Dialog.Close
type="button"
class={buttonVariants({ variant: "outline" })}
>
Cancel
</Dialog.Close>
<Button type="submit">Submit</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>
{#if isGenerating}
<div class="fixed inset-0 bg-black/75 z-999999 w-screen h-screen justify-center items-center flex">
<p class="text-6xl text-primary">Loading...</p>
</div>
{/if}
+2
View File
@@ -10,4 +10,6 @@ export interface Item {
transferred: boolean; // to L&F location
keywords?: string[];
foundLocation: string;
deleted: boolean;
image: boolean;
}