Polish UI, harden company creation, and add error pages

This commit is contained in:
cesnimda
2026-03-23 19:34:29 +01:00
parent 8f5eab2fe4
commit fcafda6f52
38 changed files with 2293 additions and 1269 deletions
+65 -38
View File
@@ -12,7 +12,6 @@ import {
Select,
Tab,
Tabs,
TextField,
Typography,
} from "@mui/material";
@@ -22,8 +21,8 @@ import GoogleAuthCard from "./GoogleAuthCard";
import RulesSettingsCard from "./RulesSettingsCard";
import BackupCard from "./BackupCard";
import AuthStatusCard from "./AuthStatusCard";
export type ThemeModePref = "system" | "light" | "dark";
import { ThemeModePref } from "../themePrefs";
import { useI18n } from "../i18n/I18nProvider";
interface Props {
pageSize: 15 | 20 | 25;
@@ -42,7 +41,7 @@ function TabPanel({ value, index, children }: { value: number; index: number; ch
return <Box sx={{ mt: 2 }}>{children}</Box>;
}
const ACCENTS = ["#7c4dff", "#4f8cff", "#16a34a", "#f59e0b", "#e11d48", "#06b6d4"]; // violet, blue, green, amber, rose, cyan
const ACCENTS = ["#15803d", "#16a34a", "#22c55e", "#0f766e", "#0f766e", "#65a30d"];
export default function SettingsView({
pageSize,
@@ -56,56 +55,60 @@ export default function SettingsView({
onResetAccentColor,
}: Props) {
const [tab, setTab] = useState(0);
const { language, setLanguage, t } = useI18n();
const accentOk = useMemo(() => /^#[0-9a-fA-F]{6}$/.test(accentColor), [accentColor]);
return (
<Paper sx={{ mt: 0, p: 2 }}>
<Typography variant="h5" sx={{ mb: 1, fontWeight: 900 }}>
Settings
{t("settingsTitle")}
</Typography>
<Typography sx={{ color: "text.secondary", mb: 2 }}>
Preferences and admin tools.
{t("settingsSubtitle")}
</Typography>
<Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ mb: 1 }}>
<Tab label="General" />
<Tab label="Follow-ups" />
<Tab label="Notifications" />
<Tab label="Account" />
<Tab label="Backup" />
<Tab label={t("settingsTabGeneral")} />
<Tab label={t("settingsTabFollowUps")} />
<Tab label={t("settingsTabNotifications")} />
<Tab label={t("settingsTabAccount")} />
<Tab label={t("settingsTabBackup")} />
</Tabs>
<TabPanel value={tab} index={0}>
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2 }}>
<Paper sx={{ p: 2 }}>
<Typography sx={{ fontWeight: 950, mb: 1 }}>Appearance</Typography>
<Typography sx={{ fontWeight: 950, mb: 1 }}>{t("settingsAppearance")}</Typography>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel id="theme-mode-label">Theme</InputLabel>
<InputLabel id="theme-mode-label">{t("settingsTheme")}</InputLabel>
<Select
labelId="theme-mode-label"
value={themeMode}
label="Theme"
label={t("settingsTheme")}
onChange={(e) => onThemeModeChange(e.target.value as ThemeModePref)}
>
<MenuItem value="system">System</MenuItem>
<MenuItem value="dark">Dark</MenuItem>
<MenuItem value="light">Light</MenuItem>
<MenuItem value="system">{t("settingsThemeSystem")}</MenuItem>
<MenuItem value="dark">{t("settingsThemeDark")}</MenuItem>
<MenuItem value="light">{t("settingsThemeLight")}</MenuItem>
</Select>
</FormControl>
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, flexWrap: "wrap" }}>
<TextField
label="Accent"
type="color"
value={accentOk ? accentColor : "#7c4dff"}
onChange={(e) => onAccentColorChange(e.target.value)}
sx={{ width: 160 }}
InputLabelProps={{ shrink: true }}
/>
<input type="hidden" />
<FormControl sx={{ width: 160 }}>
<Typography variant="caption" sx={{ mb: 0.5 }}>{t("settingsAccent")}</Typography>
<input
aria-label={t("settingsAccent")}
type="color"
value={accentOk ? accentColor : "#15803d"}
onChange={(e) => onAccentColorChange(e.target.value)}
style={{ width: 160, height: 40, border: "none", background: "transparent", padding: 0 }}
/>
</FormControl>
<Button variant="outlined" onClick={onResetAccentColor}>
Reset
{t("settingsReset")}
</Button>
</Box>
@@ -129,24 +132,48 @@ export default function SettingsView({
</Box>
<Typography variant="caption" sx={{ color: "text.secondary", display: "block", mt: 1 }}>
Saved per user on this browser.
{t("settingsSavedPerUser")}
</Typography>
</Paper>
<Paper sx={{ p: 2 }}>
<Typography sx={{ fontWeight: 950, mb: 1 }}>Jobs</Typography>
<Typography sx={{ fontWeight: 950, mb: 1 }}>{t("settingsLanguageTitle")}</Typography>
<Typography variant="body2" sx={{ color: "text.secondary", mb: 2 }}>
{t("settingsLanguageBody")}
</Typography>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel id="language-label">{t("settingsPreferredLanguage")}</InputLabel>
<Select
labelId="language-label"
value={language}
label={t("settingsPreferredLanguage")}
onChange={(e) => setLanguage(e.target.value as "en" | "no")}
>
<MenuItem value="en">{t("settingsEnglish")}</MenuItem>
<MenuItem value="no">{t("settingsNorwegian")}</MenuItem>
</Select>
</FormControl>
<Typography variant="caption" sx={{ color: "text.secondary" }}>
{t("settingsMorePagesSoon")}
</Typography>
</Paper>
<Paper sx={{ p: 2, gridColumn: { xs: "1 / -1", md: "1 / -1" } }}>
<Typography sx={{ fontWeight: 950, mb: 1 }}>{t("settingsJobs")}</Typography>
<Box sx={{ display: "flex", gap: 3, flexWrap: "wrap" }}>
<Box sx={{ minWidth: 240 }}>
<Typography variant="h6" sx={{ mb: 1 }}>
Pagination
{t("settingsPagination")}
</Typography>
<FormControl fullWidth>
<InputLabel id="page-size-label">Rows per page</InputLabel>
<InputLabel id="page-size-label">{t("settingsRowsPerPage")}</InputLabel>
<Select
labelId="page-size-label"
value={pageSize}
label="Rows per page"
label={t("settingsRowsPerPage")}
onChange={(e) => onPageSizeChange(e.target.value as 15 | 20 | 25)}
>
<MenuItem value={15}>15</MenuItem>
@@ -158,14 +185,14 @@ export default function SettingsView({
<Box sx={{ minWidth: 240 }}>
<Typography variant="h6" sx={{ mb: 1 }}>
Columns
{t("settingsColumns")}
</Typography>
{(
[
["status", "Status"],
["dateApplied", "Date applied"],
["daysSince", "Days"],
["jobUrl", "Job URL"],
["status", t("settingsColumnStatus")],
["dateApplied", t("settingsColumnDateApplied")],
["daysSince", t("settingsColumnDays")],
["jobUrl", t("settingsColumnJobUrl")],
] as const
).map(([key, label]) => (
<FormControlLabel
@@ -193,9 +220,9 @@ export default function SettingsView({
<TabPanel value={tab} index={2}>
<Paper sx={{ p: 2, mt: 2 }}>
<Typography sx={{ fontWeight: 950, mb: 0.5 }}>Email notifications</Typography>
<Typography sx={{ fontWeight: 950, mb: 0.5 }}>{t("settingsNotificationsTitle")}</Typography>
<Typography sx={{ color: "text.secondary" }}>
Notifications are sent via SMTP (Gmail works). Configure SMTP in the API (`Email:*` settings or env vars like `EMAIL_SMTP_HOST`).
{t("settingsNotificationsBody")}
</Typography>
</Paper>
</TabPanel>