diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1be587a..8f04671 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,7 +5,7 @@ module.exports = { node: true, es2021: true, }, - plugins: ['prettier', 'unicorn', 'import'], + plugins: ['prettier', 'unicorn', 'import', 'waterfall'], ignorePatterns: ['node_modules/', 'dist/', 'build/', '*.min.js'], parserOptions: { ecmaVersion: 2021, @@ -71,7 +71,10 @@ module.exports = { 'unicorn/prefer-logical-operator-over-ternary': 'off', 'unicorn/prefer-number-properties': 'off', 'unicorn/prefer-ternary': 'off', - 'no-unused-vars': 'off', + 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + 'waterfall/waterfall-imports': 'warn', + 'waterfall/waterfall-requires': 'warn', + 'waterfall/waterfall-objects': 'off', 'no-irregular-whitespace': 'off', 'no-empty': 'off', 'import/extensions': 'off', @@ -80,9 +83,9 @@ module.exports = { 'vue/html-self-closing': 'off', 'vue/max-attributes-per-line': 'off', 'vue/html-indent': 'off', - 'vue/no-unused-components': 'off', + 'vue/no-unused-components': 'error', 'vue/no-reserved-component-names': 'off', - 'vue/no-unused-vars': 'off', + 'vue/no-unused-vars': 'error', 'vue/require-v-for-key': 'off', 'vue/require-toggle-inside-transition': 'off', 'vue/require-valid-default-prop': 'off', diff --git a/package-lock.json b/package-lock.json index 258308a..08f0882 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-unicorn": "^55.0.0", "eslint-plugin-vue": "^9.15.1", + "eslint-plugin-waterfall": "^1.0.1", "postcss-html": "^1.8.0", "prettier": "2.8.8", "sass": "^1.83.0", @@ -3467,6 +3468,13 @@ "eslint": ">=6.0.0" } }, + "node_modules/eslint-plugin-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-waterfall/-/eslint-plugin-waterfall-1.0.1.tgz", + "integrity": "sha512-g6rmlPsbKXRnDDcOLjNifqU5Ctml/90KDsvJill1SMIyBVZ17MgcggC3lIM9UcnbFmOttVHBVV3a71Yd/33TKw==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", diff --git a/package.json b/package.json index d609ae3..0297ccd 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-unicorn": "^55.0.0", "eslint-plugin-vue": "^9.15.1", + "eslint-plugin-waterfall": "^1.0.1", "postcss-html": "^1.8.0", "prettier": "2.8.8", "sass": "^1.83.0", diff --git a/src/App.vue b/src/App.vue index f2115d8..b9f8711 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,16 +5,15 @@ diff --git a/src/features/admin/exams/components/modals/ExamParticipantDetailsModal.vue b/src/features/admin/exams/components/modals/ExamParticipantDetailsModal.vue index 9f91538..2b0529a 100644 --- a/src/features/admin/exams/components/modals/ExamParticipantDetailsModal.vue +++ b/src/features/admin/exams/components/modals/ExamParticipantDetailsModal.vue @@ -1,77 +1,95 @@ @@ -145,15 +188,26 @@ const answerClass = (question, answer) => { margin: 0.5rem 0 1rem; } + &__card { + border: 1px solid #e7e2dd; + border-radius: 1.75rem; + background: #fff; + padding: 1rem; + + @media (min-width: 640px) { + padding: 1.5rem; + } + } + &__list { display: flex; flex-direction: column; - gap: 1rem; + gap: 1.25rem; } &__attempt { border: 1px solid #e7e2dd; - border-radius: 1.5rem; + border-radius: 1.75rem; overflow: hidden; background: #fff; } @@ -161,129 +215,184 @@ const answerClass = (question, answer) => { &__attempt-head { display: flex; align-items: flex-start; - gap: 0.75rem; + gap: 1rem; width: 100%; - text-align: start; padding: 1rem 1.25rem; border: none; background: transparent; cursor: pointer; + text-align: start; + + @media (min-width: 640px) { + padding: 1.25rem 1.5rem; + } + } + + &__caret { + width: 2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-top: 0.25rem; } &__attempt-info { flex: 1; min-width: 0; + display: flex; + flex-direction: column; + gap: 0.25rem; } &__attempt-title { font-family: var(--font-family-fa); font-weight: 500; - font-size: 1rem; + font-size: 1.125rem; + line-height: 1.7; color: #494949; - margin: 0 0 0.5rem; + margin: 0; } &__attempt-meta { display: flex; flex-wrap: wrap; - gap: 0.625rem 1.25rem; - font-family: var(--font-family-fa); - font-size: 0.75rem; + align-items: center; + column-gap: 1.25rem; + row-gap: 0.5rem; color: #8e8e8e; } - &__sep { - color: #d6d6d6; + &__meta-item { + white-space: nowrap; + font-size: 0.875rem; + line-height: 1.5; + } + + &__meta-label { + font-family: var(--font-family-fa); + font-weight: 300; + margin-inline-end: 0.25rem; + } + + &__meta-value { + font-family: var(--font-family-fa); + color: #7c7c7c; + } + + &__meta-value--en { + font-family: var(--font-family-en); } &__score { - padding: 0.25rem 0.875rem; + padding: 0.375rem 0.75rem; border-radius: 0.55rem; font-family: var(--font-family-fa); - font-size: 0.75rem; + font-size: 0.875rem; + line-height: 1.3; - &--good { - background: rgba(0, 154, 18, 8%); - color: #009a12; + &--success { + background: #e8f8ee; + color: #169c56; + } + + &--danger { + background: #fdebeb; + color: #cc2831; } &--neutral { - background: rgba(180, 180, 180, 8%); + background: #f4f4f4; color: #8e8e8e; } - - &--bad { - background: rgba(204, 40, 49, 8%); - color: var(--color-error); - } + } + + &__score-value { + font-family: var(--font-family-en); + font-weight: 500; } &__body { - padding: 1rem 1.25rem 1.5rem; + padding: 1.25rem; display: flex; flex-direction: column; - gap: 1.25rem; + gap: 2rem; + + @media (min-width: 640px) { + padding: 1.25rem 1.5rem 1.5rem; + } } &__question-title { font-family: var(--font-family-fa); + font-weight: 300; font-size: 0.875rem; + line-height: 1.75; color: #bdbdbd; - margin: 0 0 0.75rem; + margin: 0 0 1.25rem; } &__answers { display: flex; flex-wrap: wrap; - gap: 0.625rem 1rem; + column-gap: 3rem; + row-gap: 1.25rem; } &__answer { - border: 1px solid #e7e2dd; - border-radius: 0.875rem; - padding: 0.375rem 0.875rem; - font-family: var(--font-family-fa); - font-size: 0.85rem; - color: #4b4b4b; display: inline-flex; - align-items: center; + align-items: baseline; gap: 0.375rem; + } + + &__answer-dot { + display: inline-block; + width: 0.75rem; + height: 0.75rem; + border-radius: 0.25rem; + border: 1px solid #d4d4d4; + background: transparent; + flex-shrink: 0; &--correct { - background: rgba(0, 154, 18, 8%); - border-color: rgba(0, 154, 18, 40%); - color: #009a12; - } - - &--correct-outline { - border-color: rgba(0, 154, 18, 40%); - color: #009a12; + border-color: #007074; + background: #007074; } &--wrong { - background: rgba(204, 40, 49, 8%); - border-color: rgba(204, 40, 49, 40%); - color: var(--color-error); + border-color: #cc2831; + background: #cc2831; + } + + &--selected-correct { + border-color: #9a9a9a; + background: #9a9a9a; } } - &__answer-index { - font-family: var(--font-family-en); - color: #b3b3b3; + &__answer-text { + font-family: var(--font-family-fa); + font-size: 1rem; + line-height: 1.7; + color: #5b5b5b; + margin: 0; } &__divider { - border-block-end: 1px solid var(--color-thd-gray); - margin-block: 1.25rem; + border-block-end: 1px solid #e7e2dd; + margin-block: 1.5rem 1rem; } &__footer { display: flex; justify-content: flex-end; + margin-top: 0.75rem; } - &__close-btn { - min-width: 12rem; + &__confirm-btn { + min-width: 11.5rem; + padding: 0 2rem; } } diff --git a/src/features/admin/exams/components/modals/ExamParticipantsModal.vue b/src/features/admin/exams/components/modals/ExamParticipantsModal.vue index e14bbb8..2ef413e 100644 --- a/src/features/admin/exams/components/modals/ExamParticipantsModal.vue +++ b/src/features/admin/exams/components/modals/ExamParticipantsModal.vue @@ -63,18 +63,17 @@ + + diff --git a/src/features/auth/components/studentRegister/MediaRecorder.vue b/src/features/auth/components/studentRegister/MediaRecorder.vue new file mode 100644 index 0000000..acccbb2 --- /dev/null +++ b/src/features/auth/components/studentRegister/MediaRecorder.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/PermissionModal.vue b/src/features/auth/components/studentRegister/PermissionModal.vue new file mode 100644 index 0000000..5360f23 --- /dev/null +++ b/src/features/auth/components/studentRegister/PermissionModal.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/PersonalInformation.vue b/src/features/auth/components/studentRegister/PersonalInformation.vue new file mode 100644 index 0000000..2535284 --- /dev/null +++ b/src/features/auth/components/studentRegister/PersonalInformation.vue @@ -0,0 +1,337 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/ProfessionalInformation.vue b/src/features/auth/components/studentRegister/ProfessionalInformation.vue new file mode 100644 index 0000000..edf97b5 --- /dev/null +++ b/src/features/auth/components/studentRegister/ProfessionalInformation.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/SideBarSteps.vue b/src/features/auth/components/studentRegister/SideBarSteps.vue new file mode 100644 index 0000000..f34c000 --- /dev/null +++ b/src/features/auth/components/studentRegister/SideBarSteps.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/SkillAssessmentPart1.vue b/src/features/auth/components/studentRegister/SkillAssessmentPart1.vue new file mode 100644 index 0000000..7071a96 --- /dev/null +++ b/src/features/auth/components/studentRegister/SkillAssessmentPart1.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/features/auth/components/studentRegister/SkillAssessmentPart2.vue b/src/features/auth/components/studentRegister/SkillAssessmentPart2.vue new file mode 100644 index 0000000..40cf49e --- /dev/null +++ b/src/features/auth/components/studentRegister/SkillAssessmentPart2.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/features/auth/components/studentRegister/SkillAssessmentPart3.vue b/src/features/auth/components/studentRegister/SkillAssessmentPart3.vue new file mode 100644 index 0000000..fc7d254 --- /dev/null +++ b/src/features/auth/components/studentRegister/SkillAssessmentPart3.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/features/auth/components/studentRegister/SkillAssessmentPart4.vue b/src/features/auth/components/studentRegister/SkillAssessmentPart4.vue new file mode 100644 index 0000000..3f3d15d --- /dev/null +++ b/src/features/auth/components/studentRegister/SkillAssessmentPart4.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/features/auth/components/studentRegister/SkillAssessmentPart5.vue b/src/features/auth/components/studentRegister/SkillAssessmentPart5.vue new file mode 100644 index 0000000..e86417f --- /dev/null +++ b/src/features/auth/components/studentRegister/SkillAssessmentPart5.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/SkillAssessmentPart6.vue b/src/features/auth/components/studentRegister/SkillAssessmentPart6.vue new file mode 100644 index 0000000..9f71470 --- /dev/null +++ b/src/features/auth/components/studentRegister/SkillAssessmentPart6.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/SkillAssessmentPart7.vue b/src/features/auth/components/studentRegister/SkillAssessmentPart7.vue new file mode 100644 index 0000000..94238cf --- /dev/null +++ b/src/features/auth/components/studentRegister/SkillAssessmentPart7.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/SkillSelectStep.vue b/src/features/auth/components/studentRegister/SkillSelectStep.vue new file mode 100644 index 0000000..abb2415 --- /dev/null +++ b/src/features/auth/components/studentRegister/SkillSelectStep.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/StepActions.vue b/src/features/auth/components/studentRegister/StepActions.vue new file mode 100644 index 0000000..7c741b2 --- /dev/null +++ b/src/features/auth/components/studentRegister/StepActions.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/features/auth/components/studentRegister/SuccessModal.vue b/src/features/auth/components/studentRegister/SuccessModal.vue new file mode 100644 index 0000000..25bd9b7 --- /dev/null +++ b/src/features/auth/components/studentRegister/SuccessModal.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/src/features/auth/composables/useRedirectAfterLogin.js b/src/features/auth/composables/useRedirectAfterLogin.js index eea6b83..a8991e4 100644 --- a/src/features/auth/composables/useRedirectAfterLogin.js +++ b/src/features/auth/composables/useRedirectAfterLogin.js @@ -1,6 +1,5 @@ -import { useRoute, useRouter } from 'vue-router' - import { useAuthStore } from '@/store/auth' +import { useRoute, useRouter } from 'vue-router' export const useRedirectAfterLogin = () => { const router = useRouter() diff --git a/src/features/auth/pages/LoginPage.vue b/src/features/auth/pages/LoginPage.vue index fe11ac9..3ca7dcf 100644 --- a/src/features/auth/pages/LoginPage.vue +++ b/src/features/auth/pages/LoginPage.vue @@ -18,12 +18,11 @@ + + diff --git a/src/features/auth/router.js b/src/features/auth/router.js index f53fad9..3f1577a 100644 --- a/src/features/auth/router.js +++ b/src/features/auth/router.js @@ -17,4 +17,10 @@ export default [ component: () => import('@/features/auth/pages/ResetPasswordPage.vue'), meta: { public: true, layout: 'public', title: 'تنظیم رمز عبور جدید' }, }, + { + path: '/student-register', + name: 'student-register', + component: () => import('@/features/auth/pages/StudentRegisterPage.vue'), + meta: { layout: 'public', title: 'تکمیل ثبت نام' }, + }, ] diff --git a/src/features/auth/schema/index.js b/src/features/auth/schema/index.js index 9668dfa..06671fc 100644 --- a/src/features/auth/schema/index.js +++ b/src/features/auth/schema/index.js @@ -1,7 +1,6 @@ import { object, string } from 'yup' - -import { phoneNumberRule } from '@/constants/rules/phoneNumberRule' import { passwordRule } from '@/constants/rules/passwordRule' +import { phoneNumberRule } from '@/constants/rules/phoneNumberRule' import { passwordConfirmationRule } from '@/constants/rules/passwordConfirmationRule' export const loginSchema = object().shape({ diff --git a/src/features/auth/schema/student-register.js b/src/features/auth/schema/student-register.js new file mode 100644 index 0000000..40e9048 --- /dev/null +++ b/src/features/auth/schema/student-register.js @@ -0,0 +1,115 @@ +import { array, mixed, number, object, string } from 'yup' + +import { phoneNumberRule } from '@/constants/rules/phoneNumberRule' +import { nationalCodeRule } from '@/constants/rules/nationalCodeRule' + +const requiredString = (min = 0) => { + const base = string().required() + return min > 0 ? base.min(min) : base +} + +export const personalInformationSchema = object().shape({ + avatar: mixed().required(), + avatarId: mixed().notRequired(), + firstName: requiredString(3), + lastName: requiredString(3), + birthDate: string().required(), + nationalCode: nationalCodeRule, + maritalStatus: string().required(), + gender: string().required(), + phoneNumber: phoneNumberRule, + provinceId: mixed().required(), + cityId: mixed().required(), + address: requiredString(10), +}) + +export const educationalInformationSchema = object().shape({ + educationStatus: string().required(), + seminaryLevel: string().required(), + universityLevel: string().required(), + universityName: requiredString(5), + fieldOfStudy: requiredString(5), + workExperienceSummary: requiredString(10), + activitySummary: requiredString(10), +}) + +export const professionalInformationSchema = object().shape({ + propagationExperienceYears: number() + .typeError('سنوات تجربه تبلیغ باید عدد باشد') + .required() + .min(0) + .max(70), + propagationMethodDescription: requiredString(10), + specializedTopics: requiredString(10), + propagationPlatforms: array() + .of(string()) + .min(1, 'حداقل یک بستر فعالیت را انتخاب کنید') + .max(6, 'حداکثر ۶ بستر فعالیت قابل انتخاب است') + .required() + .test( + 'no-both-none-and-others', + 'نمی‌توانید همزمان "فعالیتی نداشته‌ام" و سایر گزینه‌ها را انتخاب کنید', + (value) => { + if (!value) return false + const hasNone = value.includes('none') + const hasOthers = value.some((v) => v !== 'none') + return !(hasNone && hasOthers) + } + ), + onlinePlatformDetails: string().when('propagationPlatforms', { + is: (val) => Array.isArray(val) && val.includes('online'), + then: (s) => s.required().min(10), + otherwise: (s) => s.notRequired().nullable(), + }), + otherPlatformDetails: string().when('propagationPlatforms', { + is: (val) => Array.isArray(val) && val.includes('other'), + then: (s) => s.required().min(10), + otherwise: (s) => s.notRequired().nullable(), + }), +}) + +const skillSelectWithOtherSchema = (selectField, descField) => + object().shape({ + [selectField]: string().required(), + [descField]: string().when(selectField, { + is: 'other', + then: (s) => s.required().min(10), + otherwise: (s) => s.notRequired().nullable(), + }), + }) + +export const skillAssessmentPart1Schema = skillSelectWithOtherSchema( + 'responseToLowSatisfaction', + 'responseToLowSatisfactionDescription' +) + +export const skillAssessmentPart2Schema = skillSelectWithOtherSchema( + 'responseToSessionCancellation', + 'responseToSessionCancellationDescription' +) + +export const skillAssessmentPart3Schema = skillSelectWithOtherSchema( + 'responseToAudienceConflict', + 'responseToAudienceConflictDescription' +) + +export const skillAssessmentPart4Schema = skillSelectWithOtherSchema( + 'responseToCompetingPropagator', + 'responseToCompetingPropagatorDescription' +) + +export const skillAssessmentPart5Schema = object().shape({ + responseToUnqualifiedAdvisors: requiredString(10), + relevantCertificates: requiredString(10), + hijabApproach: requiredString(10), +}) + +export const skillAssessmentPart6Schema = object().shape({ + faithProductionAudio: mixed().required(), + faithProductionId: mixed().notRequired(), +}) + +export const skillAssessmentPart7Schema = object().shape({ + leaderMessageVideo: mixed().required(), + leaderMessageId: mixed().notRequired(), +}) diff --git a/src/features/auth/store/student-registration.js b/src/features/auth/store/student-registration.js new file mode 100644 index 0000000..6e6da5e --- /dev/null +++ b/src/features/auth/store/student-registration.js @@ -0,0 +1,220 @@ +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: '', + firstName: '', + lastName: '', + birthDate: '', + nationalCode: '', + maritalStatus: '', + gender: '', + phoneNumber: '', + provinceId: null, + cityId: null, + address: '', + }, + educational: { + educationStatus: '', + seminaryLevel: '', + universityLevel: '', + universityName: '', + fieldOfStudy: '', + workExperienceSummary: '', + 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: {}, +}) + +const readPersisted = () => { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return null + return JSON.parse(raw) + } catch { + return null + } +} + +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 currentStep = ref(initial.currentStep) + const completedSteps = ref(initial.completedSteps || []) + const registrationMedia = ref(initial.registrationMedia || {}) + + 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 (!completedSteps.value.includes(stepKey)) { + completedSteps.value = [...completedSteps.value, stepKey] + } + } + + const updateCurrentStep = (stepKey) => { + if (stepKey) currentStep.value = stepKey + } + + const goToNextStep = () => { + const idx = STUDENT_REGISTRATION_ORDER.indexOf(currentStep.value) + if (idx >= 0 && idx < STUDENT_REGISTRATION_ORDER.length - 1) { + 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 currentIndex = STUDENT_REGISTRATION_ORDER.indexOf(currentStep.value) + const targetIndex = STUDENT_REGISTRATION_ORDER.indexOf(stepKey) + return targetIndex <= currentIndex + 1 + } + + 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, + }) + + 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, + goToNextStep, + goToPrevStep, + canGoToStep, + buildCompletePayload, + reset, + } +}) diff --git a/src/features/global-components/components/ItemDetailsModal.vue b/src/features/global-components/components/ItemDetailsModal.vue index 0d5136e..65a7556 100644 --- a/src/features/global-components/components/ItemDetailsModal.vue +++ b/src/features/global-components/components/ItemDetailsModal.vue @@ -28,10 +28,9 @@