Skip to content

Commit

Permalink
Merge pull request #713 from MinBZK/termen-and-publications
Browse files Browse the repository at this point in the history
Fix termen pages SEO and add publicaties page
  • Loading branch information
onursagir authored Jan 6, 2025
2 parents 9db1b34 + 4b958cf commit f47227a
Show file tree
Hide file tree
Showing 21 changed files with 836 additions and 126 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-moose-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"web": patch
---

Added SEO tags for `/termen/*` pages
5 changes: 5 additions & 0 deletions .changeset/olive-jeans-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"web": minor
---

Added `publicaties` page
6 changes: 6 additions & 0 deletions .changeset/sharp-planets-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"web": minor
"cms": minor
---

Added `publication` content type
58 changes: 58 additions & 0 deletions apps/cms/src/api/publication/content-types/publication/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"kind": "collectionType",
"collectionName": "publications",
"info": {
"singularName": "publication",
"pluralName": "publications",
"displayName": "Publication",
"description": ""
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {},
"attributes": {
"title": {
"type": "string"
},
"summary": {
"type": "text"
},
"content": {
"type": "richtext"
},
"tags": {
"type": "text"
},
"slug": {
"type": "uid",
"targetField": "title"
},
"authors": {
"type": "text"
},
"location": {
"type": "string"
},
"file": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": [
"images",
"videos",
"audios",
"files"
]
},
"cite": {
"type": "text"
},
"originalSource": {
"type": "string"
},
"publicationDate": {
"type": "date"
}
}
}
7 changes: 7 additions & 0 deletions apps/cms/src/api/publication/controllers/publication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* publication controller
*/

import { factories } from '@strapi/strapi'

export default factories.createCoreController('api::publication.publication');
7 changes: 7 additions & 0 deletions apps/cms/src/api/publication/routes/publication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* publication router
*/

import { factories } from '@strapi/strapi';

export default factories.createCoreRouter('api::publication.publication');
7 changes: 7 additions & 0 deletions apps/cms/src/api/publication/services/publication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* publication service
*/

import { factories } from '@strapi/strapi';

export default factories.createCoreService('api::publication.publication');
3 changes: 3 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@octokit/rest": "^20.0.2",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-portal": "^1.0.4",
Expand All @@ -24,6 +25,7 @@
"@sindresorhus/slugify": "^2.2.1",
"@tabler/icons-react": "^2.46.0",
"@total-typescript/ts-reset": "^0.5.1",
"@types/react-copy-to-clipboard": "^5.0.7",
"cheerio": "1.0.0-rc.12",
"clsx": "^2.1.0",
"compare-versions": "^6.1.0",
Expand All @@ -39,6 +41,7 @@
"pg": "^8.8.0",
"puppeteer": "^22.9.0",
"react": "^18",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18",
"react-remove-scroll": "^2.5.9",
"remark-gfm": "^3.0.1",
Expand Down
15 changes: 15 additions & 0 deletions apps/web/src/app/publicaties/[slug]/copy-cite-to-clipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client';

import { Button } from '@/components/button';
import { IconClipboard } from '@tabler/icons-react';
import { CopyToClipboard } from 'react-copy-to-clipboard';

interface Props {
text: string;
}

