import { createSignal, onMount, onCleanup } from "solid-js"; import { getRefreshInterval } from "../utils/timeUtils"; import type { WidgetSchema, WidgetSettings } from "../types/widget"; interface StoicQuoteProps { settings: WidgetSettings; } interface Quote { text: string; author: string; } // Cache to prevent spam and persist across re-renders const quoteCooldowns = new Map(); const quoteCache = new Map(); export function StoicQuote(props: StoicQuoteProps) { const widgetKey = "stoic-quote"; const COOLDOWN_MS = 10000; // 10 second cooldown // Initialize quote from cache or default const initialQuote = quoteCache.get(widgetKey) || { text: "Loading wisdom...", author: "", }; const [quote, setQuote] = createSignal(initialQuote); const [error, setError] = createSignal(""); const [renderKey, setRenderKey] = createSignal(0); const fetchQuote = async (bypassCooldown: boolean = false) => { const now = Date.now(); const lastFetch = quoteCooldowns.get(widgetKey) || 0; // Cooldown check - only for manual refreshes, not interval refreshes if (!bypassCooldown && now - lastFetch < COOLDOWN_MS) { console.log(`[StoicQuote] Cooldown active, skipping manual refresh`); return; } quoteCooldowns.set(widgetKey, now); console.log("[StoicQuote] Fetching quote from API..."); try { const response = await fetch("https://stoic-quotes.com/api/quote"); if (!response.ok) { throw new Error("Failed to fetch quote"); } const data = await response.json(); const newQuote: Quote = { text: data.text, author: data.author, }; console.log("[StoicQuote] ✨ New quote received:", { author: newQuote.author, preview: newQuote.text.substring(0, 50) + "...", timestamp: new Date().toLocaleTimeString(), }); setQuote(newQuote); setError(""); quoteCache.set(widgetKey, newQuote); setRenderKey((prev) => prev + 1); // Force re-render console.log( "[StoicQuote] ✅ Quote state updated, render key incremented, forcing re-render", ); } catch (err) { console.error("[StoicQuote] Error:", err); setError("Failed to load quote"); } }; onMount(() => { // Load initial quote if not cached if (!quoteCache.has(widgetKey)) { fetchQuote(true); // Bypass cooldown on mount } // Refresh based on interval setting const refreshInterval = getRefreshInterval(props.settings, 1, "hours"); console.log(`[StoicQuote] Setting up interval: ${refreshInterval}ms`); const intervalId = setInterval(() => { console.log("[StoicQuote] Interval tick - fetching new quote"); fetchQuote(true); // Bypass cooldown for scheduled refreshes }, refreshInterval); onCleanup(() => { console.log("[StoicQuote] Cleaning up interval"); clearInterval(intervalId); }); }); // Make these reactive by moving them inside the render const showAuthor = () => props.settings.showAuthor !== false; const fontSize = () => (props.settings.fontSize as string) || "medium"; const getSizes = () => { const fontSizeMap = { small: { quote: "clamp(0.7rem, 1.5vw, 1.2rem)", author: "clamp(0.6rem, 1.2vw, 0.9rem)", }, medium: { quote: "clamp(0.9rem, 2vw, 1.5rem)", author: "clamp(0.7rem, 1.5vw, 1.1rem)", }, large: { quote: "clamp(1.1rem, 2.5vw, 2rem)", author: "clamp(0.8rem, 1.8vw, 1.3rem)", }, }; return ( fontSizeMap[fontSize() as keyof typeof fontSizeMap] || fontSizeMap.medium ); }; return (
{/* Stoic symbol - smaller and positioned absolutely */}
Σ
{/* Quote text - direct child of flex container like Clock */}
{error() ? (
{error()}
) : ( <>
"{quote().text}"
{showAuthor() && quote().author && (
— {quote().author}
)} )}
{/* Stoic badge */}
Stoic Wisdom
); } export const stoicQuoteSchema: WidgetSchema = { name: "Stoic Quote", description: "Display stoic wisdom from Marcus Aurelius, Seneca, and Epictetus", settingsSchema: { fontSize: { type: "select", label: "Font Size", options: ["small", "medium", "large"], default: "medium", }, showAuthor: { type: "boolean", label: "Show Author", default: true, }, refreshIntervalValue: { type: "number", label: "Refresh Interval", default: 1, required: true, }, refreshIntervalUnit: { type: "select", label: "Unit", options: ["seconds", "minutes", "hours", "days"], default: "hours", required: true, }, }, };