From 136e42dad61e2473f4baca1d0711714254cd2047 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sat, 4 Apr 2026 06:31:50 +0100 Subject: [PATCH 1/5] [#824] Fix Writer tab badge: use time-based deadline check instead of sunset Badge showed "active" on expired storylines because it only checked the sunset flag. Now uses DEADLINE_MS (168h) time-based check matching DeadlineCountdown logic: expired when has_deadline && last_plot_time + 168h < now, complete when sunset, otherwise active. Fixes #824 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index d1acbe49..4a30069f 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -19,7 +19,7 @@ import { usePlotUsdPrice } from "../../../hooks/usePlotUsdPrice"; import { formatUsdValue } from "../../../../lib/usd-price"; import { DisconnectButton } from "../../../components/ConnectWallet"; import { GENRES, LANGUAGES } from "../../../../lib/genres"; -import { DeadlineCountdown } from "../../../components/DeadlineCountdown"; +import { DeadlineCountdown, DEADLINE_MS } from "../../../components/DeadlineCountdown"; import { ClaimRoyalties } from "../../../components/ClaimRoyalties"; import { WriterTradingStats } from "../../../components/WriterTradingStats"; import { DropdownSelect } from "../../../components/DropdownSelect"; @@ -952,6 +952,8 @@ function StoryRow({ )} {storyline.sunset ? ( complete + ) : storyline.has_deadline && storyline.last_plot_time && Date.now() > new Date(storyline.last_plot_time).getTime() + DEADLINE_MS ? ( + expired ) : ( active )} From 6df20e7cec72c925d778c2ff52f60d248defbed6 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sat, 4 Apr 2026 06:37:33 +0100 Subject: [PATCH 2/5] [#824] Move Date.now() out of JSX render to fix react-hooks/purity lint Compute isExpired flag before return to avoid calling Date.now() during render. Same logic, just hoisted to a variable. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index 4a30069f..63319400 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -868,6 +868,9 @@ function StoryRow({ enabled: !!storyline.token_address, }); + const isExpired = !storyline.sunset && storyline.has_deadline && !!storyline.last_plot_time && + Date.now() > new Date(storyline.last_plot_time).getTime() + DEADLINE_MS; + return ( <>
@@ -952,7 +955,7 @@ function StoryRow({ )} {storyline.sunset ? ( complete - ) : storyline.has_deadline && storyline.last_plot_time && Date.now() > new Date(storyline.last_plot_time).getTime() + DEADLINE_MS ? ( + ) : isExpired ? ( expired ) : ( active From 1aebb665177404a1e63d8d497f437f9693655ea0 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sat, 4 Apr 2026 06:40:50 +0100 Subject: [PATCH 3/5] [#824] Use useState for timestamp to fix react-hooks/purity Date.now() in component body is still render-time impurity. Use useState(() => Date.now()) to capture timestamp once on mount, making the component pure for React Compiler. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index 63319400..81a3eb45 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -868,8 +868,9 @@ function StoryRow({ enabled: !!storyline.token_address, }); + const [now] = useState(() => Date.now()); const isExpired = !storyline.sunset && storyline.has_deadline && !!storyline.last_plot_time && - Date.now() > new Date(storyline.last_plot_time).getTime() + DEADLINE_MS; + now > new Date(storyline.last_plot_time).getTime() + DEADLINE_MS; return ( <> From 02a8f93915fb6e9dea2064005164bdfc8f9937fc Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sat, 4 Apr 2026 06:43:41 +0100 Subject: [PATCH 4/5] [#824] Use useEffect+setInterval for live-updating expired badge Replace frozen useState with useCallback+useEffect+setInterval pattern (same as DeadlineCountdown) so badge updates when deadline passes. Ticks every 60s. Lint-disable matches existing codebase pattern. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index 81a3eb45..ef3e9b7f 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -868,9 +868,18 @@ function StoryRow({ enabled: !!storyline.token_address, }); - const [now] = useState(() => Date.now()); - const isExpired = !storyline.sunset && storyline.has_deadline && !!storyline.last_plot_time && - now > new Date(storyline.last_plot_time).getTime() + DEADLINE_MS; + const checkExpired = useCallback( + () => !storyline.sunset && storyline.has_deadline && !!storyline.last_plot_time && + Date.now() > new Date(storyline.last_plot_time).getTime() + DEADLINE_MS, + [storyline.sunset, storyline.has_deadline, storyline.last_plot_time], + ); + const [isExpired, setIsExpired] = useState(checkExpired); + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect -- initial sync needed for SSR hydration safety + setIsExpired(checkExpired()); + const interval = setInterval(() => setIsExpired(checkExpired()), 60_000); + return () => clearInterval(interval); + }, [checkExpired]); return ( <> From 5d06c596586e423c0ff4b440039bc616609dc84c Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sat, 4 Apr 2026 06:45:56 +0100 Subject: [PATCH 5/5] [#824] Match DeadlineCountdown 1s tick interval for badge sync 60s interval left a contradiction window where DeadlineCountdown showed expired but badge still showed active. Now both tick every 1s. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index ef3e9b7f..ae6294ba 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -877,7 +877,7 @@ function StoryRow({ useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect -- initial sync needed for SSR hydration safety setIsExpired(checkExpired()); - const interval = setInterval(() => setIsExpired(checkExpired()), 60_000); + const interval = setInterval(() => setIsExpired(checkExpired()), 1_000); return () => clearInterval(interval); }, [checkExpired]);