项目文件夹

文件
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

167 行
5.8 KiB
TypeScript

"use client";
import { type FormEvent, useCallback, useState } from "react";
interface ApplyFormProps {
roleTitle: string;
}
export const ApplyForm = ({ roleTitle }: ApplyFormProps) => {
const [fallbackVisible, setFallbackVisible] = useState(false);
const [mailtoHref, setMailtoHref] = useState("");
const [composedBody, setComposedBody] = useState("");
const [copyStatus, setCopyStatus] = useState<"idle" | "success" | "error">(
"idle",
);
const handleSubmit = useCallback(
(event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = event.currentTarget;
const formData = new FormData(form);
const fullName = (formData.get("full_name") as string)?.trim() ?? "";
const urlsRaw = (formData.get("urls") as string)?.trim() ?? "";
const notes = (formData.get("notes") as string)?.trim() ?? "";
const urlLines = urlsRaw
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean)
.map((line) => `- ${line}`);
const bodyLines = [
"Hello,",
"",
`I am applying for the ${roleTitle} role.`,
"",
"Here are a few interesting links to showcase my profile:",
urlLines.length ? urlLines.join("\n") : "-",
];
if (notes) {
bodyLines.push("", notes);
}
bodyLines.push("", "Best,", fullName || "");
const body = bodyLines.join("\n");
const mailto = `mailto:careers@assistant-ui.com?subject=${encodeURIComponent(
`Application: ${roleTitle}`,
)}&body=${encodeURIComponent(body)}`;
// Attempt to open the user's default mail client.
// If it doesn't open (no handler configured), reveal fallbacks.
setComposedBody(body);
setMailtoHref(mailto);
setFallbackVisible(true);
try {
window.location.href = mailto;
} catch {
// No-op: rely on the fallback UI
}
},
[roleTitle],
);
return (
<form onSubmit={handleSubmit} className="grid gap-5">
<label className="grid gap-2 text-sm">
<span className="text-foreground font-medium">Full Name</span>
<input
type="text"
name="full_name"
required
autoComplete="name"
className="border-border bg-background focus:border-primary rounded-lg border px-3 py-2 text-base shadow-sm ring-0 transition outline-none"
placeholder="Ada Lovelace"
/>
</label>
<label className="grid gap-2 text-sm">
<span className="text-foreground font-medium">
URLs that best describe you
</span>
<textarea
name="urls"
required
rows={3}
className="border-border bg-background focus:border-primary rounded-lg border px-3 py-2 text-base shadow-sm ring-0 transition outline-none"
placeholder="Portfolio, GitHub, LinkedIn, blog – one per line"
></textarea>
</label>
<label className="grid gap-2 text-sm">
<span className="text-foreground font-medium">
Anything else?{" "}
<span className="text-muted-foreground">(optional)</span>
</span>
<textarea
name="notes"
rows={4}
className="border-border bg-background focus:border-primary rounded-lg border px-3 py-2 text-base shadow-sm ring-0 transition outline-none"
placeholder="Tell us about goals, timelines, or anything you'd like us to know."
></textarea>
</label>
<button
type="submit"
className="border-border text-foreground hover:border-primary hover:bg-primary/5 hover:text-primary inline-flex w-fit items-center justify-center rounded-full border px-5 py-2 text-sm font-medium transition"
>
Apply now
</button>
{fallbackVisible ? (
<div className="border-border/70 bg-background/50 mt-2 grid gap-2 rounded-lg border border-dashed p-3">
<p className="text-muted-foreground text-xs">
If your email client didn&apos;t open, use the options below.
</p>
<div className="flex flex-wrap items-center gap-2">
<a
href={mailtoHref}
className="border-border text-foreground hover:border-primary hover:bg-primary/5 hover:text-primary inline-flex items-center justify-center rounded-full border px-4 py-1.5 text-xs font-medium transition"
>
Open email client
</a>
<button
type="button"
className="border-border text-foreground hover:border-primary hover:bg-primary/5 hover:text-primary inline-flex items-center justify-center rounded-full border px-4 py-1.5 text-xs font-medium transition"
onClick={async () => {
try {
const text = `To: careers@assistant-ui.com
Subject: Application: ${roleTitle}
${composedBody}`;
await navigator.clipboard.writeText(text);
setCopyStatus("success");
setTimeout(() => setCopyStatus("idle"), 2000);
} catch {
setCopyStatus("error");
setTimeout(() => setCopyStatus("idle"), 2000);
}
}}
>
Copy email text
</button>
<span className="text-muted-foreground text-xs">
{copyStatus === "success"
? "Copied!"
: copyStatus === "error"
? "Copy failed"
: ""}
</span>
</div>
<p className="text-muted-foreground text-[11px]">
Or email{" "}
<span className="text-foreground font-medium">
careers@assistant-ui.com
</span>{" "}
with the subject &quot;Application: {roleTitle}&quot;.
</p>
</div>
) : null}
</form>
);
};