diff --git a/client/src/App.tsx b/client/src/App.tsx index 7cec003..0d4430d 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -10,6 +10,8 @@ import { About } from "@/pages/About"; import { News } from "@/pages/News"; import { Contact } from "@/pages/Contact"; import { SupportUs } from "@/pages/SupportUs"; +import { Privacy } from "@/pages/Privacy"; +import { NewsArticle } from "@/pages/NewsArticle"; function Router() { return ( @@ -18,8 +20,10 @@ function Router() { + + {/* Fallback to 404 */} diff --git a/client/src/content/news/2024-06-volunteers-summer.md b/client/src/content/news/2024-06-volunteers-summer.md new file mode 100644 index 0000000..52c2bae --- /dev/null +++ b/client/src/content/news/2024-06-volunteers-summer.md @@ -0,0 +1,8 @@ +--- +title: "Volunteers Summer 2024" +date: "2024-06-01" +summary: "Volunteers summer 2024" +image: "/Vols5-2880w-1024x768.avif" +slug: "volunteers-summer-2024" +--- +We welcomed a group of our volunteers to the centre for a catch up and discussion about upcoming events. It’s always great to get together! Find out how you can help out by **[Volunteering for us](/support)**. diff --git a/client/src/content/news/2024-07-summer-awards-2024.md b/client/src/content/news/2024-07-summer-awards-2024.md new file mode 100644 index 0000000..d8c04bd --- /dev/null +++ b/client/src/content/news/2024-07-summer-awards-2024.md @@ -0,0 +1,11 @@ +--- +title: "Summer Awards" +date: "2024-07-01" +summary: "Summer awards 2024" +image: "/Awardtime-1920w-768x1024.avif" +slug: "summer-awards-2024" +--- +Last week we had the pleasure of our awards ceremony. It’s always fantastic to see the progress of our riders, ponies and everyone involved in Highland Group RDA. Samantha received an award for.., read Samantha's story +. +![Alt text](/Art-1024x683.jpg) +![Alt text](/Louise-art-award-822x1024.jpg) diff --git a/client/src/content/news/2024-10-race-night.md b/client/src/content/news/2024-10-race-night.md new file mode 100644 index 0000000..046931c --- /dev/null +++ b/client/src/content/news/2024-10-race-night.md @@ -0,0 +1,8 @@ +--- +title: "Race Night" +date: "2024-10-01" +summary: "Race night" +image: "/race-night.avif" +slug: "race-night" +--- +![Alt text](/race-night.avif) diff --git a/client/src/content/news/2025-12-important-news.md b/client/src/content/news/2025-12-important-news.md new file mode 100644 index 0000000..aef587f --- /dev/null +++ b/client/src/content/news/2025-12-important-news.md @@ -0,0 +1,25 @@ +--- +title: "Important News" +date: "2025-12-19" +summary: "Important news to share with the community." +image: "/important-news.png" +slug: "important-news" +--- + +The Highland Riding for the Disabled Group (SCIO 007357) has been operating from +Reelig Estate since 2019 and where a plan was developed to create a range of indoor +and outdoor facilities. + +Due to the challenges of significant price increases across the construction sectors, +changes within the funding landscape and the groups need to focus on its core +charitable purpose, it was decided by the group that they needed to find an alternative +venue from which to operate and will terminate the lease on the 1st January 2026. +The landlords, Dan and Sarah Fraser have been supportive and patient through the +process and are able to offer a grazing license so that we have time to transition to our +new premises. + +Sarah and Dan would like to add that they are saddened by the groups’ decision but +respect their reasons. They are committed to ensure that the remaining facility will be +available for the community to use as that was part of the original planning +permission and wish the Highland RDA Group all the best in their future +developments. diff --git a/client/src/lib/news.ts b/client/src/lib/news.ts new file mode 100644 index 0000000..632e239 --- /dev/null +++ b/client/src/lib/news.ts @@ -0,0 +1,77 @@ +import { format } from "date-fns"; +import { marked } from "marked"; + +export type NewsArticle = { + title: string; + date: string; + summary: string; + image?: string; + slug: string; + body: string; + html: string; +}; + +type Frontmatter = Partial>; + +const newsModules = import.meta.glob("/src/content/news/*.md", { + eager: true, + as: "raw", +}); + +const parseFrontmatter = (raw: string): { frontmatter: Frontmatter; body: string } => { + const frontmatterMatch = raw.match(/^---\s*([\s\S]*?)\s*---\s*([\s\S]*)$/); + if (!frontmatterMatch) { + return { frontmatter: {}, body: raw.trim() }; + } + + const [, frontmatterBlock, body] = frontmatterMatch; + const frontmatter: Frontmatter = {}; + + frontmatterBlock.split("\n").forEach((line) => { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) return; + const [key, ...rest] = trimmed.split(":"); + if (!key || rest.length === 0) return; + const value = rest.join(":").trim().replace(/^["']|["']$/g, ""); + if (key in frontmatter) return; + (frontmatter as Record)[key.trim()] = value; + }); + + return { frontmatter, body: body.trim() }; +}; + +export const newsArticles: NewsArticle[] = Object.entries(newsModules) + .map(([path, raw]) => { + const { frontmatter, body } = parseFrontmatter(raw); + const slug = + frontmatter.slug || + path.split("/").pop()?.replace(/\.md$/, "") || + ""; + + return { + title: frontmatter.title || slug, + date: frontmatter.date || "", + summary: frontmatter.summary || "", + image: frontmatter.image, + slug, + body, + html: marked.parse(body), + }; + }) + .sort((a, b) => { + const aDate = Date.parse(a.date); + const bDate = Date.parse(b.date); + if (Number.isNaN(aDate) || Number.isNaN(bDate)) { + return b.title.localeCompare(a.title); + } + return bDate - aDate; + }); + +export const getNewsArticle = (slug: string): NewsArticle | undefined => + newsArticles.find((article) => article.slug === slug); + +export const formatNewsDate = (date: string): string => { + const parsed = Date.parse(date); + if (Number.isNaN(parsed)) return date; + return format(new Date(parsed), "MMM yyyy"); +}; diff --git a/client/src/pages/Desktop.tsx b/client/src/pages/Desktop.tsx index f7f16b0..c73e48f 100644 --- a/client/src/pages/Desktop.tsx +++ b/client/src/pages/Desktop.tsx @@ -53,18 +53,18 @@ export const Desktop = (): JSX.Element => { />
-

- +

+ Enriching lives through horses in the{" "} Highlands - + {" "} since{" "} 1975 - + .

@@ -72,7 +72,7 @@ export const Desktop = (): JSX.Element => {

-
+
"Our mission is to bring about meaningful and positive changes in the health and well-being of people with diff --git a/client/src/pages/News.tsx b/client/src/pages/News.tsx index 97496f7..fef7f5a 100644 --- a/client/src/pages/News.tsx +++ b/client/src/pages/News.tsx @@ -1,6 +1,7 @@ import React from "react"; import { SiteHeader } from "@/components/SiteHeader"; import { SiteFooter } from "@/components/SiteFooter"; +import { formatNewsDate, newsArticles } from "@/lib/news"; const navigationItems = [ { label: "Home", href: "/" }, @@ -10,27 +11,6 @@ const navigationItems = [ { label: "Contact", href: "/contact" }, ]; -const newsItems = [ - { - date: "Dec 2025", - title: "Winter sessions and holiday activities", - summary: - "Our winter timetable is live. Sessions continue with extra family days across December.", - }, - { - date: "Nov 2025", - title: "Volunteer spotlight: the team behind the yard", - summary: - "A huge thank you to our volunteers who keep everything running smoothly week to week.", - }, - { - date: "Oct 2025", - title: "New accessibility equipment supported by donors", - summary: - "We have added new adaptive tack to make sessions more comfortable and inclusive.", - }, -]; - export const News = (): JSX.Element => { return (
@@ -67,24 +47,46 @@ export const News = (): JSX.Element => {
-
-
- {newsItems.map((item) => ( -
-

- {item.date} -

-

- {item.title} -

-

- {item.summary} -

-
- ))} +
+
+
+ {newsArticles.map((item) => ( + + ))} +
diff --git a/client/src/pages/NewsArticle.tsx b/client/src/pages/NewsArticle.tsx new file mode 100644 index 0000000..d305464 --- /dev/null +++ b/client/src/pages/NewsArticle.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import { useRoute } from "wouter"; +import { SiteHeader } from "@/components/SiteHeader"; +import { SiteFooter } from "@/components/SiteFooter"; +import { formatNewsDate, getNewsArticle } from "@/lib/news"; +import NotFound from "@/pages/not-found"; + +const navigationItems = [ + { label: "Home", href: "/" }, + { label: "About", href: "/about" }, + { label: "News", href: "/news" }, + { label: "Support Us", href: "/support" }, + { label: "Contact", href: "/contact" }, +]; + +export const NewsArticle = (): JSX.Element => { + const [, params] = useRoute("/news/:slug"); + const article = getNewsArticle(params?.slug ?? ""); + + if (!article) { + return ; + } + + return ( +
+
+
+
+
+ +
+
+

+ {formatNewsDate(article.date)} +

+
+

+ {article.title} +

+

+ {article.summary} +

+
+
+
+
+ +
+
+ +
+ +
+
+ ); +}; diff --git a/client/src/pages/Privacy.tsx b/client/src/pages/Privacy.tsx new file mode 100644 index 0000000..e2a73f3 --- /dev/null +++ b/client/src/pages/Privacy.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { SiteHeader } from "@/components/SiteHeader"; +import { SiteFooter } from "@/components/SiteFooter"; + +const navigationItems = [ + { label: "Home", href: "/" }, + { label: "About", href: "/about" }, + { label: "News", href: "/news" }, + { label: "Support Us", href: "/support" }, + { label: "Contact", href: "/contact" }, +]; + +export const Privacy = (): JSX.Element => { + return ( +
+ + +
+
+
+

+ Privacy Policy +

+

+ Privacy Policy +

+

+ We respect your privacy and only collect the + information needed to respond to enquiries and + operate the site. +

+

+ We use Cloudflare Web Analytics, which does not use + cookies or track individuals. +

+
+
+ +
+
+ ); +}; diff --git a/package-lock.json b/package-lock.json index 2b34d19..97c811d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "framer-motion": "^11.13.1", "input-otp": "^1.4.2", "lucide-react": "^0.453.0", + "marked": "^17.0.1", "memorystore": "^1.6.7", "next-themes": "^0.4.6", "passport": "^0.7.0", @@ -5642,6 +5643,18 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", diff --git a/package.json b/package.json index 3b806aa..db71ca2 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "framer-motion": "^11.13.1", "input-otp": "^1.4.2", "lucide-react": "^0.453.0", + "marked": "^17.0.1", "memorystore": "^1.6.7", "next-themes": "^0.4.6", "passport": "^0.7.0",