This branch sends readers to the Research Gallery (the public Plugin Studio on its own subdomain) and loads the site's maps from that subdomain's /cdn. Both addresses belong to the deployment: NUXT_PUBLIC_RESEARCH_GALLERY_URL https://research.<host>/ NUXT_PUBLIC_DX_MAPS_BASE https://research.<host>/cdn/dx-maps About 4,000 pages are prerendered, and a prerendered page carries the build's runtime config, so an address read there is baked into the build. One build could then serve only one deployment; the CDN branch failed that way. So neither address is in nuxt.config any more: - the server reads both from its environment per request (server/utils/dx-runtime.ts); - GET /api/dx-runtime answers them, no-store. Every map (DoxaMapSlot via useDoxaMap, /research, /embed-test, and the static pages under public/) asks it first, then loads its bundle from that tree. The map's element is added only once the tree is known, and the map bundle's preload is added then, still while Mapbox downloads; - every gallery link, the Research menu item, and the /regions and /countries index rules go to this site's /research-gallery?tab=&lang=, which 302s (no-store) to the gallery. It works without JavaScript, and a prerendered page names no host; - old /js/doxa-maps and /embed/doxa-maps-build links are redirected per request by server/middleware/legacy-map-links.ts. They were build-time route rules. No fallback host: the maps-dev and research defaults are gone. When a setting is unset, /research-gallery answers 503 and each map says which setting to set. Changing an address needs a restart, not a rebuild.
196 lines
6 KiB
Vue
196 lines
6 KiB
Vue
<script setup lang="ts">
|
|
// Index of every WAGF region with the countries that have people groups in it.
|
|
// A region heading opens that region's dashboard (/regions/<region>); a country
|
|
// opens its own page (/regions/<country>). Data comes from /api/regions
|
|
// (derived from the prayer API).
|
|
|
|
import type { RegionSummary } from '~~/config/countries-meta'
|
|
|
|
// Regions is a view of the Research Gallery now, so /regions (in every language) REDIRECTS there with
|
|
// ?tab=regions-dashboard and old links keep working. The region and country detail pages (/regions/<slug>)
|
|
// stay. Set USE_LEGACY_REGIONS_PAGE to true (inside the middleware) to bring this index page back.
|
|
// The redirect goes through this site's /research-gallery, which resolves the deployment's gallery
|
|
// address on the server (server/routes/research-gallery.get.ts), so no address is read here.
|
|
definePageMeta({
|
|
middleware: [() => {
|
|
const USE_LEGACY_REGIONS_PAGE = false
|
|
if (!USE_LEGACY_REGIONS_PAGE) {
|
|
const lang = (useNuxtApp().$i18n as { locale: { value: string } })?.locale?.value
|
|
const q = new URLSearchParams({ tab: 'regions-dashboard' })
|
|
if (lang && lang !== 'en') q.set('lang', lang)
|
|
return navigateTo(`/research-gallery?${q}`, { external: true, redirectCode: 301 })
|
|
}
|
|
}]
|
|
})
|
|
|
|
const { t, locale } = useI18n()
|
|
const localePath = useLocalePath()
|
|
|
|
const { data: regions } = await useAsyncData(
|
|
`regions-index-${locale.value}`,
|
|
async () => {
|
|
const res = await $fetch<{ regions: RegionSummary[] }>('/api/regions', {
|
|
query: { lang: locale.value }
|
|
})
|
|
return res.regions
|
|
},
|
|
{ watch: [locale], default: () => [] as RegionSummary[] }
|
|
)
|
|
|
|
// Locale grouping separators with Latin digits, matching the rest of the site.
|
|
const formatNumber = (n: number) => n.toLocaleString(`${locale.value}-u-nu-latn`)
|
|
|
|
// Placeholders are %tokens% substituted manually (see the map note on the
|
|
// country page for why vue-i18n's {0} slots aren't used here).
|
|
function summaryLine(region: RegionSummary): string {
|
|
return t('%count% people groups in %countries% countries')
|
|
.replace('%count%', formatNumber(region.count))
|
|
.replace('%countries%', formatNumber(region.countryCount))
|
|
}
|
|
|
|
function dashboardLink(region: RegionSummary): string {
|
|
return t('See %region% dashboard').replace('%region%', region.label || t('Other'))
|
|
}
|
|
|
|
useSeoMeta({
|
|
title: () => `${t('Regions')} — Doxa`,
|
|
description: () => t('Find unreached people groups by region and country.')
|
|
})
|
|
|
|
useTextHighlight()
|
|
</script>
|
|
|
|
<template>
|
|
<div class="container page-content stack stack--3xl">
|
|
<div class="stack stack--xs text-center">
|
|
<h1
|
|
class="highlight"
|
|
data-highlight-last
|
|
>
|
|
{{ t('Regions') }}
|
|
</h1>
|
|
<p class="color-brand-lighter">
|
|
{{ t('Find unreached people groups by region and country.') }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="stack stack--lg">
|
|
<div
|
|
v-for="region in regions"
|
|
:key="region.slug"
|
|
class="region-card"
|
|
>
|
|
<div class="region-card__header">
|
|
<h2 class="region-card__title">
|
|
<NuxtLink
|
|
:to="localePath(`/regions/${region.slug}`)"
|
|
class="region-card__link | with-icon"
|
|
>
|
|
{{ region.label || t('Other') }}
|
|
<svg
|
|
class="icon | rotate-90 right"
|
|
aria-hidden="true"
|
|
>
|
|
<use href="/assets/icons/arrow-chevron.svg#chevron-up" />
|
|
</svg>
|
|
</NuxtLink>
|
|
</h2>
|
|
<p class="region-card__summary">
|
|
{{ summaryLine(region) }}
|
|
</p>
|
|
</div>
|
|
<div class="region-card__body">
|
|
<ul class="countries-grid">
|
|
<li
|
|
v-for="c in region.countries"
|
|
:key="c.slug"
|
|
>
|
|
<NuxtLink :to="localePath(`/regions/${c.slug}`)">
|
|
{{ c.name }} <span class="color-brand-lighter">({{ c.count }})</span>
|
|
</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
<p class="region-card__footer">
|
|
<NuxtLink
|
|
:to="localePath(`/regions/${region.slug}`)"
|
|
class="with-icon"
|
|
>
|
|
{{ dashboardLink(region) }}
|
|
<svg
|
|
class="icon | rotate-90 right"
|
|
aria-hidden="true"
|
|
>
|
|
<use href="/assets/icons/arrow-chevron.svg#chevron-up" />
|
|
</svg>
|
|
</NuxtLink>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Card with a brand-coloured header band (region name + count) above the
|
|
white country list, following the site's two-tone card pattern. Plain divs
|
|
rather than <section>, which carries the page-level section padding. */
|
|
.region-card {
|
|
border-radius: var(--border-radius-xlg);
|
|
overflow: hidden;
|
|
background-color: var(--color-surface-white);
|
|
}
|
|
|
|
.region-card__header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-2xs);
|
|
padding: var(--spacing-md) clamp(var(--spacing-md), 4vw, var(--spacing-2xl));
|
|
background-color: var(--color-surface-brand);
|
|
color: var(--color-text-on-brand);
|
|
}
|
|
|
|
.region-card__title {
|
|
font-size: clamp(var(--font-size-xl), 4vw, var(--font-size-3xl));
|
|
}
|
|
|
|
.region-card__link {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.region-card__link:hover {
|
|
color: var(--color-brand-primary);
|
|
}
|
|
|
|
.region-card__summary {
|
|
font-size: var(--font-size-md);
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.region-card__body {
|
|
padding: var(--spacing-lg) clamp(var(--spacing-md), 4vw, var(--spacing-2xl)) var(--spacing-xl);
|
|
}
|
|
|
|
/* Keeps the site's list bullets (global ul padding + ::before dot) and lays
|
|
the countries out in responsive columns. */
|
|
.countries-grid {
|
|
margin: 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
gap: var(--spacing-xs) var(--spacing-xl);
|
|
}
|
|
|
|
.countries-grid a {
|
|
text-decoration: none;
|
|
}
|
|
|
|
.countries-grid a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.region-card__footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
margin-top: var(--spacing-lg);
|
|
}
|
|
</style>
|