update position page design

This commit is contained in:
2026-08-13 21:41:14 +05:30
parent 6340962907
commit 1c653264b5
124 changed files with 5746 additions and 596 deletions

View File

@@ -5,6 +5,7 @@ import { useCreateJobPosting, useUpdateJobPosting, useGenerateJobDescription, us
import { toast } from 'react-hot-toast';
import { ROLE_CATEGORIES } from '@/lib/roleCategories';
import { CERT_OPTIONS, CRITERIA_LABELS, ENGLISH_LEVELS, defaultPosition } from '@/lib/positionModel';
import { SkillRequirementsField } from '@/components/krow/SkillRequirementsField';
/**
* Create a Position.
@@ -133,8 +134,8 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
: 'bg-white border border-[#E5E7EB] rounded-2xl p-6 md:p-8 space-y-6'}>
{/* Section Header */}
<div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-lg bg-[#EFF6FF] flex items-center justify-center">
<Sparkles className="w-4 h-4 text-[#0A39DF]" />
<div className="w-7 h-7 rounded-lg bg-[#EEF3FE] flex items-center justify-center">
<Sparkles className="w-4 h-4 text-[#0838E0]" />
</div>
<h2 className="text-[16px] font-semibold text-[#111827]">Create Job Posting</h2>
</div>
@@ -143,19 +144,19 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-[13px] font-medium text-[#374151]">Role Category *</label>
<select value={form.role_category} onChange={(e) => update('role_category', e.target.value)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none">
<select value={form.role_category} onChange={(e) => update('role_category', e.target.value)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none">
{allCategories.map(r => <option key={r} value={r}>{r}</option>)}
</select>
</div>
<div>
<label className="text-[13px] font-medium text-[#374151]">Job Title *</label>
<input value={form.title} onChange={(e) => update('title', e.target.value)} placeholder="e.g. Experienced Server" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input value={form.title} onChange={(e) => update('title', e.target.value)} placeholder="e.g. Experienced Server" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
</div>
</div>
{/* Add new category */}
<div className="flex gap-2">
<input value={newCategory} onChange={(e) => setNewCategory(e.target.value)} placeholder="Create a new role category (saved for reuse)..." className="flex-1 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input value={newCategory} onChange={(e) => setNewCategory(e.target.value)} placeholder="Create a new role category (saved for reuse)..." className="flex-1 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
<button
onClick={async () => {
if (newCategory.trim()) {
@@ -167,7 +168,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
}
}}
disabled={createRoleCategory.isPending || !newCategory.trim()}
className="px-4 bg-[#0A39DF] text-white text-[13px] font-medium rounded-lg hover:bg-[#082EB4] disabled:opacity-50 whitespace-nowrap"
className="px-4 bg-[#0838E0] text-white text-[13px] font-medium rounded-lg hover:bg-[#062BAF] disabled:opacity-50 whitespace-nowrap"
>
+ Add
</button>
@@ -176,22 +177,22 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
{/* Requirements */}
<div>
<label className="text-[13px] font-medium text-[#374151]">Custom Requirements (AI will use this)</label>
<textarea value={form.custom_requirements} onChange={(e) => update('custom_requirements', e.target.value)} placeholder="e.g. Must have fine dining experience..." className="w-full mt-1.5 min-h-[70px] p-3 text-[13px] rounded-lg border border-[#E5E7EB] focus:border-[#0A39DF] focus:outline-none resize-none" />
<textarea value={form.custom_requirements} onChange={(e) => update('custom_requirements', e.target.value)} placeholder="e.g. Must have fine dining experience..." className="w-full mt-1.5 min-h-[70px] p-3 text-[13px] rounded-lg border border-[#E5E7EB] focus:border-[#0838E0] focus:outline-none resize-none" />
</div>
{/* Physical / Leadership / Attendance Expectations */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="text-[13px] font-medium text-[#374151]">Physical Requirements</label>
<input value={form.physical_requirements} onChange={(e) => update('physical_requirements', e.target.value)} placeholder="e.g. Lift 50 lbs, stand 8 hrs" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input value={form.physical_requirements} onChange={(e) => update('physical_requirements', e.target.value)} placeholder="e.g. Lift 50 lbs, stand 8 hrs" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
</div>
<div>
<label className="text-[13px] font-medium text-[#374151]">Leadership Expectations</label>
<input value={form.leadership_expectations} onChange={(e) => update('leadership_expectations', e.target.value)} placeholder="e.g. Lead a team of 5" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input value={form.leadership_expectations} onChange={(e) => update('leadership_expectations', e.target.value)} placeholder="e.g. Lead a team of 5" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
</div>
<div>
<label className="text-[13px] font-medium text-[#374151]">Attendance Expectations</label>
<input value={form.attendance_expectations} onChange={(e) => update('attendance_expectations', e.target.value)} placeholder="e.g. On-time, no weekends off" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input value={form.attendance_expectations} onChange={(e) => update('attendance_expectations', e.target.value)} placeholder="e.g. On-time, no weekends off" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
</div>
</div>
@@ -199,17 +200,17 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="text-[13px] font-medium text-[#374151]">Min. Experience (yrs)</label>
<input type="number" min="0" value={form.min_experience_years} onChange={(e) => update('min_experience_years', parseInt(e.target.value) || 0)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input type="number" min="0" value={form.min_experience_years} onChange={(e) => update('min_experience_years', parseInt(e.target.value) || 0)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
</div>
<div>
<label className="text-[13px] font-medium text-[#374151]">English Level</label>
<select value={form.english_required} onChange={(e) => update('english_required', e.target.value)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none">
<select value={form.english_required} onChange={(e) => update('english_required', e.target.value)} className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-[13px] focus:border-[#0838E0] focus:outline-none">
{ENGLISH_LEVELS.map(l => <option key={l.value} value={l.value}>{l.label}</option>)}
</select>
</div>
<div>
<label className="text-[13px] font-medium text-[#374151]">Location</label>
<input value={form.location} onChange={(e) => update('location', e.target.value)} placeholder="Bay Area, LA..." className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input value={form.location} onChange={(e) => update('location', e.target.value)} placeholder="Bay Area, LA..." className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
</div>
</div>
@@ -217,11 +218,11 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-[13px] font-medium text-[#374151]">Pay Min ($/hr)</label>
<input type="number" min="0" value={form.pay_range_min} onChange={(e) => update('pay_range_min', e.target.value)} placeholder="e.g. 18" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input type="number" min="0" value={form.pay_range_min} onChange={(e) => update('pay_range_min', e.target.value)} placeholder="e.g. 18" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
</div>
<div>
<label className="text-[13px] font-medium text-[#374151]">Pay Max ($/hr)</label>
<input type="number" min="0" value={form.pay_range_max} onChange={(e) => update('pay_range_max', e.target.value)} placeholder="e.g. 28" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0A39DF] focus:outline-none" />
<input type="number" min="0" value={form.pay_range_max} onChange={(e) => update('pay_range_max', e.target.value)} placeholder="e.g. 28" className="w-full mt-1.5 h-10 rounded-lg border border-[#E5E7EB] px-3 text-[13px] focus:border-[#0838E0] focus:outline-none" />
</div>
</div>
@@ -235,7 +236,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
type="button"
onClick={() => toggleCert(cert)}
className={`px-3 py-1.5 rounded-full text-[12px] font-medium border transition-all ${
form.certifications_required.includes(cert) ? 'bg-[#0A39DF] border-[#0A39DF] text-white' : 'border-[#E5E7EB] text-[#6B7280] hover:border-[#D1D5DB]'
form.certifications_required.includes(cert) ? 'bg-[#0838E0] border-[#0838E0] text-white' : 'border-[#E5E7EB] text-[#6B7280] hover:border-[#D1D5DB]'
}`}
>
{cert}
@@ -244,11 +245,19 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
</div>
</div>
{/* Required skills — what the matching engine reads. Placed next to the
certifications it sits beside conceptually: both are "what a person
must hold", one asserted on a résumé and one verified in Forge. */}
<SkillRequirementsField
value={form.skill_requirements}
onChange={(next) => update('skill_requirements', next)}
/>
{/* AI Generate Button */}
<button
onClick={handleGenerateDescription}
disabled={generateDesc.isPending || createJob.isPending}
className="w-full bg-[#0A39DF] hover:bg-[#082EB4] disabled:opacity-60 text-white font-medium py-3 rounded-xl text-[14px] flex items-center justify-center gap-2 transition-colors"
className="w-full bg-[#0838E0] hover:bg-[#062BAF] disabled:opacity-60 text-white font-medium py-3 rounded-xl text-[14px] flex items-center justify-center gap-2 transition-colors"
>
{generateDesc.isPending || createJob.isPending ? (
<><Loader2 className="w-4 h-4 animate-spin" /> Generating...</>
@@ -259,7 +268,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
{/* AI Result Preview */}
{aiResult && (
<div className="rounded-xl border border-[#DBEAFE] bg-[#EFF6FF] p-4 space-y-3">
<div className="rounded-xl border border-[#DBEAFE] bg-[#EEF3FE] p-4 space-y-3">
<div className="flex items-center gap-1.5 text-[#1E40AF] text-[13px] font-medium">
<Sparkles className="w-3.5 h-3.5" /> AI-Generated Description
</div>
@@ -292,7 +301,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
<label className="text-[12px] text-[#374151]">{CRITERIA_LABELS[key]}</label>
<span className={`text-[12px] font-medium ${totalWeight === 100 ? 'text-[#059669]' : 'text-[#F59E0B]'}`}>{val}%</span>
</div>
<input type="range" min="0" max="100" value={val} onChange={(e) => updateWeight(key, e.target.value)} className="w-full accent-[#0A39DF]" />
<input type="range" min="0" max="100" value={val} onChange={(e) => updateWeight(key, e.target.value)} className="w-full accent-[#0838E0]" />
</div>
))}
<div className="md:col-span-2 text-[12px] text-[#6B7280]">
@@ -308,7 +317,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false,
<button onClick={() => handleSave('draft')} disabled={createJob.isPending || updateJob.isPending} className="border border-[#E5E7EB] hover:bg-[#F9FAFB] text-[#374151] font-medium py-3 rounded-xl text-[14px] transition-colors disabled:opacity-50">
Save as Draft
</button>
<button onClick={() => handleSave('active')} disabled={createJob.isPending || updateJob.isPending} className="bg-[#0A39DF] hover:bg-[#082EB4] text-white font-medium py-3 rounded-xl text-[14px] transition-colors disabled:opacity-50">
<button onClick={() => handleSave('active')} disabled={createJob.isPending || updateJob.isPending} className="bg-[#0838E0] hover:bg-[#062BAF] text-white font-medium py-3 rounded-xl text-[14px] transition-colors disabled:opacity-50">
Publish Job Posting
</button>
</div>