Skip to content

Commit 67db63a

Browse files
feat(components): improve blog presentation and content structure and connect blog components with collections (#642)
1 parent 484f6a9 commit 67db63a

File tree

98 files changed

+40942
-523
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+40942
-523
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
2+
version = 1
3+
name = "website"
4+
5+
[setup]
6+
script = ""
7+
8+
[[actions]]
9+
name = "Run"
10+
icon = "run"
11+
command = '''
12+
pnpm install
13+
pnpm dev
14+
'''
15+
16+
[[actions]]
17+
name = "run unit tests"
18+
icon = "test"
19+
command = "pnpm tests --project=unit"
20+
21+
[[actions]]
22+
name = "run integration tests"
23+
icon = "test"
24+
command = "pnpm tests --project=integration"
25+
26+
[[actions]]
27+
name = "run storybook tests"
28+
icon = "test"
29+
command = "pnpm tests --project=storybook"

.vscode/extensions.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"ms-vscode.vscode-typescript-next",
88
"ms-vsliveshare.vsliveshare",
99
"bradlc.vscode-tailwindcss",
10-
"GitHub.copilot",
1110
"GitHub.copilot-chat",
1211
"GitHub.vscode-pull-request-github",
1312
"github.vscode-github-actions",

docs/datamodel/er-mvp.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ erDiagram
88
99
BasicUsers {
1010
text id PK "UUID, auto by Payload"
11+
text stableId "Stable seed identifier, unique"
1112
text supabaseUserId "Supabase user id, unique, set by auth hook"
1213
text firstName "Given name, required"
1314
text lastName "Family name, required"
@@ -324,9 +325,10 @@ erDiagram
324325
325326
PlatformContentMedia {
326327
text id PK "UUID, auto by Payload"
328+
text stableId "Stable seed identifier, unique"
327329
text alt "Screen-reader alt text, required"
328330
richText caption "Optional caption"
329-
relationship createdBy FK "Uploader (BasicUsers), auto-set"
331+
relationship createdBy FK "Uploader (BasicUsers), auto-set, required"
330332
text storagePath "Resolved storage path, readOnly"
331333
text prefix "S3 prefix, readOnly"
332334
upload file "Platform-managed media asset"
@@ -339,7 +341,7 @@ erDiagram
339341
text title "Post title, required"
340342
text slug "System: generated, unique"
341343
relationship tags FK "Relationship to Tags, hasMany"
342-
upload heroImage FK "Relationship to PlatformContentMedia"
344+
upload heroImage FK "Relationship to PlatformContentMedia, required"
343345
richText content "Article rich text, required"
344346
text excerpt "SEO/meta summary, required"
345347
relationship relatedPosts FK "Self-relationship, hasMany"
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 16 additions & 0 deletions
Loading

src/app/(frontend)/globals.css

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,7 @@
160160
--text-size-72: 4.5rem; /* 72px */
161161
--text-size-72--line-height: 1.1389; /* 82px */
162162

163-
/* Next.js `next/font` injects `--font-dm-sans` via `src/app/(frontend)/layout.tsx` (dmSans.variable). */
164-
--font-sans: var(--font-dm-sans);
163+
--font-sans: 'DM Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
165164
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
166165
/* Radius scale with semantic naming (--radius = medium/base) */
167166
--radius-xs: calc(var(--radius) * 0.25); /* 0.125rem = 2px */

src/app/(frontend)/layout.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { Metadata } from 'next'
22

3-
import { cn } from '@/utilities/ui'
4-
import { DM_Sans } from 'next/font/google'
53
import React from 'react'
4+
import '@fontsource/dm-sans'
65

76
import { AdminBar } from '@/components/organisms/AdminBar'
87
import { Footer } from '@/components/templates/Footer/Component'
@@ -17,12 +16,6 @@ import { getCachedGlobal } from '@/utilities/getGlobals'
1716
import { normalizeNavItems } from '@/utilities/normalizeNavItems'
1817
import type { Footer as FooterType, Header as HeaderType } from '@/payload-types'
1918

20-
const dmSans = DM_Sans({
21-
subsets: ['latin'],
22-
variable: '--font-dm-sans',
23-
display: 'swap',
24-
})
25-
2619
export default async function RootLayout({ children }: { children: React.ReactNode }) {
2720
const { isEnabled } = await draftMode()
2821
const footerData: FooterType = await getCachedGlobal('footer', 1)()
@@ -32,7 +25,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
3225
const headerNavItems = normalizeNavItems(headerData)
3326

3427
return (
35-
<html className={cn(dmSans.variable)} lang="en" suppressHydrationWarning>
28+
<html lang="en" suppressHydrationWarning>
3629
<head>
3730
<link href="/favicon.ico" rel="icon" sizes="32x32" />
3831
<link href="/favicon.svg" rel="icon" type="image/svg+xml" />

src/app/(frontend)/page.tsx

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ import { LandingContact } from '@/components/organisms/Landing/LandingContact'
1111
import { BlogCardCollection } from '@/components/organisms/Blog/BlogCardCollection'
1212
import { FAQSection } from '@/components/organisms/FAQ'
1313
import { landingProcessPlaceholderStepImages } from '@/utilities/placeholders/landingProcess'
14+
import { normalizePost } from '@/utilities/blog/normalizePost'
15+
import { getPayload } from 'payload'
16+
import configPromise from '@payload-config'
1417

1518
// TODO(homepage): Replace hardcoded copy and Storybook placeholder assets with Payload-driven content.
1619
// This route is currently a visual scaffold for layout work.
1720

1821
import clinicHospitalExterior from '@/stories/assets/clinic-hospital-exterior.jpg'
19-
import blogBackground from '@/stories/assets/blog-background.jpg'
2022
import featureBackground from '@/stories/assets/feature-background.jpg'
2123
import ph80x80 from '@/stories/assets/placeholder-80-80.svg'
22-
import ph270x292 from '@/stories/assets/placeholder-270-292.svg'
2324
// TODO: Temporary fixtures for layout; replace with Payload data.
2425
import {
2526
clinicCategoriesData,
@@ -29,6 +30,32 @@ import {
2930
} from '@/stories/fixtures/listings'
3031

3132
export default async function Home() {
33+
// Fetch latest 3 blog posts for homepage
34+
const payload = await getPayload({ config: configPromise })
35+
const posts = await payload.find({
36+
collection: 'posts',
37+
depth: 1,
38+
limit: 3,
39+
overrideAccess: false,
40+
select: {
41+
title: true,
42+
slug: true,
43+
excerpt: true,
44+
content: true,
45+
categories: true,
46+
populatedAuthors: true,
47+
publishedAt: true,
48+
heroImage: true,
49+
meta: {
50+
image: true,
51+
description: true,
52+
},
53+
},
54+
sort: '-publishedAt',
55+
})
56+
57+
const normalizedPosts = posts.docs.map(normalizePost)
58+
3259
return (
3360
<main>
3461
<LandingHero
@@ -140,43 +167,13 @@ export default async function Home() {
140167
defaultOpenItemId={homepageFaqSection.defaultOpenItemId}
141168
/>
142169

143-
<BlogCardCollection
144-
variant="blue"
145-
intro="Stay informed with the latest healthcare insights, medical trends, and expert advice from our team of professionals."
146-
background={{
147-
media: {
148-
src: blogBackground,
149-
alt: '',
150-
imgClassName: 'opacity-20',
151-
priority: false,
152-
},
153-
overlay: {
154-
kind: 'none',
155-
},
156-
}}
157-
posts={[
158-
{
159-
title: 'Top 5 Medical Trends in 2024',
160-
dateLabel: '15 Jan 2024',
161-
excerpt: 'Discover the latest innovations shaping the future of healthcare and patient support systems.',
162-
image: { src: ph270x292, alt: 'Medical Trends' },
163-
},
164-
{
165-
title: 'How to Choose the Right Specialist',
166-
dateLabel: '02 Feb 2024',
167-
excerpt:
168-
'A comprehensive guide on what to look for when selecting a medical professional for your specific needs.',
169-
image: { src: ph270x292, alt: 'Choosing a Specialist' },
170-
},
171-
{
172-
title: 'The Importance of Regular Checkups',
173-
dateLabel: '10 Mar 2024',
174-
excerpt:
175-
'Why preventative care is crucial for long-term health and how often you should really be seeing your doctor.',
176-
image: { src: ph270x292, alt: 'Regular Checkups' },
177-
},
178-
]}
179-
/>
170+
{normalizedPosts.length > 0 && (
171+
<BlogCardCollection
172+
title="From our blog"
173+
intro="Explore practical insights, expert perspectives, and the latest topics across health and medicine."
174+
posts={normalizedPosts}
175+
/>
176+
)}
180177

181178
<LandingContact
182179
title="Contact"

src/app/(frontend)/partners/clinics/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,10 @@ export default function ClinicLandingPage() {
108108
<BlogCardCollection
109109
posts={clinicBlogData.map((p) => ({
110110
title: p.title,
111+
href: `/posts/${p.title.toLowerCase().replace(/\s+/g, '-')}`,
111112
excerpt: p.excerpt,
112113
dateLabel: p.date,
114+
readTime: '5 Min. Lesezeit',
113115
image: p.image ? { src: p.image, alt: p.title } : undefined,
114116
}))}
115117
/>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use client'
2+
3+
import React from 'react'
4+
5+
import { PostActionBar, type PostActionBarProps } from '@/components/molecules/PostActionBar'
6+
import { sharePostUrl } from '@/utilities/blog/sharePostUrl'
7+
8+
type PostShareActionBarProps = {
9+
shareUrl?: string
10+
shareTitle?: string
11+
shareDescription?: string
12+
backLink?: PostActionBarProps['backLink']
13+
shareLabel?: string
14+
}
15+
16+
export const PostShareActionBar: React.FC<PostShareActionBarProps> = ({
17+
shareUrl,
18+
shareTitle,
19+
shareDescription,
20+
backLink,
21+
shareLabel = 'Share',
22+
}) => {
23+
const handleShare = React.useCallback(async () => {
24+
await sharePostUrl({
25+
url: shareUrl,
26+
title: shareTitle,
27+
description: shareDescription,
28+
})
29+
}, [shareDescription, shareTitle, shareUrl])
30+
31+
return <PostActionBar backLink={backLink} shareButton={{ label: shareLabel, onClick: handleShare }} />
32+
}

0 commit comments

Comments
 (0)