Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# App
NODE_ENV=
NEXT_PUBLIC_APP_ENV=
NEXT_PUBLIC_APP_URL=

# Meta WhatsApp Cloud API
WHATSAPP_APP_SECRET=
WHATSAPP_VERIFY_TOKEN=
WHATSAPP_ACCESS_TOKEN=
WHATSAPP_PHONE_NUMBER_ID=
WHATSAPP_WABA_ID=

# Server
PORT=3000
LOG_LEVEL=info

# PostgreSQL Database
DB_HOST=
DB_PORT=5432
DB_NAME=neurowealth
DB_USER=postgres
DB_PASSWORD=

# Stellar Network
STELLAR_NETWORK=testnet
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org

# Wallet Encryption (generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
WALLET_ENCRYPTION_KEY=
56 changes: 56 additions & 0 deletions .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Deploy to Production

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: NeuroWealth-Frontend

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: NeuroWealth-Frontend/package-lock.json

- name: Install dependencies
run: npm ci --legacy-peer-deps --omit=optional

- name: Validate environment variables
run: npm run validate:env
env:
NEXT_PUBLIC_APP_ENV: ${{ secrets.PROD_APP_ENV }}
NEXT_PUBLIC_APP_URL: ${{ secrets.PROD_APP_URL }}
WHATSAPP_APP_SECRET: ${{ secrets.PROD_WHATSAPP_APP_SECRET }}
WHATSAPP_VERIFY_TOKEN: ${{ secrets.PROD_WHATSAPP_VERIFY_TOKEN }}
WHATSAPP_ACCESS_TOKEN: ${{ secrets.PROD_WHATSAPP_ACCESS_TOKEN }}
WHATSAPP_PHONE_NUMBER_ID: ${{ secrets.PROD_WHATSAPP_PHONE_NUMBER_ID }}
WHATSAPP_WABA_ID: ${{ secrets.PROD_WHATSAPP_WABA_ID }}
DB_HOST: ${{ secrets.PROD_DB_HOST }}
DB_PORT: ${{ secrets.PROD_DB_PORT }}
DB_NAME: ${{ secrets.PROD_DB_NAME }}
DB_USER: ${{ secrets.PROD_DB_USER }}
DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
STELLAR_NETWORK: mainnet
STELLAR_HORIZON_URL: https://horizon.stellar.org
WALLET_ENCRYPTION_KEY: ${{ secrets.PROD_WALLET_ENCRYPTION_KEY }}

- name: Build
run: npm run build
env:
NEXT_PUBLIC_APP_ENV: production
NEXT_PUBLIC_APP_URL: ${{ secrets.PROD_APP_URL }}

- name: Deploy to Vercel (Production)
run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }} --yes
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
57 changes: 57 additions & 0 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Deploy to Staging

on:
push:
branches:
- dev
- staging

jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: NeuroWealth-Frontend

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: Neurowealth-Frontend/package-lock.json

- name: Install dependencies
run: npm ci --legacy-peer-deps --omit=optional

- name: Validate environment variables
run: npm run validate:env
env:
NEXT_PUBLIC_APP_ENV: ${{ secrets.STAGING_APP_ENV }}
NEXT_PUBLIC_APP_URL: ${{ secrets.STAGING_APP_URL }}
WHATSAPP_APP_SECRET: ${{ secrets.STAGING_WHATSAPP_APP_SECRET }}
WHATSAPP_VERIFY_TOKEN: ${{ secrets.STAGING_WHATSAPP_VERIFY_TOKEN }}
WHATSAPP_ACCESS_TOKEN: ${{ secrets.STAGING_WHATSAPP_ACCESS_TOKEN }}
WHATSAPP_PHONE_NUMBER_ID: ${{ secrets.STAGING_WHATSAPP_PHONE_NUMBER_ID }}
WHATSAPP_WABA_ID: ${{ secrets.STAGING_WHATSAPP_WABA_ID }}
DB_HOST: ${{ secrets.STAGING_DB_HOST }}
DB_PORT: ${{ secrets.STAGING_DB_PORT }}
DB_NAME: ${{ secrets.STAGING_DB_NAME }}
DB_USER: ${{ secrets.STAGING_DB_USER }}
DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
STELLAR_NETWORK: testnet
STELLAR_HORIZON_URL: https://horizon-testnet.stellar.org
WALLET_ENCRYPTION_KEY: ${{ secrets.STAGING_WALLET_ENCRYPTION_KEY }}

