diff --git a/.env.example b/.env.example index 08a7b2d..b34807c 100644 --- a/.env.example +++ b/.env.example @@ -62,3 +62,29 @@ RESEND_AUDIENCE_ID= # routed to team@codewithpurpose.org, which is a real Google Workspace inbox. EMAIL_FROM="CodeWithPurpose " +# --- YouTube Data API (media library auto-pull) ----------------------------- +# Powers the YouTube side of /media and the homepage "See what we're making" +# section: the channel's recent uploads are fetched and refreshed hourly, so +# a new video appears on the site on its own — no code change, no redeploy. +# +# Google Cloud Console -> APIs & Services -> Credentials -> Create API key, +# with the "YouTube Data API v3" enabled on that project. Restrict the key to +# that API only. Server-only — no NEXT_PUBLIC_ prefix, so it is never bundled +# into the browser. +# +# Leave these blank and nothing breaks: the library falls back to whatever is +# hand-curated in lib/media.ts (or the "new videos are on the way" empty state +# if that's empty too). +YOUTUBE_API_KEY= + +# The channel's @handle (recommended, e.g. "codewithpurpose") or, failing +# that, its UC... channel ID via YOUTUBE_CHANNEL_ID instead. Only one is +# needed. +YOUTUBE_CHANNEL_HANDLE= +YOUTUBE_CHANNEL_ID= + +# Instagram has no equivalent here: its official Graph API needs a +# Business/Creator account linked to a Facebook Page and a long-lived access +# token that expires roughly every 60 days, so it isn't wired up as +# auto-pull yet. Add Instagram entries by hand in lib/media.ts for now. + diff --git a/src/app/media/page.tsx b/src/app/media/page.tsx new file mode 100644 index 0000000..5d164f9 --- /dev/null +++ b/src/app/media/page.tsx @@ -0,0 +1,28 @@ +import type { Metadata } from "next"; +import { MediaLibrary } from "@/components/media/MediaLibrary"; +import { PageHero, PageSection } from "@/components/PageHero"; +import { PageShell } from "@/components/PageShell"; +import { getMediaItems } from "@/lib/media"; + +export const metadata: Metadata = { + title: "Media", + description: + "Short lessons, student stories, and behind-the-scenes videos from CodeWithPurpose.", + alternates: { canonical: "/media/" }, +}; + +export default async function MediaPage() { + const items = await getMediaItems(); + + return ( + + + + + + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 92dc4aa..7a52c9b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,6 +8,7 @@ import { GlobalReachSection } from "@/components/GlobalReachSection"; import { SecuritySection } from "@/components/SecuritySection"; import { QuoteSection } from "@/components/QuoteSection"; import { HowItWorksSection } from "@/components/HowItWorksSection"; +import { MediaSection } from "@/components/media/MediaSection"; import { FaqSection } from "@/components/FaqSection"; import { FinalCtaSection } from "@/components/FinalCtaSection"; import { SiteFooter } from "@/components/SiteFooter"; @@ -40,6 +41,7 @@ export default function Home() { + diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 44e9b6a..7034243 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -22,6 +22,7 @@ export default function sitemap(): MetadataRoute.Sitemap { "/impact", "/contact", "/donate", + "/media", "/learn/ml", "/learn/vibecoding", "/learn/python", diff --git a/src/components/PageHero.tsx b/src/components/PageHero.tsx index ef65a23..0d20ca1 100644 --- a/src/components/PageHero.tsx +++ b/src/components/PageHero.tsx @@ -17,7 +17,11 @@ export function PageHero({ return (