Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
<script lang="ts">
|
||||
import { i18n } from "$lib/i18n";
|
||||
import { page } from "$app/state";
|
||||
import { failureMessage } from "$lib/api";
|
||||
import { DEMO_BOOKING_URL } from "$lib/site";
|
||||
import { submitSalesContact } from "$lib/sales-contact";
|
||||
import { notifySuccess, notifyApiError } from "$lib/notify";
|
||||
import PageShell from "$lib/components/PageShell.svelte";
|
||||
import Alert from "$lib/components/Alert.svelte";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Textarea
|
||||
} from "$lib/components/ui";
|
||||
|
||||
let name = $state("");
|
||||
let email = $state("");
|
||||
let companyName = $state("");
|
||||
let phone = $state("");
|
||||
let message = $state("");
|
||||
let estimatedSkus = $state("");
|
||||
let submitting = $state(false);
|
||||
let error = $state("");
|
||||
let done = $state(false);
|
||||
|
||||
const source = $derived(page.url.searchParams.get("source")?.trim() || "pricing");
|
||||
const canSubmit = $derived(
|
||||
name.trim().length > 0 &&
|
||||
email.trim().length > 3 &&
|
||||
message.trim().length > 0 &&
|
||||
!submitting
|
||||
);
|
||||
|
||||
async function handleSubmit(event: Event) {
|
||||
event.preventDefault();
|
||||
if (!canSubmit) return;
|
||||
submitting = true;
|
||||
error = "";
|
||||
try {
|
||||
const skusRaw = estimatedSkus.trim();
|
||||
const skus = skusRaw ? Number.parseInt(skusRaw, 10) : undefined;
|
||||
await submitSalesContact({
|
||||
name: name.trim(),
|
||||
email: email.trim(),
|
||||
company_name: companyName.trim() || undefined,
|
||||
phone: phone.trim() || undefined,
|
||||
message: message.trim(),
|
||||
estimated_skus: skus != null && Number.isFinite(skus) ? skus : undefined,
|
||||
source
|
||||
});
|
||||
done = true;
|
||||
notifySuccess(i18n.t("sales.contact.toast.success"));
|
||||
} catch (err) {
|
||||
error = failureMessage(err, i18n.t("sales.contact.error"));
|
||||
notifyApiError(err, i18n.t("sales.contact.error"));
|
||||
} finally {
|
||||
submitting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<PageShell title={i18n.t("sales.contact.title")} description={i18n.t("sales.contact.lead")}>
|
||||
{#if done}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{i18n.t("sales.contact.thanksTitle")}</CardTitle>
|
||||
<CardDescription>{i18n.t("sales.contact.thanksBody")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-wrap gap-3">
|
||||
<a
|
||||
href={DEMO_BOOKING_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex h-10 items-center justify-center rounded-md border border-border px-4 text-sm font-medium text-text transition-colors hover:bg-surface-muted"
|
||||
>
|
||||
{i18n.t("sales.contact.bookDemo")}
|
||||
</a>
|
||||
<a
|
||||
href="/pricing"
|
||||
class="inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground"
|
||||
>
|
||||
{i18n.t("sales.contact.backPricing")}
|
||||
</a>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{:else}
|
||||
<Card class="max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle>{i18n.t("sales.contact.formTitle")}</CardTitle>
|
||||
<CardDescription>{i18n.t("sales.contact.formLead")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if error}
|
||||
<Alert message={error} />
|
||||
{/if}
|
||||
<form class="space-y-4" onsubmit={handleSubmit}>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<Label for="sales-name">{i18n.t("sales.contact.name")}</Label>
|
||||
<Input id="sales-name" bind:value={name} required autocomplete="name" maxlength={200} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="sales-email">{i18n.t("sales.contact.email")}</Label>
|
||||
<Input
|
||||
id="sales-email"
|
||||
type="email"
|
||||
bind:value={email}
|
||||
required
|
||||
autocomplete="email"
|
||||
maxlength={320}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<Label for="sales-company">{i18n.t("sales.contact.company")}</Label>
|
||||
<Input id="sales-company" bind:value={companyName} autocomplete="organization" maxlength={200} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="sales-phone">{i18n.t("sales.contact.phone")}</Label>
|
||||
<Input id="sales-phone" bind:value={phone} autocomplete="tel" maxlength={40} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="sales-skus">{i18n.t("sales.contact.estimatedSkus")}</Label>
|
||||
<Input id="sales-skus" inputmode="numeric" bind:value={estimatedSkus} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="sales-message">{i18n.t("sales.contact.message")}</Label>
|
||||
<Textarea id="sales-message" bind:value={message} required rows={6} maxlength={10000} />
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<Button type="submit" disabled={!canSubmit}>
|
||||
{submitting ? i18n.t("sales.contact.submitting") : i18n.t("sales.contact.submit")}
|
||||
</Button>
|
||||
<a
|
||||
href={DEMO_BOOKING_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-sm text-text-muted underline-offset-4 hover:text-link hover:underline"
|
||||
>
|
||||
{i18n.t("sales.contact.orBookDemo")}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/if}
|
||||
</PageShell>
|
||||
Reference in New Issue
Block a user