mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-24 22:36:38 +02:00
feat: Login-Screen-Branding im Global Admin (Hintergrund + Logo)
Im Bereich Anpassungen (AdminCustomizePage) kann der Platform-Admin nun den Login-Screen individuell gestalten: - Hintergrundtyp: Farbverlauf, Einfarbig oder Hintergrundbild - Farbverlauf: zwei Farbpicker (Von/Bis) mit Hex-Eingabe - Hintergrundbild: Datei-Upload max. 2MB, Live-Vorschau - Logo auf Login-Screen: wird automatisch aus dem Sidebar-Logo uebernommen Backend: settings.controller.ts GET/POST /settings/branding um loginBgType, loginBgColor1, loginBgColor2, loginBgImage erweitert. LoginPage laedt Branding per oeffentlichem Endpoint (kein Auth), leitet containerStyle per useMemo ab und zeigt Logo-Bild statt Hardcode-Text. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b872b7e708
commit
c333cbfa4b
5 changed files with 348 additions and 13 deletions
32
Summarize.md
32
Summarize.md
|
|
@ -6,6 +6,38 @@
|
|||
|
||||
---
|
||||
|
||||
### Aenderungen 2026-03-13 (16): Global Admin — Login-Screen-Branding (Hintergrund + Logo)
|
||||
|
||||
#### Backend (Core Service)
|
||||
- `settings/settings.controller.ts` — `GET /settings/branding` + `POST /settings/branding` erweitert:
|
||||
- 4 neue Felder: `loginBgType` ('gradient'|'solid'|'image'), `loginBgColor1`, `loginBgColor2`, `loginBgImage`
|
||||
- `loginBgImage` Groeßen-Validierung: max. 2MB
|
||||
- `loginBgType` Enum-Validierung: nur erlaubte Werte werden gespeichert
|
||||
- Rueckwaertskompatibel: bestehende Redis-Daten ohne neue Felder liefern `null`-Defaults
|
||||
|
||||
#### Frontend
|
||||
- `admin/AdminCustomizePage.tsx` — Neuer Card-Block "Login-Hintergrund":
|
||||
- Typ-Toggle: Farbverlauf / Einfarbig / Hintergrundbild
|
||||
- Farbverlauf: Zwei Farbpicker (Von/Bis) mit Hex-Eingabe
|
||||
- Einfarbig: Ein Farbpicker mit Hex-Eingabe
|
||||
- Hintergrundbild: Datei-Upload (max. 2MB, nur Bilder), Entfernen-Button
|
||||
- Live-Vorschau: Mini-Login-Mockup (200x120px) mit aktuellem Hintergrund und Logo
|
||||
- `auth/LoginPage.tsx` — Dynamisches Branding:
|
||||
- `useQuery(['settings', 'branding'])` — oeffentlicher Endpoint, kein Auth noetig
|
||||
- `useMemo` → `containerStyle`: leitet CSS `background` aus `loginBgType` ab
|
||||
- Logo-Anzeige: zeigt `<img>` mit `branding.logo` statt hardcoded "INSIGHT"-Text
|
||||
- `auth/LoginPage.module.css` — Neue Klasse `.logoImage` (max-width 180px, object-fit contain)
|
||||
|
||||
#### TypeScript
|
||||
- `npx tsc --noEmit` in packages/frontend: 0 Fehler
|
||||
- `npx tsc --noEmit` in packages/core-service: 0 Fehler
|
||||
|
||||
#### Deployment-Hinweis (Schritt 16)
|
||||
- Rebuild + Restart: core-service (Backend) und frontend
|
||||
- Kein Datenbankschema-Aenderung, keine Migration noetig (nur Redis)
|
||||
|
||||
---
|
||||
|
||||
### Aenderungen 2026-03-13 (15): Experten-Profil – BulletEditor Fett/Kursiv/Unterstrichen + Aufgaben-Anzeige
|
||||
|
||||
#### Frontend
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export class SettingsController {
|
|||
|
||||
/**
|
||||
* GET /api/v1/settings/branding
|
||||
* Branding-Einstellungen lesen (Logo, Sidebar-Farbe etc.).
|
||||
* Branding-Einstellungen lesen (Logo, Sidebar-Farbe, Login-Hintergrund etc.).
|
||||
*/
|
||||
@Get('branding')
|
||||
@ApiOperation({ summary: 'Branding-Einstellungen lesen' })
|
||||
|
|
@ -147,21 +147,36 @@ export class SettingsController {
|
|||
sidebarColor: string | null;
|
||||
logoWidth: number | null;
|
||||
sidebarWidth: number | null;
|
||||
loginBgType: 'gradient' | 'solid' | 'image' | null;
|
||||
loginBgColor1: string | null;
|
||||
loginBgColor2: string | null;
|
||||
loginBgImage: string | null;
|
||||
}> {
|
||||
const raw = await this.redis.get(BRANDING_LOGO_KEY);
|
||||
if (!raw) return { logo: null, sidebarColor: null, logoWidth: null, sidebarWidth: null };
|
||||
const empty = {
|
||||
logo: null, sidebarColor: null, logoWidth: null, sidebarWidth: null,
|
||||
loginBgType: null, loginBgColor1: null, loginBgColor2: null, loginBgImage: null,
|
||||
};
|
||||
if (!raw) return empty;
|
||||
|
||||
try {
|
||||
const data = JSON.parse(raw);
|
||||
const loginBgType = ['gradient', 'solid', 'image'].includes(data.loginBgType)
|
||||
? (data.loginBgType as 'gradient' | 'solid' | 'image')
|
||||
: null;
|
||||
return {
|
||||
logo: data.logo || null,
|
||||
sidebarColor: data.sidebarColor || null,
|
||||
logoWidth: typeof data.logoWidth === 'number' ? data.logoWidth : null,
|
||||
sidebarWidth: typeof data.sidebarWidth === 'number' ? data.sidebarWidth : null,
|
||||
logo: data.logo || null,
|
||||
sidebarColor: data.sidebarColor || null,
|
||||
logoWidth: typeof data.logoWidth === 'number' ? data.logoWidth : null,
|
||||
sidebarWidth: typeof data.sidebarWidth === 'number' ? data.sidebarWidth : null,
|
||||
loginBgType,
|
||||
loginBgColor1: data.loginBgColor1 || null,
|
||||
loginBgColor2: data.loginBgColor2 || null,
|
||||
loginBgImage: data.loginBgImage || null,
|
||||
};
|
||||
} catch {
|
||||
// Legacy: nur Logo als String
|
||||
return { logo: raw, sidebarColor: null, logoWidth: null, sidebarWidth: null };
|
||||
return { ...empty, logo: raw };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,12 +194,25 @@ export class SettingsController {
|
|||
sidebarColor?: string | null;
|
||||
logoWidth?: number | null;
|
||||
sidebarWidth?: number | null;
|
||||
loginBgType?: string | null;
|
||||
loginBgColor1?: string | null;
|
||||
loginBgColor2?: string | null;
|
||||
loginBgImage?: string | null;
|
||||
},
|
||||
): Promise<{ success: boolean }> {
|
||||
if (body.logo && body.logo.length > 500_000) {
|
||||
throw new BadRequestException('Logo darf maximal 500KB gross sein');
|
||||
}
|
||||
|
||||
if (body.loginBgImage && body.loginBgImage.length > 2_000_000) {
|
||||
throw new BadRequestException('Hintergrundbild darf maximal 2MB gross sein');
|
||||
}
|
||||
|
||||
const validBgTypes = ['gradient', 'solid', 'image'];
|
||||
const loginBgType = body.loginBgType && validBgTypes.includes(body.loginBgType)
|
||||
? body.loginBgType
|
||||
: null;
|
||||
|
||||
// Werte-Grenzen
|
||||
const logoWidth = typeof body.logoWidth === 'number'
|
||||
? Math.min(Math.max(Math.round(body.logoWidth), 40), 240)
|
||||
|
|
@ -194,10 +222,14 @@ export class SettingsController {
|
|||
: null;
|
||||
|
||||
const data = {
|
||||
logo: body.logo || null,
|
||||
sidebarColor: body.sidebarColor || null,
|
||||
logo: body.logo || null,
|
||||
sidebarColor: body.sidebarColor || null,
|
||||
logoWidth,
|
||||
sidebarWidth,
|
||||
loginBgType,
|
||||
loginBgColor1: body.loginBgColor1 || null,
|
||||
loginBgColor2: body.loginBgColor2 || null,
|
||||
loginBgImage: body.loginBgImage || null,
|
||||
};
|
||||
|
||||
await this.redis.set(BRANDING_LOGO_KEY, JSON.stringify(data));
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ interface BrandingData {
|
|||
sidebarColor: string | null;
|
||||
logoWidth: number | null;
|
||||
sidebarWidth: number | null;
|
||||
loginBgType: 'gradient' | 'solid' | 'image' | null;
|
||||
loginBgColor1: string | null;
|
||||
loginBgColor2: string | null;
|
||||
loginBgImage: string | null;
|
||||
}
|
||||
|
||||
const SIDEBAR_PRESETS = [
|
||||
|
|
@ -63,10 +67,15 @@ const labelStyle: React.CSSProperties = {
|
|||
export function AdminCustomizePage() {
|
||||
const queryClient = useQueryClient();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const loginBgFileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [logo, setLogo] = useState<string | null>(null);
|
||||
const [sidebarColor, setSidebarColor] = useState<string>('#1e293b');
|
||||
const [logoWidth, setLogoWidth] = useState<number>(160);
|
||||
const [sidebarWidth, setSidebarWidth] = useState<number>(240);
|
||||
const [loginBgType, setLoginBgType] = useState<'gradient' | 'solid' | 'image'>('gradient');
|
||||
const [loginBgColor1, setLoginBgColor1] = useState<string>('#1a56db');
|
||||
const [loginBgColor2, setLoginBgColor2] = useState<string>('#1e3a5f');
|
||||
const [loginBgImage, setLoginBgImage] = useState<string | null>(null);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [saveSuccess, setSaveSuccess] = useState(false);
|
||||
|
||||
|
|
@ -84,6 +93,10 @@ export function AdminCustomizePage() {
|
|||
setSidebarColor(data.sidebarColor || '#1e293b');
|
||||
setLogoWidth(data.logoWidth ?? 160);
|
||||
setSidebarWidth(data.sidebarWidth ?? 240);
|
||||
setLoginBgType(data.loginBgType || 'gradient');
|
||||
setLoginBgColor1(data.loginBgColor1 || '#1a56db');
|
||||
setLoginBgColor2(data.loginBgColor2 || '#1e3a5f');
|
||||
setLoginBgImage(data.loginBgImage || null);
|
||||
setHasChanges(false);
|
||||
}
|
||||
}, [data]);
|
||||
|
|
@ -94,6 +107,10 @@ export function AdminCustomizePage() {
|
|||
sidebarColor: string;
|
||||
logoWidth: number;
|
||||
sidebarWidth: number;
|
||||
loginBgType: string;
|
||||
loginBgColor1: string;
|
||||
loginBgColor2: string;
|
||||
loginBgImage: string | null;
|
||||
}) => {
|
||||
const res = await api.post('/settings/branding', branding);
|
||||
return res.data;
|
||||
|
|
@ -137,9 +154,44 @@ export function AdminCustomizePage() {
|
|||
};
|
||||
|
||||
const handleSave = () => {
|
||||
saveMutation.mutate({ logo, sidebarColor, logoWidth, sidebarWidth });
|
||||
saveMutation.mutate({ logo, sidebarColor, logoWidth, sidebarWidth, loginBgType, loginBgColor1, loginBgColor2, loginBgImage });
|
||||
};
|
||||
|
||||
const handleLoginBgFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!file.type.startsWith('image/')) {
|
||||
alert('Bitte nur Bilddateien hochladen');
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size > 2_000_000) {
|
||||
alert('Datei darf maximal 2MB gross sein');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
setLoginBgImage(reader.result as string);
|
||||
setHasChanges(true);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const loginBgPreviewStyle: React.CSSProperties = (() => {
|
||||
if (loginBgType === 'gradient') {
|
||||
return { background: `linear-gradient(135deg, ${loginBgColor1} 0%, ${loginBgColor2} 100%)` };
|
||||
}
|
||||
if (loginBgType === 'solid') {
|
||||
return { background: loginBgColor1 };
|
||||
}
|
||||
if (loginBgType === 'image' && loginBgImage) {
|
||||
return { background: `url(${loginBgImage}) center/cover no-repeat` };
|
||||
}
|
||||
return { background: `linear-gradient(135deg, ${loginBgColor1} 0%, ${loginBgColor2} 100%)` };
|
||||
})();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: '1.5rem' }}>
|
||||
|
|
@ -511,6 +563,176 @@ export function AdminCustomizePage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Login-Hintergrund */}
|
||||
<div style={cardStyle}>
|
||||
<h3 style={{ fontSize: '1rem', fontWeight: 600, marginBottom: '0.5rem' }}>
|
||||
Login-Hintergrund
|
||||
</h3>
|
||||
<p style={{ fontSize: '0.8125rem', color: 'var(--color-text-secondary)', marginBottom: '1.25rem' }}>
|
||||
Hintergrund des Login-Screens. Das Plattform-Logo (oben) wird ebenfalls auf dem Login-Screen angezeigt.
|
||||
</p>
|
||||
|
||||
{/* Typ-Toggle */}
|
||||
<div style={{ marginBottom: '1.25rem' }}>
|
||||
<label style={labelStyle}>Hintergrundtyp</label>
|
||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||||
{(['gradient', 'solid', 'image'] as const).map((type) => (
|
||||
<button
|
||||
key={type}
|
||||
onClick={() => { setLoginBgType(type); setHasChanges(true); }}
|
||||
style={{
|
||||
padding: '0.375rem 0.875rem',
|
||||
fontSize: '0.8125rem',
|
||||
border: loginBgType === type ? '2px solid var(--color-primary)' : '1px solid var(--color-border)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
background: loginBgType === type ? 'var(--color-primary-light)' : 'none',
|
||||
color: 'var(--color-text)',
|
||||
cursor: 'pointer',
|
||||
fontWeight: loginBgType === type ? 600 : 400,
|
||||
}}
|
||||
>
|
||||
{type === 'gradient' ? 'Farbverlauf' : type === 'solid' ? 'Einfarbig' : 'Hintergrundbild'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Farbverlauf */}
|
||||
{loginBgType === 'gradient' && (
|
||||
<div style={{ display: 'flex', gap: '2rem', marginBottom: '1.25rem', flexWrap: 'wrap' }}>
|
||||
<div>
|
||||
<label style={labelStyle}>Von</label>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<input
|
||||
type="color"
|
||||
value={loginBgColor1}
|
||||
onChange={(e) => { setLoginBgColor1(e.target.value); setHasChanges(true); }}
|
||||
style={{ width: 40, height: 36, padding: 0, border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', cursor: 'pointer' }}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={loginBgColor1}
|
||||
onChange={(e) => { setLoginBgColor1(e.target.value); setHasChanges(true); }}
|
||||
style={{ padding: '0.375rem 0.5rem', fontSize: '0.875rem', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'var(--color-bg-card)', color: 'var(--color-text)', width: 100, fontFamily: 'monospace' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label style={labelStyle}>Bis</label>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<input
|
||||
type="color"
|
||||
value={loginBgColor2}
|
||||
onChange={(e) => { setLoginBgColor2(e.target.value); setHasChanges(true); }}
|
||||
style={{ width: 40, height: 36, padding: 0, border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', cursor: 'pointer' }}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={loginBgColor2}
|
||||
onChange={(e) => { setLoginBgColor2(e.target.value); setHasChanges(true); }}
|
||||
style={{ padding: '0.375rem 0.5rem', fontSize: '0.875rem', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'var(--color-bg-card)', color: 'var(--color-text)', width: 100, fontFamily: 'monospace' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Einfarbig */}
|
||||
{loginBgType === 'solid' && (
|
||||
<div style={{ marginBottom: '1.25rem' }}>
|
||||
<label style={labelStyle}>Farbe</label>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||||
<input
|
||||
type="color"
|
||||
value={loginBgColor1}
|
||||
onChange={(e) => { setLoginBgColor1(e.target.value); setHasChanges(true); }}
|
||||
style={{ width: 40, height: 40, padding: 0, border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', cursor: 'pointer' }}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={loginBgColor1}
|
||||
onChange={(e) => { setLoginBgColor1(e.target.value); setHasChanges(true); }}
|
||||
style={{ padding: '0.5rem 0.75rem', fontSize: '0.875rem', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'var(--color-bg-card)', color: 'var(--color-text)', width: 120, fontFamily: 'monospace' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hintergrundbild */}
|
||||
{loginBgType === 'image' && (
|
||||
<div style={{ marginBottom: '1.25rem' }}>
|
||||
<label style={labelStyle}>Hintergrundbild</label>
|
||||
<p style={{ fontSize: '0.8125rem', color: 'var(--color-text-secondary)', marginBottom: '0.75rem' }}>
|
||||
Empfohlen: JPG oder PNG, min. 1920×1080px, max. 2MB.
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||
<input
|
||||
ref={loginBgFileInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleLoginBgFileSelect}
|
||||
/>
|
||||
<button style={btnSecondary} onClick={() => loginBgFileInputRef.current?.click()}>
|
||||
Bild hochladen
|
||||
</button>
|
||||
{loginBgImage && (
|
||||
<button
|
||||
style={{ ...btnSecondary, color: 'var(--color-error)', borderColor: 'var(--color-error)' }}
|
||||
onClick={() => { setLoginBgImage(null); setHasChanges(true); }}
|
||||
>
|
||||
Entfernen
|
||||
</button>
|
||||
)}
|
||||
{loginBgImage && (
|
||||
<span style={{ fontSize: '0.8125rem', color: 'var(--color-success)' }}>✓ Bild geladen</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vorschau */}
|
||||
<div>
|
||||
<label style={labelStyle}>Vorschau</label>
|
||||
<div
|
||||
style={{
|
||||
...loginBgPreviewStyle,
|
||||
width: 200,
|
||||
height: 120,
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
border: '1px solid var(--color-border)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{/* Mini-Login-Card */}
|
||||
<div
|
||||
style={{
|
||||
width: 80,
|
||||
height: 72,
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: 6,
|
||||
padding: '0.5rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{logo ? (
|
||||
<img src={logo} alt="" style={{ maxWidth: 40, maxHeight: 14, objectFit: 'contain' }} />
|
||||
) : (
|
||||
<div style={{ fontSize: '0.5rem', fontWeight: 700, color: 'var(--color-primary)', letterSpacing: 1 }}>INSIGHT</div>
|
||||
)}
|
||||
<div style={{ width: '100%', height: 8, background: '#e5e7eb', borderRadius: 2 }} />
|
||||
<div style={{ width: '100%', height: 8, background: '#e5e7eb', borderRadius: 2 }} />
|
||||
<div style={{ width: '100%', height: 10, background: 'var(--color-primary)', borderRadius: 2, opacity: 0.8 }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Speichern */}
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@
|
|||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.logoImage {
|
||||
max-width: 180px;
|
||||
max-height: 64px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import { useState, useEffect, type FormEvent } from 'react';
|
||||
import { useState, useEffect, useMemo, type FormEvent } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import api from '../api/client';
|
||||
import { useAuth } from './AuthContext';
|
||||
import styles from './LoginPage.module.css';
|
||||
|
||||
interface BrandingData {
|
||||
logo: string | null;
|
||||
loginBgType: 'gradient' | 'solid' | 'image' | null;
|
||||
loginBgColor1: string | null;
|
||||
loginBgColor2: string | null;
|
||||
loginBgImage: string | null;
|
||||
}
|
||||
|
||||
export function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
|
@ -25,6 +33,35 @@ export function LoginPage() {
|
|||
}
|
||||
}, [searchParams]);
|
||||
|
||||
// Branding laden (kein Auth-Guard auf diesem Endpoint)
|
||||
const { data: branding } = useQuery<BrandingData>({
|
||||
queryKey: ['settings', 'branding'],
|
||||
queryFn: async () => {
|
||||
const res = await api.get<BrandingData>('/settings/branding');
|
||||
return res.data;
|
||||
},
|
||||
staleTime: 5 * 60 * 1000,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
// Dynamischer Hintergrund basierend auf Branding
|
||||
const containerStyle = useMemo((): React.CSSProperties => {
|
||||
if (!branding) return {};
|
||||
const type = branding.loginBgType || 'gradient';
|
||||
if (type === 'gradient') {
|
||||
const c1 = branding.loginBgColor1 || '#1a56db';
|
||||
const c2 = branding.loginBgColor2 || '#1e3a5f';
|
||||
return { background: `linear-gradient(135deg, ${c1} 0%, ${c2} 100%)` };
|
||||
}
|
||||
if (type === 'solid') {
|
||||
return { background: branding.loginBgColor1 || '#1a56db' };
|
||||
}
|
||||
if (type === 'image' && branding.loginBgImage) {
|
||||
return { background: `url(${branding.loginBgImage}) center/cover no-repeat` };
|
||||
}
|
||||
return {};
|
||||
}, [branding]);
|
||||
|
||||
// SSO-Status abfragen: Ist Microsoft SSO konfiguriert?
|
||||
const { data: ssoStatus } = useQuery<{ microsoft: boolean }>({
|
||||
queryKey: ['sso', 'status'],
|
||||
|
|
@ -70,10 +107,14 @@ export function LoginPage() {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.container} style={containerStyle}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.logo}>
|
||||
<h1>INSIGHT</h1>
|
||||
{branding?.logo ? (
|
||||
<img src={branding.logo} alt="Logo" className={styles.logoImage} />
|
||||
) : (
|
||||
<h1>INSIGHT</h1>
|
||||
)}
|
||||
<p>Business Platform</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue