Skip to content

Commit 4bb5470

Browse files
Merge branch 'develop' into chore/mobile-feature-flag
2 parents e3748a7 + c013f54 commit 4bb5470

File tree

96 files changed

+4668
-199
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+4668
-199
lines changed

app/components/confirm-modal.hbs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<BaseModal
2+
@closeModal={{@closeConfirmModal}}
3+
@openModal={{@openConfirmModal}}
4+
@isOpen={{@isOpen}}
5+
>
6+
<div data-test-confirm-modal class="confirm-modal">
7+
<div class="confirm-modal__heading">
8+
<h4>Device Scan Confirmation</h4>
9+
<p data-test-confirm-modal-message>Are you sure you are the one who
10+
scanned this QR code? Do you want to proceed?</p>
11+
</div>
12+
<div class="confirm-modal__buttons">
13+
<Reusables::Button
14+
@type="button"
15+
@text="Cancel"
16+
@variant="light btn--sm"
17+
@test="confirm-modal-cancel"
18+
@disabled={{@actionButtonDisabled}}
19+
@onClick={{@onCancel}}
20+
/>
21+
<Reusables::Button
22+
@type="button"
23+
@text="Confirm"
24+
@variant="dark btn--sm"
25+
@test="confirm-modal-authorise"
26+
@disabled={{@actionButtonDisabled}}
27+
@onClick={{@onConfirm}}
28+
/>
29+
</div>
30+
</div>
31+
</BaseModal>

