- Import web/ directory (Vue 3 + Vite + UnoCSS SPA) from feature/vue-spa branch - Add web/src/components/ClassicUIButton.vue: switch-back to Streamlit via cookie (prgn_ui=streamlit) + ?prgn_switch=streamlit query param bridge - Add web/src/composables/useFeatureFlag.ts: reads prgn_demo_tier cookie for demo toolbar visual consistency (not an authoritative gate, see issue #8) - Update .gitignore: add .superpowers/, pytest-output.txt, docs/superpowers/
125 lines
5.8 KiB
TypeScript
125 lines
5.8 KiB
TypeScript
import { ref } from 'vue'
|
|
import { defineStore } from 'pinia'
|
|
import { useApiFetch } from '../../composables/useApi'
|
|
|
|
export interface WorkEntry {
|
|
id: string
|
|
title: string; company: string; period: string; location: string
|
|
industry: string; responsibilities: string; skills: string[]
|
|
}
|
|
|
|
export const useResumeStore = defineStore('settings/resume', () => {
|
|
const hasResume = ref(false)
|
|
const loading = ref(false)
|
|
const saving = ref(false)
|
|
const saveError = ref<string | null>(null)
|
|
const loadError = ref<string | null>(null)
|
|
|
|
// Identity (synced from profile store)
|
|
const name = ref(''); const email = ref(''); const phone = ref(''); const linkedin_url = ref('')
|
|
// Resume-only contact
|
|
const surname = ref(''); const address = ref(''); const city = ref('')
|
|
const zip_code = ref(''); const date_of_birth = ref('')
|
|
// Experience
|
|
const experience = ref<WorkEntry[]>([])
|
|
// Prefs
|
|
const salary_min = ref(0); const salary_max = ref(0); const notice_period = ref('')
|
|
const remote = ref(false); const relocation = ref(false)
|
|
const assessment = ref(false); const background_check = ref(false)
|
|
// Self-ID
|
|
const gender = ref(''); const pronouns = ref(''); const ethnicity = ref('')
|
|
const veteran_status = ref(''); const disability = ref('')
|
|
// Keywords
|
|
const skills = ref<string[]>([]); const domains = ref<string[]>([]); const keywords = ref<string[]>([])
|
|
|
|
function syncFromProfile(p: { name: string; email: string; phone: string; linkedin_url: string }) {
|
|
name.value = p.name; email.value = p.email
|
|
phone.value = p.phone; linkedin_url.value = p.linkedin_url
|
|
}
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
loadError.value = null
|
|
const { data, error } = await useApiFetch<Record<string, unknown>>('/api/settings/resume')
|
|
loading.value = false
|
|
if (error) {
|
|
loadError.value = error.kind === 'network' ? error.message : (error.detail || 'Failed to load resume')
|
|
return
|
|
}
|
|
if (!data || !data.exists) { hasResume.value = false; return }
|
|
hasResume.value = true
|
|
name.value = String(data.name ?? ''); email.value = String(data.email ?? '')
|
|
phone.value = String(data.phone ?? ''); linkedin_url.value = String(data.linkedin_url ?? '')
|
|
surname.value = String(data.surname ?? ''); address.value = String(data.address ?? '')
|
|
city.value = String(data.city ?? ''); zip_code.value = String(data.zip_code ?? '')
|
|
date_of_birth.value = String(data.date_of_birth ?? '')
|
|
experience.value = (data.experience as Omit<WorkEntry, 'id'>[]).map(e => ({ ...e, id: crypto.randomUUID() })) ?? []
|
|
salary_min.value = Number(data.salary_min ?? 0); salary_max.value = Number(data.salary_max ?? 0)
|
|
notice_period.value = String(data.notice_period ?? '')
|
|
remote.value = Boolean(data.remote); relocation.value = Boolean(data.relocation)
|
|
assessment.value = Boolean(data.assessment); background_check.value = Boolean(data.background_check)
|
|
gender.value = String(data.gender ?? ''); pronouns.value = String(data.pronouns ?? '')
|
|
ethnicity.value = String(data.ethnicity ?? ''); veteran_status.value = String(data.veteran_status ?? '')
|
|
disability.value = String(data.disability ?? '')
|
|
skills.value = (data.skills as string[]) ?? []
|
|
domains.value = (data.domains as string[]) ?? []
|
|
keywords.value = (data.keywords as string[]) ?? []
|
|
}
|
|
|
|
async function save() {
|
|
saving.value = true; saveError.value = null
|
|
const body = {
|
|
name: name.value, email: email.value, phone: phone.value, linkedin_url: linkedin_url.value,
|
|
surname: surname.value, address: address.value, city: city.value, zip_code: zip_code.value,
|
|
date_of_birth: date_of_birth.value,
|
|
experience: experience.value.map(({ id: _id, ...e }) => e),
|
|
salary_min: salary_min.value, salary_max: salary_max.value, notice_period: notice_period.value,
|
|
remote: remote.value, relocation: relocation.value,
|
|
assessment: assessment.value, background_check: background_check.value,
|
|
gender: gender.value, pronouns: pronouns.value, ethnicity: ethnicity.value,
|
|
veteran_status: veteran_status.value, disability: disability.value,
|
|
skills: skills.value, domains: domains.value, keywords: keywords.value,
|
|
}
|
|
const { error } = await useApiFetch('/api/settings/resume', {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
})
|
|
saving.value = false
|
|
if (error) saveError.value = 'Save failed — please try again.'
|
|
}
|
|
|
|
async function createBlank() {
|
|
const { error } = await useApiFetch('/api/settings/resume/blank', { method: 'POST' })
|
|
if (!error) { hasResume.value = true; await load() }
|
|
}
|
|
|
|
function addExperience() {
|
|
experience.value.push({ id: crypto.randomUUID(), title: '', company: '', period: '', location: '', industry: '', responsibilities: '', skills: [] })
|
|
}
|
|
|
|
function removeExperience(idx: number) {
|
|
experience.value.splice(idx, 1)
|
|
}
|
|
|
|
function addTag(field: 'skills' | 'domains' | 'keywords', value: string) {
|
|
const arr = field === 'skills' ? skills.value : field === 'domains' ? domains.value : keywords.value
|
|
const trimmed = value.trim()
|
|
if (!trimmed || arr.includes(trimmed)) return
|
|
arr.push(trimmed)
|
|
}
|
|
|
|
function removeTag(field: 'skills' | 'domains' | 'keywords', value: string) {
|
|
const arr = field === 'skills' ? skills.value : field === 'domains' ? domains.value : keywords.value
|
|
const idx = arr.indexOf(value)
|
|
if (idx !== -1) arr.splice(idx, 1)
|
|
}
|
|
|
|
return {
|
|
hasResume, loading, saving, saveError, loadError,
|
|
name, email, phone, linkedin_url, surname, address, city, zip_code, date_of_birth,
|
|
experience, salary_min, salary_max, notice_period, remote, relocation, assessment, background_check,
|
|
gender, pronouns, ethnicity, veteran_status, disability,
|
|
skills, domains, keywords,
|
|
syncFromProfile, load, save, createBlank,
|
|
addExperience, removeExperience, addTag, removeTag,
|
|
}
|
|
})
|