From 33d899c2433429fad3d4e7a2815a6e6fd5452809 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sun, 12 Jul 2026 02:43:10 +0200 Subject: [PATCH] fix(auth): Google Sign-In audience mismatch + remove per-user accent color Root cause of "Google authentication failed": appsettings.Development.json had Auth:GoogleClientId set to the literal placeholder "CHANGE_ME_GOOGLE_CLIENT_ID" while the frontend's .env.development had a real (already-public, already-committed) client ID -- every Google ID token's audience check failed against the backend's placeholder. Fixed by setting the same real client ID on both sides (a client ID is a public identifier, not a secret, safe to commit -- unlike a client secret). Also enabled Auth:AllowRegistration in dev so the existing Google-first self-serve-signup path (auto-create on unmatched verified email, auto-link on matching verified email -- built during Wave 7) is actually exercisable locally. Wired the previously-missing Auth__MicrosoftClientId / NEXT_PUBLIC_MICROSOFT_CLIENT_ID into docker-compose.yml/.env.example (distinct from the existing MICROSOFT_CLIENT_ID used for Outlook mail linking) -- Microsoft sign-in was never deployable, a leftover gap from when it was built. Fixed a stale env-var name in the Microsoft setup hint copy (still said REACT_APP_*, predates the Next.js migration). Removed the per-user accent color picker entirely: it was purely client-side (localStorage + theme.ts), never touched the backend/DB. theme.ts now hardcodes a single ACCENT constant; themePrefs.ts drops get/set/clearAccentColor; App.tsx and SettingsView.tsx drop the accentColor prop threading. Dead accent-related i18n keys removed from both locales. Consolidated Settings' "Account" tab (duplicated GoogleAuthCard, which already lives on the Profile page) into Profile: moved AuthStatusCard and EmailProviderConnections there alongside the existing Google/ Microsoft auth cards, so identity/account-linking lives in one place. Settings drops from 5 tabs to 4 and its General tab uses a consistent SectionCard layout instead of ad-hoc per-card styling. Verified: dotnet build/test (177/177) and npm build/test (57/57) both green; confirmed live against a running dev server that /auth/config now reports googleEnabled with the corrected client ID, Settings has no accent controls, and Profile shows the consolidated auth section. --- .env.example | 3 + JobTrackerApi/appsettings.Development.json | 4 +- docker-compose.yml | 4 +- job-tracker-ui/Dockerfile | 2 + job-tracker-ui/src/App.tsx | 19 +- .../src/components/SettingsView.tsx | 223 +++++------------- job-tracker-ui/src/i18n/translations.ts | 18 +- job-tracker-ui/src/settings-view.test.tsx | 55 ++--- job-tracker-ui/src/theme.ts | 22 +- job-tracker-ui/src/themePrefs.ts | 14 -- job-tracker-ui/src/views/ProfilePage.tsx | 6 + 11 files changed, 115 insertions(+), 255 deletions(-) diff --git a/.env.example b/.env.example index eff4d95..d07ead5 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,9 @@ AUTH_JWT_KEY=CHANGE_ME_LONG_RANDOM_SECRET AUTH_ADMIN_EMAIL=admin@example.com AUTH_ADMIN_PASSWORD=CHANGE_ME_STRONG_PASSWORD AUTH_GOOGLE_CLIENT_ID=CHANGE_ME_GOOGLE_CLIENT_ID +# Optional: enables the "Continue with Microsoft" sign-in tab (separate from the +# MICROSOFT_CLIENT_ID below, which is for Outlook mail linking, not sign-in). +AUTH_MICROSOFT_CLIENT_ID= GOOGLE_GMAIL_CLIENT_SECRET=CHANGE_ME_GOOGLE_OAUTH_CLIENT_SECRET # Optional. If omitted, the backend uses https:///api/gmail/oauth/callback GOOGLE_GMAIL_REDIRECT_URI= diff --git a/JobTrackerApi/appsettings.Development.json b/JobTrackerApi/appsettings.Development.json index 95ab79f..43d7dcd 100644 --- a/JobTrackerApi/appsettings.Development.json +++ b/JobTrackerApi/appsettings.Development.json @@ -19,14 +19,14 @@ }, "Auth": { "Require": true, - "AllowRegistration": false, + "AllowRegistration": true, "JwtKey": "CHANGE_ME_DEV_ONLY_LONG_RANDOM_SECRET", "JwtIssuer": "JobTrackerApi", "JwtAudience": "job-tracker-ui", "JwtExpiresMinutes": 720, "AdminEmail": "admin@example.com", "AdminPassword": "CHANGE_ME_STRONG_DEV_PASSWORD", - "GoogleClientId": "CHANGE_ME_GOOGLE_CLIENT_ID", + "GoogleClientId": "723556162227-llqucvpog2esn1dutmtvuul1lv374or6.apps.googleusercontent.com", "MicrosoftClientId": "CHANGE_ME_MICROSOFT_CLIENT_ID" }, "App": { diff --git a/docker-compose.yml b/docker-compose.yml index 4855114..738e862 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,8 +19,9 @@ services: - Auth__JwtKey=${AUTH_JWT_KEY} - Auth__AdminEmail=${AUTH_ADMIN_EMAIL} - Auth__AdminPassword=${AUTH_ADMIN_PASSWORD} - # Optional: allow Google ID-token bearer auth + # Optional: allow Google / Microsoft ID-token bearer auth (sign-in, not mail access) - Auth__GoogleClientId=${AUTH_GOOGLE_CLIENT_ID} + - Auth__MicrosoftClientId=${AUTH_MICROSOFT_CLIENT_ID} - Google__GmailClientSecret=${GOOGLE_GMAIL_CLIENT_SECRET} - Google__GmailRedirectUri=${GOOGLE_GMAIL_REDIRECT_URI} # Optional: Outlook / Microsoft 365 mail linking via Microsoft Graph @@ -64,6 +65,7 @@ services: shm_size: '1gb' args: - NEXT_PUBLIC_GOOGLE_CLIENT_ID=${AUTH_GOOGLE_CLIENT_ID} + - NEXT_PUBLIC_MICROSOFT_CLIENT_ID=${AUTH_MICROSOFT_CLIENT_ID} # Optional override; default in production is `/api` - NEXT_PUBLIC_API_BASE_URL=${REACT_APP_API_BASE_URL} ports: diff --git a/job-tracker-ui/Dockerfile b/job-tracker-ui/Dockerfile index be7ac1b..28e0361 100644 --- a/job-tracker-ui/Dockerfile +++ b/job-tracker-ui/Dockerfile @@ -3,9 +3,11 @@ FROM node:20-alpine AS build WORKDIR /app ARG NEXT_PUBLIC_GOOGLE_CLIENT_ID +ARG NEXT_PUBLIC_MICROSOFT_CLIENT_ID ARG NEXT_PUBLIC_API_BASE_URL ENV NEXT_PUBLIC_GOOGLE_CLIENT_ID=$NEXT_PUBLIC_GOOGLE_CLIENT_ID +ENV NEXT_PUBLIC_MICROSOFT_CLIENT_ID=$NEXT_PUBLIC_MICROSOFT_CLIENT_ID ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL COPY package*.json .npmrc ./ diff --git a/job-tracker-ui/src/App.tsx b/job-tracker-ui/src/App.tsx index 8454325..855155d 100644 --- a/job-tracker-ui/src/App.tsx +++ b/job-tracker-ui/src/App.tsx @@ -36,7 +36,7 @@ import { api } from "./api"; import { resolveCaptureUrl } from "./captureUrl"; import { clearAuthClientState, setAuthUserKey } from "./auth"; import AppShell, { NavItem } from "./layout/AppShell"; -import { clearAccentColor, getAccentColor, getThemeModePref, setAccentColor, setThemeModePref, ThemeModePref } from "./themePrefs"; +import { getThemeModePref, setThemeModePref, ThemeModePref } from "./themePrefs"; const AddJobModal = lazy(() => import("./components/AddJobModal")); const KanbanBoard = lazy(() => import("./components/KanbanBoard")); @@ -104,7 +104,7 @@ function PageLoader() { return Loading...; } -function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMode, onThemeModeChange, accentColor, onAccentColorChange, onResetAccentColor }: { jobPageSize: 15 | 20 | 25; setJobPageSize: (n: 15 | 20 | 25) => void; jobColumns: JobTableColumns; setJobColumns: (c: JobTableColumns) => void; themeMode: ThemeModePref; onThemeModeChange: (v: ThemeModePref) => void; accentColor: string; onAccentColorChange: (v: string) => void; onResetAccentColor: () => void; }) { +function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMode, onThemeModeChange }: { jobPageSize: 15 | 20 | 25; setJobPageSize: (n: 15 | 20 | 25) => void; jobColumns: JobTableColumns; setJobColumns: (c: JobTableColumns) => void; themeMode: ThemeModePref; onThemeModeChange: (v: ThemeModePref) => void; }) { const location = useLocation(); const navigate = useNavigate(); const { t } = useI18n(); @@ -297,7 +297,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo } /> } /> } /> - } /> + } /> } /> @@ -314,19 +314,16 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo export default function App() { const systemPrefersDark = useMediaQuery("(prefers-color-scheme: dark)", { defaultMatches: true, noSsr: true }); const [themeMode, setThemeMode] = useState(() => getThemeModePref()); - const [accentColor, setAccentColorState] = useState(() => getAccentColor()); const effectiveMode: "light" | "dark" = themeMode === "light" ? "light" : themeMode === "dark" ? "dark" : systemPrefersDark ? "dark" : "light"; - const theme = useMemo(() => getTheme(effectiveMode, accentColor), [effectiveMode, accentColor]); + const theme = useMemo(() => getTheme(effectiveMode), [effectiveMode]); useEffect(() => { - const sync = () => { setThemeMode(getThemeModePref()); setAccentColorState(getAccentColor()); }; + const sync = () => { setThemeMode(getThemeModePref()); }; window.addEventListener("auth-changed", sync); return () => window.removeEventListener("auth-changed", sync); }, []); const onThemeModeChange = (v: ThemeModePref) => { setThemeModePref(v); setThemeMode(v); }; - const onAccentColorChange = (v: string) => { setAccentColor(v); setAccentColorState(getAccentColor()); }; - const onResetAccentColor = () => { clearAccentColor(); setAccentColorState(getAccentColor()); }; const [jobPageSize, setJobPageSize] = useState<15 | 20 | 25>(() => { const raw = window.localStorage.getItem("jobPageSize"); @@ -349,14 +346,14 @@ export default function App() { { path: "/login", element: , errorElement: }, { path: "/forgot-password", element: , errorElement: }, { path: "/reset-password", element: , errorElement: }, - { path: "/*", element: , errorElement: }, - ], { future: { v7_relativeSplatPath: true } }), [jobColumns, jobPageSize, themeMode, accentColor]); + { path: "/*", element: , errorElement: }, + ], { future: { v7_relativeSplatPath: true } }), [jobColumns, jobPageSize, themeMode]); return ( - + diff --git a/job-tracker-ui/src/components/SettingsView.tsx b/job-tracker-ui/src/components/SettingsView.tsx index b9187cc..2f19cc8 100644 --- a/job-tracker-ui/src/components/SettingsView.tsx +++ b/job-tracker-ui/src/components/SettingsView.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useState } from "react"; import { Box, @@ -9,11 +9,9 @@ import { InputLabel, MenuItem, Paper, - Popover, Select, Tab, Tabs, - TextField, Typography, } from "@mui/material"; @@ -21,12 +19,9 @@ import { useNavigate } from "react-router-dom"; import { JobTableColumns } from "./JobTable"; import ImportExportJobs from "./ImportExportJobs"; -import GoogleAuthCard from "./GoogleAuthCard"; -import EmailProviderConnections from "./EmailProviderConnections"; import RulesSettingsCard from "./RulesSettingsCard"; import BackupCard from "./BackupCard"; import QuickCaptureCard from "./QuickCaptureCard"; -import AuthStatusCard from "./AuthStatusCard"; import { ThemeModePref } from "../themePrefs"; import { useI18n } from "../i18n/I18nProvider"; @@ -37,17 +32,23 @@ interface Props { onColumnsChange: (next: JobTableColumns) => void; themeMode: ThemeModePref; onThemeModeChange: (v: ThemeModePref) => void; - accentColor: string; - onAccentColorChange: (v: string) => void; - onResetAccentColor: () => void; } function TabPanel({ value, index, children }: { value: number; index: number; children: React.ReactNode }) { if (value !== index) return null; - return {children}; + return {children}; +} + +function SectionCard({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) { + return ( + + {title} + {subtitle ? {subtitle} : } + {children} + + ); } -const ACCENTS = ["#6366f1", "#22d3ee", "#2563eb", "#8b5cf6", "#15803d", "#16a34a", "#0f766e", "#f97316"]; const NOTIFICATION_PREFS_KEY = "settings.notificationPrefs"; type NotificationPrefs = { @@ -88,43 +89,19 @@ export default function SettingsView({ onColumnsChange, themeMode, onThemeModeChange, - accentColor, - onAccentColorChange, - onResetAccentColor, }: Props) { const navigate = useNavigate(); const [tab, setTab] = useState(0); const { language, setLanguage, t } = useI18n(); - const [accentAnchor, setAccentAnchor] = useState(null); - const [accentDraft, setAccentDraft] = useState(accentColor); const [notificationPrefs, setNotificationPrefs] = useState(() => loadNotificationPrefs()); - const accentOk = useMemo(() => /^#[0-9a-fA-F]{6}$/.test(accentColor), [accentColor]); - const accentDraftOk = useMemo(() => /^#[0-9a-fA-F]{6}$/.test(accentDraft), [accentDraft]); - - useEffect(() => { - setAccentDraft(accentOk ? accentColor : "#15803d"); - }, [accentColor, accentOk]); - useEffect(() => { window.localStorage.setItem(NOTIFICATION_PREFS_KEY, JSON.stringify(notificationPrefs)); }, [notificationPrefs]); - const applyAccent = () => { - if (!accentDraftOk) return; - onAccentColorChange(accentDraft); - setAccentAnchor(null); - }; - - const resetAccent = () => { - onResetAccentColor(); - setAccentDraft("#15803d"); - setAccentAnchor(null); - }; - return ( - - + + {t("settingsTitle")} @@ -135,130 +112,48 @@ export default function SettingsView({ - - - - {t("settingsAppearance")} - - - {t("settingsTheme")} - - - - - - {t("settingsAccent")} - - - - - + + + {t("settingsPreferredLanguage")} + + + + - - {t("settingsSavedPerUser")} - - - - - {t("settingsLanguageTitle")} - - {t("settingsLanguageBody")} - - - - {t("settingsPreferredLanguage")} - - - - - {t("settingsMorePagesSoon")} - - - - - {t("settingsJobs")} - - + + - + {t("settingsPagination")} @@ -277,7 +172,7 @@ export default function SettingsView({ - + {t("settingsColumns")} {( @@ -297,8 +192,10 @@ export default function SettingsView({ - - + + + + @@ -309,9 +206,7 @@ export default function SettingsView({ - - {t("settingsNotificationsTitle")} - {t("settingsNotificationsBody")} + setNotificationPrefs((prev) => ({ ...prev, emailFollowUpReminders: e.target.checked }))} />} @@ -333,18 +228,10 @@ export default function SettingsView({ - + - - - - - - - - diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index c47a091..84c091f 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -128,22 +128,17 @@ export const translations = { settingsTabGeneral: "General", settingsTabFollowUps: "Follow-ups", settingsTabNotifications: "Notifications", - settingsTabAccount: "Account", settingsTabBackup: "Backup", settingsAppearance: "Appearance", settingsTheme: "Theme", settingsThemeSystem: "System", settingsThemeDark: "Dark", settingsThemeLight: "Light", - settingsAccent: "Accent", - settingsReset: "Reset", - settingsSavedPerUser: "Saved per user on this browser.", settingsLanguageTitle: "Language and localization", settingsLanguageBody: "Set your preferred app language. This is also the language used when deciding whether imported job descriptions should show translated text.", settingsPreferredLanguage: "Preferred language", settingsEnglish: "English", settingsNorwegian: "Norwegian Bokmål", - settingsMorePagesSoon: "More pages will be moved onto this translation system as the UI cleanup continues.", settingsJobs: "Jobs", settingsPagination: "Pagination", settingsRowsPerPage: "Rows per page", @@ -167,8 +162,6 @@ export const translations = { settingsNotificationsFollowUpReminders: "Email reminders for follow-ups", settingsNotificationsGhostedJobs: "Email alerts for ghosted jobs", settingsNotificationsInAppReminders: "Highlight reminders in the app", - settingsAccentHelp: "Drag in the color picker, then save when it looks right.", - settingsAccentInvalid: "Use a full hex color like #15803D.", settingsCheckSystemStatus: "Check system status", profileTitle: "Profile", profileHeadlinePlaceholder: "Add a short headline to personalize your account view.", @@ -629,7 +622,7 @@ export const translations = { googleUnlinked: "Google account unlinked.", googleUnlinkFailed: "Failed to unlink Google account.", microsoftAccountTitle: "Microsoft account", - microsoftSetupHint: "Set `REACT_APP_MICROSOFT_CLIENT_ID` in your UI environment to enable Microsoft sign-in and account linking.", + microsoftSetupHint: "Set `NEXT_PUBLIC_MICROSOFT_CLIENT_ID` in your UI environment to enable Microsoft sign-in and account linking.", microsoftLinked: "Linked", microsoftAvailableToLink: "Available to link", microsoftLinkedDate: "Linked {date}", @@ -1093,22 +1086,17 @@ export const translations = { settingsTabGeneral: "Generelt", settingsTabFollowUps: "Oppfølging", settingsTabNotifications: "Varsler", - settingsTabAccount: "Konto", settingsTabBackup: "Sikkerhetskopi", settingsAppearance: "Utseende", settingsTheme: "Tema", settingsThemeSystem: "System", settingsThemeDark: "Mørkt", settingsThemeLight: "Lyst", - settingsAccent: "Aksent", - settingsReset: "Tilbakestill", - settingsSavedPerUser: "Lagres per bruker i denne nettleseren.", settingsLanguageTitle: "Språk og lokalisering", settingsLanguageBody: "Velg foretrukket språk i appen. Dette brukes også når appen avgjør om importerte stillingsbeskrivelser skal vise oversatt tekst.", settingsPreferredLanguage: "Foretrukket språk", settingsEnglish: "Engelsk", settingsNorwegian: "Norsk Bokmål", - settingsMorePagesSoon: "Flere sider flyttes til dette oversettelsessystemet etter hvert som UI-oppryddingen fortsetter.", settingsJobs: "Jobber", settingsPagination: "Paginering", settingsRowsPerPage: "Rader per side", @@ -1132,8 +1120,6 @@ export const translations = { settingsNotificationsFollowUpReminders: "E-postpåminnelser for oppfølginger", settingsNotificationsGhostedJobs: "E-postvarsler for ghostede jobber", settingsNotificationsInAppReminders: "Fremhev påminnelser i appen", - settingsAccentHelp: "Dra i fargevelgeren, og lagre når den ser riktig ut.", - settingsAccentInvalid: "Bruk en full hex-farge som #15803D.", settingsCheckSystemStatus: "Sjekk systemstatus", profileTitle: "Profil", profileHeadlinePlaceholder: "Legg til en kort overskrift for å gjøre kontovisningen mer personlig.", @@ -1594,7 +1580,7 @@ export const translations = { googleUnlinked: "Google-konto koblet fra.", googleUnlinkFailed: "Kunne ikke koble fra Google-kontoen.", microsoftAccountTitle: "Microsoft-konto", - microsoftSetupHint: "Sett `REACT_APP_MICROSOFT_CLIENT_ID` i UI-miljøet ditt for å aktivere Microsoft-innlogging og kontokobling.", + microsoftSetupHint: "Sett `NEXT_PUBLIC_MICROSOFT_CLIENT_ID` i UI-miljøet ditt for å aktivere Microsoft-innlogging og kontokobling.", microsoftLinked: "Koblet", microsoftAvailableToLink: "Tilgjengelig for kobling", microsoftLinkedDate: "Koblet {date}", diff --git a/job-tracker-ui/src/settings-view.test.tsx b/job-tracker-ui/src/settings-view.test.tsx index cc3a408..4842a3b 100644 --- a/job-tracker-ui/src/settings-view.test.tsx +++ b/job-tracker-ui/src/settings-view.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import '@testing-library/jest-dom'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import SettingsView from './components/SettingsView'; @@ -21,35 +21,27 @@ jest.mock('./api', () => ({ })); jest.mock('./components/ImportExportJobs', () => () =>
Import Export Stub
); -jest.mock('./components/GoogleAuthCard', () => () =>
Google Auth Stub
); jest.mock('./components/BackupCard', () => () =>
Backup Stub
); -jest.mock('./components/AuthStatusCard', () => () =>
Auth Status Stub
); const mockedApi = api as jest.Mocked; -function renderView(onAccentColorChange = jest.fn()) { - return { - onAccentColorChange, - ...render( - - - - - - - , - ), - }; +function renderView() { + return render( + + + + + + + , + ); } beforeEach(() => { @@ -76,15 +68,10 @@ afterEach(() => { jest.clearAllMocks(); }); -test('settings view uses one follow-up section, one notification section, and staged accent apply', async () => { - const { onAccentColorChange } = renderView(); +test('settings view has no accent picker and uses one follow-up section, one notification section', async () => { + renderView(); - fireEvent.click(screen.getByRole('button', { name: /#15803D/i })); - const accentInput = (await screen.findAllByLabelText('Accent'))[1] as HTMLInputElement; - fireEvent.change(accentInput, { target: { value: '#2563eb' } }); - expect(onAccentColorChange).not.toHaveBeenCalled(); - fireEvent.click(screen.getByRole('button', { name: /^save$/i })); - expect(onAccentColorChange).toHaveBeenCalledWith('#2563eb'); + expect(screen.queryByText(/accent/i)).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('tab', { name: /follow-ups/i })); expect(await screen.findByText(/follow-up rules by scenario/i)).toBeInTheDocument(); diff --git a/job-tracker-ui/src/theme.ts b/job-tracker-ui/src/theme.ts index 39de651..3efe87b 100644 --- a/job-tracker-ui/src/theme.ts +++ b/job-tracker-ui/src/theme.ts @@ -2,6 +2,10 @@ import { alpha, createTheme, darken, lighten } from "@mui/material/styles"; type PaletteLike = Record; +// Single global brand accent -- matches the dark sidebar/landing page indigo used throughout +// the app. Not user-configurable; see jobbjakt-nextjs-migration memory / UI rework notes. +const ACCENT = "#6366F1"; + function buildPrimary(main: string) { return { lighter: lighten(main, 0.82), @@ -12,7 +16,7 @@ function buildPrimary(main: string) { }; } -function buildLightPalette(accentColor: string): PaletteLike { +function buildLightPalette(): PaletteLike { const textPrimary = "#1B1B1F"; const textSecondary = "#46464F"; @@ -24,7 +28,7 @@ function buildLightPalette(accentColor: string): PaletteLike { const disabledBackground = "#E4E1E6"; return { - primary: buildPrimary(accentColor || "#6366F1"), + primary: buildPrimary(ACCENT), secondary: { lighter: "#E0E0FF", light: "#C3C4E4", @@ -82,14 +86,14 @@ function buildLightPalette(accentColor: string): PaletteLike { // from the product mockups; cards/inputs (paper) sit above it. background: { default: "#F4F6FB", paper: background }, action: { - hover: alpha(accentColor || "#6366F1", 0.05), + hover: alpha(ACCENT, 0.05), disabled: alpha(disabled, 0.6), disabledBackground: alpha(disabledBackground, 0.9), }, }; } -function buildDarkPalette(accentColor: string): PaletteLike { +function buildDarkPalette(): PaletteLike { const bg = "#0B0B0E"; const paper = "#111116"; const divider = alpha("#FFFFFF", 0.10); @@ -101,7 +105,7 @@ function buildDarkPalette(accentColor: string): PaletteLike { const disabledBackground = alpha("#FFFFFF", 0.08); return { - primary: buildPrimary(accentColor || "#6366F1"), + primary: buildPrimary(ACCENT), secondary: { lighter: alpha(secondaryMain, 0.22), light: alpha(secondaryMain, 0.14), @@ -157,7 +161,7 @@ function buildDarkPalette(accentColor: string): PaletteLike { divider, background: { default: bg, paper }, action: { - hover: alpha(accentColor || "#6366F1", 0.16), + hover: alpha(ACCENT, 0.16), disabled: alpha("#FFFFFF", 0.5), disabledBackground, }, @@ -196,9 +200,9 @@ function buildTypography() { }; } -export const getTheme = (_mode: "light" | "dark", accentColor: string) => { - const lightPalette = buildLightPalette(accentColor); - const darkPalette = buildDarkPalette(accentColor); +export const getTheme = (_mode: "light" | "dark") => { + const lightPalette = buildLightPalette(); + const darkPalette = buildDarkPalette(); const theme = createTheme({ breakpoints: { diff --git a/job-tracker-ui/src/themePrefs.ts b/job-tracker-ui/src/themePrefs.ts index 45bc434..29e09ae 100644 --- a/job-tracker-ui/src/themePrefs.ts +++ b/job-tracker-ui/src/themePrefs.ts @@ -19,17 +19,3 @@ export function getThemeModePref(): ThemeModePref { export function setThemeModePref(v: ThemeModePref) { window.localStorage.setItem(k("themeMode"), v); } - -export function getAccentColor(): string { - const raw = window.localStorage.getItem(k("accentColor")); - if (raw && /^#[0-9a-fA-F]{6}$/.test(raw)) return raw; - return "#6366f1"; -} - -export function setAccentColor(v: string) { - if (v && /^#[0-9a-fA-F]{6}$/.test(v)) window.localStorage.setItem(k("accentColor"), v); -} - -export function clearAccentColor() { - window.localStorage.removeItem(k("accentColor")); -} diff --git a/job-tracker-ui/src/views/ProfilePage.tsx b/job-tracker-ui/src/views/ProfilePage.tsx index 7ef673f..318ce45 100644 --- a/job-tracker-ui/src/views/ProfilePage.tsx +++ b/job-tracker-ui/src/views/ProfilePage.tsx @@ -10,6 +10,8 @@ import ZoomInOutlinedIcon from "@mui/icons-material/ZoomInOutlined"; import { api, getApiErrorMessage } from "../api"; import GoogleAuthCard from "../components/GoogleAuthCard"; import MicrosoftAuthCard from "../components/MicrosoftAuthCard"; +import AuthStatusCard from "../components/AuthStatusCard"; +import EmailProviderConnections from "../components/EmailProviderConnections"; import CropImageDialog from "../components/CropImageDialog"; import { useToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; @@ -562,8 +564,12 @@ export default function ProfilePage() { + + + +