am i finished?

This commit is contained in:
2025-08-25 14:23:57 +01:00
parent 769f3981cd
commit fe0968716d
12 changed files with 282 additions and 105 deletions

View File

@@ -1,16 +1,136 @@
import { Component, For } from "solid-js";
import { Component, For, createSignal } from "solid-js";
import { useSearchImageContext } from "@contexts/SearchImageContext";
import { ListCard } from "@components/list-card";
import { Button } from "@kobalte/core/button";
import { Dialog } from "@kobalte/core/dialog";
import { createList } from "../../network";
export const Categories: Component = () => {
const { lists } = useSearchImageContext();
const { lists, onRefetchImages } = useSearchImageContext();
return (
<div class="rounded-xl bg-white p-4 flex flex-col gap-2">
<h2 class="text-xl font-bold">Generated Lists</h2>
<div class="w-full grid grid-cols-3 auto-rows-[minmax(100px,1fr)] gap-4">
<For each={lists()}>{(list) => <ListCard list={list} />}</For>
</div>
</div>
);
const [title, setTitle] = createSignal("");
const [description, setDescription] = createSignal("");
const [isCreating, setIsCreating] = createSignal(false);
const [showForm, setShowForm] = createSignal(false);
const handleCreateList = async () => {
if (description().trim().length === 0 || title().trim().length === 0)
return;
setIsCreating(true);
try {
await createList(title().trim(), description().trim());
setTitle("");
setDescription("");
setShowForm(false);
onRefetchImages(); // Refresh the lists
} catch (error) {
console.error("Failed to create list:", error);
} finally {
setIsCreating(false);
}
};
return (
<div class="rounded-xl bg-white p-4 flex flex-col gap-2">
<h2 class="text-xl font-bold">Generated Lists</h2>
<div class="w-full grid grid-cols-3 auto-rows-[minmax(100px,1fr)] gap-4">
<For each={lists()}>{(list) => <ListCard list={list} />}</For>
</div>
<div class="mt-4">
<Button
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium shadow-sm hover:shadow-md"
onClick={() => setShowForm(true)}
>
+ Create List
</Button>
</div>
<Dialog open={showForm()} onOpenChange={setShowForm}>
<Dialog.Portal>
<Dialog.Overlay class="fixed inset-0 bg-black/50 z-50" />
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
<Dialog.Content class="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto">
<div class="p-6">
<Dialog.Title class="text-xl font-bold text-neutral-900 mb-4">
Create New List
</Dialog.Title>
<div class="space-y-4">
<div>
<label
for="list-title"
class="block text-sm font-medium text-neutral-700 mb-2"
>
List Title
</label>
<input
id="list-title"
type="text"
value={title()}
onInput={(e) =>
setTitle(e.target.value)
}
placeholder="Enter a title for your list"
class="w-full p-3 border border-neutral-300 rounded-lg focus:ring-2 focus:ring-indigo-600 focus:border-transparent transition-colors"
disabled={isCreating()}
/>
</div>
<div>
<label
for="list-description"
class="block text-sm font-medium text-neutral-700 mb-2"
>
List Description
</label>
<textarea
id="list-description"
value={description()}
onInput={(e) =>
setDescription(e.target.value)
}
placeholder="Describe what kind of list you want to create (e.g., 'A list of my favorite recipes' or 'Photos from my vacation')"
class="w-full p-3 border border-neutral-300 rounded-lg resize-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent transition-colors"
rows="4"
disabled={isCreating()}
/>
</div>
</div>
<div class="flex gap-3 mt-6">
<Button
class="flex-1 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors disabled:opacity-50 font-medium shadow-sm hover:shadow-md"
onClick={handleCreateList}
disabled={
isCreating() ||
!title().trim() ||
!description().trim()
}
>
{isCreating()
? "Creating..."
: "Create List"}
</Button>
<Button
class="px-4 py-2 bg-neutral-300 text-neutral-700 rounded-lg hover:bg-neutral-400 transition-colors font-medium"
onClick={() => {
setShowForm(false);
setTitle("");
setDescription("");
}}
disabled={isCreating()}
>
Cancel
</Button>
</div>
</div>
</Dialog.Content>
</div>
</Dialog.Portal>
</Dialog>
</div>
);
};

View File

