Create a frontend wireframe. #7
16 changed files with 1405 additions and 57 deletions
64
frontend/app/components/ErrorFallback.tsx
Normal file
64
frontend/app/components/ErrorFallback.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Typography,
|
||||
Paper,
|
||||
} from '@mui/material'
|
||||
import {
|
||||
Refresh as RefreshIcon,
|
||||
BugReport as BugReportIcon,
|
||||
} from '@mui/icons-material'
|
||||
|
||||
interface ErrorFallbackProps {
|
||||
error: Error
|
||||
resetError: () => void
|
||||
}
|
||||
|
||||
export default function ErrorFallback({
|
||||
error,
|
||||
resetError,
|
||||
}: ErrorFallbackProps) {
|
||||
return (
|
||||
<Container maxWidth="md" sx={{ py: 8 }}>
|
||||
<Paper sx={{ p: 4, textAlign: 'center' }}>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<BugReportIcon sx={{ fontSize: 64, color: 'error.main', mb: 2 }} />
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Something went wrong
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
An unexpected error occurred. Please try refreshing the page or
|
||||
contact support if the problem persists.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Alert severity="error" sx={{ mb: 3, textAlign: 'left' }}>
|
||||
<AlertTitle>Error Details</AlertTitle>
|
||||
{error.message}
|
||||
</Alert>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={resetError}
|
||||
size="large"
|
||||
>
|
||||
Try Again
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => window.location.reload()}
|
||||
size="large"
|
||||
>
|
||||
Reload Page
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
237
frontend/app/components/Layout.tsx
Normal file
237
frontend/app/components/Layout.tsx
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import {
|
||||
AppBar,
|
||||
Box,
|
||||
CssBaseline,
|
||||
Drawer,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Toolbar,
|
||||
Typography,
|
||||
useTheme,
|
||||
Fab,
|
||||
LinearProgress,
|
||||
} from '@mui/material'
|
||||
import {
|
||||
Menu as MenuIcon,
|
||||
Dashboard as DashboardIcon,
|
||||
Add as AddIcon,
|
||||
Settings as SettingsIcon,
|
||||
DarkMode as DarkModeIcon,
|
||||
LightMode as LightModeIcon,
|
||||
} from '@mui/icons-material'
|
||||
|
||||
const drawerWidth = 240
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export default function Layout({ children, loading = false }: LayoutProps) {
|
||||
const theme = useTheme()
|
||||
const [mobileOpen, setMobileOpen] = useState(false)
|
||||
const [darkMode, setDarkMode] = useState(false)
|
||||
|
||||
// Check system preference and localStorage on mount
|
||||
useEffect(() => {
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
const prefersDark = window.matchMedia(
|
||||
'(prefers-color-scheme: dark)'
|
||||
).matches
|
||||
setDarkMode(savedTheme === 'dark' || (!savedTheme && prefersDark))
|
||||
}, [])
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setMobileOpen(!mobileOpen)
|
||||
}
|
||||
|
||||
const handleThemeToggle = () => {
|
||||
const newTheme = !darkMode
|
||||
setDarkMode(newTheme)
|
||||
localStorage.setItem('theme', newTheme ? 'dark' : 'light')
|
||||
document.documentElement.classList.toggle('dark', newTheme)
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
text: 'Tasks',
|
||||
icon: <DashboardIcon />,
|
||||
path: '/',
|
||||
},
|
||||
{
|
||||
text: 'Settings',
|
||||
icon: <SettingsIcon />,
|
||||
path: '/settings',
|
||||
},
|
||||
]
|
||||
|
||||
const drawer = (
|
||||
<div>
|
||||
<Toolbar>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
component="div"
|
||||
sx={{ fontWeight: 700 }}
|
||||
>
|
||||
Captain's Log
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
<List>
|
||||
{menuItems.map(item => (
|
||||
<ListItem key={item.text} disablePadding>
|
||||
<ListItemButton
|
||||
component="a"
|
||||
href={item.path}
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
mx: 1,
|
||||
my: 0.5,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main + '10',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{ color: theme.palette.primary.main }}>
|
||||
{item.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={item.text}
|
||||
primaryTypographyProps={{
|
||||
fontWeight: 500,
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<CssBaseline />
|
||||
|
||||
{/* Loading indicator */}
|
||||
{loading && (
|
||||
<LinearProgress
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: theme.zIndex.appBar + 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* App Bar */}
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{
|
||||
width: { md: `calc(100% - ${drawerWidth}px)` },
|
||||
ml: { md: `${drawerWidth}px` },
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
onClick={handleDrawerToggle}
|
||||
sx={{ mr: 2, display: { md: 'none' } }}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
|
||||
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||
Captain's Log
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={handleThemeToggle}
|
||||
aria-label="toggle dark mode"
|
||||
>
|
||||
{darkMode ? <LightModeIcon /> : <DarkModeIcon />}
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{/* Drawer */}
|
||||
<Box
|
||||
component="nav"
|
||||
sx={{ width: { md: drawerWidth }, flexShrink: { md: 0 } }}
|
||||
aria-label="navigation menu"
|
||||
>
|
||||
{/* Mobile drawer */}
|
||||
<Drawer
|
||||
variant="temporary"
|
||||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile.
|
||||
}}
|
||||
sx={{
|
||||
display: { xs: 'block', md: 'none' },
|
||||
'& .MuiDrawer-paper': {
|
||||
boxSizing: 'border-box',
|
||||
width: drawerWidth,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
|
||||
{/* Desktop drawer */}
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: { xs: 'none', md: 'block' },
|
||||
'& .MuiDrawer-paper': {
|
||||
boxSizing: 'border-box',
|
||||
width: drawerWidth,
|
||||
},
|
||||
}}
|
||||
open
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
</Box>
|
||||
|
||||
{/* Main content */}
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
p: 3,
|
||||
width: { md: `calc(100% - ${drawerWidth}px)` },
|
||||
minHeight: '100vh',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
{/* Quick capture FAB */}
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label="add task"
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
zIndex: theme.zIndex.fab,
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
53
frontend/app/components/LoadingSpinner.tsx
Normal file
53
frontend/app/components/LoadingSpinner.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react'
|
||||
import { Box, CircularProgress, Typography, Skeleton } from '@mui/material'
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
size?: number
|
||||
message?: string
|
||||
variant?: 'spinner' | 'skeleton'
|
||||
}
|
||||
|
||||
export default function LoadingSpinner({
|
||||
size = 40,
|
||||
message = 'Loading...',
|
||||
variant = 'spinner',
|
||||
}: LoadingSpinnerProps) {
|
||||
if (variant === 'skeleton') {
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Skeleton variant="text" width="60%" height={32} sx={{ mb: 2 }} />
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
width="100%"
|
||||
height={120}
|
||||
sx={{ mb: 1 }}
|
||||
/>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
width="100%"
|
||||
height={120}
|
||||
sx={{ mb: 1 }}
|
||||
/>
|
||||
<Skeleton variant="rectangular" width="100%" height={120} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
p: 4,
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={size} thickness={4} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{message}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useCallback, useRef, useEffect } from 'react'
|
||||
import { ApiError } from '~/types/task'
|
||||
import type { ApiError } from '~/types/task'
|
||||
|
||||
interface UseApiState<T> {
|
||||
data: T | null
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { renderHook, act, waitFor } from '@testing-library/react'
|
||||
import { useTask } from './useTask'
|
||||
import { Task, TaskStatus, UpdateTaskRequest } from '~/types/task'
|
||||
import type { Task, UpdateTaskRequest } from '~/types/task'
|
||||
import { TaskStatus } from '~/types/task'
|
||||
import { apiClient } from '~/services/api'
|
||||
|
||||
// Mock the API client
|
||||
|
|
@ -13,7 +14,7 @@ vi.mock('~/services/api', () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
const mockApiClient = apiClient as {
|
||||
const mockApiClient = apiClient as unknown as {
|
||||
getTask: ReturnType<typeof vi.fn>
|
||||
updateTask: ReturnType<typeof vi.fn>
|
||||
deleteTask: ReturnType<typeof vi.fn>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useCallback } from 'react'
|
||||
import { Task, UpdateTaskRequest, ApiError } from '~/types/task'
|
||||
import type { Task, UpdateTaskRequest, ApiError } from '~/types/task'
|
||||
import { apiClient } from '~/services/api'
|
||||
|
||||
interface UseTaskState {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { renderHook, act, waitFor } from '@testing-library/react'
|
||||
import { useTasks } from './useTasks'
|
||||
import { Task, TaskStatus, CreateTaskRequest } from '~/types/task'
|
||||
import type { Task, CreateTaskRequest } from '~/types/task'
|
||||
import { TaskStatus } from '~/types/task'
|
||||
import { apiClient } from '~/services/api'
|
||||
|
||||
// Mock the API client
|
||||
|
|
@ -12,7 +13,7 @@ vi.mock('~/services/api', () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
const mockApiClient = apiClient as {
|
||||
const mockApiClient = apiClient as unknown as {
|
||||
listTasks: ReturnType<typeof vi.fn>
|
||||
createTask: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useCallback, useEffect } from 'react'
|
||||
import { Task, CreateTaskRequest, TaskStatus, ApiError } from '~/types/task'
|
||||
import type { Task, CreateTaskRequest, ApiError } from '~/types/task'
|
||||
import { TaskStatus } from '~/types/task'
|
||||
import { apiClient } from '~/services/api'
|
||||
|
||||
interface UseTasksState {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,12 @@ import {
|
|||
Scripts,
|
||||
ScrollRestoration,
|
||||
} from 'react-router'
|
||||
import { ThemeProvider } from '@mui/material/styles'
|
||||
import { CssBaseline } from '@mui/material'
|
||||
|
||||
import type { Route } from './+types/root'
|
||||
import { theme, darkTheme } from './theme'
|
||||
import AppLayout from './components/Layout'
|
||||
import './app.css'
|
||||
|
||||
export const links: Route.LinksFunction = () => [
|
||||
|
|
@ -42,7 +46,20 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||
}
|
||||
|
||||
export default function App() {
|
||||
return <Outlet />
|
||||
const isDarkMode =
|
||||
typeof window !== 'undefined' &&
|
||||
(localStorage.getItem('theme') === 'dark' ||
|
||||
(!localStorage.getItem('theme') &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches))
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={isDarkMode ? darkTheme : theme}>
|
||||
<CssBaseline />
|
||||
<AppLayout>
|
||||
<Outlet />
|
||||
</AppLayout>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
|
|
@ -61,7 +78,16 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|||
stack = error.stack
|
||||
}
|
||||
|
||||
const isDarkMode =
|
||||
typeof window !== 'undefined' &&
|
||||
(localStorage.getItem('theme') === 'dark' ||
|
||||
(!localStorage.getItem('theme') &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches))
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={isDarkMode ? darkTheme : theme}>
|
||||
<CssBaseline />
|
||||
<AppLayout>
|
||||
<main className="pt-16 p-4 container mx-auto">
|
||||
<h1>{message}</h1>
|
||||
<p>{details}</p>
|
||||
|
|
@ -71,5 +97,7 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|||
</pre>
|
||||
)}
|
||||
</main>
|
||||
</AppLayout>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ import { describe, it, expect } from 'vitest'
|
|||
import Home from './home'
|
||||
|
||||
describe('Home component', () => {
|
||||
it('should render welcome component', () => {
|
||||
it('should render task management interface', () => {
|
||||
render(<Home />)
|
||||
expect(screen.getByText(/React Router/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Tasks/i)).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText(/GTD-inspired task management system/i)
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText(/Task Management Interface Coming Soon/i)
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,13 +1,44 @@
|
|||
import type { Route } from './+types/home'
|
||||
import { Welcome } from '../welcome/welcome'
|
||||
import { Box, Typography, Container } from '@mui/material'
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: 'New React Router App' },
|
||||
{ name: 'description', content: 'Welcome to React Router!' },
|
||||
{ title: "Captain's Log - Tasks" },
|
||||
{ name: 'description', content: 'GTD-inspired task management system' },
|
||||
]
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return <Welcome />
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ py: 4 }}>
|
||||
<Typography variant="h1" component="h1" gutterBottom>
|
||||
Tasks
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
|
||||
Your GTD-inspired task management system. Capture everything, see only
|
||||
what matters.
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
p: 4,
|
||||
textAlign: 'center',
|
||||
color: 'text.secondary',
|
||||
border: '2px dashed',
|
||||
borderColor: 'grey.300',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Task Management Interface Coming Soon
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
The task list, task cards, and quick capture components will be
|
||||
implemented in the next phase.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { apiClient } from './api'
|
||||
import {
|
||||
Task,
|
||||
TaskStatus,
|
||||
CreateTaskRequest,
|
||||
UpdateTaskRequest,
|
||||
} from '~/types/task'
|
||||
import type { Task, CreateTaskRequest, UpdateTaskRequest } from '~/types/task'
|
||||
import { TaskStatus } from '~/types/task'
|
||||
|
||||
// Mock fetch globally
|
||||
const mockFetch = vi.fn()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {
|
||||
import type {
|
||||
Task,
|
||||
CreateTaskRequest,
|
||||
UpdateTaskRequest,
|
||||
|
|
|
|||
280
frontend/app/theme.ts
Normal file
280
frontend/app/theme.ts
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
import { createTheme } from '@mui/material/styles'
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
interface Theme {
|
||||
custom: {
|
||||
task: {
|
||||
todo: string
|
||||
done: string
|
||||
backlog: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ThemeOptions {
|
||||
custom?: {
|
||||
task?: {
|
||||
todo?: string
|
||||
done?: string
|
||||
backlog?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
500: '#3b82f6',
|
||||
main: '#3b82f6',
|
||||
contrastText: '#ffffff',
|
||||
},
|
||||
background: {
|
||||
default: '#f9fafb',
|
||||
paper: '#ffffff',
|
||||
},
|
||||
text: {
|
||||
primary: '#111827',
|
||||
secondary: '#6b7280',
|
||||
},
|
||||
grey: {
|
||||
50: '#f9fafb',
|
||||
100: '#f3f4f6',
|
||||
200: '#e5e7eb',
|
||||
300: '#d1d5db',
|
||||
600: '#4b5563',
|
||||
700: '#374151',
|
||||
800: '#1f2937',
|
||||
900: '#111827',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: '"Inter", ui-sans-serif, system-ui, sans-serif',
|
||||
h1: {
|
||||
fontSize: '2rem',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h2: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.3,
|
||||
},
|
||||
h3: {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
body1: {
|
||||
fontSize: '1rem',
|
||||
lineHeight: 1.6,
|
||||
},
|
||||
body2: {
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 12,
|
||||
},
|
||||
components: {
|
||||
MuiAppBar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: '#ffffff',
|
||||
color: '#111827',
|
||||
boxShadow:
|
||||
'0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
borderBottom: '1px solid #e5e7eb',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiDrawer: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
backgroundColor: '#ffffff',
|
||||
borderRight: '1px solid #e5e7eb',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: 'none',
|
||||
borderRadius: '0.75rem',
|
||||
fontWeight: 500,
|
||||
},
|
||||
contained: {
|
||||
boxShadow: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
'&:hover': {
|
||||
boxShadow:
|
||||
'0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: '0.75rem',
|
||||
boxShadow:
|
||||
'0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
border: '1px solid #e5e7eb',
|
||||
'&:hover': {
|
||||
boxShadow:
|
||||
'0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
borderColor: '#d1d5db',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTextField: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '0.75rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
custom: {
|
||||
task: {
|
||||
todo: '#3b82f6',
|
||||
done: '#22c55e',
|
||||
backlog: '#6b7280',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
500: '#3b82f6',
|
||||
main: '#3b82f6',
|
||||
contrastText: '#ffffff',
|
||||
},
|
||||
background: {
|
||||
default: '#030712',
|
||||
paper: '#1f2937',
|
||||
},
|
||||
text: {
|
||||
primary: '#f9fafb',
|
||||
secondary: '#9ca3af',
|
||||
},
|
||||
grey: {
|
||||
50: '#f9fafb',
|
||||
100: '#f3f4f6',
|
||||
200: '#e5e7eb',
|
||||
300: '#d1d5db',
|
||||
600: '#4b5563',
|
||||
700: '#374151',
|
||||
800: '#1f2937',
|
||||
900: '#111827',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: '"Inter", ui-sans-serif, system-ui, sans-serif',
|
||||
h1: {
|
||||
fontSize: '2rem',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
h2: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.3,
|
||||
},
|
||||
h3: {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
body1: {
|
||||
fontSize: '1rem',
|
||||
lineHeight: 1.6,
|
||||
},
|
||||
body2: {
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 12,
|
||||
},
|
||||
components: {
|
||||
MuiAppBar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: '#1f2937',
|
||||
color: '#f9fafb',
|
||||
boxShadow:
|
||||
'0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
borderBottom: '1px solid #374151',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiDrawer: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
backgroundColor: '#1f2937',
|
||||
borderRight: '1px solid #374151',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: 'none',
|
||||
borderRadius: '0.75rem',
|
||||
fontWeight: 500,
|
||||
},
|
||||
contained: {
|
||||
boxShadow: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
'&:hover': {
|
||||
boxShadow:
|
||||
'0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: '#1f2937',
|
||||
borderRadius: '0.75rem',
|
||||
boxShadow:
|
||||
'0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
border: '1px solid #374151',
|
||||
'&:hover': {
|
||||
boxShadow:
|
||||
'0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
borderColor: '#4b5563',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTextField: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '0.75rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
custom: {
|
||||
task: {
|
||||
todo: '#3b82f6',
|
||||
done: '#22c55e',
|
||||
backlog: '#6b7280',
|
||||
},
|
||||
},
|
||||
})
|
||||
700
frontend/package-lock.json
generated
700
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -16,6 +16,10 @@
|
|||
"typecheck": "react-router typegen && tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@mui/icons-material": "^7.3.2",
|
||||
"@mui/material": "^7.3.2",
|
||||
"@react-router/node": "^7.7.1",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"isbot": "^5.1.27",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue