Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(router): add logic to router to handle meta tags #216

Merged
merged 4 commits into from Mar 20, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
93 changes: 89 additions & 4 deletions client/src/router.js
Expand Up @@ -8,10 +8,21 @@ import QuickSearchPage from '../src/pages/QuickSearchPage.vue';
import ResetPassword from './pages/ResetPassword.vue';
import SearchResultPage from '../src/pages/SearchResultPage.vue';

export const PRIVATE_ROUTES = ['/change-password'];
import acronym from 'pdap-design-system/images/acronym.svg';

const routes = [
{ path: '/', component: QuickSearchPage, name: 'QuickSearchPage' },
{
path: '/',
component: QuickSearchPage,
name: 'QuickSearchPage',
// Use meta property on route to override meta tag defaults
// meta: { title: 'Police Data Accessibility Project - Search', metaTags: [{ property: 'og:title', title: 'Police Data Accessibility Project - Search' }] },
Comment on lines +18 to +19
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @josh-chamberlain just wanted to call your attention to this. We can override any meta tag by route (in absence of specific route data, we fall back to the defaults). So if there are any of them that should have specific values, let me know and I can add those here as well.

},
{
path: '/search',
component: SearchResultPage,
name: 'SearchResultPage',
},
{
path: '/search/:searchTerm/:location',
component: SearchResultPage,
Expand Down Expand Up @@ -44,14 +55,88 @@ const router = createRouter({
routes,
});

router.beforeEach(async (to) => {
router.beforeEach(async (to, _, next) => {
// Update meta tags per route
refreshMetaTagsByRoute(to);

// redirect to login page if not logged in and trying to access a restricted page
const auth = useAuthStore();

if (PRIVATE_ROUTES.includes(to.fullPath) && !auth.userId) {
auth.returnUrl = to.path;
router.push('/login');
}

next();
});

// Util
export const PRIVATE_ROUTES = ['/change-password'];

const DEFAULT_META_TAGS = new Map([
[
'description',
'Data and tools for answering questions about any police system in the United States',
],
['title', 'Police Data Accessibility Project'],
[
'og:description',
'Data and tools for answering questions about any police system in the United States',
],
['og:title', 'Police Data Accessibility Project'],
['og:type', 'website'],
['og:site_name', 'PDAP'],
['og:image', acronym],
]);
const META_PROPERTIES = [...DEFAULT_META_TAGS.keys(), 'og:url'];

/**
* Adds meta tags by route
* @param {RouteLocationNormalized} to Vue router route location
*/
function refreshMetaTagsByRoute(to) {
// Get nearest matched route that has title / meta tag overrides
const nearestRouteWithTitle = [...to.matched]
.reverse()
.find((route) => route?.meta?.title);

const nearestRouteWithMeta = [...to.matched]
.reverse()
.find((route) => route?.meta?.metaTags);

// Update document title
document.title =
nearestRouteWithTitle?.meta?.title ?? DEFAULT_META_TAGS.get('title');

// Update meta tags
Array.from(document.querySelectorAll('[data-controlled-meta]')).forEach(
(el) => el.parentNode.removeChild(el),
);

META_PROPERTIES.filter((prop) => prop !== 'title')
.map((prop) => {
const tagInRouteMetaData = nearestRouteWithMeta?.meta?.metaTags?.find(
(tag) => tag.property === prop,
);

let content;
switch (true) {
case prop === 'og:url':
content = `${import.meta.env.VITE_VUE_APP_BASE_URL}${to.fullPath}`;
break;
case Boolean(tagInRouteMetaData):
content = tagInRouteMetaData.content;
break;
default:
content = DEFAULT_META_TAGS.get(prop);
}

const tag = document.createElement('meta');
tag.setAttribute(prop.includes(':') ? 'property' : 'name', prop);
tag.setAttribute('content', content);
tag.setAttribute('data-controlled-meta', true);
return tag;
})
.forEach((tag) => document.head.appendChild(tag));
}

export default router;