diff --git a/backend/package.json b/backend/package.json index dd3ab67..1560ef5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "@family-budget/backend", - "version": "0.1.0", + "version": "0.5.12", "private": true, "scripts": { "dev": "tsx watch src/app.ts", diff --git a/backend/src/app.ts b/backend/src/app.ts index 80d7727..c38a6d4 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,3 +1,5 @@ +import fs from 'fs'; +import path from 'path'; import express from 'express'; import cookieParser from 'cookie-parser'; import cors from 'cors'; @@ -5,6 +7,10 @@ import { config } from './config'; import { runMigrations } from './db/migrate'; import { requireAuth } from './middleware/auth'; +const pkg = JSON.parse( + fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'), +) as { version: string }; + import authRouter from './routes/auth'; import importRouter from './routes/import'; import importsRouter from './routes/imports'; @@ -24,6 +30,10 @@ app.use(cors({ origin: true, credentials: true })); // Auth routes (login is public; me/logout apply auth internally) app.use('/api/auth', authRouter); +app.get('/api/version', (_req, res) => { + res.json({ version: pkg.version }); +}); + // All remaining /api routes require authentication app.use('/api', requireAuth); app.use('/api/import', importRouter); diff --git a/frontend/package.json b/frontend/package.json index c399da6..c8ea786 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@family-budget/frontend", - "version": "0.1.0", + "version": "0.8.5", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/api/version.ts b/frontend/src/api/version.ts new file mode 100644 index 0000000..c15cae6 --- /dev/null +++ b/frontend/src/api/version.ts @@ -0,0 +1,5 @@ +import { api } from './client'; + +export async function getBackendVersion(): Promise<{ version: string }> { + return api.get<{ version: string }>('/api/version'); +} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index fa38ca7..12c282b 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,13 +1,21 @@ -import { useState, type ReactNode } from 'react'; +import { useState, useEffect, type ReactNode } from 'react'; import { NavLink } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; +import { getBackendVersion } from '../api/version'; export function Layout({ children }: { children: ReactNode }) { const { user, logout } = useAuth(); const [drawerOpen, setDrawerOpen] = useState(false); + const [beVersion, setBeVersion] = useState(null); const closeDrawer = () => setDrawerOpen(false); + useEffect(() => { + getBackendVersion() + .then((r) => setBeVersion(r.version)) + .catch(() => setBeVersion(null)); + }, []); + return (
+
+ {user?.login} + +
+
+ + FE {__FE_VERSION__} · BE {beVersion ?? '…'} + + © 2025 Семейный бюджет +
diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css index c84fc3b..c5f0f7f 100644 --- a/frontend/src/styles/index.css +++ b/frontend/src/styles/index.css @@ -138,11 +138,34 @@ body { .sidebar-footer { padding: 16px 20px; border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + flex-direction: column; + gap: 10px; +} + +.sidebar-footer-top { display: flex; align-items: center; justify-content: space-between; } +.sidebar-footer-bottom { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 11px; + color: var(--color-sidebar-text); + opacity: 0.85; +} + +.sidebar-version { + font-family: ui-monospace, monospace; +} + +.sidebar-copyright { + font-size: 10px; +} + .sidebar-user { font-size: 13px; color: var(--color-sidebar-text); diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 11f02fe..b490c72 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1 +1,3 @@ /// + +declare const __FE_VERSION__: string; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 55584f1..6f40b48 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,8 +1,19 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { readFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pkg = JSON.parse( + readFileSync(join(__dirname, 'package.json'), 'utf-8'), +) as { version: string }; export default defineConfig({ plugins: [react()], + define: { + __FE_VERSION__: JSON.stringify(pkg.version), + }, server: { port: 5173, proxy: {