Skip to content

Commit

Permalink
feat: switch grant finder to use history API for client-side routing
Browse files Browse the repository at this point in the history
  • Loading branch information
sanason committed Dec 14, 2023
1 parent 24e227c commit 01e4541
Show file tree
Hide file tree
Showing 12 changed files with 58 additions and 24 deletions.
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ See [here](../docker/README.md) for more information about commands to use when

![AWS SES Error](./img/error-aws-ses.png)

1. Visit `client_url/login` (e.g <http://localhost:8080/#/login>) and login w/ user `[email protected]`. You'll see a confirmation message on the screen. Check your logs to find the generated session link.
1. Visit `client_url/login` (e.g <http://localhost:8080/login>) and login w/ user `[email protected]`. You'll see a confirmation message on the screen. Check your logs to find the generated session link.

**NOTE:** if you only see a blank screen then ensure you've set up the `packages/client/.env`

Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/components/Layout.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<b-navbar :type="navBarType" :variant="navBarVariant" class="header-dropshadow py-1">
<b-navbar-brand href="/#/grants" class="d-flex align-items-center">
<b-navbar-brand to="grants" class="d-flex align-items-center">
<b-img v-if="myProfileEnabled" :src="require('../assets/usdr_logo_standard_wide.svg')" style="height: 2.5rem;" class="" alt="United States Digital Response - Home" />
<b-img v-else :src="require('../assets/usdr_logo_white_wide.svg')" style="height: 2.5rem;" class="" alt="United States Digital Response - Home" />
<h1 class="ml-3 mb-0 h4">Federal Grant Finder</h1>
Expand All @@ -25,7 +25,7 @@
<template v-else #button-content>
<em>{{loggedInUser.email}}</em>
</template>
<b-dropdown-item href="#/my-profile">
<b-dropdown-item to="my-profile">
<b-icon icon="person-circle" scale="1" class="dropdown-icon"></b-icon>
<p class="dropdown-item-text">My profile</p>
</b-dropdown-item>
Expand Down
21 changes: 16 additions & 5 deletions packages/client/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import store from '../store';

Vue.use(VueRouter);

const routes = [
export const routes = [
{
path: '/login',
name: 'login',
Expand All @@ -27,7 +27,13 @@ const routes = [
{
path: '/',
name: 'layout',
redirect: '/my-grants',
redirect: (to) => {
if (to.fullPath.startsWith('/#/')) {
// Redirect any old hash-style URLs to the new history API URL.
return { path: to.hash.substr(1), hash: '' };
}
return 'my-grants';
},
component: Layout,
meta: {
requiresAuth: true,
Expand Down Expand Up @@ -131,12 +137,17 @@ const routes = [
},
{
path: '*',
redirect: '/my-grants',
component: () => import('../views/NotFound.vue'),
name: 'notFound',
meta: {
requiresAuth: true,
},
},
];

const router = new VueRouter({
base: process.env.BASE_URL,
mode: 'history',
routes,
});

Expand All @@ -154,8 +165,8 @@ router.beforeEach((to, from, next) => {
next({ name: 'login', query: { redirect_to: redirectTo } });
} else if (to.name === 'login' && authenticated) {
next({ name: 'grants' });
} else if (to.name === 'not-found'
|| (to.meta.requiresMyProfileEnabled && !myProfileEnabled())
} else if (
(to.meta.requiresMyProfileEnabled && !myProfileEnabled())
|| (to.meta.requiresNewTerminologyEnabled && !newTerminologyEnabled())
) {
if (authenticated) {
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/views/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<div v-if="totalInterestedGrants > 4">
<b-row align-v="center" >
<b-navbar toggleable="sm py-0" bg-transparent class="gutter-activity row">
<a class="nav-link active" href="#/RecentActivity">See All Activity</a>
<b-link class="nav-link active" to="RecentActivity">See All Activity</b-link>
</b-navbar>
</b-row>
</div>
Expand Down Expand Up @@ -68,7 +68,7 @@
<div v-if="totalUpcomingGrants > 3">
<b-row align-v="center">
<b-navbar toggleable="sm py-0" bg-transparent class="gutter-upcoming row">
<a class="nav-link active" href="#/UpcomingClosingDates">See All Upcoming</a>
<b-link class="nav-link active" to="UpcomingClosingDates">See All Upcoming</b-link>
</b-navbar>
</b-row>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/views/MyProfile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default {
breadcrumb_items: [
{
text: 'Home',
href: '/#/grants',
to: 'grants',
},
{
text: 'My Profile',
Expand Down
7 changes: 7 additions & 0 deletions packages/client/src/views/NotFound.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<div id="app" style="text-align: center;">
<h1 style="font-size: 100px;">404</h1>
<h1>Page not found</h1>
<h1>The page you are looking for does not exist. Click here to go to the <b-link to="/">home page</b-link>.</h1>
</div>
</template>
19 changes: 19 additions & 0 deletions packages/client/tests/unit/router/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from 'chai';
import VueRouter from 'vue-router';

import { routes } from '@/router';

describe('Router logic', () => {
it('displays 404 page for unknown route', async () => {
const router = new VueRouter({ routes, mode: 'history' });
await router.push('/rants');
expect(router.currentRoute.name).to.equal('notFound');
});

it('redirects hash URLs to non-hash URLS', async () => {
const router = new VueRouter({ routes, mode: 'history' });
await router.push('/#/grants');
expect(router.currentRoute.fullPath).to.equal('/grants');
expect(router.currentRoute.name).to.equal('grants');
});
});
3 changes: 2 additions & 1 deletion packages/server/__tests__/api/sessions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('/api/sessions', () => {
'#/grants': false, // hash based URLs should still be prefixed with /
'/': true,
'/#/grants': true,
'/grants': true,
'/arpa_reporter/uploads': true,
};

Expand Down Expand Up @@ -78,7 +79,7 @@ describe('/api/sessions', () => {
}

it('redirects to valid redirect_to', async () => {
const redirect_to = '/#/grants';
const redirect_to = '/grants';
const { redirectUrl } = await testLogin({ redirect_to });
expect(redirectUrl).to.have.string(redirect_to);
});
Expand Down
3 changes: 0 additions & 3 deletions packages/server/__tests__/lib/redirect_validation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@ describe('Redirect Validation Module', () => {
expect(result_undefined).to.equal(null);
});
it('returns URL if part of safe URL list', async () => {
const grantsModalUrl = '#/grants?manageSettings=true';
const auditReportUrl = '/api/audit_report/3/99/some-example-file.xlsx';
const result_grants_modal = validatePostLoginRedirectPath(grantsModalUrl);
const result_audit_report = validatePostLoginRedirectPath(auditReportUrl);

expect(result_grants_modal).to.equal(grantsModalUrl);
expect(result_audit_report).to.equal(auditReportUrl);
});
it('returns null if string that does not start with / is passed in', async () => {
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/lib/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function getGrantDetail(grant, emailNotificationType) {
// estimated_funding: grant.estimated_funding, TODO: add once field is available in the database.
cost_sharing: grant.cost_sharing,
link_url: `https://www.grants.gov/web/grants/view-opportunity.html?oppId=${grant.grant_id}`,
grants_url: `${process.env.WEBSITE_DOMAIN}/#/${emailNotificationType === notificationType.grantDigest ? 'grants' : 'my-grants'}`,
grants_url: `${process.env.WEBSITE_DOMAIN}/${emailNotificationType === notificationType.grantDigest ? 'grants' : 'my-grants'}`,
view_grant_label: emailNotificationType === notificationType.grantDigest ? undefined : 'View My Grants',
},
);
Expand Down Expand Up @@ -163,7 +163,7 @@ async function sendGrantAssignedNotficationForAgency(assignee_agency, grantDetai
const emailHTML = module.exports.addBaseBranding(grantAssignedBody, {
tool_name: 'Grants Identification Tool',
title: 'Grants Assigned Notification',
notifications_url: `${process.env.WEBSITE_DOMAIN}/#/grants?manageSettings=true`,
notifications_url: `${process.env.WEBSITE_DOMAIN}/grants?manageSettings=true`,
});

// TODO: add plain text version of the email
Expand Down Expand Up @@ -207,7 +207,7 @@ async function buildDigestBody({ name, openDate, matchedGrants }) {
let additionalBody = grantDetails.join(contentSpacerStr).concat(contentSpacerStr);

const additionalButtonTemplate = fileSystem.readFileSync(path.join(__dirname, '../static/email_templates/_additional_grants_button.html'));
additionalBody += mustache.render(additionalButtonTemplate.toString(), { additional_grants_url: `${process.env.WEBSITE_DOMAIN}/#/grants` });
additionalBody += mustache.render(additionalButtonTemplate.toString(), { additional_grants_url: `${process.env.WEBSITE_DOMAIN}/grants` });

const formattedBody = mustache.render(formattedBodyTemplate.toString(), {
body_title: `${name} - ${matchedGrants.length} NEW GRANTS`,
Expand Down Expand Up @@ -238,7 +238,7 @@ async function sendGrantDigest({
const emailHTML = module.exports.addBaseBranding(formattedBody, {
tool_name: 'Federal Grant Finder',
title: 'New Grants Digest',
notifications_url: `${process.env.WEBSITE_DOMAIN}/#/grants?manageSettings=true`,
notifications_url: `${process.env.WEBSITE_DOMAIN}/grants?manageSettings=true`,
});

// TODO: add plain text version of the email
Expand Down
1 change: 0 additions & 1 deletion packages/server/src/lib/redirect_validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

function matchesSafeUrl(url) {
const safeUrls = [
/^#\/grants\?manageSettings=true$/,
/^\/api\/audit_report\/\d+\/\d+\/.*\.xlsx$/,
];

Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/routes/sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ router.post('/init', async (req, res) => {
const WEBSITE_DOMAIN = process.env.WEBSITE_DOMAIN || '';
const { passcode } = req.body;
if (!passcode) {
res.redirect(`${WEBSITE_DOMAIN}/#/login?message=${encodeURIComponent('Invalid access token')}`);
res.redirect(`${WEBSITE_DOMAIN}/login?message=${encodeURIComponent('Invalid access token')}`);
return;
}

const token = await getAccessToken(passcode);
if (!token) {
res.redirect(`${WEBSITE_DOMAIN}/#/login?message=${encodeURIComponent('Invalid access token')}`);
res.redirect(`${WEBSITE_DOMAIN}/login?message=${encodeURIComponent('Invalid access token')}`);
} else if (new Date() > token.expires) {
res.redirect(
`${WEBSITE_DOMAIN}/#/login?message=${encodeURIComponent('Access token has expired')}`,
`${WEBSITE_DOMAIN}/login?message=${encodeURIComponent('Access token has expired')}`,
);
} else if (token.used) {
res.redirect(`${WEBSITE_DOMAIN}/#/login?message=${encodeURIComponent(
res.redirect(`${WEBSITE_DOMAIN}/login?message=${encodeURIComponent(
'Login link has already been used - please re-submit your email address',
)}`);
} else {
Expand Down

0 comments on commit 01e4541

Please sign in to comment.