From d0adec74b598558ba00dce3e1e6a9d56fe893066 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Mon, 3 Nov 2025 20:24:05 +0530 Subject: [PATCH 01/35] feat: add step one for image upload and personal details --- .../new-join-steps/new-step-one.hbs | 79 +++++ app/components/new-join-steps/new-step-one.js | 81 +++++ .../new-join-steps/stepper-header.hbs | 14 + .../new-join-steps/stepper-header.js | 7 + app/components/new-stepper.hbs | 18 ++ app/constants/new-join-form.js | 23 ++ app/styles/new-stepper.module.css | 289 ++++++++++++++++++ 7 files changed, 511 insertions(+) create mode 100644 app/components/new-join-steps/new-step-one.hbs create mode 100644 app/components/new-join-steps/new-step-one.js create mode 100644 app/components/new-join-steps/stepper-header.hbs create mode 100644 app/components/new-join-steps/stepper-header.js create mode 100644 app/constants/new-join-form.js create mode 100644 app/styles/new-stepper.module.css diff --git a/app/components/new-join-steps/new-step-one.hbs b/app/components/new-join-steps/new-step-one.hbs new file mode 100644 index 00000000..dabc85a1 --- /dev/null +++ b/app/components/new-join-steps/new-step-one.hbs @@ -0,0 +1,79 @@ +
+
+

Profile Picture

+
+
+

Upload Image

+
+
+

Image Requirements:

+
    +
  • Must be a real, clear photograph (no anime, filters, or drawings)
  • +
  • Must contain exactly one face
  • +
  • Face must cover at least 60% of the image
  • +
  • Supported formats: JPG, PNG, GIF
  • +
  • Image will be validated during analysis
  • +
+
+
+ +
+

Personal Details

+

Please provide real name and details, any spam/incorrect details will lead to rejection.

+ + + + + + + + + +
+

Applying as:

+
+ {{#each this.roleOptions as |role|}} + + {{/each}} +
+
+
+
\ No newline at end of file diff --git a/app/components/new-join-steps/new-step-one.js b/app/components/new-join-steps/new-step-one.js new file mode 100644 index 00000000..00fe1c01 --- /dev/null +++ b/app/components/new-join-steps/new-step-one.js @@ -0,0 +1,81 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { validator } from '../../utils/validator'; +import { debounce } from '@ember/runloop'; +import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; +import { NEW_STEP_LIMITS, ROLE_OPTIONS } from '../../constants/new-join-form'; +import { inject as service } from '@ember/service'; + +export default class NewStepOneComponent extends Component { + @service login; + + @tracked data = JSON.parse(localStorage.getItem('newStepOneData')) ?? { + fullName: '', + country: '', + state: '', + city: '', + role: '', + }; + + isValid; + setIsValid; + setIsPreValid; + roleOptions = ROLE_OPTIONS; + + constructor(...args) { + super(...args); + + this.isValid = this.args.isValid; + this.setIsValid = this.args.setIsValid; + this.setIsPreValid = this.args.setIsPreValid; + + if (this.login.userData) { + this.data.fullName = `${this.login.userData.first_name} ${this.login.userData.last_name}`; + } + + const validated = this.isDataValid(); + localStorage.setItem('isValid', validated); + this.setIsPreValid(validated); + } + + isDataValid() { + for (let field in this.data) { + if (field === 'role') { + if (!this.data[field]) return false; + } else { + const { isValid } = validator( + this.data[field], + NEW_STEP_LIMITS.stepOne[field], + ); + if (!isValid) { + return false; + } + } + } + return true; + } + + @action inputHandler(e) { + this.setIsPreValid(false); + const setValToLocalStorage = () => { + this.data = { ...this.data, [e.target.name]: e.target.value }; + localStorage.setItem('newStepOneData', JSON.stringify(this.data)); + localStorage.setItem('selectedRole', this.data.role); + const validated = this.isDataValid(); + this.setIsValid(validated); + localStorage.setItem('isValid', validated); + }; + debounce(this.data, setValToLocalStorage, JOIN_DEBOUNCE_TIME); + } + + @action selectRole(role) { + this.setIsPreValid(false); + this.data = { ...this.data, role }; + localStorage.setItem('newStepOneData', JSON.stringify(this.data)); + localStorage.setItem('selectedRole', role); + const validated = this.isDataValid(); + this.setIsValid(validated); + localStorage.setItem('isValid', validated); + } +} diff --git a/app/components/new-join-steps/stepper-header.hbs b/app/components/new-join-steps/stepper-header.hbs new file mode 100644 index 00000000..026c7c8b --- /dev/null +++ b/app/components/new-join-steps/stepper-header.hbs @@ -0,0 +1,14 @@ +
+
+
+

{{@heading}}

+

{{@subheading}}

+
+
+ Step {{@currentStep}} of {{@totalSteps}} +
+
+
+
+
+
\ No newline at end of file diff --git a/app/components/new-join-steps/stepper-header.js b/app/components/new-join-steps/stepper-header.js new file mode 100644 index 00000000..384da0c9 --- /dev/null +++ b/app/components/new-join-steps/stepper-header.js @@ -0,0 +1,7 @@ +import Component from '@glimmer/component'; + +export default class StepperHeaderComponent extends Component { + get progressPercentage() { + return Math.round((this.args.currentStep / this.args.totalSteps) * 100); + } +} diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 598cf547..280dae94 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -1,4 +1,14 @@
+ + {{#if (not-eq this.currentStep 0)}} + + {{/if}} +
{{#if (eq this.currentStep 0)}} @@ -13,6 +23,14 @@ @disabled={{not this.joinApplicationTerms.hasUserAcceptedTerms}} /> + + {{else if (eq this.currentStep 1)}} + + {{/if}}
\ No newline at end of file diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js new file mode 100644 index 00000000..0b6d57ea --- /dev/null +++ b/app/constants/new-join-form.js @@ -0,0 +1,23 @@ +export const NEW_FORM_STEPS = { + headings: ['Upload Professional HeadShot and fill up personal details'], + subheadings: [ + 'Please provide accurate information for verification purposes.', + ], +}; + +export const ROLE_OPTIONS = [ + 'Developer', + 'Designer', + 'Product Manager', + 'Project Manager', + 'QA', +]; + +export const NEW_STEP_LIMITS = { + stepOne: { + country: 1, + state: 1, + city: 1, + role: 1, + }, +}; diff --git a/app/styles/new-stepper.module.css b/app/styles/new-stepper.module.css new file mode 100644 index 00000000..e83fafc7 --- /dev/null +++ b/app/styles/new-stepper.module.css @@ -0,0 +1,289 @@ +.stepper__form { + max-width: 75%; + margin: 0 auto 3rem; + padding: 2rem; + background-color: var(--color-bgpink); + border-radius: 10px; +} + +.stepper-welcome { + display: flex; + flex-direction: column; + gap: 2rem; + justify-content: center; +} + +.stepper-welcome__logo { + display: flex; + justify-content: center; +} + +.stepper-welcome__info-item--bullet { + display: flex; + align-items: center; + margin-bottom: 1rem; + color: var(--color-black-light); + font-size: 1rem; + line-height: 1.5; +} + +.stepper-welcome__info-item--bullet::before { + content: "•"; + color: var(--color-pink); + font-size: 1.5rem; + font-weight: bold; + margin-right: 0.75rem; +} + +.stepper-welcome__checkbox { + display: flex; + align-items: center; + gap: 0.75rem; + cursor: pointer; +} + +.stepper-welcome__checkbox-text { + color: var(--color-black-light); + font-size: 0.9rem; + line-height: 1.4; +} + +.stepper-welcome__actions { + display: flex; + justify-content: center; +} + +.terms-modal__content { + background: var(--color-white); + border-radius: 10px; + padding: 2rem; + width: 90vw; + max-width: 500px; + max-height: 80vh; + overflow-y: scroll; + display: flex; + flex-direction: column; + gap: 2rem; +} + +.terms-modal__header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.terms-modal__header h2 { + font-size: 1.5rem; +} + +.terms-modal__close { + background-color: transparent !important; + margin: 0 !important; +} + +.terms-modal__close:hover { + background-color: var(--color-lightgrey) !important; + transition: all 0.3s ease-in-out; +} + +.terms-modal__section { + padding-block: 0.5rem; + display: flex; + flex-direction: column; + gap: 0.2rem; +} + +.terms-modal__footer { + display: flex; + justify-content: center; +} + +.form-header { + margin-bottom: 2rem; +} + +.form-header__content { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.form-header__text { + flex: 1; +} + +.form-header__heading { + font-size: 1.5rem; + font-weight: 600; + margin: 0 0 0.5rem; + color: #333; +} + +.form-header__subheading { + font-size: 1rem; + color: #666; + margin: 0; +} + +.form-header__step-count { + font-size: 0.9rem; + color: #888; + font-weight: 500; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #f0f0f0; + border-radius: 4px; + overflow: hidden; +} + +.progress-bar__fill { + height: 100%; + background: linear-gradient(90deg, #6366f1 0%, #ec4899 100%); + transition: width 0.3s ease; +} + +.two-column-layout { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + margin-bottom: 2rem; +} + +.two-column-layout__left, +.two-column-layout__right { + padding: 1rem; +} + +.section-heading { + font-size: 1.2rem; + font-weight: 600; + margin-bottom: 1rem; + color: #333; +} + +.section-instruction { + font-size: 0.9rem; + color: #666; + margin-bottom: 1rem; +} + +/* Image Upload Box */ +.image-upload-box { + border: 2px dashed #ccc; + border-radius: 8px; + padding: 2rem; + text-align: center; + background-color: #fafafa; + margin-bottom: 1rem; +} + +.image-upload-box__icon { + font-size: 2rem; + color: #999; + margin-bottom: 0.5rem; +} + +.image-upload-box__text { + color: #666; + margin: 0; +} + +.image-requirements { + font-size: 0.9rem; + color: #666; +} + +.image-requirements h4 { + margin: 0 0 0.5rem; + font-size: 1rem; + color: #333; +} + +.image-requirements ul { + margin: 0; + padding-left: 1.2rem; +} + +.image-requirements li { + margin-bottom: 0.25rem; +} + +.role-selection { + margin-top: 1rem; +} + +.role-selection h4 { + margin: 0 0 0.75rem; + font-size: 1rem; + color: #333; +} + +.role-buttons { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.role-button { + padding: 0.5rem 1rem; + border: 2px solid #e5e7eb; + background-color: white; + border-radius: 6px; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.2s ease; +} + +.role-button:hover { + border-color: #6366f1; + background-color: #f8fafc; +} + +.role-button--selected { + border-color: #6366f1; + background-color: #6366f1; + color: white; +} + +@media screen and (width <= 768px) { + .stepper__form { + max-width: 85%; + } + + .stepper-welcome { + margin: 1rem auto 3rem; + padding: 1.5rem; + } + + .stepper-welcome img { + width: 100px; + } + + .terms-modal__content { + width: 95vw; + margin: 1rem; + } +} + +@media (width <= 480px) { + .stepper__form { + max-width: 95%; + } + + .stepper-welcome { + margin: 0.5rem auto 3rem; + padding: 1rem; + } + + .stepper-welcome img { + width: 80px; + } + + .stepper-welcome__checkbox-text { + font-size: 0.9rem; + } +} From 6c6687f194daa5e3fa598456e7cf693f8d22317d Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Mon, 3 Nov 2025 21:06:00 +0530 Subject: [PATCH 02/35] fix: styling for form header --- .../new-join-steps/new-step-one.hbs | 1 + .../new-join-steps/stepper-header.hbs | 12 +++--- .../new-join-steps/stepper-header.js | 9 ++++- app/components/new-stepper.hbs | 2 +- app/components/reusables/input-box.hbs | 1 + app/styles/new-stepper.module.css | 40 +++++++++++++------ 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/app/components/new-join-steps/new-step-one.hbs b/app/components/new-join-steps/new-step-one.hbs index dabc85a1..cbb19edd 100644 --- a/app/components/new-join-steps/new-step-one.hbs +++ b/app/components/new-join-steps/new-step-one.hbs @@ -26,6 +26,7 @@ @name='fullName' @placeHolder='Full Name' @type='text' + @disabled={{true}} @required={{true}} @value={{this.data.fullName}} @onInput={{this.inputHandler}} diff --git a/app/components/new-join-steps/stepper-header.hbs b/app/components/new-join-steps/stepper-header.hbs index 026c7c8b..7fcd9438 100644 --- a/app/components/new-join-steps/stepper-header.hbs +++ b/app/components/new-join-steps/stepper-header.hbs @@ -1,14 +1,14 @@
-

{{@heading}}

-

{{@subheading}}

+

RDS Application Form

-
- Step {{@currentStep}} of {{@totalSteps}} +
+ {{@currentStep}} + of {{@totalSteps}}
-
-
+
+
\ No newline at end of file diff --git a/app/components/new-join-steps/stepper-header.js b/app/components/new-join-steps/stepper-header.js index 384da0c9..3c6e20c7 100644 --- a/app/components/new-join-steps/stepper-header.js +++ b/app/components/new-join-steps/stepper-header.js @@ -1,7 +1,14 @@ import Component from '@glimmer/component'; +import { htmlSafe } from '@ember/template'; export default class StepperHeaderComponent extends Component { get progressPercentage() { - return Math.round((this.args.currentStep / this.args.totalSteps) * 100); + const totalSteps = +this.args.totalSteps ?? 1; + const currentStep = +this.args.currentStep ?? 0; + return Math.round((currentStep / totalSteps) * 100); + } + + get progressStyle() { + return htmlSafe(`width: ${this.progressPercentage}%`); } } diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 280dae94..a31b32fc 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -5,7 +5,7 @@ @heading={{this.currentHeading}} @subheading={{this.currentSubheading}} @currentStep={{this.currentStep}} - @totalSteps={{6}} + @totalSteps={{this.MAX_STEP}} /> {{/if}} diff --git a/app/components/reusables/input-box.hbs b/app/components/reusables/input-box.hbs index 4d842bb4..30491d75 100644 --- a/app/components/reusables/input-box.hbs +++ b/app/components/reusables/input-box.hbs @@ -13,6 +13,7 @@ id={{@name}} placeholder={{@placeHolder}} required={{@required}} + disabled={{@disabled}} value={{@value}} {{on "input" @onInput}} /> diff --git a/app/styles/new-stepper.module.css b/app/styles/new-stepper.module.css index e83fafc7..7b203789 100644 --- a/app/styles/new-stepper.module.css +++ b/app/styles/new-stepper.module.css @@ -99,50 +99,56 @@ } .form-header { + width: 77.5%; margin-bottom: 2rem; } .form-header__content { display: flex; justify-content: space-between; - align-items: flex-start; + align-items: center; margin-bottom: 1rem; } -.form-header__text { - flex: 1; -} - .form-header__heading { font-size: 1.5rem; font-weight: 600; - margin: 0 0 0.5rem; - color: #333; } .form-header__subheading { - font-size: 1rem; - color: #666; + color: var(--color-lightgrey); margin: 0; } .form-header__step-count { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.form-header__step-current { + font-size: 1.75rem; + font-weight: 700; + color: var(--color-darkblack); +} + +.form-header__step-total { font-size: 0.9rem; - color: #888; - font-weight: 500; + color: var(--color-black-light); } .progress-bar { width: 100%; height: 8px; - background-color: #f0f0f0; + background-color: var(--color-pink-low-opacity); border-radius: 4px; overflow: hidden; } .progress-bar__fill { height: 100%; - background: linear-gradient(90deg, #6366f1 0%, #ec4899 100%); + background: var(--color-navyblue); + width: 0%; transition: width 0.3s ease; } @@ -250,6 +256,10 @@ } @media screen and (width <= 768px) { + .form-header { + width: 87.5%; + } + .stepper__form { max-width: 85%; } @@ -270,6 +280,10 @@ } @media (width <= 480px) { + .form-header { + width: 97.5%; + } + .stepper__form { max-width: 95%; } From 5ebca46b187fa855a370acad1980517f11d39da2 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Mon, 3 Nov 2025 23:49:53 +0530 Subject: [PATCH 03/35] fix: styling for image box and input boxes --- .../new-join-steps/new-step-one.hbs | 24 +++---- app/components/new-join-steps/new-step-one.js | 2 + app/styles/new-stepper.module.css | 71 ++++++++++--------- 3 files changed, 50 insertions(+), 47 deletions(-) diff --git a/app/components/new-join-steps/new-step-one.hbs b/app/components/new-join-steps/new-step-one.hbs index cbb19edd..f11399a1 100644 --- a/app/components/new-join-steps/new-step-one.hbs +++ b/app/components/new-join-steps/new-step-one.hbs @@ -2,7 +2,7 @@

Profile Picture

-
+

Upload Image

@@ -11,15 +11,15 @@
  • Must be a real, clear photograph (no anime, filters, or drawings)
  • Must contain exactly one face
  • Face must cover at least 60% of the image
  • -
  • Supported formats: JPG, PNG, GIF
  • -
  • Image will be validated during analysis
  • +
  • Supported formats: JPG, PNG
  • +
  • Image will be validated before moving to next step
  • Personal Details

    -

    Please provide real name and details, any spam/incorrect details will lead to rejection.

    +

    Please provide correct details and choose your role carefully, it won't be changed later.

    -
    -

    Applying as:

    +

    Applying as

    {{#each this.roleOptions as |role|}}
    {{/if}} From 77f0031f8990b7809afa5fd72c29f6256fac09a7 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 4 Nov 2025 18:17:11 +0530 Subject: [PATCH 06/35] feat: add base step component for all new steps --- app/components/new-join-steps/base-step.js | 102 ++++++++++++++++++ app/components/new-join-steps/new-step-one.js | 80 +++----------- .../new-join-steps/new-step-three.hbs | 38 +++++++ .../new-join-steps/new-step-three.js | 14 +++ .../new-join-steps/new-step-two.hbs | 54 ++++++++++ app/components/new-join-steps/new-step-two.js | 12 +++ app/components/new-stepper.js | 9 ++ 7 files changed, 246 insertions(+), 63 deletions(-) create mode 100644 app/components/new-join-steps/base-step.js create mode 100644 app/components/new-join-steps/new-step-three.hbs create mode 100644 app/components/new-join-steps/new-step-three.js create mode 100644 app/components/new-join-steps/new-step-two.hbs create mode 100644 app/components/new-join-steps/new-step-two.js diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js new file mode 100644 index 00000000..25af01dd --- /dev/null +++ b/app/components/new-join-steps/base-step.js @@ -0,0 +1,102 @@ +import { action } from '@ember/object'; +import { debounce } from '@ember/runloop'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; +import { validateWordCount } from '../../utils/validator'; + +export default class BaseStepComponent extends Component { + storageKey = ''; + validationMap = {}; + + @tracked data = {}; + @tracked errorMessage = {}; + @tracked wordCount = {}; + + isValid; + setIsValid; + setIsPreValid; + + constructor(...args) { + super(...args); + this.isValid = this.args.isValid; + this.setIsValid = this.args.setIsValid; + this.setIsPreValid = this.args.setIsPreValid; + + const saved = JSON.parse(localStorage.getItem(this.storageKey) || 'null'); + this.data = saved ?? {}; + + this.errorMessage = Object.fromEntries( + Object.keys(this.validationMap).map((key) => [key, '']), + ); + + this.wordCount = Object.fromEntries( + Object.keys(this.validationMap).map((key) => { + const value = this?.data?.[key] ?? ''; + return [key, value?.trim()?.split(/\s+/).filter(Boolean).length ?? 0]; + }), + ); + + const validated = this.isDataValid(); + localStorage.setItem('isValid', validated); + this.setIsPreValid(validated); + } + + @action inputHandler(e) { + this.setIsPreValid(false); + const field = e.target.name; + const value = e.target.value; + debounce(this, this.handleFieldUpdate, field, value, JOIN_DEBOUNCE_TIME); + } + + validateField(field, value) { + const limits = this.validationMap[field]; + return validateWordCount(value, limits); + } + + isDataValid() { + for (let field of Object.keys(this.validationMap)) { + const result = this.validateField(field, this.data[field]); + if (!result.isValid) return false; + } + return true; + } + + handleFieldUpdate(field, value) { + this.updateFieldValue(field, value); + const result = this.validateField(field, value); + this.updateWordCount(field, value, result); + this.updateErrorMessage(field, result); + this.syncFormValidity(); + } + + updateFieldValue(field, value) { + this.data = { ...this.data, [field]: value }; + localStorage.setItem(this.storageKey, JSON.stringify(this.data)); + } + + updateWordCount(field, value, result) { + const wordCount = + result.wordCount ?? + value?.trim()?.split(/\s+/).filter(Boolean).length ?? + 0; + this.wordCount = { ...this.wordCount, [field]: wordCount }; + } + + updateErrorMessage(field, result) { + this.errorMessage = { + ...this.errorMessage, + [field]: this.formatError(field, result), + }; + } + + formatError(field, result) { + const limits = this.validationMap[field]; + if (result.isValid) return ''; + if (result.remainingToMin) { + return `At least ${result.remainingToMin} more word(s) required`; + } + const max = limits.max; + return `Maximum ${max} words allowed`; + } +} diff --git a/app/components/new-join-steps/new-step-one.js b/app/components/new-join-steps/new-step-one.js index 18520bd4..035fbf02 100644 --- a/app/components/new-join-steps/new-step-one.js +++ b/app/components/new-join-steps/new-step-one.js @@ -1,83 +1,37 @@ -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; -import { action } from '@ember/object'; -import { validator } from '../../utils/validator'; -import { debounce } from '@ember/runloop'; -import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; +import BaseStepComponent from './base-step'; import { NEW_STEP_LIMITS, ROLE_OPTIONS } from '../../constants/new-join-form'; import { countryList } from '../../constants/country-list'; import { inject as service } from '@ember/service'; -export default class NewStepOneComponent extends Component { +export default class NewStepOneComponent extends BaseStepComponent { @service login; - @tracked data = JSON.parse(localStorage.getItem('newStepOneData')) ?? { - fullName: '', - country: '', - state: '', - city: '', - role: '', + storageKey = 'newStepOneData'; + validationMap = { + country: NEW_STEP_LIMITS.stepOne.country, + state: NEW_STEP_LIMITS.stepOne.state, + city: NEW_STEP_LIMITS.stepOne.city, + role: NEW_STEP_LIMITS.stepOne.role, }; - isValid; - setIsValid; - setIsPreValid; roleOptions = ROLE_OPTIONS; countries = countryList; constructor(...args) { super(...args); - - this.isValid = this.args.isValid; - this.setIsValid = this.args.setIsValid; - this.setIsPreValid = this.args.setIsPreValid; - - if (this.login.userData) { - this.data.fullName = `${this.login.userData.first_name} ${this.login.userData.last_name}`; - } - - const validated = this.isDataValid(); - localStorage.setItem('isValid', validated); - this.setIsPreValid(validated); - } - - isDataValid() { - for (let field in this.data) { - if (field === 'role') { - if (!this.data[field]) return false; - } else { - const { isValid } = validator( - this.data[field], - NEW_STEP_LIMITS.stepOne[field], - ); - if (!isValid) { - return false; - } - } - } - return true; - } - - @action inputHandler(e) { - this.setIsPreValid(false); - const setValToLocalStorage = () => { - this.data = { ...this.data, [e.target.name]: e.target.value }; - localStorage.setItem('newStepOneData', JSON.stringify(this.data)); - localStorage.setItem('selectedRole', this.data.role); + if (this.login.userData && !this.data.fullName) { + this.data = { + ...this.data, + fullName: `${this.login.userData.first_name} ${this.login.userData.last_name}`, + }; + localStorage.setItem(this.storageKey, JSON.stringify(this.data)); const validated = this.isDataValid(); this.setIsValid(validated); localStorage.setItem('isValid', validated); - }; - debounce(this.data, setValToLocalStorage, JOIN_DEBOUNCE_TIME); + } } - @action selectRole(role) { - this.setIsPreValid(false); - this.data = { ...this.data, role }; - localStorage.setItem('newStepOneData', JSON.stringify(this.data)); - localStorage.setItem('selectedRole', role); - const validated = this.isDataValid(); - this.setIsValid(validated); - localStorage.setItem('isValid', validated); + selectRole(role) { + this.inputHandler({ target: { name: 'role', value: role } }); } } diff --git a/app/components/new-join-steps/new-step-three.hbs b/app/components/new-join-steps/new-step-three.hbs new file mode 100644 index 00000000..43d8c901 --- /dev/null +++ b/app/components/new-join-steps/new-step-three.hbs @@ -0,0 +1,38 @@ +
    +

    {{@heading}}

    +

    {{@subHeading}}

    +
    + +
    +
    +

    What do you do for fun? Your hobbies/interests?

    + +
    {{this.wordCount.hobbies}}/{{this.maxWords.hobbies}} words
    + {{#if this.errorMessage.hobbies}} +
    {{this.errorMessage.hobbies}}
    + {{/if}} +
    + +
    +

    Fun fact about you

    + +
    {{this.wordCount.funFact}}/{{this.maxWords.funFact}} words
    + {{#if this.errorMessage.funFact}} +
    {{this.errorMessage.funFact}}
    + {{/if}} +
    +
    \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-three.js b/app/components/new-join-steps/new-step-three.js new file mode 100644 index 00000000..9b88406f --- /dev/null +++ b/app/components/new-join-steps/new-step-three.js @@ -0,0 +1,14 @@ +import BaseStepComponent from './base-step'; +import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; + +export default class NewStepThreeComponent extends BaseStepComponent { + storageKey = 'newStepThreeData'; + validationMap = { + hobbies: NEW_STEP_LIMITS.stepThree.hobbies, + funFact: NEW_STEP_LIMITS.stepThree.funFact, + }; + maxWords = { + hobbies: NEW_STEP_LIMITS.stepThree.hobbies.max, + funFact: NEW_STEP_LIMITS.stepThree.funFact.max, + }; +} diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs new file mode 100644 index 00000000..4d947d99 --- /dev/null +++ b/app/components/new-join-steps/new-step-two.hbs @@ -0,0 +1,54 @@ +
    +
    +

    {{@heading}}

    +

    {{@subHeading}}

    +
    + +
    +
    + + {{#if this.errorMessage.skills}} +
    {{this.errorMessage.skills}}
    + {{/if}} +
    + +
    + + {{#if this.errorMessage.company}} +
    {{this.errorMessage.company}}
    + {{/if}} +
    +
    + +

    Please introduce yourself

    + +
    {{this.wordCount.introduction}}/{{this.maxWords.introduction}} words
    +

    Share your background, experiences, and what drives you.

    + + {{#if this.errorMessage.introduction}} +
    {{this.errorMessage.introduction}}
    + {{/if}} +
    \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-two.js b/app/components/new-join-steps/new-step-two.js new file mode 100644 index 00000000..813ed5a4 --- /dev/null +++ b/app/components/new-join-steps/new-step-two.js @@ -0,0 +1,12 @@ +import BaseStepComponent from './base-step'; +import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; + +export default class NewStepTwoComponent extends BaseStepComponent { + storageKey = 'newStepTwoData'; + validationMap = { + skills: NEW_STEP_LIMITS.stepTwo.skills, + company: NEW_STEP_LIMITS.stepTwo.company, + introduction: NEW_STEP_LIMITS.stepTwo.introduction, + }; + maxWords = { introduction: NEW_STEP_LIMITS.stepTwo.introduction.max }; +} diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index 472c8e7f..17497728 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -2,6 +2,7 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; +import { NEW_FORM_STEPS } from '../constants/new-join-form'; export default class NewStepperComponent extends Component { MIN_STEP = 0; @@ -30,6 +31,14 @@ export default class NewStepperComponent extends Component { return this.currentStep > this.MIN_STEP + 1; } + get currentHeading() { + return NEW_FORM_STEPS.headings[this.currentStep - 1] ?? ''; + } + + get currentSubheading() { + return NEW_FORM_STEPS.subheadings[this.currentStep - 1] ?? ''; + } + @action incrementStep() { if (this.currentStep < this.MAX_STEP) { const nextStep = this.currentStep + 1; From aff01fa5d9f005e2ed253ec8cece7e5fd7a7d82e Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 4 Nov 2025 21:00:03 +0530 Subject: [PATCH 07/35] fix: input validation in new step one --- app/components/new-join-steps/base-step.js | 45 +++++++++++-------- app/components/new-join-steps/new-step-one.js | 35 +++++++-------- app/components/new-stepper.hbs | 1 - app/constants/new-join-form.js | 8 ++-- app/utils/validator.js | 17 +++++++ 5 files changed, 65 insertions(+), 41 deletions(-) diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js index 25af01dd..f313305d 100644 --- a/app/components/new-join-steps/base-step.js +++ b/app/components/new-join-steps/base-step.js @@ -4,46 +4,50 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; import { validateWordCount } from '../../utils/validator'; +import { scheduleOnce } from '@ember/runloop'; export default class BaseStepComponent extends Component { - storageKey = ''; validationMap = {}; @tracked data = {}; @tracked errorMessage = {}; @tracked wordCount = {}; - isValid; - setIsValid; - setIsPreValid; + get storageKey() { + return ''; + } + + postLoadInitialize() {} constructor(...args) { super(...args); - this.isValid = this.args.isValid; - this.setIsValid = this.args.setIsValid; - this.setIsPreValid = this.args.setIsPreValid; + scheduleOnce('afterRender', this, this.initializeFormState); + } - const saved = JSON.parse(localStorage.getItem(this.storageKey) || 'null'); + initializeFormState() { + const saved = JSON.parse(localStorage.getItem(this.storageKey) || '{}'); this.data = saved ?? {}; this.errorMessage = Object.fromEntries( - Object.keys(this.validationMap).map((key) => [key, '']), + Object.keys(this.validationMap).map((k) => [k, '']), ); this.wordCount = Object.fromEntries( - Object.keys(this.validationMap).map((key) => { - const value = this?.data?.[key] ?? ''; - return [key, value?.trim()?.split(/\s+/).filter(Boolean).length ?? 0]; + Object.keys(this.validationMap).map((k) => { + let val = this.data[k] || ''; + return [k, val.trim().split(/\s+/).filter(Boolean).length || 0]; }), ); - const validated = this.isDataValid(); - localStorage.setItem('isValid', validated); - this.setIsPreValid(validated); + this.postLoadInitialize(); + + const valid = this.isDataValid(); + this.args.setIsPreValid(valid); + localStorage.setItem('isValid', valid); } @action inputHandler(e) { - this.setIsPreValid(false); + this.args.setIsPreValid(false); const field = e.target.name; const value = e.target.value; debounce(this, this.handleFieldUpdate, field, value, JOIN_DEBOUNCE_TIME); @@ -96,7 +100,12 @@ export default class BaseStepComponent extends Component { if (result.remainingToMin) { return `At least ${result.remainingToMin} more word(s) required`; } - const max = limits.max; - return `Maximum ${max} words allowed`; + return `Maximum ${limits.max} words allowed`; + } + + syncFormValidity() { + const allValid = this.isDataValid(); + this.args.setIsValid(allValid); + localStorage.setItem('isValid', allValid); } } diff --git a/app/components/new-join-steps/new-step-one.js b/app/components/new-join-steps/new-step-one.js index 035fbf02..8b800c4f 100644 --- a/app/components/new-join-steps/new-step-one.js +++ b/app/components/new-join-steps/new-step-one.js @@ -1,12 +1,19 @@ -import BaseStepComponent from './base-step'; -import { NEW_STEP_LIMITS, ROLE_OPTIONS } from '../../constants/new-join-form'; -import { countryList } from '../../constants/country-list'; +import { action } from '@ember/object'; import { inject as service } from '@ember/service'; +import { countryList } from '../../constants/country-list'; +import { NEW_STEP_LIMITS, ROLE_OPTIONS } from '../../constants/new-join-form'; +import BaseStepComponent from './base-step'; export default class NewStepOneComponent extends BaseStepComponent { @service login; - storageKey = 'newStepOneData'; + roleOptions = ROLE_OPTIONS; + countries = countryList; + + get storageKey() { + return 'newStepOneData'; + } + validationMap = { country: NEW_STEP_LIMITS.stepOne.country, state: NEW_STEP_LIMITS.stepOne.state, @@ -14,24 +21,16 @@ export default class NewStepOneComponent extends BaseStepComponent { role: NEW_STEP_LIMITS.stepOne.role, }; - roleOptions = ROLE_OPTIONS; - countries = countryList; - - constructor(...args) { - super(...args); + postLoadInitialize() { if (this.login.userData && !this.data.fullName) { - this.data = { - ...this.data, - fullName: `${this.login.userData.first_name} ${this.login.userData.last_name}`, - }; - localStorage.setItem(this.storageKey, JSON.stringify(this.data)); - const validated = this.isDataValid(); - this.setIsValid(validated); - localStorage.setItem('isValid', validated); + this.updateFieldValue( + 'fullName', + `${this.login.userData.first_name} ${this.login.userData.last_name}`, + ); } } - selectRole(role) { + @action selectRole(role) { this.inputHandler({ target: { name: 'role', value: role } }); } } diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index aaf5c2bf..99fce813 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -31,7 +31,6 @@ @setIsValid={{this.setIsValid}} /> {{/if}} - {{#if (not-eq this.currentStep this.MIN_STEP)}} diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 0b6d57ea..e7f47846 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -15,9 +15,9 @@ export const ROLE_OPTIONS = [ export const NEW_STEP_LIMITS = { stepOne: { - country: 1, - state: 1, - city: 1, - role: 1, + country: { min: 1 }, + state: { min: 1 }, + city: { min: 1 }, + role: { min: 1 }, }, }; diff --git a/app/utils/validator.js b/app/utils/validator.js index ec26bdea..13927746 100644 --- a/app/utils/validator.js +++ b/app/utils/validator.js @@ -13,3 +13,20 @@ export const validator = (value, words) => { remainingWords: remainingWords, }; }; + +export const validateWordCount = (text, wordLimits) => { + const trimmedText = text?.trim(); + const wordCount = trimmedText?.split(/\s+/)?.length ?? 0; + + const { min, max } = wordLimits; + if (!trimmedText) { + return { isValid: false, wordCount: 0, remainingToMin: min }; + } + + if (wordCount < min) { + return { isValid: false, wordCount, remainingToMin: min - wordCount }; + } else if (max && wordCount > max) { + return { isValid: false, wordCount, overByMax: wordCount - max }; + } + return { isValid: true, wordCount }; +}; From e0e1d3ba7fe32cd0acb30a0b4a2696d96039f086 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Thu, 6 Nov 2025 22:39:22 +0530 Subject: [PATCH 08/35] refactor: improve progress calculation and update header text --- app/components/new-join-steps/stepper-header.js | 7 +++++-- app/constants/new-join-form.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/components/new-join-steps/stepper-header.js b/app/components/new-join-steps/stepper-header.js index 3c6e20c7..a9cb4c1d 100644 --- a/app/components/new-join-steps/stepper-header.js +++ b/app/components/new-join-steps/stepper-header.js @@ -1,13 +1,16 @@ import Component from '@glimmer/component'; import { htmlSafe } from '@ember/template'; +import { cached } from '@glimmer/tracking'; export default class StepperHeaderComponent extends Component { + @cached get progressPercentage() { - const totalSteps = +this.args.totalSteps ?? 1; - const currentStep = +this.args.currentStep ?? 0; + const totalSteps = Number(this.args.totalSteps) ?? 1; + const currentStep = Number(this.args.currentStep) ?? 0; return Math.round((currentStep / totalSteps) * 100); } + @cached get progressStyle() { return htmlSafe(`width: ${this.progressPercentage}%`); } diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index e7f47846..1480c45a 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -1,5 +1,5 @@ export const NEW_FORM_STEPS = { - headings: ['Upload Professional HeadShot and fill up personal details'], + headings: ['Upload Professional Headshot and Complete Personal Details'], subheadings: [ 'Please provide accurate information for verification purposes.', ], From a90a012e6ca91ebe5c7b1dcfbd051cce69995202 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Sat, 8 Nov 2025 17:20:13 +0530 Subject: [PATCH 09/35] refactor: improve step navigation --- app/components/new-join-steps/base-step.js | 9 +++------ app/components/new-join-steps/stepper-header.js | 4 ++-- app/components/new-stepper.hbs | 4 ++-- app/components/new-stepper.js | 8 ++++++++ app/utils/validator.js | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js index f313305d..07d073fe 100644 --- a/app/components/new-join-steps/base-step.js +++ b/app/components/new-join-steps/base-step.js @@ -69,7 +69,7 @@ export default class BaseStepComponent extends Component { handleFieldUpdate(field, value) { this.updateFieldValue(field, value); const result = this.validateField(field, value); - this.updateWordCount(field, value, result); + this.updateWordCount(field, result); this.updateErrorMessage(field, result); this.syncFormValidity(); } @@ -79,11 +79,8 @@ export default class BaseStepComponent extends Component { localStorage.setItem(this.storageKey, JSON.stringify(this.data)); } - updateWordCount(field, value, result) { - const wordCount = - result.wordCount ?? - value?.trim()?.split(/\s+/).filter(Boolean).length ?? - 0; + updateWordCount(field, result) { + const wordCount = result.wordCount ?? 0; this.wordCount = { ...this.wordCount, [field]: wordCount }; } diff --git a/app/components/new-join-steps/stepper-header.js b/app/components/new-join-steps/stepper-header.js index a9cb4c1d..9b81fd3c 100644 --- a/app/components/new-join-steps/stepper-header.js +++ b/app/components/new-join-steps/stepper-header.js @@ -5,8 +5,8 @@ import { cached } from '@glimmer/tracking'; export default class StepperHeaderComponent extends Component { @cached get progressPercentage() { - const totalSteps = Number(this.args.totalSteps) ?? 1; - const currentStep = Number(this.args.currentStep) ?? 0; + const totalSteps = Number(this.args.totalSteps) || 1; + const currentStep = Number(this.args.currentStep) || 0; return Math.round((currentStep / totalSteps) * 100); } diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 99fce813..9c6ae4de 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -9,7 +9,7 @@ /> {{/if}} -
    + {{#if (eq this.currentStep this.MIN_STEP)}} @@ -34,7 +34,7 @@ {{#if (not-eq this.currentStep this.MIN_STEP)}} -
    +
    {{#if this.showPreviousButton}} (this.isValid = newVal); + setIsPreValid = (newVal) => (this.preValid = newVal); + updateQueryParam(step) { const existingQueryParams = this.router.currentRoute?.queryParams; @@ -43,6 +49,7 @@ export default class NewStepperComponent extends Component { if (this.currentStep < this.MAX_STEP) { const nextStep = this.currentStep + 1; localStorage.setItem('currentStep', String(nextStep)); + this.currentStep = nextStep; this.updateQueryParam(nextStep); } } @@ -51,6 +58,7 @@ export default class NewStepperComponent extends Component { if (this.currentStep > this.MIN_STEP) { const previousStep = this.currentStep - 1; localStorage.setItem('currentStep', String(previousStep)); + this.currentStep = previousStep; this.updateQueryParam(previousStep); } } diff --git a/app/utils/validator.js b/app/utils/validator.js index 13927746..d1cdd428 100644 --- a/app/utils/validator.js +++ b/app/utils/validator.js @@ -16,7 +16,7 @@ export const validator = (value, words) => { export const validateWordCount = (text, wordLimits) => { const trimmedText = text?.trim(); - const wordCount = trimmedText?.split(/\s+/)?.length ?? 0; + const wordCount = trimmedText?.split(/\s+/).filter(Boolean).length ?? 0; const { min, max } = wordLimits; if (!trimmedText) { From e5edda0e2436fbaa0315f8858abff00e8eb61a45 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Sat, 8 Nov 2025 18:41:13 +0530 Subject: [PATCH 10/35] fix: image upload preview and update storage keys --- app/components/new-join-steps/base-step.js | 12 +- .../new-join-steps/new-step-one.hbs | 31 +- app/components/new-join-steps/new-step-one.js | 43 ++- .../new-join-steps/new-step-three.hbs | 38 --- .../new-join-steps/new-step-three.js | 14 - .../new-join-steps/new-step-two.hbs | 54 --- app/components/new-join-steps/new-step-two.js | 12 - app/constants/new-join-form.js | 5 + app/styles/new-stepper.module.css | 321 ------------------ 9 files changed, 77 insertions(+), 453 deletions(-) delete mode 100644 app/components/new-join-steps/new-step-three.hbs delete mode 100644 app/components/new-join-steps/new-step-three.js delete mode 100644 app/components/new-join-steps/new-step-two.hbs delete mode 100644 app/components/new-join-steps/new-step-two.js delete mode 100644 app/styles/new-stepper.module.css diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js index 07d073fe..a6935cae 100644 --- a/app/components/new-join-steps/base-step.js +++ b/app/components/new-join-steps/base-step.js @@ -7,7 +7,7 @@ import { validateWordCount } from '../../utils/validator'; import { scheduleOnce } from '@ember/runloop'; export default class BaseStepComponent extends Component { - validationMap = {}; + stepValidation = {}; @tracked data = {}; @tracked errorMessage = {}; @@ -29,11 +29,11 @@ export default class BaseStepComponent extends Component { this.data = saved ?? {}; this.errorMessage = Object.fromEntries( - Object.keys(this.validationMap).map((k) => [k, '']), + Object.keys(this.stepValidation).map((k) => [k, '']), ); this.wordCount = Object.fromEntries( - Object.keys(this.validationMap).map((k) => { + Object.keys(this.stepValidation).map((k) => { let val = this.data[k] || ''; return [k, val.trim().split(/\s+/).filter(Boolean).length || 0]; }), @@ -54,12 +54,12 @@ export default class BaseStepComponent extends Component { } validateField(field, value) { - const limits = this.validationMap[field]; + const limits = this.stepValidation[field]; return validateWordCount(value, limits); } isDataValid() { - for (let field of Object.keys(this.validationMap)) { + for (let field of Object.keys(this.stepValidation)) { const result = this.validateField(field, this.data[field]); if (!result.isValid) return false; } @@ -92,7 +92,7 @@ export default class BaseStepComponent extends Component { } formatError(field, result) { - const limits = this.validationMap[field]; + const limits = this.stepValidation[field]; if (result.isValid) return ''; if (result.remainingToMin) { return `At least ${result.remainingToMin} more word(s) required`; diff --git a/app/components/new-join-steps/new-step-one.hbs b/app/components/new-join-steps/new-step-one.hbs index f11399a1..c175fcf2 100644 --- a/app/components/new-join-steps/new-step-one.hbs +++ b/app/components/new-join-steps/new-step-one.hbs @@ -1,10 +1,33 @@

    Profile Picture

    -
    - -

    Upload Image

    -
    + {{#if this.imagePreview}} +
    + Profile preview + +
    + {{else}} + + {{/if}} +

    Image Requirements:

      diff --git a/app/components/new-join-steps/new-step-one.js b/app/components/new-join-steps/new-step-one.js index 8b800c4f..6e3864d2 100644 --- a/app/components/new-join-steps/new-step-one.js +++ b/app/components/new-join-steps/new-step-one.js @@ -1,7 +1,12 @@ import { action } from '@ember/object'; import { inject as service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; import { countryList } from '../../constants/country-list'; -import { NEW_STEP_LIMITS, ROLE_OPTIONS } from '../../constants/new-join-form'; +import { + NEW_STEP_LIMITS, + ROLE_OPTIONS, + STEP_DATA_STORAGE_KEY, +} from '../../constants/new-join-form'; import BaseStepComponent from './base-step'; export default class NewStepOneComponent extends BaseStepComponent { @@ -10,11 +15,13 @@ export default class NewStepOneComponent extends BaseStepComponent { roleOptions = ROLE_OPTIONS; countries = countryList; + @tracked imagePreview = null; + get storageKey() { - return 'newStepOneData'; + return STEP_DATA_STORAGE_KEY.stepOne; } - validationMap = { + stepValidation = { country: NEW_STEP_LIMITS.stepOne.country, state: NEW_STEP_LIMITS.stepOne.state, city: NEW_STEP_LIMITS.stepOne.city, @@ -22,15 +29,43 @@ export default class NewStepOneComponent extends BaseStepComponent { }; postLoadInitialize() { - if (this.login.userData && !this.data.fullName) { + if (!this.data.fullName && this.login.userData) { this.updateFieldValue( 'fullName', `${this.login.userData.first_name} ${this.login.userData.last_name}`, ); } + if (this.data.profileImageBase64) { + this.imagePreview = this.data.profileImageBase64; + } } @action selectRole(role) { this.inputHandler({ target: { name: 'role', value: role } }); } + + @action triggerFileInput() { + const fileInput = document.getElementById('profile-image-input'); + if (fileInput) { + fileInput.click(); + } + } + + @action handleImageSelect(event) { + const file = event.target.files?.[0]; + if (!file) return; + + if (file.size > 2 * 1024 * 1024) { + alert('Image size must be less than 2MB'); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + const base64String = e.target.result; + this.imagePreview = base64String; + this.updateFieldValue('profileImageBase64', base64String); + }; + reader.readAsDataURL(file); + } } diff --git a/app/components/new-join-steps/new-step-three.hbs b/app/components/new-join-steps/new-step-three.hbs deleted file mode 100644 index 43d8c901..00000000 --- a/app/components/new-join-steps/new-step-three.hbs +++ /dev/null @@ -1,38 +0,0 @@ -
      -

      {{@heading}}

      -

      {{@subHeading}}

      -
      - -
      -
      -

      What do you do for fun? Your hobbies/interests?

      - -
      {{this.wordCount.hobbies}}/{{this.maxWords.hobbies}} words
      - {{#if this.errorMessage.hobbies}} -
      {{this.errorMessage.hobbies}}
      - {{/if}} -
      - -
      -

      Fun fact about you

      - -
      {{this.wordCount.funFact}}/{{this.maxWords.funFact}} words
      - {{#if this.errorMessage.funFact}} -
      {{this.errorMessage.funFact}}
      - {{/if}} -
      -
      \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-three.js b/app/components/new-join-steps/new-step-three.js deleted file mode 100644 index 9b88406f..00000000 --- a/app/components/new-join-steps/new-step-three.js +++ /dev/null @@ -1,14 +0,0 @@ -import BaseStepComponent from './base-step'; -import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; - -export default class NewStepThreeComponent extends BaseStepComponent { - storageKey = 'newStepThreeData'; - validationMap = { - hobbies: NEW_STEP_LIMITS.stepThree.hobbies, - funFact: NEW_STEP_LIMITS.stepThree.funFact, - }; - maxWords = { - hobbies: NEW_STEP_LIMITS.stepThree.hobbies.max, - funFact: NEW_STEP_LIMITS.stepThree.funFact.max, - }; -} diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs deleted file mode 100644 index 4d947d99..00000000 --- a/app/components/new-join-steps/new-step-two.hbs +++ /dev/null @@ -1,54 +0,0 @@ -
      -
      -

      {{@heading}}

      -

      {{@subHeading}}

      -
      - -
      -
      - - {{#if this.errorMessage.skills}} -
      {{this.errorMessage.skills}}
      - {{/if}} -
      - -
      - - {{#if this.errorMessage.company}} -
      {{this.errorMessage.company}}
      - {{/if}} -
      -
      - -

      Please introduce yourself

      - -
      {{this.wordCount.introduction}}/{{this.maxWords.introduction}} words
      -

      Share your background, experiences, and what drives you.

      - - {{#if this.errorMessage.introduction}} -
      {{this.errorMessage.introduction}}
      - {{/if}} -
      \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-two.js b/app/components/new-join-steps/new-step-two.js deleted file mode 100644 index 813ed5a4..00000000 --- a/app/components/new-join-steps/new-step-two.js +++ /dev/null @@ -1,12 +0,0 @@ -import BaseStepComponent from './base-step'; -import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; - -export default class NewStepTwoComponent extends BaseStepComponent { - storageKey = 'newStepTwoData'; - validationMap = { - skills: NEW_STEP_LIMITS.stepTwo.skills, - company: NEW_STEP_LIMITS.stepTwo.company, - introduction: NEW_STEP_LIMITS.stepTwo.introduction, - }; - maxWords = { introduction: NEW_STEP_LIMITS.stepTwo.introduction.max }; -} diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 1480c45a..6911a135 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -11,6 +11,7 @@ export const ROLE_OPTIONS = [ 'Product Manager', 'Project Manager', 'QA', + 'Social Media', ]; export const NEW_STEP_LIMITS = { @@ -21,3 +22,7 @@ export const NEW_STEP_LIMITS = { role: { min: 1 }, }, }; + +export const STEP_DATA_STORAGE_KEY = { + stepOne: 'newStepOneData', +}; diff --git a/app/styles/new-stepper.module.css b/app/styles/new-stepper.module.css deleted file mode 100644 index 3a48c261..00000000 --- a/app/styles/new-stepper.module.css +++ /dev/null @@ -1,321 +0,0 @@ -.stepper__form { - max-width: 75%; - margin: 0 auto 3rem; - padding: 2rem; - background-color: var(--color-bgpink); - border-radius: 10px; -} - -.stepper__buttons { - width: 77.5%; - display: flex; - justify-content: space-between; - align-items: center; - margin: 0; -} - -.stepper-welcome { - display: flex; - flex-direction: column; - gap: 2rem; - justify-content: center; -} - -.stepper-welcome__logo { - display: flex; - justify-content: center; -} - -.stepper-welcome__info-item--bullet { - display: flex; - align-items: center; - margin-bottom: 1rem; - color: var(--color-black-light); - font-size: 1rem; - line-height: 1.5; -} - -.stepper-welcome__info-item--bullet::before { - content: "•"; - color: var(--color-pink); - font-size: 1.5rem; - font-weight: bold; - margin-right: 0.75rem; -} - -.stepper-welcome__checkbox { - display: flex; - align-items: center; - gap: 0.75rem; - cursor: pointer; -} - -.stepper-welcome__checkbox-text { - color: var(--color-black-light); - font-size: 0.9rem; - line-height: 1.4; -} - -.stepper-welcome__actions { - margin-top: 2rem; - display: flex; - justify-content: center; -} - -.terms-modal__content { - background: var(--color-white); - border-radius: 10px; - padding: 2rem; - width: 90vw; - max-width: 500px; - max-height: 80vh; - overflow-y: scroll; - display: flex; - flex-direction: column; - gap: 2rem; -} - -.terms-modal__header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.terms-modal__header h2 { - font-size: 1.5rem; -} - -.terms-modal__close { - background-color: transparent !important; - margin: 0 !important; -} - -.terms-modal__close:hover { - background-color: var(--color-lightgrey) !important; - transition: all 0.3s ease-in-out; -} - -.terms-modal__section { - padding-block: 0.5rem; - display: flex; - flex-direction: column; - gap: 0.2rem; -} - -.terms-modal__footer { - display: flex; - justify-content: center; -} - -.form-header { - width: 77.5%; - margin-bottom: 2rem; -} - -.form-header__content { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; -} - -.form-header__heading { - font-size: 1.5rem; - font-weight: 600; -} - -.form-header__subheading { - color: var(--color-lightgrey); - margin: 0; -} - -.form-header__step-count { - display: flex; - flex-direction: column; - align-items: flex-end; -} - -.form-header__step-current { - font-size: 1.75rem; - font-weight: 700; - color: var(--color-darkblack); -} - -.form-header__step-total { - font-size: 0.9rem; - color: var(--color-black-light); -} - -.progress-bar { - width: 100%; - height: 8px; - background-color: var(--color-pink-low-opacity); - border-radius: 4px; - overflow: hidden; -} - -.progress-bar__fill { - height: 100%; - background: var(--color-navyblue); - width: 0%; - transition: width 0.3s ease; -} - -.two-column-layout { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 2rem; -} - -.two-column-layout__left { - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -.section-heading { - font-size: 1.2rem; - font-weight: 600; - margin-bottom: 0.25rem; - color: var(--color-black); -} - -.section-instruction { - font-size: 0.9rem; - color: var(--color-black-light); -} - -.image-upload-box { - border: 2px dashed var(--color-lightgrey); - border-radius: 10px; - padding: 2rem; - text-align: center; - background-color: var(--color-white); -} - -.image-upload-box:hover { - border-color: var(--color-navyblue); - cursor: pointer; - transition: all 0.3s ease-in-out; -} - -.image-requirements { - font-size: 0.9rem; - color: var(--color-black-light); -} - -.image-requirements h4 { - margin: 0 0 0.5rem; - font-size: 1rem; - color: var(--color-black-light); -} - -.image-requirements ul { - margin: 0; - padding-left: 1.2rem; -} - -.image-requirements li { - margin-bottom: 0.25rem; -} - -.role-selection { - margin-top: 1rem; -} - -.role-selection p { - margin: 0 0 0.5rem; - font-size: 1rem; - font-weight: 500; - color: var(--color-black); -} - -.role-buttons { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; -} - -.role-button { - padding: 0.5rem 1rem; - border: 2px solid var(--profile-field-input-border-clr); - background-color: var(--color-white); - border-radius: 6px; - cursor: pointer; - font-size: 0.9rem; -} - -.role-button:hover { - border-color: var(--color-navyblue); - background-color: var(--color-offwhite); - transition: all 0.3s ease; -} - -.role-button--selected { - border-color: var(--color-navyblue); - background-color: var(--color-navyblue); - color: var(--color-white); -} - -.role-button--selected:hover { - background-color: var(--color-navyblue); -} - -@media screen and (width <= 768px) { - .two-column-layout { - grid-template-columns: 1fr; - } - - .form-header { - width: 87.5%; - } - - .stepper__form { - max-width: 85%; - } - - .stepper__buttons { - width: 87.5%; - } - - .stepper-welcome { - margin: 1rem auto 3rem; - padding: 1.5rem; - } - - .stepper-welcome img { - width: 100px; - } - - .terms-modal__content { - width: 95vw; - margin: 1rem; - } -} - -@media (width <= 480px) { - .form-header { - width: 97.5%; - } - - .stepper__form { - max-width: 95%; - } - - .stepper__buttons { - width: 97.5%; - } - - .stepper-welcome { - margin: 0.5rem auto 3rem; - padding: 1rem; - } - - .stepper-welcome img { - width: 80px; - } - - .stepper-welcome__checkbox-text { - font-size: 0.9rem; - } -} From 2a697eaa00d09dfa59e534f18163f0cc1a8517a8 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Sat, 8 Nov 2025 20:33:18 +0530 Subject: [PATCH 11/35] refactor: use try catch in local storage actions --- app/components/new-join-steps/base-step.js | 9 +++++---- app/components/new-stepper.js | 9 +++++---- app/utils/storage.js | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 app/utils/storage.js diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js index a6935cae..5e8e1a49 100644 --- a/app/components/new-join-steps/base-step.js +++ b/app/components/new-join-steps/base-step.js @@ -5,6 +5,7 @@ import { tracked } from '@glimmer/tracking'; import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; import { validateWordCount } from '../../utils/validator'; import { scheduleOnce } from '@ember/runloop'; +import { getLocalStorageItem, setLocalStorageItem } from '../../utils/storage'; export default class BaseStepComponent extends Component { stepValidation = {}; @@ -25,7 +26,7 @@ export default class BaseStepComponent extends Component { } initializeFormState() { - const saved = JSON.parse(localStorage.getItem(this.storageKey) || '{}'); + const saved = JSON.parse(getLocalStorageItem(this.storageKey, '{}')); this.data = saved ?? {}; this.errorMessage = Object.fromEntries( @@ -43,7 +44,7 @@ export default class BaseStepComponent extends Component { const valid = this.isDataValid(); this.args.setIsPreValid(valid); - localStorage.setItem('isValid', valid); + setLocalStorageItem('isValid', String(valid)); } @action inputHandler(e) { @@ -76,7 +77,7 @@ export default class BaseStepComponent extends Component { updateFieldValue(field, value) { this.data = { ...this.data, [field]: value }; - localStorage.setItem(this.storageKey, JSON.stringify(this.data)); + setLocalStorageItem(this.storageKey, JSON.stringify(this.data)); } updateWordCount(field, result) { @@ -103,6 +104,6 @@ export default class BaseStepComponent extends Component { syncFormValidity() { const allValid = this.isDataValid(); this.args.setIsValid(allValid); - localStorage.setItem('isValid', allValid); + setLocalStorageItem('isValid', String(allValid)); } } diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index e957b389..c65366d6 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -3,6 +3,7 @@ import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import { NEW_FORM_STEPS } from '../constants/new-join-form'; +import { getLocalStorageItem, setLocalStorageItem } from '../utils/storage'; export default class NewStepperComponent extends Component { MIN_STEP = 0; @@ -14,10 +15,10 @@ export default class NewStepperComponent extends Component { @service joinApplicationTerms; @tracked currentStep = - Number(localStorage.getItem('currentStep') ?? this.args.step) || 0; + Number(getLocalStorageItem('currentStep') ?? this.args.step) || 0; @tracked preValid = false; - @tracked isValid = localStorage.getItem('isValid') === 'true'; + @tracked isValid = getLocalStorageItem('isValid') === 'true'; setIsValid = (newVal) => (this.isValid = newVal); setIsPreValid = (newVal) => (this.preValid = newVal); @@ -48,7 +49,7 @@ export default class NewStepperComponent extends Component { @action incrementStep() { if (this.currentStep < this.MAX_STEP) { const nextStep = this.currentStep + 1; - localStorage.setItem('currentStep', String(nextStep)); + setLocalStorageItem('currentStep', String(nextStep)); this.currentStep = nextStep; this.updateQueryParam(nextStep); } @@ -57,7 +58,7 @@ export default class NewStepperComponent extends Component { @action decrementStep() { if (this.currentStep > this.MIN_STEP) { const previousStep = this.currentStep - 1; - localStorage.setItem('currentStep', String(previousStep)); + setLocalStorageItem('currentStep', String(previousStep)); this.currentStep = previousStep; this.updateQueryParam(previousStep); } diff --git a/app/utils/storage.js b/app/utils/storage.js new file mode 100644 index 00000000..31a65c78 --- /dev/null +++ b/app/utils/storage.js @@ -0,0 +1,18 @@ +export function getLocalStorageItem(key, defaultValue = null) { + try { + return localStorage.getItem(key) ?? defaultValue; + } catch (error) { + console.warn(`Failed to get localStorage item "${key}":`, error); + return defaultValue; + } +} + +export function setLocalStorageItem(key, value) { + try { + localStorage.setItem(key, value); + return true; + } catch (error) { + console.warn(`Failed to set localStorage item "${key}":`, error); + return false; + } +} From d656d030ab05f2b2c25d422f8879274a657c3716 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Sat, 8 Nov 2025 20:41:03 +0530 Subject: [PATCH 12/35] refactor: improve accessibility in new step one --- .../new-join-steps/new-step-one.hbs | 95 +++++-------------- 1 file changed, 25 insertions(+), 70 deletions(-) diff --git a/app/components/new-join-steps/new-step-one.hbs b/app/components/new-join-steps/new-step-one.hbs index c175fcf2..2bb38f03 100644 --- a/app/components/new-join-steps/new-step-one.hbs +++ b/app/components/new-join-steps/new-step-one.hbs @@ -2,32 +2,18 @@

      Profile Picture

      {{#if this.imagePreview}} -
      - Profile preview - -
      +
      + Profile preview + +
      {{else}} - + {{/if}} - +

      Image Requirements:

        @@ -42,60 +28,29 @@

        Personal Details

        -

        Please provide correct details and choose your role carefully, it won't be changed later.

        +

        Please provide correct details and choose your role carefully, it won't be changed + later.

        - + - + - + - +

        Applying as

        -
        +
        {{#each this.roleOptions as |role|}} - + {{/each}}
        From 3a70ecd82ea1e0fd9041bd3f2583e4f740e6d98e Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 4 Nov 2025 05:09:22 +0530 Subject: [PATCH 13/35] feat: add new step two for introduction --- .../new-join-steps/new-step-two.hbs | 48 ++++++++ app/components/new-join-steps/new-step-two.js | 108 ++++++++++++++++++ app/components/new-stepper.hbs | 7 ++ app/constants/new-join-form.js | 11 +- 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 app/components/new-join-steps/new-step-two.hbs create mode 100644 app/components/new-join-steps/new-step-two.js diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs new file mode 100644 index 00000000..b91320a1 --- /dev/null +++ b/app/components/new-join-steps/new-step-two.hbs @@ -0,0 +1,48 @@ +
        +
        +

        {{this.heading}}

        +

        {{this.subHeading}}

        +
        + + + {{#if this.errorMessage.skills}} +
        {{this.errorMessage.skills}}
        + {{/if}} + + + {{#if this.errorMessage.company}} +
        {{this.errorMessage.company}}
        + {{/if}} + + + +
        0/500 words
        + {{!--

        Share your background, experiences, and what drives you (100-500 words)

        --}} + + {{#if this.errorMessage.introduction}} +
        {{this.errorMessage.introduction}}
        + {{/if}} +
        \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-two.js b/app/components/new-join-steps/new-step-two.js new file mode 100644 index 00000000..fc5a8720 --- /dev/null +++ b/app/components/new-join-steps/new-step-two.js @@ -0,0 +1,108 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { validator } from '../../utils/validator'; +import { debounce } from '@ember/runloop'; +import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; +import { NEW_FORM_STEPS, NEW_STEP_LIMITS } from '../../constants/new-join-form'; + +export default class NewStepTwoComponent extends Component { + @tracked data = JSON.parse(localStorage.getItem('newStepTwoData')) ?? { + skills: '', + company: '', + introduction: '', + }; + + @tracked errorMessage = { + skills: '', + company: '', + introduction: '', + }; + + heading = NEW_FORM_STEPS.headings[this.currentStep - 1]; + subHeading = NEW_FORM_STEPS.subheadings[this.currentStep - 1]; + + isValid; + setIsValid; + setIsPreValid; + + constructor(...args) { + super(...args); + this.isValid = this.args.isValid; + this.setIsValid = this.args.setIsValid; + this.setIsPreValid = this.args.setIsPreValid; + const validated = this.isDataValid(); + localStorage.setItem('isValid', validated); + this.setIsPreValid(validated); + } + + isDataValid() { + for (let field in this.data) { + if (field === 'introduction') { + const wordCount = this.data[field].trim().split(/\s+/).length; + if ( + wordCount < NEW_STEP_LIMITS.stepTwo.introduction.min || + wordCount > NEW_STEP_LIMITS.stepTwo.introduction.max + ) { + return false; + } + } else { + const { isValid } = validator( + this.data[field], + NEW_STEP_LIMITS.stepTwo[field], + ); + if (!isValid) { + return false; + } + } + } + return true; + } + + @action inputHandler(e) { + this.setIsPreValid(false); + + const setValToLocalStorage = () => { + this.data = { ...this.data, [e.target.name]: e.target.value }; + localStorage.setItem('newStepTwoData', JSON.stringify(this.data)); + + const field = e.target.name; + if (field === 'introduction') { + const wordCount = this.data[field].trim().split(/\s+/).length; + if (wordCount < NEW_STEP_LIMITS.stepTwo.introduction.min) { + this.errorMessage = { + ...this.errorMessage, + [field]: `At least ${NEW_STEP_LIMITS.stepTwo.introduction.min - wordCount} more word(s) required`, + }; + } else if (wordCount > NEW_STEP_LIMITS.stepTwo.introduction.max) { + this.errorMessage = { + ...this.errorMessage, + [field]: `Maximum ${NEW_STEP_LIMITS.stepTwo.introduction.max} words allowed`, + }; + } else { + this.errorMessage = { + ...this.errorMessage, + [field]: '', + }; + } + } else { + const { isValid, remainingWords } = validator( + this.data[field], + NEW_STEP_LIMITS.stepTwo[field], + ); + this.errorMessage = { + ...this.errorMessage, + [field]: isValid + ? '' + : `At least, ${remainingWords} more word(s) required`, + }; + } + + const isAllValid = this.isDataValid(); + this.setIsValid(isAllValid); + localStorage.setItem('isValid', isAllValid); + }; + + debounce(this.data, setValToLocalStorage, JOIN_DEBOUNCE_TIME); + } +} diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 9c6ae4de..594581df 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -30,6 +30,13 @@ @isValid={{this.isValid}} @setIsValid={{this.setIsValid}} /> + + {{else if (eq this.currentStep 2)}} + {{/if}} diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 6911a135..dd38e2d4 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -1,7 +1,11 @@ export const NEW_FORM_STEPS = { - headings: ['Upload Professional Headshot and Complete Personal Details'], + headings: [ + 'Upload Professional Headshot and Complete Personal Details', + 'More personal details please', + ], subheadings: [ 'Please provide accurate information for verification purposes.', + 'Introduce and help us get to know you better', ], }; @@ -21,6 +25,11 @@ export const NEW_STEP_LIMITS = { city: { min: 1 }, role: { min: 1 }, }, + stepTwo: { + skills: 5, + company: 1, + introduction: { min: 100, max: 500 }, + }, }; export const STEP_DATA_STORAGE_KEY = { From 4e9fe8a3b0d6d3ba6945a37a092002589916ba5b Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 4 Nov 2025 15:09:39 +0530 Subject: [PATCH 14/35] feat: add step three for interests --- .../new-join-steps/new-step-three.hbs | 38 ++++++++ .../new-join-steps/new-step-three.js | 90 +++++++++++++++++++ .../new-join-steps/new-step-two.hbs | 86 +++++++++--------- app/components/new-join-steps/new-step-two.js | 62 +++++++------ app/components/new-stepper.hbs | 7 ++ app/constants/new-join-form.js | 6 ++ 6 files changed, 220 insertions(+), 69 deletions(-) create mode 100644 app/components/new-join-steps/new-step-three.hbs create mode 100644 app/components/new-join-steps/new-step-three.js diff --git a/app/components/new-join-steps/new-step-three.hbs b/app/components/new-join-steps/new-step-three.hbs new file mode 100644 index 00000000..8ab34a1c --- /dev/null +++ b/app/components/new-join-steps/new-step-three.hbs @@ -0,0 +1,38 @@ +
        +

        {{this.heading}}

        +

        {{this.subHeading}}

        +
        + +
        +
        +

        What do you do for fun? Your hobbies/interests?

        + +
        {{this.wordCount.hobbies}}/{{this.maxWords.hobbies}} words
        + {{#if this.errorMessage.hobbies}} +
        {{this.errorMessage.hobbies}}
        + {{/if}} +
        + +
        +

        Fun fact about you

        + +
        {{this.wordCount.funFact}}/{{this.maxWords.funFact}} words
        + {{#if this.errorMessage.funFact}} +
        {{this.errorMessage.funFact}}
        + {{/if}} +
        +
        \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-three.js b/app/components/new-join-steps/new-step-three.js new file mode 100644 index 00000000..e8f57710 --- /dev/null +++ b/app/components/new-join-steps/new-step-three.js @@ -0,0 +1,90 @@ +import { action } from '@ember/object'; +import { debounce } from '@ember/runloop'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; +import { NEW_FORM_STEPS, NEW_STEP_LIMITS } from '../../constants/new-join-form'; +import { validateWordCount } from '../../utils/validator'; + +export default class NewStepThreeComponent extends Component { + @tracked data = JSON.parse(localStorage.getItem('newStepThreeData')) ?? { + hobbies: '', + funFact: '', + }; + + @tracked errorMessage = { + hobbies: '', + funFact: '', + }; + + @tracked wordCount = { + hobbies: + this?.data?.hobbies?.trim()?.split(/\s+/).filter(Boolean).length || 0, + funFact: + this?.data?.funFact?.trim()?.split(/\s+/).filter(Boolean).length || 0, + }; + + maxWords = { + hobbies: NEW_STEP_LIMITS.stepThree.hobbies.max, + funFact: NEW_STEP_LIMITS.stepThree.funFact.max, + }; + + isValid; + setIsValid; + setIsPreValid; + + heading = NEW_FORM_STEPS.headings[2]; + subHeading = NEW_FORM_STEPS.subheadings[2]; + + constructor(...args) { + super(...args); + this.isValid = this.args.isValid; + this.setIsValid = this.args.setIsValid; + this.setIsPreValid = this.args.setIsPreValid; + const validated = this.isDataValid(); + localStorage.setItem('isValid', validated); + this.setIsPreValid(validated); + } + + isDataValid() { + for (let field in this.data) { + const { isValid } = validateWordCount( + this.data[field], + NEW_STEP_LIMITS.stepThree[field], + ); + if (!isValid) return false; + } + return true; + } + + @action inputHandler(e) { + this.setIsPreValid(false); + + const setValToLocalStorage = () => { + this.data = { ...this.data, [e.target.name]: e.target.value }; + localStorage.setItem('newStepThreeData', JSON.stringify(this.data)); + + // Only validate the changed field + const field = e.target.name; + const { isValid, wordCount, remainingToMin } = validateWordCount( + this.data[field], + NEW_STEP_LIMITS.stepThree[field], + ); + this.wordCount = { ...this.wordCount, [field]: wordCount }; + this.errorMessage = { + ...this.errorMessage, + [field]: isValid + ? '' + : remainingToMin + ? `At least, ${remainingToMin} more word(s) required` + : `Maximum ${NEW_STEP_LIMITS.stepThree[field].max} words allowed`, + }; + + const isAllValid = this.isDataValid(); + this.setIsValid(isAllValid); + localStorage.setItem('isValid', isAllValid); + }; + + debounce(this.data, setValToLocalStorage, JOIN_DEBOUNCE_TIME); + } +} diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs index b91320a1..629b9d3f 100644 --- a/app/components/new-join-steps/new-step-two.hbs +++ b/app/components/new-join-steps/new-step-two.hbs @@ -1,48 +1,54 @@
        -
        -

        {{this.heading}}

        -

        {{this.subHeading}}

        -
        - - - {{#if this.errorMessage.skills}} -
        {{this.errorMessage.skills}}
        - {{/if}} +
        +

        {{this.heading}}

        +

        {{this.subHeading}}

        +
        - - {{#if this.errorMessage.company}} -
        {{this.errorMessage.company}}
        - {{/if}} +
        +
        + + {{#if this.errorMessage.skills}} +
        {{this.errorMessage.skills}}
        + {{/if}} +
        - + + /> + {{#if this.errorMessage.company}} +
        {{this.errorMessage.company}}
        + {{/if}} +
        +
        -
        0/500 words
        - {{!--

        Share your background, experiences, and what drives you (100-500 words)

        --}} +

        Please introduce yourself

        + +
        {{this.wordCount.introduction}}/{{this.maxWords.introduction}} words
        +

        Share your background, experiences, and what drives you.

        - {{#if this.errorMessage.introduction}} -
        {{this.errorMessage.introduction}}
        - {{/if}} + {{#if this.errorMessage.introduction}} +
        {{this.errorMessage.introduction}}
        + {{/if}}
        \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-two.js b/app/components/new-join-steps/new-step-two.js index fc5a8720..3f72b711 100644 --- a/app/components/new-join-steps/new-step-two.js +++ b/app/components/new-join-steps/new-step-two.js @@ -1,10 +1,10 @@ -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import { validator } from '../../utils/validator'; import { debounce } from '@ember/runloop'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; import { NEW_FORM_STEPS, NEW_STEP_LIMITS } from '../../constants/new-join-form'; +import { validateWordCount, validator } from '../../utils/validator'; export default class NewStepTwoComponent extends Component { @tracked data = JSON.parse(localStorage.getItem('newStepTwoData')) ?? { @@ -19,8 +19,18 @@ export default class NewStepTwoComponent extends Component { introduction: '', }; - heading = NEW_FORM_STEPS.headings[this.currentStep - 1]; - subHeading = NEW_FORM_STEPS.subheadings[this.currentStep - 1]; + @tracked wordCount = { + introduction: + this?.data?.introduction?.trim()?.split(/\s+/).filter(Boolean).length || + 0, + }; + + maxWords = { + introduction: NEW_STEP_LIMITS.stepTwo.introduction.max, + }; + + heading = NEW_FORM_STEPS.headings[1]; + subHeading = NEW_FORM_STEPS.subheadings[1]; isValid; setIsValid; @@ -39,13 +49,11 @@ export default class NewStepTwoComponent extends Component { isDataValid() { for (let field in this.data) { if (field === 'introduction') { - const wordCount = this.data[field].trim().split(/\s+/).length; - if ( - wordCount < NEW_STEP_LIMITS.stepTwo.introduction.min || - wordCount > NEW_STEP_LIMITS.stepTwo.introduction.max - ) { - return false; - } + const { isValid } = validateWordCount( + this.data[field], + NEW_STEP_LIMITS.stepTwo.introduction, + ); + if (!isValid) return false; } else { const { isValid } = validator( this.data[field], @@ -68,23 +76,19 @@ export default class NewStepTwoComponent extends Component { const field = e.target.name; if (field === 'introduction') { - const wordCount = this.data[field].trim().split(/\s+/).length; - if (wordCount < NEW_STEP_LIMITS.stepTwo.introduction.min) { - this.errorMessage = { - ...this.errorMessage, - [field]: `At least ${NEW_STEP_LIMITS.stepTwo.introduction.min - wordCount} more word(s) required`, - }; - } else if (wordCount > NEW_STEP_LIMITS.stepTwo.introduction.max) { - this.errorMessage = { - ...this.errorMessage, - [field]: `Maximum ${NEW_STEP_LIMITS.stepTwo.introduction.max} words allowed`, - }; - } else { - this.errorMessage = { - ...this.errorMessage, - [field]: '', - }; - } + const { isValid, wordCount, remainingToMin } = validateWordCount( + this.data[field], + NEW_STEP_LIMITS.stepTwo.introduction, + ); + this.wordCount = { ...this.wordCount, introduction: wordCount }; + this.errorMessage = { + ...this.errorMessage, + [field]: isValid + ? '' + : remainingToMin + ? `At least ${remainingToMin} more word(s) required` + : `Maximum ${NEW_STEP_LIMITS.stepTwo.introduction.max} words allowed`, + }; } else { const { isValid, remainingWords } = validator( this.data[field], diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 594581df..d40869ca 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -37,6 +37,13 @@ @isValid={{this.isValid}} @setIsValid={{this.setIsValid}} /> + + {{else if (eq this.currentStep 3)}} + {{/if}} diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index dd38e2d4..3b273c10 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -2,10 +2,12 @@ export const NEW_FORM_STEPS = { headings: [ 'Upload Professional Headshot and Complete Personal Details', 'More personal details please', + 'Your hobbies, interests, fun fact', ], subheadings: [ 'Please provide accurate information for verification purposes.', 'Introduce and help us get to know you better', + 'Show us your funny and interesting side', ], }; @@ -30,6 +32,10 @@ export const NEW_STEP_LIMITS = { company: 1, introduction: { min: 100, max: 500 }, }, + stepThree: { + hobbies: { min: 100, max: 500 }, + funFact: { min: 100, max: 500 }, + }, }; export const STEP_DATA_STORAGE_KEY = { From 99ed2b6fd5ce4e049796b5c77b132ae1759eb366 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 4 Nov 2025 18:17:11 +0530 Subject: [PATCH 15/35] feat: add base step class to handle input validation and local storage sync --- .../new-join-steps/new-step-three.hbs | 4 +- .../new-join-steps/new-step-three.js | 90 ++----------- .../new-join-steps/new-step-two.hbs | 4 +- app/components/new-join-steps/new-step-two.js | 118 ++---------------- app/components/new-stepper.hbs | 4 + app/constants/new-join-form.js | 3 +- 6 files changed, 25 insertions(+), 198 deletions(-) diff --git a/app/components/new-join-steps/new-step-three.hbs b/app/components/new-join-steps/new-step-three.hbs index 8ab34a1c..43d8c901 100644 --- a/app/components/new-join-steps/new-step-three.hbs +++ b/app/components/new-join-steps/new-step-three.hbs @@ -1,6 +1,6 @@
        -

        {{this.heading}}

        -

        {{this.subHeading}}

        +

        {{@heading}}

        +

        {{@subHeading}}

        diff --git a/app/components/new-join-steps/new-step-three.js b/app/components/new-join-steps/new-step-three.js index e8f57710..9b88406f 100644 --- a/app/components/new-join-steps/new-step-three.js +++ b/app/components/new-join-steps/new-step-three.js @@ -1,90 +1,14 @@ -import { action } from '@ember/object'; -import { debounce } from '@ember/runloop'; -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; -import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; -import { NEW_FORM_STEPS, NEW_STEP_LIMITS } from '../../constants/new-join-form'; -import { validateWordCount } from '../../utils/validator'; +import BaseStepComponent from './base-step'; +import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; -export default class NewStepThreeComponent extends Component { - @tracked data = JSON.parse(localStorage.getItem('newStepThreeData')) ?? { - hobbies: '', - funFact: '', +export default class NewStepThreeComponent extends BaseStepComponent { + storageKey = 'newStepThreeData'; + validationMap = { + hobbies: NEW_STEP_LIMITS.stepThree.hobbies, + funFact: NEW_STEP_LIMITS.stepThree.funFact, }; - - @tracked errorMessage = { - hobbies: '', - funFact: '', - }; - - @tracked wordCount = { - hobbies: - this?.data?.hobbies?.trim()?.split(/\s+/).filter(Boolean).length || 0, - funFact: - this?.data?.funFact?.trim()?.split(/\s+/).filter(Boolean).length || 0, - }; - maxWords = { hobbies: NEW_STEP_LIMITS.stepThree.hobbies.max, funFact: NEW_STEP_LIMITS.stepThree.funFact.max, }; - - isValid; - setIsValid; - setIsPreValid; - - heading = NEW_FORM_STEPS.headings[2]; - subHeading = NEW_FORM_STEPS.subheadings[2]; - - constructor(...args) { - super(...args); - this.isValid = this.args.isValid; - this.setIsValid = this.args.setIsValid; - this.setIsPreValid = this.args.setIsPreValid; - const validated = this.isDataValid(); - localStorage.setItem('isValid', validated); - this.setIsPreValid(validated); - } - - isDataValid() { - for (let field in this.data) { - const { isValid } = validateWordCount( - this.data[field], - NEW_STEP_LIMITS.stepThree[field], - ); - if (!isValid) return false; - } - return true; - } - - @action inputHandler(e) { - this.setIsPreValid(false); - - const setValToLocalStorage = () => { - this.data = { ...this.data, [e.target.name]: e.target.value }; - localStorage.setItem('newStepThreeData', JSON.stringify(this.data)); - - // Only validate the changed field - const field = e.target.name; - const { isValid, wordCount, remainingToMin } = validateWordCount( - this.data[field], - NEW_STEP_LIMITS.stepThree[field], - ); - this.wordCount = { ...this.wordCount, [field]: wordCount }; - this.errorMessage = { - ...this.errorMessage, - [field]: isValid - ? '' - : remainingToMin - ? `At least, ${remainingToMin} more word(s) required` - : `Maximum ${NEW_STEP_LIMITS.stepThree[field].max} words allowed`, - }; - - const isAllValid = this.isDataValid(); - this.setIsValid(isAllValid); - localStorage.setItem('isValid', isAllValid); - }; - - debounce(this.data, setValToLocalStorage, JOIN_DEBOUNCE_TIME); - } } diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs index 629b9d3f..4d947d99 100644 --- a/app/components/new-join-steps/new-step-two.hbs +++ b/app/components/new-join-steps/new-step-two.hbs @@ -1,7 +1,7 @@
        -

        {{this.heading}}

        -

        {{this.subHeading}}

        +

        {{@heading}}

        +

        {{@subHeading}}

        diff --git a/app/components/new-join-steps/new-step-two.js b/app/components/new-join-steps/new-step-two.js index 3f72b711..813ed5a4 100644 --- a/app/components/new-join-steps/new-step-two.js +++ b/app/components/new-join-steps/new-step-two.js @@ -1,112 +1,12 @@ -import { action } from '@ember/object'; -import { debounce } from '@ember/runloop'; -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; -import { JOIN_DEBOUNCE_TIME } from '../../constants/join'; -import { NEW_FORM_STEPS, NEW_STEP_LIMITS } from '../../constants/new-join-form'; -import { validateWordCount, validator } from '../../utils/validator'; +import BaseStepComponent from './base-step'; +import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; -export default class NewStepTwoComponent extends Component { - @tracked data = JSON.parse(localStorage.getItem('newStepTwoData')) ?? { - skills: '', - company: '', - introduction: '', +export default class NewStepTwoComponent extends BaseStepComponent { + storageKey = 'newStepTwoData'; + validationMap = { + skills: NEW_STEP_LIMITS.stepTwo.skills, + company: NEW_STEP_LIMITS.stepTwo.company, + introduction: NEW_STEP_LIMITS.stepTwo.introduction, }; - - @tracked errorMessage = { - skills: '', - company: '', - introduction: '', - }; - - @tracked wordCount = { - introduction: - this?.data?.introduction?.trim()?.split(/\s+/).filter(Boolean).length || - 0, - }; - - maxWords = { - introduction: NEW_STEP_LIMITS.stepTwo.introduction.max, - }; - - heading = NEW_FORM_STEPS.headings[1]; - subHeading = NEW_FORM_STEPS.subheadings[1]; - - isValid; - setIsValid; - setIsPreValid; - - constructor(...args) { - super(...args); - this.isValid = this.args.isValid; - this.setIsValid = this.args.setIsValid; - this.setIsPreValid = this.args.setIsPreValid; - const validated = this.isDataValid(); - localStorage.setItem('isValid', validated); - this.setIsPreValid(validated); - } - - isDataValid() { - for (let field in this.data) { - if (field === 'introduction') { - const { isValid } = validateWordCount( - this.data[field], - NEW_STEP_LIMITS.stepTwo.introduction, - ); - if (!isValid) return false; - } else { - const { isValid } = validator( - this.data[field], - NEW_STEP_LIMITS.stepTwo[field], - ); - if (!isValid) { - return false; - } - } - } - return true; - } - - @action inputHandler(e) { - this.setIsPreValid(false); - - const setValToLocalStorage = () => { - this.data = { ...this.data, [e.target.name]: e.target.value }; - localStorage.setItem('newStepTwoData', JSON.stringify(this.data)); - - const field = e.target.name; - if (field === 'introduction') { - const { isValid, wordCount, remainingToMin } = validateWordCount( - this.data[field], - NEW_STEP_LIMITS.stepTwo.introduction, - ); - this.wordCount = { ...this.wordCount, introduction: wordCount }; - this.errorMessage = { - ...this.errorMessage, - [field]: isValid - ? '' - : remainingToMin - ? `At least ${remainingToMin} more word(s) required` - : `Maximum ${NEW_STEP_LIMITS.stepTwo.introduction.max} words allowed`, - }; - } else { - const { isValid, remainingWords } = validator( - this.data[field], - NEW_STEP_LIMITS.stepTwo[field], - ); - this.errorMessage = { - ...this.errorMessage, - [field]: isValid - ? '' - : `At least, ${remainingWords} more word(s) required`, - }; - } - - const isAllValid = this.isDataValid(); - this.setIsValid(isAllValid); - localStorage.setItem('isValid', isAllValid); - }; - - debounce(this.data, setValToLocalStorage, JOIN_DEBOUNCE_TIME); - } + maxWords = { introduction: NEW_STEP_LIMITS.stepTwo.introduction.max }; } diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index d40869ca..ecb753fd 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -36,6 +36,8 @@ @setIsPreValid={{this.setIsPreValid}} @isValid={{this.isValid}} @setIsValid={{this.setIsValid}} + @heading={{this.currentHeading}} + @subHeading={{this.currentSubheading}} /> {{else if (eq this.currentStep 3)}} @@ -43,6 +45,8 @@ @setIsPreValid={{this.setIsPreValid}} @isValid={{this.isValid}} @setIsValid={{this.setIsValid}} + @heading={{this.currentHeading}} + @subHeading={{this.currentSubheading}} /> {{/if}} diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 3b273c10..49b22e31 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -28,8 +28,7 @@ export const NEW_STEP_LIMITS = { role: { min: 1 }, }, stepTwo: { - skills: 5, - company: 1, + skills: { min: 5, max: 20 }, introduction: { min: 100, max: 500 }, }, stepThree: { From 2478f760445e270b3982450af9463bc90530a886 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 4 Nov 2025 21:00:03 +0530 Subject: [PATCH 16/35] fix: postIntialization in new step one --- app/constants/new-join-form.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 49b22e31..1fab4739 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -29,6 +29,7 @@ export const NEW_STEP_LIMITS = { }, stepTwo: { skills: { min: 5, max: 20 }, + company: { min: 1 }, introduction: { min: 100, max: 500 }, }, stepThree: { From 93969aa479dacd5860a56674793a5dae17ed38a2 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Wed, 5 Nov 2025 18:40:25 +0530 Subject: [PATCH 17/35] fix: grid styling for step two and three --- .../new-join-steps/new-step-three.hbs | 68 +++++++++--------- .../new-join-steps/new-step-two.hbs | 69 +++++++------------ 2 files changed, 60 insertions(+), 77 deletions(-) diff --git a/app/components/new-join-steps/new-step-three.hbs b/app/components/new-join-steps/new-step-three.hbs index 43d8c901..4e6ecfad 100644 --- a/app/components/new-join-steps/new-step-three.hbs +++ b/app/components/new-join-steps/new-step-three.hbs @@ -1,38 +1,38 @@ -
        -

        {{@heading}}

        -

        {{@subHeading}}

        -
        - -
        -
        -

        What do you do for fun? Your hobbies/interests?

        - -
        {{this.wordCount.hobbies}}/{{this.maxWords.hobbies}} words
        - {{#if this.errorMessage.hobbies}} -
        {{this.errorMessage.hobbies}}
        - {{/if}} +
        +
        +

        {{@heading}}

        +

        {{@subHeading}}

        -
        -

        Fun fact about you

        - -
        {{this.wordCount.funFact}}/{{this.maxWords.funFact}} words
        - {{#if this.errorMessage.funFact}} -
        {{this.errorMessage.funFact}}
        - {{/if}} +
        +
        + +
        {{this.wordCount.hobbies}}/{{this.maxWords.hobbies}} words
        + {{#if this.errorMessage.hobbies}} +
        {{this.errorMessage.hobbies}}
        + {{/if}} +
        + +
        + +
        {{this.wordCount.funFact}}/{{this.maxWords.funFact}} words
        + {{#if this.errorMessage.funFact}} +
        {{this.errorMessage.funFact}}
        + {{/if}} +
        \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs index 4d947d99..451601e5 100644 --- a/app/components/new-join-steps/new-step-two.hbs +++ b/app/components/new-join-steps/new-step-two.hbs @@ -1,54 +1,37 @@
        -

        {{@heading}}

        -

        {{@subHeading}}

        +

        {{@heading}}

        +

        {{@subHeading}}

        -
        -
        - - {{#if this.errorMessage.skills}} +
        +
        +
        + + {{#if this.errorMessage.skills}}
        {{this.errorMessage.skills}}
        - {{/if}} -
        + {{/if}} +
        -
        - - {{#if this.errorMessage.company}} +
        + + {{#if this.errorMessage.company}}
        {{this.errorMessage.company}}
        + {{/if}} +
        +
        +
        + +
        {{this.wordCount.introduction}}/{{this.maxWords.introduction}} words
        + + {{#if this.errorMessage.introduction}} +
        {{this.errorMessage.introduction}}
        {{/if}}
        - -

        Please introduce yourself

        - -
        {{this.wordCount.introduction}}/{{this.maxWords.introduction}} words
        -

        Share your background, experiences, and what drives you.

        - - {{#if this.errorMessage.introduction}} -
        {{this.errorMessage.introduction}}
        - {{/if}}
        \ No newline at end of file From fe14c9285c7c33bbcf3fb128a6119ea7d65721ac Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Fri, 7 Nov 2025 17:48:05 +0530 Subject: [PATCH 18/35] refactor: remove redundant max words --- app/components/new-join-steps/new-step-three.hbs | 12 ++++++------ app/components/new-join-steps/new-step-three.js | 4 ---- app/components/new-join-steps/new-step-two.hbs | 12 ++++++------ app/components/new-join-steps/new-step-two.js | 1 - 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/app/components/new-join-steps/new-step-three.hbs b/app/components/new-join-steps/new-step-three.hbs index 4e6ecfad..dc47323e 100644 --- a/app/components/new-join-steps/new-step-three.hbs +++ b/app/components/new-join-steps/new-step-three.hbs @@ -1,7 +1,7 @@
        -

        {{@heading}}

        -

        {{@subHeading}}

        +

        {{@heading}}

        +

        {{@subHeading}}

        @@ -14,9 +14,9 @@ @value={{this.data.hobbies}} @onInput={{this.inputHandler}} /> -
        {{this.wordCount.hobbies}}/{{this.maxWords.hobbies}} words
        +
        {{this.wordCount.hobbies}}/{{this.validationMap.hobbies.max}} words
        {{#if this.errorMessage.hobbies}} -
        {{this.errorMessage.hobbies}}
        +
        {{this.errorMessage.hobbies}}
        {{/if}}
        @@ -29,9 +29,9 @@ @value={{this.data.funFact}} @onInput={{this.inputHandler}} /> -
        {{this.wordCount.funFact}}/{{this.maxWords.funFact}} words
        +
        {{this.wordCount.funFact}}/{{this.validationMap.funFact.max}} words
        {{#if this.errorMessage.funFact}} -
        {{this.errorMessage.funFact}}
        +
        {{this.errorMessage.funFact}}
        {{/if}}
        diff --git a/app/components/new-join-steps/new-step-three.js b/app/components/new-join-steps/new-step-three.js index 9b88406f..e52570f7 100644 --- a/app/components/new-join-steps/new-step-three.js +++ b/app/components/new-join-steps/new-step-three.js @@ -7,8 +7,4 @@ export default class NewStepThreeComponent extends BaseStepComponent { hobbies: NEW_STEP_LIMITS.stepThree.hobbies, funFact: NEW_STEP_LIMITS.stepThree.funFact, }; - maxWords = { - hobbies: NEW_STEP_LIMITS.stepThree.hobbies.max, - funFact: NEW_STEP_LIMITS.stepThree.funFact.max, - }; } diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs index 451601e5..360b5670 100644 --- a/app/components/new-join-steps/new-step-two.hbs +++ b/app/components/new-join-steps/new-step-two.hbs @@ -1,7 +1,7 @@
        -

        {{@heading}}

        -

        {{@subHeading}}

        +

        {{@heading}}

        +

        {{@subHeading}}

        @@ -10,7 +10,7 @@ {{#if this.errorMessage.skills}} -
        {{this.errorMessage.skills}}
        +
        {{this.errorMessage.skills}}
        {{/if}}
        @@ -19,7 +19,7 @@ @placeHolder='Institution or company name' @type='text' @required={{true}} @value={{this.data.company}} @onInput={{this.inputHandler}} /> {{#if this.errorMessage.company}} -
        {{this.errorMessage.company}}
        +
        {{this.errorMessage.company}}
        {{/if}}
        @@ -27,10 +27,10 @@ -
        {{this.wordCount.introduction}}/{{this.maxWords.introduction}} words
        +
        {{this.wordCount.introduction}}/{{this.validationMap.introduction.max}} words
        {{#if this.errorMessage.introduction}} -
        {{this.errorMessage.introduction}}
        +
        {{this.errorMessage.introduction}}
        {{/if}}
        diff --git a/app/components/new-join-steps/new-step-two.js b/app/components/new-join-steps/new-step-two.js index 813ed5a4..67ad7da6 100644 --- a/app/components/new-join-steps/new-step-two.js +++ b/app/components/new-join-steps/new-step-two.js @@ -8,5 +8,4 @@ export default class NewStepTwoComponent extends BaseStepComponent { company: NEW_STEP_LIMITS.stepTwo.company, introduction: NEW_STEP_LIMITS.stepTwo.introduction, }; - maxWords = { introduction: NEW_STEP_LIMITS.stepTwo.introduction.max }; } From 34dfc52b2a8b1c321d8c6f68407a95fcc9a395c9 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Fri, 7 Nov 2025 17:58:43 +0530 Subject: [PATCH 19/35] fix: lint issue and form headings --- app/constants/new-join-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 1fab4739..126625cc 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -1,7 +1,7 @@ export const NEW_FORM_STEPS = { headings: [ 'Upload Professional Headshot and Complete Personal Details', - 'More personal details please', + 'Additional Personal Information', 'Your hobbies, interests, fun fact', ], subheadings: [ From ee06d8a7afd32f8a742db36f7660b8483932ec2c Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Mon, 10 Nov 2025 01:53:44 +0530 Subject: [PATCH 20/35] refactor: use constant for storage keys --- app/components/new-join-steps/new-step-three.hbs | 4 ++-- app/components/new-join-steps/new-step-three.js | 9 ++++++--- app/components/new-join-steps/new-step-two.hbs | 2 +- app/components/new-join-steps/new-step-two.js | 9 ++++++--- app/constants/new-join-form.js | 2 ++ 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/components/new-join-steps/new-step-three.hbs b/app/components/new-join-steps/new-step-three.hbs index dc47323e..7614c45f 100644 --- a/app/components/new-join-steps/new-step-three.hbs +++ b/app/components/new-join-steps/new-step-three.hbs @@ -14,7 +14,7 @@ @value={{this.data.hobbies}} @onInput={{this.inputHandler}} /> -
        {{this.wordCount.hobbies}}/{{this.validationMap.hobbies.max}} words
        +
        {{this.wordCount.hobbies}}/{{this.stepValidation.hobbies.max}} words
        {{#if this.errorMessage.hobbies}}
        {{this.errorMessage.hobbies}}
        {{/if}} @@ -29,7 +29,7 @@ @value={{this.data.funFact}} @onInput={{this.inputHandler}} /> -
        {{this.wordCount.funFact}}/{{this.validationMap.funFact.max}} words
        +
        {{this.wordCount.funFact}}/{{this.stepValidation.funFact.max}} words
        {{#if this.errorMessage.funFact}}
        {{this.errorMessage.funFact}}
        {{/if}} diff --git a/app/components/new-join-steps/new-step-three.js b/app/components/new-join-steps/new-step-three.js index e52570f7..0c3fd6b2 100644 --- a/app/components/new-join-steps/new-step-three.js +++ b/app/components/new-join-steps/new-step-three.js @@ -1,9 +1,12 @@ import BaseStepComponent from './base-step'; -import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; +import { + NEW_STEP_LIMITS, + STEP_DATA_STORAGE_KEY, +} from '../../constants/new-join-form'; export default class NewStepThreeComponent extends BaseStepComponent { - storageKey = 'newStepThreeData'; - validationMap = { + storageKey = STEP_DATA_STORAGE_KEY.stepThree; + stepValidation = { hobbies: NEW_STEP_LIMITS.stepThree.hobbies, funFact: NEW_STEP_LIMITS.stepThree.funFact, }; diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs index 360b5670..c96c5175 100644 --- a/app/components/new-join-steps/new-step-two.hbs +++ b/app/components/new-join-steps/new-step-two.hbs @@ -27,7 +27,7 @@ -
        {{this.wordCount.introduction}}/{{this.validationMap.introduction.max}} words
        +
        {{this.wordCount.introduction}}/{{this.stepValidation.introduction.max}} words
        {{#if this.errorMessage.introduction}}
        {{this.errorMessage.introduction}}
        diff --git a/app/components/new-join-steps/new-step-two.js b/app/components/new-join-steps/new-step-two.js index 67ad7da6..edb38294 100644 --- a/app/components/new-join-steps/new-step-two.js +++ b/app/components/new-join-steps/new-step-two.js @@ -1,9 +1,12 @@ import BaseStepComponent from './base-step'; -import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; +import { + NEW_STEP_LIMITS, + STEP_DATA_STORAGE_KEY, +} from '../../constants/new-join-form'; export default class NewStepTwoComponent extends BaseStepComponent { - storageKey = 'newStepTwoData'; - validationMap = { + storageKey = STEP_DATA_STORAGE_KEY.stepTwo; + stepValidation = { skills: NEW_STEP_LIMITS.stepTwo.skills, company: NEW_STEP_LIMITS.stepTwo.company, introduction: NEW_STEP_LIMITS.stepTwo.introduction, diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 126625cc..5c1aadcd 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -40,4 +40,6 @@ export const NEW_STEP_LIMITS = { export const STEP_DATA_STORAGE_KEY = { stepOne: 'newStepOneData', + stepTwo: 'newStepTwoData', + stepThree: 'newStepThreeData', }; From 0fd986f35ca37d8ee30d6a375f2a26de4b35dd00 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Wed, 5 Nov 2025 03:43:08 +0530 Subject: [PATCH 21/35] feat: add new step for social links --- .../new-join-steps/new-step-four.hbs | 116 ++++++++++++++++++ .../new-join-steps/new-step-four.js | 46 +++++++ app/components/new-stepper.hbs | 9 ++ app/constants/new-join-form.js | 12 ++ 4 files changed, 183 insertions(+) create mode 100644 app/components/new-join-steps/new-step-four.hbs create mode 100644 app/components/new-join-steps/new-step-four.js diff --git a/app/components/new-join-steps/new-step-four.hbs b/app/components/new-join-steps/new-step-four.hbs new file mode 100644 index 00000000..0d0367fa --- /dev/null +++ b/app/components/new-join-steps/new-step-four.hbs @@ -0,0 +1,116 @@ +
        +
        +

        {{@heading}}

        +

        {{@subHeading}}

        +
        + + + {{#if this.errorMessage.phoneNumber}} +
        {{this.errorMessage.phoneNumber}}
        + {{/if}} + + + {{#if this.errorMessage.twitter}} +
        {{this.errorMessage.twitter}}
        + {{/if}} + + {{#if this.showGitHub}} + + {{#if this.errorMessage.github}} +
        {{this.errorMessage.github}}
        + {{/if}} + {{/if}} + + + {{#if this.errorMessage.linkedin}} +
        {{this.errorMessage.linkedin}}
        + {{/if}} + + + {{#if this.errorMessage.instagram}} +
        {{this.errorMessage.instagram}}
        + {{/if}} + + + {{#if this.errorMessage.peerlist}} +
        {{this.errorMessage.peerlist}}
        + {{/if}} + + {{#if this.showBehance}} + + {{#if this.errorMessage.behance}} +
        {{this.errorMessage.behance}}
        + {{/if}} + {{/if}} + + {{#if this.showDribble}} + + {{#if this.errorMessage.dribble}} +
        {{this.errorMessage.dribble}}
        + {{/if}} + {{/if}} +
        diff --git a/app/components/new-join-steps/new-step-four.js b/app/components/new-join-steps/new-step-four.js new file mode 100644 index 00000000..39b427b3 --- /dev/null +++ b/app/components/new-join-steps/new-step-four.js @@ -0,0 +1,46 @@ +import BaseStepComponent from './base-step'; +import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; + +export default class NewStepFourComponent extends BaseStepComponent { + storageKey = 'newStepFourData'; + + get userRole() { + const stepOneData = JSON.parse( + localStorage.getItem('newStepOneData') || '{}', + ); + return stepOneData.role || ''; + } + + get validationMap() { + const baseMap = { + phoneNumber: NEW_STEP_LIMITS.stepFour.phoneNumber, + twitter: NEW_STEP_LIMITS.stepFour.twitter, + linkedin: NEW_STEP_LIMITS.stepFour.linkedin, + instagram: NEW_STEP_LIMITS.stepFour.instagram, + peerlist: NEW_STEP_LIMITS.stepFour.peerlist, + }; + + if (this.userRole === 'Developer') { + baseMap.github = NEW_STEP_LIMITS.stepFour.github; + } + + if (this.userRole === 'Designer') { + baseMap.behance = NEW_STEP_LIMITS.stepFour.behance; + baseMap.dribble = NEW_STEP_LIMITS.stepFour.dribble; + } + + return baseMap; + } + + get showGitHub() { + return this.userRole === 'Developer'; + } + + get showBehance() { + return this.userRole === 'Designer'; + } + + get showDribble() { + return this.userRole === 'Designer'; + } +} diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index ecb753fd..e49a0976 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -48,6 +48,15 @@ @heading={{this.currentHeading}} @subHeading={{this.currentSubheading}} /> + + {{else if (eq this.currentStep 4)}} + {{/if}} diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 5c1aadcd..e59fb781 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -3,11 +3,13 @@ export const NEW_FORM_STEPS = { 'Upload Professional Headshot and Complete Personal Details', 'Additional Personal Information', 'Your hobbies, interests, fun fact', + 'Connect your social profiles', ], subheadings: [ 'Please provide accurate information for verification purposes.', 'Introduce and help us get to know you better', 'Show us your funny and interesting side', + 'Share your social media and professional profiles', ], }; @@ -36,6 +38,16 @@ export const NEW_STEP_LIMITS = { hobbies: { min: 100, max: 500 }, funFact: { min: 100, max: 500 }, }, + stepFour: { + phoneNumber: { min: 1 }, + twitter: { min: 1 }, + github: { min: 1 }, + linkedin: { min: 1 }, + instagram: {}, + peerlist: { min: 1 }, + behance: { min: 1 }, + dribble: { min: 1 }, + }, }; export const STEP_DATA_STORAGE_KEY = { From cd7db59ffeaf2e4fdc07d5bfa8b99d15fdc67e6f Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Wed, 5 Nov 2025 04:28:15 +0530 Subject: [PATCH 22/35] feat: add snew step four for social links --- .../new-join-steps/new-step-four.hbs | 10 +++- .../new-join-steps/new-step-four.js | 57 ++++++++++++++----- app/constants/new-join-form.js | 2 +- app/utils/validator.js | 2 +- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/app/components/new-join-steps/new-step-four.hbs b/app/components/new-join-steps/new-step-four.hbs index 0d0367fa..27a6d96f 100644 --- a/app/components/new-join-steps/new-step-four.hbs +++ b/app/components/new-join-steps/new-step-four.hbs @@ -8,10 +8,11 @@ @field='Phone Number' @name='phoneNumber' @placeHolder='+91 80000 00000' - @type='text' + @type='tel' @required={{true}} @value={{this.data.phoneNumber}} @onInput={{this.inputHandler}} + @variant='input--full-width' /> {{#if this.errorMessage.phoneNumber}}
        {{this.errorMessage.phoneNumber}}
        @@ -25,6 +26,7 @@ @required={{true}} @value={{this.data.twitter}} @onInput={{this.inputHandler}} + @variant='input--full-width' /> {{#if this.errorMessage.twitter}}
        {{this.errorMessage.twitter}}
        @@ -39,6 +41,7 @@ @required={{true}} @value={{this.data.github}} @onInput={{this.inputHandler}} + @variant='input--full-width' /> {{#if this.errorMessage.github}}
        {{this.errorMessage.github}}
        @@ -53,6 +56,7 @@ @required={{true}} @value={{this.data.linkedin}} @onInput={{this.inputHandler}} + @variant='input--full-width' /> {{#if this.errorMessage.linkedin}}
        {{this.errorMessage.linkedin}}
        @@ -66,6 +70,7 @@ @required={{false}} @value={{this.data.instagram}} @onInput={{this.inputHandler}} + @variant='input--full-width' /> {{#if this.errorMessage.instagram}}
        {{this.errorMessage.instagram}}
        @@ -79,6 +84,7 @@ @required={{true}} @value={{this.data.peerlist}} @onInput={{this.inputHandler}} + @variant='input--full-width' /> {{#if this.errorMessage.peerlist}}
        {{this.errorMessage.peerlist}}
        @@ -93,6 +99,7 @@ @required={{true}} @value={{this.data.behance}} @onInput={{this.inputHandler}} + @variant='input--full-width' /> {{#if this.errorMessage.behance}}
        {{this.errorMessage.behance}}
        @@ -108,6 +115,7 @@ @required={{true}} @value={{this.data.dribble}} @onInput={{this.inputHandler}} + @variant='input--full-width' /> {{#if this.errorMessage.dribble}}
        {{this.errorMessage.dribble}}
        diff --git a/app/components/new-join-steps/new-step-four.js b/app/components/new-join-steps/new-step-four.js index 39b427b3..5dff623a 100644 --- a/app/components/new-join-steps/new-step-four.js +++ b/app/components/new-join-steps/new-step-four.js @@ -1,9 +1,18 @@ import BaseStepComponent from './base-step'; import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; +import { phoneNumberRegex } from '../../constants/regex'; export default class NewStepFourComponent extends BaseStepComponent { storageKey = 'newStepFourData'; + validationMap = { + phoneNumber: NEW_STEP_LIMITS.stepFour.phoneNumber, + twitter: NEW_STEP_LIMITS.stepFour.twitter, + linkedin: NEW_STEP_LIMITS.stepFour.linkedin, + instagram: NEW_STEP_LIMITS.stepFour.instagram, + peerlist: NEW_STEP_LIMITS.stepFour.peerlist, + }; + get userRole() { const stepOneData = JSON.parse( localStorage.getItem('newStepOneData') || '{}', @@ -11,25 +20,27 @@ export default class NewStepFourComponent extends BaseStepComponent { return stepOneData.role || ''; } - get validationMap() { - const baseMap = { - phoneNumber: NEW_STEP_LIMITS.stepFour.phoneNumber, - twitter: NEW_STEP_LIMITS.stepFour.twitter, - linkedin: NEW_STEP_LIMITS.stepFour.linkedin, - instagram: NEW_STEP_LIMITS.stepFour.instagram, - peerlist: NEW_STEP_LIMITS.stepFour.peerlist, - }; - + postLoadInitialize() { if (this.userRole === 'Developer') { - baseMap.github = NEW_STEP_LIMITS.stepFour.github; + this.validationMap.github = NEW_STEP_LIMITS.stepFour.github; } if (this.userRole === 'Designer') { - baseMap.behance = NEW_STEP_LIMITS.stepFour.behance; - baseMap.dribble = NEW_STEP_LIMITS.stepFour.dribble; + this.validationMap.behance = NEW_STEP_LIMITS.stepFour.behance; + this.validationMap.dribble = NEW_STEP_LIMITS.stepFour.dribble; } - return baseMap; + // re-calculate the errorMessage and wordCount for new input fields + this.errorMessage = Object.fromEntries( + Object.keys(this.validationMap).map((k) => [k, '']), + ); + + this.wordCount = Object.fromEntries( + Object.keys(this.validationMap).map((k) => { + let val = this.data[k] || ''; + return [k, val.trim().split(/\s+/).filter(Boolean).length || 0]; + }), + ); } get showGitHub() { @@ -43,4 +54,24 @@ export default class NewStepFourComponent extends BaseStepComponent { get showDribble() { return this.userRole === 'Designer'; } + + validateField(field, value) { + if (field === 'phoneNumber') { + const trimmedValue = value?.trim() || ''; + const isValid = trimmedValue && phoneNumberRegex.test(trimmedValue); + return { + isValid, + wordCount: 0, + }; + } + return super.validateField(field, value); + } + + formatError(field, result) { + if (field === 'phoneNumber') { + if (result.isValid) return ''; + return 'Please enter a valid phone number (e.g., +91 80000 00000)'; + } + return super.formatError(field, result); + } } diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index e59fb781..a4421183 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -43,7 +43,7 @@ export const NEW_STEP_LIMITS = { twitter: { min: 1 }, github: { min: 1 }, linkedin: { min: 1 }, - instagram: {}, + instagram: { min: 0 }, peerlist: { min: 1 }, behance: { min: 1 }, dribble: { min: 1 }, diff --git a/app/utils/validator.js b/app/utils/validator.js index d1cdd428..aa6f8825 100644 --- a/app/utils/validator.js +++ b/app/utils/validator.js @@ -19,7 +19,7 @@ export const validateWordCount = (text, wordLimits) => { const wordCount = trimmedText?.split(/\s+/).filter(Boolean).length ?? 0; const { min, max } = wordLimits; - if (!trimmedText) { + if (!trimmedText && min > 0) { return { isValid: false, wordCount: 0, remainingToMin: min }; } From 5fe42207d5e1892a938d960746605e91b66d344b Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Wed, 5 Nov 2025 04:46:14 +0530 Subject: [PATCH 23/35] feat: add new step fivve for stepper --- .../new-join-steps/new-step-five.hbs | 45 +++++++++++++++++++ .../new-join-steps/new-step-five.js | 16 +++++++ app/components/new-stepper.hbs | 9 ++++ app/constants/new-join-form.js | 6 +++ 4 files changed, 76 insertions(+) create mode 100644 app/components/new-join-steps/new-step-five.hbs create mode 100644 app/components/new-join-steps/new-step-five.js diff --git a/app/components/new-join-steps/new-step-five.hbs b/app/components/new-join-steps/new-step-five.hbs new file mode 100644 index 00000000..e0f52bbe --- /dev/null +++ b/app/components/new-join-steps/new-step-five.hbs @@ -0,0 +1,45 @@ +
        +
        +

        {{@heading}}

        +

        {{@subHeading}}

        +
        + + +{{#if this.errorMessage.whyRds}} +
        {{this.errorMessage.whyRds}}
        +{{/if}} + + +{{#if this.errorMessage.numberOfHours}} +
        {{this.errorMessage.numberOfHours}}
        +{{/if}} + + +{{#if this.errorMessage.foundFrom}} +
        {{this.errorMessage.foundFrom}}
        +{{/if}} +
        \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-five.js b/app/components/new-join-steps/new-step-five.js new file mode 100644 index 00000000..a66b2320 --- /dev/null +++ b/app/components/new-join-steps/new-step-five.js @@ -0,0 +1,16 @@ +import BaseStepComponent from './base-step'; +import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; +import { heardFrom } from '../../constants/social-data'; + +export default class NewStepFiveComponent extends BaseStepComponent { + storageKey = 'newStepFiveData'; + heardFrom = heardFrom; + + validationMap = { + whyRds: NEW_STEP_LIMITS.stepFive.whyRds, + foundFrom: NEW_STEP_LIMITS.stepFive.foundFrom, + }; + maxWords = { + whyRds: NEW_STEP_LIMITS.stepFive.whyRds.max, + }; +} diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index e49a0976..810415c6 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -57,6 +57,15 @@ @heading={{this.currentHeading}} @subHeading={{this.currentSubheading}} /> + + {{else if (eq this.currentStep 5)}} + {{/if}} diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index a4421183..5d7e0e33 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -4,12 +4,14 @@ export const NEW_FORM_STEPS = { 'Additional Personal Information', 'Your hobbies, interests, fun fact', 'Connect your social profiles', + 'Why Real Dev Squad?', ], subheadings: [ 'Please provide accurate information for verification purposes.', 'Introduce and help us get to know you better', 'Show us your funny and interesting side', 'Share your social media and professional profiles', + 'Tell us why you want to join our community', ], }; @@ -48,6 +50,10 @@ export const NEW_STEP_LIMITS = { behance: { min: 1 }, dribble: { min: 1 }, }, + stepFive: { + whyRds: { min: 100 }, + foundFrom: { min: 1 }, + }, }; export const STEP_DATA_STORAGE_KEY = { From 24ad300b6b6e72310d51a3245edc8e1cdc91a4ad Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Wed, 5 Nov 2025 19:07:30 +0530 Subject: [PATCH 24/35] fix: styling for selectt in new step five --- app/components/new-join-steps/new-step-five.hbs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/components/new-join-steps/new-step-five.hbs b/app/components/new-join-steps/new-step-five.hbs index e0f52bbe..e1089de3 100644 --- a/app/components/new-join-steps/new-step-five.hbs +++ b/app/components/new-join-steps/new-step-five.hbs @@ -16,8 +16,10 @@
        {{this.errorMessage.whyRds}}
        {{/if}} +
        +
        {{this.errorMessage.numberOfHours}}
        {{/if}} +
        +
        {{this.errorMessage.foundFrom}}
        {{/if}} +
        +
        \ No newline at end of file From 14566a3edae8b73150a0775457aded2bd233e1f2 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Mon, 10 Nov 2025 02:40:06 +0530 Subject: [PATCH 25/35] refactor: replace hardcoded storage keys with constants --- .../new-join-steps/new-step-five.hbs | 64 ++++----- .../new-join-steps/new-step-five.js | 12 +- .../new-join-steps/new-step-four.hbs | 128 +++++------------- .../new-join-steps/new-step-four.js | 19 +-- app/constants/new-join-form.js | 2 + 5 files changed, 75 insertions(+), 150 deletions(-) diff --git a/app/components/new-join-steps/new-step-five.hbs b/app/components/new-join-steps/new-step-five.hbs index e1089de3..ded36fdc 100644 --- a/app/components/new-join-steps/new-step-five.hbs +++ b/app/components/new-join-steps/new-step-five.hbs @@ -4,48 +4,30 @@

        {{@subHeading}}

        - -{{#if this.errorMessage.whyRds}} + + {{#if this.errorMessage.whyRds}}
        {{this.errorMessage.whyRds}}
        -{{/if}} + {{/if}} -
        -
        - -{{#if this.errorMessage.numberOfHours}} -
        {{this.errorMessage.numberOfHours}}
        -{{/if}} -
        +
        +
        + + {{#if this.errorMessage.numberOfHours}} +
        {{this.errorMessage.numberOfHours}}
        + {{/if}} +
        -
        - -{{#if this.errorMessage.foundFrom}} -
        {{this.errorMessage.foundFrom}}
        -{{/if}} -
        -
        +
        + + {{#if this.errorMessage.foundFrom}} +
        {{this.errorMessage.foundFrom}}
        + {{/if}} +
        +
        \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-five.js b/app/components/new-join-steps/new-step-five.js index a66b2320..71a35326 100644 --- a/app/components/new-join-steps/new-step-five.js +++ b/app/components/new-join-steps/new-step-five.js @@ -1,16 +1,16 @@ import BaseStepComponent from './base-step'; -import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; +import { + NEW_STEP_LIMITS, + STEP_DATA_STORAGE_KEY, +} from '../../constants/new-join-form'; import { heardFrom } from '../../constants/social-data'; export default class NewStepFiveComponent extends BaseStepComponent { - storageKey = 'newStepFiveData'; + storageKey = STEP_DATA_STORAGE_KEY.stepFive; heardFrom = heardFrom; - validationMap = { + stepValidation = { whyRds: NEW_STEP_LIMITS.stepFive.whyRds, foundFrom: NEW_STEP_LIMITS.stepFive.foundFrom, }; - maxWords = { - whyRds: NEW_STEP_LIMITS.stepFive.whyRds.max, - }; } diff --git a/app/components/new-join-steps/new-step-four.hbs b/app/components/new-join-steps/new-step-four.hbs index 27a6d96f..40106a44 100644 --- a/app/components/new-join-steps/new-step-four.hbs +++ b/app/components/new-join-steps/new-step-four.hbs @@ -4,121 +4,59 @@

        {{@subHeading}}

      - + {{#if this.errorMessage.phoneNumber}} -
      {{this.errorMessage.phoneNumber}}
      +
      {{this.errorMessage.phoneNumber}}
      {{/if}} - + {{#if this.errorMessage.twitter}} -
      {{this.errorMessage.twitter}}
      +
      {{this.errorMessage.twitter}}
      {{/if}} {{#if this.showGitHub}} - - {{#if this.errorMessage.github}} -
      {{this.errorMessage.github}}
      - {{/if}} + + {{#if this.errorMessage.github}} +
      {{this.errorMessage.github}}
      + {{/if}} {{/if}} - + {{#if this.errorMessage.linkedin}} -
      {{this.errorMessage.linkedin}}
      +
      {{this.errorMessage.linkedin}}
      {{/if}} - + {{#if this.errorMessage.instagram}} -
      {{this.errorMessage.instagram}}
      +
      {{this.errorMessage.instagram}}
      {{/if}} - + {{#if this.errorMessage.peerlist}} -
      {{this.errorMessage.peerlist}}
      +
      {{this.errorMessage.peerlist}}
      {{/if}} {{#if this.showBehance}} - - {{#if this.errorMessage.behance}} -
      {{this.errorMessage.behance}}
      - {{/if}} + + {{#if this.errorMessage.behance}} +
      {{this.errorMessage.behance}}
      + {{/if}} {{/if}} {{#if this.showDribble}} - - {{#if this.errorMessage.dribble}} -
      {{this.errorMessage.dribble}}
      - {{/if}} + + {{#if this.errorMessage.dribble}} +
      {{this.errorMessage.dribble}}
      + {{/if}} {{/if}} -
      +
    \ No newline at end of file diff --git a/app/components/new-join-steps/new-step-four.js b/app/components/new-join-steps/new-step-four.js index 5dff623a..bae00758 100644 --- a/app/components/new-join-steps/new-step-four.js +++ b/app/components/new-join-steps/new-step-four.js @@ -1,11 +1,14 @@ import BaseStepComponent from './base-step'; -import { NEW_STEP_LIMITS } from '../../constants/new-join-form'; +import { + NEW_STEP_LIMITS, + STEP_DATA_STORAGE_KEY, +} from '../../constants/new-join-form'; import { phoneNumberRegex } from '../../constants/regex'; export default class NewStepFourComponent extends BaseStepComponent { - storageKey = 'newStepFourData'; + storageKey = STEP_DATA_STORAGE_KEY.stepFour; - validationMap = { + stepValidation = { phoneNumber: NEW_STEP_LIMITS.stepFour.phoneNumber, twitter: NEW_STEP_LIMITS.stepFour.twitter, linkedin: NEW_STEP_LIMITS.stepFour.linkedin, @@ -22,21 +25,21 @@ export default class NewStepFourComponent extends BaseStepComponent { postLoadInitialize() { if (this.userRole === 'Developer') { - this.validationMap.github = NEW_STEP_LIMITS.stepFour.github; + this.stepValidation.github = NEW_STEP_LIMITS.stepFour.github; } if (this.userRole === 'Designer') { - this.validationMap.behance = NEW_STEP_LIMITS.stepFour.behance; - this.validationMap.dribble = NEW_STEP_LIMITS.stepFour.dribble; + this.stepValidation.behance = NEW_STEP_LIMITS.stepFour.behance; + this.stepValidation.dribble = NEW_STEP_LIMITS.stepFour.dribble; } // re-calculate the errorMessage and wordCount for new input fields this.errorMessage = Object.fromEntries( - Object.keys(this.validationMap).map((k) => [k, '']), + Object.keys(this.stepValidation).map((k) => [k, '']), ); this.wordCount = Object.fromEntries( - Object.keys(this.validationMap).map((k) => { + Object.keys(this.stepValidation).map((k) => { let val = this.data[k] || ''; return [k, val.trim().split(/\s+/).filter(Boolean).length || 0]; }), diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 5d7e0e33..11083928 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -60,4 +60,6 @@ export const STEP_DATA_STORAGE_KEY = { stepOne: 'newStepOneData', stepTwo: 'newStepTwoData', stepThree: 'newStepThreeData', + stepFour: 'newStepFourData', + stepFive: 'newStepFiveData', }; From e794f607e44a4c17872baeaffe3ccce2217a6b59 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Wed, 5 Nov 2025 17:19:13 +0530 Subject: [PATCH 26/35] feat: add review step for new stepper --- .../new-join-steps/new-step-six.hbs | 207 ++++++++++++++++++ app/components/new-join-steps/new-step-six.js | 65 ++++++ app/components/new-stepper.hbs | 54 +++-- app/components/new-stepper.js | 18 ++ app/constants/new-join-form.js | 2 + app/styles/new-stepper.module.css | 95 ++++++++ 6 files changed, 423 insertions(+), 18 deletions(-) create mode 100644 app/components/new-join-steps/new-step-six.hbs create mode 100644 app/components/new-join-steps/new-step-six.js diff --git a/app/components/new-join-steps/new-step-six.hbs b/app/components/new-join-steps/new-step-six.hbs new file mode 100644 index 00000000..aedcb588 --- /dev/null +++ b/app/components/new-join-steps/new-step-six.hbs @@ -0,0 +1,207 @@ +
    +
    +

    {{@heading}}

    +

    {{@subHeading}}

    +
    + +
    +
    +

    Personal Information

    + +
    +
    +
    + Full Name: + + {{if this.stepOneData.fullName this.stepOneData.fullName 'Not provided'}} + +
    +
    + Location: + + {{this.locationDisplay}} + +
    +
    + Applying as: + + {{if this.stepOneData.role this.stepOneData.role 'Not provided'}} + +
    +
    + Profile Image: + + Not uploaded + +
    +
    +
    + +
    +
    +

    Professional Details

    + +
    +
    +
    + Skills: + + {{if this.stepTwoData.skills this.stepTwoData.skills 'Not provided'}} + +
    +
    + Institution/Company: + + {{if this.stepTwoData.company this.stepTwoData.company 'Not provided'}} + +
    +
    + Introduction: + + {{if this.stepTwoData.introduction this.stepTwoData.introduction 'Not provided'}} + +
    +
    +
    + +
    +
    +

    Hobbies & Interests

    + +
    +
    +
    + Hobbies: + + {{if this.stepThreeData.hobbies this.stepThreeData.hobbies 'Not provided'}} + +
    +
    + Fun Fact: + + {{if this.stepThreeData.funFact this.stepThreeData.funFact 'Not provided'}} + +
    +
    +
    + +
    +
    +

    Social Profiles

    + +
    +
    +
    + Phone Number: + + {{if this.stepFourData.phoneNumber this.stepFourData.phoneNumber 'Not provided'}} + +
    +
    + Twitter: + + {{if this.stepFourData.twitter this.stepFourData.twitter 'Not provided'}} + +
    + {{#if this.showGitHub}} +
    + GitHub: + + {{if this.stepFourData.github this.stepFourData.github 'Not provided'}} + +
    + {{/if}} +
    + LinkedIn: + + {{if this.stepFourData.linkedin this.stepFourData.linkedin 'Not provided'}} + +
    +
    + Instagram: + + {{if this.stepFourData.instagram this.stepFourData.instagram 'Not uploaded'}} + +
    +
    + Peerlist: + + {{if this.stepFourData.peerlist this.stepFourData.peerlist 'Not provided'}} + +
    + {{#if this.showBehance}} +
    + Behance: + + {{if this.stepFourData.behance this.stepFourData.behance 'Not provided'}} + +
    + {{/if}} + {{#if this.showDribble}} +
    + Dribble: + + {{if this.stepFourData.dribble this.stepFourData.dribble 'Not provided'}} + +
    + {{/if}} +
    +
    + +
    +
    +

    Why Real Dev Squad?

    + +
    +
    +
    + Why you want to join Real Dev Squad?: + + {{if this.stepFiveData.whyRds this.stepFiveData.whyRds 'Not provided'}} + +
    +
    + Hours per week: + + {{if this.stepFiveData.numberOfHours this.stepFiveData.numberOfHours 'Not provided'}} + +
    +
    + How did you hear about us?: + + {{if this.stepFiveData.foundFrom this.stepFiveData.foundFrom 'Not provided'}} + +
    +
    +
    +
    diff --git a/app/components/new-join-steps/new-step-six.js b/app/components/new-join-steps/new-step-six.js new file mode 100644 index 00000000..ca2aac52 --- /dev/null +++ b/app/components/new-join-steps/new-step-six.js @@ -0,0 +1,65 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +export default class NewStepSixComponent extends Component { + @tracked stepOneData = {}; + @tracked stepTwoData = {}; + @tracked stepThreeData = {}; + @tracked stepFourData = {}; + @tracked stepFiveData = {}; + + constructor(...args) { + super(...args); + this.loadAllStepData(); + } + + loadAllStepData() { + this.stepOneData = JSON.parse( + localStorage.getItem('newStepOneData') || '{}', + ); + this.stepTwoData = JSON.parse( + localStorage.getItem('newStepTwoData') || '{}', + ); + this.stepThreeData = JSON.parse( + localStorage.getItem('newStepThreeData') || '{}', + ); + this.stepFourData = JSON.parse( + localStorage.getItem('newStepFourData') || '{}', + ); + this.stepFiveData = JSON.parse( + localStorage.getItem('newStepFiveData') || '{}', + ); + } + + get userRole() { + return this.stepOneData.role || ''; + } + + get showGitHub() { + return this.userRole === 'Developer'; + } + + get showBehance() { + return this.userRole === 'Designer'; + } + + get showDribble() { + return this.userRole === 'Designer'; + } + + get locationDisplay() { + const parts = []; + if (this.stepOneData.city) parts.push(this.stepOneData.city); + if (this.stepOneData.state) parts.push(this.stepOneData.state); + if (this.stepOneData.country) parts.push(this.stepOneData.country); + return parts.length > 0 ? parts.join(', ') : 'Not provided'; + } + + get hasLocation() { + return !!( + this.stepOneData.country || + this.stepOneData.state || + this.stepOneData.city + ); + } +} diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 810415c6..9f81393e 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -32,9 +32,9 @@ /> {{else if (eq this.currentStep 2)}} - - + {{else if (eq this.currentStep 5)}} - + + {{else if (eq this.currentStep 6)}} + {{/if}} @@ -83,14 +91,24 @@
    {{/if}} - + {{#if (eq this.currentStep 6)}} + + {{else}} + + {{/if}}
    {{/if}} \ No newline at end of file diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index c65366d6..627013b3 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -70,4 +70,22 @@ export default class NewStepperComponent extends Component { sessionStorage.setItem('last_name', this.login.userData.last_name); this.incrementStep(); } + + @action navigateToStep(stepNumber) { + if (stepNumber >= this.MIN_STEP + 1 && stepNumber <= this.MAX_STEP) { + this.isValid = false; + this.preValid = false; + this.currentStep = stepNumber; + localStorage.setItem('currentStep', this.currentStep); + localStorage.setItem('isValid', false); + this.router.transitionTo({ + queryParams: { dev: true, step: this.currentStep }, + }); + } + } + + // handle create application using this action + @action handleSubmit() { + console.log('Submit review'); + } } diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 11083928..520bfcf0 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -5,6 +5,7 @@ export const NEW_FORM_STEPS = { 'Your hobbies, interests, fun fact', 'Connect your social profiles', 'Why Real Dev Squad?', + 'Review and Submit', ], subheadings: [ 'Please provide accurate information for verification purposes.', @@ -12,6 +13,7 @@ export const NEW_FORM_STEPS = { 'Show us your funny and interesting side', 'Share your social media and professional profiles', 'Tell us why you want to join our community', + 'Review your answers before submitting.', ], }; diff --git a/app/styles/new-stepper.module.css b/app/styles/new-stepper.module.css index bbef5d22..91412635 100644 --- a/app/styles/new-stepper.module.css +++ b/app/styles/new-stepper.module.css @@ -500,3 +500,98 @@ font-size: 0.875rem; } } + +.review-section { + background-color: var(--color-bgpink); + border-radius: 10px; + padding: 1.5rem; + margin-bottom: 1.5rem; + box-shadow: 0 2px 4px var(--color-blackshadow2); +} + +.review-section__header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--color-lightgrey); +} + +.review-section__title { + font-size: 1.1rem; + font-weight: 600; + color: var(--color-black); + margin: 0; +} + +.review-section__content { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.review-field { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 0.5rem 0; + gap: 1rem; +} + +.review-field__label { + font-weight: 500; + color: var(--color-black); + flex-shrink: 0; + min-width: 200px; +} + +.review-field__value { + text-align: right; + color: var(--color-black); + word-wrap: break-word; + flex: 1; + max-width: 60%; +} + +.review-field__value--missing { + color: var(--text-red); +} + +.review-field__value--empty { + color: var(--color-lightgrey); +} + +.review-actions { + display: flex; + justify-content: center; + margin-top: 2rem; + padding-top: 1.5rem; + border-top: 1px solid var(--color-lightgrey); +} + +@media screen and (width <= 768px) { + .review-section { + padding: 1rem; + } + + .review-section__header { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .review-field { + flex-direction: column; + gap: 0.25rem; + } + + .review-field__label { + min-width: auto; + } + + .review-field__value { + text-align: left; + max-width: 100%; + } +} From c4a96bde999cef393253610b8e188cfbcd27117c Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Sun, 9 Nov 2025 20:20:34 +0530 Subject: [PATCH 27/35] feat: add thank you screen for new stepper --- .../new-join-steps/thank-you-screen.hbs | 33 ++++ app/components/new-stepper.hbs | 11 +- app/components/new-stepper.js | 21 ++- app/styles/new-stepper.module.css | 158 ++++++------------ 4 files changed, 107 insertions(+), 116 deletions(-) create mode 100644 app/components/new-join-steps/thank-you-screen.hbs diff --git a/app/components/new-join-steps/thank-you-screen.hbs b/app/components/new-join-steps/thank-you-screen.hbs new file mode 100644 index 00000000..b014fad5 --- /dev/null +++ b/app/components/new-join-steps/thank-you-screen.hbs @@ -0,0 +1,33 @@ +
    + + +
    +

    {{@firstName}}, thank you for applying to RDS.

    +

    Great work filling up the application. However, it takes more to join us early.

    +
    + +
    +
    + Head over to Application Tracking Page. +
    + +
    + Checkout AI review and and edit your application to improve application rank. +
    + +
    + Complete quests to improve your ranking and increase your chances of early reviews. +
    +
    + +
    +

    Application ID

    +

    {{@applicationId}}

    +
    + +
    + +
    +
    \ No newline at end of file diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 9f81393e..6ae3fe09 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -1,6 +1,6 @@
    - {{#if (not-eq this.currentStep this.MIN_STEP)}} + {{#if (and (not-eq this.currentStep this.MIN_STEP) (not-eq this.currentStep 7))}} + + {{else if (eq this.currentStep 7)}} + {{/if}} - {{#if (not-eq this.currentStep this.MIN_STEP)}} + {{#if (and (not-eq this.currentStep this.MIN_STEP) (not-eq this.currentStep 7))}}
    {{#if this.showPreviousButton}} Date: Sun, 9 Nov 2025 20:44:01 +0530 Subject: [PATCH 28/35] refactor: use structure for step data and replace hardcoded storage keys --- .../new-join-steps/new-step-six.hbs | 74 +++++++++---------- app/components/new-join-steps/new-step-six.js | 50 ++++++------- 2 files changed, 58 insertions(+), 66 deletions(-) diff --git a/app/components/new-join-steps/new-step-six.hbs b/app/components/new-join-steps/new-step-six.hbs index aedcb588..cb18d7d5 100644 --- a/app/components/new-join-steps/new-step-six.hbs +++ b/app/components/new-join-steps/new-step-six.hbs @@ -18,20 +18,20 @@
    Full Name: - - {{if this.stepOneData.fullName this.stepOneData.fullName 'Not provided'}} + + {{if this.stepData.one.fullName this.stepData.one.fullName 'Not provided'}}
    Location: - + {{this.locationDisplay}}
    Applying as: - - {{if this.stepOneData.role this.stepOneData.role 'Not provided'}} + + {{if this.stepData.one.role this.stepData.one.role 'Not provided'}}
    @@ -57,20 +57,20 @@
    Skills: - - {{if this.stepTwoData.skills this.stepTwoData.skills 'Not provided'}} + + {{if this.stepData.two.skills this.stepData.two.skills 'Not provided'}}
    Institution/Company: - - {{if this.stepTwoData.company this.stepTwoData.company 'Not provided'}} + + {{if this.stepData.two.company this.stepData.two.company 'Not provided'}}
    Introduction: - - {{if this.stepTwoData.introduction this.stepTwoData.introduction 'Not provided'}} + + {{if this.stepData.two.introduction this.stepData.two.introduction 'Not provided'}}
    @@ -90,14 +90,14 @@
    Hobbies: - - {{if this.stepThreeData.hobbies this.stepThreeData.hobbies 'Not provided'}} + + {{if this.stepData.three.hobbies this.stepData.three.hobbies 'Not provided'}}
    Fun Fact: - - {{if this.stepThreeData.funFact this.stepThreeData.funFact 'Not provided'}} + + {{if this.stepData.three.funFact this.stepData.three.funFact 'Not provided'}}
    @@ -117,55 +117,55 @@
    Phone Number: - - {{if this.stepFourData.phoneNumber this.stepFourData.phoneNumber 'Not provided'}} + + {{if this.stepData.four.phoneNumber this.stepData.four.phoneNumber 'Not provided'}}
    Twitter: - - {{if this.stepFourData.twitter this.stepFourData.twitter 'Not provided'}} + + {{if this.stepData.four.twitter this.stepData.four.twitter 'Not provided'}}
    {{#if this.showGitHub}}
    GitHub: - - {{if this.stepFourData.github this.stepFourData.github 'Not provided'}} + + {{if this.stepData.four.github this.stepData.four.github 'Not provided'}}
    {{/if}}
    LinkedIn: - - {{if this.stepFourData.linkedin this.stepFourData.linkedin 'Not provided'}} + + {{if this.stepData.four.linkedin this.stepData.four.linkedin 'Not provided'}}
    Instagram: - - {{if this.stepFourData.instagram this.stepFourData.instagram 'Not uploaded'}} + + {{if this.stepData.four.instagram this.stepData.four.instagram 'Not uploaded'}}
    Peerlist: - - {{if this.stepFourData.peerlist this.stepFourData.peerlist 'Not provided'}} + + {{if this.stepData.four.peerlist this.stepData.four.peerlist 'Not provided'}}
    {{#if this.showBehance}}
    Behance: - - {{if this.stepFourData.behance this.stepFourData.behance 'Not provided'}} + + {{if this.stepData.four.behance this.stepData.four.behance 'Not provided'}}
    {{/if}} {{#if this.showDribble}}
    Dribble: - - {{if this.stepFourData.dribble this.stepFourData.dribble 'Not provided'}} + + {{if this.stepData.four.dribble this.stepData.four.dribble 'Not provided'}}
    {{/if}} @@ -186,20 +186,20 @@
    Why you want to join Real Dev Squad?: - - {{if this.stepFiveData.whyRds this.stepFiveData.whyRds 'Not provided'}} + + {{if this.stepData.five.whyRds this.stepData.five.whyRds 'Not provided'}}
    Hours per week: - - {{if this.stepFiveData.numberOfHours this.stepFiveData.numberOfHours 'Not provided'}} + + {{if this.stepData.five.numberOfHours this.stepData.five.numberOfHours 'Not provided'}}
    How did you hear about us?: - - {{if this.stepFiveData.foundFrom this.stepFiveData.foundFrom 'Not provided'}} + + {{if this.stepData.five.foundFrom this.stepData.five.foundFrom 'Not provided'}}
    diff --git a/app/components/new-join-steps/new-step-six.js b/app/components/new-join-steps/new-step-six.js index ca2aac52..9928e663 100644 --- a/app/components/new-join-steps/new-step-six.js +++ b/app/components/new-join-steps/new-step-six.js @@ -1,12 +1,16 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; +import { getLocalStorageItem } from '../../utils/storage'; +import { STEP_DATA_STORAGE_KEY } from '../../constants/new-join-form'; export default class NewStepSixComponent extends Component { - @tracked stepOneData = {}; - @tracked stepTwoData = {}; - @tracked stepThreeData = {}; - @tracked stepFourData = {}; - @tracked stepFiveData = {}; + @tracked stepData = { + one: {}, + two: {}, + three: {}, + four: {}, + five: {}, + }; constructor(...args) { super(...args); @@ -14,25 +18,25 @@ export default class NewStepSixComponent extends Component { } loadAllStepData() { - this.stepOneData = JSON.parse( - localStorage.getItem('newStepOneData') || '{}', + this.stepData.one = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepOne), ); - this.stepTwoData = JSON.parse( - localStorage.getItem('newStepTwoData') || '{}', + this.stepData.two = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepTwo), ); - this.stepThreeData = JSON.parse( - localStorage.getItem('newStepThreeData') || '{}', + this.stepData.three = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepThree), ); - this.stepFourData = JSON.parse( - localStorage.getItem('newStepFourData') || '{}', + this.stepData.four = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepFour), ); - this.stepFiveData = JSON.parse( - localStorage.getItem('newStepFiveData') || '{}', + this.stepData.five = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepFive), ); } get userRole() { - return this.stepOneData.role || ''; + return this.stepData.one.role || ''; } get showGitHub() { @@ -48,18 +52,6 @@ export default class NewStepSixComponent extends Component { } get locationDisplay() { - const parts = []; - if (this.stepOneData.city) parts.push(this.stepOneData.city); - if (this.stepOneData.state) parts.push(this.stepOneData.state); - if (this.stepOneData.country) parts.push(this.stepOneData.country); - return parts.length > 0 ? parts.join(', ') : 'Not provided'; - } - - get hasLocation() { - return !!( - this.stepOneData.country || - this.stepOneData.state || - this.stepOneData.city - ); + return `${this.stepData.one.city}, ${this.stepData.one.state}, ${this.stepData.one.country}`; } } From 7dd08c554b43c48f025da962bcb7e809068d1a12 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Mon, 10 Nov 2025 22:31:31 +0530 Subject: [PATCH 29/35] refactor: add svalidation for current step --- app/components/new-join-steps/base-step.js | 7 +- app/components/new-stepper.hbs | 22 +--- app/components/new-stepper.js | 146 +++++++++++++++++---- 3 files changed, 130 insertions(+), 45 deletions(-) diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js index 5e8e1a49..e14acd0a 100644 --- a/app/components/new-join-steps/base-step.js +++ b/app/components/new-join-steps/base-step.js @@ -43,12 +43,10 @@ export default class BaseStepComponent extends Component { this.postLoadInitialize(); const valid = this.isDataValid(); - this.args.setIsPreValid(valid); - setLocalStorageItem('isValid', String(valid)); + this.args.onValidityChange(valid); } @action inputHandler(e) { - this.args.setIsPreValid(false); const field = e.target.name; const value = e.target.value; debounce(this, this.handleFieldUpdate, field, value, JOIN_DEBOUNCE_TIME); @@ -103,7 +101,6 @@ export default class BaseStepComponent extends Component { syncFormValidity() { const allValid = this.isDataValid(); - this.args.setIsValid(allValid); - setLocalStorageItem('isValid', String(allValid)); + this.args.onValidityChange(allValid); } } diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 6ae3fe09..63b8666e 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -26,43 +26,33 @@ {{else if (eq this.currentStep 1)}} {{else if (eq this.currentStep 2)}} {{else if (eq this.currentStep 3)}} {{else if (eq this.currentStep 4)}} {{else if (eq this.currentStep 5)}} @@ -113,7 +103,7 @@ @test="next-btn" @type="button" @onClick={{this.incrementStep}} - @disabled={{not (or this.preValid this.isValid)}} + @disabled={{not this.canProceedFromStep}} /> {{/if}}
    diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index 1d16a3a3..9f71cc8b 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -2,8 +2,38 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; -import { NEW_FORM_STEPS } from '../constants/new-join-form'; +import { + NEW_FORM_STEPS, + NEW_STEP_LIMITS, + STEP_DATA_STORAGE_KEY, +} from '../constants/new-join-form'; import { getLocalStorageItem, setLocalStorageItem } from '../utils/storage'; +import { scheduleOnce } from '@ember/runloop'; +import { validateWordCount } from '../utils/validator'; +import { phoneNumberRegex } from '../constants/regex'; + +const NEW_STEP_CONFIG = { + 1: { + storageKey: STEP_DATA_STORAGE_KEY.stepOne, + limits: NEW_STEP_LIMITS.stepOne, + }, + 2: { + storageKey: STEP_DATA_STORAGE_KEY.stepTwo, + limits: NEW_STEP_LIMITS.stepTwo, + }, + 3: { + storageKey: STEP_DATA_STORAGE_KEY.stepThree, + limits: NEW_STEP_LIMITS.stepThree, + }, + 4: { + storageKey: STEP_DATA_STORAGE_KEY.stepFour, + limits: NEW_STEP_LIMITS.stepFour, + }, + 5: { + storageKey: STEP_DATA_STORAGE_KEY.stepFive, + limits: NEW_STEP_LIMITS.stepFive, + }, +}; export default class NewStepperComponent extends Component { MIN_STEP = 0; @@ -16,13 +46,32 @@ export default class NewStepperComponent extends Component { @service joinApplicationTerms; @tracked currentStep = - Number(getLocalStorageItem('currentStep') ?? this.args.step) || 0; + Number(this.args.step || getLocalStorageItem('currentStep')) || + this.MIN_STEP; + @tracked canAccessStep = false; + @tracked canProceedFromStep = false; + + constructor(...args) { + super(...args); + scheduleOnce('afterRender', this, this.initializeFromQuery); + } - @tracked preValid = false; - @tracked isValid = getLocalStorageItem('isValid') === 'true'; + clampStep(step) { + return Math.max(this.MIN_STEP, Math.min(this.MAX_STEP + 1, step)); + } - setIsValid = (newVal) => (this.isValid = newVal); - setIsPreValid = (newVal) => (this.preValid = newVal); + persistStep(step) { + setLocalStorageItem('currentStep', String(step)); + } + + initializeFromQuery() { + const targetStep = this.currentStep; + const accessibleStep = this.resolveAccessibleStep(targetStep); + this.currentStep = accessibleStep; + this.canProceedFromStep = this.isStepComplete(accessibleStep); + this.persistStep(accessibleStep); + this.updateQueryParam(accessibleStep); + } updateQueryParam(step) { const existingQueryParams = this.router.currentRoute?.queryParams; @@ -50,20 +99,69 @@ export default class NewStepperComponent extends Component { return localStorage.getItem('first_name') ?? ''; } + @action onCurrentStepValidityChange(isValid) { + this.canProceedFromStep = Boolean(isValid); + if (isValid) { + this.persistStep(this.currentStep); + } + } + + resolveAccessibleStep(stepNumber) { + const desiredStep = this.clampStep(stepNumber); + for (let step = 1; step < desiredStep; step++) { + if (!this.isStepComplete(step)) { + this.canAccessStep = false; + return step; + } + } + + this.canAccessStep = true; + return desiredStep; + } + + isStepComplete(stepNumber) { + const config = NEW_STEP_CONFIG[stepNumber]; + if (!config) { + return true; + } + + const stored = JSON.parse(getLocalStorageItem(config.storageKey, '{}')); + + for (const [field, limits] of Object.entries(config.limits || {})) { + const value = stored?.[field] ?? ''; + if (field === 'phoneNumber') { + return phoneNumberRegex.test(value); + } + const result = validateWordCount(value, limits); + if (!result.isValid) { + return false; + } + } + return true; + } + @action incrementStep() { - if (this.currentStep < this.MAX_STEP) { - const nextStep = this.currentStep + 1; - setLocalStorageItem('currentStep', String(nextStep)); + const current = this.currentStep; + if (current < this.MAX_STEP && this.isStepComplete(current)) { + const nextStep = current + 1; + this.canAccessStep = true; + this.canProceedFromStep = this.isStepComplete(nextStep); this.currentStep = nextStep; + this.persistStep(nextStep); this.updateQueryParam(nextStep); + } else { + this.canProceedFromStep = false; } } @action decrementStep() { - if (this.currentStep > this.MIN_STEP) { - const previousStep = this.currentStep - 1; - setLocalStorageItem('currentStep', String(previousStep)); + const current = this.currentStep; + if (current > this.MIN_STEP) { + const previousStep = current - 1; + this.canAccessStep = true; + this.canProceedFromStep = this.isStepComplete(previousStep); this.currentStep = previousStep; + this.persistStep(previousStep); this.updateQueryParam(previousStep); } } @@ -76,21 +174,21 @@ export default class NewStepperComponent extends Component { } @action navigateToStep(stepNumber) { - if (stepNumber >= this.MIN_STEP + 1 && stepNumber <= this.MAX_STEP) { - this.isValid = false; - this.preValid = false; - this.currentStep = stepNumber; - setLocalStorageItem('currentStep', String(stepNumber)); - setLocalStorageItem('isValid', 'false'); - this.updateQueryParam(stepNumber); - } + const desired = this.resolveAccessibleStep(stepNumber); + this.canProceedFromStep = this.isStepComplete(desired); + this.currentStep = desired; + this.persistStep(desired); + this.updateQueryParam(desired); } @action handleSubmit() { - // ToDo: handle create application + // ToDo: handle create application and move thank you screen away from new stepper console.log('Submit application for review'); - this.currentStep = this.MAX_STEP + 1; - setLocalStorageItem('currentStep', String(this.currentStep)); - this.updateQueryParam(this.currentStep); + const completionStep = this.MAX_STEP + 1; + this.currentStep = completionStep; + this.persistStep(completionStep); + this.canAccessStep = true; + this.canProceedFromStep = false; + this.updateQueryParam(completionStep); } } From 2da4b3f1c904f9a3f35edd41f1e6e8acbf08f9ad Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Mon, 10 Nov 2025 23:18:35 +0530 Subject: [PATCH 30/35] fix: stub router for new stepper test --- tests/integration/components/new-stepper-test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/integration/components/new-stepper-test.js b/tests/integration/components/new-stepper-test.js index bce8ee5c..dc84904d 100644 --- a/tests/integration/components/new-stepper-test.js +++ b/tests/integration/components/new-stepper-test.js @@ -2,10 +2,20 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'website-www/tests/helpers'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; +import Service from '@ember/service'; module('Integration | Component | new-stepper', function (hooks) { setupRenderingTest(hooks); + hooks.beforeEach(function () { + class RouterStub extends Service { + currentRoute = { queryParams: {} }; + transitionTo() {} + } + + this.owner.register('service:router', RouterStub); + }); + test('it renders the welcome screen at step 0', async function (assert) { await render(hbs``); From aaa348cfaa0c167dc21e8eb06118ad514babf3fe Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 11 Nov 2025 01:26:36 +0530 Subject: [PATCH 31/35] test: add unit test for base-step --- .../new-join-steps/base-step-test.js | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/unit/components/new-join-steps/base-step-test.js diff --git a/tests/unit/components/new-join-steps/base-step-test.js b/tests/unit/components/new-join-steps/base-step-test.js new file mode 100644 index 00000000..139efb36 --- /dev/null +++ b/tests/unit/components/new-join-steps/base-step-test.js @@ -0,0 +1,120 @@ +import { setOwner } from '@ember/application'; +import { module, test } from 'qunit'; +import BaseStepComponent from 'website-www/components/new-join-steps/base-step'; +import { setupTest } from 'website-www/tests/helpers'; + +module('Unit | Component | new-join-steps/base-step', function (hooks) { + setupTest(hooks); + + hooks.beforeEach(function () { + const TestComponent = class extends BaseStepComponent { + constructor(owner, args) { + super(owner, args); + } + }; + this.component = Object.create(TestComponent.prototype); + setOwner(this.component, this.owner); + this.component.args = { onValidityChange: () => {} }; + this.component.data = {}; + this.component.errorMessage = {}; + this.component.wordCount = {}; + this.component.stepValidation = {}; + }); + + test('validateField returns correct result for valid input', function (assert) { + this.component.stepValidation = { field1: { min: 1, max: 10 } }; + const result = this.component.validateField('field1', 'valid input'); + + assert.true(result.isValid); + assert.strictEqual(result.wordCount, 2); + }); + + test('validateField returns invalid for empty required field', function (assert) { + this.component.stepValidation = { field1: { min: 1, max: 10 } }; + const result = this.component.validateField('field1', ''); + + assert.false(result.isValid); + assert.strictEqual(result.remainingToMin, 1); + }); + + test('validateField returns invalid when exceeds max words', function (assert) { + this.component.stepValidation = { field1: { min: 1, max: 3 } }; + const result = this.component.validateField('field1', 'one two three four'); + assert.false(result.isValid); + }); + + test('isDataValid returns true when all fields valid', function (assert) { + this.component.stepValidation = { + field1: { min: 1, max: 10 }, + field2: { min: 1, max: 5 }, + }; + this.component.data = { field1: 'valid text', field2: 'ok' }; + + const result = this.component.isDataValid(); + assert.true(result); + }); + + test('isDataValid returns false when any field invalid', function (assert) { + this.component.stepValidation = { + field1: { min: 1, max: 10 }, + field2: { min: 1, max: 5 }, + }; + this.component.data = { field1: 'valid text', field2: '' }; + + const result = this.component.isDataValid(); + assert.false(result); + }); + + test('updateFieldValue updates data and syncs storage', function (assert) { + this.component.data = {}; + Object.defineProperty(this.component, 'storageKey', { + value: 'test-key', + writable: true, + configurable: true, + }); + + this.component.updateFieldValue('field1', 'new value'); + assert.strictEqual(this.component.data.field1, 'new value'); + }); + + test('updateWordCount updates word count correctly', function (assert) { + this.component.wordCount = {}; + this.component.updateWordCount('field1', { wordCount: 5 }); + assert.strictEqual(this.component.wordCount.field1, 5); + }); + + test('formatError returns empty string when valid', function (assert) { + this.component.stepValidation = { field1: { min: 1, max: 10 } }; + const result = this.component.formatError('field1', { isValid: true }); + assert.strictEqual(result, ''); + }); + + test('formatError returns min words message', function (assert) { + this.component.stepValidation = { field1: { min: 5, max: 10 } }; + const result = this.component.formatError('field1', { + isValid: false, + remainingToMin: 3, + }); + assert.strictEqual(result, 'At least 3 more word(s) required'); + }); + + test('formatError returns max words message', function (assert) { + this.component.stepValidation = { field1: { min: 1, max: 5 } }; + const result = this.component.formatError('field1', { isValid: false }); + assert.strictEqual(result, 'Maximum 5 words allowed'); + }); + + test('updateErrorMessage updates error correctly', function (assert) { + this.component.stepValidation = { field1: { min: 1, max: 10 } }; + this.component.errorMessage = {}; + this.component.updateErrorMessage('field1', { + isValid: false, + remainingToMin: 2, + }); + + assert.strictEqual( + this.component.errorMessage.field1, + 'At least 2 more word(s) required', + ); + }); +}); From 1b8f4777631ad5825763b179f75207bcdb81b034 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 11 Nov 2025 19:29:07 +0530 Subject: [PATCH 32/35] refactor: enhance image upload and new stepper error handling --- app/components/new-join-steps/base-step.js | 18 ++++-- .../new-join-steps/new-step-one.hbs | 25 +++++--- app/components/new-join-steps/new-step-one.js | 57 +++++++++++++++---- .../new-join-steps/stepper-header.js | 2 +- app/components/new-stepper.hbs | 4 +- app/components/new-stepper.js | 4 ++ app/utils/validator.js | 6 +- 7 files changed, 85 insertions(+), 31 deletions(-) diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js index 5e8e1a49..be48caa1 100644 --- a/app/components/new-join-steps/base-step.js +++ b/app/components/new-join-steps/base-step.js @@ -26,8 +26,15 @@ export default class BaseStepComponent extends Component { } initializeFormState() { - const saved = JSON.parse(getLocalStorageItem(this.storageKey, '{}')); - this.data = saved ?? {}; + let saved = {}; + try { + const stored = getLocalStorageItem(this.storageKey, '{}'); + saved = stored ? JSON.parse(stored) : {}; + } catch (e) { + console.warn('Failed to parse stored form data:', e); + saved = {}; + } + this.data = saved; this.errorMessage = Object.fromEntries( Object.keys(this.stepValidation).map((k) => [k, '']), @@ -35,7 +42,7 @@ export default class BaseStepComponent extends Component { this.wordCount = Object.fromEntries( Object.keys(this.stepValidation).map((k) => { - let val = this.data[k] || ''; + let val = String(this.data[k] || ''); return [k, val.trim().split(/\s+/).filter(Boolean).length || 0]; }), ); @@ -48,6 +55,7 @@ export default class BaseStepComponent extends Component { } @action inputHandler(e) { + if (!e?.target) return; this.args.setIsPreValid(false); const field = e.target.name; const value = e.target.value; @@ -60,7 +68,7 @@ export default class BaseStepComponent extends Component { } isDataValid() { - for (let field of Object.keys(this.stepValidation)) { + for (const field of Object.keys(this.stepValidation)) { const result = this.validateField(field, this.data[field]); if (!result.isValid) return false; } @@ -98,7 +106,7 @@ export default class BaseStepComponent extends Component { if (result.remainingToMin) { return `At least ${result.remainingToMin} more word(s) required`; } - return `Maximum ${limits.max} words allowed`; + return `Maximum ${limits?.max ?? 'N/A'} words allowed`; } syncFormValidity() { diff --git a/app/components/new-join-steps/new-step-one.hbs b/app/components/new-join-steps/new-step-one.hbs index 2bb38f03..bbe08122 100644 --- a/app/components/new-join-steps/new-step-one.hbs +++ b/app/components/new-join-steps/new-step-one.hbs @@ -1,19 +1,26 @@

    Profile Picture

    - {{#if this.imagePreview}} + {{#if this.isImageUploading}}
    - Profile preview - + +

    Processing image...

    {{else}} - + {{#if this.imagePreview}} +
    + Profile preview + +
    + {{else}} + + {{/if}} {{/if}} - +

    Image Requirements:

      diff --git a/app/components/new-join-steps/new-step-one.js b/app/components/new-join-steps/new-step-one.js index 6e3864d2..86b3d720 100644 --- a/app/components/new-join-steps/new-step-one.js +++ b/app/components/new-join-steps/new-step-one.js @@ -11,11 +11,14 @@ import BaseStepComponent from './base-step'; export default class NewStepOneComponent extends BaseStepComponent { @service login; + @service toast; roleOptions = ROLE_OPTIONS; countries = countryList; @tracked imagePreview = null; + @tracked isImageUploading = false; + @tracked fileInputElement = null; get storageKey() { return STEP_DATA_STORAGE_KEY.stepOne; @@ -29,7 +32,11 @@ export default class NewStepOneComponent extends BaseStepComponent { }; postLoadInitialize() { - if (!this.data.fullName && this.login.userData) { + if ( + !this.data.fullName && + this.login.userData?.first_name && + this.login.userData?.last_name + ) { this.updateFieldValue( 'fullName', `${this.login.userData.first_name} ${this.login.userData.last_name}`, @@ -44,28 +51,54 @@ export default class NewStepOneComponent extends BaseStepComponent { this.inputHandler({ target: { name: 'role', value: role } }); } - @action triggerFileInput() { - const fileInput = document.getElementById('profile-image-input'); - if (fileInput) { - fileInput.click(); - } + @action + setFileInputElement(element) { + this.fileInputElement = element; } - @action handleImageSelect(event) { - const file = event.target.files?.[0]; - if (!file) return; + @action + clearFileInputElement() { + this.fileInputElement = null; + } - if (file.size > 2 * 1024 * 1024) { - alert('Image size must be less than 2MB'); + @action + triggerFileInput() { + this.fileInputElement?.click(); + } + + @action + handleImageSelect(event) { + const file = event.target.files?.[0]; + if (!file || !file.type.startsWith('image/')) { + this.toast.error( + 'Invalid file type. Please upload an image file.', + 'Error!', + ); return; } + const maxSize = 2 * 1024 * 1024; + if (file.size > maxSize) { + this.toast.error('Image size must be less than 2MB', 'Error!'); + return; + } + + this.isImageUploading = true; const reader = new FileReader(); reader.onload = (e) => { const base64String = e.target.result; this.imagePreview = base64String; - this.updateFieldValue('profileImageBase64', base64String); + this.updateFieldValue?.('profileImageBase64', base64String); + this.isImageUploading = false; }; + reader.onerror = () => { + this.toast.error( + 'Failed to read the selected file. Please try again.', + 'Error!', + ); + this.isImageUploading = false; + }; + reader.readAsDataURL(file); } } diff --git a/app/components/new-join-steps/stepper-header.js b/app/components/new-join-steps/stepper-header.js index 9b81fd3c..77ce058e 100644 --- a/app/components/new-join-steps/stepper-header.js +++ b/app/components/new-join-steps/stepper-header.js @@ -7,7 +7,7 @@ export default class StepperHeaderComponent extends Component { get progressPercentage() { const totalSteps = Number(this.args.totalSteps) || 1; const currentStep = Number(this.args.currentStep) || 0; - return Math.round((currentStep / totalSteps) * 100); + return Math.min(100, Math.round((currentStep / totalSteps) * 100)); } @cached diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 9c6ae4de..05c567e6 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -2,8 +2,6 @@ {{#if (not-eq this.currentStep this.MIN_STEP)}} @@ -53,7 +51,7 @@ @test="next-btn" @type="button" @onClick={{this.incrementStep}} - @disabled={{not (or this.preValid this.isValid)}} + @disabled={{this.isNextButtonDisabled}} />
    {{/if}} diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index c65366d6..73993fef 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -46,6 +46,10 @@ export default class NewStepperComponent extends Component { return NEW_FORM_STEPS.subheadings[this.currentStep - 1] ?? ''; } + get isNextButtonDisabled() { + return !(this.preValid || this.isValid); + } + @action incrementStep() { if (this.currentStep < this.MAX_STEP) { const nextStep = this.currentStep + 1; diff --git a/app/utils/validator.js b/app/utils/validator.js index d1cdd428..c9bdcd9c 100644 --- a/app/utils/validator.js +++ b/app/utils/validator.js @@ -20,7 +20,11 @@ export const validateWordCount = (text, wordLimits) => { const { min, max } = wordLimits; if (!trimmedText) { - return { isValid: false, wordCount: 0, remainingToMin: min }; + return { + isValid: min === 0, + wordCount: 0, + remainingToMin: min > 0 ? min : 0, + }; } if (wordCount < min) { From 677dc2758925e36e998137e94bcf8e33f4f90a8f Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 11 Nov 2025 20:43:37 +0530 Subject: [PATCH 33/35] refactor: validation for fields in new stepper and enhance button layout --- app/components/new-join-steps/base-step.js | 11 ++++ app/components/new-stepper.hbs | 59 +++++++--------------- app/components/new-stepper.js | 17 +++++-- app/components/reusables/button.hbs | 3 +- app/constants/new-join-form.js | 4 +- app/styles/new-stepper.module.css | 26 ++++++---- 6 files changed, 62 insertions(+), 58 deletions(-) diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js index be48caa1..2f9578ae 100644 --- a/app/components/new-join-steps/base-step.js +++ b/app/components/new-join-steps/base-step.js @@ -64,6 +64,12 @@ export default class BaseStepComponent extends Component { validateField(field, value) { const limits = this.stepValidation[field]; + const fieldType = limits?.type || 'text'; + + if (fieldType === 'select' || fieldType === 'dropdown') { + const hasValue = value && String(value).trim().length > 0; + return { isValid: hasValue }; + } return validateWordCount(value, limits); } @@ -103,6 +109,11 @@ export default class BaseStepComponent extends Component { formatError(field, result) { const limits = this.stepValidation[field]; if (result.isValid) return ''; + + const fieldType = limits?.type || 'text'; + if (fieldType === 'select' || fieldType === 'dropdown') { + return 'Please choose an option'; + } if (result.remainingToMin) { return `At least ${result.remainingToMin} more word(s) required`; } diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index 05c567e6..3c6b76af 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -1,58 +1,33 @@
    {{#if (not-eq this.currentStep this.MIN_STEP)}} - + {{/if}}
    {{#if (eq this.currentStep this.MIN_STEP)}} - + -
    - -
    +
    + +
    - {{else if (eq this.currentStep 1)}} - + {{else if (eq this.currentStep 1)}} + {{/if}} {{#if (not-eq this.currentStep this.MIN_STEP)}} -
    - {{#if this.showPreviousButton}} - - {{else}} -
    - {{/if}} +
    + {{#if this.showPreviousButton}} + + {{/if}} - -
    + +
    {{/if}}
    \ No newline at end of file diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index 73993fef..919c6803 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -14,12 +14,23 @@ export default class NewStepperComponent extends Component { @service onboarding; @service joinApplicationTerms; - @tracked currentStep = - Number(getLocalStorageItem('currentStep') ?? this.args.step) || 0; - @tracked preValid = false; @tracked isValid = getLocalStorageItem('isValid') === 'true'; + @tracked currentStep = 0; + + constructor() { + super(...arguments); + + const storedStep = getLocalStorageItem('currentStep'); + const stepFromArgs = this.args.step; + this.currentStep = storedStep + ? Number(storedStep) + : stepFromArgs != null + ? Number(stepFromArgs) + : 0; + } + setIsValid = (newVal) => (this.isValid = newVal); setIsPreValid = (newVal) => (this.preValid = newVal); diff --git a/app/components/reusables/button.hbs b/app/components/reusables/button.hbs index 60c9caf1..da4b0155 100644 --- a/app/components/reusables/button.hbs +++ b/app/components/reusables/button.hbs @@ -2,7 +2,8 @@ data-test-button={{@test}} class="btn btn-{{@variant}} btn-{{@classGenerateUsername}} - {{if @disabled 'btn-disabled' ''}}" + {{if @disabled 'btn-disabled' ''}} + {{@class}}" type={{@type}} disabled={{@disabled}} title={{@title}} diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 6911a135..9a570d71 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -16,10 +16,10 @@ export const ROLE_OPTIONS = [ export const NEW_STEP_LIMITS = { stepOne: { - country: { min: 1 }, + country: { min: 1, type: 'dropdown' }, state: { min: 1 }, city: { min: 1 }, - role: { min: 1 }, + role: { min: 1, type: 'select' }, }, }; diff --git a/app/styles/new-stepper.module.css b/app/styles/new-stepper.module.css index bbef5d22..b9da0542 100644 --- a/app/styles/new-stepper.module.css +++ b/app/styles/new-stepper.module.css @@ -8,12 +8,19 @@ } .new-stepper__buttons { - width: 60vw; - display: flex; - justify-content: space-between; + display: grid; + grid-template-columns: 1fr auto 1fr; align-items: center; - gap: 1rem; - margin: 0; + width: 60vw; +} + +.prev-button { + grid-column: 1; +} + +.next-button { + grid-column: 3; + justify-self: end; } .welcome-screen { @@ -398,8 +405,7 @@ border-top: 1px solid var(--color-lightgrey); } -/* MEDIA QUERIES */ -@media screen and (width <= 1280px) { +@media screen and (width <=1280px) { .new-stepper__form, .form-header, .new-stepper__buttons { @@ -407,7 +413,7 @@ } } -@media screen and (width <= 1024px) { +@media screen and (width <=1024px) { .new-stepper__form, .form-header, .new-stepper__buttons { @@ -415,7 +421,7 @@ } } -@media screen and (width <= 768px) { +@media screen and (width <=768px) { .two-column-layout { grid-template-columns: 1fr; } @@ -472,7 +478,7 @@ } } -@media screen and (width <= 480px) { +@media screen and (width <=480px) { .form-header, .new-stepper__form, .new-stepper__buttons { From c3fd9428f04dabf990e339aa987b4d2e7a354d05 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 11 Nov 2025 22:52:36 +0530 Subject: [PATCH 34/35] refactor: use getter func for review step button --- app/components/new-stepper.hbs | 102 ++++++++++----------------------- app/components/new-stepper.js | 4 ++ 2 files changed, 34 insertions(+), 72 deletions(-) diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index f835e905..f2962269 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -1,6 +1,6 @@
    {{#if (and (not-eq this.currentStep this.MIN_STEP) (not-eq this.currentStep 7))}} - + {{/if}}
    @@ -12,88 +12,46 @@ @disabled={{not this.joinApplicationTerms.hasUserAcceptedTerms}} />
    - {{else if (eq this.currentStep 1)}} - - {{else if (eq this.currentStep 2)}} - + {{else if (eq this.currentStep 2)}} + - {{else if (eq this.currentStep 3)}} - + {{else if (eq this.currentStep 3)}} + - {{else if (eq this.currentStep 4)}} - + {{else if (eq this.currentStep 4)}} + - {{else if (eq this.currentStep 5)}} - + {{else if (eq this.currentStep 5)}} + - {{else if (eq this.currentStep 6)}} - + {{else if (eq this.currentStep 6)}} + - {{else if (eq this.currentStep 7)}} - + {{else if (eq this.currentStep 7)}} + {{/if}} {{#if (and (not-eq this.currentStep this.MIN_STEP) (not-eq this.currentStep 7))}} -
    - {{#if this.showPreviousButton} - - {{/if}} +
    + {{#if this.showPreviousButton}} + + {{/if}} - {{#if (eq this.currentStep 6)}} - - {{else}} - - {{/if}} -
    + +
    {{/if}}
    \ No newline at end of file diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index dc1a01e5..ff0cc434 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -65,6 +65,10 @@ export default class NewStepperComponent extends Component { return !(this.preValid || this.isValid); } + get isReviewStep() { + return this.currentStep === this.MAX_STEP; + } + @action incrementStep() { if (this.currentStep < this.MAX_STEP) { const nextStep = this.currentStep + 1; From 4e8b4023e25fd3d1feeb4722d1245026439a88de Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Wed, 12 Nov 2025 00:11:38 +0530 Subject: [PATCH 35/35] refactor: update button logic and improve state management in new stepper --- app/components/new-join-steps/base-step.js | 1 - app/components/new-stepper.hbs | 2 +- app/components/new-stepper.js | 10 +++------- app/constants/new-join-form.js | 2 +- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/components/new-join-steps/base-step.js b/app/components/new-join-steps/base-step.js index e2e38432..8818a345 100644 --- a/app/components/new-join-steps/base-step.js +++ b/app/components/new-join-steps/base-step.js @@ -55,7 +55,6 @@ export default class BaseStepComponent extends Component { @action inputHandler(e) { if (!e?.target) return; - this.args.setIsPreValid(false); const field = e.target.name; const value = e.target.value; debounce(this, this.handleFieldUpdate, field, value, JOIN_DEBOUNCE_TIME); diff --git a/app/components/new-stepper.hbs b/app/components/new-stepper.hbs index b9b83c38..fb5f8a1e 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -64,7 +64,7 @@ + this.incrementStep}} @disabled={{not this.canProceedFromStep}} />
    {{/if}} \ No newline at end of file diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index c353fda7..3f39b7ab 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -69,7 +69,7 @@ export default class NewStepperComponent extends Component { this.persistStep(accessibleStep); this.updateQueryParam(accessibleStep); } - + clampStep(step) { return Math.max(this.MIN_STEP, Math.min(this.MAX_STEP + 1, step)); } @@ -101,17 +101,13 @@ export default class NewStepperComponent extends Component { } get firstName() { - return localStorage.getItem('first_name') ?? ''; - } - - get isNextButtonDisabled() { - return !(this.preValid || this.isValid); + return sessionStorage.getItem('first_name') ?? ''; } get isReviewStep() { return this.currentStep === this.MAX_STEP; } - + resolveAccessibleStep(stepNumber) { const desiredStep = this.clampStep(stepNumber); for (let step = 1; step < desiredStep; step++) { diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 0cfb9fb6..ebd19a80 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -54,7 +54,7 @@ export const NEW_STEP_LIMITS = { }, stepFive: { whyRds: { min: 100 }, - foundFrom: { min: 1 }, + foundFrom: { min: 1, type: 'dropdown' }, }, };