@@ -3,7 +3,6 @@ import { useSearchImageContext } from "@contexts/SearchImageContext";
import { useParams } from "@solidjs/router";
import { For, type Component } from "solid-js";
import SolidjsMarkdown from "solidjs-markdown";
import { List } from "../list";
import { ListCard } from "@components/list-card";
export const ImagePage: Component = () => {

View File

@@ -1,43 +1,107 @@
import { ImageComponent } from "@components/image";
import { useSearchImageContext } from "@contexts/SearchImageContext";
import { useParams } from "@solidjs/router";
import { Component, For, Show } from "solid-js";
import { base } from "../../network";
export const List: Component = () => {
const { listId } = useParams();
const { listId } = useParams();
const { lists } = useSearchImageContext();
const { lists } = useSearchImageContext();
const list = () => lists().find((l) => l.ID === listId);
const list = () => lists().find((l) => l.ID === listId);
return (
<Show when={list()} fallback="List could not be found">
{(l) => (
<table>
<thead>
<tr>
<th>Image</th>
<For each={l().Schema.SchemaItems}>
{(item) => <th>{item.Item}</th>}
</For>
</tr>
</thead>
<tbody>
<For each={l().Images}>
{(image) => (
<tr>
<td>
<ImageComponent ID={image.ImageID} />
</td>
<For each={image.Items}>
{(item) => <td>{item.Value}</td>}
</For>
</tr>
)}
</For>
</tbody>
</table>
)}
</Show>
);
return (
<Show when={list()} fallback="List could not be found">
{(l) => (
<div class="w-full h-full bg-white rounded-lg shadow-sm border border-neutral-200 overflow-hidden">
<div class="overflow-x-auto overflow-y-auto h-full">
<table class="w-full min-w-full">
<thead class="bg-neutral-50 border-b border-neutral-200 sticky top-0 z-10">
<tr>
<th class="px-6 py-4 text-left text-sm font-semibold text-neutral-900 border-r border-neutral-200 min-w-40">
Image
</th>
<For each={l().Schema.SchemaItems}>
{(item, index) => (
<th
class={`px-6 py-4 text-left text-sm font-semibold text-neutral-900 min-w-32 ${
index() <
l().Schema.SchemaItems
.length -
1
? "border-r border-neutral-200"
: ""
}`}
>
{item.Item}
</th>
)}
</For>
</tr>
</thead>
<tbody class="divide-y divide-neutral-200">
<For each={l().Images}>
{(image, rowIndex) => (
<tr
class={`hover:bg-neutral-50 transition-colors ${
rowIndex() % 2 === 0
? "bg-white"
: "bg-neutral-25"
}`}
>
<td class="px-6 py-4 border-r border-neutral-200">
<div class="w-32 h-24 overflow-hidden rounded-lg">
<a
href={`/image/${image.ImageID}`}
class="w-full h-full flex justify-center"
>
<img
class="w-full h-full object-cover rounded-lg"
src={`${base}/images/${image.ImageID}`}
alt="List item"
/>
</a>
</div>
</td>
<For each={image.Items}>
{(item, colIndex) => (
<td
class={`px-6 py-4 text-sm text-neutral-700 ${
colIndex() <
image.Items.length -
1
? "border-r border-neutral-200"
: ""
}`}
>
<div
class="max-w-xs truncate"
title={item.Value}
>
{item.Value}
</div>
</td>
)}
</For>
</tr>
)}
</For>
</tbody>
</table>
<Show when={l().Images.length === 0}>
<div class="px-6 py-12 text-center text-neutral-500">
<p class="text-lg">
No images in this list yet
</p>
<p class="text-sm mt-1">
Images will appear here once added to the
list
</p>
</div>
</Show>
</div>
</div>
)}
</Show>
);
};

View File

@@ -1,7 +1,7 @@
import { isTokenValid } from "@components/protected-route";
import { Button } from "@kobalte/core/button";
import { TextField } from "@kobalte/core/text-field";
import { postCode, postDemoLogin, postLogin } from "@network/index";
import { postCode, postLogin } from "@network/index";
import { Navigate } from "@solidjs/router";
import { type Component, Show, createSignal } from "solid-js";
@@ -18,16 +18,6 @@ export const Login: Component = () => {
throw new Error("bruh, no email");
}
if (email.toString() === "demo@email.com") {
const { access, refresh } = await postDemoLogin();
localStorage.setItem("access", access);
localStorage.setItem("refresh", refresh);
window.location.href = "/";
return;
}
if (!submitted()) {
await postLogin(email.toString());
setSubmitted(true);