diff --git a/app/routes.ts b/app/routes.ts index 602d51a..3df1392 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -43,5 +43,6 @@ export default [ route("/api/insights", "routes/api.insights.ts"), route("/api/export/tax", "routes/api.export.tax.ts"), route("/api/inventory/search", "routes/api.inventory.search.ts"), + route("/:username", "routes/$username.tsx"), route("/api/integrations", "routes/api.integrations.ts"), ] satisfies RouteConfig; diff --git a/app/routes/$username.tsx b/app/routes/$username.tsx new file mode 100644 index 0000000..dce2c95 --- /dev/null +++ b/app/routes/$username.tsx @@ -0,0 +1,68 @@ +import { useLoaderData } from "react-router"; +import type { Route } from "./+types/$username"; +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +export async function loader({ params }: Route.LoaderArgs) { + const { username } = params; + + const user = await prisma.user.findUnique({ + where: { username }, + select: { id: true, name: true, username: true, avatarUrl: true }, + }); + + if (!user) throw new Response("Not Found", { status: 404 }); + + const items = await prisma.inventoryItem.findMany({ + where: { userId: user.id, isPublic: true }, + select: { + id: true, + name: true, + brand: true, + sku: true, + size: true, + condition: true, + status: true, + askingPrice: true, + imageUrl: true, + category: true, + tags: true, + }, + orderBy: { createdAt: "desc" }, + }); + + return { + user, + items: items.map((item) => ({ + ...item, + askingPrice: item.askingPrice ? Number(item.askingPrice) : null, + })), + }; +} + +export default function PublicShowroom() { + const { user, items } = useLoaderData(); + + return ( +
+

{user.name ?? user.username}'s Showroom

+ {items.length === 0 ? ( +

No items listed for sale.

+ ) : ( +
+ {items.map((item) => ( +
+ {item.imageUrl && {item.name}} +

{item.name}

+

{item.brand} · {item.size}

+

+ {item.askingPrice ? `$${item.askingPrice.toFixed(2)}` : "Price on request"} +

+
+ ))} +
+ )} +
+ ); +} diff --git a/package-lock.json b/package-lock.json index fde8452..7753209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4017,6 +4017,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, diff --git a/prisma/migrations/20260701_add_username_and_ispublic/migration.sql b/prisma/migrations/20260701_add_username_and_ispublic/migration.sql new file mode 100644 index 0000000..38c257a --- /dev/null +++ b/prisma/migrations/20260701_add_username_and_ispublic/migration.sql @@ -0,0 +1,6 @@ +-- Add username to User model +ALTER TABLE "User" ADD COLUMN "username" TEXT; +CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); + +-- Add isPublic to InventoryItem model +ALTER TABLE "InventoryItem" ADD COLUMN "isPublic" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1c71dde..f49b283 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -96,26 +96,27 @@ enum Theme { // ─── CORE MODELS ────────────────────────────────────────────────────────────── model User { - id String @id @default(uuid()) - email String @unique - name String? - avatarUrl String? - plan Plan @default(FREE) - stripeCustomerId String? @unique - stripeSubId String? @unique - currency Currency @default(USD) - theme Theme @default(LIGHT) - phone String? - pushEndpoint String? // Web push subscription JSON - emailNotifications Boolean @default(true) - smsNotifications Boolean @default(false) - pushNotifications Boolean @default(false) - weeklySummary Boolean @default(true) - priceAlertTriggered Boolean @default(true) - teamId String? - role String @default("owner") // owner | member (team) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(uuid()) + email String @unique + username String? @unique + name String? + avatarUrl String? + plan Plan @default(FREE) + stripeCustomerId String? @unique + stripeSubId String? @unique + currency Currency @default(USD) + theme Theme @default(LIGHT) + phone String? + pushEndpoint String? // Web push subscription JSON + emailNotifications Boolean @default(true) + smsNotifications Boolean @default(false) + pushNotifications Boolean @default(false) + weeklySummary Boolean @default(true) + priceAlertTriggered Boolean @default(true) + teamId String? + role String @default("owner") // owner | member (team) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt inventory InventoryItem[] sales Sale[] @@ -168,6 +169,7 @@ model InventoryItem { askingPrice Decimal? @db.Decimal(10, 2) notes String? imageUrl String? + isPublic Boolean @default(false) currency Currency @default(USD) tags String[] // flexible tags createdAt DateTime @default(now())