diff --git a/app/(pages)/(hackers)/_components/ProjectInfo/WhatHappens/WhatHappens.module.scss b/app/(pages)/(hackers)/_components/ProjectInfo/WhatHappens/WhatHappens.module.scss
index 79c3afa95..1afcab511 100644
--- a/app/(pages)/(hackers)/_components/ProjectInfo/WhatHappens/WhatHappens.module.scss
+++ b/app/(pages)/(hackers)/_components/ProjectInfo/WhatHappens/WhatHappens.module.scss
@@ -31,8 +31,8 @@
}
}
- > h2 {
- font-weight: bold;
+ h2 {
+ font-weight: 700;
padding-bottom: 32px;
color: var(--text-dark);
@@ -54,7 +54,7 @@
background: none;
color: var(--background-secondary);
border-radius: 8px;
- font-weight: 500;
+ font-weight: 600;
margin-right: 1%;
border: 1.5px solid var(--background-secondary);
diff --git a/app/(pages)/_components/TimeProtectedDisplay/ClientTimeProtectedDisplay.tsx b/app/(pages)/_components/TimeProtectedDisplay/ClientTimeProtectedDisplay.tsx
index 72f62ed29..cdbca2117 100644
--- a/app/(pages)/_components/TimeProtectedDisplay/ClientTimeProtectedDisplay.tsx
+++ b/app/(pages)/_components/TimeProtectedDisplay/ClientTimeProtectedDisplay.tsx
@@ -14,26 +14,31 @@ export default function ClientTimeProtectedDisplay({
callback?: () => void;
children: React.ReactNode;
}) {
- const { ok, loading, available, rollout, error, fetchAvailability } =
+ const { loading, available, rollout, error, fetchAvailability } =
useFeatureAvailability(featureId);
- if (loading) {
+
+ // initial loading state
+ if (loading && !rollout) {
return 'loading...';
}
- if (!ok) {
- return JSON.stringify(error);
+ // error or no rollout info, then don't render 24 hr timer
+ if (error || !rollout) {
+ return <>{fallback}>;
}
+ const handleTrigger = async () => {
+ await fetchAvailability(featureId, true);
+ callback?.();
+ };
+
if (!available) {
return (
<>
{fallback}
{
- callback?.();
- fetchAvailability(featureId);
- }}
+ callback={handleTrigger}
/>
>
);
@@ -45,10 +50,7 @@ export default function ClientTimeProtectedDisplay({
{rollout.rollback_time && (
{
- callback?.();
- fetchAvailability(featureId);
- }}
+ callback={handleTrigger}
/>
)}
>
diff --git a/app/(pages)/_globals/metadata.json b/app/(pages)/_globals/metadata.json
index 99fd940dc..0a8ea5ff7 100644
--- a/app/(pages)/_globals/metadata.json
+++ b/app/(pages)/_globals/metadata.json
@@ -1,7 +1,7 @@
{
"title": "HackDavis Hub",
- "description": "For all hackers and judges at HackDavis 2025",
+ "description": "For all hackers and judges at HackDavis 2026",
"icons": {
- "icon": "/icons/icon.ico"
+ "icon": "/icons/icon-hd26.ico"
}
}
\ No newline at end of file
diff --git a/app/(pages)/_hooks/useFeatureAvailability.ts b/app/(pages)/_hooks/useFeatureAvailability.ts
index 7ec524ad5..7495694ee 100644
--- a/app/(pages)/_hooks/useFeatureAvailability.ts
+++ b/app/(pages)/_hooks/useFeatureAvailability.ts
@@ -1,26 +1,42 @@
import checkFeatureAvailability from '@actions/rollouts/checkFeatureAvailability';
-import { useEffect, useState } from 'react';
+import { useEffect, useState, useCallback, useRef } from 'react';
export function useFeatureAvailability(featureId: string) {
const [loading, setLoading] = useState(true);
- const [ok, setOk] = useState(true);
const [error, setError] = useState(null);
- const [available, setAvailable] = useState(null);
+ const [available, setAvailable] = useState(false);
const [rollout, setRollout] = useState(null);
- const fetchAvailability = async (featureId: string) => {
- setLoading(true);
- const { ok, body, error } = await checkFeatureAvailability(featureId);
- setOk(ok);
- setAvailable(body?.available);
- setRollout(body?.rollout);
- setError(error);
- setLoading(false);
- };
+ const lastFetchedId = useRef(null);
+
+ const fetchAvailability = useCallback(
+ async (id: string, force = false) => {
+ if (!force && lastFetchedId.current === id && rollout !== null) return;
+
+ setLoading(true);
+ setError(null);
+ try {
+ const { ok, body, error } = await checkFeatureAvailability(id);
+ if (ok && body) {
+ setAvailable(body.available);
+ setRollout(body.rollout);
+ lastFetchedId.current = id;
+ } else {
+ setError(error);
+ }
+ } catch (err: any) {
+ console.error('Failed to fetch feature availability:', err);
+ setError(err instanceof Error ? err.message : String(err));
+ } finally {
+ setLoading(false);
+ }
+ },
+ [rollout]
+ );
useEffect(() => {
fetchAvailability(featureId);
- }, [featureId]);
+ }, [featureId, fetchAvailability]);
- return { ok, loading, available, rollout, error, fetchAvailability };
+ return { loading, available, rollout, error, fetchAvailability };
}
diff --git a/app/(pages)/_hooks/useTimeTrigger.ts b/app/(pages)/_hooks/useTimeTrigger.ts
index fc91db3e4..8aaeca611 100644
--- a/app/(pages)/_hooks/useTimeTrigger.ts
+++ b/app/(pages)/_hooks/useTimeTrigger.ts
@@ -4,9 +4,14 @@ import { useRouter } from 'next/navigation';
export function useTimeTrigger(triggerTime: number, callback: any) {
const timerRef = useRef(null);
+ const callbackRef = useRef(callback);
const [triggered, setTriggered] = useState(false);
const router = useRouter();
+ useEffect(() => {
+ callbackRef.current = callback;
+ }, [callback]);
+
useEffect(() => {
const updateTimer = () => {
if (timerRef.current) {
@@ -18,7 +23,7 @@ export function useTimeTrigger(triggerTime: number, callback: any) {
return;
}
timerRef.current = setTimeout(async () => {
- await callback?.();
+ await callbackRef.current?.();
router.refresh();
setTriggered(true);
}, timeToTrigger);
@@ -32,10 +37,10 @@ export function useTimeTrigger(triggerTime: number, callback: any) {
updateTimer();
return () => {
- clearTimeout(timerRef.current);
+ if (timerRef.current) clearTimeout(timerRef.current);
window.removeEventListener('focus', onFocus);
};
- }, [triggerTime, callback, router]);
+ }, [triggerTime, router]);
return { triggered };
}
diff --git a/package-lock.json b/package-lock.json
index ce510424c..89b602b6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6549,9 +6549,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001734",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001734.tgz",
- "integrity": "sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==",
+ "version": "1.0.30001769",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
+ "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
"funding": [
{
"type": "opencollective",
diff --git a/public/icons/icon-hd26.ico b/public/icons/icon-hd26.ico
new file mode 100644
index 000000000..9cb268901
Binary files /dev/null and b/public/icons/icon-hd26.ico differ
diff --git a/public/icons/icon.ico b/public/icons/icon.ico
deleted file mode 100644
index 8a397ab42..000000000
Binary files a/public/icons/icon.ico and /dev/null differ