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 |
@ -7,6 +7,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
import { LanguageProvider } from "@/contexts/LanguageContext";
|
||||
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
|
||||
import { ThemeProvider } from "@/contexts/ThemeContext";
|
||||
import Main from "./components/app/Main";
|
||||
import LoginContainer from "./components/auth/LoginContainer";
|
||||
import ArtistCollabForm from "@/features/event/components/ArtistCollabForm";
|
||||
@ -31,6 +32,7 @@ const App = () => (
|
||||
<HelmetProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TooltipProvider>
|
||||
<ThemeProvider>
|
||||
<LanguageProvider>
|
||||
<AuthProvider>
|
||||
<Toaster />
|
||||
@ -40,6 +42,7 @@ const App = () => (
|
||||
</BrowserRouter>
|
||||
</AuthProvider>
|
||||
</LanguageProvider>
|
||||
</ThemeProvider>
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
</HelmetProvider>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
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 { useTheme } from '@/contexts/ThemeContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DashboardContainer from '../dashboard/DashboardContainer';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
@ -17,6 +18,7 @@ const MainShell: React.FC = () => {
|
||||
}, [resetHeader]);
|
||||
|
||||
const { logout } = useAuth();
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [hasActiveEvent, setHasActiveEvent] = useState(false);
|
||||
// header is already available above
|
||||
@ -71,13 +73,13 @@ const MainShell: React.FC = () => {
|
||||
<img
|
||||
src="/icon.png"
|
||||
alt="Logo"
|
||||
className="h-9 w-9 object-contain"
|
||||
className="h-9 w-9 object-contain dark:invert dark:brightness-125"
|
||||
/>
|
||||
{!header.left && (
|
||||
<img
|
||||
src="/Logo.png"
|
||||
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>
|
||||
@ -91,6 +93,10 @@ const MainShell: React.FC = () => {
|
||||
<DropdownMenuContent align="start">
|
||||
{header.mobileMenu}
|
||||
{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">
|
||||
<LogOut className="h-4 w-4" />
|
||||
<span>Abmelden</span>
|
||||
@ -108,6 +114,15 @@ const MainShell: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<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
|
||||
variant="outline"
|
||||
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">
|
||||
<Card className="w-full max-w-md">
|
||||
<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>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
@ -4,6 +4,7 @@ import { type Event } from '@/features/event/api/eventService';
|
||||
import { type DashboardModule, type DashboardModuleContext } from '@/components/dashboard/dashboardModuleTypes';
|
||||
import { eventDashboardModule } from '@/features/event/dashboardModule';
|
||||
import { quickPostDashboardModule } from '@/features/photopost/dashboardModule';
|
||||
import { useTheme } from '@/contexts/ThemeContext';
|
||||
|
||||
const DASHBOARD_ACTIVE_MODULE_KEY = 'kgb.dashboard.activeModuleId';
|
||||
|
||||
@ -51,6 +52,7 @@ interface DashboardProps {
|
||||
}
|
||||
|
||||
const Dashboard: React.FC<DashboardProps> = ({ onActionClick, hasActiveEvent, currentEvent }) => {
|
||||
const { theme } = useTheme();
|
||||
const [activeModuleId, setActiveModuleId] = useState<string | null>(() => {
|
||||
try {
|
||||
return sessionStorage.getItem(DASHBOARD_ACTIVE_MODULE_KEY);
|
||||
@ -110,7 +112,11 @@ const Dashboard: React.FC<DashboardProps> = ({ onActionClick, hasActiveEvent, cu
|
||||
title={module.getTitle(ctx)}
|
||||
description={module.getDescription(ctx)}
|
||||
onClick={() => setActiveModuleId(module.id)}
|
||||
illustration={module.getIllustration?.(ctx)}
|
||||
illustration={
|
||||
theme === 'dark'
|
||||
? module.getIllustrationDark?.(ctx) ?? module.getIllustration?.(ctx)
|
||||
: module.getIllustration?.(ctx)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -10,6 +10,7 @@ export interface DashboardModule {
|
||||
getTitle: (ctx: DashboardModuleContext) => string;
|
||||
getDescription: (ctx: DashboardModuleContext) => string;
|
||||
getIllustration?: (ctx: DashboardModuleContext) => string | undefined;
|
||||
getIllustrationDark?: (ctx: DashboardModuleContext) => string | undefined;
|
||||
isVisible?: (ctx: DashboardModuleContext) => boolean;
|
||||
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 { Card, CardContent } from "@/components/ui/card";
|
||||
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 { ArrowLeft, LayoutGrid } from 'lucide-react';
|
||||
import { useHeader } from '@/contexts/HeaderContext';
|
||||
@ -103,6 +103,7 @@ const EventWizard: React.FC<EventWizardProps> = ({ onComplete, onBackToOverview,
|
||||
<LayoutGrid className="h-4 w-4" />
|
||||
<span>Zurück zum Dashboard</span>
|
||||
</DropdownMenuItem>
|
||||
{showForm ? <DropdownMenuSeparator /> : null}
|
||||
{showForm && (
|
||||
<DropdownMenuItem onClick={handleBackToTimeline} className="gap-2">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
|
||||
@ -7,6 +7,7 @@ export const eventDashboardModule: DashboardModule = {
|
||||
getTitle: () => 'Veranstaltung',
|
||||
getDescription: () => 'Verwalte deine Veranstaltungen',
|
||||
getIllustration: () => '/images/robo/robo_event.png',
|
||||
getIllustrationDark: () => '/images/robo/robo_event_dark.png',
|
||||
renderWizard: (_ctx, onBack) => (
|
||||
<EventWizardContainer onComplete={onBack} onBackToOverview={onBack} />
|
||||
),
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
--accent: 175 70% 41%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive: 0 84.2% 62%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user