import { defineStore } from 'pinia' import { computed, ref, watch } from 'vue' import { STUDENT_REGISTRATION, STUDENT_REGISTRATION_ORDER } from '@/enums' const STORAGE_KEY = 'student-registration' const emptyState = () => ({ isProfileCompleted: false, personal: { avatar: null, avatarId: '', name: '', birthday: '', nationalCode: '', maritalStatus: '', gender: '', phoneNumber: '', provinceId: null, cityId: null, address: '', postalCode: '', virtualPhoneNumber: '', seminaryCode: '', seminaryServiceCenterCode: '', childrenCount: '', }, educational: { educationStatus: '', seminaryLevel: '', universityLevel: '', universityName: '', fieldOfStudy: '', activitySummary: '', }, professional: { propagationExperienceYears: 0, propagationMethodDescription: '', specializedTopics: '', propagationPlatforms: [], }, skill1: { responseToLowSatisfaction: '', responseToLowSatisfactionDescription: '', }, skill2: { responseToSessionCancellation: '', responseToSessionCancellationDescription: '', }, skill3: { responseToAudienceConflict: '', responseToAudienceConflictDescription: '', }, skill4: { responseToCompetingPropagator: '', responseToCompetingPropagatorDescription: '', }, skill5: { responseToUnqualifiedAdvisors: '', relevantCertificates: '', hijabApproach: '', }, skill6: { faithProductionAudio: null, faithProductionId: '', }, skill7: { leaderMessageVideo: null, leaderMessageId: '', }, currentStep: STUDENT_REGISTRATION.PERSONAL_INFORMATION, completedSteps: [], registrationMedia: {}, }) // Each field is owned by exactly one section. Used to route GET /me/register-data // values back into the matching slice on hydration. const SECTION_FIELDS = { personal: [ 'avatar', 'avatarId', 'name', 'birthday', 'nationalCode', 'maritalStatus', 'gender', 'phoneNumber', 'provinceId', 'cityId', 'address', 'postalCode', 'virtualPhoneNumber', 'seminaryCode', 'seminaryServiceCenterCode', 'childrenCount', ], educational: [ 'educationStatus', 'seminaryLevel', 'universityLevel', 'universityName', 'fieldOfStudy', 'activitySummary', ], professional: [ 'propagationExperienceYears', 'propagationMethodDescription', 'specializedTopics', 'propagationPlatforms', ], skill1: ['responseToLowSatisfaction', 'responseToLowSatisfactionDescription'], skill2: ['responseToSessionCancellation', 'responseToSessionCancellationDescription'], skill3: ['responseToAudienceConflict', 'responseToAudienceConflictDescription'], skill4: ['responseToCompetingPropagator', 'responseToCompetingPropagatorDescription'], skill5: ['responseToUnqualifiedAdvisors', 'relevantCertificates', 'hijabApproach'], skill6: ['faithProductionAudio', 'faithProductionId'], skill7: ['leaderMessageVideo', 'leaderMessageId'], } const FIELD_TO_SECTION = Object.entries(SECTION_FIELDS).reduce((acc, [section, fields]) => { fields.forEach((field) => { acc[field] = section }) return acc }, {}) // Register-data travels as a flat `{ field: value }` object. Legacy payloads // were a `[{ key, value }]` list — tolerate both when reading. export const toRegisterDataObject = (input) => { if (Array.isArray(input)) { return input.reduce((acc, item) => { if (item && typeof item === 'object' && 'key' in item) acc[item.key] = item.value return acc }, {}) } return input && typeof input === 'object' ? input : {} } const readPersisted = () => { try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return null return JSON.parse(raw) } catch { return null } } const normalizeCompletedSteps = (steps) => Array.isArray(steps) ? STUDENT_REGISTRATION_ORDER.filter((step) => steps.includes(step)) : [] const firstIncompleteIndex = (steps) => { const index = STUDENT_REGISTRATION_ORDER.findIndex((step) => !steps.includes(step)) return index === -1 ? STUDENT_REGISTRATION_ORDER.length - 1 : index } const resolveCurrentStep = (candidate, completed) => { const candidateIndex = STUDENT_REGISTRATION_ORDER.indexOf(candidate) const reachableIndex = firstIncompleteIndex(completed) if (candidateIndex >= 0 && candidateIndex <= reachableIndex) return candidate return STUDENT_REGISTRATION_ORDER[reachableIndex] } export const useStudentRegistrationStore = defineStore('studentRegistration', () => { const persisted = readPersisted() || {} const initial = { ...emptyState(), ...persisted } const isProfileCompleted = ref(initial.isProfileCompleted) const personal = ref({ ...emptyState().personal, ...persisted.personal }) const educational = ref({ ...emptyState().educational, ...persisted.educational }) const professional = ref({ ...emptyState().professional, ...persisted.professional }) const skill1 = ref({ ...emptyState().skill1, ...persisted.skill1 }) const skill2 = ref({ ...emptyState().skill2, ...persisted.skill2 }) const skill3 = ref({ ...emptyState().skill3, ...persisted.skill3 }) const skill4 = ref({ ...emptyState().skill4, ...persisted.skill4 }) const skill5 = ref({ ...emptyState().skill5, ...persisted.skill5 }) const skill6 = ref({ ...emptyState().skill6, ...persisted.skill6 }) const skill7 = ref({ ...emptyState().skill7, ...persisted.skill7 }) const completedSteps = ref(normalizeCompletedSteps(initial.completedSteps)) const currentStep = ref(resolveCurrentStep(initial.currentStep, completedSteps.value)) const registrationMedia = ref(initial.registrationMedia || {}) const sectionRefs = { personal, educational, professional, skill1, skill2, skill3, skill4, skill5, skill6, skill7, } const snapshot = computed(() => ({ isProfileCompleted: isProfileCompleted.value, personal: personal.value, educational: educational.value, professional: professional.value, skill1: skill1.value, skill2: skill2.value, skill3: skill3.value, skill4: skill4.value, skill5: skill5.value, skill6: skill6.value, skill7: skill7.value, currentStep: currentStep.value, completedSteps: completedSteps.value, registrationMedia: registrationMedia.value, })) watch( snapshot, (value) => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(value)) } catch { /* storage may be unavailable */ } }, { deep: true } ) const markStepAsCompleted = (stepKey) => { if (STUDENT_REGISTRATION_ORDER.includes(stepKey) && !completedSteps.value.includes(stepKey)) { completedSteps.value = [...completedSteps.value, stepKey] } } const updateCurrentStep = (stepKey) => { if (canGoToStep(stepKey)) currentStep.value = stepKey } const updateSection = (section, value) => { const target = sectionRefs[section] if (!target || !value || typeof value !== 'object') return target.value = { ...target.value, ...value } } const setRegistrationMedia = (value) => { if (!value || typeof value !== 'object') return registrationMedia.value = { ...registrationMedia.value, ...value } } const goToNextStep = () => { const idx = STUDENT_REGISTRATION_ORDER.indexOf(currentStep.value) if ( idx >= 0 && idx < STUDENT_REGISTRATION_ORDER.length - 1 && completedSteps.value.includes(currentStep.value) ) { currentStep.value = STUDENT_REGISTRATION_ORDER[idx + 1] } } const goToPrevStep = () => { const idx = STUDENT_REGISTRATION_ORDER.indexOf(currentStep.value) if (idx > 0) currentStep.value = STUDENT_REGISTRATION_ORDER[idx - 1] } const canGoToStep = (stepKey) => { const targetIndex = STUDENT_REGISTRATION_ORDER.indexOf(stepKey) return targetIndex >= 0 && targetIndex <= firstIncompleteIndex(completedSteps.value) } const buildCompletePayload = () => ({ ...personal.value, ...educational.value, ...professional.value, ...skill1.value, ...skill2.value, ...skill3.value, ...skill4.value, ...skill5.value, ...skill6.value, ...skill7.value, avatarId: personal.value.avatar?.uploadId ? Number.parseInt(personal.value.avatar.uploadId) : personal.value.avatarId, }) // Take the flat `{ field: value }` map from GET /me/register-data and slot // each value into the right section ref, leaving anything we don't // recognise alone. const hydrateFromRegisterData = (input) => { const flat = toRegisterDataObject(input) const buckets = {} Object.entries(flat).forEach(([key, value]) => { const section = FIELD_TO_SECTION[key] if (!section) return buckets[section] = { ...buckets[section], [key]: value } }) Object.entries(buckets).forEach(([section, patch]) => { const target = sectionRefs[section] if (target) target.value = { ...target.value, ...patch } }) } const reset = () => { const fresh = emptyState() isProfileCompleted.value = fresh.isProfileCompleted personal.value = fresh.personal educational.value = fresh.educational professional.value = fresh.professional skill1.value = fresh.skill1 skill2.value = fresh.skill2 skill3.value = fresh.skill3 skill4.value = fresh.skill4 skill5.value = fresh.skill5 skill6.value = fresh.skill6 skill7.value = fresh.skill7 currentStep.value = fresh.currentStep completedSteps.value = fresh.completedSteps registrationMedia.value = fresh.registrationMedia try { localStorage.removeItem(STORAGE_KEY) } catch { /* ignore */ } } return { isProfileCompleted, personal, educational, professional, skill1, skill2, skill3, skill4, skill5, skill6, skill7, currentStep, completedSteps, registrationMedia, markStepAsCompleted, updateCurrentStep, updateSection, setRegistrationMedia, goToNextStep, goToPrevStep, canGoToStep, buildCompletePayload, hydrateFromRegisterData, reset, } })