Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions app/api/shopping-list/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from "next/server";
import { getSession } from "@/lib/auth";
import prisma from "@/lib/prisma";

type Ctx = {
params: Promise<{ id: string }>;
};

export async function DELETE(_req: NextRequest, context: Ctx) {
const session = await getSession();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const { id } = await context.params;
const listId = Number(id);

if (!Number.isFinite(listId)) {
return NextResponse.json({ error: "Invalid id" }, { status: 400 });
}

const list = await prisma.shoppingList.findUnique({
where: { id: listId },
select: { id: true, userId: true },
});

if (!list || list.userId !== session.user.id) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}

await prisma.shoppingList.delete({ where: { id: listId } });

return NextResponse.json({ ok: true }, { status: 200 });
}
35 changes: 35 additions & 0 deletions app/components/DeleteShoppingListButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import { useRouter } from "next/navigation";

export default function DeleteShoppingListButton({ id }: { id: number }) {
const router = useRouter();

async function onDelete() {
const ok = confirm("Na pewno usunąć tę listę zakupów?");
if (!ok) return;

const res = await fetch(`/api/shopping-list/${id}`, {
method: "DELETE",
});

if (!res.ok) {
const txt = await res.text();
console.error("DELETE /api/shopping-list/[id] nieudany", txt);
alert("Nie udało się usunąć listy.");
return;
}

router.refresh();
}

return (
<button
onClick={onDelete}
className="inline-flex items-center rounded-xl border border-red-400/40 bg-red-500/10 px-3 py-1.5 text-sm font-medium text-red-200 hover:bg-red-500/20 transition"
type="button"
>
Usuń
</button>
);
}
80 changes: 55 additions & 25 deletions app/shopping-list/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,73 @@
import prisma from "@/lib/prisma"
import { getSession } from "@/lib/auth"
import prisma from "@/lib/prisma";
import { getSession } from "@/lib/auth";
import Link from "next/link";

type Props = {
params : Promise<{ id: string }>
params: Promise<{ id: string }>;
};

type ShoppingItem = { name: string; amount: number };

function formatDate(d: Date) {
return d.toISOString().slice(0, 10);
}

export default async function ShoppingListDetailPage({ params }: Props) {
const session = await getSession()
if (!session?.user?.id) return <p> Musisz być zalogowany</p>
const session = await getSession();
if (!session?.user?.id) return <p className="p-10">Musisz być zalogowany</p>;

const corParams = await params
const id = parseInt(corParams.id, 10)
console.log("ID listy zakupów:", id)
const { id } = await params;
const listId = parseInt(id, 10);

const list = await prisma.shoppingList.findUnique({
where: { id },
})
where: { id: listId },
});

if (!list || list.userId !== session.user.id) {
return <p>Lista zakupów nie znaleziona</p>
return <p className="p-10">Lista zakupów nie znaleziona</p>;
}

const shoppingListItems = (list.items as { name: string; amount: number }[])
const items = list.items as ShoppingItem[];

return (
<div className="flex items-center min-h-screen py-12 px-40">
<div className="mx-auto max-w-4xl px-6 py-10">
<div className="mb-6 flex items-start justify-between gap-4">
<div>
<h1 className="text-m text-violet-400 font-semibold mb-6">
Lista zakupów: od {list.startDate.toISOString().slice(0,10)} do {list.endDate.toISOString().slice(0,10)}
</h1>

<ul className="space-y-2">
{shoppingListItems.map((item: any) => (
<li key={item.name}>
{item.name}: {item.amount}g
</li>
))}
</ul>
<h1 className="text-2xl md:text-3xl font-bold text-zinc-100">
Lista zakupów
</h1>
<p className="mt-1 text-sm text-zinc-300">
Zakres: {formatDate(list.startDate)} → {formatDate(list.endDate)}
</p>
</div>

<Link
href="/shopping-list"
className="rounded-xl border border-purple-300 px-3 py-1 text-sm text-purple-200 hover:bg-purple-100/10 transition"
>
← Wróć
</Link>
</div>

<div className="rounded-2xl border border-zinc-800 bg-zinc-900/60 p-6">
{items.length === 0 ? (
<p className="text-zinc-300">Brak produktów na liście.</p>
) : (
<ul className="space-y-3">
{items.map((item) => (
<li
key={item.name}
className="flex items-center justify-between rounded-xl border border-zinc-800 bg-zinc-950/40 px-4 py-3"
>
<span className="text-zinc-100">{item.name}</span>
<span className="text-sm font-semibold text-violet-200">
{item.amount} g
</span>
</li>
))}
</ul>
)}
</div>
</div>
)
);
}
78 changes: 62 additions & 16 deletions app/shopping-list/page.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,84 @@
import prisma from "@/lib/prisma";
import { getSession } from "@/lib/auth";
import Link from "next/link";
import DeleteShoppingListButton from "@/app/components/DeleteShoppingListButton";

