From 526a9eefcff41f9e1a96561309678ea03516e222 Mon Sep 17 00:00:00 2001 From: HotelSync Date: Tue, 17 Mar 2026 01:03:28 +0300 Subject: [PATCH] Separate CP admin panel into independent Vite project at /cp/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New /cp/ Vite project with own package.json, Dockerfile, tailwind config - Migrated CpLoginPage, CpLayout, AdminDashboard → cp/src/ (self-contained) - cp/src/lib/utils.ts has only what CP needs (cn, PLAN_LABELS, PLAN_COLORS) - cp/src/data/mockData.ts has Hotel/User types and mock data - cp/ builds to nginx static container (hotelsync-cp) - docker-compose.yml: added cp service - nginx: added cp.hotelsync.ru → hotelsync-cp:80 (SSL block ready for certbot) - deploy.yml: builds hotelsync-cp:latest and restarts container on push - Cleaned src/App.tsx: removed IS_CP branch, CpLoginPage/CpLayout/AdminDashboard imports Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/deploy.yml | 18 +++- cp/Dockerfile | 14 +++ cp/index.html | 16 ++++ cp/nginx.conf | 19 ++++ cp/package.json | 29 ++++++ cp/postcss.config.js | 6 ++ cp/src/App.tsx | 31 +++++++ cp/src/components/ui/Badge.tsx | 14 +++ cp/src/data/mockData.ts | 80 ++++++++++++++++ cp/src/index.css | 53 +++++++++++ cp/src/layouts/CpLayout.tsx | 139 ++++++++++++++++++++++++++++ cp/src/lib/utils.ts | 20 ++++ cp/src/main.tsx | 10 ++ cp/src/pages/Dashboard.tsx | 119 ++++++++++++++++++++++++ cp/src/pages/LoginPage.tsx | 144 +++++++++++++++++++++++++++++ cp/tailwind.config.ts | 34 +++++++ cp/tsconfig.json | 20 ++++ cp/vite.config.ts | 6 ++ deploy/nginx/conf.d/hotelsync.conf | 21 ++++- docker-compose.yml | 13 ++- src/App.tsx | 38 -------- 21 files changed, 801 insertions(+), 43 deletions(-) create mode 100644 cp/Dockerfile create mode 100644 cp/index.html create mode 100644 cp/nginx.conf create mode 100644 cp/package.json create mode 100644 cp/postcss.config.js create mode 100644 cp/src/App.tsx create mode 100644 cp/src/components/ui/Badge.tsx create mode 100644 cp/src/data/mockData.ts create mode 100644 cp/src/index.css create mode 100644 cp/src/layouts/CpLayout.tsx create mode 100644 cp/src/lib/utils.ts create mode 100644 cp/src/main.tsx create mode 100644 cp/src/pages/Dashboard.tsx create mode 100644 cp/src/pages/LoginPage.tsx create mode 100644 cp/tailwind.config.ts create mode 100644 cp/tsconfig.json create mode 100644 cp/vite.config.ts diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 9aadcca..aeda8a2 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -20,11 +20,12 @@ jobs: - name: Copy env run: cp /opt/hotelsync/.env /opt/hotelsync/app/.env - - name: Build Docker image + - name: Build Docker images run: | cd /opt/hotelsync/app docker build -t hotelsync-frontend:latest . - echo "✅ Image built" + docker build -t hotelsync-cp:latest ./cp + echo "✅ Images built" - name: Restart frontend container run: | @@ -35,7 +36,18 @@ jobs: --network hotelsync_hotelsync-net \ --restart unless-stopped \ hotelsync-frontend:latest - echo "✅ Container started" + echo "✅ Frontend started" + + - name: Restart CP container + run: | + docker stop hotelsync-cp 2>/dev/null || true + docker rm hotelsync-cp 2>/dev/null || true + docker run -d \ + --name hotelsync-cp \ + --network hotelsync_hotelsync-net \ + --restart unless-stopped \ + hotelsync-cp:latest + echo "✅ CP started" - name: Health check run: | diff --git a/cp/Dockerfile b/cp/Dockerfile new file mode 100644 index 0000000..1a16b9c --- /dev/null +++ b/cp/Dockerfile @@ -0,0 +1,14 @@ +# ── Stage 1: Build ────────────────────────────────────────────────────────── +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# ── Stage 2: Serve ────────────────────────────────────────────────────────── +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/cp/index.html b/cp/index.html new file mode 100644 index 0000000..d5685e2 --- /dev/null +++ b/cp/index.html @@ -0,0 +1,16 @@ + + + + + + + HotelSync · Control Panel + + + + + +
+ + + diff --git a/cp/nginx.conf b/cp/nginx.conf new file mode 100644 index 0000000..663190b --- /dev/null +++ b/cp/nginx.conf @@ -0,0 +1,19 @@ +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + + # React SPA — все пути отдают index.html + location / { + try_files $uri $uri/ /index.html; + } + + # Кэшировать статику + location ~* \.(js|css|png|svg|ico|woff2?)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript; +} diff --git a/cp/package.json b/cp/package.json new file mode 100644 index 0000000..3b7cc01 --- /dev/null +++ b/cp/package.json @@ -0,0 +1,29 @@ +{ + "name": "hotelsync-cp", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.0", + "lucide-react": "^0.446.0", + "clsx": "^2.1.1", + "tailwind-merge": "^2.5.2" + }, + "devDependencies": { + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.45", + "tailwindcss": "^3.4.10", + "typescript": "^5.5.3", + "vite": "^5.4.1" + } +} diff --git a/cp/postcss.config.js b/cp/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/cp/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/cp/src/App.tsx b/cp/src/App.tsx new file mode 100644 index 0000000..21f1a53 --- /dev/null +++ b/cp/src/App.tsx @@ -0,0 +1,31 @@ +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' +import { LoginPage } from './pages/LoginPage' +import { CpLayout } from './layouts/CpLayout' +import { Dashboard } from './pages/Dashboard' + +function CpGuard({ children }: { children: React.ReactNode }) { + const auth = localStorage.getItem('cpAuth') + if (!auth) return + return <>{children} +} + +export default function App() { + return ( + + + } /> + + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + } /> + + + ) +} diff --git a/cp/src/components/ui/Badge.tsx b/cp/src/components/ui/Badge.tsx new file mode 100644 index 0000000..d67d9e8 --- /dev/null +++ b/cp/src/components/ui/Badge.tsx @@ -0,0 +1,14 @@ +import { cn } from '../../lib/utils' + +interface BadgeProps { + children: React.ReactNode + className?: string +} + +export function Badge({ children, className }: BadgeProps) { + return ( + + {children} + + ) +} diff --git a/cp/src/data/mockData.ts b/cp/src/data/mockData.ts new file mode 100644 index 0000000..b252af5 --- /dev/null +++ b/cp/src/data/mockData.ts @@ -0,0 +1,80 @@ +import type { HotelPlan } from '../lib/utils' + +export interface Hotel { + id: string + name: string + slug: string + address: string + plan: HotelPlan + isActive: boolean + roomCount: number + createdAt: string +} + +export interface User { + id: string + email: string + name: string + role: string + hotelId: string | null + hotelName?: string +} + +export const MOCK_HOTELS: Hotel[] = [ + { + id: 'hotel-1', + name: 'Grand Palace Hotel', + slug: 'grand-palace', + address: 'ул. Тверская 1, Москва', + plan: 'pro', + isActive: true, + roomCount: 12, + createdAt: '2024-01-15', + }, + { + id: 'hotel-2', + name: 'Sea Breeze Resort', + slug: 'sea-breeze', + address: 'ул. Набережная 12, Сочи', + plan: 'enterprise', + isActive: true, + roomCount: 48, + createdAt: '2024-03-22', + }, + { + id: 'hotel-3', + name: 'City Inn Express', + slug: 'city-inn', + address: 'пр. Невский 88, Санкт-Петербург', + plan: 'starter', + isActive: false, + roomCount: 20, + createdAt: '2024-06-01', + }, +] + +export const MOCK_USERS: User[] = [ + { + id: 'user-admin', + email: 'admin@hotelsync.io', + name: 'Александр Петров', + role: 'super_admin', + hotelId: null, + }, + { + id: 'user-manager-1', + email: 'manager@grand-palace.ru', + name: 'Елена Смирнова', + role: 'hotel_manager', + hotelId: 'hotel-1', + hotelName: 'Grand Palace Hotel', + }, + { + id: 'user-housekeeper-1', + email: 'cleaner@grand-palace.ru', + name: 'Мария Иванова', + role: 'housekeeper', + hotelId: 'hotel-1', + hotelName: 'Grand Palace Hotel', + }, +] diff --git a/cp/src/index.css b/cp/src/index.css new file mode 100644 index 0000000..bbe3c51 --- /dev/null +++ b/cp/src/index.css @@ -0,0 +1,53 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + * { + box-sizing: border-box; + } + + html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + body { + @apply bg-slate-950 text-slate-100 font-sans; + } + + ::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + ::-webkit-scrollbar-track { + @apply bg-transparent; + } + + ::-webkit-scrollbar-thumb { + @apply bg-slate-700 rounded-full; + } + + ::-webkit-scrollbar-thumb:hover { + @apply bg-slate-600; + } +} + +@layer components { + .badge { + @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium; + } + + .card { + @apply bg-slate-900 border border-slate-800 rounded-xl; + } + + .btn-primary { + @apply inline-flex items-center gap-2 px-4 py-2 rounded-lg + bg-brand-600 hover:bg-brand-500 + text-white text-sm font-medium + transition-colors duration-150 + disabled:opacity-50 disabled:cursor-not-allowed; + } +} diff --git a/cp/src/layouts/CpLayout.tsx b/cp/src/layouts/CpLayout.tsx new file mode 100644 index 0000000..b9c9e2c --- /dev/null +++ b/cp/src/layouts/CpLayout.tsx @@ -0,0 +1,139 @@ +import { useState } from 'react' +import { NavLink, Outlet, useNavigate } from 'react-router-dom' +import { + Building2, Users, BarChart2, CreditCard, Settings, + Shield, LogOut, Menu, X, Bell, Activity, + ChevronRight, +} from 'lucide-react' +import { cn } from '../lib/utils' + +const NAV = [ + { to: '/', icon: BarChart2, label: 'Обзор' }, + { to: '/hotels', icon: Building2, label: 'Отели' }, + { to: '/cp-users', icon: Users, label: 'Пользователи' }, + { to: '/billing', icon: CreditCard, label: 'Тарифы и оплата' }, + { to: '/monitoring', icon: Activity, label: 'Мониторинг' }, + { to: '/cp-settings', icon: Settings, label: 'Настройки' }, +] + +export function CpLayout() { + const navigate = useNavigate() + const [open, setOpen] = useState(false) + + const stored = localStorage.getItem('cpAuth') + const admin = stored ? JSON.parse(stored) : null + + const logout = () => { + localStorage.removeItem('cpAuth') + navigate('/login', { replace: true }) + } + + return ( +
+ {/* Mobile overlay */} + {open && ( +
setOpen(false)} + /> + )} + + {/* Sidebar */} + + + {/* Main area */} +
+ {/* Topbar */} +
+ + +
+ cp.hotelsync.ru + + Control Panel +
+ +
+
+ + Система в норме +
+ + +
+
+ + {/* Page content */} +
+ +
+
+
+ ) +} diff --git a/cp/src/lib/utils.ts b/cp/src/lib/utils.ts new file mode 100644 index 0000000..3fa703c --- /dev/null +++ b/cp/src/lib/utils.ts @@ -0,0 +1,20 @@ +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} + +export type HotelPlan = 'starter' | 'pro' | 'enterprise' + +export const PLAN_LABELS: Record = { + starter: 'Starter', + pro: 'Pro', + enterprise: 'Enterprise', +} + +export const PLAN_COLORS: Record = { + starter: 'bg-slate-700 text-slate-300', + pro: 'bg-brand-900/30 text-brand-300', + enterprise: 'bg-amber-900/30 text-amber-300', +} diff --git a/cp/src/main.tsx b/cp/src/main.tsx new file mode 100644 index 0000000..db032b7 --- /dev/null +++ b/cp/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/cp/src/pages/Dashboard.tsx b/cp/src/pages/Dashboard.tsx new file mode 100644 index 0000000..1d9844e --- /dev/null +++ b/cp/src/pages/Dashboard.tsx @@ -0,0 +1,119 @@ +import { Building2, Users, TrendingUp, CheckCircle2, Plus } from 'lucide-react' +import { MOCK_HOTELS, MOCK_USERS } from '../data/mockData' +import { cn, PLAN_COLORS, PLAN_LABELS } from '../lib/utils' +import { Badge } from '../components/ui/Badge' + +export function Dashboard() { + const activeHotels = MOCK_HOTELS.filter(h => h.isActive).length + const totalRooms = MOCK_HOTELS.reduce((s, h) => s + h.roomCount, 0) + + return ( +
+ {/* Header */} +
+

Панель администратора

+

HotelSync SaaS · Обзор платформы

+
+ + {/* Stats */} +
+ {[ + { label: 'Всего отелей', value: MOCK_HOTELS.length, icon: Building2, color: 'text-brand-400', bg: 'bg-brand-900/20' }, + { label: 'Активных', value: activeHotels, icon: CheckCircle2, color: 'text-emerald-400', bg: 'bg-emerald-900/20' }, + { label: 'Всего номеров', value: totalRooms, icon: TrendingUp, color: 'text-amber-400', bg: 'bg-amber-900/20' }, + { label: 'Пользователей', value: MOCK_USERS.length, icon: Users, color: 'text-violet-400', bg: 'bg-violet-900/20' }, + ].map(s => { + const Icon = s.icon + return ( +
+
+ +
+

{s.value}

+

{s.label}

+
+ ) + })} +
+ + {/* Hotels + Plan stats */} +
+
+
+

Отели

+ +
+
+ {MOCK_HOTELS.map(hotel => ( +
+
+ {hotel.name.charAt(0)} +
+
+

{hotel.name}

+

{hotel.address} · {hotel.roomCount} номеров

+
+
+ {PLAN_LABELS[hotel.plan]} +
+
+
+ ))} +
+
+ +
+

Статистика по тарифам

+
+ {(['starter', 'pro', 'enterprise'] as const).map(plan => { + const count = MOCK_HOTELS.filter(h => h.plan === plan).length + const pct = Math.round((count / MOCK_HOTELS.length) * 100) + return ( +
+
+ {PLAN_LABELS[plan]} + {count} отелей ({pct}%) +
+
+
+
+
+ ) + })} +
+ +
+

Пользователи

+
+ {MOCK_USERS.map(u => ( +
+
+ {u.name.charAt(0)} +
+
+

{u.name}

+

{u.email}

+
+ + {u.role} + +
+ ))} +
+
+
+
+
+ ) +} diff --git a/cp/src/pages/LoginPage.tsx b/cp/src/pages/LoginPage.tsx new file mode 100644 index 0000000..dbf13c2 --- /dev/null +++ b/cp/src/pages/LoginPage.tsx @@ -0,0 +1,144 @@ +import { useState, FormEvent } from 'react' +import { useNavigate } from 'react-router-dom' +import { Eye, EyeOff, Shield, AlertCircle } from 'lucide-react' +import { cn } from '../lib/utils' + +const ADMIN_EMAIL = 'admin@hotelsync.io' +const ADMIN_PASSWORD = 'demo' + +export function LoginPage() { + const navigate = useNavigate() + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [showPass, setShowPass] = useState(false) + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault() + setError('') + setLoading(true) + + await new Promise(r => setTimeout(r, 600)) + + if (email.trim() === ADMIN_EMAIL && password === ADMIN_PASSWORD) { + localStorage.setItem('cpAuth', JSON.stringify({ email, ts: Date.now() })) + navigate('/', { replace: true }) + } else { + setError('Неверный email или пароль') + } + setLoading(false) + } + + return ( +
+ {/* Subtle grid background */} +
+ +
+ {/* Logo */} +
+
+ +
+

HotelSync

+

Control Panel · Только для команды

+
+ + {/* Card */} +
+
+ {/* Email */} +
+ + { setEmail(e.target.value); setError('') }} + /> +
+ + {/* Password */} +
+ +
+ { setPassword(e.target.value); setError('') }} + /> + +
+
+ + {/* Error */} + {error && ( +
+ +

{error}

+
+ )} + + {/* Submit */} + +
+
+ +

+ Доступ только для сотрудников HotelSync Inc. +

+
+
+ ) +} diff --git a/cp/tailwind.config.ts b/cp/tailwind.config.ts new file mode 100644 index 0000000..aa0199f --- /dev/null +++ b/cp/tailwind.config.ts @@ -0,0 +1,34 @@ +import type { Config } from 'tailwindcss' + +export default { + darkMode: 'class', + content: [ + './index.html', + './src/**/*.{js,ts,jsx,tsx}', + ], + theme: { + extend: { + colors: { + brand: { + 50: '#eef2ff', + 100: '#e0e7ff', + 200: '#c7d2fe', + 300: '#a5b4fc', + 400: '#818cf8', + 500: '#6366f1', + 600: '#4f46e5', + 700: '#4338ca', + 800: '#3730a3', + 900: '#312e81', + }, + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + }, + boxShadow: { + 'card': '0 1px 3px 0 rgb(0 0 0 / 0.07), 0 1px 2px -1px rgb(0 0 0 / 0.07)', + }, + }, + }, + plugins: [], +} satisfies Config diff --git a/cp/tsconfig.json b/cp/tsconfig.json new file mode 100644 index 0000000..6bfa73a --- /dev/null +++ b/cp/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/cp/vite.config.ts b/cp/vite.config.ts new file mode 100644 index 0000000..9ffcc67 --- /dev/null +++ b/cp/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], +}) diff --git a/deploy/nginx/conf.d/hotelsync.conf b/deploy/nginx/conf.d/hotelsync.conf index fbd4af5..3c22731 100644 --- a/deploy/nginx/conf.d/hotelsync.conf +++ b/deploy/nginx/conf.d/hotelsync.conf @@ -18,7 +18,7 @@ server { # ── Редирект HTTP → HTTPS ───────────────────────────────────────────────── server { listen 80; - server_name hotelsync.ru www.hotelsync.ru app.hotelsync.ru api.hotelsync.ru git.hotelsync.ru; + server_name hotelsync.ru www.hotelsync.ru app.hotelsync.ru api.hotelsync.ru git.hotelsync.ru cp.hotelsync.ru; # Certbot challenge location /.well-known/acme-challenge/ { @@ -64,6 +64,25 @@ server { } } +# ── cp.hotelsync.ru — Control Panel ────────────────────────────────────── +server { + listen 443 ssl; + server_name cp.hotelsync.ru; + + ssl_certificate /etc/letsencrypt/live/cp.hotelsync.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/cp.hotelsync.ru/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://cp:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + # ── api.hotelsync.ru — API Backend ──────────────────────────────────────── server { listen 443 ssl; diff --git a/docker-compose.yml b/docker-compose.yml index 7a451ca..f2342f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.9' services: - # ── Frontend (React) ─────────────────────────────────────────────────────── + # ── Frontend (React PMS) ─────────────────────────────────────────────────── frontend: build: context: . @@ -12,6 +12,16 @@ services: networks: - hotelsync-net + # ── CP (Control Panel) ───────────────────────────────────────────────────── + cp: + build: + context: ./cp + dockerfile: Dockerfile + container_name: hotelsync-cp + restart: unless-stopped + networks: + - hotelsync-net + # ── API (Node.js + Fastify + PostgreSQL) ────────────────────────────────── api: build: @@ -75,6 +85,7 @@ services: - certbot_certs:/etc/letsencrypt:ro depends_on: - frontend + - cp - api networks: - hotelsync-net diff --git a/src/App.tsx b/src/App.tsx index dc53c49..1ae9e48 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,9 +5,7 @@ import { ModulesProvider } from './contexts/ModulesContext' import { AmenitiesProvider } from './contexts/AmenitiesContext' import { NotificationsProvider } from './contexts/NotificationsContext' import { AppLayout } from './layouts/AppLayout' -import { CpLayout } from './layouts/CpLayout' import { LoginPage } from './pages/LoginPage' -import { CpLoginPage } from './pages/CpLoginPage' import { CalendarPage } from './pages/CalendarPage' import { BookingsPage } from './pages/BookingsPage' import { RoomsPage } from './pages/RoomsPage' @@ -21,7 +19,6 @@ import { WebsitePage } from './pages/WebsitePage' import { BookingWidgetPage } from './pages/BookingWidgetPage' import { AvailabilityPage } from './pages/AvailabilityPage' import { FloorMapPage } from './pages/FloorMapPage' -import { AdminDashboard } from './pages/AdminDashboard' import { MigrationPage } from './pages/MigrationPage' import { PosPage } from './pages/PosPage' import { ReviewsPage } from './pages/ReviewsPage' @@ -39,42 +36,7 @@ import { DiscountsPage } from './pages/DiscountsPage' import { GuestReviewPage } from './pages/GuestReviewPage' import { GuestRoomServicePage } from './pages/GuestRoomServicePage' -// Определяем на каком домене работаем -const IS_CP = window.location.hostname === 'cp.hotelsync.ru' - -// ── Guard для CP: если не авторизован → /login ──────────────────────────── -function CpGuard({ children }: { children: React.ReactNode }) { - const auth = localStorage.getItem('cpAuth') - if (!auth) return - return <>{children} -} - export default function App() { - // ── Control Panel (cp.hotelsync.ru) ────────────────────────────────────── - if (IS_CP) { - return ( - - - - } /> - - }> - } /> - } /> - } /> - } /> - } /> - } /> - - - } /> - - - - ) - } - - // ── PMS App (app.hotelsync.ru) ──────────────────────────────────────────── return (