+
+ );
+}
+
export default function CreateJob() {
const navigate = useNavigate();
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal('');
-
+ const [submitted, setSubmitted] = createSignal(false);
+
const [usage] = createResource(async () => {
const auth = getAuthHeader();
if (!auth.Authorization) return { count: 0 };
- // Fetch recent jobs to count monthly usage
const res = await fetch(`${API}/api/companies/jobs?limit=100`, { headers: auth });
if (!res.ok) return { count: 0 };
const data = await res.json();
@@ -27,6 +44,7 @@ export default function CreateJob() {
});
const isFree = () => (usage()?.count ?? 0) < 1;
+
const [form, setForm] = createSignal({
title: '',
category: '',
@@ -43,28 +61,44 @@ export default function CreateJob() {
return (e: any) => setForm(f => ({ ...f, [key]: e.target.value }));
}
+ // Real-time validation
+ const titleOk = createMemo(() => isValidTitle(form().title, 5, 200));
+ const descOk = createMemo(() => isValidDescription(form().description, 20, 5000));
+ const locationOk = createMemo(() => isValidLocation(form().location, 2, 255));
+ const salaryOk = createMemo(() => isValidSalaryRange(form().salary_min, form().salary_max));
+ const expOk = createMemo(() => {
+ if (!form().experience_years) return true;
+ const n = parseInt(form().experience_years, 10);
+ return !isNaN(n) && n >= 0 && n <= 50;
+ });
+ const skillsOk = createMemo(() => isValidTags(form().skills));
+ const canSubmit = createMemo(() => titleOk() && descOk() && locationOk() && salaryOk() && expOk() && skillsOk());
+
async function handleSubmit(e: Event) {
e.preventDefault();
+ setSubmitted(true);
setError('');
- const f = form();
- if (!f.title || !f.description || !f.location) {
- setError('Title, description, and location are required.');
- return;
- }
+ if (!titleOk()) { setError('Job title must be 5–200 characters.'); return; }
+ if (!descOk()) { setError('Description must be at least 20 characters.'); return; }
+ if (!locationOk()) { setError('Location must be at least 2 characters.'); return; }
+ if (!salaryOk()) { setError('Salary range is invalid — min must be less than max and both must be positive.'); return; }
+ if (!expOk()) { setError('Experience years must be between 0 and 50.'); return; }
+ if (!skillsOk()) { setError('Each skill must be under 40 characters, max 20 skills.'); return; }
+ const f = form();
const body: Record = {
- title: f.title,
- category: f.category || null,
- description: f.description,
- location: f.location,
+ title: f.title.trim(),
+ category: f.category.trim() || null,
+ description: f.description.trim(),
+ location: f.location.trim(),
job_type: f.job_type,
};
- if (f.salary_min) body.salary_min = parseInt(f.salary_min) * 100; // convert ₹ to paise
- if (f.salary_max) body.salary_max = parseInt(f.salary_max) * 100;
+ if (f.salary_min) body.salary_min = parseInt(f.salary_min) * 100; // ₹ → paise
+ if (f.salary_max) body.salary_max = parseInt(f.salary_max) * 100;
if (f.experience_years) body.experience_years = parseInt(f.experience_years);
- if (f.skills) body.skills = f.skills.split(',').map(s => s.trim()).filter(Boolean);
+ if (f.skills) body.skills = f.skills.split(',').map(s => s.trim()).filter(Boolean);
setLoading(true);
try {
@@ -95,8 +129,8 @@ export default function CreateJob() {
{isFree() ? '✨ Monthly Free Job Posting' : '🪙 Paid Job Posting'}
- {isFree()
- ? "This is your 1st job this month—it's completely free!"
+ {isFree()
+ ? "This is your 1st job this month — it's completely free!"
: "Your monthly free quota is exhausted. Posting this job will cost 100 Tracecoins."}
@@ -107,39 +141,72 @@ export default function CreateJob() {
-