fix: garden ui polish - form styles, back nav, species dropdown, upload limit
This commit is contained in:
@@ -11,5 +11,19 @@ services:
|
||||
volumes:
|
||||
- famapp-db-data:/var/lib/postgresql/data
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
command: server /data --console-address ":9001"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MINIO_ROOT_USER: famapp
|
||||
MINIO_ROOT_PASSWORD: changeme
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- famapp-minio-data:/data
|
||||
|
||||
volumes:
|
||||
famapp-db-data:
|
||||
famapp-minio-data:
|
||||
|
||||
@@ -4,8 +4,13 @@ import { getCurrentSession } from "@/lib/session";
|
||||
import { ensureBucket, minioClient, MINIO_BUCKET } from "@/lib/minio";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const maxDuration = 60;
|
||||
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
export const config = {
|
||||
api: { bodyParser: { sizeLimit: "100mb" } },
|
||||
};
|
||||
|
||||
const MAX_FILE_SIZE = 100 * 1024 * 1024;
|
||||
|
||||
export async function POST(request: Request) {
|
||||
let session: Awaited<ReturnType<typeof getCurrentSession>>;
|
||||
@@ -49,8 +54,15 @@ export async function POST(request: Request) {
|
||||
"Content-Type": file.type,
|
||||
});
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.error("Upload failed", err);
|
||||
return NextResponse.json({ error: "Upload service unavailable" }, { status: 503 });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Upload service unavailable",
|
||||
detail: process.env.NODE_ENV !== "production" ? msg : undefined,
|
||||
},
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ url: `/api/uploads/${key}` });
|
||||
|
||||
@@ -14,7 +14,23 @@ export default function NewContainerPage() {
|
||||
|
||||
return (
|
||||
<div className="page-content max-w-lg">
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<a href="/garden" className="btn btn-ghost btn-icon" aria-label="Back to garden">
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
</a>
|
||||
<h1 className="page-title">New container</h1>
|
||||
</div>
|
||||
<form action={handleCreate} className="flex flex-col gap-4 mt-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="name" className="text-sm font-medium">
|
||||
|
||||
@@ -11,7 +11,23 @@ export default async function NewPlantPage({
|
||||
|
||||
return (
|
||||
<div className="page-content max-w-xl">
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<a href="/garden" className="btn btn-ghost btn-icon" aria-label="Back to garden">
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
</a>
|
||||
<h1 className="page-title">Add Plant</h1>
|
||||
</div>
|
||||
<PlantForm containers={containers} defaultContainerId={params.containerId ?? null} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1295,3 +1295,54 @@
|
||||
color: var(--ink-mute);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ── Form controls ──────────────────────────────────────────────── */
|
||||
.input,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 7px 10px;
|
||||
border-radius: var(--r-sm);
|
||||
border: 1px solid var(--hair-2);
|
||||
background: var(--card);
|
||||
color: var(--ink);
|
||||
font: inherit;
|
||||
font-size: 13.5px;
|
||||
line-height: 1.4;
|
||||
box-shadow: inset 0 1px 2px rgba(31, 27, 22, 0.05);
|
||||
transition:
|
||||
border-color 0.12s,
|
||||
box-shadow 0.12s;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
outline: none;
|
||||
}
|
||||
.input:focus,
|
||||
select:focus {
|
||||
border-color: var(--ink-soft);
|
||||
box-shadow:
|
||||
inset 0 1px 2px rgba(31, 27, 22, 0.05),
|
||||
0 0 0 2px rgba(31, 27, 22, 0.08);
|
||||
}
|
||||
.input::placeholder {
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
select {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238a7e6e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
padding-right: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dark .input,
|
||||
.dark select {
|
||||
background: var(--paper-2);
|
||||
border-color: var(--hair-2);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.dark .input:focus,
|
||||
.dark select:focus {
|
||||
border-color: var(--ink-soft);
|
||||
box-shadow:
|
||||
inset 0 1px 2px rgba(0, 0, 0, 0.12),
|
||||
0 0 0 2px rgba(244, 239, 229, 0.08);
|
||||
}
|
||||
|
||||
@@ -83,13 +83,13 @@ export function SpeciesSearch({ onSelect, initialValue = "" }: Props) {
|
||||
</div>
|
||||
|
||||
{isOpen && results.length > 0 && (
|
||||
<ul className="absolute z-50 w-full mt-1 bg-[var(--surface-1)] border border-[var(--ink-faint)] rounded-md shadow-lg max-h-52 overflow-auto">
|
||||
<ul className="absolute z-50 w-full mt-1 bg-[var(--card)] border border-[var(--hair-2)] rounded-md shadow-lg max-h-52 overflow-auto">
|
||||
{results.map((s) => (
|
||||
<li key={s.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSelect(s)}
|
||||
className="w-full text-left px-3 py-2 hover:bg-[var(--surface-2)] text-sm"
|
||||
className="w-full text-left px-3 py-2 hover:bg-[var(--paper-2)] text-sm text-[var(--ink)]"
|
||||
>
|
||||
<span className="font-medium">{s.common_name}</span>
|
||||
{s.scientific_name && (
|
||||
|
||||
Reference in New Issue
Block a user