diff --git a/src/app/credentials/page.tsx b/src/app/credentials/page.tsx
index 35103d3..c69908a 100644
--- a/src/app/credentials/page.tsx
+++ b/src/app/credentials/page.tsx
@@ -79,7 +79,13 @@ export default function CredentialsPage() {
return sorted;
}, [credentials, courseId, debouncedSearch, sort]);
- if (!ready) return null;
+ if (!ready) {
+ return (
+
+
+
+ );
+ }
if (loading) {
return (
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 1654d59..47a7364 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -75,7 +75,13 @@ export default function DashboardPage() {
const { credentials, loading: credentialsLoading, error: credentialsError } = useCredentials();
const progress = useCourseStore((s) => s.progress);
- if (!ready) return null;
+ if (!ready) {
+ return (
+
+
+
+ );
+ }
const isLoading =
coursesLoading || rewardsLoading || credentialsLoading || recommendedLoading;
diff --git a/src/app/rewards/page.tsx b/src/app/rewards/page.tsx
index 4f1d780..96bf3a4 100644
--- a/src/app/rewards/page.tsx
+++ b/src/app/rewards/page.tsx
@@ -35,7 +35,13 @@ export default function RewardsPage() {
}
};
- if (!ready) return null;
+ if (!ready) {
+ return (
+
+
+
+ );
+ }
if (loading) {
return (
diff --git a/src/lib/hooks/use-intersection-observer.ts b/src/lib/hooks/use-intersection-observer.ts
index 5b4995e..0c3fd5d 100644
--- a/src/lib/hooks/use-intersection-observer.ts
+++ b/src/lib/hooks/use-intersection-observer.ts
@@ -1,6 +1,6 @@
"use client";
-import { useEffect, useState, RefObject } from "react";
+import { useEffect, useMemo, useState, RefObject } from "react";
export interface UseIntersectionObserverOptions {
threshold?: number | number[];
@@ -35,6 +35,10 @@ export function useIntersectionObserver(
const isIntersecting = !!entry?.isIntersecting;
const frozen = isIntersecting && freezeOnceVisible;
+ // Keyed on the serialized value so callers passing a new array literal each render don't rebuild the observer.
+ const thresholdKey = JSON.stringify(threshold);
+ const stableThreshold = useMemo(() => JSON.parse(thresholdKey), [thresholdKey]);
+
useEffect(() => {
const node = elementRef?.current;
if (!node || frozen || typeof window === "undefined" || !("IntersectionObserver" in window)) {
@@ -45,7 +49,7 @@ export function useIntersectionObserver(
([newEntry]) => {
setEntry(newEntry);
},
- { threshold, root, rootMargin }
+ { threshold: stableThreshold, root, rootMargin }
);
observer.observe(node);
@@ -53,7 +57,7 @@ export function useIntersectionObserver(
return () => {
observer.disconnect();
};
- }, [elementRef, JSON.stringify(threshold), root, rootMargin, frozen]);
+ }, [elementRef, stableThreshold, root, rootMargin, frozen]);
return {
isIntersecting,
diff --git a/src/middleware.ts b/src/middleware.ts
index 355483c..d8794f8 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -10,6 +10,7 @@ const PROTECTED_PREFIXES = [
"/dashboard",
"/rewards",
"/credentials",
+ "/settings",
"/courses/", // individual course pages, modules, quiz — NOT /courses list
];
@@ -89,6 +90,7 @@ export const config = {
"/dashboard/:path*",
"/rewards/:path*",
"/credentials/:path*",
+ "/settings/:path*",
"/courses/:courseId/:path*", // individual course + sub-pages only
"/connect",
"/onboarding",