Compare commits
No commits in common. "3cae562f7fe4dd7d933bd54af91dec57f11e09e0" and "cb5ad1965580499d3bc5ae691c8a078b62f42864" have entirely different histories.
3cae562f7f
...
cb5ad19655
40
Dockerfile
40
Dockerfile
|
|
@ -1,40 +0,0 @@
|
||||||
# Build stage
|
|
||||||
FROM node:22-alpine as build
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package files
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build-time env (Vite inlines these at build time)
|
|
||||||
ARG VITE_API_BASE_URL
|
|
||||||
|
|
||||||
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
|
|
||||||
|
|
||||||
# Build the application
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# Production stage
|
|
||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
# Copy built assets from build stage
|
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
|
||||||
|
|
||||||
# Copy nginx configuration
|
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
# Expose port 80
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
# Health check
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
||||||
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
|
|
||||||
|
|
||||||
# Start nginx
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
34
nginx.conf
34
nginx.conf
|
|
@ -1,34 +0,0 @@
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# Gzip compression
|
|
||||||
gzip on;
|
|
||||||
gzip_vary on;
|
|
||||||
gzip_min_length 1024;
|
|
||||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
||||||
|
|
||||||
# SPA routing - serve index.html for all routes
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Cache static assets
|
|
||||||
location /assets/ {
|
|
||||||
expires 1y;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Disable access to hidden files
|
|
||||||
location ~ /\. {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
60
src/App.tsx
60
src/App.tsx
|
|
@ -1,63 +1,45 @@
|
||||||
import type { ComponentType } from "react";
|
|
||||||
import { Navigate, Route, Routes } from "react-router-dom";
|
import { Navigate, Route, Routes } from "react-router-dom";
|
||||||
import { Suspense, lazy } from "react";
|
|
||||||
|
|
||||||
import { AppLayout } from "@/components/layout/AppLayout";
|
import { AppLayout } from "@/components/layout/AppLayout";
|
||||||
import { useAuthStore } from "@/store/authStore";
|
import { useAuthStore } from "@/store/authStore";
|
||||||
import { Spinner } from "./components/ui/spinner";
|
import { BookingDetailPage } from "@/pages/BookingDetailPage";
|
||||||
|
import { BookingsPage } from "@/pages/BookingsPage";
|
||||||
function lazyNamed<T extends ComponentType<unknown>, TModule extends Record<string, T>>(
|
import { CalendarPage } from "@/pages/CalendarPage";
|
||||||
importer: () => Promise<TModule>,
|
import { CustomersPage } from "@/pages/CustomersPage";
|
||||||
exportName: keyof TModule,
|
import { DashboardPage } from "@/pages/DashboardPage";
|
||||||
) {
|
import { DiscountCodesPage } from "@/pages/DiscountCodesPage";
|
||||||
return lazy(async () => ({
|
import { LoginPage } from "@/pages/LoginPage";
|
||||||
default: (await importer())[exportName],
|
import { NewBookingPage } from "@/pages/NewBookingPage";
|
||||||
}));
|
import { PaymentsPage } from "@/pages/PaymentsPage";
|
||||||
}
|
import { ReferralCodesPage } from "@/pages/ReferralCodesPage";
|
||||||
|
import { ReservationsPage } from "@/pages/ReservationsPage";
|
||||||
// Lazy load pages
|
import { RoomsPage } from "@/pages/RoomsPage";
|
||||||
const LoginPage = lazyNamed(() => import("@/pages/LoginPage"), "LoginPage");
|
import { SettingsPage } from "@/pages/SettingsPage";
|
||||||
const DashboardPage = lazyNamed(() => import("@/pages/DashboardPage"), "DashboardPage");
|
import { TransactionsPage } from "@/pages/TransactionsPage";
|
||||||
const ReservationsPage = lazyNamed(() => import("@/pages/ReservationsPage"), "ReservationsPage");
|
import { VisitsPage } from "@/pages/VisitsPage";
|
||||||
const BookingsPage = lazyNamed(() => import("@/pages/BookingsPage"), "BookingsPage");
|
import { ManageUsersPage } from "@/pages/ManageUsersPage";
|
||||||
const NewBookingPage = lazyNamed(() => import("@/pages/NewBookingPage"), "NewBookingPage");
|
import { GuestServicesPage } from "@/pages/GuestServicesPage";
|
||||||
const BookingDetailPage = lazyNamed(() => import("@/pages/BookingDetailPage"), "BookingDetailPage");
|
import { LoyaltyPointsPage } from "@/pages/LoyaltyPointsPage";
|
||||||
const CalendarPage = lazyNamed(() => import("@/pages/CalendarPage"), "CalendarPage");
|
import { HotelRafflesPage } from "@/pages/HotelRafflesPage";
|
||||||
const RoomsPage = lazyNamed(() => import("@/pages/RoomsPage"), "RoomsPage");
|
|
||||||
const GuestServicesPage = lazyNamed(() => import("@/pages/GuestServicesPage"), "GuestServicesPage");
|
|
||||||
const LoyaltyPointsPage = lazyNamed(() => import("@/pages/LoyaltyPointsPage"), "LoyaltyPointsPage");
|
|
||||||
const HotelRafflesPage = lazyNamed(() => import("@/pages/HotelRafflesPage"), "HotelRafflesPage");
|
|
||||||
const CustomersPage = lazyNamed(() => import("@/pages/CustomersPage"), "CustomersPage");
|
|
||||||
const TransactionsPage = lazyNamed(() => import("@/pages/TransactionsPage"), "TransactionsPage");
|
|
||||||
const PaymentsPage = lazyNamed(() => import("@/pages/PaymentsPage"), "PaymentsPage");
|
|
||||||
const VisitsPage = lazyNamed(() => import("@/pages/VisitsPage"), "VisitsPage");
|
|
||||||
const DiscountCodesPage = lazyNamed(() => import("@/pages/DiscountCodesPage"), "DiscountCodesPage");
|
|
||||||
const ReferralCodesPage = lazyNamed(() => import("@/pages/ReferralCodesPage"), "ReferralCodesPage");
|
|
||||||
const ManageUsersPage = lazyNamed(() => import("@/pages/ManageUsersPage"), "ManageUsersPage");
|
|
||||||
const SettingsPage = lazyNamed(() => import("@/pages/SettingsPage"), "SettingsPage");
|
|
||||||
|
|
||||||
function ProtectedLayout() {
|
function ProtectedLayout() {
|
||||||
const accessToken = useAuthStore((s) => s.accessToken);
|
const accessToken = useAuthStore((s) => s.accessToken);
|
||||||
const bootstrapped = useAuthStore((s) => s.bootstrapped);
|
const bootstrapped = useAuthStore((s) => s.bootstrapped);
|
||||||
|
|
||||||
if (!bootstrapped) {
|
if (!bootstrapped) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center text-muted-foreground">
|
<div className="flex min-h-screen items-center justify-center text-muted-foreground">
|
||||||
<Spinner />
|
Loading…
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accessToken) return <Navigate to="/login" replace />;
|
if (!accessToken) return <Navigate to="/login" replace />;
|
||||||
return <AppLayout />;
|
return <AppLayout />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div className="p-4">Loading...</div>}>
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
|
||||||
<Route element={<ProtectedLayout />}>
|
<Route element={<ProtectedLayout />}>
|
||||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
<Route path="/dashboard" element={<DashboardPage />} />
|
<Route path="/dashboard" element={<DashboardPage />} />
|
||||||
|
|
@ -79,9 +61,7 @@ export default function App() {
|
||||||
<Route path="/settings/users" element={<ManageUsersPage />} />
|
<Route path="/settings/users" element={<ManageUsersPage />} />
|
||||||
<Route path="/settings" element={<SettingsPage />} />
|
<Route path="/settings" element={<SettingsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,21 @@
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { defineConfig, loadEnv } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig({
|
||||||
const isProduction = mode === "production";
|
|
||||||
const env = loadEnv(mode, process.cwd(), "");
|
|
||||||
|
|
||||||
const backendUrl =
|
|
||||||
env.VITE_PROXY_TARGET?.replace(/\/api\/?$/, "") ||
|
|
||||||
"http://localhost:3000";
|
|
||||||
|
|
||||||
return {
|
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: process.env.VITE_PROXY_TARGET ?? "http://localhost:3000",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
server: {
|
|
||||||
proxy: {
|
|
||||||
"/api": {
|
|
||||||
target: backendUrl,
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
build: {
|
|
||||||
target: "esnext",
|
|
||||||
minify: "esbuild",
|
|
||||||
sourcemap: !isProduction,
|
|
||||||
reportCompressedSize: false,
|
|
||||||
},
|
|
||||||
esbuild: {
|
|
||||||
drop: isProduction ? ["console", "debugger"] : [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue
Block a user