export const CopyCiteToClipboard: React.FC<Props> = ({ text }) => (
<CopyToClipboard text={text}>
<Button startIcon={<IconClipboard />}>Kopiëren naar klembord</Button>
</CopyToClipboard>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { db } from '@/drizzle/db';
import { files, filesRelatedMorphs, publications } from '@/drizzle/schema';
import { and, eq } from 'drizzle-orm';

interface Args {
slug: string;
}

export async function getPublicationFileBySlug({ slug }: Args) {
const [result] = await db
.select({ file: files })
.from(publications)
.leftJoin(filesRelatedMorphs, eq(publications.id, filesRelatedMorphs.relatedId))
.leftJoin(files, eq(files.id, filesRelatedMorphs.fileId))
.where(and(eq(publications.slug, slug), eq(filesRelatedMorphs.relatedType, 'api::publication.publication')))
.limit(1);

return result.file;
}
22 changes: 22 additions & 0 deletions apps/web/src/app/publicaties/[slug]/download/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { resolveCmsImage } from '@/common/resolve-cms-image';
import slugify from '@sindresorhus/slugify';
import { NextRequest } from 'next/server';
import { getPublicationFileBySlug } from './get-publication-file-by-slug';

export async function GET(req: NextRequest, { params }: { params: { slug: string } }) {
const file = await getPublicationFileBySlug({ slug: params.slug });

const fetchResponse = await fetch(resolveCmsImage(file as any), {
method: 'GET',
});

const headers = new Headers();

headers.set('Content-Disposition', `attachment; filename="${slugify(params.slug)}${file?.ext}"`);
headers.set('Content-Type', file?.mime || '');

return new Response(fetchResponse.body, {
headers,
status: fetchResponse.status,
});
}
13 changes: 13 additions & 0 deletions apps/web/src/app/publicaties/[slug]/get-publication-by-slug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { db } from '@/drizzle/db';
import { publications } from '@/drizzle/schema';
import { eq } from 'drizzle-orm';

interface Args {
slug: string;
}

export async function getPublicationBySlug({ slug }: Args) {
const [publication] = await db.select().from(publications).where(eq(publications.slug, slug)).limit(1);

return publication;
}
120 changes: 120 additions & 0 deletions apps/web/src/app/publicaties/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { EnhanceMenuBreadcrumbs } from '@/app/menu-breadcrumbs';
import { Button } from '@/components/button';
import { Container } from '@/components/container';
import * as Dialog from '@/components/dialog';
import { Pill } from '@/components/pill';
import { RemoteMdx } from '@/components/remote-mdx';
import { Typography } from '@/components/typography';
import { IconArrowLeft, IconBlockquote, IconDownload, IconExternalLink, IconFileTypePdf } from '@tabler/icons-react';
import { format } from 'date-fns';
import Link from 'next/link';
import { CopyCiteToClipboard } from './copy-cite-to-clipboard';
import { getPublicationBySlug } from './get-publication-by-slug';

type Args = { params: { slug: string } };

export default async function PublicatiesSlugPage({ params }: Args) {
const publication = await getPublicationBySlug({ slug: params.slug });

return (
<>
<EnhanceMenuBreadcrumbs append={publication.title!} />
<Container component="header" className="-mt-14 bg-[#F7FBFD] py-12">
<Typography variant="h1">{publication.title}</Typography>
<section className="flex flex-col gap-y-1 pb-2 pt-12">
<Typography className="!mt-0 text-lg text-primary-dark">
Gepubliceerd op: {format(publication.publicationDate!, 'dd MMMM yyyy')}
</Typography>
<Typography className="!mt-0 text-lg text-primary-dark">Auteurs: {publication.authors}</Typography>
<Typography className="!mt-0 text-lg text-primary-dark">Locatie: {publication.location}</Typography>
</section>
<section>
<ul className="-ml-4 flex">
<li>
<Dialog.Root>
<Dialog.Trigger asChild>
<Button startIcon={<IconBlockquote />} variant="text">
Citeer dit artikel
</Button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content>
<Dialog.Title>Citeer deze publicatie</Dialog.Title>
<Dialog.Description>
{publication.cite}
<div className="mt-4 flex justify-center">
<CopyCiteToClipboard text={publication.cite!} />
</div>
</Dialog.Description>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</li>
<li>
<Button
component={Link}
href={`/publicaties/${params.slug}/download`}
startIcon={<IconFileTypePdf />}
variant="text"
>
Download dit artikel
</Button>
</li>
<li>
<Button
component="a"
variant="text"
target="_blank"
rel="noopener noreferrer"
startIcon={<IconExternalLink />}
href={publication.originalSource!}
>
Originele bron
</Button>
</li>
</ul>
</section>
</Container>
<Container component="main" className="pt-12">
<section>
<Typography variant="h2">Samenvatting</Typography>
<RemoteMdx content={publication.content!} />
</section>
<section>
<Typography variant="h3">Trefwoorden</Typography>
<ul className="mt-4 flex gap-x-6">
{publication.tags?.split(',').map((tag) => (
<Pill key={tag} component="li" label={tag} />
))}
</ul>
</section>
</Container>
<Container component="footer" className="pb-12">
<div className="mt-16 flex justify-between">
<Button component={Link} href="/publicaties" startIcon={<IconArrowLeft />}>
Overzicht
</Button>
<Button
component={Link}
color="primary-light"
href={`/publicaties/${params.slug}/download`}
startIcon={<IconDownload />}
>
Download
</Button>
</div>
</Container>
</>
);
}

export async function generateMetadata({ params }: Args) {
const publication = await getPublicationBySlug({ slug: params.slug });

return {
title: publication.title + ' - Regelregister van de Nederlandse Overheid',
keywords: publication.tags,
authors: publication.authors?.split(',').map((author) => ({ name: author.trim() })),
description: publication.summary,
};
}
26 changes: 26 additions & 0 deletions apps/web/src/app/publicaties/card-publication.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Pill } from '@/components/pill';
import { Typography } from '@/components/typography';
import { getPublications } from './get-publications';
import Link from 'next/link';

interface Props {
publication: Awaited<ReturnType<typeof getPublications>>[number];
}

export const CardPublication: React.FC<Props> = ({ publication }) => {
if (!publication.slug || !publication.title || !publication.summary) return null;

return (
<Link href={'/publicaties/' + publication.slug} className="rounded-md border border-grey-lighter p-6">
<Typography variant="h3" className="mt-0 text-xl text-grey-dark">
{publication.title}
</Typography>
<Typography className="mb-4 mt-2 text-grey-dark">{publication.summary}</Typography>
<div className="flex items-start gap-x-4">
{publication.tags?.split(',').map((tag) => (
<Pill key={tag} label={tag.trim()} />
))}
</div>
</Link>
);
};
11 changes: 11 additions & 0 deletions apps/web/src/app/publicaties/get-publications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { db } from '@/drizzle/db';
import { publications } from '@/drizzle/schema';
import { and, desc, lte } from 'drizzle-orm';

export function getPublications() {
return db
.select()
.from(publications)
.orderBy(desc(publications.publicationDate))
.where(and(lte(publications.publishedAt, new Date().toISOString())));
}
28 changes: 28 additions & 0 deletions apps/web/src/app/publicaties/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Container } from '@/components/container';
import { RemotePage } from '@/components/remote-page';
import { Typography } from '@/components/typography';
import { Metadata } from 'next';
import { CardPublication } from './card-publication';
import { getPublications } from './get-publications';

export default async function PublicatiesPage() {
const publications = await getPublications();

return (
<>
<Container className="mb-16">
<Typography variant="h1">Publicaties</Typography>
<RemotePage page="publicaties" />
<div className="mt-28 flex flex-col gap-y-4">
{publications.map((publication) => (
<CardPublication key={publication.id} publication={publication} />
))}
</div>
</Container>
</>
);
}

export const metadata: Metadata = {
title: 'Regelregister van de Nederlandse Overheid - Publicaties',
};
Loading

0 comments on commit f47227a

Please sign in to comment.