The form posts to admin's signup and the slug preview goes: there is no instance at this point, and previewing one promises something the submission does not create. Creating the instance is now a step in the portal, which the page's What happens next panel spells out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
/*
|
|
* The marketing site is a static bundle. Both forms post to sitesvc, which owns
|
|
* the contact mailer and the signup flow; the control plane is not involved.
|
|
*
|
|
* The base URL is baked in at build time. Contact falls back to composing an
|
|
* email when sitesvc is not configured, so it never silently swallows what
|
|
* someone typed. Signup has no fallback: an account cannot be created over
|
|
* mailto, so the form says so rather than pretending.
|
|
*/
|
|
|
|
const SITE_API = (process.env.NEXT_PUBLIC_SITE_API ?? "").replace(/\/$/, "");
|
|
const ADMIN_API = (process.env.NEXT_PUBLIC_ADMIN_API_URL ?? "").replace(/\/$/, "");
|
|
const FALLBACK_ADDRESS = process.env.NEXT_PUBLIC_CONTACT_EMAIL ?? "support@hostxtra.co.uk";
|
|
|
|
export type SubmitState = "idle" | "sending" | "sent" | "error";
|
|
|
|
export type SubmitResult = {
|
|
state: SubmitState;
|
|
/** Message to show when the submission was refused. */
|
|
message?: string;
|
|
/** Per-field messages, keyed by field name. */
|
|
fields?: Record<string, string>;
|
|
};
|
|
|
|
type FieldProblem = { field: string; message: string };
|
|
|
|
async function post(url: string, payload: unknown): Promise<SubmitResult> {
|
|
try {
|
|
const res = await fetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (res.ok) return { state: "sent" };
|
|
|
|
const data = await res.json().catch(() => null);
|
|
const problems: FieldProblem[] = data?.fields ?? [];
|
|
|
|
return {
|
|
state: "error",
|
|
message: data?.error ?? "That did not go through. Try again in a moment.",
|
|
fields: Object.fromEntries(problems.map((p) => [p.field, p.message])),
|
|
};
|
|
} catch {
|
|
return { state: "error", message: "We could not reach the server. Check your connection and try again." };
|
|
}
|
|
}
|
|
|
|
export async function submitContact(fields: { name: string; email: string; servers: string; topic: string; message: string; website: string }): Promise<SubmitResult> {
|
|
if (!SITE_API) {
|
|
return {
|
|
state: "error",
|
|
message: `The contact form is not connected yet. Email ${FALLBACK_ADDRESS} directly.`,
|
|
};
|
|
}
|
|
return post(`${SITE_API}/api/contact`, fields);
|
|
}
|
|
|
|
/*
|
|
* Account signup posts to the admin service, not sitesvc. The two form targets
|
|
* are deliberately separate variables rather than one base URL: contact and
|
|
* signup are owned by different services, and an implied shared host is how they
|
|
* silently end up pointing at the wrong one.
|
|
*/
|
|
export async function submitAccountSignup(fields: {
|
|
name: string;
|
|
email: string;
|
|
password: string;
|
|
website: string;
|
|
}): Promise<SubmitResult> {
|
|
if (!ADMIN_API) {
|
|
return {
|
|
state: "error",
|
|
message: `Signup is not connected yet. Email ${FALLBACK_ADDRESS} and we will set you up.`,
|
|
};
|
|
}
|
|
return post(`${ADMIN_API}/auth/signup`, fields);
|
|
}
|