Refactor code
This commit is contained in:
@ -2,15 +2,6 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
/**
|
|
||||||
* Server action
|
|
||||||
* Forwards a multipart/form-data request (payload + files[])
|
|
||||||
* from the public app to the admin app's public commissions endpoint.
|
|
||||||
*
|
|
||||||
* Server-only env required:
|
|
||||||
* ADMIN_URL=https://admin.domain.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
const submitPayloadSchema = z.object({
|
const submitPayloadSchema = z.object({
|
||||||
typeId: z.string().optional().nullable(),
|
typeId: z.string().optional().nullable(),
|
||||||
optionId: z.string().optional().nullable(),
|
optionId: z.string().optional().nullable(),
|
||||||
@ -36,7 +27,6 @@ export async function submitCommissionRequest(input: {
|
|||||||
const payload = submitPayloadSchema.parse(input.payload);
|
const payload = submitPayloadSchema.parse(input.payload);
|
||||||
const files = input.files ?? [];
|
const files = input.files ?? [];
|
||||||
|
|
||||||
// Optional safety limits
|
|
||||||
const MAX_FILES = 10;
|
const MAX_FILES = 10;
|
||||||
const MAX_BYTES_EACH = 10 * 1024 * 1024; // 10MB
|
const MAX_BYTES_EACH = 10 * 1024 * 1024; // 10MB
|
||||||
|
|
||||||
@ -70,7 +60,6 @@ export async function submitCommissionRequest(input: {
|
|||||||
const raw = await res.text().catch(() => "");
|
const raw = await res.text().catch(() => "");
|
||||||
const statusLine = `${res.status} ${res.statusText || ""}`.trim();
|
const statusLine = `${res.status} ${res.statusText || ""}`.trim();
|
||||||
|
|
||||||
// Show something useful even if raw is empty
|
|
||||||
let message = `Admin API error: ${statusLine}`;
|
let message = `Admin API error: ${statusLine}`;
|
||||||
|
|
||||||
if (raw) {
|
if (raw) {
|
||||||
@ -87,12 +76,9 @@ export async function submitCommissionRequest(input: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log full body server-side for debugging (safe; this is server-only)
|
|
||||||
console.error("[submitCommissionRequest] upstream error", { statusLine, raw });
|
console.error("[submitCommissionRequest] upstream error", { statusLine, raw });
|
||||||
|
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expected response: { id: string; createdAt: string }
|
|
||||||
return (await res.json()) as { id: string; createdAt: string };
|
return (await res.json()) as { id: string; createdAt: string };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import type { PortfolioFilters } from "@/actions/portfolio/getPortfolioArtworksPage";
|
import type { PortfolioFilters } from "@/actions/portfolio/getPortfolioArtworksPage";
|
||||||
import { Prisma } from "@/generated/prisma/browser";
|
import type { Prisma } from "@/generated/prisma/browser";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
function coerceYear(y: PortfolioFilters["year"]) {
|
function coerceYear(y: PortfolioFilters["year"]) {
|
||||||
|
|||||||
@ -107,7 +107,6 @@ export async function getPortfolioArtworksPage(args: {
|
|||||||
.filter((y): y is number => typeof y === "number")
|
.filter((y): y is number => typeof y === "number")
|
||||||
.sort((a, b) => b - a);
|
.sort((a, b) => b - a);
|
||||||
|
|
||||||
// Segment logic (sortKey != null first, then null)
|
|
||||||
const inNullSegment = cursor?.afterSortKey === null;
|
const inNullSegment = cursor?.afterSortKey === null;
|
||||||
|
|
||||||
const select = {
|
const select = {
|
||||||
@ -180,7 +179,6 @@ export async function getPortfolioArtworksPage(args: {
|
|||||||
if (!last) {
|
if (!last) {
|
||||||
return { items, nextCursor: null, total, years, albums };
|
return { items, nextCursor: null, total, years, albums };
|
||||||
}
|
}
|
||||||
// last.sortKey can be null only in null-segment, which we are not in here.
|
|
||||||
if (last.sortKey == null) {
|
if (last.sortKey == null) {
|
||||||
return { items, nextCursor: null, total, years, albums };
|
return { items, nextCursor: null, total, years, albums };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,29 +19,6 @@ export default function Home() {
|
|||||||
<SocialLinks />
|
<SocialLinks />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Section Cards */}
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
|
||||||
{/* <p>
|
|
||||||
If you want to commission me you can find all the information you need under following link: <a href="https://linktr.ee/gaertan" target="_blank">Linktree</a>
|
|
||||||
</p> */}
|
|
||||||
{/* {sections.map((section) => (
|
|
||||||
<Link href={section.href} key={section.title}>
|
|
||||||
<Card className="hover:shadow-xl transition-shadow group">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2 text-lg">
|
|
||||||
<section.icon className="w-5 h-5 text-muted-foreground group-hover:text-primary" />
|
|
||||||
{section.title}
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="text-sm text-muted-foreground">
|
|
||||||
{section.description}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Link>
|
|
||||||
))} */}
|
|
||||||
</div>
|
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,6 @@ export default async function TosPage() {
|
|||||||
orderBy: [{ version: "desc" }],
|
orderBy: [{ version: "desc" }],
|
||||||
})
|
})
|
||||||
|
|
||||||
// console.log(tos?.markdown)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-6xl px-4 py-8">
|
<div className="mx-auto w-full max-w-6xl px-4 py-8">
|
||||||
<div className="markdown">
|
<div className="markdown">
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export async function GET(_req: NextRequest, context: { params: Promise<{ key: s
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": contentType,
|
"Content-Type": contentType,
|
||||||
"Cache-Control": "public, max-age=3600",
|
"Cache-Control": "public, max-age=3600",
|
||||||
"Content-Disposition": "inline", // use 'attachment' to force download
|
"Content-Disposition": "inline",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -67,6 +67,7 @@ function BrandSvg({ icon }: { icon: SimpleIcon }) {
|
|||||||
className="h-5 w-5 fill-current"
|
className="h-5 w-5 fill-current"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
|
// biome-ignore lint: lint/security/noDangerouslySetInnerHtml
|
||||||
dangerouslySetInnerHTML={{ __html: icon.svg }}
|
dangerouslySetInnerHTML={{ __html: icon.svg }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export default function RawCloseButton({ targetHref }: RawCloseButtonProps) {
|
|||||||
onClick={() => router.push(targetHref)}
|
onClick={() => router.push(targetHref)}
|
||||||
className="absolute top-4 right-4 z-50 rounded-md bg-background/80 p-2 hover:bg-background/60 transition"
|
className="absolute top-4 right-4 z-50 rounded-md bg-background/80 p-2 hover:bg-background/60 transition"
|
||||||
title="Close full view (ESC)"
|
title="Close full view (ESC)"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<X className="w-6 h-6" />
|
<X className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -25,7 +25,6 @@ type Tag = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
sortIndex: number;
|
sortIndex: number;
|
||||||
parentId: string | null;
|
parentId: string | null;
|
||||||
// these may exist, but we do NOT rely on them:
|
|
||||||
parent?: { id: string; name: string; slug: string; sortIndex: number } | null;
|
parent?: { id: string; name: string; slug: string; sortIndex: number } | null;
|
||||||
children?: { id: string; name: string; slug: string; sortIndex: number; parentId: string | null }[];
|
children?: { id: string; name: string; slug: string; sortIndex: number; parentId: string | null }[];
|
||||||
};
|
};
|
||||||
@ -65,7 +64,6 @@ export default function TagFilterDialog({
|
|||||||
|
|
||||||
const byId = useMemo(() => new Map(tags.map((t) => [t.id, t])), [tags]);
|
const byId = useMemo(() => new Map(tags.map((t) => [t.id, t])), [tags]);
|
||||||
|
|
||||||
// Build children mapping from the flat list: parentId -> Tag[]
|
|
||||||
const childrenByParentId = useMemo(() => {
|
const childrenByParentId = useMemo(() => {
|
||||||
const map = new Map<string, Tag[]>();
|
const map = new Map<string, Tag[]>();
|
||||||
for (const t of tags) {
|
for (const t of tags) {
|
||||||
@ -74,7 +72,6 @@ export default function TagFilterDialog({
|
|||||||
arr.push(t);
|
arr.push(t);
|
||||||
map.set(t.parentId, arr);
|
map.set(t.parentId, arr);
|
||||||
}
|
}
|
||||||
// sort each child list
|
|
||||||
for (const [k, arr] of map) {
|
for (const [k, arr] of map) {
|
||||||
map.set(k, arr.slice().sort(sortTags));
|
map.set(k, arr.slice().sort(sortTags));
|
||||||
}
|
}
|
||||||
@ -101,7 +98,6 @@ export default function TagFilterDialog({
|
|||||||
const s = new Set(prev);
|
const s = new Set(prev);
|
||||||
if (next) {
|
if (next) {
|
||||||
s.add(parent.slug);
|
s.add(parent.slug);
|
||||||
// when selecting parent, remove child selections (redundant)
|
|
||||||
for (const c of children) s.delete(c.slug);
|
for (const c of children) s.delete(c.slug);
|
||||||
} else {
|
} else {
|
||||||
s.delete(parent.slug);
|
s.delete(parent.slug);
|
||||||
@ -188,9 +184,6 @@ export default function TagFilterDialog({
|
|||||||
/>
|
/>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="truncate font-medium">{p.name}</div>
|
<div className="truncate font-medium">{p.name}</div>
|
||||||
{/* <div className="text-xs text-muted-foreground">
|
|
||||||
{children.length ? "Parent tag" : "Tag"}
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
@ -231,11 +224,6 @@ export default function TagFilterDialog({
|
|||||||
|
|
||||||
{orphanChildren.length ? (
|
{orphanChildren.length ? (
|
||||||
<div className="rounded-lg border p-4">
|
<div className="rounded-lg border p-4">
|
||||||
{/* <div className="mb-2 font-medium">Other tags</div> */}
|
|
||||||
{/* <div className="mb-3 text-xs text-muted-foreground">
|
|
||||||
These tags are not currently assigned to a visible parent.
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||||
{orphanChildren.map((t) => {
|
{orphanChildren.map((t) => {
|
||||||
const checked = selectedSet.has(t.slug);
|
const checked = selectedSet.has(t.slug);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma/client"
|
import type { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma/client"
|
||||||
|
|
||||||
type CommissionTypeWithItems = CommissionType & {
|
type CommissionTypeWithItems = CommissionType & {
|
||||||
options: (CommissionTypeOption & {
|
options: (CommissionTypeOption & {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import {
|
import type {
|
||||||
CommissionCustomInput,
|
CommissionCustomInput,
|
||||||
CommissionExtra,
|
CommissionExtra,
|
||||||
CommissionOption,
|
CommissionOption,
|
||||||
@ -28,7 +28,7 @@ import Link from "next/link";
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useForm, useWatch } from "react-hook-form";
|
import { useForm, useWatch } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import * as z from "zod/v4";
|
import type * as z from "zod/v4";
|
||||||
import { FileDropzone } from "./FileDropzone";
|
import { FileDropzone } from "./FileDropzone";
|
||||||
|
|
||||||
type CommissionTypeWithRelations = CommissionType & {
|
type CommissionTypeWithRelations = CommissionType & {
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
// import { Fraunces } from "next/font/google";
|
|
||||||
import localFont from 'next/font/local';
|
import localFont from 'next/font/local';
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
// const pacifico = Fraunces({ weight: "700", subsets: ["latin"] });
|
|
||||||
|
|
||||||
const myFont = localFont({
|
const myFont = localFont({
|
||||||
src: './Echotopia-Regular.woff2',
|
src: './Echotopia-Regular.woff2',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
import * as React from "react"
|
import type * as React from "react"
|
||||||
|
|
||||||
export function ThemeProvider({
|
export function ThemeProvider({
|
||||||
children,
|
children,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||||
import { ChevronDownIcon } from "lucide-react"
|
import { ChevronDownIcon } from "lucide-react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@ -63,4 +63,5 @@ function AccordionContent({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export function calculatePrice(source: PriceSource, base: number): number {
|
|||||||
if (source.priceRange) {
|
if (source.priceRange) {
|
||||||
const parts = source.priceRange.split("–").map(Number)
|
const parts = source.priceRange.split("–").map(Number)
|
||||||
const max = Math.max(...parts)
|
const max = Math.max(...parts)
|
||||||
return isNaN(max) ? 0 : max
|
return Number.isNaN(max) ? 0 : max
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -39,8 +39,8 @@ export function calculatePriceRange(
|
|||||||
const min = Number(minStr)
|
const min = Number(minStr)
|
||||||
const max = Number(maxStr)
|
const max = Number(maxStr)
|
||||||
|
|
||||||
if (!isNaN(min)) minExtra += min
|
if (!Number.isNaN(min)) minExtra += min
|
||||||
if (!isNaN(max)) maxExtra += max
|
if (!Number.isNaN(max)) maxExtra += max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user