Skip to content

Commit 19e3620

Browse files
authored
Merge pull request #98 from celestiaorg/feature/remove-newsletter
fix: removed newsletter and fixed next hydration errors
2 parents a55d65b + 5a7fd90 commit 19e3620

File tree

5 files changed

+97
-30
lines changed

5 files changed

+97
-30
lines changed

src/app/page.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Blog from "@/components/Resources/Blog/Blog";
77
import HomepageScrollText from "@/components/ScrollText/views/HomepageScrollText";
88
import { ANALYTICS_EVENTS } from "@/constants/analytics";
99
import { Link } from "@/micros/TertiaryPageMicors/TertiaryPageMicors";
10+
import React from "react";
1011

1112
export default async function Home() {
1213
const posts = await getPosts();
@@ -71,10 +72,10 @@ export default async function Home() {
7172
title: "Onchain Abundance",
7273
body: [
7374
"Build expressive applications previously unimaginable onchain.",
74-
<>
75+
<React.Fragment key='roadmap-text'>
7576
Celestia&apos;s <Link href={"https://blog.celestia.org/roadmap/"}>roadmap</Link> has a core objective: relentlessly
7677
scale beyond 1 GB/s data throughput, removing crypto&apos;s ultimate scaling bottleneck.
77-
</>,
78+
</React.Fragment>,
7879
],
7980
buttons: [
8081
{
@@ -125,7 +126,7 @@ export const getPosts = async () => {
125126
const res = await fetch(
126127
"https://blog.celestia.org/ghost/api/v3/content/posts/?key=91c2a7dc379b796be090aeab63&limit=6&fields=title,text,feature_image,url,excerpt,published_at&formats=plaintext"
127128
);
128-
129+
129130
if (!res.ok) {
130131
console.error(`Ghost API responded with status: ${res.status}`);
131132
throw new Error(`Ghost API responded with status: ${res.status}`);
@@ -135,13 +136,13 @@ export const getPosts = async () => {
135136
const posts = responseJson.posts;
136137

137138
if (!posts) {
138-
console.error('No posts found in response:', responseJson);
139+
console.error("No posts found in response:", responseJson);
139140
throw new Error("Failed to fetch blog posts");
140141
}
141142

142143
return posts;
143144
} catch (error) {
144-
console.error('Error fetching blog posts:', error);
145+
console.error("Error fetching blog posts:", error);
145146
throw error;
146147
}
147148
};

src/components/AlternatingMediaRows/MediaRow.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import PrimaryButton from "@/macros/Buttons/PrimaryButton";
44
import SecondaryButton from "@/macros/Buttons/SecondaryButton";
55
import VideoPlayer from "@/components/VideoPlayer/VideoPlayer";
66
import { usePlausible } from "next-plausible";
7+
import React from "react";
78

89
const MediaRow = ({ title, body, buttons, videoSrc, className, index, totalRows }) => {
910
const videoRight = index % 2 === 0 ? true : false;
@@ -51,7 +52,7 @@ const MediaRow = ({ title, body, buttons, videoSrc, className, index, totalRows
5152
</div>
5253
{buttons.map((button, index) => {
5354
return (
54-
<>
55+
<React.Fragment key={`button-${index}`}>
5556
{button.type === "primary" ? (
5657
<PrimaryButton
5758
key={index}
@@ -75,7 +76,7 @@ const MediaRow = ({ title, body, buttons, videoSrc, className, index, totalRows
7576
{button.text}
7677
</SecondaryButton>
7778
)}
78-
</>
79+
</React.Fragment>
7980
);
8081
})}
8182
</div>

src/components/Ecosystem/EcosytemExplorer/EcosytemExplorer.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,28 @@ import Link from "next/link";
77
import "./EcosystemExplorer.scss";
88
import { stringToId } from "@/utils/stringToId";
99
import { usePlausible } from "next-plausible";
10+
import { useEffect, useState } from "react";
1011

1112
const EcosytemExplorer = ({ trackEvent: trackEventName }) => {
1213
const trackEvent = usePlausible();
13-
// randomly select 22 ecosystem items and mix them up
14-
const randomEcosystemItems = ecosystemItems.sort(() => Math.random() - 0.5);
14+
// Use state to store the randomly sorted ecosystem items
15+
const [randomItems, setRandomItems] = useState([]);
16+
const [isClient, setIsClient] = useState(false);
1517

16-
// split the 22 ecosystem items into 12 foregroundItems and 10 backgroundItems
17-
const foregroundItems = randomEcosystemItems.slice(0, 12);
18-
const backgroundItems = randomEcosystemItems.slice(12, 22);
18+
useEffect(() => {
19+
// Only run the random sorting on the client side after hydration
20+
setIsClient(true);
21+
// randomly select 22 ecosystem items and mix them up
22+
const shuffledItems = [...ecosystemItems].sort(() => Math.random() - 0.5);
23+
setRandomItems(shuffledItems);
24+
}, []);
25+
26+
// Use the first 22 items for server-side rendering, and randomItems after client-side hydration
27+
const itemsToRender = isClient ? randomItems : ecosystemItems.slice(0, 22);
28+
29+
// split the ecosystem items into 12 foregroundItems and 10 backgroundItems
30+
const foregroundItems = itemsToRender.slice(0, 12);
31+
const backgroundItems = itemsToRender.slice(12, 22);
1932

2033
const handleViewAllClick = () => {
2134
if (!trackEventName) return;

src/components/Footer/Footer.js

+29-10
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,38 @@ const Footer = () => {
3030
<Heading size='lg' className={`mb-12`}>
3131
Build whatever with Celestia underneath
3232
</Heading>
33-
<Newsletter />
33+
<div className='flex flex-row lg:justify-start gap-4 mb-8'>
34+
{columns[columns.length - 1].links.map((link, linkIndex) => {
35+
const isInternal = isInternalLink(link.url);
36+
return (
37+
<Link
38+
key={linkIndex}
39+
href={link.url}
40+
target={isInternal ? "_self" : "_blank"}
41+
rel={isInternal ? "" : "noopener noreferrer"}
42+
className={`flex items-center group`}
43+
>
44+
{link.icon && (
45+
<Icon
46+
Icon={<link.icon dark />}
47+
hover
48+
HoverIcon={<link.icon dark className='opacity-50' />}
49+
size='sm'
50+
border={false}
51+
transparentBg
52+
direction='up'
53+
/>
54+
)}
55+
</Link>
56+
);
57+
})}
58+
</div>
59+
{/* <Newsletter /> */}
3460
</div>
3561
<div className={`flex flex-wrap lg:flex-nowrap w-full lg:1/2 lg:gap-6 lg:justify-end ml-auto mr-0`}>
36-
{columns.map((column, index) => {
62+
{columns.slice(0, -1).map((column, index) => {
3763
return (
38-
<ul
39-
key={index}
40-
className={`block ${
41-
index === columns.length - 1
42-
? "w-full flex flex-row justify-center lg:justify-end gap-4 order-last lg:order-none lg:flex-col lg:w-auto"
43-
: "w-1/2 lg:w-1/5"
44-
} ${index === columns.length - 1 ? "" : "mb-10 lg:mb-0"}`}
45-
>
64+
<ul key={index} className={`block w-1/2 lg:w-1/5 mb-10 lg:mb-0`}>
4665
{column.links.map((link, linkIndex) => {
4766
const isInternal = isInternalLink(link.url);
4867
return (

src/utils/scrollLock.js

+41-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export const ScrollPositionProvider = ({ children }) => {
1313
};
1414
const [scrollIsLocked, setScrollIsLocked] = useState(false);
1515
const [menuIsOpen, setMenuIsOpen] = useState(false);
16+
// State for body styles to prevent hydration issues
17+
const [bodyStyles, setBodyStyles] = useState({});
1618

1719
// Get the height of each navigation section
1820
const primaryNavRef = useRef(null);
@@ -121,22 +123,53 @@ export const ScrollPositionProvider = ({ children }) => {
121123
}
122124
}, [pathname, navHeights]);
123125

124-
// Lock scroll when the menu is open
126+
// Lock scroll when the menu is open - only runs on client-side
125127
useEffect(() => {
128+
// Skip during server-side rendering to prevent hydration issues
129+
if (typeof window === "undefined") return;
130+
126131
if (scrollIsLocked) {
127-
// Save the current scroll position and apply styles to lock scroll
132+
// Save the current scroll position and set lock styles
128133
const currentY = window.scrollY;
129134
setScrollY(currentY);
130-
document.body.style.cssText += `position: fixed; top: -${currentY}px; width: 100%; overflow: hidden;`;
135+
// Update body styles through state
136+
setBodyStyles({
137+
position: "fixed",
138+
top: `-${currentY}px`,
139+
width: "100%",
140+
overflow: "hidden",
141+
overscrollBehaviorX: "none",
142+
});
131143
} else {
132-
// Reset styles and scroll to the saved position
133-
const bodyStyle = document.body.style;
134-
bodyStyle.position = "";
135-
bodyStyle.top = "";
136-
bodyStyle.overflow = "";
144+
// Reset styles when unlocked
145+
setBodyStyles({});
146+
// Restore scroll position if needed
147+
if (scrollY.current !== 0) {
148+
window.scrollTo(0, scrollY.current);
149+
}
137150
}
138151
}, [scrollIsLocked]);
139152

153+
// Apply body styles using an effect that only runs on the client
154+
useEffect(() => {
155+
// Skip during server-side rendering
156+
if (typeof window === "undefined") return;
157+
158+
// Apply styles directly to body element
159+
Object.entries(bodyStyles).forEach(([prop, value]) => {
160+
document.body.style[prop] = value;
161+
});
162+
163+
// Clean up function to reset styles when component unmounts
164+
return () => {
165+
document.body.style.position = "";
166+
document.body.style.top = "";
167+
document.body.style.width = "";
168+
document.body.style.overflow = "";
169+
document.body.style.overscrollBehaviorX = "";
170+
};
171+
}, [bodyStyles]);
172+
140173
useEffect(() => {
141174
setMenuIsOpen(false);
142175
}, [pathname]);

0 commit comments

Comments
 (0)