function formatDate(d: Date) {
return d.toISOString().slice(0, 10);
}

export default async function ShoppingListPage() {
const session = await getSession();
if (!session?.user?.id) return <p>Musisz być zalogowany</p>;
if (!session?.user?.id) return <p className="p-10">Musisz być zalogowany</p>;

const lists = await prisma.shoppingList.findMany({
where: { userId: session.user.id },
orderBy: { createdAt: "desc" },
});


return (
<div className="mx-auto max-w-4xl p-10">
<h1 className="text-3xl font-semibold text-violet-600 mb-4">
Listy zakupowe
</h1>
<div className="mx-auto max-w-6xl px-6 py-10">
<div className="mb-8 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-3xl md:text-4xl font-bold tracking-tight text-zinc-100">
Listy zakupowe
</h1>
<p className="text-sm text-zinc-300 mt-1">
Wybierz listę, aby zobaczyć szczegóły.
</p>
</div>

<Link
href="/shopping-list/new"
className="inline-flex w-fit items-center justify-center rounded-xl bg-violet-600 px-4 py-2 text-sm font-semibold text-white hover:bg-violet-500 transition"
>
+ Wygeneruj nową listę
</Link>
</div>

{lists.length === 0 ? (
<p>Brak list zakupów. Wygeneruj nową listę zakupów.</p>
<div className="rounded-2xl border border-zinc-800 bg-zinc-900/60 p-6 text-zinc-300">
Brak list zakupów. Kliknij <span className="font-semibold">“Wygeneruj nową listę”</span>.
</div>
) : (
<ul className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{lists.map((list) => (
<li
key={list.id}
className="group rounded-2xl border border-zinc-800 bg-zinc-900/60 p-5 shadow-sm transition hover:bg-zinc-900"
>
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-xs uppercase tracking-wide text-zinc-400">
Zakres dat
</p>
<h2 className="mt-1 text-lg font-semibold text-white group-hover:text-violet-300 transition">
{formatDate(list.startDate)} → {formatDate(list.endDate)}
</h2>
</div>

// bardzo proste przekierowanie do list
<ul className="space-y-2 text-xl text-violet-800">
{lists.map(list => (
<li key={list.id} className="hover:text-violet-950 hover:underline">
<a href={`/shopping-list/${list.id}`}>
▫︎ {list.startDate.toISOString().slice(0, 10)} → {list.endDate.toISOString().slice(0, 10)}
</a>
</div>

<div className="mt-4 flex items-center justify-between">
<p className="text-xs text-zinc-400">
Utworzono: {formatDate(list.createdAt)}
</p>

<div className="flex items-center gap-2">
<Link
href={`/shopping-list/${list.id}`}
className="inline-flex items-center rounded-xl border border-violet-400/40 bg-violet-600/10 px-3 py-1.5 text-sm font-medium text-violet-200 hover:bg-violet-600/20 transition"
>
Zobacz →
</Link>

<DeleteShoppingListButton id={list.id} />
</div>
</div>
</li>
))}
</ul>

)}
)}
</div>
);
}