refactor: list -> stack
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -4,4 +4,4 @@ export * from "./settings";
|
||||
export * from "./login";
|
||||
export * from "./search";
|
||||
export * from "./all-images";
|
||||
export * from "./list";
|
||||
export * from "./stack";
|
||||
|
@ -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
|
Reference in New Issue
Block a user