refactor: list -> stack

This commit is contained in:
2025-10-05 15:12:37 +01:00
parent 9948d2521b
commit 838ab37fc1
8 changed files with 59 additions and 59 deletions

View File

@ -53,7 +53,7 @@ func (h *StackHandler) getStackItems(w http.ResponseWriter, r *http.Request) {
return
}
stackID, err := middleware.GetPathParamID(h.logger, "listID", w, r)
stackID, err := middleware.GetPathParamID(h.logger, "stackID", w, r)
if err != nil {
return
}
@ -86,7 +86,7 @@ func (h *StackHandler) deleteStack(w http.ResponseWriter, r *http.Request) {
return
}
stackID, err := middleware.GetPathParamID(h.logger, "listID", w, r)
stackID, err := middleware.GetPathParamID(h.logger, "stackID", w, r)
if err != nil {
return
}

View File

@ -7,7 +7,7 @@ import {
Settings,
SearchPage,
AllImages,
List,
Stack,
} from "./pages";
import { SearchImageContextProvider } from "@contexts/SearchImageContext";
import { WithNotifications } from "@contexts/Notifications";
@ -41,7 +41,7 @@ export const App = () => {
path="/image/:imageId"
component={ImagePage}
/>
<Route path="/list/:listId" component={List} />
<Route path="/stack/:stackID" component={Stack} />
<Route path="/settings" component={Settings} />
</Route>
</Route>

View File

@ -1,4 +1,4 @@
import { List } from "@network/index";
import { Stack } from "@network/index";
import { Component } from "solid-js";
import fastHashCode from "../../utils/hash";
import { A } from "@solidjs/router";
@ -17,19 +17,19 @@ const colors = [
"bg-pink-50",
];
export const StackCard: Component<{ list: List }> = (props) => {
export const StackCard: Component<{ stack: Stack }> = (props) => {
return (
<A
href={`/list/${props.list.ID}`}
href={`/stack/${props.stack.ID}`}
class={
"flex flex-col p-4 border border-neutral-200 rounded-lg " +
colors[
fastHashCode(props.list.Name, { forcePositive: true }) % colors.length
fastHashCode(props.stack.Name, { forcePositive: true }) % colors.length
]
}
>
<p class="text-xl font-bold">{props.list.Name}</p>
<p class="text-lg">{props.list.Images.length}</p>
<p class="text-xl font-bold">{props.stack.Name}</p>
<p class="text-lg">{props.stack.Images.length}</p>
</A>
);
};

View File

@ -133,7 +133,7 @@ export const deleteStackItem = async (
await fetch(request);
}
export const deleteList = async (listID: string): Promise<void> => {
export const deleteStack = async (listID: string): Promise<void> => {
const request = await getBaseAuthorizedRequest({
path: `stacks/${listID}`,
method: "DELETE",
@ -238,7 +238,7 @@ const stackValidator = strictObject({
SchemaItems: array(stackSchemaItem),
});
export type List = InferOutput<typeof stackValidator>;
export type Stack = InferOutput<typeof stackValidator>;
const imageRequestValidator = strictObject({
UserImages: array(userImageValidator),
@ -303,13 +303,13 @@ export const postCode = async (
return parsedRes.output;
};
export class ReachedListLimit extends Error {
export class ReachedStackLimit extends Error {
constructor() {
super();
}
}
export const createList = async (
export const createStack = async (
title: string,
description: string,
): Promise<void> => {
@ -323,6 +323,6 @@ export const createList = async (
const res = await fetch(request);
if (!res.ok && res.status == 429) {
throw new ReachedListLimit();
throw new ReachedStackLimit();
}
};

View File

@ -1,10 +1,10 @@
import { Component, For, createSignal } from "solid-js";
import { useSearchImageContext } from "@contexts/SearchImageContext";
import { StackCard } from "@components/list-card";
import { Button } from "@kobalte/core/button";
import { Dialog } from "@kobalte/core/dialog";
import { createList, ReachedListLimit } from "../../network";
import { createStack, ReachedStackLimit } from "../../network";
import { createToast } from "../../utils/show-toast";
import { StackCard } from "@components/stack-card";
export const Categories: Component = () => {
const { stacks, onRefetchImages } = useSearchImageContext();
@ -15,20 +15,20 @@ export const Categories: Component = () => {
const [isCreating, setIsCreating] = createSignal(false);
const [showForm, setShowForm] = createSignal(false);
const handleCreateList = async () => {
const handleCreatestack = async () => {
if (description().trim().length === 0 || title().trim().length === 0)
return;
setIsCreating(true);
try {
await createList(title().trim(), description().trim());
await createStack(title().trim(), description().trim());
setTitle("");
setDescription("");
setShowForm(false);
onRefetchImages(); // Refresh the stacks
} catch (error) {
console.error("Failed to create list:", error);
if (error instanceof ReachedListLimit) {
console.error("Failed to create stack:", error);
if (error instanceof ReachedStackLimit) {
createToast("Reached limit!", "You've reached your limit for new stacks");
}
} finally {
@ -40,7 +40,7 @@ export const Categories: Component = () => {
<div class="rounded-xl bg-white p-4 flex flex-col gap-2">
<h2 class="text-xl font-bold">Generated stacks</h2>
<div class="w-full grid grid-cols-3 auto-rows-[minmax(100px,1fr)] gap-4">
<For each={stacks()}>{(list) => <StackCard list={list} />}</For>
<For each={stacks()}>{(stack) => <StackCard stack={stack} />}</For>
</div>
<div class="mt-4">
@ -48,7 +48,7 @@ export const Categories: Component = () => {
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
+ Create stack
</Button>
</div>
@ -59,25 +59,25 @@ export const Categories: Component = () => {
<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
Create New stack
</Dialog.Title>
<div class="space-y-4">
<div>
<label
for="list-title"
for="stack-title"
class="block text-sm font-medium text-neutral-700 mb-2"
>
List Title
stack Title
</label>
<input
id="list-title"
id="stack-title"
type="text"
value={title()}
onInput={(e) =>
setTitle(e.target.value)
}
placeholder="Enter a title for your list"
placeholder="Enter a title for your stack"
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()}
/>
@ -85,18 +85,18 @@ export const Categories: Component = () => {
<div>
<label
for="list-description"
for="stack-description"
class="block text-sm font-medium text-neutral-700 mb-2"
>
List Description
stack Description
</label>
<textarea
id="list-description"
id="stack-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')"
placeholder="Describe what kind of stack 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()}
@ -107,7 +107,7 @@ export const Categories: Component = () => {
<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}
onClick={handleCreatestack}
disabled={
isCreating() ||
!title().trim() ||
@ -116,7 +116,7 @@ export const Categories: Component = () => {
>
{isCreating()
? "Creating..."
: "Create List"}
: "Create stack"}
</Button>
<Button
class="px-4 py-2 bg-neutral-300 text-neutral-700 rounded-lg hover:bg-neutral-400 transition-colors font-medium"

View File

@ -1,9 +1,9 @@
import { ImageComponentFullHeight } from "@components/image";
import { StackCard } from "@components/stack-card";
import { useSearchImageContext } from "@contexts/SearchImageContext";
import { useNavigate, useParams } from "@solidjs/router";
import { For, type Component } from "solid-js";
import SolidjsMarkdown from "solidjs-markdown";
import { StackCard } from "@components/list-card";
export const ImagePage: Component = () => {
const { imageId } = useParams<{ imageId: string }>();
@ -27,7 +27,7 @@ export const ImagePage: Component = () => {
<For each={image()?.ImageStacks}>
{(imageList) => (
<StackCard
list={stacks().find((l) => l.ID === imageList.ListID)!}
stack={stacks().find((l) => l.ID === imageList.ListID)!}
/>
)}
</For>

View File

@ -4,4 +4,4 @@ export * from "./settings";
export * from "./login";
export * from "./search";
export * from "./all-images";
export * from "./list";
export * from "./stack";

View File

@ -8,7 +8,7 @@ import {
createResource,
createSignal,
} from "solid-js";
import { base, deleteList, getAccessToken } from "../../network";
import { base, deleteStack, getAccessToken } from "../../network";
import { Dialog } from "@kobalte/core";
const DeleteButton: Component<{ onDelete: () => void }> = (props) => {
@ -57,7 +57,7 @@ const DeleteButton: Component<{ onDelete: () => void }> = (props) => {
);
};
const DeleteListButton: Component<{ onDelete: () => void }> = (props) => {
const DeleteStackButton: Component<{ onDelete: () => void }> = (props) => {
const [isOpen, setIsOpen] = createSignal(false);
return (
@ -67,7 +67,7 @@ const DeleteListButton: Component<{ onDelete: () => void }> = (props) => {
class="text-white bg-red-600 hover:bg-red-700 rounded px-3 py-2 text-sm font-medium"
onClick={() => setIsOpen(true)}
>
Delete List
Delete Stack
</button>
<Dialog.Root open={isOpen()} onOpenChange={setIsOpen}>
@ -76,7 +76,7 @@ const DeleteListButton: Component<{ onDelete: () => void }> = (props) => {
<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 p-6">
<Dialog.Title class="text-lg font-bold mb-2">
Confirm Delete List
Confirm Delete Stack
</Dialog.Title>
<Dialog.Description class="mb-4">
Are you sure you want to delete this entire
@ -92,7 +92,7 @@ const DeleteListButton: Component<{ onDelete: () => void }> = (props) => {
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
onClick={props.onDelete}
>
Delete List
Delete Stack
</button>
</div>
</Dialog.Content>
@ -103,39 +103,39 @@ const DeleteListButton: Component<{ onDelete: () => void }> = (props) => {
);
};
export const List: Component = () => {
const { listId } = useParams();
export const Stack: Component = () => {
const { stackID } = useParams();
const nav = useNavigate();
const { stacks, onDeleteImageFromStack } = useSearchImageContext();
const [accessToken] = createResource(getAccessToken);
const list = () => stacks().find((l) => l.ID === listId);
const stack = () => stacks().find((l) => l.ID === stackID);
const handleDeleteList = async () => {
await deleteList(listId)
const handleDeleteStack = async () => {
await deleteStack(stackID)
nav("/");
};
return (
<Suspense>
<Show when={list()} fallback="List could not be found">
{(l) => (
<Show when={stack()} fallback="Stack could not be found">
{(s) => (
<div class="w-full h-full bg-white rounded-lg shadow-sm border border-neutral-200 overflow-hidden">
<div class="px-6 py-4 border-b border-neutral-200 bg-neutral-50">
<div class="flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold text-neutral-900">
{l().Name}
{s().Name}
</h1>
<Show when={l().Description}>
<Show when={s().Description}>
<p class="text-sm text-neutral-600 mt-1">
{l().Description}
{s().Description}
</p>
</Show>
</div>
<DeleteListButton onDelete={handleDeleteList} />
<DeleteStackButton onDelete={handleDeleteStack} />
</div>
</div>
<div
@ -148,11 +148,11 @@ export const List: Component = () => {
<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().SchemaItems}>
<For each={s().SchemaItems}>
{(item, index) => (
<th
class={`px-6 py-4 text-left text-sm font-semibold text-neutral-900 min-w-32 ${index() <
l().SchemaItems
s().SchemaItems
.length -
1
? "border-r border-neutral-200"
@ -166,7 +166,7 @@ export const List: Component = () => {
</tr>
</thead>
<tbody class="divide-y divide-neutral-200">
<For each={l().Images}>
<For each={s().Images}>
{(image, rowIndex) => (
<tr
class={`hover:bg-neutral-50 transition-colors ${rowIndex() % 2 === 0
@ -183,13 +183,13 @@ export const List: Component = () => {
<img
class="w-full h-full object-cover rounded-lg"
src={`${base}/images/${image.ImageID}?token=${accessToken()}`}
alt="List item"
alt="Stack item"
/>
</a>
<DeleteButton
onDelete={() =>
onDeleteImageFromStack(
l().ID,
s().ID,
image.ImageID,
)
}
@ -223,7 +223,7 @@ export const List: Component = () => {
</For>
</tbody>
</table>
<Show when={l().Images.length === 0}>
<Show when={s().Images.length === 0}>
<div class="px-6 py-12 text-center text-neutral-500">
<p class="text-lg">
No images in this list yet