Merge pull request #145 from BEDOLAGA-DEV/dev

Dev
This commit is contained in:
c0mrade
2026-02-04 13:19:40 +03:00
committed by GitHub
4 changed files with 106 additions and 194 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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)');

View File

@@ -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 {