app/components/header.hbs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@
6161
>Live</LinkTo>
6262
</li>
6363
{{/if}}
64+
{{#if (and @dev this.isSuperUser)}}
65+
<li>
66+
<LinkTo
67+
data-test-applications
68+
@route="applications"
69+
@query={{hash dev="true"}}
70+
class="nav__element"
71+
>Applications</LinkTo>
72+
</li>
73+
{{/if}}
6474
</ul>
6575
</nav>
6676

app/components/header.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { inject as service } from '@ember/service';
77
export default class HeaderComponent extends Component {
88
@service router;
99
@service fastboot;
10+
@service login;
1011
@tracked isNavOpen = false;
1112
@tracked isMenuOpen = false;
1213
@tracked authURL = this.generateAuthURL();
@@ -24,6 +25,10 @@ export default class HeaderComponent extends Component {
2425
IDENTITY_URL = APPS.IDENTITY;
2526
MY_STATUS_URL = APPS.MY_STATUS;
2627

28+
get isSuperUser() {
29+
return this.login.userData?.roles?.super_user || false;
30+
}
31+
2732
@action toggleNavbar() {
2833
this.isNavOpen = !this.isNavOpen;
2934
}

app/components/identity/reload.hbs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,5 @@
22
<div class='identity-box-desc' data-test-reload-desc>Reload to complete and
33
verify the link between Profile Service and RealDevSquad Service.</div>
44
<button
5-
class='identity-box-button'
6-
data-test-blocked-button
7-
type="button"
8-
{{on "click" (fn this.setState "step1")}}
9-
>
10-
Retry
11-
</button>
5+
class='identity-box-button' type="button" {{on 'click' this.handleReload}}
6+
>Reload</button>

app/components/join-section.hbs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
</p>
1414
<div class='links__container'>
1515
<LinkTo data-test-join-link @route='subscribe' class='subscribe__link join__link'>Subscribe <span>🔔</span></LinkTo>
16-
<LinkTo data-test-join-link @route='join' class='join__link'>Join the Squad</LinkTo>
16+
{{!-- TODO: remove this button to stop redirecting to join page --}}
17+
{{!-- <LinkTo data-test-join-link @route='join' class='join__link'>Join the Squad</LinkTo> --}}
1718
</div>
1819
</section>
1920
{{else}}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { action } from '@ember/object';
2+
import { debounce } from '@ember/runloop';
3+
import Component from '@glimmer/component';
4+
import { tracked } from '@glimmer/tracking';
5+
import { JOIN_DEBOUNCE_TIME } from '../../constants/join';
6+
import { validateWordCount } from '../../utils/validator';
7+
import { scheduleOnce } from '@ember/runloop';
8+
import { getLocalStorageItem, setLocalStorageItem } from '../../utils/storage';
9+
10+
export default class BaseStepComponent extends Component {
11+
stepValidation = {};
12+
13+
@tracked data = {};
14+
@tracked errorMessage = {};
15+
@tracked wordCount = {};
16+
17+
get storageKey() {
18+
return '';
19+
}
20+
21+
postLoadInitialize() {}
22+
23+
constructor(...args) {
24+
super(...args);
25+
scheduleOnce('afterRender', this, this.initializeFormState);
26+
}
27+
28+
initializeFormState() {
29+
let saved = {};
30+
try {
31+
const stored = getLocalStorageItem(this.storageKey, '{}');
32+
saved = stored ? JSON.parse(stored) : {};
33+
} catch (e) {
34+
console.warn('Failed to parse stored form data:', e);
35+
saved = {};
36+
}
37+
this.data = saved;
38+
39+
this.errorMessage = Object.fromEntries(
40+
Object.keys(this.stepValidation).map((k) => [k, '']),
41+
);
42+
43+
this.wordCount = Object.fromEntries(
44+
Object.keys(this.stepValidation).map((k) => {
45+
let val = String(this.data[k] || '');
46+
return [k, val.trim().split(/\s+/).filter(Boolean).length || 0];
47+
}),
48+
);
49+
50+
this.postLoadInitialize();
51+
52+
const valid = this.isDataValid();
53+
this.args.setIsPreValid(valid);
54+
setLocalStorageItem('isValid', String(valid));
55+
}
56+
57+
@action inputHandler(e) {
58+
if (!e?.target) return;
59+
this.args.setIsPreValid(false);
60+
const field = e.target.name;
61+
const value = e.target.value;
62+
debounce(this, this.handleFieldUpdate, field, value, JOIN_DEBOUNCE_TIME);
63+
}
64+
65+
validateField(field, value) {
66+
const limits = this.stepValidation[field];
67+
const fieldType = limits?.type || 'text';
68+
69+
if (fieldType === 'select' || fieldType === 'dropdown') {
70+
const hasValue = value && String(value).trim().length > 0;
71+
return { isValid: hasValue };
72+
}
73+
return validateWordCount(value, limits);
74+
}
75+
76+
isDataValid() {
77+
for (const field of Object.keys(this.stepValidation)) {
78+
const result = this.validateField(field, this.data[field]);
79+
if (!result.isValid) return false;
80+
}
81+
return true;
82+
}
83+
84+
handleFieldUpdate(field, value) {
85+
this.updateFieldValue(field, value);
86+
const result = this.validateField(field, value);
87+
this.updateWordCount(field, result);
88+
this.updateErrorMessage(field, result);
89+
this.syncFormValidity();
90+
}
91+
92+
updateFieldValue(field, value) {
93+
this.data = { ...this.data, [field]: value };
94+
setLocalStorageItem(this.storageKey, JSON.stringify(this.data));
95+
}
96+
97+
updateWordCount(field, result) {
98+
const wordCount = result.wordCount ?? 0;
99+
this.wordCount = { ...this.wordCount, [field]: wordCount };
100+
}
101+
102+
updateErrorMessage(field, result) {
103+
this.errorMessage = {
104+
...this.errorMessage,
105+
[field]: this.formatError(field, result),
106+
};
107+
}
108+
109+
formatError(field, result) {
110+
const limits = this.stepValidation[field];
111+
if (result.isValid) return '';
112+
113+
const fieldType = limits?.type || 'text';
114+
if (fieldType === 'select' || fieldType === 'dropdown') {
115+
return 'Please choose an option';
116+
}
117+
if (result.remainingToMin) {
118+
return `At least ${result.remainingToMin} more word(s) required`;
119+
}
120+
return `Maximum ${limits?.max ?? 'N/A'} words allowed`;
121+
}
122+
123+
syncFormValidity() {
124+
const allValid = this.isDataValid();
125+
this.args.setIsValid(allValid);
126+
setLocalStorageItem('isValid', String(allValid));
127+
}
128+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<div class="step-container">
2+
<div class="form-header__text">
3+
<h1 class="section-heading">{{@heading}}</h1>
4+
<p class="section-instruction">{{@subHeading}}</p>
5+
</div>
6+
7+
<Reusables::TextAreaBox @field='Why you want to join Real Dev Squad?' @name='whyRds'
8+
@placeHolder='Tell us why you want to join our community...' @required={{true}} @value={{this.data.whyRds}}
9+
@onInput={{this.inputHandler}} />
10+
{{#if this.errorMessage.whyRds}}
11+
<div class='error__message'>{{this.errorMessage.whyRds}}</div>
12+
{{/if}}
13+
14+
<div class="form-grid form-grid--2">
15+
<div class="form-grid__item">
16+
<Reusables::InputBox @field='No of hours/week you are willing to contribute?' @name='numberOfHours'
17+
@placeHolder='Enter value between 1-100.' @type='number' @required={{true}} @value={{this.data.numberOfHours}}
18+
@onInput={{this.inputHandler}} @options={{this.heardFrom}} />
19+
{{#if this.errorMessage.numberOfHours}}
20+
<div class='error__message'>{{this.errorMessage.numberOfHours}}</div>
21+
{{/if}}
22+
</div>
23+
24+
<div class="form-grid__item">
25+
<Reusables::Dropdown @field='How did you hear about us?' @name='foundFrom'
26+
@placeHolder='Choose from below options' @required={{true}} @value={{this.data.foundFrom}}
27+
@options={{this.heardFrom}} @onChange={{this.inputHandler}} />
28+
{{#if this.errorMessage.foundFrom}}
29+
<div class='error__message'>{{this.errorMessage.foundFrom}}</div>
30+
{{/if}}
31+
</div>
32+
</div>
33+
</div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import BaseStepComponent from './base-step';
2+
import {
3+
NEW_STEP_LIMITS,
4+
STEP_DATA_STORAGE_KEY,
5+
} from '../../constants/new-join-form';
6+
import { heardFrom } from '../../constants/social-data';
7+
8+
export default class NewStepFiveComponent extends BaseStepComponent {
9+
storageKey = STEP_DATA_STORAGE_KEY.stepFive;
10+
heardFrom = heardFrom;
11+
12+
stepValidation = {
13+
whyRds: NEW_STEP_LIMITS.stepFive.whyRds,
14+
foundFrom: NEW_STEP_LIMITS.stepFive.foundFrom,
15+
};
16+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<div class="step-container">
2+
<div class="form-header__text">
3+
<h1 class="section-heading">{{@heading}}</h1>
4+
<p class="section-instruction">{{@subHeading}}</p>
5+
</div>
6+
7+
<Reusables::InputBox @field='Phone Number' @name='phoneNumber' @placeHolder='+91 80000 00000' @type='tel'
8+
@required={{true}} @value={{this.data.phoneNumber}} @onInput={{this.inputHandler}} @variant='input--full-width' />
9+
{{#if this.errorMessage.phoneNumber}}
10+
<div class='error__message'>{{this.errorMessage.phoneNumber}}</div>
11+
{{/if}}
12+
13+
<Reusables::InputBox @field='Twitter' @name='twitter' @placeHolder='https://twitter.com/gangster-rishi' @type='text'
14+
@required={{true}} @value={{this.data.twitter}} @onInput={{this.inputHandler}} @variant='input--full-width' />
15+
{{#if this.errorMessage.twitter}}
16+
<div class='error__message'>{{this.errorMessage.twitter}}</div>
17+
{{/if}}
18+
19+
{{#if this.showGitHub}}
20+
<Reusables::InputBox @field='GitHub' @name='github' @placeHolder='https://github.com/codewithrishi' @type='text'
21+
@required={{true}} @value={{this.data.github}} @onInput={{this.inputHandler}} @variant='input--full-width' />
22+
{{#if this.errorMessage.github}}
23+
<div class='error__message'>{{this.errorMessage.github}}</div>
24+
{{/if}}
25+
{{/if}}
26+
27+
<Reusables::InputBox @field='LinkedIn' @name='linkedin' @placeHolder='https://linkedin.com/in/professional-rishi'
28+
@type='text' @required={{true}} @value={{this.data.linkedin}} @onInput={{this.inputHandler}}
29+
@variant='input--full-width' />
30+
{{#if this.errorMessage.linkedin}}
31+
<div class='error__message'>{{this.errorMessage.linkedin}}</div>
32+
{{/if}}
33+
34+
<Reusables::InputBox @field='Instagram' @name='instagram' @placeHolder='https://instagram.com/gangster-rishi'
35+
@type='text' @required={{false}} @value={{this.data.instagram}} @onInput={{this.inputHandler}}
36+
@variant='input--full-width' />
37+
{{#if this.errorMessage.instagram}}
38+
<div class='error__message'>{{this.errorMessage.instagram}}</div>
39+
{{/if}}
40+
41+
<Reusables::InputBox @field='Peerlist' @name='peerlist' @placeHolder='https://peerlist.io/richy-rishi' @type='text'
42+
@required={{true}} @value={{this.data.peerlist}} @onInput={{this.inputHandler}} @variant='input--full-width' />
43+
{{#if this.errorMessage.peerlist}}
44+
<div class='error__message'>{{this.errorMessage.peerlist}}</div>
45+
{{/if}}
46+
47+
{{#if this.showBehance}}
48+
<Reusables::InputBox @field='Behance' @name='behance' @placeHolder='https://behance.net/designer-rishi' @type='text'
49+
@required={{true}} @value={{this.data.behance}} @onInput={{this.inputHandler}} @variant='input--full-width' />
50+
{{#if this.errorMessage.behance}}
51+
<div class='error__message'>{{this.errorMessage.behance}}</div>
52+
{{/if}}
53+
{{/if}}
54+
55+
{{#if this.showDribble}}
56+
<Reusables::InputBox @field='Dribble' @name='dribble' @placeHolder='https://dribbble.com/dribwithrishi' @type='text'
57+
@required={{true}} @value={{this.data.dribble}} @onInput={{this.inputHandler}} @variant='input--full-width' />
58+
{{#if this.errorMessage.dribble}}
59+
<div class='error__message'>{{this.errorMessage.dribble}}</div>
60+
{{/if}}
61+
{{/if}}
62+
</div>

0 commit comments

Comments
 (0)