From d0f424e3db4edd1572cfcc9533c09bba60be3b7d Mon Sep 17 00:00:00 2001 From: Joachim LK Date: Wed, 17 Jun 2026 21:07:07 +0200 Subject: [PATCH 01/12] Add Playwright script for sign-in page testing - Create a new script `test-step3.js` to automate the testing of the sign-in page. - Launch Chromium browser with specific arguments and set viewport size. - Navigate to the sign-in page and take a screenshot. - Log input types and placeholders for all input fields on the page. - Handle errors gracefully by logging them to the console. --- app/pages/dashboard/jobs/new.vue | 1116 ++++++++++++++---------------- test-step3.js | 26 + 2 files changed, 535 insertions(+), 607 deletions(-) create mode 100644 test-step3.js diff --git a/app/pages/dashboard/jobs/new.vue b/app/pages/dashboard/jobs/new.vue index 9fdb5312..f6c8188c 100644 --- a/app/pages/dashboard/jobs/new.vue +++ b/app/pages/dashboard/jobs/new.vue @@ -7,6 +7,8 @@ import { Trash2, ChevronUp, ChevronDown, + ChevronRight, + ArrowRight, GripVertical, Link2, ClipboardCopy, @@ -110,9 +112,11 @@ type ScoringCriterionDraft = { } const scoringCriteria = ref([]) const scoringMode = ref<'none' | 'premade' | 'ai' | 'custom'>('none') +const aiScoringChosen = ref(false) const selectedTemplate = ref<'standard' | 'technical' | 'non_technical'>('standard') const isGeneratingCriteria = ref(false) const showCustomForm = ref(false) +const showAdvanced = ref(false) const editingCriterion = ref(null) const autoScoreOnApply = ref(false) @@ -143,35 +147,52 @@ const categoryColorClasses: Record = { custom: 'bg-surface-50 text-surface-700 ring-surface-200 dark:bg-surface-800/50 dark:text-surface-300 dark:ring-surface-700', } -async function loadPremadeCriteria(template: 'standard' | 'technical' | 'non_technical') { - try { - // Use local pre-made templates (no API call needed) - const templates: Record = { - standard: [ - { key: 'technical_skills', name: 'Technical Skills', description: 'Evaluate the candidate\'s technical competencies against the job requirements.', category: 'technical', maxScore: 10, weight: 50 }, - { key: 'relevant_experience', name: 'Relevant Experience', description: 'Assess years and quality of experience directly relevant to the role.', category: 'experience', maxScore: 10, weight: 50 }, - { key: 'education_fit', name: 'Education & Certifications', description: 'Evaluate educational background and certifications relevant to the position.', category: 'education', maxScore: 10, weight: 30 }, - ], - technical: [ - { key: 'core_tech_stack', name: 'Core Tech Stack Match', description: 'How well the candidate\'s technical skills match the primary technologies.', category: 'technical', maxScore: 10, weight: 70 }, - { key: 'system_design', name: 'System Design & Architecture', description: 'Evidence of system design experience and architectural decision-making.', category: 'technical', maxScore: 10, weight: 50 }, - { key: 'engineering_practices', name: 'Engineering Practices', description: 'Testing, CI/CD, code review, and software development lifecycle experience.', category: 'technical', maxScore: 10, weight: 40 }, - { key: 'relevant_experience', name: 'Relevant Experience', description: 'Years and depth of experience in similar roles or domains.', category: 'experience', maxScore: 10, weight: 50 }, - { key: 'leadership_collab', name: 'Leadership & Collaboration', description: 'Evidence of mentoring, tech leadership, and cross-team collaboration.', category: 'soft_skills', maxScore: 10, weight: 30 }, - ], - non_technical: [ - { key: 'relevant_experience', name: 'Relevant Experience', description: 'Depth and breadth of experience applicable to the role.', category: 'experience', maxScore: 10, weight: 60 }, - { key: 'communication', name: 'Communication Skills', description: 'Evidence of written and verbal communication ability.', category: 'soft_skills', maxScore: 10, weight: 50 }, - { key: 'domain_knowledge', name: 'Domain Knowledge', description: 'Relevant industry or domain expertise.', category: 'experience', maxScore: 10, weight: 40 }, - { key: 'education_fit', name: 'Education & Certifications', description: 'Educational background and certifications relevant to the position.', category: 'education', maxScore: 10, weight: 30 }, - { key: 'culture_fit', name: 'Culture & Values Alignment', description: 'Indicators of alignment with company values and team culture.', category: 'culture', maxScore: 10, weight: 30 }, - ], - } - scoringCriteria.value = templates[template] ?? [] - scoringMode.value = 'premade' - } catch (err: any) { - toast.error('Failed to load template', { message: err?.data?.statusMessage }) - } +// Pre-made scoring templates (no API call needed) +const scoringTemplates: Record<'standard' | 'technical' | 'non_technical', ScoringCriterionDraft[]> = { + standard: [ + { key: 'technical_skills', name: 'Technical Skills', description: 'Evaluate the candidate\'s technical competencies against the job requirements.', category: 'technical', maxScore: 10, weight: 50 }, + { key: 'relevant_experience', name: 'Relevant Experience', description: 'Assess years and quality of experience directly relevant to the role.', category: 'experience', maxScore: 10, weight: 50 }, + { key: 'education_fit', name: 'Education & Certifications', description: 'Evaluate educational background and certifications relevant to the position.', category: 'education', maxScore: 10, weight: 30 }, + ], + technical: [ + { key: 'core_tech_stack', name: 'Core Tech Stack Match', description: 'How well the candidate\'s technical skills match the primary technologies.', category: 'technical', maxScore: 10, weight: 70 }, + { key: 'system_design', name: 'System Design & Architecture', description: 'Evidence of system design experience and architectural decision-making.', category: 'technical', maxScore: 10, weight: 50 }, + { key: 'engineering_practices', name: 'Engineering Practices', description: 'Testing, CI/CD, code review, and software development lifecycle experience.', category: 'technical', maxScore: 10, weight: 40 }, + { key: 'relevant_experience', name: 'Relevant Experience', description: 'Years and depth of experience in similar roles or domains.', category: 'experience', maxScore: 10, weight: 50 }, + { key: 'leadership_collab', name: 'Leadership & Collaboration', description: 'Evidence of mentoring, tech leadership, and cross-team collaboration.', category: 'soft_skills', maxScore: 10, weight: 30 }, + ], + non_technical: [ + { key: 'relevant_experience', name: 'Relevant Experience', description: 'Depth and breadth of experience applicable to the role.', category: 'experience', maxScore: 10, weight: 60 }, + { key: 'communication', name: 'Communication Skills', description: 'Evidence of written and verbal communication ability.', category: 'soft_skills', maxScore: 10, weight: 50 }, + { key: 'domain_knowledge', name: 'Domain Knowledge', description: 'Relevant industry or domain expertise.', category: 'experience', maxScore: 10, weight: 40 }, + { key: 'education_fit', name: 'Education & Certifications', description: 'Educational background and certifications relevant to the position.', category: 'education', maxScore: 10, weight: 30 }, + { key: 'culture_fit', name: 'Culture & Values Alignment', description: 'Indicators of alignment with company values and team culture.', category: 'culture', maxScore: 10, weight: 30 }, + ], +} + +// Recommend a template from the job title so the default path needs zero decisions. +const TECHNICAL_TITLE_RE = /\b(engineer|developer|programmer|software|data|devops|back[\s-]?end|front[\s-]?end|full[\s-]?stack|sysadmin|sre|architect|scientist|machine learning|ml|ai|qa|security|cloud|infrastructure)\b/i +const recommendedTemplate = computed<'standard' | 'technical' | 'non_technical'>(() => + TECHNICAL_TITLE_RE.test(form.value.title) ? 'technical' : 'standard', +) +const recommendedCriteria = computed(() => scoringTemplates[recommendedTemplate.value]) + +function loadPremadeCriteria(template: 'standard' | 'technical' | 'non_technical') { + // Clone so per-job weight edits never mutate the shared template objects. + scoringCriteria.value = scoringTemplates[template].map(c => ({ ...c })) + scoringMode.value = 'premade' +} + +function useRecommendedScoring() { + selectedTemplate.value = recommendedTemplate.value + loadPremadeCriteria(recommendedTemplate.value) +} + +function skipScoring() { + scoringCriteria.value = [] + scoringMode.value = 'none' + showAdvanced.value = false + nextStep() } async function generateAiCriteria() { @@ -286,6 +307,7 @@ function saveFormToStorage() { applicationForm: applicationForm.value, scoringCriteria: scoringCriteria.value, scoringMode: scoringMode.value, + aiScoringChosen: aiScoringChosen.value, autoScoreOnApply: autoScoreOnApply.value, currentStep: currentStep.value, } @@ -303,6 +325,7 @@ function restoreFormFromStorage() { if (data.applicationForm) Object.assign(applicationForm.value, data.applicationForm) if (data.scoringCriteria) scoringCriteria.value = data.scoringCriteria if (data.scoringMode) scoringMode.value = data.scoringMode + if (data.aiScoringChosen != null) aiScoringChosen.value = data.aiScoringChosen if (data.autoScoreOnApply != null) autoScoreOnApply.value = data.autoScoreOnApply if (data.currentStep) currentStep.value = data.currentStep } catch { /* corrupted data, ignore */ } @@ -335,6 +358,7 @@ function resetState() { } scoringCriteria.value = [] scoringMode.value = 'none' + aiScoringChosen.value = false autoScoreOnApply.value = false isPublished.value = false createdJobId.value = '' @@ -353,22 +377,10 @@ watch(newJobResetSignal, (next, prev) => { }) // Auto-save when step changes or form data changes -watch([currentStep, form, applicationForm, scoringCriteria, scoringMode, autoScoreOnApply], () => { +watch([currentStep, form, applicationForm, scoringCriteria, scoringMode, aiScoringChosen, autoScoreOnApply], () => { saveFormToStorage() }, { deep: true }) -// Notify user when entering step 3 without AI configured -watch(currentStep, (step) => { - if (step === 3 && !isAiConfigured.value) { - toast.add({ - type: 'warning', - title: 'AI integration not set up', - message: 'To use AI-powered candidate scoring, configure your AI provider in Settings → AI. You can still add criteria manually.', - link: { label: 'Go to AI Settings', href: '/dashboard/settings/ai' }, - duration: 10000, - }) - } -}) // Step 4: Publish & Distribute const publishChoice = ref<'publish' | 'draft'>('publish') @@ -379,6 +391,12 @@ const finalApplicationLink = ref('') const linkCopiedFinal = ref(false) // Distribution channels for quick tracking link creation +const distributionGroups = [ + { key: 'job_board', label: 'Job boards' }, + { key: 'outreach', label: 'Direct outreach' }, + { key: 'social', label: 'Social media' }, +] as const + const distributionChannels = [ { channel: 'linkedin', name: 'LinkedIn', description: 'Post on LinkedIn Jobs or share in your feed', category: 'job_board' }, { channel: 'indeed', name: 'Indeed', description: 'List on the Indeed job board', category: 'job_board' }, @@ -778,42 +796,33 @@ const questionTypeLabels: Record = {