- name: Build
run: npm run build
env:
NEXT_PUBLIC_APP_ENV: staging
NEXT_PUBLIC_APP_URL: ${{ secrets.STAGING_APP_URL }}

- name: Deploy to Vercel (Staging)
run: npx vercel --token=${{ secrets.VERCEL_TOKEN }} --yes
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ yarn-error.log*

# local env files
.env*.local
.env.production
.env.staging

# vercel
.vercel
Expand Down
62 changes: 62 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Deployment Guide for NeuroWealth Frontend

## Environments

| Environment | Branch | Stellar Network | URL |
|-------------|---------|-----------------|-----|
| Staging | `dev` | Testnet | https://staging.neurowealth.com |
| Production | `main` | **Mainnet** | https://neurowealth.com |

> ⚠️ Production uses Stellar Mainnet and real USDC. Never deploy untested code to `main`.

## Pre-Release Checklist

- [ ] PR reviewed and approved by at least 1 maintainer
- [ ] All CI checks passing (env validation + build)
- [ ] Staging deploy verified — test deposit, balance check, and withdrawal flows via WhatsApp
- [ ] No console errors on staging
- [ ] `STELLAR_NETWORK` confirmed as `mainnet` in production secrets
- [ ] `WALLET_ENCRYPTION_KEY` is unique and securely stored in a password manager
- [ ] Database migrations run on production DB if schema changed:
```bash
psql -d neurowealth -f backend/migrations/001_create_users_table.sql
psql -d neurowealth -f backend/migrations/002_create_deposits_table.sql
```
- [ ] CHANGELOG updated

## Deploying to Staging

Push to or merge a PR into `dev`. GitHub Actions runs automatically.
Monitor: **Actions → Deploy to Staging**

## Deploying to Production

Merge `dev` → `main` via a Pull Request. GitHub Actions runs automatically.
Monitor: **Actions → Deploy to Production**

## Rollback Instructions

### Option A — Vercel Dashboard (fastest, ~30 seconds)
1. Go to vercel.com → NeuroWealth project → **Deployments** tab
2. Find the last known-good deployment
3. Click **⋮ → Promote to Production**
4. Verify live URL is restored

### Option B — Git Revert (triggers redeploy)
```bash
git checkout main
git revert HEAD # or: git revert <bad-commit-sha>
git push origin main # CI will redeploy automatically
```

### Option C — Vercel CLI
```bash
vercel rollback --token=$VERCEL_TOKEN
```

## Post-Rollback Verification
- [ ] Site loads at production URL
- [ ] Send "hi" to NeuroWealth WhatsApp number — bot responds
- [ ] `NEXT_PUBLIC_APP_ENV` shows `production` in browser console
- [ ] Stellar Horizon URL points to `https://horizon.stellar.org` (mainnet)
- [ ] Notify team with: rollback reason, affected versions, resolution ETA
36 changes: 36 additions & 0 deletions lib/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const requiredEnvVars = [
"NEXT_PUBLIC_APP_ENV",
"NEXT_PUBLIC_APP_URL",
"WHATSAPP_APP_SECRET",
"WHATSAPP_VERIFY_TOKEN",
"WHATSAPP_ACCESS_TOKEN",
"WHATSAPP_PHONE_NUMBER_ID",
"WHATSAPP_WABA_ID",
"DB_HOST",
"DB_PORT",
"DB_NAME",
"DB_USER",
"DB_PASSWORD",
"STELLAR_NETWORK",
"STELLAR_HORIZON_URL",
"WALLET_ENCRYPTION_KEY",
] as const;

export function validateEnv() {
const missing = requiredEnvVars.filter((key) => !process.env[key]);
if (missing.length > 0) {
throw new Error(
`Missing required environment variables:\n${missing.map((k) => ` - ${k}`).join("\n")}`
);
}

const key = process.env.WALLET_ENCRYPTION_KEY!;
if (!/^[a-f0-9]{64}$/i.test(key)) {
throw new Error(" WALLET_ENCRYPTION_KEY must be a 64-character hex string");
}

const network = process.env.STELLAR_NETWORK!;
if (!["testnet", "mainnet"].includes(network)) {
throw new Error("STELLAR_NETWORK must be 'testnet' or 'mainnet'");
}
}
6 changes: 2 additions & 4 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
const nextConfig = {};

export default nextConfig;
export default nextConfig;
Loading