feat: enhance login page with random theme and background features
- Added RandomThemeApplier to apply a random theme on login. - Introduced RandomBackground component for setting a random background from presets. - Updated GlobalKeyboardShortcuts import in RootLayout for consistent keyboard shortcut handling. - Refactored BackgroundContext to include cycleBackground functionality for dynamic background changes. - Removed deprecated useBackgroundCycle hook to streamline background management.
This commit is contained in:
@@ -58,8 +58,8 @@ export default async function RootLayout({
|
|||||||
<KeyboardShortcuts />
|
<KeyboardShortcuts />
|
||||||
<JiraConfigProvider config={initialPreferences?.jiraConfig || { enabled: false }}>
|
<JiraConfigProvider config={initialPreferences?.jiraConfig || { enabled: false }}>
|
||||||
<UserPreferencesProvider initialPreferences={initialPreferences}>
|
<UserPreferencesProvider initialPreferences={initialPreferences}>
|
||||||
<GlobalKeyboardShortcuts />
|
|
||||||
<BackgroundProvider>
|
<BackgroundProvider>
|
||||||
|
<GlobalKeyboardShortcuts />
|
||||||
{children}
|
{children}
|
||||||
</BackgroundProvider>
|
</BackgroundProvider>
|
||||||
</UserPreferencesProvider>
|
</UserPreferencesProvider>
|
||||||
|
|||||||
@@ -8,8 +8,56 @@ import { Button } from '@/components/ui/Button'
|
|||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
import { TowerLogo } from '@/components/TowerLogo'
|
import { TowerLogo } from '@/components/TowerLogo'
|
||||||
import { TowerBackground } from '@/components/TowerBackground'
|
import { TowerBackground } from '@/components/TowerBackground'
|
||||||
|
import { THEME_CONFIG } from '@/lib/theme-config'
|
||||||
|
import { useTheme } from '@/contexts/ThemeContext'
|
||||||
|
import { PRESET_BACKGROUNDS } from '@/lib/ui-config'
|
||||||
|
|
||||||
export default function LoginPage() {
|
function RandomThemeApplier() {
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
const [applied, setApplied] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!applied) {
|
||||||
|
// Sélectionner un thème aléatoire côté client seulement
|
||||||
|
const randomTheme = THEME_CONFIG.allThemes[Math.floor(Math.random() * THEME_CONFIG.allThemes.length)];
|
||||||
|
console.log('Applying random theme:', randomTheme);
|
||||||
|
|
||||||
|
// Utiliser setTheme du ThemeContext pour forcer le changement
|
||||||
|
setTheme(randomTheme);
|
||||||
|
setApplied(true);
|
||||||
|
}
|
||||||
|
}, [applied, setTheme]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RandomBackground() {
|
||||||
|
const [background, setBackground] = useState<string>('var(--background)');
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Sélectionner un background aléatoire parmi les presets (sauf 'none')
|
||||||
|
const availableBackgrounds = PRESET_BACKGROUNDS.filter(bg => bg.id !== 'none');
|
||||||
|
const randomBackground = availableBackgrounds[Math.floor(Math.random() * availableBackgrounds.length)];
|
||||||
|
setBackground(randomBackground.preview);
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 -z-10"
|
||||||
|
style={{ background: mounted ? background : 'var(--background)' }}
|
||||||
|
>
|
||||||
|
{/* Effet de profondeur */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent"></div>
|
||||||
|
|
||||||
|
{/* Effet de lumière */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-transparent via-white/5 to-transparent"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoginPageContent() {
|
||||||
const [email, setEmail] = useState('')
|
const [email, setEmail] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
@@ -71,7 +119,8 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen relative overflow-hidden">
|
<div className="min-h-screen relative overflow-hidden">
|
||||||
<TowerBackground />
|
<RandomThemeApplier />
|
||||||
|
<RandomBackground />
|
||||||
|
|
||||||
{/* Contenu principal */}
|
{/* Contenu principal */}
|
||||||
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
||||||
@@ -150,3 +199,7 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
return <LoginPageContent />
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
|
import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
|
||||||
import { useBackgroundCycle } from '@/hooks/useBackgroundCycle';
|
import { useBackground } from '@/contexts/BackgroundContext';
|
||||||
|
|
||||||
export function GlobalKeyboardShortcuts() {
|
export function GlobalKeyboardShortcuts() {
|
||||||
const { cycleBackground } = useBackgroundCycle();
|
const { cycleBackground } = useBackground();
|
||||||
|
|
||||||
useGlobalKeyboardShortcuts({
|
useGlobalKeyboardShortcuts({
|
||||||
onCycleBackground: cycleBackground
|
onCycleBackground: cycleBackground
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||||
import { useUserPreferences } from './UserPreferencesContext';
|
import { useUserPreferences } from './UserPreferencesContext';
|
||||||
import { PRESET_BACKGROUNDS } from '@/lib/ui-config';
|
import { PRESET_BACKGROUNDS, BACKGROUND_NAMES, TOAST_ICONS, getNextBackground } from '@/lib/ui-config';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { useToast } from '@/components/ui/Toast';
|
||||||
|
|
||||||
interface BackgroundContextType {
|
interface BackgroundContextType {
|
||||||
backgroundImage: string | undefined;
|
backgroundImage: string | undefined;
|
||||||
setBackgroundImage: (image: string | undefined) => void;
|
setBackgroundImage: (image: string | undefined) => void;
|
||||||
|
cycleBackground: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackgroundContext = createContext<BackgroundContextType | undefined>(undefined);
|
const BackgroundContext = createContext<BackgroundContextType | undefined>(undefined);
|
||||||
@@ -16,7 +19,9 @@ interface BackgroundProviderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
||||||
const { preferences } = useUserPreferences();
|
const { preferences, updateViewPreferences } = useUserPreferences();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const { showToast } = useToast();
|
||||||
const [backgroundImage, setBackgroundImageState] = useState<string | undefined>(
|
const [backgroundImage, setBackgroundImageState] = useState<string | undefined>(
|
||||||
preferences?.viewPreferences?.backgroundImage
|
preferences?.viewPreferences?.backgroundImage
|
||||||
);
|
);
|
||||||
@@ -33,9 +38,10 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
|||||||
setMounted(true);
|
setMounted(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Sync with preferences
|
// Sync with preferences (only for authenticated users)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (preferences?.viewPreferences?.backgroundImage !== backgroundImage) {
|
// Only sync if user is authenticated
|
||||||
|
if (session?.user?.id && preferences?.viewPreferences?.backgroundImage !== backgroundImage) {
|
||||||
setBackgroundImageState(preferences?.viewPreferences?.backgroundImage);
|
setBackgroundImageState(preferences?.viewPreferences?.backgroundImage);
|
||||||
}
|
}
|
||||||
if (preferences?.viewPreferences?.backgroundBlur !== backgroundBlur) {
|
if (preferences?.viewPreferences?.backgroundBlur !== backgroundBlur) {
|
||||||
@@ -44,7 +50,7 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
|||||||
if (preferences?.viewPreferences?.backgroundOpacity !== backgroundOpacity) {
|
if (preferences?.viewPreferences?.backgroundOpacity !== backgroundOpacity) {
|
||||||
setBackgroundOpacityState(preferences?.viewPreferences?.backgroundOpacity || 100);
|
setBackgroundOpacityState(preferences?.viewPreferences?.backgroundOpacity || 100);
|
||||||
}
|
}
|
||||||
}, [preferences?.viewPreferences?.backgroundImage, preferences?.viewPreferences?.backgroundBlur, preferences?.viewPreferences?.backgroundOpacity, backgroundImage, backgroundBlur, backgroundOpacity]);
|
}, [preferences?.viewPreferences?.backgroundImage, preferences?.viewPreferences?.backgroundBlur, preferences?.viewPreferences?.backgroundOpacity, backgroundImage, backgroundBlur, backgroundOpacity, session?.user?.id]);
|
||||||
|
|
||||||
// Apply background image to document body
|
// Apply background image to document body
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -58,6 +64,7 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (backgroundImage) {
|
if (backgroundImage) {
|
||||||
|
console.log('Creating background element for:', backgroundImage);
|
||||||
// Créer un élément div pour l'image de fond avec les effets
|
// Créer un élément div pour l'image de fond avec les effets
|
||||||
const backgroundElement = document.createElement('div');
|
const backgroundElement = document.createElement('div');
|
||||||
backgroundElement.id = 'custom-background';
|
backgroundElement.id = 'custom-background';
|
||||||
@@ -76,11 +83,15 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
|||||||
// Trouver le preset correspondant
|
// Trouver le preset correspondant
|
||||||
const preset = PRESET_BACKGROUNDS.find(p => p.id === backgroundImage);
|
const preset = PRESET_BACKGROUNDS.find(p => p.id === backgroundImage);
|
||||||
if (preset) {
|
if (preset) {
|
||||||
backgroundElement.style.backgroundImage = preset.preview;
|
// Appliquer le gradient directement
|
||||||
|
backgroundElement.style.background = preset.preview;
|
||||||
|
// Ajouter une classe pour identifier le type de background
|
||||||
|
backgroundElement.className = `preset-background preset-${preset.id}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Appliquer l'URL d'image personnalisée
|
// Appliquer l'URL d'image personnalisée
|
||||||
backgroundElement.style.backgroundImage = `url(${backgroundImage})`;
|
backgroundElement.style.backgroundImage = `url(${backgroundImage})`;
|
||||||
|
backgroundElement.className = 'custom-background';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Appliquer les propriétés communes
|
// Appliquer les propriétés communes
|
||||||
@@ -104,8 +115,27 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
|||||||
setBackgroundImageState(image);
|
setBackgroundImageState(image);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cycleBackground = () => {
|
||||||
|
const currentBackground = backgroundImage; // Utiliser le state local au lieu des préférences
|
||||||
|
const customImages = preferences?.viewPreferences?.customImages || [];
|
||||||
|
|
||||||
|
const nextBackground = getNextBackground(currentBackground || 'none', customImages);
|
||||||
|
const newBackgroundImage = nextBackground === 'none' ? undefined : nextBackground;
|
||||||
|
|
||||||
|
setBackgroundImageState(newBackgroundImage);
|
||||||
|
|
||||||
|
// Sauvegarder seulement si l'utilisateur est authentifié
|
||||||
|
if (session?.user?.id) {
|
||||||
|
updateViewPreferences({ backgroundImage: newBackgroundImage });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher le toast avec le nom du background
|
||||||
|
const backgroundName = BACKGROUND_NAMES[nextBackground] || 'Image personnalisée';
|
||||||
|
showToast(`Background: ${backgroundName}`, 2000, TOAST_ICONS.background);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BackgroundContext.Provider value={{ backgroundImage, setBackgroundImage }}>
|
<BackgroundContext.Provider value={{ backgroundImage, setBackgroundImage, cycleBackground }}>
|
||||||
{children}
|
{children}
|
||||||
</BackgroundContext.Provider>
|
</BackgroundContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { createContext, useContext, useEffect, useState, ReactNode } from 'react
|
|||||||
import { updateViewPreferences } from '@/actions/preferences';
|
import { updateViewPreferences } from '@/actions/preferences';
|
||||||
import { useToast } from '@/components/ui/Toast';
|
import { useToast } from '@/components/ui/Toast';
|
||||||
import { Theme, THEME_CONFIG, getNextDarkTheme, THEME_NAMES, getThemeIcon } from '@/lib/ui-config';
|
import { Theme, THEME_CONFIG, getNextDarkTheme, THEME_NAMES, getThemeIcon } from '@/lib/ui-config';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
|
||||||
interface ThemeContextType {
|
interface ThemeContextType {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
@@ -26,6 +27,7 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
|||||||
const [userPreferredTheme, setUserPreferredTheme] = useState<Theme>(initialUserPreferredTheme);
|
const [userPreferredTheme, setUserPreferredTheme] = useState<Theme>(initialUserPreferredTheme);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
// Hydration safe initialization
|
// Hydration safe initialization
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -47,7 +49,8 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
|||||||
const newTheme = theme === 'light' ? userPreferredTheme : 'light';
|
const newTheme = theme === 'light' ? userPreferredTheme : 'light';
|
||||||
setThemeState(newTheme);
|
setThemeState(newTheme);
|
||||||
|
|
||||||
// Sauvegarder en base de façon asynchrone via server action
|
// Sauvegarder en base seulement si l'utilisateur est authentifié
|
||||||
|
if (session?.user?.id) {
|
||||||
try {
|
try {
|
||||||
const result = await updateViewPreferences({
|
const result = await updateViewPreferences({
|
||||||
theme: newTheme
|
theme: newTheme
|
||||||
@@ -58,6 +61,7 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors de la sauvegarde du thème:', error);
|
console.error('Erreur lors de la sauvegarde du thème:', error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Afficher le toast avec le nom du thème
|
// Afficher le toast avec le nom du thème
|
||||||
showToast(`Thème: ${THEME_NAMES[newTheme]}`, 2000, getThemeIcon(newTheme));
|
showToast(`Thème: ${THEME_NAMES[newTheme]}`, 2000, getThemeIcon(newTheme));
|
||||||
@@ -71,7 +75,8 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
|||||||
setUserPreferredTheme(newTheme);
|
setUserPreferredTheme(newTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sauvegarder en base de façon asynchrone via server action
|
// Sauvegarder en base seulement si l'utilisateur est authentifié
|
||||||
|
if (session?.user?.id) {
|
||||||
try {
|
try {
|
||||||
const result = await updateViewPreferences({
|
const result = await updateViewPreferences({
|
||||||
theme: newTheme
|
theme: newTheme
|
||||||
@@ -82,6 +87,7 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors de la sauvegarde du thème:', error);
|
console.error('Erreur lors de la sauvegarde du thème:', error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Afficher le toast avec le nom du thème
|
// Afficher le toast avec le nom du thème
|
||||||
showToast(`Thème: ${THEME_NAMES[newTheme]}`, 2000, getThemeIcon(newTheme));
|
showToast(`Thème: ${THEME_NAMES[newTheme]}`, 2000, getThemeIcon(newTheme));
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
|
||||||
import { useToast } from '@/components/ui/Toast';
|
|
||||||
import { BACKGROUND_NAMES, TOAST_ICONS, getNextBackground } from '@/lib/ui-config';
|
|
||||||
|
|
||||||
export function useBackgroundCycle() {
|
|
||||||
const { preferences, updateViewPreferences } = useUserPreferences();
|
|
||||||
const { showToast } = useToast();
|
|
||||||
|
|
||||||
const cycleBackground = () => {
|
|
||||||
const currentBackground = preferences?.viewPreferences?.backgroundImage;
|
|
||||||
const customImages = preferences?.viewPreferences?.customImages || [];
|
|
||||||
|
|
||||||
const nextBackground = getNextBackground(currentBackground || 'none', customImages);
|
|
||||||
const backgroundImage = nextBackground === 'none' ? undefined : nextBackground;
|
|
||||||
|
|
||||||
updateViewPreferences({ backgroundImage });
|
|
||||||
|
|
||||||
// Afficher le toast avec le nom du background
|
|
||||||
const backgroundName = BACKGROUND_NAMES[nextBackground] || 'Image personnalisée';
|
|
||||||
showToast(`Background: ${backgroundName}`, 2000, TOAST_ICONS.background);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
cycleBackground
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user