Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/components/header.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@
>Live</LinkTo>
</li>
{{/if}}
{{#if (and @dev this.isSuperUser)}}
<li>
<LinkTo
data-test-applications
@route="applications"
@query={{hash dev="true"}}
class="nav__element"
>Applications</LinkTo>
</li>
{{/if}}
</ul>
</nav>

Expand Down
5 changes: 5 additions & 0 deletions app/components/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { inject as service } from '@ember/service';
export default class HeaderComponent extends Component {
@service router;
@service fastboot;
@service login;
@tracked isNavOpen = false;
@tracked isMenuOpen = false;
@tracked authURL = this.generateAuthURL();
Expand All @@ -24,6 +25,10 @@ export default class HeaderComponent extends Component {
IDENTITY_URL = APPS.IDENTITY;
MY_STATUS_URL = APPS.MY_STATUS;

get isSuperUser() {
return this.login.userData?.roles?.super_user || false;
}

@action toggleNavbar() {
this.isNavOpen = !this.isNavOpen;
}
Expand Down
128 changes: 128 additions & 0 deletions app/components/new-join-steps/base-step.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
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';
import { scheduleOnce } from '@ember/runloop';
import { getLocalStorageItem, setLocalStorageItem } from '../../utils/storage';

export default class BaseStepComponent extends Component {
stepValidation = {};

@tracked data = {};
@tracked errorMessage = {};
@tracked wordCount = {};

get storageKey() {
return '';
}

postLoadInitialize() {}

constructor(...args) {
super(...args);
scheduleOnce('afterRender', this, this.initializeFormState);
}

initializeFormState() {
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, '']),
);

this.wordCount = Object.fromEntries(
Object.keys(this.stepValidation).map((k) => {
let val = String(this.data[k] || '');
return [k, val.trim().split(/\s+/).filter(Boolean).length || 0];
}),
);

this.postLoadInitialize();

const valid = this.isDataValid();
this.args.setIsPreValid(valid);
setLocalStorageItem('isValid', String(valid));
}

@action inputHandler(e) {
if (!e?.target) return;
this.args.setIsPreValid(false);
const field = e.target.name;
const value = e.target.value;
debounce(this, this.handleFieldUpdate, field, value, JOIN_DEBOUNCE_TIME);
}

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);
}

isDataValid() {
for (const field of Object.keys(this.stepValidation)) {
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, result);
this.updateErrorMessage(field, result);
this.syncFormValidity();
}

updateFieldValue(field, value) {
this.data = { ...this.data, [field]: value };
setLocalStorageItem(this.storageKey, JSON.stringify(this.data));
}

