From d0adec74b598558ba00dce3e1e6a9d56fe893066 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Mon, 3 Nov 2025 20:24:05 +0530 Subject: [PATCH 01/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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 1b8f4777631ad5825763b179f75207bcdb81b034 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Tue, 11 Nov 2025 19:29:07 +0530 Subject: [PATCH 13/15] 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 14/15] 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 8b271fa4ceaa7d4fe67d02f8c32099513a611950 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Wed, 12 Nov 2025 02:08:28 +0530 Subject: [PATCH 15/15] feat: add new step 2 and 3 for intro and interests [Onboarding-Form] (#1084) * feat: add new step two for introduction * feat: add step three for interests * feat: add base step class to handle input validation and local storage sync * fix: postIntialization in new step one * fix: grid styling for step two and three * refactor: remove redundant max words * fix: lint issue and form headings * refactor: use constant for storage keys * feat: add new step four and five for social links and whyRds [Onboarding-Form] (#1086) * feat: add new step for social links * feat: add snew step four for social links * feat: add new step fivve for stepper * fix: styling for selectt in new step five * refactor: replace hardcoded storage keys with constants * feat: add review step for new stepper [Onboarding-Form] (#1087) * feat: add review step for new stepper * feat: add thank you screen for new stepper * refactor: use structure for step data and replace hardcoded storage keys * refactor: use getter func for review step button --- .../new-join-steps/new-step-five.hbs | 33 +++ .../new-join-steps/new-step-five.js | 16 ++ .../new-join-steps/new-step-four.hbs | 62 ++++++ .../new-join-steps/new-step-four.js | 80 +++++++ .../new-join-steps/new-step-six.hbs | 207 ++++++++++++++++++ app/components/new-join-steps/new-step-six.js | 57 +++++ .../new-join-steps/new-step-three.hbs | 38 ++++ .../new-join-steps/new-step-three.js | 13 ++ .../new-join-steps/new-step-two.hbs | 37 ++++ app/components/new-join-steps/new-step-two.js | 14 ++ .../new-join-steps/thank-you-screen.hbs | 33 +++ app/components/new-stepper.hbs | 34 ++- app/components/new-stepper.js | 29 ++- app/constants/new-join-form.js | 41 +++- app/styles/new-stepper.module.css | 54 ++++- 15 files changed, 735 insertions(+), 13 deletions(-) create mode 100644 app/components/new-join-steps/new-step-five.hbs create mode 100644 app/components/new-join-steps/new-step-five.js create mode 100644 app/components/new-join-steps/new-step-four.hbs create mode 100644 app/components/new-join-steps/new-step-four.js create mode 100644 app/components/new-join-steps/new-step-six.hbs create mode 100644 app/components/new-join-steps/new-step-six.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 create mode 100644 app/components/new-join-steps/thank-you-screen.hbs 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..ded36fdc --- /dev/null +++ b/app/components/new-join-steps/new-step-five.hbs @@ -0,0 +1,33 @@ +
        +
        +

        {{@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..71a35326 --- /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, + STEP_DATA_STORAGE_KEY, +} from '../../constants/new-join-form'; +import { heardFrom } from '../../constants/social-data'; + +export default class NewStepFiveComponent extends BaseStepComponent { + storageKey = STEP_DATA_STORAGE_KEY.stepFive; + heardFrom = heardFrom; + + stepValidation = { + whyRds: NEW_STEP_LIMITS.stepFive.whyRds, + foundFrom: NEW_STEP_LIMITS.stepFive.foundFrom, + }; +} 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..40106a44 --- /dev/null +++ b/app/components/new-join-steps/new-step-four.hbs @@ -0,0 +1,62 @@ +
        +
        +

        {{@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}} +
        \ 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 new file mode 100644 index 00000000..bae00758 --- /dev/null +++ b/app/components/new-join-steps/new-step-four.js @@ -0,0 +1,80 @@ +import BaseStepComponent from './base-step'; +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 = STEP_DATA_STORAGE_KEY.stepFour; + + stepValidation = { + 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') || '{}', + ); + return stepOneData.role || ''; + } + + postLoadInitialize() { + if (this.userRole === 'Developer') { + this.stepValidation.github = NEW_STEP_LIMITS.stepFour.github; + } + + if (this.userRole === 'Designer') { + 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.stepValidation).map((k) => [k, '']), + ); + + this.wordCount = Object.fromEntries( + Object.keys(this.stepValidation).map((k) => { + let val = this.data[k] || ''; + return [k, val.trim().split(/\s+/).filter(Boolean).length || 0]; + }), + ); + } + + get showGitHub() { + return this.userRole === 'Developer'; + } + + get showBehance() { + return this.userRole === 'Designer'; + } + + 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/components/new-join-steps/new-step-six.hbs b/app/components/new-join-steps/new-step-six.hbs new file mode 100644 index 00000000..cb18d7d5 --- /dev/null +++ b/app/components/new-join-steps/new-step-six.hbs @@ -0,0 +1,207 @@ +
        +
        +

        {{@heading}}

        +

        {{@subHeading}}

        +
        + +
        +
        +

        Personal Information

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

        Professional Details

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

        Hobbies & Interests

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

        Social Profiles

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

        Why Real Dev Squad?

        + +
        +
        +
        + Why you want to join Real Dev Squad?: + + {{if this.stepData.five.whyRds this.stepData.five.whyRds 'Not provided'}} + +
        +
        + Hours per week: + + {{if this.stepData.five.numberOfHours this.stepData.five.numberOfHours 'Not provided'}} + +
        +
        + How did you hear about us?: + + {{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 new file mode 100644 index 00000000..9928e663 --- /dev/null +++ b/app/components/new-join-steps/new-step-six.js @@ -0,0 +1,57 @@ +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 stepData = { + one: {}, + two: {}, + three: {}, + four: {}, + five: {}, + }; + + constructor(...args) { + super(...args); + this.loadAllStepData(); + } + + loadAllStepData() { + this.stepData.one = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepOne), + ); + this.stepData.two = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepTwo), + ); + this.stepData.three = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepThree), + ); + this.stepData.four = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepFour), + ); + this.stepData.five = JSON.parse( + getLocalStorageItem(STEP_DATA_STORAGE_KEY.stepFive), + ); + } + + get userRole() { + return this.stepData.one.role || ''; + } + + get showGitHub() { + return this.userRole === 'Developer'; + } + + get showBehance() { + return this.userRole === 'Designer'; + } + + get showDribble() { + return this.userRole === 'Designer'; + } + + get locationDisplay() { + return `${this.stepData.one.city}, ${this.stepData.one.state}, ${this.stepData.one.country}`; + } +} 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..7614c45f --- /dev/null +++ b/app/components/new-join-steps/new-step-three.hbs @@ -0,0 +1,38 @@ +
        +
        +

        {{@heading}}

        +

        {{@subHeading}}

        +
        + +
        +
        + +
        {{this.wordCount.hobbies}}/{{this.stepValidation.hobbies.max}} words
        + {{#if this.errorMessage.hobbies}} +
        {{this.errorMessage.hobbies}}
        + {{/if}} +
        + +
        + +
        {{this.wordCount.funFact}}/{{this.stepValidation.funFact.max}} 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..0c3fd6b2 --- /dev/null +++ b/app/components/new-join-steps/new-step-three.js @@ -0,0 +1,13 @@ +import BaseStepComponent from './base-step'; +import { + NEW_STEP_LIMITS, + STEP_DATA_STORAGE_KEY, +} from '../../constants/new-join-form'; + +export default class NewStepThreeComponent extends BaseStepComponent { + 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 new file mode 100644 index 00000000..c96c5175 --- /dev/null +++ b/app/components/new-join-steps/new-step-two.hbs @@ -0,0 +1,37 @@ +
        +
        +

        {{@heading}}

        +

        {{@subHeading}}

        +
        + +
        +
        +
        + + {{#if this.errorMessage.skills}} +
        {{this.errorMessage.skills}}
        + {{/if}} +
        + +
        + + {{#if this.errorMessage.company}} +
        {{this.errorMessage.company}}
        + {{/if}} +
        +
        +
        + +
        {{this.wordCount.introduction}}/{{this.stepValidation.introduction.max}} 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..edb38294 --- /dev/null +++ b/app/components/new-join-steps/new-step-two.js @@ -0,0 +1,14 @@ +import BaseStepComponent from './base-step'; +import { + NEW_STEP_LIMITS, + STEP_DATA_STORAGE_KEY, +} from '../../constants/new-join-form'; + +export default class NewStepTwoComponent extends BaseStepComponent { + 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/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 3c6b76af..f2962269 100644 --- a/app/components/new-stepper.hbs +++ b/app/components/new-stepper.hbs @@ -1,6 +1,5 @@
        - - {{#if (not-eq this.currentStep this.MIN_STEP)}} + {{#if (and (not-eq this.currentStep this.MIN_STEP) (not-eq this.currentStep 7))}} {{/if}} @@ -16,18 +15,43 @@ {{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)}} + + + {{else if (eq this.currentStep 6)}} + + + {{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}} {{/if}} - +
        {{/if}}
        \ No newline at end of file diff --git a/app/components/new-stepper.js b/app/components/new-stepper.js index 919c6803..ff0cc434 100644 --- a/app/components/new-stepper.js +++ b/app/components/new-stepper.js @@ -8,6 +8,7 @@ import { getLocalStorageItem, setLocalStorageItem } from '../utils/storage'; export default class NewStepperComponent extends Component { MIN_STEP = 0; MAX_STEP = 6; + applicationId = '4gchuf690'; @service login; @service router; @@ -36,7 +37,6 @@ export default class NewStepperComponent extends Component { updateQueryParam(step) { const existingQueryParams = this.router.currentRoute?.queryParams; - this.router.transitionTo('join', { queryParams: { ...existingQueryParams, @@ -57,10 +57,18 @@ export default class NewStepperComponent extends Component { return NEW_FORM_STEPS.subheadings[this.currentStep - 1] ?? ''; } + get firstName() { + return localStorage.getItem('first_name') ?? ''; + } + get isNextButtonDisabled() { 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; @@ -85,4 +93,23 @@ 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; + setLocalStorageItem('currentStep', String(stepNumber)); + setLocalStorageItem('isValid', 'false'); + this.updateQueryParam(stepNumber); + } + } + + @action handleSubmit() { + // ToDo: handle create application + console.log('Submit application for review'); + this.currentStep = this.MAX_STEP + 1; + setLocalStorageItem('currentStep', String(this.currentStep)); + this.updateQueryParam(this.currentStep); + } } diff --git a/app/constants/new-join-form.js b/app/constants/new-join-form.js index 9a570d71..0cfb9fb6 100644 --- a/app/constants/new-join-form.js +++ b/app/constants/new-join-form.js @@ -1,7 +1,19 @@ export const NEW_FORM_STEPS = { - headings: ['Upload Professional Headshot and Complete Personal Details'], + headings: [ + 'Upload Professional Headshot and Complete Personal Details', + 'Additional Personal Information', + 'Your hobbies, interests, fun fact', + 'Connect your social profiles', + 'Why Real Dev Squad?', + 'Review and Submit', + ], 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', + 'Review your answers before submitting.', ], }; @@ -21,8 +33,35 @@ export const NEW_STEP_LIMITS = { city: { min: 1 }, role: { min: 1, type: 'select' }, }, + stepTwo: { + skills: { min: 5, max: 20 }, + company: { min: 1 }, + introduction: { min: 100, max: 500 }, + }, + stepThree: { + hobbies: { min: 100, max: 500 }, + funFact: { min: 100, max: 500 }, + }, + stepFour: { + phoneNumber: { min: 1 }, + twitter: { min: 1 }, + github: { min: 1 }, + linkedin: { min: 1 }, + instagram: { min: 0 }, + peerlist: { min: 1 }, + behance: { min: 1 }, + dribble: { min: 1 }, + }, + stepFive: { + whyRds: { min: 100 }, + foundFrom: { min: 1 }, + }, }; export const STEP_DATA_STORAGE_KEY = { stepOne: 'newStepOneData', + stepTwo: 'newStepTwoData', + stepThree: 'newStepThreeData', + stepFour: 'newStepFourData', + stepFive: 'newStepFiveData', }; diff --git a/app/styles/new-stepper.module.css b/app/styles/new-stepper.module.css index b9da0542..10f1aec8 100644 --- a/app/styles/new-stepper.module.css +++ b/app/styles/new-stepper.module.css @@ -23,7 +23,8 @@ justify-self: end; } -.welcome-screen { +.welcome-screen, +.thank-you-screen { width: 90%; margin: 0 auto 2rem; display: flex; @@ -37,7 +38,8 @@ justify-content: center; } -.welcome-screen__info-item--bullet { +.welcome-screen__info-item--bullet, +.thank-you-screen__info-item--bullet { display: flex; align-items: center; margin-bottom: 1rem; @@ -46,7 +48,8 @@ line-height: 1.5; } -.welcome-screen__info-item--bullet::before { +.welcome-screen__info-item--bullet::before, +.thank-you-screen__info-item--bullet::before { content: "•"; color: var(--color-pink); font-size: 1.5rem; @@ -67,7 +70,8 @@ line-height: 1.5; } -.welcome-screen__actions { +.welcome-screen__actions, +.thank-you-screen__actions { display: flex; justify-content: center; } @@ -405,6 +409,38 @@ border-top: 1px solid var(--color-lightgrey); } +.thank-you-screen { + align-items: center; + text-align: center; + margin: 2rem auto 0; +} + +.thank-you-screen__info-container { + text-align: start; + margin-block: 1.5rem; +} + +.thank-you-screen__logo { + display: flex; + justify-content: center; + align-items: center; + width: 3rem; + height: 3em; + border-radius: 50%; + background-color: var(--color-navyblue); + color: var(--color-white); +} + +.application-id h3 { + font-size: 1rem; + font-weight: 500; +} + +.application-id p { + font-size: 1.25rem; + font-weight: 700; +} + @media screen and (width <=1280px) { .new-stepper__form, .form-header, @@ -440,7 +476,8 @@ grid-template-columns: 1fr; } - .welcome-screen { + .welcome-screen, + .thank-you-screen { margin: 1rem auto; padding: 1.5rem; } @@ -476,6 +513,10 @@ text-align: left; max-width: 100%; } + + .form-header__text { + line-height: 1.5; + } } @media screen and (width <=480px) { @@ -493,7 +534,8 @@ padding: 1rem; } - .welcome-screen { + .welcome-screen, + .thank-you-screen { margin: 0.5rem auto 1rem; padding: 1rem; }