diff --git a/client/src/components/ColorPicker.tsx b/client/src/components/ColorPicker.tsx
new file mode 100644
index 0000000..dc8468c
--- /dev/null
+++ b/client/src/components/ColorPicker.tsx
@@ -0,0 +1,151 @@
+import { useState } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Box,
+ Typography,
+ TextField,
+} from '@mui/material';
+import { Palette } from '@mui/icons-material';
+import { useThemeStore } from '../stores/themeStore';
+
+interface ColorPickerProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+const predefinedColors = [
+ // Purples (3 shades as requested)
+ { name: 'Deep Purple', value: '#673ab7' },
+ { name: 'Medium Purple', value: '#9c27b0' },
+ { name: 'Light Purple', value: '#e1bee7' },
+
+ // Blues
+ { name: 'Deep Blue', value: '#1976d2' },
+ { name: 'Light Blue', value: '#03dac6' },
+ { name: 'Indigo', value: '#3f51b5' },
+
+ // Greens
+ { name: 'Forest Green', value: '#2e7d32' },
+ { name: 'Mint Green', value: '#4caf50' },
+ { name: 'Teal', value: '#009688' },
+
+ // Reds/Oranges
+ { name: 'Deep Red', value: '#d32f2f' },
+ { name: 'Coral', value: '#ff5722' },
+ { name: 'Amber', value: '#ff9800' },
+
+ // Grays
+ { name: 'Charcoal', value: '#424242' },
+ { name: 'Slate', value: '#607d8b' },
+ { name: 'Warm Gray', value: '#795548' },
+];
+
+export function ColorPicker({ open, onClose }: ColorPickerProps) {
+ const { primaryColor, setPrimaryColor } = useThemeStore();
+ const [customColor, setCustomColor] = useState(primaryColor);
+
+ const handleColorSelect = (color: string) => {
+ setPrimaryColor(color);
+ onClose();
+ };
+
+ const handleCustomColorSubmit = () => {
+ if (customColor && /^#[0-9A-F]{6}$/i.test(customColor)) {
+ setPrimaryColor(customColor);
+ onClose();
+ }
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/components/ColorPickerButton.tsx b/client/src/components/ColorPickerButton.tsx
new file mode 100644
index 0000000..bf24907
--- /dev/null
+++ b/client/src/components/ColorPickerButton.tsx
@@ -0,0 +1,38 @@
+import { useState } from 'react';
+import { IconButton, Tooltip } from '@mui/material';
+import { Palette } from '@mui/icons-material';
+import { useThemeStore } from '../stores/themeStore';
+import { ColorPicker } from './ColorPicker';
+
+export function ColorPickerButton() {
+ const [open, setOpen] = useState(false);
+ const { primaryColor } = useThemeStore();
+
+ return (
+ <>
+
+ setOpen(true)}
+ sx={{
+ position: 'relative',
+ '&::after': {
+ content: '""',
+ position: 'absolute',
+ bottom: 2,
+ right: 2,
+ width: 12,
+ height: 12,
+ borderRadius: '50%',
+ bgcolor: primaryColor,
+ border: 1,
+ borderColor: 'background.paper',
+ },
+ }}
+ >
+
+
+
+ setOpen(false)} />
+ >
+ );
+}
\ No newline at end of file
diff --git a/client/src/components/ThemeProvider.tsx b/client/src/components/ThemeProvider.tsx
new file mode 100644
index 0000000..95758ba
--- /dev/null
+++ b/client/src/components/ThemeProvider.tsx
@@ -0,0 +1,25 @@
+import { createTheme, ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
+import { useThemeStore } from '../stores/themeStore';
+import { ReactNode } from 'react';
+
+interface ThemeProviderProps {
+ children: ReactNode;
+}
+
+export function ThemeProvider({ children }: ThemeProviderProps) {
+ const { primaryColor } = useThemeStore();
+
+ const theme = createTheme({
+ palette: {
+ primary: {
+ main: primaryColor,
+ },
+ },
+ });
+
+ return (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/layouts/DesktopLayout.tsx b/client/src/layouts/DesktopLayout.tsx
index 4556f3a..7039bcb 100644
--- a/client/src/layouts/DesktopLayout.tsx
+++ b/client/src/layouts/DesktopLayout.tsx
@@ -2,6 +2,7 @@ import { Box, Typography, IconButton, TextField, Button } from '@mui/material';
import PrintIcon from '@mui/icons-material/Print';
import type { GroupWithTasks, TaskWithSteps, StepWithNotes } from '../types';
import { CreateButtons } from '../components/CreateButtons';
+import { ColorPickerButton } from '../components/ColorPickerButton';
import { useUserSelection } from '../hooks/useUserSelection';
interface DesktopLayoutProps {
@@ -45,7 +46,10 @@ export function DesktopLayout({
Groups
-
+
+
+
+
{/* Groups List */}
diff --git a/client/src/layouts/MobileLayout.tsx b/client/src/layouts/MobileLayout.tsx
index c540481..d8d9c64 100644
--- a/client/src/layouts/MobileLayout.tsx
+++ b/client/src/layouts/MobileLayout.tsx
@@ -3,6 +3,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import PrintIcon from '@mui/icons-material/Print';
import type { GroupWithTasks, TaskWithSteps, StepWithNotes } from '../types';
import { CreateButtons } from '../components/CreateButtons';
+import { ColorPickerButton } from '../components/ColorPickerButton';
import { useUserSelection } from '../hooks/useUserSelection';
function isGroupView(selectedGroup?: GroupWithTasks) {
@@ -103,7 +104,10 @@ export function MobileLayout({
<>
Groups
-
+
+
+
+
{/* Groups List */}
diff --git a/client/src/main.tsx b/client/src/main.tsx
index c6257cc..bdff80d 100644
--- a/client/src/main.tsx
+++ b/client/src/main.tsx
@@ -10,12 +10,15 @@ if (process.env.NODE_ENV !== "production") {
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
+import { ThemeProvider } from './components/ThemeProvider';
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
-
+
+
+
);
diff --git a/client/src/stores/themeStore.ts b/client/src/stores/themeStore.ts
new file mode 100644
index 0000000..7dac9a6
--- /dev/null
+++ b/client/src/stores/themeStore.ts
@@ -0,0 +1,21 @@
+import { create } from 'zustand';
+import { devtools, persist } from 'zustand/middleware';
+
+interface ThemeState {
+ primaryColor: string;
+ setPrimaryColor: (color: string) => void;
+}
+
+export const useThemeStore = create()(
+ devtools(
+ persist(
+ (set) => ({
+ primaryColor: '#1976d2', // MUI default blue
+ setPrimaryColor: (color) => set({ primaryColor: color }),
+ }),
+ {
+ name: 'theme-storage',
+ }
+ )
+ )
+);
\ No newline at end of file