diff --git a/client/src/pages/SearchResultPage.vue b/client/src/pages/SearchResultPage.vue index 0c5f82ee..8ddfe9e9 100644 --- a/client/src/pages/SearchResultPage.vue +++ b/client/src/pages/SearchResultPage.vue @@ -42,32 +42,34 @@
- - +

{{ section.header }} - +

- - - -
+
+ + + +
+
- - diff --git a/client/src/pages/__tests__/__snapshots__/searchResultPage.test.js.snap b/client/src/pages/__tests__/__snapshots__/searchResultPage.test.js.snap index b0190c24..1c818fc2 100644 --- a/client/src/pages/__tests__/__snapshots__/searchResultPage.test.js.snap +++ b/client/src/pages/__tests__/__snapshots__/searchResultPage.test.js.snap @@ -4,7 +4,7 @@ exports[`SearchResultPage renders with data > Calls API and renders search resul

Data Sources Search results

-

Searching for "calls" in "Cook". Found 1 result. +

Searching for "calls" in "Cook". Found 3 results.

-
-

Police & public interactions

-
-

311 Calls for City of Chicago

- -

Record type

-
Calls for Service
-
-

Agency

-

Chicago Police Department - IL

+
+

Police & public interactions

+
+
+

Calls for Service for Cicero Police Department - IN

+ +

Record type

+
Calls for Service
+
+

Agency

+

Cicero Police Department - IN

+
+ +

Time range

+

2016–Unknown end

+

Formats available

+
    +
  • [
  • +
  • ]
  • +
+ +
+
+

Calls for Service for Chicago Police Department - IL

+ +

Record type

+
Calls for Service
+
+

Agency

+

Chicago Police Department - IL

+
+ +

Time range

+

2018–Unknown end

+

Formats available

+
    +
  • [
  • +
  • ]
  • +
+
- -

Time range

-

12/17/2018–Unknown end

-

Formats available

-
    -
  • [
  • -
  • '
  • -
  • C
  • -
  • S
  • -
  • V
  • -
  • '
  • -
  • ,
  • -
  • -
  • '
  • -
  • X
  • -
  • M
  • -
  • L
  • -
  • '
  • -
  • ,
  • -
  • -
  • '
  • -
  • R
  • -
  • D
  • -
  • F
  • -
  • '
  • -
  • ,
  • -
  • -
  • '
  • -
  • R
  • -
  • S
  • -
  • S
  • -
  • '
  • -
  • ]
  • -
- +

Time range

+

12/17/2018–Unknown end

+

Formats available

+
    +
  • [
  • +
  • '
  • +
  • C
  • +
  • S
  • +
  • V
  • +
  • '
  • +
  • ,
  • +
  • +
  • '
  • +
  • X
  • +
  • M
  • +
  • L
  • +
  • '
  • +
  • ,
  • +
  • +
  • '
  • +
  • R
  • +
  • D
  • +
  • F
  • +
  • '
  • +
  • ,
  • +
  • +
  • '
  • +
  • R
  • +
  • S
  • +
  • S
  • +
  • '
  • +
  • ]
  • +
+ +
diff --git a/client/src/router.js b/client/src/router.js index 1a80396e..3074298e 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -9,10 +9,21 @@ import ResetPassword from './pages/ResetPassword.vue'; import SearchResultPage from './pages/SearchResultPage.vue'; import NotFound from './pages/NotFound.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' }] }, + }, + { + path: '/search', + component: SearchResultPage, + name: 'SearchResultPage', + }, { path: '/search/:searchTerm/:location', component: SearchResultPage, @@ -46,14 +57,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;