diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..48b7053 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +dist +.git +.gitignore +*.md +.DS_Store +npm-debug.log* +yarn-debug.log* +yarn-error.log* +portal-data.json +.env +.env.local diff --git a/.env.example b/.env.example index 7a550fe..b7c2cd5 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,13 @@ -# GEMINI_API_KEY: Required for Gemini AI API calls. -# AI Studio automatically injects this at runtime from user secrets. -# Users configure this via the Secrets panel in the AI Studio UI. -GEMINI_API_KEY="MY_GEMINI_API_KEY" +# Server Configuration +PORT=3000 +NODE_ENV=production -# APP_URL: The URL where this applet is hosted. -# AI Studio automatically injects this at runtime with the Cloud Run service URL. -# Used for self-referential links, OAuth callbacks, and API endpoints. -APP_URL="MY_APP_URL" +# Database & Data Persistence Path +DATA_PATH=/app/data/portal-data.json + +# Optional SMTP Email Configuration (If using real SMTP server) +# SMTP_HOST=smtp.gmail.com +# SMTP_PORT=587 +# SMTP_USER=your-email@domain.com +# SMTP_PASS=your-app-password +# SMTP_FROM="Balance Sheet Portal " diff --git a/COOLIFY.md b/COOLIFY.md new file mode 100644 index 0000000..16460ad --- /dev/null +++ b/COOLIFY.md @@ -0,0 +1,69 @@ +# Deployment Guide for Coolify + +This repository is optimized for deployment on **Coolify** using Docker or Docker Compose. + +--- + +## Quick Deployment Options in Coolify + +### Option A: Deployment via Git Repository (Recommended) + +1. **Push Code to Git**: Push this repository to GitHub, GitLab, or your self-hosted Git service. +2. **Add New Resource in Coolify**: + - Go to your Coolify dashboard. + - Click **+ Add Resource** -> **Public Repository** or **Private Repository**. + - Paste your repository URL and select the `main` branch. +3. **Select Build Pack**: + - Select **Dockerfile**. Coolify will automatically detect the `Dockerfile` in the root directory. +4. **Configure Port & Network**: + - **Port**: Set `3000`. +5. **Configure Persistent Volume**: + - Under **Storage / Volumes**, add a persistent volume mapping to prevent data loss on container redeployments: + - **Destination Path**: `/app/data` +6. **Deploy**: + - Click **Deploy**. Coolify will build the multi-stage Docker image and start your application. + +--- + +### Option B: Deployment via Docker Compose + +1. **Add New Resource in Coolify**: + - Click **+ Add Resource** -> **Docker Compose**. +2. **Source Code**: + - Select your Git Repository or paste the contents of `docker-compose.yml`. +3. **Environment Variables**: + - Add the following environment variables in Coolify UI: + ```env + NODE_ENV=production + PORT=3000 + DATA_PATH=/app/data/portal-data.json + ``` +4. **Deploy**: + - Click **Deploy**. Coolify will spin up the container and attach the `portal_data` volume automatically. + +--- + +## Local Testing with Docker + +To build and test locally before deploying to Coolify: + +### Using Docker Compose: +```bash +docker-compose up --build -d +``` +Access the app at: `http://localhost:3000` + +### Using Docker directly: +```bash +docker build -t balance-sheet-portal . +docker run -p 3000:3000 -v portal_data:/app/data balance-sheet-portal +``` + +--- + +## Health Check Endpoint + +The container includes a built-in health check targeting: +`GET /api/health` + +It returns `{"status": "ok"}` when the Express server and Vite static engine are healthy. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4b7035f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.8' + +services: + balance-sheet-portal: + build: + context: . + dockerfile: Dockerfile + image: balance-sheet-portal:latest + container_name: balance-sheet-portal + restart: unless-stopped + ports: + - "${PORT:-3000}:3000" + environment: + - NODE_ENV=production + - PORT=3000 + - DATA_PATH=/app/data/portal-data.json + volumes: + - portal_data:/app/data + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + +volumes: + portal_data: + name: balance_sheet_portal_data + driver: local diff --git a/server.ts b/server.ts index 64f7396..f1c7fc8 100644 --- a/server.ts +++ b/server.ts @@ -40,7 +40,7 @@ import { import { AccountingEngine, DEFAULT_FX_RATES } from './src/services/accountingEngine.js'; const app = express(); -const PORT = 3000; +const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; app.use(express.json()); @@ -62,7 +62,8 @@ interface DB { pkrRates?: Record; } -const dbPath = path.join(process.cwd(), 'portal-data.json'); +const dataDir = process.env.DATA_DIR || process.cwd(); +const dbPath = process.env.DATA_PATH || path.join(dataDir, 'portal-data.json'); const DEFAULT_PKR_RATES: Record = { USDPKR: 278.16, @@ -177,6 +178,10 @@ function loadDB(): DB { function saveDB(data: DB) { try { + const parentDir = path.dirname(dbPath); + if (!fs.existsSync(parentDir)) { + fs.mkdirSync(parentDir, { recursive: true }); + } fs.writeFileSync(dbPath, JSON.stringify(data, null, 2)); } catch (err) { console.error('Error saving portal-data.json:', err); diff --git a/src/App.tsx b/src/App.tsx index 0b186c8..79bbc5b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -78,6 +78,11 @@ export default function App() { } }; + // Handle user logout + const handleLogout = () => { + setIsAuthModalOpen(true); + }; + return (
{/* 3D WebGL Holographic Particle Canvas Background */} @@ -90,6 +95,7 @@ export default function App() { activeTab={activeTab} setActiveTab={setActiveTab} allUsers={allUsers} + onLogout={handleLogout} onOpenAuthModal={() => setIsAuthModalOpen(true)} /> @@ -105,7 +111,7 @@ export default function App() { setActivePeriod={setActivePeriod} periodsList={periodsList} allUsers={allUsers} - onResetSeed={handleResetSeed} + onLogout={handleLogout} onOpenAuthModal={() => setIsAuthModalOpen(true)} /> diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 03c2e9e..f4b10eb 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -25,7 +25,8 @@ import { PanelLeftOpen, ChevronLeft, ChevronRight, - Key + Key, + LogOut } from 'lucide-react'; import { User, BranchId, BRANCHES_LIST } from '../types'; @@ -38,7 +39,8 @@ interface NavigationProps { setActivePeriod: (period: string) => void; periodsList: string[]; allUsers: User[]; - onResetSeed: () => void; + onLogout?: () => void; + onResetSeed?: () => void; onOpenAuthModal?: () => void; } @@ -49,6 +51,7 @@ export const Sidebar: React.FC { const [showUserMenu, setShowUserMenu] = useState(false); @@ -315,16 +318,11 @@ export const Sidebar: React.FC {/* Sidebar Footer User Profile */} -
- +
+ + {!isCollapsed && onLogout && ( + + )}
); @@ -357,6 +367,7 @@ export const HeaderBar: React.FC = ({ setActivePeriod, periodsList, allUsers, + onLogout, onResetSeed, onOpenAuthModal }) => { @@ -439,25 +450,16 @@ export const HeaderBar: React.FC = ({
- {onOpenAuthModal && ( + {onLogout && ( )} - -
@@ -582,38 +584,19 @@ export const HeaderBar: React.FC = ({ )} - {/* Quick User Identity Switcher for Mobile */} + {/* Sign Out Button for Mobile */}
- - - {showUserMenu && ( -
- {allUsers.filter((u) => u.approved).map((user) => ( - - ))} -
+ {onLogout && ( + )}