Update to common styling
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>StrafesNET Dev Portal</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Header from "./components/Header.tsx";
|
||||
import AnimatedBackground from "./components/AnimatedBackground.tsx";
|
||||
import ApiKeyDialog from "./components/applications/dialog/ApiKeyDialog.tsx";
|
||||
import ApplicationFormDialog from "./components/applications/dialog/ApplicationFormDialog.tsx";
|
||||
import ApplicationMenu from "./components/applications/ApplicationMenu.tsx";
|
||||
@@ -142,10 +143,9 @@ const AppContent = () => {
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<ApplicationProvider permissions={user.permissions}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', position: 'relative' }}>
|
||||
<AnimatedBackground />
|
||||
<Box sx={{ position: 'relative', zIndex: 1, display: 'flex', flexDirection: 'column' }}>
|
||||
<AppUI user={user} isAdmin={isAdmin} />
|
||||
|
||||
{/* Notifications */}
|
||||
@@ -168,6 +168,7 @@ const AppContent = () => {
|
||||
{notification.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Box>
|
||||
</Box>
|
||||
</ApplicationProvider>
|
||||
</ThemeProvider>
|
||||
|
||||
34
web/src/components/AnimatedBackground.tsx
Normal file
34
web/src/components/AnimatedBackground.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { surface } from '../theme/colors';
|
||||
|
||||
const AnimatedBackground: React.FC = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
zIndex: 0,
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'none',
|
||||
background: `
|
||||
radial-gradient(ellipse 80% 60% at 50% -20%, rgba(124, 58, 237, 0.08) 0%, transparent 100%),
|
||||
radial-gradient(ellipse 60% 40% at 80% 80%, rgba(34, 211, 238, 0.04) 0%, transparent 100%),
|
||||
${surface.base}
|
||||
`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
opacity: 0.03,
|
||||
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`,
|
||||
backgroundRepeat: 'repeat',
|
||||
backgroundSize: '128px 128px',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnimatedBackground;
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import {UserInfo} from "../types";
|
||||
import {useConfig} from "../context/ConfigContext.tsx";
|
||||
import RateLimitDisplay from './RateLimitDisplay';
|
||||
import { primary, border, fill, text } from '../theme/colors';
|
||||
|
||||
interface HeaderProps {
|
||||
user: UserInfo;
|
||||
@@ -64,9 +65,18 @@ const Header: React.FC<HeaderProps> = ({ user, onCreateAppClick, isAdmin, adminV
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBar position="static" elevation={0}>
|
||||
<Toolbar>
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||
<AppBar position="sticky">
|
||||
<Toolbar sx={{ gap: 1 }}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="div"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '-0.02em',
|
||||
color: text.secondary,
|
||||
}}
|
||||
>
|
||||
StrafesNET Developer Portal
|
||||
</Typography>
|
||||
|
||||
@@ -82,15 +92,17 @@ const Header: React.FC<HeaderProps> = ({ user, onCreateAppClick, isAdmin, adminV
|
||||
startIcon={adminView ? <AppsIcon /> : <AdminIcon />}
|
||||
onClick={onToggleAdminView}
|
||||
sx={{
|
||||
backgroundColor: adminView
|
||||
? 'rgba(149, 128, 255, 0.15)'
|
||||
: 'rgba(255, 255, 255, 0.08)',
|
||||
px: 1.5,
|
||||
py: 0.75,
|
||||
borderRadius: 2,
|
||||
backgroundColor: adminView ? fill.primaryStrong : fill.default,
|
||||
border: `1px solid ${adminView ? border.primaryStrong : border.default}`,
|
||||
color: adminView ? primary.main : text.tertiary,
|
||||
'&:hover': {
|
||||
backgroundColor: adminView
|
||||
? 'rgba(149, 128, 255, 0.25)'
|
||||
: 'rgba(255, 255, 255, 0.15)',
|
||||
backgroundColor: adminView ? fill.primaryActive : fill.hover,
|
||||
borderColor: border.primaryMedium,
|
||||
color: primary.main,
|
||||
},
|
||||
mr: 2,
|
||||
}}
|
||||
>
|
||||
{adminView ? 'My Apps' : 'Admin'}
|
||||
@@ -105,11 +117,17 @@ const Header: React.FC<HeaderProps> = ({ user, onCreateAppClick, isAdmin, adminV
|
||||
onClick={onCreateAppClick}
|
||||
disabled={!user.active}
|
||||
sx={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||
px: 1.5,
|
||||
py: 0.75,
|
||||
borderRadius: 2,
|
||||
backgroundColor: fill.default,
|
||||
border: `1px solid ${border.default}`,
|
||||
color: text.tertiary,
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||
backgroundColor: fill.hover,
|
||||
borderColor: border.primaryMedium,
|
||||
color: primary.main,
|
||||
},
|
||||
mr: 2
|
||||
}}
|
||||
>
|
||||
Create App
|
||||
@@ -123,14 +141,22 @@ const Header: React.FC<HeaderProps> = ({ user, onCreateAppClick, isAdmin, adminV
|
||||
aria-controls={open ? 'profile-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
sx={{
|
||||
p: 0.5,
|
||||
transition: 'all 0.15s ease',
|
||||
'&:hover': { boxShadow: `0 0 12px rgba(167, 139, 250, 0.3)` },
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
width: 32,
|
||||
height: 32,
|
||||
cursor: 'pointer',
|
||||
opacity: user.active ? 1 : 0.6,
|
||||
border: !user.active ? '2px solid #f44336' : 'none'
|
||||
border: !user.active ? `2px solid #f87171` : 'none',
|
||||
backgroundColor: primary.dark,
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
alt="Profile Photo"
|
||||
src={ user.avatar_url }
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { ThemeProvider, createTheme, CssBaseline } from '@mui/material'
|
||||
import App from './App.tsx'
|
||||
|
||||
// Create a custom theme (customize colors as needed)
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#1976d2',
|
||||
},
|
||||
secondary: {
|
||||
main: '#dc004e',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline /> {/* Provides baseline CSS normalization */}
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
)
|
||||
|
||||
84
web/src/theme/colors.ts
Normal file
84
web/src/theme/colors.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/* ─── Core palette ─────────────────────────────────────────────────────
|
||||
* Single source of truth for all colors used across the UI.
|
||||
* Components should import from here instead of hardcoding hex values.
|
||||
* ──────────────────────────────────────────────────────────────────── */
|
||||
|
||||
// Brand / accent
|
||||
export const primary = {
|
||||
main: '#a78bfa',
|
||||
light: '#c4b5fd',
|
||||
dark: '#7c3aed',
|
||||
darker: '#6d28d9',
|
||||
mid: '#8b5cf6',
|
||||
} as const;
|
||||
|
||||
export const secondary = {
|
||||
main: '#22d3ee',
|
||||
light: '#67e8f9',
|
||||
dark: '#0891b2',
|
||||
} as const;
|
||||
|
||||
// Semantic
|
||||
export const semantic = {
|
||||
error: '#f87171',
|
||||
warning: '#fbbf24',
|
||||
success: '#4ade80',
|
||||
info: '#38bdf8',
|
||||
} as const;
|
||||
|
||||
// Surfaces
|
||||
export const surface = {
|
||||
base: '#09090b',
|
||||
raised: '#18181b',
|
||||
raisedAlpha: 'rgba(24, 24, 27, 0.6)',
|
||||
raisedSolid: 'rgba(24, 24, 27, 0.95)',
|
||||
overlay: 'rgba(0, 0, 0, 0.6)',
|
||||
appBar: 'rgba(9, 9, 11, 0.8)',
|
||||
} as const;
|
||||
|
||||
// Text hierarchy (lightest → dimmest)
|
||||
export const text = {
|
||||
primary: '#fafafa',
|
||||
secondary: '#d4d4d8',
|
||||
tertiary: '#a1a1aa',
|
||||
muted: '#71717a',
|
||||
dim: '#52525b',
|
||||
faint: '#3f3f46',
|
||||
} as const;
|
||||
|
||||
// Borders & dividers
|
||||
export const border = {
|
||||
subtle: 'rgba(255, 255, 255, 0.04)',
|
||||
default: 'rgba(255, 255, 255, 0.06)',
|
||||
medium: 'rgba(255, 255, 255, 0.08)',
|
||||
strong: 'rgba(255, 255, 255, 0.1)',
|
||||
primarySubtle: 'rgba(167, 139, 250, 0.1)',
|
||||
primaryDefault: 'rgba(167, 139, 250, 0.15)',
|
||||
primaryMedium: 'rgba(167, 139, 250, 0.2)',
|
||||
primaryStrong: 'rgba(167, 139, 250, 0.3)',
|
||||
} as const;
|
||||
|
||||
// Interactive surface fills
|
||||
export const fill = {
|
||||
subtle: 'rgba(255, 255, 255, 0.03)',
|
||||
default: 'rgba(255, 255, 255, 0.04)',
|
||||
hover: 'rgba(255, 255, 255, 0.06)',
|
||||
primaryHover: 'rgba(167, 139, 250, 0.06)',
|
||||
primaryActive: 'rgba(167, 139, 250, 0.08)',
|
||||
primaryStrong: 'rgba(167, 139, 250, 0.1)',
|
||||
} as const;
|
||||
|
||||
/* ─── Gradient presets ────────────────────────────────────────────── */
|
||||
|
||||
export const gradients = {
|
||||
brand: `linear-gradient(135deg, ${primary.dark}, ${secondary.main})`,
|
||||
button: `linear-gradient(135deg, ${primary.dark} 0%, ${primary.main} 100%)`,
|
||||
buttonHover: `linear-gradient(135deg, ${primary.darker} 0%, ${primary.mid} 100%)`,
|
||||
} as const;
|
||||
|
||||
/* ─── Glow / shadow presets ───────────────────────────────────────── */
|
||||
|
||||
export const glow = {
|
||||
brand: `0 0 12px rgba(124, 58, 237, 0.5)`,
|
||||
button: `0 0 20px rgba(124, 58, 237, 0.3)`,
|
||||
} as const;
|
||||
@@ -1,99 +1,220 @@
|
||||
import { createTheme } from '@mui/material';
|
||||
import { primary, secondary, semantic, surface, text, border, fill, gradients, glow } from './colors';
|
||||
|
||||
export const createAppTheme = () => createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
main: '#9580ff', // Purple instead of blue
|
||||
main: primary.main,
|
||||
light: primary.light,
|
||||
dark: primary.dark,
|
||||
},
|
||||
secondary: {
|
||||
main: '#80ffea', // Teal/cyan
|
||||
main: secondary.main,
|
||||
light: secondary.light,
|
||||
dark: secondary.dark,
|
||||
},
|
||||
background: {
|
||||
paper: '#1e1e1e', // Darker paper background
|
||||
default: '#121212', // Very dark background
|
||||
},
|
||||
error: {
|
||||
main: '#ff80bf', // Pink-ish error
|
||||
},
|
||||
warning: {
|
||||
main: '#ffca80', // Soft orange
|
||||
},
|
||||
success: {
|
||||
main: '#8aff80', // Light green
|
||||
},
|
||||
info: {
|
||||
main: '#80ffea', // Teal
|
||||
default: surface.base,
|
||||
paper: surface.raised,
|
||||
},
|
||||
error: { main: semantic.error },
|
||||
warning: { main: semantic.warning },
|
||||
success: { main: semantic.success },
|
||||
info: { main: semantic.info },
|
||||
text: {
|
||||
primary: '#ffffff',
|
||||
secondary: 'rgba(255, 255, 255, 0.7)',
|
||||
primary: text.primary,
|
||||
secondary: text.tertiary,
|
||||
},
|
||||
divider: border.default,
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 12,
|
||||
},
|
||||
typography: {
|
||||
fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
|
||||
fontSize: 14,
|
||||
h4: { fontWeight: 700, letterSpacing: '-0.02em' },
|
||||
h5: { fontWeight: 700, letterSpacing: '-0.02em' },
|
||||
h6: { fontWeight: 700, letterSpacing: '-0.01em' },
|
||||
},
|
||||
components: {
|
||||
MuiCard: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: '#1e1e1e', // Slightly lighter than default background
|
||||
backgroundImage: 'none', // Remove any gradient
|
||||
boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)', // Stronger shadow for depth
|
||||
body: {
|
||||
backgroundColor: surface.base,
|
||||
backgroundImage: `radial-gradient(ellipse 80% 50% at 50% -20%, rgba(120, 60, 255, 0.15), transparent)`,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAppBar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: '#272727', // Slightly lighter app bar
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAccordion: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: '#1e1e1e',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: surface.appBar,
|
||||
backdropFilter: 'blur(16px)',
|
||||
boxShadow: 'none',
|
||||
borderBottom: `1px solid ${border.default}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiPaper: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundImage: 'none', // Remove any gradient
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: surface.raisedAlpha,
|
||||
backdropFilter: 'blur(12px)',
|
||||
border: `1px solid ${border.default}`,
|
||||
},
|
||||
outlined: {
|
||||
borderColor: 'rgba(255, 255, 255, 0.12)',
|
||||
},
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: surface.raisedAlpha,
|
||||
backdropFilter: 'blur(12px)',
|
||||
border: `1px solid ${border.default}`,
|
||||
boxShadow: 'none',
|
||||
transition: 'border-color 0.15s ease',
|
||||
'&:hover': {
|
||||
borderColor: border.primaryDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiDialog: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
backgroundColor: '#1e1e1e',
|
||||
backgroundColor: surface.raisedSolid,
|
||||
backdropFilter: 'blur(20px)',
|
||||
border: `1px solid ${border.default}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTableContainer: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundImage: 'none',
|
||||
borderRadius: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTableHead: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiTableCell-head': {
|
||||
backgroundColor: fill.subtle,
|
||||
color: text.tertiary,
|
||||
fontWeight: 600,
|
||||
fontSize: '0.75rem',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
borderBottom: `1px solid ${border.default}`,
|
||||
padding: '12px 16px',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTableBody: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiTableRow-root': {
|
||||
transition: 'background-color 0.15s ease',
|
||||
'&:hover': {
|
||||
backgroundColor: fill.primaryHover,
|
||||
},
|
||||
},
|
||||
'& .MuiTableCell-body': {
|
||||
borderBottom: `1px solid ${border.subtle}`,
|
||||
padding: '14px 16px',
|
||||
fontSize: '0.875rem',
|
||||
color: text.secondary,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: 'none',
|
||||
fontWeight: 500,
|
||||
fontSize: '0.875rem',
|
||||
minHeight: 44,
|
||||
'&.Mui-selected': { color: primary.main },
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTabs: {
|
||||
styleOverrides: {
|
||||
indicator: {
|
||||
backgroundColor: primary.main,
|
||||
height: 2,
|
||||
borderRadius: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: 'none',
|
||||
fontWeight: 600,
|
||||
borderRadius: 8,
|
||||
},
|
||||
contained: {
|
||||
boxShadow: 'none',
|
||||
background: gradients.button,
|
||||
'&:hover': {
|
||||
boxShadow: glow.button,
|
||||
background: gradients.buttonHover,
|
||||
},
|
||||
},
|
||||
outlined: {
|
||||
borderColor: border.primaryStrong,
|
||||
color: primary.main,
|
||||
'&:hover': {
|
||||
borderColor: primary.main,
|
||||
backgroundColor: fill.primaryActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTextField: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: 8,
|
||||
'& fieldset': { borderColor: border.strong },
|
||||
'&:hover fieldset': { borderColor: border.primaryStrong },
|
||||
'&.Mui-focused fieldset': { borderColor: primary.main },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontWeight: 600,
|
||||
fontSize: '0.75rem',
|
||||
borderRadius: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAccordion: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: surface.raisedAlpha,
|
||||
border: `1px solid ${border.default}`,
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiDivider: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderColor: 'rgba(255, 255, 255, 0.12)',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
contained: {
|
||||
boxShadow: '0 3px 5px 2px rgba(0, 0, 0, .3)',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiContainer: {
|
||||
styleOverrides: {
|
||||
maxWidthXl: {
|
||||
maxWidth: '100% !important', // Make sure container is full width
|
||||
paddingLeft: '24px',
|
||||
paddingRight: '24px',
|
||||
borderColor: border.default,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user