diff --git a/.env.local.example b/.env.local.example index 1ee80ef..691292f 100644 --- a/.env.local.example +++ b/.env.local.example @@ -5,12 +5,6 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key # Service role key is only used server-side (API routes / webhooks) SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key -# ─── Cloudinary ────────────────────────────────────────────────────────────── -# Found in: Cloudinary Dashboard → Settings → API Keys -NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name -CLOUDINARY_API_KEY=your_api_key -CLOUDINARY_API_SECRET=your_api_secret - # ─── Stripe ───────────────────────────────────────────────────────────────── # Found in: Stripe Dashboard → Developers → API keys NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..af516ff --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + branches: + - '**' + +jobs: + biome: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Biome + uses: biomejs/setup-biome@v2 + with: + version: latest + + - name: Run Biome + run: biome check \ No newline at end of file diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx index a3a97c2..501eb8b 100644 --- a/app/[slug]/page.tsx +++ b/app/[slug]/page.tsx @@ -304,7 +304,11 @@ export default async function ArtworkDetailPage(props: { params: Promise<{ slug: {details.map(({ label, value, accent, bold }) => (
{label} - {value} + + {value} +
))} {collection && ( diff --git a/app/api/webhook/route.ts b/app/api/webhook/route.ts index e3506a3..2254cf6 100644 --- a/app/api/webhook/route.ts +++ b/app/api/webhook/route.ts @@ -131,7 +131,7 @@ export async function POST(request: Request) { emailItems = [{ title: artwork.title, price: artwork.price, type: "print", quantity }]; } - // Case 2: cart checkout (metadata.items JSON) + // Case 2: cart checkout (metadata.items JSON) } else if (meta.items) { const cartItems: { id: string; qty: number; type: "original" | "print" }[] = JSON.parse(meta.items); const ids = cartItems.map(i => i.id); @@ -156,7 +156,7 @@ export async function POST(request: Request) { quantity: item.qty, })); - // Case 3: original artwork direct purchase (existing metadata.artworkIds) + // Case 3: original artwork direct purchase (existing metadata.artworkIds) } else if (meta.artworkIds) { const artworkIds = meta.artworkIds.split(",").filter(Boolean); @@ -197,7 +197,6 @@ export async function POST(request: Request) { type: "original" as const, quantity: 1, })); - } else { console.error("Webhook: unrecognised metadata", { sessionId: session.id, meta }); return Response.json({ error: "Missing metadata" }, { status: 400 }); diff --git a/app/globals.css b/app/globals.css index 9035ba8..ac8dde8 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,22 +1,18 @@ @import "tailwindcss"; -@variant dark -( -&:where(.dark, .dark *)); - @theme { - --color-bg-main: #FAF8F5; - --color-bg-warm: #F0ECE6; - --color-bg-deep: #1A1917; - --color-bg-stone: #E8E3DB; - --color-accent: #260614; - --color-divider: #DDD8D0; - --color-divider-dark: #2A2926; - --color-text-primary: #1A1917; - --color-text-secondary: #6B6660; - --color-text-tertiary: #6E6A68; - --color-text-light: #FAF8F5; - --color-text-light-muted: #A8A29C; + --color-bg-main: #faf8f5; + --color-bg-warm: #f0ece6; + --color-bg-deep: #1a1917; + --color-bg-stone: #e8e3db; + --color-accent: #260614; + --color-divider: #ddd8d0; + --color-divider-dark: #2a2926; + --color-text-primary: #1a1917; + --color-text-secondary: #6b6660; + --color-text-tertiary: #6e6a68; + --color-text-light: #faf8f5; + --color-text-light-muted: #a8a29c; --font-sans: "Funnel Sans", ui-sans-serif, system-ui, sans-serif; @@ -33,7 +29,6 @@ html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - } body { @@ -58,8 +53,5 @@ button { } @utility input-line { - @apply w-full border-0 border-b border-divider bg-transparent - pb-3 pt-1 text-text-primary placeholder-text-tertiary - text-sm font-sans outline-hidden transition-colors - focus:border-text-secondary resize-none; + @apply w-full border-0 border-b border-divider bg-transparent pb-3 pt-1 text-text-primary placeholder-text-tertiary text-sm font-sans outline-hidden transition-colors focus:border-text-secondary resize-none; } diff --git a/drizzle/migrations/meta/20260403115657_snapshot.json b/drizzle/migrations/meta/20260403115657_snapshot.json index 42568de..228d80c 100644 --- a/drizzle/migrations/meta/20260403115657_snapshot.json +++ b/drizzle/migrations/meta/20260403115657_snapshot.json @@ -207,12 +207,8 @@ "tableFrom": "artworks", "tableTo": "collections", "schemaTo": "public", - "columnsFrom": [ - "collection_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -225,9 +221,7 @@ "name": "artworks: public read", "as": "PERMISSIVE", "for": "SELECT", - "to": [ - "public" - ], + "to": ["public"], "using": "true" } }, @@ -391,12 +385,8 @@ "tableFrom": "orders", "tableTo": "artworks", "schemaTo": "public", - "columnsFrom": [ - "artwork_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["artwork_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -404,10 +394,7 @@ "compositePrimaryKeys": {}, "uniqueConstraints": { "orders_session_artwork_unique": { - "columns": [ - "artwork_id", - "stripe_session_id" - ], + "columns": ["artwork_id", "stripe_session_id"], "nullsNotDistinct": false, "name": "orders_session_artwork_unique" } @@ -520,9 +507,7 @@ "compositePrimaryKeys": {}, "uniqueConstraints": { "collections_slug_key": { - "columns": [ - "slug" - ], + "columns": ["slug"], "nullsNotDistinct": false, "name": "collections_slug_key" } @@ -533,9 +518,7 @@ "name": "Public read", "as": "PERMISSIVE", "for": "SELECT", - "to": [ - "public" - ], + "to": ["public"], "using": "true" } }, @@ -566,4 +549,4 @@ } } } -} \ No newline at end of file +} diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index 4557b80..a718b86 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -10,4 +10,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/features/artwork/artwork-info-section.tsx b/features/artwork/artwork-info-section.tsx index a08f212..17c73ba 100644 --- a/features/artwork/artwork-info-section.tsx +++ b/features/artwork/artwork-info-section.tsx @@ -39,9 +39,10 @@ const returnsItem = { export default function ArtworkInfoSection({ type }: { type?: string | null }) { const typeItem = type === "print" ? printItem : originalItem; - const items = type === "print" - ? [...sharedItems, typeItem, signedEditionItem, returnsItem] - : [...sharedItems, typeItem, returnsItem]; + const items = + type === "print" + ? [...sharedItems, typeItem, signedEditionItem, returnsItem] + : [...sharedItems, typeItem, returnsItem]; return (
diff --git a/features/breadcrumb.tsx b/features/breadcrumb.tsx index d080497..13e45bd 100644 --- a/features/breadcrumb.tsx +++ b/features/breadcrumb.tsx @@ -1,12 +1,12 @@ "use client"; import Link from "next/link"; + interface BreadcrumbItem { label: string; href?: string; } export default function Breadcrumb({ items }: { items: BreadcrumbItem[] }) { - function handleClick(href: string) { const hashMatch = href.match(/^\/#(.+)$/); if (hashMatch) { diff --git a/features/cart/cart-drawer.tsx b/features/cart/cart-drawer.tsx index f3f4dbe..fe0f7df 100644 --- a/features/cart/cart-drawer.tsx +++ b/features/cart/cart-drawer.tsx @@ -127,7 +127,9 @@ export default function CartDrawer() { {item.type === "print" && item.quantity > 1 && (

Qty: {item.quantity}

)} -

{formatPrice(item.price * item.quantity)}

+

+ {formatPrice(item.price * item.quantity)} +

))}