diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4fdd47d --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Cosmic CMS – get these from your Cosmic bucket dashboard +# https://app.cosmicjs.com → your bucket → Settings → API Keys +COSMIC_BUCKET_SLUG=your-bucket-slug +COSMIC_READ_KEY=your-read-key +COSMIC_WRITE_KEY=your-write-key + +# Behold.so Instagram feed – public URL, no secret. Drives the homepage News & Announcements section. +# Find the feed URL in your Behold dashboard: https://app.behold.so/ +NEXT_PUBLIC_BEHOLD_FEED_URL=https://feeds.behold.so/your-feed-id diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml new file mode 100644 index 0000000..ec528a8 --- /dev/null +++ b/.github/workflows/preview.yaml @@ -0,0 +1,25 @@ +name: Vercel Preview Deployment +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + COSMIC_BUCKET_SLUG: ${{ secrets.COSMIC_BUCKET_SLUG }} + COSMIC_READ_KEY: ${{ secrets.COSMIC_READ_KEY }} + COSMIC_WRITE_KEY: ${{ secrets.COSMIC_WRITE_KEY }} + NEXT_PUBLIC_BEHOLD_FEED_URL: ${{ secrets.NEXT_PUBLIC_BEHOLD_FEED_URL }} +on: + push: + branches-ignore: + - main +jobs: + Deploy-Preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Vercel CLI + run: npm install --global vercel@latest + - name: Pull Vercel Environment Information + run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} + - name: Build Project Artifacts + run: vercel build --token=${{ secrets.VERCEL_TOKEN }} + - name: Deploy Project Artifacts to Vercel + run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml new file mode 100644 index 0000000..b207f8c --- /dev/null +++ b/.github/workflows/production.yaml @@ -0,0 +1,25 @@ +name: Vercel Production Deployment +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + COSMIC_BUCKET_SLUG: ${{ secrets.COSMIC_BUCKET_SLUG }} + COSMIC_READ_KEY: ${{ secrets.COSMIC_READ_KEY }} + COSMIC_WRITE_KEY: ${{ secrets.COSMIC_WRITE_KEY }} + NEXT_PUBLIC_BEHOLD_FEED_URL: ${{ secrets.NEXT_PUBLIC_BEHOLD_FEED_URL }} +on: + push: + branches: + - main +jobs: + Deploy-Production: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Vercel CLI + run: npm install --global vercel@latest + - name: Pull Vercel Environment Information + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + - name: Build Project Artifacts + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + - name: Deploy Project Artifacts to Vercel + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.gitignore b/.gitignore index e25f3d5..d084156 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel diff --git a/app/components/InstagramPostCard.tsx b/app/components/InstagramPostCard.tsx deleted file mode 100644 index ed49849..0000000 --- a/app/components/InstagramPostCard.tsx +++ /dev/null @@ -1,132 +0,0 @@ -type Props = { - imageUrl?: string; - username?: string; - caption?: string; - likes?: number; - postUrl?: string; -}; - -export default function InstagramPostCard({ - imageUrl, - username = "mindmoralitylab", - caption = "", - likes = 0, - postUrl = "#", -}: Props) { - return ( -
- {/* Header */} -
-
- M -
- {username} - - - - - - - -
- - {/* Image */} -
- {imageUrl ? ( - Instagram post - ) : ( -
- - - - - -
- )} -
- - {/* Actions */} -
- - - - - - - - - - -
- - {/* Likes */} - {likes > 0 && ( -

- {likes.toLocaleString()} likes -

- )} - - {/* Caption */} - {caption && ( -

- {username} - {caption} -

- )} - - {/* View on Instagram */} - - View on Instagram - -
- ); -} diff --git a/app/page.tsx b/app/page.tsx index 1e442b5..6a73908 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -17,17 +17,29 @@ const page_to_link = new Map([ ["students", "/get-involved/students"], ]); -export default function Home() { - const slides = ["/slideshow1.png", "/slideshow1.png", "/slideshow1.png"]; - // Clone of first slide appended so we can slide into it, then snap back invisibly - const extendedSlides = [...slides, slides[0]]; +type BeholdPost = { + id: string; + timestamp: string; + permalink: string; + mediaType: "IMAGE" | "VIDEO" | "CAROUSEL_ALBUM"; + mediaUrl: string; + thumbnailUrl?: string | null; + caption?: string; +}; + +const formatPostDate = (iso: string) => + new Date(iso).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +export default function Home() { const [home, setHome] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); - const [current, setCurrent] = useState(0); - const [animated, setAnimated] = useState(true); + const [instagramPosts, setInstagramPosts] = useState(null); useEffect(() => { const fetchData = async () => { @@ -48,33 +60,14 @@ export default function Home() { }, []); useEffect(() => { - const interval = setInterval(() => { - setCurrent((prev) => prev + 1); - }, 4000); - return () => clearInterval(interval); + const feedUrl = process.env.NEXT_PUBLIC_BEHOLD_FEED_URL; + if (!feedUrl) return; + fetch(feedUrl) + .then((r) => (r.ok ? r.json() : null)) + .then((data) => setInstagramPosts(data?.posts ?? null)) + .catch(() => setInstagramPosts(null)); }, []); - useEffect(() => { - if (current === extendedSlides.length - 1) { - // After sliding into the clone, snap back to real first slide without animation - const timer = setTimeout(() => { - setAnimated(false); - setCurrent(0); - }, 700); - return () => clearTimeout(timer); - } - }, [current]); - - useEffect(() => { - if (!animated) { - // Re-enable animation after the snap - const timer = setTimeout(() => setAnimated(true), 50); - return () => clearTimeout(timer); - } - }, [animated]); - - const activeIndex = current === extendedSlides.length - 1 ? 0 : current; - if (loading) { return (
@@ -102,42 +95,21 @@ export default function Home() { `}
-
-
- {extendedSlides.map((slide, i) => ( -
- Lab photo -
- ))} -
- -
- {slides.map((_, i) => ( -
-
+
- + Mind & Morality Lab logo

- The Mind & Morality Lab is a developmental psychology lab at Brown - University in the Department of Cognitive & Psychological Sciences. + {home.metadata.lab_header}

- We focus on understanding the psychological roots of human morality - through an interdisciplinary lens, drawing on philosophical, legal, - and psychological perspectives in our work with both children and - adults. + {home.metadata.lab_description}

@@ -148,18 +120,27 @@ export default function Home() { News & Announcements
- - - + {instagramPosts && instagramPosts.length >= 3 ? ( + instagramPosts.slice(0, 3).map((post) => ( + + )) + ) : ( + <> + + + + + )}
diff --git a/components/InstagramPostCard.tsx b/components/InstagramPostCard.tsx index 67e4486..717603c 100644 --- a/components/InstagramPostCard.tsx +++ b/components/InstagramPostCard.tsx @@ -2,7 +2,7 @@ type Props = { imageUrl?: string; username?: string; caption?: string; - likes?: number; + date?: string; postUrl?: string; }; @@ -10,32 +10,11 @@ export default function InstagramPostCard({ imageUrl, username = "mindmoralitylab", caption = "", - likes = 0, + date, postUrl = "#", }: Props) { return (
- - {/* Header */} -
-
- M -
- {username} - - - - - - - -
- {/* Image */}
{imageUrl ? ( @@ -55,45 +34,36 @@ export default function InstagramPostCard({ )}
- {/* Actions */} -
- - - - - - - - - - + {/* User + date row */} +
+
+ M +
+
+ {username} + {date && {date}} +
+ + + + + + +
- {/* Likes */} - {likes > 0 && ( -

- {likes.toLocaleString()} likes -

- )} - {/* Caption */} {caption && ( -

- {username} +

{caption}

)} - - {/* View on Instagram */} - - View on Instagram - -
); } diff --git a/components/Slideshow.tsx b/components/Slideshow.tsx index 1e57fdf..b3ca862 100644 --- a/components/Slideshow.tsx +++ b/components/Slideshow.tsx @@ -58,7 +58,7 @@ export default function Slideshow({ images }: SlideshowProps) {