From d3eeaa7f40190bba190f3d566939e959162cfcf5 Mon Sep 17 00:00:00 2001
From: Drew Peifer
Date: Fri, 13 Feb 2026 23:49:18 -0500
Subject: [PATCH] styles restructuring
---
claudash/index.html | 3 +
claudash/src/App.css | 396 ++++++++++++++--
claudash/src/App.jsx | 18 +-
claudash/src/components/ChatInterface.jsx | 24 +-
claudash/src/components/Dashboard.jsx | 432 +++++++++---------
claudash/src/components/DebugPanel.jsx | 12 +-
.../src/components/widgets/BriefingWidget.jsx | 15 +-
.../src/components/widgets/GenericWidget.jsx | 8 +-
.../src/components/widgets/NewsWidget.jsx | 57 +--
.../src/components/widgets/WeatherWidget.jsx | 30 +-
claudash/src/index.css | 68 ---
claudash/src/theme.js | 3 -
12 files changed, 638 insertions(+), 428 deletions(-)
diff --git a/claudash/index.html b/claudash/index.html
index 0654a82..65e1862 100644
--- a/claudash/index.html
+++ b/claudash/index.html
@@ -4,6 +4,9 @@
+
+
+
claudash
diff --git a/claudash/src/App.css b/claudash/src/App.css
index b9d355d..af301c7 100644
--- a/claudash/src/App.css
+++ b/claudash/src/App.css
@@ -1,42 +1,376 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
+/* CSS Reset - replaces MUI CssBaseline */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
}
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
+* {
+ margin: 0;
+ padding: 0;
}
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+html {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
}
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
+body {
+ margin: 0;
+ min-height: 100vh;
+ line-height: 1.5;
+ font-synthesis: none;
+ font-weight: 400;
+ font-family: "Chivo", sans-serif !important;
}
-.card {
- padding: 2em;
+h1, h2, h3, h4, h5, h6 {
+ font-family: "Russo One", sans-serif !important;
+ font-size:1.2rem;
+}
+/* App theme containers */
+.app-dark {
+ min-height: 100vh;
+ background-color: #0a1929;
+ color: rgba(255, 255, 255, 0.87);
}
-.read-the-docs {
- color: #888;
+.app-light {
+ min-height: 100vh;
+ background-color: #ffffff;
+ color: rgba(0, 0, 0, 0.87);
+}
+.widget-grid {
+ display:flex;
+ flex-direction: column;
+ gap: 20px;
+ justify-content: space-evenly;
+}
+.widget-row {
+ display:flex;
+ justify-content: flex-start;
+ flex-direction: row;
+ gap: 10px;
+}
+.widget-row.condensed {
+ flex-wrap: wrap;
+ justify-content: space-between;
+}
+.widget-row.condensed .news-widget{
+ flex-grow: 2;
+}
+
+a {
+ font-weight: 500;
+ color: #90caf9;
+ text-decoration: inherit;
+}
+
+a:hover {
+ color: #64b5f6;
+}
+
+.app-light a {
+ color: #1976d2;
+}
+
+.app-light a:hover {
+ color: #1565c0;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+
+button:hover {
+ border-color: #646cff;
+}
+
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+.app-light button {
+ background-color: #f5f5f5;
+ color: rgba(0, 0, 0, 0.87);
+}
+
+/* Input/TextField resets - form elements need explicit inheritance */
+input, textarea {
+ font-family: inherit;
+}
+
+/* Chat Interface Styles */
+.chat-header {
+ margin-left: 8px;
+ font-size: 0.875rem;
+}
+
+.chat-placeholder {
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 0.875rem;
+ margin: 0;
+}
+
+.app-light .chat-placeholder {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.chat-message-user {
+ display: inline-block;
+ padding: 12px;
+ border-radius: 8px;
+ max-width: 70%;
+ background-color: #1976d2;
+ color: white;
+ font-size: 0.875rem;
+}
+
+.chat-message-assistant {
+ display: inline-block;
+ padding: 12px;
+ border-radius: 8px;
+ max-width: 70%;
+ background-color: #424242;
+ color: white;
+ font-size: 0.875rem;
+}
+
+.app-light .chat-message-assistant {
+ background-color: #e0e0e0;
+ color: #000000;
+}
+
+/* Dashboard Styles */
+.loading-status {
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 1rem;
+ margin: 0;
+}
+
+.app-light .loading-status {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.token-usage-box {
+ margin-bottom: 16px;
+ padding: 16px;
+ background-color: #1e2a35;
+ border-radius: 4px;
+ border: 1px solid rgba(255, 255, 255, 0.12);
+}
+
+.app-light .token-usage-box {
+ background-color: #ffffff;
+ border: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+.token-usage-text {
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 0.875rem;
+ margin: 0;
+}
+
+.app-light .token-usage-text {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.token-usage-caption {
+ color: rgba(255, 255, 255, 0.38);
+ font-size: 0.75rem;
+}
+
+.app-light .token-usage-caption {
+ color: rgba(0, 0, 0, 0.38);
+}
+
+.cache-info-box {
+ margin-bottom: 16px;
+ padding: 16px;
+ background-color: #2e7d32;
+ color: white;
+ border-radius: 4px;
+ border: 1px solid #1b5e20;
+}
+
+.app-light .cache-info-box {
+ background-color: #4caf50;
+ color: white;
+ border: 1px solid #388e3c;
+}
+
+.cache-info-text {
+ font-size: 0.875rem;
+ margin: 0;
+}
+
+.cache-info-caption {
+ font-size: 0.75rem;
+ opacity: 0.9;
+}
+
+/* Widget Styles */
+.widget-title {
+ font-size: 2rem;
+ font-weight: 500;
+ margin: 0 0 8px 0;
+ line-height: 1.6;
+}
+
+.widget-error {
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 0.875rem;
+ margin: 0;
+}
+
+.app-light .widget-error {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.widget-info {
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 0.875rem;
+ margin: 0 0 8px 0;
+}
+
+.app-light .widget-info {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.widget-caption {
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 0.75rem;
+ margin: 0;
+}
+
+.app-light .widget-caption {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+/* Briefing Widget Styles */
+.briefing-text {
+ white-space: pre-line;
+ line-height: 1.7;
+ font-size: 1.05rem;
+ margin: 8px 0 0 0;
+}
+
+/* Weather Widget Styles */
+.weather-temp {
+ font-size: 3rem;
+ font-weight: 400;
+ line-height: 1;
+}
+
+.weather-unit {
+ font-size: 1.5rem;
+ color: rgba(255, 255, 255, 0.6);
+ margin-left: 4px;
+}
+
+.app-light .weather-unit {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.weather-description {
+ font-size: 1rem;
+ margin: 0 0 16px 0;
+}
+
+.forecast-label {
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: rgba(255, 255, 255, 0.6);
+ margin-bottom: 8px;
+}
+
+.app-light .forecast-label {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.forecast-day {
+ font-size: 0.75rem;
+ display: block;
+ margin-bottom: 4px;
+}
+
+.forecast-temp {
+ font-size: 0.875rem;
+}
+
+/* News Widget Styles */
+.news-title {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 0.875rem;
+}
+
+.news-author {
+ font-size: 0.75rem;
+ color: rgba(255, 255, 255, 0.6);
+ margin-left: 8px;
+ align-self: center;
+}
+
+.app-light .news-author {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.news-date {
+ font-size: 0.75rem;
+ color: rgba(255, 255, 255, 0.6);
+ margin-bottom: 4px;
+}
+
+.app-light .news-date {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.news-description {
+ font-size: 0.75rem;
+ color: rgba(255, 255, 255, 0.6);
+ display: -webkit-box;
+ margin-top: 4px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.app-light .news-description {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+/* Debug Panel Styles */
+.debug-title {
+ font-size: 1.25rem;
+ font-weight: 500;
+ margin: 0 0 16px 0;
+}
+
+.debug-warning {
+ color: #ed6c02;
+ font-size: 0.75rem;
+ margin-top: 8px;
+ display: block;
+}
+
+.app-light .debug-warning {
+ color: #e65100;
}
diff --git a/claudash/src/App.jsx b/claudash/src/App.jsx
index 69c78ec..4f70eb5 100644
--- a/claudash/src/App.jsx
+++ b/claudash/src/App.jsx
@@ -1,8 +1,9 @@
-import { ThemeProvider, CssBaseline } from '@mui/material';
+import { ThemeProvider } from '@mui/material';
import { useState } from 'react';
import Dashboard from './components/Dashboard';
import DebugPanel from './components/DebugPanel';
import { lightTheme, darkTheme } from './theme';
+import './App.css';
function App() {
const [isDark, setIsDark] = useState(true);
@@ -10,14 +11,15 @@ function App() {
return (
-
- setIsDark(!isDark)}
- isDark={isDark}
- />
- {debugMode && }
+
+ setIsDark(!isDark)}
+ isDark={isDark}
+ />
+ {debugMode && }
+
);
}
-export default App;
\ No newline at end of file
+export default App;
diff --git a/claudash/src/components/ChatInterface.jsx b/claudash/src/components/ChatInterface.jsx
index fb708bf..ce5dc6f 100644
--- a/claudash/src/components/ChatInterface.jsx
+++ b/claudash/src/components/ChatInterface.jsx
@@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react';
-import { Box, TextField, IconButton, Paper, Typography } from '@mui/material';
+import { Box, TextField, IconButton, Paper } from '@mui/material';
import { Send, ExpandLess, ExpandMore } from '@mui/icons-material';
import { sendChatMessage } from '../services/claudeService';
@@ -66,18 +66,16 @@ function ChatInterface() {
>
{isExpanded ? : }
-
- Chat with Claude
-
+ Chat with Claude
{/* Messages area - only visible when expanded */}
{isExpanded && (
{messages.length === 0 && (
-
+
Ask me anything about your briefing or the news...
-
+
)}
{messages.map((msg, idx) => (
-
+
{msg.content}
-
+
))}
diff --git a/claudash/src/components/Dashboard.jsx b/claudash/src/components/Dashboard.jsx
index 3e0ba02..aa00cf4 100644
--- a/claudash/src/components/Dashboard.jsx
+++ b/claudash/src/components/Dashboard.jsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
-import { Box, Container, CircularProgress, IconButton, Alert, Typography } from '@mui/material';
+import { Box, Container, CircularProgress, IconButton, Alert } from '@mui/material';
import { Brightness4, Brightness7, Refresh } from '@mui/icons-material';
import { fetchAllSources } from '../services/dataFetcher';
import { generateBriefing } from '../services/claudeService';
@@ -10,241 +10,233 @@ import NewsWidget from './widgets/NewsWidget';
import ChatInterface from './ChatInterface';
function Dashboard({ themeToggle, isDark }) {
- const [loading, setLoading] = useState(true);
- const [loadingStatus, setLoadingStatus] = useState('Initializing...');
- const [briefing, setBriefing] = useState('');
- const [sourceData, setSourceData] = useState({});
- const [error, setError] = useState(null);
- const [refreshing, setRefreshing] = useState(false);
- const [tokenUsage, setTokenUsage] = useState(null);
- const [cacheInfo, setCacheInfo] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [loadingStatus, setLoadingStatus] = useState('Initializing...');
+ const [briefing, setBriefing] = useState('');
+ const [sourceData, setSourceData] = useState({});
+ const [error, setError] = useState(null);
+ const [refreshing, setRefreshing] = useState(false);
+ const [tokenUsage, setTokenUsage] = useState(null);
+ const [cacheInfo, setCacheInfo] = useState(null);
- useEffect(() => {
- loadDashboard();
- }, []);
-
- async function loadDashboard(forceRefresh = false) {
- try {
- console.log('=== loadDashboard called ===');
- console.log('Force refresh:', forceRefresh);
- setError(null);
-
- // Check if we already have today's briefing (unless forcing refresh)
- if (!forceRefresh && hasTodaysBriefing()) {
- console.log('Using cached briefing from today');
- const todaysBriefing = getTodaysBriefing();
- setBriefing(todaysBriefing.briefing);
- setSourceData(todaysBriefing.sources || {});
-
- // Calculate cache age
- const cacheAge = Date.now() - todaysBriefing.timestamp;
- const cacheAgeMinutes = Math.floor(cacheAge / (1000 * 60));
-
- // Set cache info to display message
- setCacheInfo({
- age: cacheAgeMinutes,
- timestamp: todaysBriefing.timestamp
- });
- setTokenUsage(null); // Clear token usage when using cache
-
- setLoading(false);
- return;
- }
-
- console.log('Fetching fresh data...');
- setLoadingStatus('Fetching weather and news sources...');
-
- // Fetch all configured sources
- const data = await fetchAllSources();
- console.log('Fetched sources:', Object.keys(data.sources || {}));
- console.log('Failed sources:', data.failed);
- setSourceData(data.sources || {});
-
- // If all sources failed, show error but continue
- if (data.failed > 0 && data.successful === 0) {
- setError('All data sources failed to load. Please check your internet connection and try again.');
- } else if (data.failed > 0) {
- setError(`${data.failed} data source(s) failed to load. Showing available data.`);
- }
-
- // Check if API key is configured
- const apiKey = import.meta.env.REACT_APP_ANTHROPIC_API_KEY;
- console.log('API key configured:', !!apiKey);
-
- if (!apiKey || apiKey === 'sk-ant-your-key-here') {
- console.warn('API key not configured properly');
- setBriefing('Please add your Anthropic API key to .env.local to generate briefings.');
- setLoading(false);
- return;
- }
-
- // Get yesterday's briefing for comparison
- const previousBriefing = getPreviousBriefing(1);
- console.log('Previous briefing exists:', !!previousBriefing);
-
- // Only generate briefing if we have some data
- if (Object.keys(data.sources || {}).length > 0) {
- console.log('Calling generateBriefing...');
- setLoadingStatus('Generating your personalized briefing with AI...');
+ useEffect(() => {
+ loadDashboard();
+ }, []);
+ async function loadDashboard(forceRefresh = false) {
try {
- // Generate today's briefing with Claude
- const result = await generateBriefing(data, previousBriefing);
- console.log('Briefing generated');
+ console.log('=== loadDashboard called ===');
+ console.log('Force refresh:', forceRefresh);
+ setError(null);
- // Handle result object with text and usage
- if (result && typeof result === 'object' && result.text) {
- setBriefing(result.text);
- setTokenUsage(result.usage);
- setCacheInfo(null); // Clear cache info when generating fresh briefing
- console.log('Token usage:', result.usage);
- } else if (typeof result === 'string') {
- // Fallback for string responses
- setBriefing(result);
- setCacheInfo(null);
- }
+ // Check if we already have today's briefing (unless forcing refresh)
+ if (!forceRefresh && hasTodaysBriefing()) {
+ console.log('Using cached briefing from today');
+ const todaysBriefing = getTodaysBriefing();
+ setBriefing(todaysBriefing.briefing);
+ setSourceData(todaysBriefing.sources || {});
- // Save to localStorage
- const today = new Date().toISOString().split('T')[0];
- console.log('Saving briefing for:', today);
- const briefingText = result?.text || result;
- saveBriefing(today, briefingText, data.sources);
- } catch (briefingError) {
- console.error('Failed to generate briefing:', briefingError);
- setBriefing(`Unable to generate AI briefing: ${briefingError.message}\n\nYou can still view the data widgets below.`);
+ // Calculate cache age
+ const cacheAge = Date.now() - todaysBriefing.timestamp;
+ const cacheAgeMinutes = Math.floor(cacheAge / (1000 * 60));
+
+ // Set cache info to display message
+ setCacheInfo({
+ age: cacheAgeMinutes,
+ timestamp: todaysBriefing.timestamp
+ });
+ setTokenUsage(null); // Clear token usage when using cache
+
+ setLoading(false);
+ return;
+ }
+
+ console.log('Fetching fresh data...');
+ setLoadingStatus('Fetching weather and news sources...');
+
+ // Fetch all configured sources
+ const data = await fetchAllSources();
+ console.log('Fetched sources:', Object.keys(data.sources || {}));
+ console.log('Failed sources:', data.failed);
+ setSourceData(data.sources || {});
+
+ // If all sources failed, show error but continue
+ if (data.failed > 0 && data.successful === 0) {
+ setError('All data sources failed to load. Please check your internet connection and try again.');
+ } else if (data.failed > 0) {
+ setError(`${data.failed} data source(s) failed to load. Showing available data.`);
+ }
+
+ // Check if API key is configured
+ const apiKey = import.meta.env.REACT_APP_ANTHROPIC_API_KEY;
+ console.log('API key configured:', !!apiKey);
+
+ if (!apiKey || apiKey === 'sk-ant-your-key-here') {
+ console.warn('API key not configured properly');
+ setBriefing('Please add your Anthropic API key to .env.local to generate briefings.');
+ setLoading(false);
+ return;
+ }
+
+ // Get yesterday's briefing for comparison
+ const previousBriefing = getPreviousBriefing(1);
+ console.log('Previous briefing exists:', !!previousBriefing);
+
+ // Only generate briefing if we have some data
+ if (Object.keys(data.sources || {}).length > 0) {
+ console.log('Calling generateBriefing...');
+ setLoadingStatus('Generating your personalized briefing with AI...');
+
+ try {
+ // Generate today's briefing with Claude
+ const result = await generateBriefing(data, previousBriefing);
+ console.log('Briefing generated');
+
+ // Handle result object with text and usage
+ if (result && typeof result === 'object' && result.text) {
+ setBriefing(result.text);
+ setTokenUsage(result.usage);
+ setCacheInfo(null); // Clear cache info when generating fresh briefing
+ console.log('Token usage:', result.usage);
+ } else if (typeof result === 'string') {
+ // Fallback for string responses
+ setBriefing(result);
+ setCacheInfo(null);
+ }
+
+ // Save to localStorage
+ const today = new Date().toISOString().split('T')[0];
+ console.log('Saving briefing for:', today);
+ const briefingText = result?.text || result;
+ saveBriefing(today, briefingText, data.sources);
+ } catch (briefingError) {
+ console.error('Failed to generate briefing:', briefingError);
+ setBriefing(`Unable to generate AI briefing: ${briefingError.message}\n\nYou can still view the data widgets below.`);
+ }
+ } else {
+ setBriefing('No data sources loaded successfully. Please check your configuration and try again.');
+ }
+
+ } catch (error) {
+ console.error('Dashboard load error:', error);
+ console.error('Error stack:', error.stack);
+ setError(`Failed to load dashboard: ${error.message}`);
+ } finally {
+ setLoading(false);
+ setRefreshing(false);
}
- } else {
- setBriefing('No data sources loaded successfully. Please check your configuration and try again.');
- }
-
- } catch (error) {
- console.error('Dashboard load error:', error);
- console.error('Error stack:', error.stack);
- setError(`Failed to load dashboard: ${error.message}`);
- } finally {
- setLoading(false);
- setRefreshing(false);
}
- }
- const handleRefresh = () => {
- setRefreshing(true);
- loadDashboard(true);
- };
+ const handleRefresh = () => {
+ setRefreshing(true);
+ loadDashboard(true);
+ };
+
+ if (loading) {
+ return (
+
+
+ {loadingStatus}
+
+ );
+ }
- if (loading) {
return (
-
-
-
- {loadingStatus}
-
-
- );
- }
+
+ {/* Top toolbar */}
+
+ {
+ localStorage.clear();
+ window.location.reload();
+ }}
+ title="Clear all cache and reload"
+ color="error"
+ >
+ 🗑️
+
+
+
+
+
+ {isDark ? : }
+
+
- return (
-
- {/* Top toolbar */}
-
- {
- localStorage.clear();
- window.location.reload();
- }}
- title="Clear all cache and reload"
- color="error"
- >
- 🗑️
-
-
-
-
-
- {isDark ? : }
-
-
+
+ {error && (
+
+ {error}
+
+ )}
-
- {error && (
-
- {error}
-
- )}
+ {tokenUsage && (
+
+
+ API Usage: {tokenUsage.input_tokens?.toLocaleString()} input + {tokenUsage.output_tokens?.toLocaleString()} output = {(tokenUsage.input_tokens + tokenUsage.output_tokens).toLocaleString()} total tokens
+ {' • '}
+ Cost: ${((tokenUsage.input_tokens * 0.003 / 1000) + (tokenUsage.output_tokens * 0.015 / 1000)).toFixed(4)}
+ {' '}
+
+ (Claude Sonnet 4 pricing: $0.003/1K input, $0.015/1K output)
+
+
+
+ )}
- {tokenUsage && (
-
-
- API Usage: {tokenUsage.input_tokens?.toLocaleString()} input + {tokenUsage.output_tokens?.toLocaleString()} output = {(tokenUsage.input_tokens + tokenUsage.output_tokens).toLocaleString()} total tokens
- {' • '}
- Cost: ${((tokenUsage.input_tokens * 0.003 / 1000) + (tokenUsage.output_tokens * 0.015 / 1000)).toFixed(4)}
- {' '}
-
- (Claude Sonnet 4 pricing: $0.003/1K input, $0.015/1K output)
-
-
-
- )}
+ {cacheInfo && (
+
+
+ 💾 Using cached results from {cacheInfo.age < 1 ? 'less than a minute' : cacheInfo.age === 1 ? '1 minute' : `${cacheInfo.age} minutes`} ago — no API costs incurred
+ {' '}
+
+ (Click refresh to generate a new briefing)
+
+
+
+ )}
- {cacheInfo && (
-
-
- 💾 Using cached results from {cacheInfo.age < 1 ? 'less than a minute' : cacheInfo.age === 1 ? '1 minute' : `${cacheInfo.age} minutes`} ago — no API costs incurred
- {' '}
-
- (Click refresh to generate a new briefing)
-
-
-
- )}
+
+
+
+
+
+
+ {/* Render NewsWidget for each news source */}
+ {Object.entries(sourceData).map(([key, data]) => {
+ if (key === 'weather') return null;
+ return (
+
+ );
+ })}
+
+
+
- {/* Widget Grid - easily rearrangeable */}
-
-
-
-
-
- {/* Render NewsWidget for each news source */}
- {Object.entries(sourceData).map(([key, data]) => {
- if (key === 'weather') return null;
- return (
-
- );
- })}
+ {/* Fixed chat at bottom */}
+
-
-
- {/* Fixed chat at bottom */}
-
-
- );
+ );
}
export default Dashboard;
\ No newline at end of file
diff --git a/claudash/src/components/DebugPanel.jsx b/claudash/src/components/DebugPanel.jsx
index b36ae75..10b5672 100644
--- a/claudash/src/components/DebugPanel.jsx
+++ b/claudash/src/components/DebugPanel.jsx
@@ -1,5 +1,5 @@
import { useState } from 'react';
-import { Button, Box, Paper, Typography } from '@mui/material';
+import { Button, Box, Paper } from '@mui/material';
import { Download, Upload } from '@mui/icons-material';
import { exportAllData, importDummyData } from '../services/storageService';
@@ -38,7 +38,7 @@ function DebugPanel() {
return (
- Debug Tools
+ Debug Tools
{dummyMode && (
-
- Using dummy data
-
+ Using dummy data
)}
);
diff --git a/claudash/src/components/widgets/BriefingWidget.jsx b/claudash/src/components/widgets/BriefingWidget.jsx
index 7a9f058..bd661db 100644
--- a/claudash/src/components/widgets/BriefingWidget.jsx
+++ b/claudash/src/components/widgets/BriefingWidget.jsx
@@ -1,4 +1,4 @@
-import { Typography, Skeleton, Box } from '@mui/material';
+import { Skeleton, Box } from '@mui/material';
import GenericWidget from './GenericWidget';
function BriefingWidget({ briefing, sx }) {
@@ -9,16 +9,7 @@ function BriefingWidget({ briefing, sx }) {
>
{briefing ? (
-
- {briefing}
-
+ {briefing}
) : (
<>
@@ -33,4 +24,4 @@ function BriefingWidget({ briefing, sx }) {
);
}
-export default BriefingWidget;
\ No newline at end of file
+export default BriefingWidget;
diff --git a/claudash/src/components/widgets/GenericWidget.jsx b/claudash/src/components/widgets/GenericWidget.jsx
index 1dcf2b4..389e7f4 100644
--- a/claudash/src/components/widgets/GenericWidget.jsx
+++ b/claudash/src/components/widgets/GenericWidget.jsx
@@ -1,4 +1,4 @@
-import { Paper, Typography, Box } from '@mui/material';
+import { Paper, Box } from '@mui/material';
function GenericWidget({ title, data, children, sx, ...props }) {
return (
@@ -11,12 +11,12 @@ function GenericWidget({ title, data, children, sx, ...props }) {
}}
{...props}
>
- {title}
+ {title}
- {children || {JSON.stringify(data)} }
+ {children || {JSON.stringify(data)}
}
);
}
-export default GenericWidget;
\ No newline at end of file
+export default GenericWidget;
diff --git a/claudash/src/components/widgets/NewsWidget.jsx b/claudash/src/components/widgets/NewsWidget.jsx
index 8d715d7..f49408c 100644
--- a/claudash/src/components/widgets/NewsWidget.jsx
+++ b/claudash/src/components/widgets/NewsWidget.jsx
@@ -1,14 +1,14 @@
-import { Typography, Box, Link, Chip, List, ListItem, ListItemText } from '@mui/material';
+import { Box, Link, Chip, List, ListItem, ListItemText } from '@mui/material';
import { OpenInNew, Comment, TrendingUp } from '@mui/icons-material';
import GenericWidget from './GenericWidget';
function NewsWidget({ data, sx }) {
if (!data || data.error) {
return (
-
-
+
+
{data?.message || 'News data unavailable'}
-
+
);
}
@@ -18,7 +18,7 @@ function NewsWidget({ data, sx }) {
const subreddit = data.url?.match(/\/r\/([^\/\.]+)/)?.[1] || 'Reddit';
return (
-
+
{data.posts.slice(0, 5).map((post, index) => (
@@ -55,10 +55,10 @@ function NewsWidget({ data, sx }) {
'&:hover': { textDecoration: 'underline' }
}}
>
-
+
{post.title}
-
+
}
@@ -77,9 +77,7 @@ function NewsWidget({ data, sx }) {
size="small"
variant="outlined"
/>
-
- by {post.author}
-
+ by {post.author}
}
/>
@@ -130,34 +128,23 @@ function NewsWidget({ data, sx }) {
'&:hover': { textDecoration: 'underline' }
}}
>
-
+
{item.title}
-
+
}
secondaryTypographyProps={{ component: 'div' }}
secondary={
-
+
{item.pubDate && new Date(item.pubDate).toLocaleDateString()}
-
+
{item.description && (
-
+
{item.description.replace(/<[^>]*>?/gm, '')}
-
+
)}
}
@@ -173,12 +160,12 @@ function NewsWidget({ data, sx }) {
if (data.type === 'hackernews' && data.storyIds) {
return (
-
+
{data.storyIds.length} top stories loaded
-
-
+
+
(Story details will load when API is configured)
-
+
);
}
@@ -186,12 +173,12 @@ function NewsWidget({ data, sx }) {
// Default fallback for unknown data types
return (
-
+
Data format not recognized
-
-
+
+
{JSON.stringify(data).substring(0, 100)}...
-
+
);
}
diff --git a/claudash/src/components/widgets/WeatherWidget.jsx b/claudash/src/components/widgets/WeatherWidget.jsx
index 3326788..31e4c6d 100644
--- a/claudash/src/components/widgets/WeatherWidget.jsx
+++ b/claudash/src/components/widgets/WeatherWidget.jsx
@@ -1,4 +1,4 @@
-import { Typography, Box, Chip, Stack } from '@mui/material';
+import { Box, Chip, Stack } from '@mui/material';
import { Cloud, WbSunny, Thunderstorm, AcUnit } from '@mui/icons-material';
import GenericWidget from './GenericWidget';
@@ -15,9 +15,9 @@ function WeatherWidget({ data, sx }) {
if (!data || data.error) {
return (
-
+
{data?.message || 'Weather data unavailable'}
-
+
);
}
@@ -39,18 +39,12 @@ function WeatherWidget({ data, sx }) {
{getWeatherIcon(description)}
-
- {temp}
-
-
- {tempUnit}
-
+ {temp}
+ {tempUnit}
-
- {description}
-
+ {description}
0 && (
-
- Forecast
-
+ Forecast
{data.forecast.slice(0, 3).map((day, index) => (
-
- Day {index + 1}
-
-
+ Day {index + 1}
+
{day.maxtempF || day.maxtempC}° / {day.mintempF || day.mintempC}°
-
+
))}
diff --git a/claudash/src/index.css b/claudash/src/index.css
index 08a3ac9..e69de29 100644
--- a/claudash/src/index.css
+++ b/claudash/src/index.css
@@ -1,68 +0,0 @@
-:root {
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
diff --git a/claudash/src/theme.js b/claudash/src/theme.js
index 3eb6710..817a4b2 100644
--- a/claudash/src/theme.js
+++ b/claudash/src/theme.js
@@ -1,9 +1,6 @@
import { createTheme } from '@mui/material/styles';
const sharedTheme = {
- typography: {
- fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
- },
shape: {
borderRadius: 8,
},