diff --git a/apps/backoffice/public/brand/ema-black.png b/apps/backoffice/public/brand/ema-black.png new file mode 100644 index 000000000..3274ce8fa Binary files /dev/null and b/apps/backoffice/public/brand/ema-black.png differ diff --git a/apps/backoffice/public/brand/ema-white.png b/apps/backoffice/public/brand/ema-white.png new file mode 100644 index 000000000..3274ce8fa Binary files /dev/null and b/apps/backoffice/public/brand/ema-white.png differ diff --git a/apps/backoffice/public/ema-logo.png b/apps/backoffice/public/ema-logo.png new file mode 100644 index 000000000..b35ba3e7b Binary files /dev/null and b/apps/backoffice/public/ema-logo.png differ diff --git a/apps/backoffice/src/app/providers/AppProviders.tsx b/apps/backoffice/src/app/providers/AppProviders.tsx index 390138892..c8521ac36 100644 --- a/apps/backoffice/src/app/providers/AppProviders.tsx +++ b/apps/backoffice/src/app/providers/AppProviders.tsx @@ -1,5 +1,6 @@ import { Provider } from 'react-redux'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { AuthConfigProvider } from '@ema-platform/auth'; import type { ReactNode } from 'react'; import { store } from '../store'; import { MantineThemeProvider } from './MantineThemeProvider'; @@ -14,7 +15,17 @@ export function AppProviders({ children }: { children: ReactNode }) { return ( - {children} + + {children} + ); diff --git a/apps/backoffice/src/app/router/index.tsx b/apps/backoffice/src/app/router/index.tsx index 32c82a2d1..153f96256 100644 --- a/apps/backoffice/src/app/router/index.tsx +++ b/apps/backoffice/src/app/router/index.tsx @@ -1,23 +1,20 @@ import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom'; import { - Login, - SetPasswordPage, -} from '@tria-plc/iamui-common'; + LoginPage, + ForgotPasswordPage, + OTPVerificationPage, +} from '@ema-platform/auth'; import { AuthLayout } from '../layouts/AuthLayout'; import { AuthProviders } from '../iam/IamProviders'; import UserManagementHostPage from '../features/user-management-host/UserManagementHostPage'; - const router = createBrowserRouter([ { - element: ( - - - - ), + element: , children: [ - { path: '/login', element: }, - { path: '/otp-verify', element: }, + { path: '/login', element: }, + { path: '/forgot-password', element: }, + { path: '/otp-verify', element: }, ], }, { path: '/um/*', element: }, diff --git a/apps/backoffice/src/app/store/index.ts b/apps/backoffice/src/app/store/index.ts index 4a2291664..22cc870f6 100644 --- a/apps/backoffice/src/app/store/index.ts +++ b/apps/backoffice/src/app/store/index.ts @@ -1,10 +1,26 @@ import { configureStore } from '@reduxjs/toolkit'; import { baseApi } from '@ema-platform/api'; +import { authReducer, signupReducer, configureAuthStorage, authStorage } from '@ema-platform/auth'; +import type { AuthUser } from '@ema-platform/auth'; + +configureAuthStorage('ema-backoffice'); + +const preloadedAuth = (() => { + const token = authStorage.getToken(); + const user = authStorage.getUser(); + if (token && user) { + return { token, user, isAuthenticated: true }; + } + return undefined; +})(); export const store = configureStore({ reducer: { + auth: authReducer, + signup: signupReducer, [baseApi.reducerPath]: baseApi.reducer, }, + preloadedState: preloadedAuth ? { auth: preloadedAuth } : undefined, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware), }); diff --git a/apps/portal/src/app/features/auth/utils/auth-storage.ts b/apps/portal/src/app/features/auth/utils/auth-storage.ts deleted file mode 100644 index 17a3e3b21..000000000 --- a/apps/portal/src/app/features/auth/utils/auth-storage.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { AuthUser } from '../types/auth.types'; - -const KEYS = { - token: 'ema-portal-auth-token', - refreshToken: 'ema-portal-refresh-token', - user: 'ema-portal-auth-user', -} as const; - -export const authStorage = { - getToken: () => localStorage.getItem(KEYS.token) ?? undefined, - setToken: (token: string) => localStorage.setItem(KEYS.token, token), - getRefreshToken: () => localStorage.getItem(KEYS.refreshToken) ?? undefined, - setRefreshToken: (t: string) => localStorage.setItem(KEYS.refreshToken, t), - getUser: (): AuthUser | null => { - try { - return JSON.parse(localStorage.getItem(KEYS.user) ?? 'null') as AuthUser | null; - } catch { - return null; - } - }, - setUser: (u: AuthUser) => localStorage.setItem(KEYS.user, JSON.stringify(u)), - clear: () => Object.values(KEYS).forEach((k) => localStorage.removeItem(k)), -}; diff --git a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx index 3e8c35961..48de3620d 100644 --- a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx +++ b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx @@ -45,7 +45,7 @@ import { useApiMutation } from '@ema-platform/api'; import { PageHeader } from '../../../components/PageHeader'; import { SUPPORTED_LANGUAGES, type AppLanguage } from '../../../i18n/config'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; -import { setUser } from '../../auth/store/auth.slice'; +import { setUser } from '@ema-platform/auth'; import type { AuthUser } from '../../auth/types/auth.types'; import classes from './ProfilePage.module.css'; diff --git a/apps/portal/src/app/providers/AppProviders.tsx b/apps/portal/src/app/providers/AppProviders.tsx index 5fbc567da..659cb187d 100644 --- a/apps/portal/src/app/providers/AppProviders.tsx +++ b/apps/portal/src/app/providers/AppProviders.tsx @@ -1,6 +1,7 @@ import { Provider } from 'react-redux'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AuthProvider } from '@tria-plc/iamui-common'; +import { AuthConfigProvider } from '@ema-platform/auth'; import type { ReactNode } from 'react'; import { store } from '../store'; import { MantineThemeProvider } from './MantineThemeProvider'; @@ -16,7 +17,17 @@ export function AppProviders({ children }: { children: ReactNode }) { - {children} + + {children} + diff --git a/apps/portal/src/app/router.tsx b/apps/portal/src/app/router.tsx index d89c41f57..3ce011417 100644 --- a/apps/portal/src/app/router.tsx +++ b/apps/portal/src/app/router.tsx @@ -5,10 +5,7 @@ import { i18n } from './i18n/config'; import { PortalLayout } from './layouts/PortalLayout'; // Auth (standalone pages, no portal chrome) -import { LoginPage } from './features/auth/pages/LoginPage'; -import { SignupPage } from './features/auth/pages/SignupPage'; -import { OTPVerificationPage } from './features/auth/pages/OTPVerificationPage'; -import { ForgotPasswordPage } from './features/auth/pages/ForgotPasswordPage'; +import { LoginPage, SignupPage, OTPVerificationPage, ForgotPasswordPage } from '@ema-platform/auth'; // Portal feature pages import { DashboardPage } from './features/dashboard/pages/DashboardPage'; diff --git a/apps/portal/src/app/store/index.ts b/apps/portal/src/app/store/index.ts index a12829ad0..eceaa9fbd 100644 --- a/apps/portal/src/app/store/index.ts +++ b/apps/portal/src/app/store/index.ts @@ -1,13 +1,14 @@ import { configureStore } from '@reduxjs/toolkit'; import { baseApi } from '@ema-platform/api'; -import { authReducer } from '../features/auth/store/auth.slice'; -import { signupReducer } from '../features/auth/store/signup.slice'; +import { authReducer, signupReducer, configureAuthStorage, authStorage } from '@ema-platform/auth'; import { licensesReducer } from '../features/licenses/store/licenses.slice'; -import { authStorage } from '../features/auth/utils/auth-storage'; +import type { AuthUser } from '@ema-platform/auth'; + +configureAuthStorage('ema-portal'); const preloadedAuth = (() => { const token = authStorage.getToken(); - const user = authStorage.getUser(); + const user = authStorage.getUser(); if (token && user) { return { token, user, isAuthenticated: true }; } diff --git a/apps/portal/src/app/v2/PortalLayoutV2.tsx b/apps/portal/src/app/v2/PortalLayoutV2.tsx index 20f405354..ecb4ea3c6 100644 --- a/apps/portal/src/app/v2/PortalLayoutV2.tsx +++ b/apps/portal/src/app/v2/PortalLayoutV2.tsx @@ -29,7 +29,7 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { notify } from '@ema-platform/ui'; import { LanguageSwitcher } from '../components/LanguageSwitcher'; import { ColorSchemeToggle } from '../components/ColorSchemeToggle'; -import { BrandMark } from '../features/auth/components/AuthShell'; +import { BrandMark } from '@ema-platform/auth'; import { BrandAvatar } from './components/ui'; interface NavItem { diff --git a/libs/auth/project.json b/libs/auth/project.json new file mode 100644 index 000000000..0512e8da0 --- /dev/null +++ b/libs/auth/project.json @@ -0,0 +1,7 @@ +{ + "name": "@ema-platform/auth", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/auth/src", + "projectType": "library", + "tags": [] +} diff --git a/libs/auth/src/index.ts b/libs/auth/src/index.ts new file mode 100644 index 000000000..972adb5ab --- /dev/null +++ b/libs/auth/src/index.ts @@ -0,0 +1,11 @@ +export { AuthConfigProvider, useAuthConfig } from './lib/AuthConfig'; +export type { AuthConfigValue } from './lib/AuthConfig'; +export { AuthShell, BrandMark } from './lib/components/AuthShell'; +export { LoginPage } from './lib/pages/LoginPage'; +export { SignupPage } from './lib/pages/SignupPage'; +export { ForgotPasswordPage } from './lib/pages/ForgotPasswordPage'; +export { OTPVerificationPage } from './lib/pages/OTPVerificationPage'; +export { authReducer, loginSuccess, setUser, logout, hydrateAuth } from './lib/store/auth.slice'; +export { signupReducer, setSignupData, setSignupStep, resetSignup } from './lib/store/signup.slice'; +export { configureAuthStorage, authStorage } from './lib/utils/auth-storage'; +export type { AuthUser, AuthState, LoginPayload } from './lib/types/auth.types'; diff --git a/libs/auth/src/lib/AuthConfig.tsx b/libs/auth/src/lib/AuthConfig.tsx new file mode 100644 index 000000000..bbc446adb --- /dev/null +++ b/libs/auth/src/lib/AuthConfig.tsx @@ -0,0 +1,40 @@ +import { createContext, useContext, type ReactNode } from 'react'; + +export interface AuthConfigValue { + appName: string; + storagePrefix: string; + loginRedirectPath: string; + enableSignup: boolean; + enableForgotPassword: boolean; + logoUrl: string; +} + +const defaultConfig: AuthConfigValue = { + appName: 'Portal', + storagePrefix: 'ema-auth', + loginRedirectPath: '/dashboard', + enableSignup: true, + enableForgotPassword: true, + logoUrl: '/brand/ema-white.png', +}; + +const AuthConfigContext = createContext(defaultConfig); + +export function AuthConfigProvider({ + children, + value, +}: { + children: ReactNode; + value: Partial; +}) { + const merged = { ...defaultConfig, ...value }; + return ( + + {children} + + ); +} + +export function useAuthConfig() { + return useContext(AuthConfigContext); +} diff --git a/apps/portal/src/app/features/auth/components/AuthShell.tsx b/libs/auth/src/lib/components/AuthShell.tsx similarity index 81% rename from apps/portal/src/app/features/auth/components/AuthShell.tsx rename to libs/auth/src/lib/components/AuthShell.tsx index 74a6f62fb..07b7c7204 100644 --- a/apps/portal/src/app/features/auth/components/AuthShell.tsx +++ b/libs/auth/src/lib/components/AuthShell.tsx @@ -9,9 +9,25 @@ import { Title, rem, useMantineTheme, + type BoxProps, } from '@mantine/core'; import { IconCheck } from '@tabler/icons-react'; -import { Logo } from '../../../components/Logo'; +import { useAuthConfig } from '../AuthConfig'; + +export function BrandMark({ size = 44, ...boxProps }: BoxProps & { size?: number }) { + const { logoUrl } = useAuthConfig(); + return ( + + ); +} const FEATURES = [ 'Submit applications online, 24/7', @@ -19,36 +35,19 @@ const FEATURES = [ 'Available in English & አማርኛ', ]; -/** - * EMA brand mark — the app logo, kept in sync across the portal via ``. - * Use `variant="white"` on dark backgrounds (e.g. the auth brand panel). - */ -export function BrandMark({ - size = 44, - variant = 'gradient', -}: { - size?: number; - variant?: 'gradient' | 'white'; -}) { - return ; -} - interface AuthShellProps { children: ReactNode; brandTitle?: string; brandSubtitle?: string; } -/** - * Two-pane auth card: a centered card with a form panel (left) and a branded - * gradient panel (right, hidden below `lg`). Inspired by the UM login layout. - */ export function AuthShell({ children, brandTitle = 'Maritime licensing, made simple.', brandSubtitle = 'Apply for vessel and seafarer licenses, upload documents, and track every application in one secure portal.', }: AuthShellProps) { const theme = useMantineTheme(); + const { logoUrl } = useAuthConfig(); const heroGradient = theme.other.heroGradient as string; return ( @@ -72,17 +71,15 @@ export function AuthShell({ }} > - {/* ---- Form panel (left) ---------------------------------------- */} {children} - {/* ---- Brand panel (right) -------------------------------------- */} - {/* Decorative blurred orbs */} diff --git a/apps/portal/src/app/features/auth/pages/ForgotPasswordPage.tsx b/libs/auth/src/lib/pages/ForgotPasswordPage.tsx similarity index 93% rename from apps/portal/src/app/features/auth/pages/ForgotPasswordPage.tsx rename to libs/auth/src/lib/pages/ForgotPasswordPage.tsx index 08aa7394f..3b932d586 100644 --- a/apps/portal/src/app/features/auth/pages/ForgotPasswordPage.tsx +++ b/libs/auth/src/lib/pages/ForgotPasswordPage.tsx @@ -23,6 +23,7 @@ import { Link } from 'react-router-dom'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; import { AuthShell } from '../components/AuthShell'; +import { useAuthConfig } from '../AuthConfig'; const schema = z.object({ email: z.string().email({ message: 'Enter a valid email' }), @@ -31,6 +32,7 @@ const schema = z.object({ type FormValues = z.infer; export function ForgotPasswordPage() { + const { appName } = useAuthConfig(); const [forgotTrigger, { isLoading }] = useApiMutation(); const [sentTo, setSentTo] = useState(null); @@ -90,7 +92,7 @@ export function ForgotPasswordPage() { return ( @@ -145,7 +147,7 @@ export function ForgotPasswordPage() { return (
diff --git a/apps/portal/src/app/features/auth/pages/LoginPage.tsx b/libs/auth/src/lib/pages/LoginPage.tsx similarity index 81% rename from apps/portal/src/app/features/auth/pages/LoginPage.tsx rename to libs/auth/src/lib/pages/LoginPage.tsx index eb5d0e907..dcea227b9 100644 --- a/apps/portal/src/app/features/auth/pages/LoginPage.tsx +++ b/libs/auth/src/lib/pages/LoginPage.tsx @@ -21,12 +21,13 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useNavigate, Link } from 'react-router-dom'; -import { useAppDispatch } from '../../../store/hooks'; -import { loginSuccess, setUser } from '../store/auth.slice'; -import type { LoginPayload, AuthUser } from '../types/auth.types'; +import { useDispatch } from 'react-redux'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; import { AuthShell } from '../components/AuthShell'; +import { loginSuccess, setUser } from '../store/auth.slice'; +import type { LoginPayload, AuthUser } from '../types/auth.types'; +import { useAuthConfig } from '../AuthConfig'; const schema = z.object({ email: z.string().email({ message: 'Enter a valid email' }), @@ -37,7 +38,9 @@ type FormValues = z.infer; export function LoginPage() { const navigate = useNavigate(); - const dispatch = useAppDispatch(); + const dispatch = useDispatch(); + const { appName, loginRedirectPath, enableSignup, enableForgotPassword } = + useAuthConfig(); const [isLoading, setIsLoading] = useState(false); const [rememberMe, setRememberMe] = useState(true); const [loginTrigger] = useApiMutation(); @@ -68,7 +71,7 @@ export function LoginPage() { dispatch(setUser(me)); if (me.isPhoneNumberVerified) { - navigate('/dashboard'); + navigate(loginRedirectPath); } else { navigate('/otp-verify', { state: { email: me.email, phoneNumber: me.phoneNumber }, @@ -86,10 +89,10 @@ export function LoginPage() {
- Welcome back + Welcome to {appName} - Sign in to access your maritime licensing dashboard. + Sign in to access your account.
@@ -119,14 +122,16 @@ export function LoginPage() { checked={rememberMe} onChange={(e) => setRememberMe(e.currentTarget.checked)} /> - - Forgot password? - + {enableForgotPassword && ( + + Forgot password? + + )} diff --git a/apps/portal/src/app/features/auth/pages/SignupPage.tsx b/libs/auth/src/lib/pages/SignupPage.tsx similarity index 95% rename from apps/portal/src/app/features/auth/pages/SignupPage.tsx rename to libs/auth/src/lib/pages/SignupPage.tsx index a6499dc19..676a82676 100644 --- a/apps/portal/src/app/features/auth/pages/SignupPage.tsx +++ b/libs/auth/src/lib/pages/SignupPage.tsx @@ -23,11 +23,12 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useNavigate, Link } from 'react-router-dom'; -import { useAppDispatch } from '../../../store/hooks'; -import { loginSuccess } from '../store/auth.slice'; +import { useDispatch } from 'react-redux'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; import { AuthShell } from '../components/AuthShell'; +import { loginSuccess } from '../store/auth.slice'; +import { useAuthConfig } from '../AuthConfig'; const schema = z .object({ @@ -62,7 +63,8 @@ interface SignupPayload { export function SignupPage() { const navigate = useNavigate(); - const dispatch = useAppDispatch(); + const dispatch = useDispatch(); + const { appName, loginRedirectPath } = useAuthConfig(); const [agreed, setAgreed] = useState(false); const [signupTrigger, { isLoading: loading }] = useApiMutation<{ token: string; @@ -106,7 +108,7 @@ export function SignupPage() { ); if (data.isPhoneNumberVerified) { - navigate('/dashboard'); + navigate(loginRedirectPath); } else { navigate('/otp-verify', { state: { email: values.email, phoneNumber: values.phoneNumber }, @@ -120,8 +122,8 @@ export function SignupPage() { return (
diff --git a/apps/portal/src/app/features/auth/store/auth.slice.ts b/libs/auth/src/lib/store/auth.slice.ts similarity index 96% rename from apps/portal/src/app/features/auth/store/auth.slice.ts rename to libs/auth/src/lib/store/auth.slice.ts index 48d397b8f..f93ce4ddc 100644 --- a/apps/portal/src/app/features/auth/store/auth.slice.ts +++ b/libs/auth/src/lib/store/auth.slice.ts @@ -30,7 +30,7 @@ const authSlice = createSlice({ }, hydrateAuth(state) { const token = authStorage.getToken(); - const user = authStorage.getUser(); + const user = authStorage.getUser(); if (token && user) { state.token = token; state.user = user; diff --git a/apps/portal/src/app/features/auth/store/signup.slice.ts b/libs/auth/src/lib/store/signup.slice.ts similarity index 100% rename from apps/portal/src/app/features/auth/store/signup.slice.ts rename to libs/auth/src/lib/store/signup.slice.ts diff --git a/apps/portal/src/app/features/auth/types/auth.types.ts b/libs/auth/src/lib/types/auth.types.ts similarity index 100% rename from apps/portal/src/app/features/auth/types/auth.types.ts rename to libs/auth/src/lib/types/auth.types.ts diff --git a/libs/auth/src/lib/utils/auth-storage.ts b/libs/auth/src/lib/utils/auth-storage.ts new file mode 100644 index 000000000..19ff15fbf --- /dev/null +++ b/libs/auth/src/lib/utils/auth-storage.ts @@ -0,0 +1,29 @@ +let _prefix = 'ema-auth'; + +export function configureAuthStorage(prefix: string) { + _prefix = prefix; +} + +function key(k: string) { + return `${_prefix}-${k}`; +} + +export const authStorage = { + getToken: () => localStorage.getItem(key('auth-token')) ?? undefined, + setToken: (token: string) => localStorage.setItem(key('auth-token'), token), + getRefreshToken: () => localStorage.getItem(key('refresh-token')) ?? undefined, + setRefreshToken: (t: string) => localStorage.setItem(key('refresh-token'), t), + getUser: (): T | null => { + try { + return JSON.parse(localStorage.getItem(key('auth-user')) ?? 'null') as T | null; + } catch { + return null; + } + }, + setUser: (u: T) => localStorage.setItem(key('auth-user'), JSON.stringify(u)), + clear: () => { + [key('auth-token'), key('refresh-token'), key('auth-user')].forEach((k) => + localStorage.removeItem(k), + ); + }, +}; diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json new file mode 100644 index 000000000..8c54e1fa5 --- /dev/null +++ b/libs/auth/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "../../dist/out-tsc" }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/shared/src/lib/theme/ema-theme.ts b/libs/shared/src/lib/theme/ema-theme.ts index 3b4d6cd56..de3b3ca72 100644 --- a/libs/shared/src/lib/theme/ema-theme.ts +++ b/libs/shared/src/lib/theme/ema-theme.ts @@ -31,4 +31,7 @@ export const emaTheme = createTheme({ md: '0 4px 20px rgba(15,23,42,0.08)', lg: '0 8px 30px rgba(15,23,42,0.12)', }, + other: { + heroGradient: 'linear-gradient(135deg, #3160b7 0%, #1fc29d 100%)', + }, }); diff --git a/tsconfig.base.json b/tsconfig.base.json index fb046a0ce..96df34488 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -19,7 +19,8 @@ "paths": { "@ema-platform/shared": ["libs/shared/src/index.ts"], "@ema-platform/ui": ["libs/ui/src/index.ts"], - "@ema-platform/api": ["libs/api/src/index.ts"] + "@ema-platform/api": ["libs/api/src/index.ts"], + "@ema-platform/auth": ["libs/auth/src/index.ts"] } }, "exclude": ["node_modules", "tmp"] diff --git a/user-management-config/fhc.theme.ts b/user-management-config/fhc.theme.ts index 6620b0b4a..22429eef7 100644 --- a/user-management-config/fhc.theme.ts +++ b/user-management-config/fhc.theme.ts @@ -39,7 +39,7 @@ export const FHC_COLORS = { "#96BDEB", "#6FA4E0", "#4A90E2", - "#357ABD", + "#4b7fe5", "#2C669D", "#224F7A", "#173654", @@ -120,7 +120,7 @@ export const FHC_LAYOUT = { brick: "#1A3A5C", brickDark: "#0F2440", blue: "#4A90E2", - blueDark: "#357ABD", + blueDark: "#4b7fe5", gold: "#4A90E2", text: "#1F2937", }, @@ -216,7 +216,7 @@ export const fhcMantineTheme: MantineThemeOverride = { * color while still getting the fhc* palettes + layout tokens via `mantineTheme`. */ export const fhcDesignPreset = { - colors: { primary: "#357ABD" }, + colors: { primary: "#4b7fe5" }, typography: { fontFamily: "Plus Jakarta Sans, sans-serif" }, shape: { radius: "10px" }, mantineTheme: fhcMantineTheme, diff --git a/user-management-config/project.theme.ts b/user-management-config/project.theme.ts index a010a44a0..32fd4a78e 100644 --- a/user-management-config/project.theme.ts +++ b/user-management-config/project.theme.ts @@ -40,7 +40,7 @@ export const projectTheme: DesignConfig = { // still have different colors. // ───────────────────────────────────────────────────────────────────────── colors: { - primary: "#357ABD", // FHC blue (fhcBlue-6) — buttons, links, active states + primary: "#4b7fe5", // FHC blue (fhcBlue-6) — buttons, links, active states // // "#5D2E1F" brick (FHC chrome) is used by the sidebar/modal skin below // // "#2563eb" blue | "#7c3aed" purple // // "#16a34a" green | "#dc2626" red @@ -174,18 +174,18 @@ export const projectTheme: DesignConfig = { // ── Top tab bar skin (legacy view) ───────────────────────────────────── menuBackground: "#ffffff", // TODO: tab bar background menuColor: "#334155", // inactive tab text - menuActiveColor: "#357ABD", // active tab text (FHC blue) - menuActiveBorderColor: "#357ABD", // active tab underline - menuHoverColor: "#357ABD", // tab hover text + menuActiveColor: "#4b7fe5", // active tab text (FHC blue) + menuActiveBorderColor: "#4b7fe5", // active tab underline + menuHoverColor: "#4b7fe5", // tab hover text // ── Create / edit modal skin (shared BackofficeModal) ────────────────── - modalAccentColor: "#357ABD", // blue top strip + modalAccentColor: "#4b7fe5", // blue top strip modalHeaderBackground: "#EEF4FC", // header bg (view) modalHeaderEditBackground: "#D9E8FA", // header bg (edit) modalIconBackground: "#D9E8FA", // header icon chip bg - modalIconColor: "#357ABD", // header icon chip color + modalIconColor: "#4b7fe5", // header icon chip color modalTitleColor: "#1F2937", // modal title text - modalFocusColor: "#357ABD", // input focus ring inside modals + modalFocusColor: "#4b7fe5", // input focus ring inside modals modalSurface: "#ffffff", // modal body surface },