156 lines
4.9 KiB
Svelte
156 lines
4.9 KiB
Svelte
<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>
|