Skip to content

Commit 6910260

Browse files
authored
Merge pull request #99 from ConcealNetwork/modern
improvment as per sonarcloud suggestions
2 parents 73dce03 + aec1742 commit 6910260

20 files changed

+2264
-2759
lines changed

biome.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"linter": {
1616
"enabled": true,
1717
"rules": {
18-
"recommended": true
18+
"recommended": true,
19+
"complexity": "warn"
1920
}
2021
},
2122
"javascript": {

src/App.tsx

Lines changed: 34 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -19,102 +19,61 @@ interface AppProps {
1919
onReady?: () => void;
2020
}
2121

22-
function App({ onReady }: AppProps) {
23-
const [isScrolledPastHero, setIsScrolledPastHero] = useState(false);
24-
const heroSectionRef = useRef<HTMLElement | null>(null);
25-
const location = useLocation();
22+
const doubleRAF = (cb: () => void) => requestAnimationFrame(() => requestAnimationFrame(cb));
2623

27-
useEffect(() => {
28-
const handleScroll = () => {
29-
if (heroSectionRef.current) {
30-
const heroBottom = heroSectionRef.current.offsetTop + heroSectionRef.current.offsetHeight;
31-
const scrollY = window.scrollY;
32-
setIsScrolledPastHero(scrollY > heroBottom);
33-
}
34-
};
35-
36-
window.addEventListener('scroll', handleScroll);
37-
handleScroll(); // Check initial state
24+
function scrollToWithOffset(element: Element) {
25+
const top = element.getBoundingClientRect().top + globalThis.pageYOffset - 100;
26+
globalThis.scrollTo({ top, behavior: 'smooth' });
27+
}
3828

39-
return () => {
40-
window.removeEventListener('scroll', handleScroll);
29+
function useHeroScroll(heroRef: React.RefObject<HTMLElement | null>) {
30+
const [isPast, setIsPast] = useState(false);
31+
useEffect(() => {
32+
const check = () => {
33+
if (!heroRef.current) return;
34+
setIsPast(globalThis.scrollY > heroRef.current.offsetTop + heroRef.current.offsetHeight);
4135
};
42-
}, []);
36+
globalThis.addEventListener('scroll', check);
37+
check();
38+
return () => globalThis.removeEventListener('scroll', check);
39+
}, [heroRef]);
40+
return isPast;
41+
}
4342

44-
// Handle hash scrolling from navigation state and URL hash
43+
function useHashScroll(location: ReturnType<typeof useLocation>) {
4544
useEffect(() => {
46-
// Only handle on main page
4745
if (location.pathname !== '/') return;
48-
49-
// Check both navigation state and URL hash
5046
const state = location.state as { scrollToHash?: string } | null;
51-
const hashFromState = state?.scrollToHash;
52-
const hashFromUrl = location.hash;
53-
54-
// Also check window.location.hash as fallback (for direct navigation)
55-
const hashFromWindow = window.location.hash;
56-
57-
const hash = hashFromState || hashFromUrl || hashFromWindow;
47+
const hash = state?.scrollToHash || location.hash || globalThis.location.hash;
5848
if (!hash) return;
59-
60-
// Remove # if present and ensure it starts with #
6149
const cleanHash = hash.startsWith('#') ? hash : `#${hash}`;
62-
63-
// Helper function to scroll to element with offset
64-
const scrollToElement = (element: Element) => {
65-
const elementTop = element.getBoundingClientRect().top + window.pageYOffset;
66-
const offset = 100; // Offset for header
67-
window.scrollTo({
68-
top: elementTop - offset,
69-
behavior: 'smooth',
70-
});
71-
};
72-
73-
// Helper function to execute after double RAF (ensures DOM is painted)
74-
const executeAfterDoubleRAF = (callback: () => void) => {
75-
requestAnimationFrame(() => {
76-
requestAnimationFrame(callback);
77-
});
78-
};
79-
80-
// Robust scrolling: retry until element exists or timeout
81-
const maxAttempts = 50; // Increased attempts for slower renders
8250
let attempts = 0;
83-
8451
const tryScroll = () => {
8552
attempts++;
86-
const element = document.querySelector(cleanHash);
87-
88-
if (element) {
89-
// Element found, scroll to it with slight offset for header
90-
executeAfterDoubleRAF(() => scrollToElement(element));
53+
const el = document.querySelector(cleanHash);
54+
if (el) {
55+
doubleRAF(() => scrollToWithOffset(el));
9156
return;
9257
}
93-
94-
// Element not found yet, retry after a short delay
95-
if (attempts < maxAttempts) {
96-
setTimeout(tryScroll, appConfig.animations.scrollRetryDelayCrossPage);
97-
}
58+
if (attempts < 50) setTimeout(tryScroll, appConfig.animations.scrollRetryDelayCrossPage);
9859
};
99-
100-
// Start trying after a delay to allow DOM to render (longer delay for cross-page navigation)
10160
setTimeout(tryScroll, appConfig.animations.scrollInitialDelayCrossPage);
10261
}, [location.state, location.hash, location.pathname]);
62+
}
10363

104-
// Signal that App is ready (after initial render)
64+
function useAppReady(onReady?: () => void) {
10565
useEffect(() => {
10666
if (!onReady) return;
107-
108-
// Helper function to execute after double RAF (ensures DOM is painted)
109-
const executeAfterDoubleRAF = (callback: () => void) => {
110-
requestAnimationFrame(() => {
111-
requestAnimationFrame(callback);
112-
});
113-
};
114-
115-
// Use requestAnimationFrame to ensure DOM is painted
116-
executeAfterDoubleRAF(onReady);
67+
doubleRAF(onReady);
11768
}, [onReady]);
69+
}
70+
71+
function App({ onReady }: AppProps) {
72+
const heroSectionRef = useRef<HTMLElement | null>(null);
73+
const location = useLocation();
74+
const isScrolledPastHero = useHeroScroll(heroSectionRef);
75+
useHashScroll(location);
76+
useAppReady(onReady);
11877

11978
return (
12079
<div

src/components/Footer.tsx

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -164,86 +164,73 @@ function FooterColumn({ data }: { data: ColumnData }) {
164164
);
165165
}
166166

167-
export function Footer() {
168-
const currentYear = new Date().getFullYear();
167+
function isPageAtBottom(margin = 10) {
168+
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
169+
return (window.pageYOffset || scrollTop) + clientHeight >= scrollHeight - margin;
170+
}
171+
172+
function useFooterState() {
169173
const [isExpanded, setIsExpanded] = useState(false);
170174
const [isPulling, setIsPulling] = useState(false);
171175
const [pullDistance, setPullDistance] = useState(0);
172176
const footerRef = useRef<HTMLElement>(null);
173-
const startYRef = useRef<number>(0);
174-
const lastScrollTopRef = useRef<number>(0);
177+
const startYRef = useRef(0);
178+
const lastScrollTopRef = useRef(0);
175179

176180
useEffect(() => {
177181
const handleScroll = () => {
178182
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
179-
const scrollHeight = document.documentElement.scrollHeight;
180-
const clientHeight = document.documentElement.clientHeight;
181-
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10;
182-
183-
// Auto-expand when scrolled to bottom
184-
if (isAtBottom && !isExpanded) {
185-
setIsExpanded(true);
186-
}
187-
// Auto-collapse when scrolling up (if not at bottom)
188-
else if (!isAtBottom && scrollTop < lastScrollTopRef.current && isExpanded) {
183+
if (isPageAtBottom() && !isExpanded) setIsExpanded(true);
184+
else if (!isPageAtBottom() && scrollTop < lastScrollTopRef.current && isExpanded)
189185
setIsExpanded(false);
190-
}
191-
192186
lastScrollTopRef.current = scrollTop;
193187
};
194-
195188
window.addEventListener('scroll', handleScroll);
196189
return () => window.removeEventListener('scroll', handleScroll);
197190
}, [isExpanded]);
198191

199192
useEffect(() => {
200193
const footer = footerRef.current;
201194
if (!footer) return;
202-
203-
const handleTouchStart = (e: TouchEvent) => {
204-
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
205-
const scrollHeight = document.documentElement.scrollHeight;
206-
const clientHeight = document.documentElement.clientHeight;
207-
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
208-
209-
if (isAtBottom) {
195+
const onTouchStart = (e: TouchEvent) => {
196+
if (isPageAtBottom(50)) {
210197
startYRef.current = e.touches[0].clientY;
211198
setIsPulling(true);
212199
}
213200
};
214-
215-
const handleTouchMove = (e: TouchEvent) => {
201+
const onTouchMove = (e: TouchEvent) => {
216202
if (!isPulling) return;
217-
218-
const currentY = e.touches[0].clientY;
219-
const distance = Math.max(0, currentY - startYRef.current);
220-
setPullDistance(Math.min(distance, 150)); // Max pull distance
221-
203+
const distance = Math.max(0, e.touches[0].clientY - startYRef.current);
204+
setPullDistance(Math.min(distance, 150));
222205
if (distance > 50 && !isExpanded) {
223206
setIsExpanded(true);
224207
setIsPulling(false);
225208
setPullDistance(0);
226209
}
227210
};
228-
229-
const handleTouchEnd = () => {
211+
const onTouchEnd = () => {
230212
if (isPulling) {
231213
setIsPulling(false);
232214
setPullDistance(0);
233215
}
234216
};
235-
236-
footer.addEventListener('touchstart', handleTouchStart);
237-
footer.addEventListener('touchmove', handleTouchMove);
238-
footer.addEventListener('touchend', handleTouchEnd);
239-
217+
footer.addEventListener('touchstart', onTouchStart);
218+
footer.addEventListener('touchmove', onTouchMove);
219+
footer.addEventListener('touchend', onTouchEnd);
240220
return () => {
241-
footer.removeEventListener('touchstart', handleTouchStart);
242-
footer.removeEventListener('touchmove', handleTouchMove);
243-
footer.removeEventListener('touchend', handleTouchEnd);
221+
footer.removeEventListener('touchstart', onTouchStart);
222+
footer.removeEventListener('touchmove', onTouchMove);
223+
footer.removeEventListener('touchend', onTouchEnd);
244224
};
245225
}, [isPulling, isExpanded]);
246226

227+
return { isExpanded, isPulling, pullDistance, footerRef };
228+
}
229+
230+
export function Footer() {
231+
const currentYear = new Date().getFullYear();
232+
const { isExpanded, isPulling, pullDistance, footerRef } = useFooterState();
233+
247234
return (
248235
<footer
249236
ref={footerRef}

0 commit comments

Comments
 (0)