Attempt 2 of a management ui.
This commit is contained in:
54 files changed
+8804
-267
No files matched your search
@@ -0,0 +1,22 @@
|
||||
"use server";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
import { provisionBusiness } from "@/lib/provisioning";
|
||||
|
||||
export interface AddBusinessState {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export async function addBusinessAction(_prevState: AddBusinessState, formData: FormData): Promise<AddBusinessState> {
|
||||
const slug = String(formData.get("slug") ?? "");
|
||||
const domain = String(formData.get("domain") ?? "");
|
||||
|
||||
let result: { slug: string };
|
||||
try {
|
||||
result = await provisionBusiness({ slug, domain });
|
||||
} catch (error) {
|
||||
return { error: error instanceof Error ? error.message : "Failed to provision this business." };
|
||||
}
|
||||
|
||||
redirect(`/dashboard/${result.slug}?justCreated=1`);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState, useState } from "react";
|
||||
import { suggestSlug } from "@/lib/slug";
|
||||
import { addBusinessAction, type AddBusinessState } from "./actions";
|
||||
|
||||
const initialState: AddBusinessState = { error: null };
|
||||
|
||||
export function AddBusinessForm() {
|
||||
const [state, formAction, isPending] = useActionState(addBusinessAction, initialState);
|
||||
const [slug, setSlug] = useState("");
|
||||
const [slugTouched, setSlugTouched] = useState(false);
|
||||
|
||||
return (
|
||||
<form action={formAction} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-muted">
|
||||
Business name
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
onChange={(e) => {
|
||||
if (!slugTouched) setSlug(suggestSlug(e.target.value));
|
||||
}}
|
||||
className="mt-1 w-full rounded-md border border-line px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="slug" className="block text-sm font-medium text-muted">
|
||||
Slug
|
||||
</label>
|
||||
<input
|
||||
id="slug"
|
||||
name="slug"
|
||||
type="text"
|
||||
required
|
||||
value={slug}
|
||||
onChange={(e) => {
|
||||
setSlugTouched(true);
|
||||
setSlug(e.target.value);
|
||||
}}
|
||||
className="mt-1 w-full rounded-md border border-line px-3 py-2 font-mono text-sm"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
Used for the container name, database name, and stack directory — lowercase letters, numbers, and hyphens
|
||||
only.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="domain" className="block text-sm font-medium text-muted">
|
||||
Domain
|
||||
</label>
|
||||
<input
|
||||
id="domain"
|
||||
name="domain"
|
||||
type="text"
|
||||
required
|
||||
placeholder="business.example.com"
|
||||
className="mt-1 w-full rounded-md border border-line px-3 py-2 text-sm"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
Must already point at this server’s IP before Caddy can issue a certificate for it.
|
||||
</p>
|
||||
</div>
|
||||
{state.error && (
|
||||
<p role="alert" className="text-sm font-medium text-danger">
|
||||
{state.error}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="w-full rounded-md bg-ink py-2 text-sm font-semibold text-white disabled:opacity-60"
|
||||
>
|
||||
{isPending ? "Provisioning…" : "Create Business"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { AddBusinessForm } from "./add-business-form";
|
||||
|
||||
export default function NewBusinessPage() {
|
||||
return (
|
||||
<div className="max-w-lg space-y-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold">Add Business</h1>
|
||||
<p className="text-sm text-muted">
|
||||
Creates the database, generates secrets, writes the compose stack and Caddy site, and starts the
|
||||
container. Takes a few seconds — the button stays disabled until it’s done.
|
||||
</p>
|
||||
</div>
|
||||
<AddBusinessForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user