Skip to content
Open
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
56 changes: 49 additions & 7 deletions app/components/application/detail-header.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
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';

export default class DetailHeader extends Component {
@service toast;

@tracked isLoading = false;
get application() {
return this.args.application;
}
Expand Down Expand Up @@ -41,18 +49,22 @@ 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 isNudgeDisabled() {
if (this.status !== 'pending') {
if (this.isLoading || this.status !== 'pending') {
return true;
}
if (!this.application?.lastNudgedAt) {
if (!this.lastNudgeAt) {
return false;
}
const now = Date.now();
const lastNudgeTime = new Date(this.application.lastNudgedAt).getTime();
const lastNudgeTime = new Date(this.lastNudgeAt).getTime();
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
return now - lastNudgeTime < TWENTY_FOUR_HOURS;
}
Expand Down Expand Up @@ -80,9 +92,39 @@ 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
Expand Down
8 changes: 8 additions & 0 deletions app/constants/apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,11 @@ export const APPLICATION_BY_ID_URL = (applicationId) => {
};

export const CREATE_APPLICATION_URL = `${APPS.API_BACKEND}/applications`;

export const NUDGE_APPLICATION_URL = (applicationId) => {
return `${APPS.API_BACKEND}/applications/${applicationId}/nudge`;
};

export const APPLICATIONS_BY_USER_URL = (userId) => {
return `${APPS.API_BACKEND}/applications?userId=${userId}&dev=true`;
};
19 changes: 19 additions & 0 deletions app/controllers/applications/detail.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { adminMessage } from '../../constants/applications';

export default class ApplicationsDetailController extends Controller {
@tracked nudgeCount = null;
@tracked lastNudgeAt = null;

get application() {
return this.model?.application;
}

get nudgeCountValue() {
return this.nudgeCount ?? this.application?.nudgeCount ?? 0;
}

get lastNudgeAtValue() {
return this.lastNudgeAt ?? this.application?.lastNudgeAt ?? null;
}

get currentUser() {
return this.model?.currentUser;
}
Expand Down Expand Up @@ -41,4 +54,10 @@ export default class ApplicationsDetailController extends Controller {
get showAdminMessage() {
return adminMessage(this.application?.status);
}

@action
handleApplicationNudge(nudgeData) {
this.nudgeCount = nudgeData.nudgeCount;
this.lastNudgeAt = nudgeData.lastNudgeAt;
}
}
25 changes: 16 additions & 9 deletions app/routes/applications/detail.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import {
APPLICATION_BY_ID_URL,
APPLICATIONS_BY_USER_URL,
SELF_USER_PROFILE_URL,
} from '../../constants/apis';
import { ERROR_MESSAGES } from '../../constants/error-messages';
Expand All @@ -13,7 +13,7 @@ export default class ApplicationsDetailRoute extends Route {
@service toast;
@service router;

async model(params) {
async model() {
try {
const userResponse = await apiRequest(SELF_USER_PROFILE_URL);
if (userResponse.status === 401) {
Expand All @@ -22,25 +22,32 @@ export default class ApplicationsDetailRoute extends Route {
return { application: null, currentUser: null };
}

const userData = await userResponse.json();
const userId = userData.id || userData.user?.id;

if (!userId) {
this.toast.error('User ID not found', 'Error!', TOAST_OPTIONS);
return { application: null, currentUser: userData };
}

const applicationResponse = await apiRequest(
APPLICATION_BY_ID_URL(params.id),
APPLICATIONS_BY_USER_URL(userId),
);

if (applicationResponse.status === 404) {
this.toast.error('Application not found', 'Error!', TOAST_OPTIONS);
return { application: null, currentUser: null };
return { application: null, currentUser: userData };
}

if (!applicationResponse.ok) {
throw new Error(`HTTP error! status: ${applicationResponse.status}`);
}

const userData = await userResponse.json();
const applicationData = await applicationResponse.json();
return {
application: applicationData?.application,
currentUser: userData,
};
const applications = applicationData?.applications || [];
const application = applications[0] || null;

return { application, currentUser: userData };
} catch (error) {
this.toast.error(
'Something went wrong. ' + error.message,
Expand Down
3 changes: 3 additions & 0 deletions app/templates/applications/detail.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
@application={{this.application}}
@userDetails={{this.currentUser}}
@isAdmin={{this.isAdmin}}
@onNudge={{this.handleApplicationNudge}}
@nudgeCount={{this.nudgeCountValue}}
@lastNudgeAt={{this.lastNudgeAtValue}}
data-test-detail-header
/>

Expand Down
88 changes: 86 additions & 2 deletions tests/integration/components/application/detail-header-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'website-www/tests/helpers';
import { render } from '@ember/test-helpers';
import { render, click, settled, waitFor } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { APPLICATIONS_DATA } from 'website-www/tests/constants/application-data';

Expand Down Expand Up @@ -85,12 +85,96 @@ module('Integration | Component | application/detail-header', function (hooks) {

this.set('application', {
status: 'pending',
lastNudgedAt: recentNudge,
lastNudgeAt: recentNudge,
});

await render(
hbs`<Application::DetailHeader @application={{this.application}} />`,
);
assert.dom('[data-test-button="nudge-button"]').hasAttribute('disabled');
});

test('it shows loading state during nudge API call', async function (assert) {
const application = {
...APPLICATIONS_DATA,
status: 'pending',
id: 'test-id',
};
this.set('application', application);
this.set('onNudge', () => {});

await render(hbs`
<Application::DetailHeader
@application={{this.application}}
@onNudge={{this.onNudge}}
/>
`);

const originalFetch = window.fetch;
let resolveNudge;
window.fetch = () =>
new Promise((resolve) => {
resolveNudge = () =>
resolve({
ok: true,
json: () =>
Promise.resolve({
nudgeCount: 1,
lastNudgeAt: new Date().toISOString(),
}),
});
});

click('[data-test-button="nudge-button"]');

await waitFor('[data-test-button="nudge-button"][disabled]');
assert.dom('[data-test-button="nudge-button"]').hasAttribute('disabled');

resolveNudge();
await settled();

window.fetch = originalFetch;
});

test('it calls onNudge callback with updated data on successful nudge', async function (assert) {
assert.expect(2);

const application = {
...APPLICATIONS_DATA,
status: 'pending',
nudgeCount: 5,
id: 'test-id',
};
this.set('application', application);
this.set('onNudge', (nudgeData) => {
assert.strictEqual(
nudgeData.nudgeCount,
6,
'Nudge count should be incremented',
);
assert.ok(nudgeData.lastNudgeAt, 'Last nudge at should be set');
});

const originalFetch = window.fetch;
window.fetch = () =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
nudgeCount: 6,
lastNudgeAt: new Date().toISOString(),
}),
});

await render(hbs`
<Application::DetailHeader
@application={{this.application}}
@onNudge={{this.onNudge}}
/>
`);

await click('[data-test-button="nudge-button"]');

window.fetch = originalFetch;
});
});
Loading