Files
2026-03-08 12:35:05 -05:00

292 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useRef, type FormEvent, type ChangeEvent } from "react";
import { Link, useLocale, useTranslation } from "@/i18n/navigation";
const positionOptions = [
{ value: "content", labelZh: "内容运营", labelEn: "Content Operations" },
{ value: "dev", labelZh: "前端/全栈开发", labelEn: "Frontend / Full-stack Dev" },
{ value: "design", labelZh: "UI/UX 设计", labelEn: "UI/UX Design" },
{ value: "marketing", labelZh: "市场推广", labelEn: "Marketing" },
{ value: "other", labelZh: "其他", labelEn: "Other" },
];
interface FormData {
position: string;
name: string;
email: string;
phone: string;
intro: string;
}
function Field({
label,
children,
}: {
label: string;
children: React.ReactNode;
}) {
return (
<div className="relative flex items-center gap-3 py-3.5 sm:flex-col sm:items-stretch sm:gap-0 sm:py-4 border-b border-slate-100 sm:border-b-0">
<label className="w-[90px] shrink-0 text-[15px] font-bold text-slate-700 sm:mb-2 sm:w-auto sm:text-[17px]">
{label}
</label>
<div className="relative min-w-0 flex-1">{children}</div>
</div>
);
}
function ChevronDown() {
return (
<svg
className="pointer-events-none absolute top-1/2 right-2 h-4 w-4 -translate-y-1/2 text-slate-400 sm:right-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
);
}
export default function JobsContent() {
const { t } = useTranslation("jobs");
const locale = useLocale();
const [form, setForm] = useState<FormData>({
position: "",
name: "",
email: "",
phone: "",
intro: "",
});
const [resume, setResume] = useState<File | null>(null);
const [resumeError, setResumeError] = useState<string | null>(null);
const fileRef = useRef<HTMLInputElement>(null);
const set = (key: keyof FormData, value: string) =>
setForm((prev) => ({ ...prev, [key]: value }));
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
setResumeError(null);
if (!resume) {
setResumeError(locale === "zh" ? "请上传简历PDF 或 Word" : "Please upload your resume (PDF or Word)");
return;
}
// 暂不提交,无业务逻辑
if (locale === "zh") {
alert("表单已填写完成,提交功能即将上线。");
} else {
alert("Form completed. Submit feature coming soon.");
}
};
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const ext = file.name.split(".").pop()?.toLowerCase();
const allowed = ["pdf", "doc", "docx"];
if (!ext || !allowed.includes(ext)) {
setResumeError(locale === "zh" ? "请上传 PDF、DOC 或 DOCX 格式" : "Please upload PDF, DOC or DOCX");
return;
}
if (file.size > 10 * 1024 * 1024) {
setResumeError(locale === "zh" ? "文件大小不能超过 10MB" : "File size must be under 10MB");
return;
}
setResumeError(null);
setResume(file);
};
const removeResume = () => {
setResume(null);
setResumeError(null);
if (fileRef.current) fileRef.current.value = "";
};
const getLabel = (opt: (typeof positionOptions)[0]) =>
locale === "zh" ? opt.labelZh : opt.labelEn;
return (
<div className="min-h-screen bg-[#f0f4f8]">
<div className="mx-auto max-w-2xl px-0 py-0 sm:px-6 sm:py-10 lg:py-14">
{/* Banner */}
<div className="overflow-hidden bg-white shadow-sm sm:rounded-t-2xl">
<div className="relative h-40 overflow-hidden bg-gradient-to-br from-amber-400 via-orange-500 to-rose-500 sm:h-52">
<div className="absolute inset-0 flex flex-col items-center justify-center text-white">
<div className="text-6xl sm:text-7xl">💼</div>
<div className="mt-2 text-sm font-medium opacity-90">
{locale === "zh" ? "加入我们" : "Join Us"}
</div>
</div>
<div className="absolute -top-10 -left-10 h-40 w-40 rounded-full bg-white/10 blur-2xl" />
<div className="absolute -right-10 -bottom-10 h-48 w-48 rounded-full bg-white/10 blur-2xl" />
</div>
<div className="px-5 py-5 text-center sm:px-8 sm:py-7">
<h1 className="text-xl font-bold text-slate-800 sm:text-2xl">
{t("title")}
</h1>
<p className="mt-3 text-sm leading-relaxed text-slate-500 sm:text-base">
{t("subtitle")}
</p>
</div>
</div>
{/* Form */}
<form
onSubmit={handleSubmit}
className="mt-0 bg-white px-5 pb-8 pt-2 shadow-sm sm:mt-5 sm:rounded-2xl sm:px-8 sm:py-8 sm:shadow-md"
>
<div className="grid gap-0 sm:grid-cols-2 sm:gap-6">
<Field label={t("position")}>
<select
value={form.position}
onChange={(e) => set("position", e.target.value)}
required
className={`form-input appearance-none ${
form.position ? "text-slate-800" : "text-slate-400"
}`}
>
<option value="" disabled>
{locale === "zh" ? "请选择职位" : "Select position"}
</option>
{positionOptions.map((opt) => (
<option key={opt.value} value={opt.value}>
{getLabel(opt)}
</option>
))}
</select>
<ChevronDown />
</Field>
<Field label={t("name")}>
<input
type="text"
maxLength={80}
placeholder={locale === "zh" ? "您的姓名" : "Your name"}
value={form.name}
onChange={(e) => set("name", e.target.value)}
required
className="form-input"
/>
</Field>
</div>
<div className="grid gap-0 sm:grid-cols-2 sm:gap-6">
<Field label={t("email")}>
<input
type="email"
placeholder="your@email.com"
value={form.email}
onChange={(e) => set("email", e.target.value)}
required
className="form-input"
/>
</Field>
<Field label={t("phone")}>
<input
type="tel"
placeholder={locale === "zh" ? "手机号(选填)" : "Phone (optional)"}
value={form.phone}
onChange={(e) => set("phone", e.target.value)}
className="form-input"
/>
</Field>
</div>
<div className="border-b border-slate-100 py-4 sm:border-b-0 sm:py-5">
<label className="mb-2 block text-base font-bold text-slate-700 sm:text-[17px]">
{t("intro")}
</label>
<div className="relative">
<textarea
maxLength={800}
rows={5}
placeholder={t("introPlaceholder")}
value={form.intro}
onChange={(e) => set("intro", e.target.value)}
className="form-input min-h-[120px] resize-none sm:min-h-[140px]"
/>
<span className="absolute right-3 bottom-3 text-xs text-slate-300">
{form.intro.length}/800
</span>
</div>
</div>
{/* Resume upload (required) */}
<div className="py-4 sm:py-5">
<label className="mb-1 block text-[15px] font-bold text-slate-700 sm:text-[17px]">
📄 {t("resume")} <span className="ml-1 text-red-500">*</span>
</label>
<p className="mb-3 text-xs text-slate-500">
{t("resumeHint")}
</p>
<input
ref={fileRef}
type="file"
accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
className="hidden"
onChange={handleFileChange}
/>
{resumeError && (
<p className="mb-2 text-sm text-red-500">{resumeError}</p>
)}
{resume ? (
<div className="flex items-center gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3">
<span className="text-lg">📎</span>
<span className="flex-1 truncate text-sm font-medium text-slate-700">
{resume.name}
</span>
<span className="text-xs text-slate-400">
({(resume.size / 1024).toFixed(1)} KB)
</span>
<button
type="button"
onClick={removeResume}
className="rounded p-1 text-slate-400 transition-colors hover:bg-slate-200 hover:text-slate-600"
aria-label="Remove"
>
</button>
</div>
) : (
<button
type="button"
onClick={() => fileRef.current?.click()}
className="flex w-full flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed border-slate-200 py-8 text-slate-400 transition-colors hover:border-amber-300 hover:text-amber-600"
>
<svg className="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<span className="text-sm">{t("resumeUpload")}</span>
</button>
)}
</div>
<div className="mt-8 sm:mt-10">
<button
type="submit"
className="w-full rounded-full bg-amber-500 px-8 py-4 text-base font-semibold text-white transition-colors hover:bg-amber-600"
>
{t("submit")}
</button>
</div>
</form>
<div className="mt-6 text-center">
<Link
href="/"
className="text-sm font-medium text-slate-500 transition-colors hover:text-slate-700"
>
{locale === "zh" ? "返回首页" : "Back to Home"}
</Link>
</div>
</div>
</div>
);
}