diff --git a/app/components/application/detail-header.hbs b/app/components/application/detail-header.hbs
index 34849825e..c0f603ff6 100644
--- a/app/components/application/detail-header.hbs
+++ b/app/components/application/detail-header.hbs
@@ -72,6 +72,7 @@
@text="Edit"
@variant="light"
@class="btn--xs"
+ @disabled={{this.isEditDisabled}}
@onClick={{this.editApplication}}
@test="edit-button"
/>
diff --git a/app/components/application/detail-header.js b/app/components/application/detail-header.js
index b7df3dde6..04363fccc 100644
--- a/app/components/application/detail-header.js
+++ b/app/components/application/detail-header.js
@@ -1,7 +1,29 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
+import { service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import { TOAST_OPTIONS } from '../../constants/toast-options';
+import { NUDGE_APPLICATION_URL } from '../../constants/apis';
+import apiRequest from '../../utils/api-request';
+
+const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
+
+function isWithinCooldown(timestamp, cooldownMs = TWENTY_FOUR_HOURS) {
+ if (!timestamp) {
+ return false;
+ }
+
+ const now = Date.now();
+ const time = new Date(timestamp).getTime();
+
+ return now - time < cooldownMs;
+}
export default class DetailHeader extends Component {
+ @service router;
+ @service toast;
+
+ @tracked isLoading = false;
get application() {
return this.args.application;
}
@@ -41,20 +63,29 @@ export default class DetailHeader extends Component {
}
get nudgeCount() {
- return this.application?.nudgeCount ?? 0;
+ return this.args.nudgeCount ?? this.application?.nudgeCount ?? 0;
+ }
+
+ get lastNudgeAt() {
+ return this.args.lastNudgeAt ?? this.application?.lastNudgeAt ?? null;
+ }
+
+ get lastEditAt() {
+ return this.application?.lastEditAt ?? null;
}
get isNudgeDisabled() {
- if (this.status !== 'pending') {
+ if (this.isLoading || this.status !== 'pending') {
return true;
}
- if (!this.application?.lastNudgedAt) {
- return false;
+ return isWithinCooldown(this.lastNudgeAt);
+ }
+
+ get isEditDisabled() {
+ if (this.isLoading) {
+ return true;
}
- const now = Date.now();
- const lastNudgeTime = new Date(this.application.lastNudgedAt).getTime();
- const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
- return now - lastNudgeTime < TWENTY_FOUR_HOURS;
+ return isWithinCooldown(this.lastEditAt);
}
get socialLinks() {
@@ -80,20 +111,54 @@ export default class DetailHeader extends Component {
}
@action
- nudgeApplication() {
- //ToDo: Implement logic for callling nudge API here
- console.log('nudge application');
+ async nudgeApplication() {
+ this.isLoading = true;
+
+ try {
+ const response = await apiRequest(
+ NUDGE_APPLICATION_URL(this.application.id),
+ 'PATCH',
+ );
+
+ if (!response.ok) {
+ throw new Error(`Nudge failed: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ const updatedNudgeData = {
+ nudgeCount: data?.nudgeCount ?? this.nudgeCount + 1,
+ lastNudgeAt: data?.lastNudgeAt ?? new Date().toISOString(),
+ };
+
+ this.toast.success(
+ 'Nudge successful, you will be able to nudge again after 24hrs',
+ 'Success!',
+ TOAST_OPTIONS,
+ );
+
+ this.args.onNudge?.(updatedNudgeData);
+ } catch (error) {
+ console.error('Nudge failed:', error);
+ this.toast.error('Failed to nudge application', 'Error!', TOAST_OPTIONS);
+ } finally {
+ this.isLoading = false;
+ }
}
@action
editApplication() {
- //ToDo: Implement logic for edit application here
- console.log('edit application');
+ this.router.transitionTo('join', {
+ queryParams: {
+ edit: true,
+ dev: true,
+ step: 1,
+ },
+ });
}
@action
navigateToDashboard() {
- //ToDo: Navigate to dashboard site for admin actions
- console.log('navigate to dashboard');
+ this.router.transitionTo(`/intro?id=${this.userDetails?.id}`);
}
}
diff --git a/app/components/join-steps/status-card.hbs b/app/components/join-steps/status-card.hbs
index 6e9f32203..9970dfb5c 100644
--- a/app/components/join-steps/status-card.hbs
+++ b/app/components/join-steps/status-card.hbs
@@ -67,15 +67,25 @@
Profile Picture
{{#if this.isImageUploading}}
-
-
+
{{else}}
@@ -13,6 +12,7 @@
src={{this.imagePreview}}
alt="Profile preview"
class="image-preview"
+ data-test-image-preview
/>
diff --git a/app/components/new-join-steps/new-step-one.js b/app/components/new-join-steps/new-step-one.js
index fdf2742e1..b0bfd73f7 100644
--- a/app/components/new-join-steps/new-step-one.js
+++ b/app/components/new-join-steps/new-step-one.js
@@ -7,6 +7,8 @@ import {
ROLE_OPTIONS,
STEP_DATA_STORAGE_KEY,
} from '../../constants/new-join-form';
+import { USER_PROFILE_IMAGE_URL } from '../../constants/apis';
+import { TOAST_OPTIONS } from '../../constants/toast-options';
import BaseStepComponent from './base-step';
export default class NewStepOneComponent extends BaseStepComponent {
@@ -31,19 +33,24 @@ export default class NewStepOneComponent extends BaseStepComponent {
role: NEW_STEP_LIMITS.stepOne.role,
};
+ get fullName() {
+ const firstName = this.data.firstName || '';
+ const lastName = this.data.lastName || '';
+ return `${firstName} ${lastName}`.trim();
+ }
+
postLoadInitialize() {
if (
- !this.data.fullName &&
+ !this.data.firstName &&
+ !this.data.lastName &&
this.login.userData?.first_name &&
this.login.userData?.last_name
) {
- this.updateFieldValue(
- 'fullName',
- `${this.login.userData.first_name} ${this.login.userData.last_name}`,
- );
+ this.updateFieldValue('firstName', this.login.userData.first_name);
+ this.updateFieldValue('lastName', this.login.userData.last_name);
}
- if (this.data.profileImageBase64) {
- this.imagePreview = this.data.profileImageBase64;
+ if (this.data.imageUrl) {
+ this.imagePreview = this.data.imageUrl;
}
}
@@ -67,7 +74,7 @@ export default class NewStepOneComponent extends BaseStepComponent {
}
@action
- handleImageSelect(event) {
+ async handleImageSelect(event) {
const file = event.target.files?.[0];
if (!file || !file.type.startsWith('image/')) {
this.toast.error(
@@ -84,21 +91,50 @@ export default class NewStepOneComponent extends BaseStepComponent {
this.isImageUploading = true;
- const reader = new FileReader();
- reader.onload = (e) => {
- const base64String = e.target.result;
- this.imagePreview = base64String;
- this.updateFieldValue?.('profileImageBase64', base64String);
- this.isImageUploading = false;
- };
- reader.onerror = () => {
+ try {
+ const formData = new FormData();
+ formData.append('type', 'application');
+ formData.append('profile', file);
+
+ const response = await fetch(USER_PROFILE_IMAGE_URL, {
+ method: 'POST',
+ credentials: 'include',
+ body: formData,
+ });
+ if (response.ok) {
+ const data = await response.json();
+ const imageUrl = data?.image?.url || data.picture;
+
+ if (!imageUrl) {
+ this.toast.error(
+ 'Upload succeeded but no image URL was returned. Please try again.',
+ 'Error!',
+ );
+ return;
+ }
+ this.imagePreview = imageUrl;
+ this.updateFieldValue?.('imageUrl', imageUrl);
+
+ this.toast.success(
+ 'Profile image uploaded successfully!',
+ 'Success!',
+ TOAST_OPTIONS,
+ );
+ } else {
+ const errorData = await response.json();
+ this.toast.error(
+ errorData.message || 'Failed to upload image. Please try again.',
+ 'Error!',
+ TOAST_OPTIONS,
+ );
+ }
+ } catch (error) {
this.toast.error(
- 'Failed to read the selected file. Please try again.',
+ error.message || 'Failed to upload image. Please try again.',
'Error!',
);
+ } finally {
this.isImageUploading = false;
- };
-
- reader.readAsDataURL(file);
+ }
}
}
diff --git a/app/components/new-join-steps/new-step-six.hbs b/app/components/new-join-steps/new-step-six.hbs
index 17d2b4c2a..e44573d97 100644
--- a/app/components/new-join-steps/new-step-six.hbs
+++ b/app/components/new-join-steps/new-step-six.hbs
@@ -20,16 +20,9 @@
Full Name:
- {{if
- this.stepData.one.fullName
- this.stepData.one.fullName
- "Not provided"
- }}
+ {{if this.fullName this.fullName "Not provided"}}
@@ -52,9 +45,19 @@
Profile Image:
-
- Not uploaded
-
+ {{#if this.hasProfileImage}}
+
+

+
+ {{else}}
+
+ Not uploaded
+
+ {{/if}}
@@ -88,13 +91,16 @@
- Hobbies:
+ For fun:
- {{if
- this.stepData.three.hobbies
- this.stepData.three.hobbies
- "Not provided"
- }}
+ {{#if this.stepData.three.forFun}}
+ {{this.stepData.three.forFun}}
+ {{else}}
+ Not provided
+ {{/if}}
diff --git a/app/components/new-join-steps/new-step-six.js b/app/components/new-join-steps/new-step-six.js
index 9928e6631..7c8cef104 100644
--- a/app/components/new-join-steps/new-step-six.js
+++ b/app/components/new-join-steps/new-step-six.js
@@ -1,7 +1,7 @@
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';
+import { safeParse } from '../../utils/storage';
export default class NewStepSixComponent extends Component {
@tracked stepData = {
@@ -18,21 +18,17 @@ export default class NewStepSixComponent extends Component {
}
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),
- );
+ this.stepData.one = safeParse(STEP_DATA_STORAGE_KEY.stepOne);
+ this.stepData.two = safeParse(STEP_DATA_STORAGE_KEY.stepTwo);
+ this.stepData.three = safeParse(STEP_DATA_STORAGE_KEY.stepThree);
+ this.stepData.four = safeParse(STEP_DATA_STORAGE_KEY.stepFour);
+ this.stepData.five = safeParse(STEP_DATA_STORAGE_KEY.stepFive);
+ }
+
+ get fullName() {
+ const firstName = this.stepData.one.firstName || '';
+ const lastName = this.stepData.one.lastName || '';
+ return `${firstName} ${lastName}`.trim();
}
get userRole() {
@@ -54,4 +50,12 @@ export default class NewStepSixComponent extends Component {
get locationDisplay() {
return `${this.stepData.one.city}, ${this.stepData.one.state}, ${this.stepData.one.country}`;
}
+
+ get profileImage() {
+ return this.stepData.one.imageUrl || null;
+ }
+
+ get hasProfileImage() {
+ return !!this.profileImage;
+ }
}
diff --git a/app/components/new-join-steps/new-step-three.hbs b/app/components/new-join-steps/new-step-three.hbs
index 14ad2c677..141efdf4c 100644
--- a/app/components/new-join-steps/new-step-three.hbs
+++ b/app/components/new-join-steps/new-step-three.hbs
@@ -8,22 +8,22 @@
{{/if}}
diff --git a/app/components/new-join-steps/new-step-three.js b/app/components/new-join-steps/new-step-three.js
index 0c3fd6b25..6aaba6443 100644
--- a/app/components/new-join-steps/new-step-three.js
+++ b/app/components/new-join-steps/new-step-three.js
@@ -5,9 +5,11 @@ import {
} from '../../constants/new-join-form';
export default class NewStepThreeComponent extends BaseStepComponent {
- storageKey = STEP_DATA_STORAGE_KEY.stepThree;
+ get storageKey() {
+ return STEP_DATA_STORAGE_KEY.stepThree;
+ }
stepValidation = {
- hobbies: NEW_STEP_LIMITS.stepThree.hobbies,
+ forFun: NEW_STEP_LIMITS.stepThree.forFun,
funFact: NEW_STEP_LIMITS.stepThree.funFact,
};
}
diff --git a/app/components/new-join-steps/new-step-two.hbs b/app/components/new-join-steps/new-step-two.hbs
index d0113acd3..91512ce7d 100644
--- a/app/components/new-join-steps/new-step-two.hbs
+++ b/app/components/new-join-steps/new-step-two.hbs
@@ -27,18 +27,18 @@
{{/if}}
diff --git a/app/components/new-join-steps/new-step-two.js b/app/components/new-join-steps/new-step-two.js
index edb382940..c7b61423d 100644
--- a/app/components/new-join-steps/new-step-two.js
+++ b/app/components/new-join-steps/new-step-two.js
@@ -5,10 +5,12 @@ import {
} from '../../constants/new-join-form';
export default class NewStepTwoComponent extends BaseStepComponent {
- storageKey = STEP_DATA_STORAGE_KEY.stepTwo;
+ get storageKey() {
+ return STEP_DATA_STORAGE_KEY.stepTwo;
+ }
stepValidation = {
skills: NEW_STEP_LIMITS.stepTwo.skills,
- company: NEW_STEP_LIMITS.stepTwo.company,
+ institution: NEW_STEP_LIMITS.stepTwo.institution,
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
deleted file mode 100644
index 071a5742f..000000000
--- a/app/components/new-join-steps/thank-you-screen.hbs
+++ /dev/null
@@ -1,41 +0,0 @@
-