mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
@@ -6,6 +6,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
import { useBackButton, useHaptic } from '@/platform';
|
||||
import { useTelegramSDK } from '@/hooks/useTelegramSDK';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { themeColorsApi } from '@/api/themeColors';
|
||||
import { referralApi } from '@/api/referral';
|
||||
import { wheelApi } from '@/api/wheel';
|
||||
import { contestsApi } from '@/api/contests';
|
||||
@@ -142,6 +144,38 @@ const LogoutIcon = ({ className }: { className?: string }) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SunIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const MoonIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const FALLBACK_NAME = import.meta.env.VITE_APP_NAME || 'Cabinet';
|
||||
const FALLBACK_LOGO = import.meta.env.VITE_APP_LOGO || 'V';
|
||||
|
||||
@@ -164,6 +198,15 @@ export function AppShell({ children }: AppShellProps) {
|
||||
isMobile,
|
||||
} = useTelegramSDK();
|
||||
const haptic = useHaptic();
|
||||
const { toggleTheme, isDark } = useTheme();
|
||||
|
||||
// Theme toggle visibility
|
||||
const { data: enabledThemes } = useQuery({
|
||||
queryKey: ['enabled-themes'],
|
||||
queryFn: themeColorsApi.getEnabledThemes,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
});
|
||||
const canToggleTheme = enabledThemes?.dark && enabledThemes?.light;
|
||||
|
||||
// Only apply fullscreen UI adjustments on mobile Telegram (iOS/Android)
|
||||
const isMobileFullscreen = isFullscreen && isMobile;
|
||||
@@ -460,6 +503,18 @@ export function AppShell({ children }: AppShellProps) {
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{canToggleTheme && (
|
||||
<button
|
||||
onClick={() => {
|
||||
haptic.impact('light');
|
||||
toggleTheme();
|
||||
}}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-lg text-dark-400 transition-colors hover:bg-dark-800/50 hover:text-dark-200"
|
||||
title={isDark ? t('theme.light') || 'Light mode' : t('theme.dark') || 'Dark mode'}
|
||||
>
|
||||
{isDark ? <MoonIcon className="h-4 w-4" /> : <SunIcon className="h-4 w-4" />}
|
||||
</button>
|
||||
)}
|
||||
<TicketNotificationBell isAdmin={location.pathname.startsWith('/admin')} />
|
||||
<LanguageSwitcher />
|
||||
<button
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';
|
||||
import { brandingApi } from '@/api/branding';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { ThemeSettings, DEFAULT_THEME_COLORS } from '@/types/theme';
|
||||
|
||||
const VERT = /* glsl */ `#version 300 es
|
||||
@@ -106,25 +107,8 @@ function hexToRgb(hex: string): [number, number, number] {
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
function generateColorStops(accent: string, background: string): string[] {
|
||||
const [ar, ag, ab] = hexToRgb(accent);
|
||||
|
||||
// Color 1: Dark background
|
||||
const color1 = background;
|
||||
|
||||
// Color 2: Accent at 40% intensity
|
||||
const midR = Math.round(ar * 255 * 0.4);
|
||||
const midG = Math.round(ag * 255 * 0.4);
|
||||
const midB = Math.round(ab * 255 * 0.4);
|
||||
const color2 = `#${midR.toString(16).padStart(2, '0')}${midG.toString(16).padStart(2, '0')}${midB.toString(16).padStart(2, '0')}`;
|
||||
|
||||
// Color 3: Accent at 70% intensity
|
||||
const brightR = Math.round(ar * 255 * 0.7);
|
||||
const brightG = Math.round(ag * 255 * 0.7);
|
||||
const brightB = Math.round(ab * 255 * 0.7);
|
||||
const color3 = `#${brightR.toString(16).padStart(2, '0')}${brightG.toString(16).padStart(2, '0')}${brightB.toString(16).padStart(2, '0')}`;
|
||||
|
||||
return [color1, color2, color3];
|
||||
function generateColorStops(background: string, surface: string, accent: string): string[] {
|
||||
return [background, surface, accent];
|
||||
}
|
||||
|
||||
export function Aurora() {
|
||||
@@ -148,6 +132,11 @@ export function Aurora() {
|
||||
const themeColors =
|
||||
queryClient.getQueryData<ThemeSettings>(['theme-colors']) || DEFAULT_THEME_COLORS;
|
||||
|
||||
// Pick background and surface based on current theme
|
||||
const { isDark } = useTheme();
|
||||
const background = isDark ? themeColors.darkBackground : themeColors.lightBackground;
|
||||
const surface = isDark ? themeColors.darkSurface : themeColors.lightSurface;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEnabled || !containerRef.current) return;
|
||||
|
||||
@@ -168,7 +157,7 @@ export function Aurora() {
|
||||
|
||||
const geometry = new Triangle(gl);
|
||||
|
||||
const colorStops = generateColorStops(themeColors.accent, themeColors.darkBackground);
|
||||
const colorStops = generateColorStops(background, surface, themeColors.accent);
|
||||
const colorStopsArray = colorStops
|
||||
.map((hex) => {
|
||||
const c = new Color(hex);
|
||||
@@ -231,7 +220,7 @@ export function Aurora() {
|
||||
rendererRef.current = null;
|
||||
programRef.current = null;
|
||||
};
|
||||
}, [isEnabled, themeColors.accent, themeColors.darkBackground]);
|
||||
}, [isEnabled, themeColors.accent, background, surface]);
|
||||
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
|
||||
@@ -42,8 +42,9 @@ function getCachedEnabledThemes(): EnabledThemes {
|
||||
return DEFAULT_ENABLED_THEMES;
|
||||
}
|
||||
|
||||
// Custom event for same-tab updates
|
||||
// Custom events for same-tab updates
|
||||
const ENABLED_THEMES_CHANGED_EVENT = 'enabledThemesChanged';
|
||||
const THEME_CHANGED_EVENT = 'themeChanged';
|
||||
|
||||
// Update cache (called from admin settings)
|
||||
export function updateEnabledThemesCache(themes: EnabledThemes) {
|
||||
@@ -161,8 +162,21 @@ export function useTheme() {
|
||||
}
|
||||
|
||||
localStorage.setItem(THEME_KEY, theme);
|
||||
// Notify other useTheme() instances in the same tab
|
||||
window.dispatchEvent(new CustomEvent(THEME_CHANGED_EVENT, { detail: theme }));
|
||||
}, [theme, enabledThemes]);
|
||||
|
||||
// Listen for same-tab theme changes (from other useTheme() instances)
|
||||
useEffect(() => {
|
||||
const handleThemeChange = (e: CustomEvent<Theme>) => {
|
||||
setThemeState(e.detail);
|
||||
};
|
||||
|
||||
window.addEventListener(THEME_CHANGED_EVENT, handleThemeChange as EventListener);
|
||||
return () =>
|
||||
window.removeEventListener(THEME_CHANGED_EVENT, handleThemeChange as EventListener);
|
||||
}, []);
|
||||
|
||||
// Listen for system theme changes
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');
|
||||
|
||||
@@ -217,168 +217,22 @@
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Light theme text color overrides - convert dark-* text colors to readable colors */
|
||||
.light .text-dark-50 {
|
||||
color: #1f1a12 !important; /* champagne-950 */
|
||||
}
|
||||
|
||||
.light .text-dark-100 {
|
||||
color: #3d3424 !important; /* champagne-900 */
|
||||
}
|
||||
|
||||
.light .text-dark-200 {
|
||||
color: #5c4f35 !important; /* champagne-800 */
|
||||
}
|
||||
|
||||
.light .text-dark-300 {
|
||||
color: #7d6b48 !important; /* champagne-700 */
|
||||
}
|
||||
|
||||
.light .text-dark-400 {
|
||||
color: #a08b5e !important; /* champagne-600 */
|
||||
}
|
||||
|
||||
.light .text-dark-500 {
|
||||
color: #bfa477 !important; /* champagne-500 */
|
||||
}
|
||||
|
||||
.light .text-dark-600 {
|
||||
color: #d4bc96 !important; /* champagne-400 */
|
||||
}
|
||||
|
||||
/* Light theme background color overrides */
|
||||
.light .bg-dark-700 {
|
||||
background-color: #e8d4b5 !important; /* champagne-300 */
|
||||
}
|
||||
|
||||
.light .bg-dark-800 {
|
||||
background-color: #f7e7ce !important; /* champagne-200 */
|
||||
}
|
||||
|
||||
.light .bg-dark-800\/50 {
|
||||
background-color: rgba(232, 212, 181, 0.5) !important; /* champagne-300/50 */
|
||||
}
|
||||
|
||||
.light .bg-dark-800\/30 {
|
||||
background-color: rgba(232, 212, 181, 0.4) !important; /* champagne-300/40 - more visible */
|
||||
}
|
||||
|
||||
.light .bg-dark-700\/50 {
|
||||
background-color: rgba(212, 188, 150, 0.5) !important; /* champagne-400/50 */
|
||||
}
|
||||
|
||||
.light .bg-dark-900\/30 {
|
||||
background-color: rgba(247, 231, 206, 0.6) !important; /* champagne-200/60 */
|
||||
}
|
||||
|
||||
/* Light theme additional border overrides for semi-transparent borders */
|
||||
.light .border-dark-800\/30 {
|
||||
border-color: rgba(191, 164, 119, 0.4) !important; /* champagne-500/40 */
|
||||
}
|
||||
|
||||
.light .border-dark-700\/30 {
|
||||
border-color: rgba(191, 164, 119, 0.5) !important; /* champagne-500/50 - more visible */
|
||||
}
|
||||
|
||||
.light .bg-dark-900 {
|
||||
background-color: #fef9f0 !important; /* champagne-100 */
|
||||
}
|
||||
|
||||
.light .bg-dark-900\/50 {
|
||||
background-color: rgba(254, 249, 240, 0.5) !important; /* champagne-100/50 */
|
||||
}
|
||||
|
||||
.light .bg-dark-900\/95 {
|
||||
background-color: rgba(
|
||||
254,
|
||||
249,
|
||||
240,
|
||||
0.95
|
||||
) !important; /* champagne-100/95 - mobile menu sticky header */
|
||||
}
|
||||
|
||||
.light .bg-dark-950 {
|
||||
background-color: #f7e7ce !important; /* champagne-200 */
|
||||
}
|
||||
|
||||
.light .bg-dark-950\/80 {
|
||||
background-color: rgba(247, 231, 206, 0.85) !important; /* champagne-200/85 */
|
||||
}
|
||||
|
||||
.light .bg-dark-600 {
|
||||
background-color: #d4bc96 !important; /* champagne-400 */
|
||||
}
|
||||
|
||||
/* Light theme border color overrides */
|
||||
.light .border-dark-700 {
|
||||
border-color: #e8d4b5 !important; /* champagne-300 */
|
||||
}
|
||||
|
||||
.light .border-dark-700\/50 {
|
||||
border-color: rgba(232, 212, 181, 0.5) !important;
|
||||
}
|
||||
|
||||
.light .border-dark-800 {
|
||||
border-color: #d4bc96 !important; /* champagne-400 */
|
||||
}
|
||||
|
||||
.light .border-dark-800\/50 {
|
||||
border-color: rgba(212, 188, 150, 0.5) !important;
|
||||
}
|
||||
|
||||
/* Light theme hover state overrides */
|
||||
.light .hover\:text-dark-100:hover {
|
||||
color: #3d3424 !important; /* champagne-900 */
|
||||
}
|
||||
|
||||
.light .hover\:text-dark-200:hover {
|
||||
color: #5c4f35 !important; /* champagne-800 */
|
||||
}
|
||||
|
||||
.light .hover\:bg-dark-800:hover {
|
||||
background-color: #e8d4b5 !important; /* champagne-300 */
|
||||
}
|
||||
|
||||
.light .hover\:bg-dark-800\/50:hover {
|
||||
background-color: rgba(232, 212, 181, 0.5) !important;
|
||||
}
|
||||
|
||||
.light .hover\:bg-dark-700\/50:hover {
|
||||
background-color: rgba(212, 188, 150, 0.5) !important; /* champagne-400/50 */
|
||||
}
|
||||
|
||||
.light .hover\:bg-dark-700:hover {
|
||||
background-color: #d4bc96 !important; /* champagne-400 */
|
||||
}
|
||||
|
||||
.light .hover\:bg-dark-600:hover {
|
||||
background-color: #bfa477 !important; /* champagne-500 */
|
||||
}
|
||||
|
||||
.light .hover\:border-dark-600:hover {
|
||||
border-color: #bfa477 !important; /* champagne-500 */
|
||||
}
|
||||
|
||||
.light .hover\:border-dark-700\/50:hover {
|
||||
border-color: rgba(232, 212, 181, 0.5) !important;
|
||||
}
|
||||
|
||||
/* Light theme gradient overrides for login page */
|
||||
.light .from-dark-950 {
|
||||
--tw-gradient-from: #f7e7ce !important;
|
||||
}
|
||||
|
||||
.light .via-dark-900 {
|
||||
--tw-gradient-stops: var(--tw-gradient-from), #fef9f0, var(--tw-gradient-to) !important;
|
||||
}
|
||||
|
||||
.light .to-dark-950 {
|
||||
--tw-gradient-to: #f7e7ce !important;
|
||||
}
|
||||
|
||||
/* Light theme divider for desktop nav */
|
||||
.light .bg-dark-700.w-px {
|
||||
background-color: #d4bc96 !important;
|
||||
/* Light theme: swap dark palette → dynamic champagne palette from API.
|
||||
Uses var() to reference values set by applyThemeColors().
|
||||
!important overrides inline styles from applyThemeColors on :root. */
|
||||
.light {
|
||||
--color-dark-50: var(--color-champagne-950) !important;
|
||||
--color-dark-100: var(--color-champagne-900) !important;
|
||||
--color-dark-200: var(--color-champagne-800) !important;
|
||||
--color-dark-300: var(--color-champagne-700) !important;
|
||||
--color-dark-400: var(--color-champagne-600) !important;
|
||||
--color-dark-500: var(--color-champagne-500) !important;
|
||||
--color-dark-600: var(--color-champagne-400) !important;
|
||||
--color-dark-700: var(--color-champagne-300) !important;
|
||||
--color-dark-800: var(--color-champagne-50) !important;
|
||||
--color-dark-850: var(--color-champagne-100) !important;
|
||||
--color-dark-900: var(--color-champagne-100) !important;
|
||||
--color-dark-950: var(--color-champagne-200) !important;
|
||||
}
|
||||
|
||||
/* Custom scrollbar - Dark */
|
||||
@@ -516,19 +370,19 @@
|
||||
}
|
||||
|
||||
.light .bento-card {
|
||||
@apply border-champagne-300/50 bg-white/90 shadow-sm;
|
||||
@apply border-champagne-300/50 bg-champagne-50/90 shadow-sm;
|
||||
/* Light Theme Glass Border */
|
||||
box-shadow: inset 0 1px 0 0 rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.light .bento-card {
|
||||
@apply bg-white/80 backdrop-blur-sm;
|
||||
@apply bg-champagne-50/80 backdrop-blur-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.light .bento-card-hover:hover {
|
||||
@apply border-champagne-400/50 bg-white shadow-md;
|
||||
@apply border-champagne-400/50 bg-champagne-50 shadow-md;
|
||||
transform: translateY(-4px);
|
||||
/* Intensify Light Theme Glass Border */
|
||||
box-shadow:
|
||||
@@ -761,7 +615,7 @@
|
||||
|
||||
/* Cards - Light */
|
||||
.light .card {
|
||||
@apply border-champagne-300/50 bg-white/90 p-5 shadow-sm sm:p-6;
|
||||
@apply border-champagne-300/50 bg-champagne-50/90 p-5 shadow-sm sm:p-6;
|
||||
border-radius: var(--bento-radius);
|
||||
transform: translateZ(0);
|
||||
box-shadow: inset 0 1px 0 0 rgba(0, 0, 0, 0.03);
|
||||
@@ -769,12 +623,12 @@
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.light .card {
|
||||
@apply bg-white/80 backdrop-blur-sm;
|
||||
@apply bg-champagne-50/80 backdrop-blur-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.light .card-hover {
|
||||
@apply hover:border-champagne-400/50 hover:bg-white hover:shadow-md;
|
||||
@apply hover:border-champagne-400/50 hover:bg-champagne-50 hover:shadow-md;
|
||||
}
|
||||
|
||||
.light .card-glow {
|
||||
@@ -783,13 +637,13 @@
|
||||
|
||||
/* Glass effect - Light (optimized for mobile) */
|
||||
.light .glass {
|
||||
@apply border border-champagne-300/40 bg-white/90;
|
||||
@apply border border-champagne-300/40 bg-champagne-50/90;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.light .glass {
|
||||
@apply bg-white/60 backdrop-blur-xl;
|
||||
@apply bg-champagne-50/60 backdrop-blur-xl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,7 +657,7 @@
|
||||
}
|
||||
|
||||
.light .btn-secondary {
|
||||
@apply border border-champagne-300 bg-white text-champagne-800 hover:border-champagne-400 hover:bg-champagne-50 hover:text-champagne-900 focus-visible:ring-champagne-400 active:bg-champagne-100;
|
||||
@apply border border-champagne-300 bg-champagne-50 text-champagne-800 hover:border-champagne-400 hover:bg-champagne-100 hover:text-champagne-900 focus-visible:ring-champagne-400 active:bg-champagne-200;
|
||||
}
|
||||
|
||||
.light .btn-ghost {
|
||||
@@ -820,7 +674,7 @@
|
||||
|
||||
/* Inputs - Light */
|
||||
.light .input {
|
||||
@apply rounded-xl border border-champagne-300 bg-white text-champagne-900 placeholder-champagne-400 focus:border-accent-400 focus:ring-accent-300/30;
|
||||
@apply rounded-xl border border-champagne-300 bg-champagne-50 text-champagne-900 placeholder-champagne-400 focus:border-accent-400 focus:ring-accent-300/30;
|
||||
}
|
||||
|
||||
.light .input-error {
|
||||
|
||||
Reference in New Issue
Block a user