mobile desighn test4
#deploy
This commit is contained in:
parent
6a0381ff99
commit
af78237985
BIN
public/images/robo/robo_event_dark.png
Normal file
BIN
public/images/robo/robo_event_dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
21
src/App.tsx
21
src/App.tsx
@ -7,6 +7,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|||||||
import { HelmetProvider } from "react-helmet-async";
|
import { HelmetProvider } from "react-helmet-async";
|
||||||
import { LanguageProvider } from "@/contexts/LanguageContext";
|
import { LanguageProvider } from "@/contexts/LanguageContext";
|
||||||
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
|
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
|
||||||
|
import { ThemeProvider } from "@/contexts/ThemeContext";
|
||||||
import Main from "./components/app/Main";
|
import Main from "./components/app/Main";
|
||||||
import LoginContainer from "./components/auth/LoginContainer";
|
import LoginContainer from "./components/auth/LoginContainer";
|
||||||
import ArtistCollabForm from "@/features/event/components/ArtistCollabForm";
|
import ArtistCollabForm from "@/features/event/components/ArtistCollabForm";
|
||||||
@ -31,15 +32,17 @@ const App = () => (
|
|||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<LanguageProvider>
|
<ThemeProvider>
|
||||||
<AuthProvider>
|
<LanguageProvider>
|
||||||
<Toaster />
|
<AuthProvider>
|
||||||
<Sonner />
|
<Toaster />
|
||||||
<BrowserRouter>
|
<Sonner />
|
||||||
<AppContent />
|
<BrowserRouter>
|
||||||
</BrowserRouter>
|
<AppContent />
|
||||||
</AuthProvider>
|
</BrowserRouter>
|
||||||
</LanguageProvider>
|
</AuthProvider>
|
||||||
|
</LanguageProvider>
|
||||||
|
</ThemeProvider>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { LogOut, Menu } from 'lucide-react';
|
import { LogOut, Menu, Moon, Sun } from 'lucide-react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import DashboardContainer from '../dashboard/DashboardContainer';
|
import DashboardContainer from '../dashboard/DashboardContainer';
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||||
@ -17,6 +18,7 @@ const MainShell: React.FC = () => {
|
|||||||
}, [resetHeader]);
|
}, [resetHeader]);
|
||||||
|
|
||||||
const { logout } = useAuth();
|
const { logout } = useAuth();
|
||||||
|
const { theme, toggleTheme } = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [hasActiveEvent, setHasActiveEvent] = useState(false);
|
const [hasActiveEvent, setHasActiveEvent] = useState(false);
|
||||||
// header is already available above
|
// header is already available above
|
||||||
@ -71,13 +73,13 @@ const MainShell: React.FC = () => {
|
|||||||
<img
|
<img
|
||||||
src="/icon.png"
|
src="/icon.png"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
className="h-9 w-9 object-contain"
|
className="h-9 w-9 object-contain dark:invert dark:brightness-125"
|
||||||
/>
|
/>
|
||||||
{!header.left && (
|
{!header.left && (
|
||||||
<img
|
<img
|
||||||
src="/Logo.png"
|
src="/Logo.png"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
className="hidden md:block h-10 w-auto object-contain"
|
className="hidden md:block h-10 w-auto object-contain dark:invert dark:brightness-125"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -91,6 +93,10 @@ const MainShell: React.FC = () => {
|
|||||||
<DropdownMenuContent align="start">
|
<DropdownMenuContent align="start">
|
||||||
{header.mobileMenu}
|
{header.mobileMenu}
|
||||||
{header.mobileMenu ? <DropdownMenuSeparator /> : null}
|
{header.mobileMenu ? <DropdownMenuSeparator /> : null}
|
||||||
|
<DropdownMenuItem onClick={toggleTheme} className="gap-2">
|
||||||
|
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
||||||
|
<span>{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={logout} className="gap-2">
|
<DropdownMenuItem onClick={logout} className="gap-2">
|
||||||
<LogOut className="h-4 w-4" />
|
<LogOut className="h-4 w-4" />
|
||||||
<span>Abmelden</span>
|
<span>Abmelden</span>
|
||||||
@ -108,6 +114,15 @@ const MainShell: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 pointer-events-auto">
|
<div className="flex items-center gap-2 pointer-events-auto">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="hidden md:inline-flex"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
aria-label={theme === 'dark' ? 'Light Mode aktivieren' : 'Dark Mode aktivieren'}
|
||||||
|
>
|
||||||
|
{theme === 'dark' ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -31,7 +31,7 @@ const LoginForm: React.FC<LoginFormProps> = ({ onSubmit, isLoading: externalLoad
|
|||||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-primary/20 to-secondary/20">
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-primary/20 to-secondary/20">
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<img src="/Logo.png" alt="Logo" className="mx-auto mb-2 h-24 w-40 object-contain" />
|
<img src="/Logo.png" alt="Logo" className="mx-auto mb-2 h-24 w-40 object-contain dark:invert dark:brightness-125" />
|
||||||
<CardTitle className="text-2xl font-bold text-center">Login Erforderlich</CardTitle>
|
<CardTitle className="text-2xl font-bold text-center">Login Erforderlich</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { type Event } from '@/features/event/api/eventService';
|
|||||||
import { type DashboardModule, type DashboardModuleContext } from '@/components/dashboard/dashboardModuleTypes';
|
import { type DashboardModule, type DashboardModuleContext } from '@/components/dashboard/dashboardModuleTypes';
|
||||||
import { eventDashboardModule } from '@/features/event/dashboardModule';
|
import { eventDashboardModule } from '@/features/event/dashboardModule';
|
||||||
import { quickPostDashboardModule } from '@/features/photopost/dashboardModule';
|
import { quickPostDashboardModule } from '@/features/photopost/dashboardModule';
|
||||||
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
|
|
||||||
const DASHBOARD_ACTIVE_MODULE_KEY = 'kgb.dashboard.activeModuleId';
|
const DASHBOARD_ACTIVE_MODULE_KEY = 'kgb.dashboard.activeModuleId';
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ interface DashboardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Dashboard: React.FC<DashboardProps> = ({ onActionClick, hasActiveEvent, currentEvent }) => {
|
const Dashboard: React.FC<DashboardProps> = ({ onActionClick, hasActiveEvent, currentEvent }) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const [activeModuleId, setActiveModuleId] = useState<string | null>(() => {
|
const [activeModuleId, setActiveModuleId] = useState<string | null>(() => {
|
||||||
try {
|
try {
|
||||||
return sessionStorage.getItem(DASHBOARD_ACTIVE_MODULE_KEY);
|
return sessionStorage.getItem(DASHBOARD_ACTIVE_MODULE_KEY);
|
||||||
@ -110,7 +112,11 @@ const Dashboard: React.FC<DashboardProps> = ({ onActionClick, hasActiveEvent, cu
|
|||||||
title={module.getTitle(ctx)}
|
title={module.getTitle(ctx)}
|
||||||
description={module.getDescription(ctx)}
|
description={module.getDescription(ctx)}
|
||||||
onClick={() => setActiveModuleId(module.id)}
|
onClick={() => setActiveModuleId(module.id)}
|
||||||
illustration={module.getIllustration?.(ctx)}
|
illustration={
|
||||||
|
theme === 'dark'
|
||||||
|
? module.getIllustrationDark?.(ctx) ?? module.getIllustration?.(ctx)
|
||||||
|
: module.getIllustration?.(ctx)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export interface DashboardModule {
|
|||||||
getTitle: (ctx: DashboardModuleContext) => string;
|
getTitle: (ctx: DashboardModuleContext) => string;
|
||||||
getDescription: (ctx: DashboardModuleContext) => string;
|
getDescription: (ctx: DashboardModuleContext) => string;
|
||||||
getIllustration?: (ctx: DashboardModuleContext) => string | undefined;
|
getIllustration?: (ctx: DashboardModuleContext) => string | undefined;
|
||||||
|
getIllustrationDark?: (ctx: DashboardModuleContext) => string | undefined;
|
||||||
isVisible?: (ctx: DashboardModuleContext) => boolean;
|
isVisible?: (ctx: DashboardModuleContext) => boolean;
|
||||||
renderWizard: (ctx: DashboardModuleContext, onBack: () => void) => React.ReactNode;
|
renderWizard: (ctx: DashboardModuleContext, onBack: () => void) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/contexts/ThemeContext.tsx
Normal file
62
src/contexts/ThemeContext.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
type Theme = 'light' | 'dark';
|
||||||
|
|
||||||
|
type ThemeContextValue = {
|
||||||
|
theme: Theme;
|
||||||
|
setTheme: (next: Theme) => void;
|
||||||
|
toggleTheme: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const THEME_STORAGE_KEY = 'kgb.theme';
|
||||||
|
|
||||||
|
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
||||||
|
|
||||||
|
function getSystemTheme(): Theme {
|
||||||
|
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return 'light';
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyThemeToDom(theme: Theme) {
|
||||||
|
if (typeof document === 'undefined') return;
|
||||||
|
document.documentElement.classList.toggle('dark', theme === 'dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const [theme, setThemeState] = useState<Theme>(() => {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(THEME_STORAGE_KEY);
|
||||||
|
if (stored === 'light' || stored === 'dark') return stored;
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return getSystemTheme();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
applyThemeToDom(theme);
|
||||||
|
try {
|
||||||
|
localStorage.setItem(THEME_STORAGE_KEY, theme);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
const setTheme = useCallback((next: Theme) => {
|
||||||
|
setThemeState(next);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleTheme = useCallback(() => {
|
||||||
|
setThemeState((prev) => (prev === 'dark' ? 'light' : 'dark'));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const value = useMemo(() => ({ theme, setTheme, toggleTheme }), [theme, setTheme, toggleTheme]);
|
||||||
|
|
||||||
|
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTheme = () => {
|
||||||
|
const ctx = useContext(ThemeContext);
|
||||||
|
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
|
import { DropdownMenuItem, DropdownMenuSeparator } from '@/components/ui/dropdown-menu';
|
||||||
import { type Event } from '@/features/event/api/eventService';
|
import { type Event } from '@/features/event/api/eventService';
|
||||||
import { ArrowLeft, LayoutGrid } from 'lucide-react';
|
import { ArrowLeft, LayoutGrid } from 'lucide-react';
|
||||||
import { useHeader } from '@/contexts/HeaderContext';
|
import { useHeader } from '@/contexts/HeaderContext';
|
||||||
@ -103,6 +103,7 @@ const EventWizard: React.FC<EventWizardProps> = ({ onComplete, onBackToOverview,
|
|||||||
<LayoutGrid className="h-4 w-4" />
|
<LayoutGrid className="h-4 w-4" />
|
||||||
<span>Zurück zum Dashboard</span>
|
<span>Zurück zum Dashboard</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
{showForm ? <DropdownMenuSeparator /> : null}
|
||||||
{showForm && (
|
{showForm && (
|
||||||
<DropdownMenuItem onClick={handleBackToTimeline} className="gap-2">
|
<DropdownMenuItem onClick={handleBackToTimeline} className="gap-2">
|
||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export const eventDashboardModule: DashboardModule = {
|
|||||||
getTitle: () => 'Veranstaltung',
|
getTitle: () => 'Veranstaltung',
|
||||||
getDescription: () => 'Verwalte deine Veranstaltungen',
|
getDescription: () => 'Verwalte deine Veranstaltungen',
|
||||||
getIllustration: () => '/images/robo/robo_event.png',
|
getIllustration: () => '/images/robo/robo_event.png',
|
||||||
|
getIllustrationDark: () => '/images/robo/robo_event_dark.png',
|
||||||
renderWizard: (_ctx, onBack) => (
|
renderWizard: (_ctx, onBack) => (
|
||||||
<EventWizardContainer onComplete={onBack} onBackToOverview={onBack} />
|
<EventWizardContainer onComplete={onBack} onBackToOverview={onBack} />
|
||||||
),
|
),
|
||||||
|
|||||||
@ -69,7 +69,7 @@
|
|||||||
--accent: 175 70% 41%;
|
--accent: 175 70% 41%;
|
||||||
--accent-foreground: 210 40% 98%;
|
--accent-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 84.2% 62%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--border: 217.2 32.6% 17.5%;
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user