finalized UI and website for 1.0 release

This commit is contained in:
fuomag9
2026-01-15 01:16:25 +01:00
parent d3b77a394e
commit 85c7a0f8c7
19 changed files with 1743 additions and 1632 deletions
+77
View File
@@ -0,0 +1,77 @@
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
IconButton,
Typography,
Button
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { ReactNode } from "react";
type AppDialogProps = {
open: boolean;
onClose: () => void;
title: string;
children: ReactNode;
maxWidth?: "xs" | "sm" | "md" | "lg" | "xl";
actions?: ReactNode;
submitLabel?: string;
onSubmit?: () => void;
isSubmitting?: boolean;
};
export function AppDialog({
open,
onClose,
title,
children,
maxWidth = "sm",
actions,
submitLabel = "Save",
onSubmit,
isSubmitting = false
}: AppDialogProps) {
return (
<Dialog
open={open}
onClose={onClose}
maxWidth={maxWidth}
fullWidth
PaperProps={{
elevation: 0,
variant: "outlined"
}}
>
<DialogTitle sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="h6">{title}</Typography>
<IconButton onClick={onClose} size="small" sx={{ color: "text.secondary" }}>
<CloseIcon fontSize="small" />
</IconButton>
</DialogTitle>
<DialogContent dividers>
{children}
</DialogContent>
<DialogActions sx={{ p: 2 }}>
{actions ? actions : (
<>
<Button onClick={onClose} color="inherit">
Cancel
</Button>
{onSubmit && (
<Button
onClick={onSubmit}
variant="contained"
disabled={isSubmitting}
>
{submitLabel}
</Button>
)}
</>
)}
</DialogActions>
</Dialog>
);
}
+76
View File
@@ -0,0 +1,76 @@
import {
Card,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
Box
} from "@mui/material";
import { ReactNode } from "react";
type Column<T> = {
id: string;
label: string;
align?: "left" | "right" | "center";
width?: string | number;
render?: (row: T) => ReactNode;
};
type DataTableProps<T> = {
columns: Column<T>[];
data: T[];
keyField: keyof T;
emptyMessage?: string;
loading?: boolean;
};
export function DataTable<T>({
columns,
data,
keyField,
emptyMessage = "No data available",
loading = false
}: DataTableProps<T>) {
return (
<TableContainer component={Card} variant="outlined">
<Table>
<TableHead>
<TableRow>
{columns.map((col) => (
<TableCell
key={col.id}
align={col.align || "left"}
width={col.width}
>
{col.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{data.length === 0 && !loading ? (
<TableRow>
<TableCell colSpan={columns.length} align="center" sx={{ py: 8 }}>
<Typography color="text.secondary">{emptyMessage}</Typography>
</TableCell>
</TableRow>
) : (
data.map((row) => (
<TableRow key={String(row[keyField])}>
{columns.map((col) => (
<TableCell key={col.id} align={col.align || "left"}>
{col.render ? col.render(row) : (row as any)[col.id]}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
);
}
+42
View File
@@ -0,0 +1,42 @@
import { Box, Button, Stack, Typography } from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import { ReactNode } from "react";
type PageHeaderProps = {
title: string;
description?: string;
action?: {
label: string;
onClick: () => void;
icon?: ReactNode;
};
};
export function PageHeader({ title, description, action }: PageHeaderProps) {
return (
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={2} sx={{ mb: 4 }}>
<Stack spacing={1}>
<Typography variant="h4" color="text.primary">
{title}
</Typography>
{description && (
<Typography variant="body1" color="text.secondary" sx={{ maxWidth: 600 }}>
{description}
</Typography>
)}
</Stack>
{action && (
<Button
variant="contained"
startIcon={action.icon ?? <AddIcon />}
onClick={action.onClick}
color="primary"
sx={{ fontWeight: 600 }}
>
{action.label}
</Button>
)}
</Stack>
);
}
+37
View File
@@ -0,0 +1,37 @@
import { InputAdornment, TextField, TextFieldProps } from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
export function SearchField(props: TextFieldProps) {
return (
<TextField
placeholder="Search..."
variant="outlined"
size="small"
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">
<SearchIcon sx={{ color: "text.secondary" }} />
</InputAdornment>
)
}
}}
sx={{
maxWidth: 400,
"& .MuiOutlinedInput-root": {
bgcolor: "background.paper",
transition: "all 0.2s",
"&:hover": {
bgcolor: "action.hover"
},
"&.Mui-focused": {
bgcolor: "background.paper",
boxShadow: "0 4px 20px rgba(0,0,0,0.2)"
}
}
}}
{...props}
/>
);
}
+58
View File
@@ -0,0 +1,58 @@
import { Box, Typography, ChipProps } from "@mui/material";
type StatusType = "active" | "inactive" | "error" | "warning";
type StatusChipProps = {
status: StatusType;
label?: string;
sx?: any;
};
const STATUS_CONFIG: Record<StatusType, { color: string; label: string }> = {
active: { color: "#22c55e", label: "Active" }, // Green-500
inactive: { color: "#71717a", label: "Paused" }, // Zinc-500
error: { color: "#ef4444", label: "Error" }, // Red-500
warning: { color: "#f59e0b", label: "Warning" } // Amber-500
};
export function StatusChip({ status, label, sx }: StatusChipProps) {
const config = STATUS_CONFIG[status];
const displayLabel = label || config.label;
return (
<Box
sx={{
display: "inline-flex",
alignItems: "center",
gap: 1,
px: 1.5,
py: 0.5,
borderRadius: "9999px",
bgcolor: "rgba(255,255,255,0.03)",
border: "1px solid rgba(255,255,255,0.08)",
...sx
}}
>
<Box
sx={{
width: 8,
height: 8,
borderRadius: "50%",
bgcolor: config.color,
boxShadow: `0 0 8px ${config.color}66`
}}
/>
<Typography
variant="caption"
sx={{
fontWeight: 600,
color: "text.primary",
lineHeight: 1
}}
>
{displayLabel}
</Typography>
</Box>
);
}