updateWordCount(field, result) {
const wordCount = result.wordCount ?? 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.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`;
}
return `Maximum ${limits?.max ?? 'N/A'} words allowed`;
}

syncFormValidity() {
const allValid = this.isDataValid();
this.args.setIsValid(allValid);
setLocalStorageItem('isValid', String(allValid));
}
}
33 changes: 33 additions & 0 deletions app/components/new-join-steps/new-step-five.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<div class="step-container">
<div class="form-header__text">
<h1 class="section-heading">{{@heading}}</h1>
<p class="section-instruction">{{@subHeading}}</p>
</div>

<Reusables::TextAreaBox @field='Why you want to join Real Dev Squad?' @name='whyRds'
@placeHolder='Tell us why you want to join our community...' @required={{true}} @value={{this.data.whyRds}}
@onInput={{this.inputHandler}} />
{{#if this.errorMessage.whyRds}}
<div class='error__message'>{{this.errorMessage.whyRds}}</div>
{{/if}}

<div class="form-grid form-grid--2">
<div class="form-grid__item">
<Reusables::InputBox @field='No of hours/week you are willing to contribute?' @name='numberOfHours'
@placeHolder='Enter value between 1-100.' @type='number' @required={{true}} @value={{this.data.numberOfHours}}
@onInput={{this.inputHandler}} @options={{this.heardFrom}} />
{{#if this.errorMessage.numberOfHours}}
<div class='error__message'>{{this.errorMessage.numberOfHours}}</div>
{{/if}}
</div>

<div class="form-grid__item">
<Reusables::Dropdown @field='How did you hear about us?' @name='foundFrom'
@placeHolder='Choose from below options' @required={{true}} @value={{this.data.foundFrom}}
@options={{this.heardFrom}} @onChange={{this.inputHandler}} />
{{#if this.errorMessage.foundFrom}}
<div class='error__message'>{{this.errorMessage.foundFrom}}</div>
{{/if}}
</div>
</div>
</div>
16 changes: 16 additions & 0 deletions app/components/new-join-steps/new-step-five.js
Original file line number Diff line number Diff line change
@@ -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,
};
}
62 changes: 62 additions & 0 deletions app/components/new-join-steps/new-step-four.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<div class="step-container">
<div class="form-header__text">
<h1 class="section-heading">{{@heading}}</h1>
<p class="section-instruction">{{@subHeading}}</p>
</div>

<Reusables::InputBox @field='Phone Number' @name='phoneNumber' @placeHolder='+91 80000 00000' @type='tel'
@required={{true}} @value={{this.data.phoneNumber}} @onInput={{this.inputHandler}} @variant='input--full-width' />
{{#if this.errorMessage.phoneNumber}}
<div class='error__message'>{{this.errorMessage.phoneNumber}}</div>
{{/if}}

<Reusables::InputBox @field='Twitter' @name='twitter' @placeHolder='https://twitter.com/gangster-rishi' @type='text'
@required={{true}} @value={{this.data.twitter}} @onInput={{this.inputHandler}} @variant='input--full-width' />
{{#if this.errorMessage.twitter}}
<div class='error__message'>{{this.errorMessage.twitter}}</div>
{{/if}}

{{#if this.showGitHub}}
<Reusables::InputBox @field='GitHub' @name='github' @placeHolder='https://github.com/codewithrishi' @type='text'
@required={{true}} @value={{this.data.github}} @onInput={{this.inputHandler}} @variant='input--full-width' />
{{#if this.errorMessage.github}}
<div class='error__message'>{{this.errorMessage.github}}</div>
{{/if}}
{{/if}}

<Reusables::InputBox @field='LinkedIn' @name='linkedin' @placeHolder='https://linkedin.com/in/professional-rishi'
@type='text' @required={{true}} @value={{this.data.linkedin}} @onInput={{this.inputHandler}}
@variant='input--full-width' />
{{#if this.errorMessage.linkedin}}
<div class='error__message'>{{this.errorMessage.linkedin}}</div>
{{/if}}

<Reusables::InputBox @field='Instagram' @name='instagram' @placeHolder='https://instagram.com/gangster-rishi'
@type='text' @required={{false}} @value={{this.data.instagram}} @onInput={{this.inputHandler}}
@variant='input--full-width' />
{{#if this.errorMessage.instagram}}
<div class='error__message'>{{this.errorMessage.instagram}}</div>
{{/if}}

<Reusables::InputBox @field='Peerlist' @name='peerlist' @placeHolder='https://peerlist.io/richy-rishi' @type='text'
@required={{true}} @value={{this.data.peerlist}} @onInput={{this.inputHandler}} @variant='input--full-width' />
{{#if this.errorMessage.peerlist}}
<div class='error__message'>{{this.errorMessage.peerlist}}</div>
{{/if}}

{{#if this.showBehance}}
<Reusables::InputBox @field='Behance' @name='behance' @placeHolder='https://behance.net/designer-rishi' @type='text'
@required={{true}} @value={{this.data.behance}} @onInput={{this.inputHandler}} @variant='input--full-width' />
{{#if this.errorMessage.behance}}
<div class='error__message'>{{this.errorMessage.behance}}</div>
{{/if}}
{{/if}}

{{#if this.showDribble}}
<Reusables::InputBox @field='Dribble' @name='dribble' @placeHolder='https://dribbble.com/dribwithrishi' @type='text'
@required={{true}} @value={{this.data.dribble}} @onInput={{this.inputHandler}} @variant='input--full-width' />
{{#if this.errorMessage.dribble}}
<div class='error__message'>{{this.errorMessage.dribble}}</div>
{{/if}}
{{/if}}
</div>
80 changes: 80 additions & 0 deletions app/components/new-join-steps/new-step-four.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading