getting lost files back and making some changes p1

This commit is contained in:
2026-01-14 20:15:07 +01:00
parent 26d140f910
commit bcc5164abb
30609 changed files with 18693 additions and 77811 deletions
View File
+21
View File
@@ -0,0 +1,21 @@
// make api route to get all sellables
import { NextResponse } from "next/server";
import { auth } from "~/server/auth";
import { db } from "~/server/db";
export async function GET() {
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const sellables = await db.sellable.findMany({
include: {
shop: { select: { label: true } },
item: { select: { stock: true } },
},
});
return NextResponse.json(sellables);
}
+175
View File
@@ -0,0 +1,175 @@
// make api route to get user data if user is logged in (nextauth)
import { NextResponse } from "next/server";
import { auth } from "~/server/auth";
import type { Session } from "next-auth";
import { db } from "~/server/db";
export async function PATCH(request: Request) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { adress } = (await request.json()) as { adress: string };
if (!adress || typeof adress !== "string") {
return NextResponse.json({ error: "Invalid adress" }, { status: 400 });
}
const result = await db.adress.create({
data: {
adress,
userId: session.user.id,
},
});
return NextResponse.json(result, { status: 201 });
}
export async function GET() {
const session: Session | null = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = await db.user.findUnique({
where: { id: session.user.id },
select: {
id: true,
name: true,
adresses: {
select: { adress: true },
},
shops: {
select: { id: true, label: true, sellables: true },
},
},
});
if (!user) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
return NextResponse.json({
...user,
adresses: user.adresses.map((a) => a.adress),
});
}
type ShopInput = {
id: number;
label: string;
};
export async function POST(request: Request) {
const session: Session | null = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = (await request.json()) as {
adresses: string[];
shops: ShopInput[];
};
if (!Array.isArray(body.adresses) || !Array.isArray(body.shops)) {
return NextResponse.json(
{ error: "Invalid input format" },
{ status: 400 },
);
}
/* ─────────────── ADDRESSES ─────────────── */
const currentAdresses = await db.adress.findMany({
where: { userId: session.user.id },
select: { id: true, adress: true },
});
const currentAdressSet = new Set(currentAdresses.map((a) => a.adress));
const incomingAdressSet = new Set(body.adresses);
// Add only new addresses
const adressesToAdd = body.adresses.filter((a) => !currentAdressSet.has(a));
// Remove only missing addresses
const adressesToDelete = currentAdresses
.filter((a) => !incomingAdressSet.has(a.adress))
.map((a) => a.id);
if (adressesToAdd.length > 0) {
await db.adress.createMany({
data: adressesToAdd.map((adress) => ({
adress,
userId: session.user.id,
})),
});
}
if (adressesToDelete.length > 0) {
await db.adress.deleteMany({
where: { id: { in: adressesToDelete } },
});
}
/* ─────────────── SHOPS ─────────────── */
const currentShops = await db.shop.findMany({
where: { userId: session.user.id },
select: { id: true, label: true },
});
const incomingShopMap = new Map(body.shops.map((s) => [s.id, s.label]));
const currentShopIds = new Set(currentShops.map((s) => s.id));
const incomingShopIds = new Set(body.shops.map((s) => s.id));
if (!Array.from(incomingShopIds).every((id) => Number.isInteger(id))) {
throw new Error("Invalid shop id");
}
// Create new shops
const shopsToCreate = body.shops.filter((s) => !currentShopIds.has(s.id));
if (shopsToCreate.length > 0) {
await db.shop.createMany({
data: shopsToCreate.map((s) => ({
id: s.id,
label: s.label,
userId: session.user.id,
})),
});
}
// Update labels ONLY if changed
const shopsToUpdate = currentShops.filter(
(s) => incomingShopMap.has(s.id) && incomingShopMap.get(s.id) !== s.label,
);
await Promise.all(
shopsToUpdate.map((s) =>
db.shop.update({
where: { id: s.id },
data: { label: incomingShopMap.get(s.id)! },
}),
),
);
// Delete removed shops (Sellables cascade)
const shopsToDelete = currentShops
.filter((s) => !incomingShopIds.has(s.id))
.map((s) => s.id);
if (shopsToDelete.length > 0) {
await db.shop.deleteMany({
where: { id: { in: shopsToDelete } },
});
}
/* ─────────────── RESPONSE ─────────────── */
return NextResponse.json({ success: true });
}
+204
View File
@@ -0,0 +1,204 @@
import { db } from "~/server/db";
import { auth } from "~/server/auth";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
export default async function ItemsPage() {
const session = await auth();
if (!session?.user?.id) {
redirect("/sign-in");
}
const userId = session.user.id;
// fetch all items for shops owned by the user
const items = await db.sellable.findMany({
where: { shop: { userId: userId } },
include: { shop: true },
});
// extract unique shops for dropdown
const uniqueShops = await db.shop.findMany({
where: { userId: userId },
});
// --- Server actions ---
async function addItem(data: FormData) {
"use server";
const name = data.get("item_name") as string;
const price = parseFloat(data.get("price") as string);
const amount = parseInt(data.get("amount") as string);
const shopId = parseInt(data.get("shopId") as string);
if (!name || isNaN(price) || isNaN(amount) || isNaN(shopId)) return;
await db.sellable.create({
data: { item_name: name, price, amount, shopId },
});
revalidatePath("/items");
}
async function updateItem(data: FormData) {
"use server";
const id = data.get("id") as string;
const name = data.get("item_name") as string;
const price = parseFloat(data.get("price") as string);
const amount = parseInt(data.get("amount") as string);
if (!id || !name || isNaN(price) || isNaN(amount)) return;
await db.sellable.update({
where: { id },
data: { item_name: name, price, amount },
});
revalidatePath("/items");
}
async function deleteItem(data: FormData) {
"use server";
const id = data.get("id") as string;
if (!id) return;
await db.sellable.delete({ where: { id } });
revalidatePath("/items");
}
return (
<main className="flex min-h-screen items-center justify-center bg-linear-to-b from-[#2e026d] to-[#7f3b3b] p-4 text-white">
<div className="w-full max-w-4xl space-y-8 rounded-lg bg-white/10 p-6 backdrop-blur-md">
<h1 className="text-4xl font-extrabold">
Manage <span className="text-[hsl(280,100%,70%)]">Items</span>
</h1>
{/* Add new item */}
<section>
<h2 className="mb-2 text-2xl font-semibold">Add New Item</h2>
<form action={addItem} className="flex flex-col gap-4">
<label className="flex flex-col text-sm">
Item Name
<input
type="text"
name="item_name"
placeholder="Item Name"
required
className="rounded border border-white/30 bg-white/10 p-2 text-sm text-white placeholder-white/70"
/>
</label>
<label className="flex flex-col text-sm">
Price
<input
type="number"
step="0.01"
name="price"
placeholder="Price"
required
className="rounded border border-white/30 bg-white/10 p-2 text-sm text-white placeholder-white/70"
/>
</label>
<label className="flex flex-col text-sm">
Amount
<input
type="number"
name="amount"
placeholder="Amount"
required
className="rounded border border-white/30 bg-white/10 p-2 text-sm text-white placeholder-white/70"
/>
</label>
<label className="flex flex-col text-sm">
Shop
<select
name="shopId"
required
className="rounded border border-white/30 bg-white/10 p-2 text-sm text-white"
>
{uniqueShops.map((shop) => (
<option key={shop.id} value={shop.id}>
{shop.label}
</option>
))}
</select>
</label>
<button
type="submit"
className="rounded bg-[hsl(280,100%,70%)] p-2 text-sm font-semibold"
>
Add Item
</button>
</form>
</section>
{/* Existing items in a grid */}
<section>
<h2 className="mb-2 text-2xl font-semibold">Your Items</h2>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{items.map((item) => (
<div
key={item.id}
className="space-y-2 rounded bg-white/10 p-4 text-sm backdrop-blur-sm"
>
{/* Update form */}
<form action={updateItem} className="flex flex-col gap-2">
<input type="hidden" name="id" value={item.id} />
<label className="flex flex-col">
Name
<input
type="text"
name="item_name"
defaultValue={item.item_name}
className="rounded border border-white/30 bg-white/10 p-1 text-sm text-white"
/>
</label>
<label className="flex flex-col">
Price
<input
type="number"
step="0.01"
name="price"
defaultValue={item.price}
className="rounded border border-white/30 bg-white/10 p-1 text-sm text-white"
/>
</label>
<label className="flex flex-col">
Amount
<input
type="number"
name="amount"
defaultValue={item.amount}
className="rounded border border-white/30 bg-white/10 p-1 text-sm text-white"
/>
</label>
<button
type="submit"
className="rounded bg-green-600 p-1 text-sm font-semibold"
>
Update
</button>
</form>
{/* Delete button */}
<form action={deleteItem}>
<input type="hidden" name="id" value={item.id} />
<button
type="submit"
className="mt-1 w-full rounded bg-red-600 p-1 text-sm font-semibold"
>
Delete
</button>
</form>
<p className="text-xs text-white/70">Shop: {item.shop.label}</p>
</div>
))}
</div>
</section>
</div>
</main>
);
}
+7 -35
View File
@@ -1,37 +1,9 @@
import Link from "next/link";
import { auth } from "~/server/auth";
import type { Session } from "next-auth";
import HomeClient from "~/components/HomeClient";
export default function HomePage() {
return (
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16">
<h1 className="text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]">
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App
</h1>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8">
<Link
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20"
href="https://create.t3.gg/en/usage/first-steps"
target="_blank"
>
<h3 className="text-2xl font-bold">First Steps </h3>
<div className="text-lg">
Just the basics - Everything you need to know to set up your
database and authentication.
</div>
</Link>
<Link
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20"
href="https://create.t3.gg/en/introduction"
target="_blank"
>
<h3 className="text-2xl font-bold">Documentation </h3>
<div className="text-lg">
Learn more about Create T3 App, the libraries it uses, and how to
deploy it.
</div>
</Link>
</div>
</div>
</main>
);
export default async function HomePage() {
const session: Session | null = await auth();
return <HomeClient session={session} />;
}