Add VITE_APP_VERSION environment variable and integrate versioning into the application

- Introduced VITE_APP_VERSION in Dockerfile and GitHub workflows to capture the version from Git tags.
- Updated vite.config.ts to define a global constant for the app version.
- Enhanced Layout component to display the app version in the footer for admin pages.
- Added logic to conditionally render the LanguageSwitcher based on active promotions.
This commit is contained in:
PEDZEO
2026-01-19 07:17:49 +03:00
parent a261a5143f
commit 5409317501
6 changed files with 73 additions and 21 deletions

View File

@@ -62,3 +62,4 @@ jobs:
VITE_TELEGRAM_BOT_USERNAME=
VITE_APP_NAME=Cabinet
VITE_APP_LOGO=V
VITE_APP_VERSION=${{ github.ref_name }}

View File

@@ -34,6 +34,7 @@ jobs:
VITE_TELEGRAM_BOT_USERNAME:
VITE_APP_NAME: Bedolaga Cabinet
VITE_APP_LOGO: V
VITE_APP_VERSION: ${{ github.ref_name }}
- name: Create dist archive
run: |

View File

@@ -18,12 +18,14 @@ ARG VITE_API_URL=/api
ARG VITE_TELEGRAM_BOT_USERNAME
ARG VITE_APP_NAME=Cabinet
ARG VITE_APP_LOGO=V
ARG VITE_APP_VERSION
# Set environment variables for build
ENV VITE_API_URL=$VITE_API_URL
ENV VITE_TELEGRAM_BOT_USERNAME=$VITE_TELEGRAM_BOT_USERNAME
ENV VITE_APP_NAME=$VITE_APP_NAME
ENV VITE_APP_LOGO=$VITE_APP_LOGO
ENV VITE_APP_VERSION=$VITE_APP_VERSION
# Build the application
RUN npm run build

View File

@@ -11,6 +11,7 @@ import { pollsApi } from '../../api/polls'
import { brandingApi } from '../../api/branding'
import { wheelApi } from '../../api/wheel'
import { themeColorsApi } from '../../api/themeColors'
import { promoApi } from '../../api/promo'
import { useTheme } from '../../hooks/useTheme'
// Fallback branding from environment variables
@@ -210,6 +211,17 @@ export default function Layout({ children }: LayoutProps) {
retry: false,
})
// Fetch active discount to determine mobile layout
const { data: activeDiscount } = useQuery({
queryKey: ['active-discount'],
queryFn: promoApi.getActiveDiscount,
enabled: isAuthenticated,
staleTime: 30000,
})
// Check if promo is active (to hide language switcher on mobile)
const isPromoActive = activeDiscount?.is_active && activeDiscount?.discount_percent
const navItems = useMemo(() => {
const items = [
{ path: '/', label: t('nav.dashboard'), icon: HomeIcon },
@@ -339,7 +351,10 @@ export default function Layout({ children }: LayoutProps) {
<PromoDiscountBadge />
<TicketNotificationBell isAdmin={isAdminActive()} />
<LanguageSwitcher />
{/* Hide language switcher on mobile when promo is active */}
<div className={isPromoActive ? 'hidden sm:block' : ''}>
<LanguageSwitcher />
</div>
{/* Profile - Desktop */}
<div className="hidden sm:flex items-center gap-3">
@@ -389,29 +404,33 @@ export default function Layout({ children }: LayoutProps) {
<div className="absolute inset-x-0 top-0 bottom-0 bg-dark-900 border-t border-dark-800/50 overflow-y-auto pb-[calc(5rem+env(safe-area-inset-bottom,0px))]">
<div className="max-w-6xl mx-auto px-4 py-4">
{/* User info */}
<div className="flex items-center gap-3 pb-4 mb-4 border-b border-dark-800/50">
{userPhotoUrl ? (
<img
src={userPhotoUrl}
alt="Avatar"
className="w-10 h-10 rounded-full object-cover"
onError={(e) => {
e.currentTarget.style.display = 'none'
e.currentTarget.nextElementSibling?.classList.remove('hidden')
}}
/>
) : null}
<div className={`w-10 h-10 rounded-full bg-dark-700 flex items-center justify-center ${userPhotoUrl ? 'hidden' : ''}`}>
<UserIcon />
</div>
<div>
<div className="text-sm font-medium text-dark-100">
{user?.first_name || user?.username}
<div className="flex items-center justify-between pb-4 mb-4 border-b border-dark-800/50">
<div className="flex items-center gap-3">
{userPhotoUrl ? (
<img
src={userPhotoUrl}
alt="Avatar"
className="w-10 h-10 rounded-full object-cover"
onError={(e) => {
e.currentTarget.style.display = 'none'
e.currentTarget.nextElementSibling?.classList.remove('hidden')
}}
/>
) : null}
<div className={`w-10 h-10 rounded-full bg-dark-700 flex items-center justify-center ${userPhotoUrl ? 'hidden' : ''}`}>
<UserIcon />
</div>
<div className="text-xs text-dark-500">
@{user?.username || `ID: ${user?.telegram_id}`}
<div>
<div className="text-sm font-medium text-dark-100">
{user?.first_name || user?.username}
</div>
<div className="text-xs text-dark-500">
@{user?.username || `ID: ${user?.telegram_id}`}
</div>
</div>
</div>
{/* Language switcher in mobile menu when promo is active */}
{isPromoActive && <LanguageSwitcher />}
</div>
{/* Nav items */}
@@ -480,6 +499,15 @@ export default function Layout({ children }: LayoutProps) {
<div className="animate-fade-in">
{children}
</div>
{/* Version footer for admin pages */}
{isAdminActive() && (
<div className="mt-8 pt-4 border-t border-dark-800/50 text-center">
<span className="text-xs text-dark-500">
Cabinet {__APP_VERSION__}
</span>
</div>
)}
</main>
{/* Mobile Bottom Navigation - only core items */}

3
src/vite-env.d.ts vendored
View File

@@ -1,5 +1,8 @@
/// <reference types="vite/client" />
// Global constant defined in vite.config.ts
declare const __APP_VERSION__: string
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_TELEGRAM_BOT_USERNAME?: string

View File

@@ -1,9 +1,26 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { execSync } from 'child_process'
// Get version from Git tag or fallback to 'dev'
function getGitVersion(): string {
try {
// Try to get version from git tag
return execSync('git describe --tags --always', { encoding: 'utf-8' }).trim()
} catch {
return 'dev'
}
}
const appVersion = process.env.VITE_APP_VERSION || getGitVersion()
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
// Define global constants
define: {
__APP_VERSION__: JSON.stringify(appVersion),
},
// Base path - use '/' for standalone Docker deployment
// Change to '/cabinet/' if serving from a sub-path
base: '/',