diff --git a/deploy.sh b/deploy.sh index 1990b43..fdaa8ba 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,8 +1,11 @@ #!/bin/bash # ============================================================================ -# POUNCE DEPLOY SCRIPT -# Commits all changes and deploys to server +# POUNCE ZERO-DOWNTIME DEPLOY SCRIPT +# - Builds locally first (optional) +# - Syncs only changed files +# - Hot-reloads backend without full restart +# - Rebuilds frontend in background # ============================================================================ set -e @@ -12,6 +15,7 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' RED='\033[0;31m' +CYAN='\033[0;36m' NC='\033[0m' # No Color # Server config @@ -20,62 +24,163 @@ SERVER_HOST="10.42.0.73" SERVER_PATH="/home/user/pounce" SERVER_PASS="user" +# Parse flags +QUICK_MODE=false +BACKEND_ONLY=false +FRONTEND_ONLY=false + +while [[ "$#" -gt 0 ]]; do + case $1 in + -q|--quick) QUICK_MODE=true ;; + -b|--backend) BACKEND_ONLY=true ;; + -f|--frontend) FRONTEND_ONLY=true ;; + *) COMMIT_MSG="$1" ;; + esac + shift +done + echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" -echo -e "${BLUE} POUNCE DEPLOY SCRIPT ${NC}" +echo -e "${BLUE} POUNCE ZERO-DOWNTIME DEPLOY ${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" -# Get commit message -if [ -z "$1" ]; then - COMMIT_MSG="Deploy: $(date '+%Y-%m-%d %H:%M')" -else - COMMIT_MSG="$1" +if $QUICK_MODE; then + echo -e "${CYAN}⚡ Quick mode: Skipping git, only syncing changes${NC}" fi -echo -e "\n${YELLOW}[1/5] Staging changes...${NC}" -git add -A +if $BACKEND_ONLY; then + echo -e "${CYAN}🔧 Backend only mode${NC}" +fi -echo -e "\n${YELLOW}[2/5] Committing: ${COMMIT_MSG}${NC}" -git commit -m "$COMMIT_MSG" || echo "Nothing to commit" +if $FRONTEND_ONLY; then + echo -e "${CYAN}🎨 Frontend only mode${NC}" +fi -echo -e "\n${YELLOW}[3/5] Pushing to git.6bit.ch...${NC}" -git push origin main - -echo -e "\n${YELLOW}[4/5] Syncing files to server...${NC}" -# Sync frontend -sshpass -p "$SERVER_PASS" rsync -avz --delete \ - --exclude 'node_modules' \ - --exclude '.next' \ - --exclude '.git' \ - frontend/ $SERVER_USER@$SERVER_HOST:$SERVER_PATH/frontend/ - -# Sync backend (exclude .env to preserve server config!) -sshpass -p "$SERVER_PASS" rsync -avz --delete \ - --exclude '__pycache__' \ - --exclude '.pytest_cache' \ - --exclude 'venv' \ - --exclude '.git' \ - --exclude '*.pyc' \ - --exclude '.env' \ - --exclude '*.db' \ - backend/ $SERVER_USER@$SERVER_HOST:$SERVER_PATH/backend/ - -echo -e "\n${YELLOW}[5/5] Building and restarting on server...${NC}" -sshpass -p "$SERVER_PASS" ssh $SERVER_USER@$SERVER_HOST << 'EOF' - cd ~/pounce +# Step 1: Git (unless quick mode) +if ! $QUICK_MODE; then + echo -e "\n${YELLOW}[1/4] Git operations...${NC}" - # Build frontend - echo "Building frontend..." - cd frontend - npm run build 2>&1 | tail -10 - cd .. + # Check for changes + if git diff --quiet && git diff --staged --quiet; then + echo " No changes to commit" + else + git add -A + + if [ -z "$COMMIT_MSG" ]; then + COMMIT_MSG="Deploy: $(date '+%Y-%m-%d %H:%M')" + fi + + git commit -m "$COMMIT_MSG" || true + echo " ✓ Committed: $COMMIT_MSG" + fi - # Restart services - echo "Restarting services..." - ./start.sh 2>&1 | tail -5 -EOF + git push origin main 2>/dev/null && echo " ✓ Pushed to git.6bit.ch" || echo " ⚠ Push failed or nothing to push" +else + echo -e "\n${YELLOW}[1/4] Skipping git (quick mode)${NC}" +fi +# Step 2: Sync files (only changed) +echo -e "\n${YELLOW}[2/4] Syncing changed files...${NC}" + +RSYNC_OPTS="-az --info=name1 --delete" + +if ! $BACKEND_ONLY; then + echo " Frontend:" + sshpass -p "$SERVER_PASS" rsync $RSYNC_OPTS \ + --exclude 'node_modules' \ + --exclude '.next' \ + --exclude '.git' \ + frontend/ $SERVER_USER@$SERVER_HOST:$SERVER_PATH/frontend/ 2>&1 | sed 's/^/ /' +fi + +if ! $FRONTEND_ONLY; then + echo " Backend:" + sshpass -p "$SERVER_PASS" rsync $RSYNC_OPTS \ + --exclude '__pycache__' \ + --exclude '.pytest_cache' \ + --exclude 'venv' \ + --exclude '.git' \ + --exclude '*.pyc' \ + --exclude '.env' \ + --exclude '*.db' \ + backend/ $SERVER_USER@$SERVER_HOST:$SERVER_PATH/backend/ 2>&1 | sed 's/^/ /' +fi + +# Step 3: Reload backend (graceful, no restart) +if ! $FRONTEND_ONLY; then + echo -e "\n${YELLOW}[3/4] Reloading backend (graceful)...${NC}" + sshpass -p "$SERVER_PASS" ssh $SERVER_USER@$SERVER_HOST << 'BACKEND_EOF' + # Signal uvicorn to reload (if running with --reload) + # Otherwise, just check it's running + BACKEND_PID=$(pgrep -f 'uvicorn app.main:app' | head -1) + + if [ -n "$BACKEND_PID" ]; then + # Touch a file to trigger auto-reload if uvicorn has --reload + touch ~/pounce/backend/app/main.py + echo " ✓ Backend reload triggered (PID: $BACKEND_PID)" + else + echo " ⚠ Backend not running, starting..." + cd ~/pounce/backend + source ../venv/bin/activate + nohup uvicorn app.main:app --host 0.0.0.0 --port 8000 > backend.log 2>&1 & + sleep 2 + echo " ✓ Backend started" + fi +BACKEND_EOF +else + echo -e "\n${YELLOW}[3/4] Skipping backend (frontend only)${NC}" +fi + +# Step 4: Rebuild frontend (in background to minimize downtime) +if ! $BACKEND_ONLY; then + echo -e "\n${YELLOW}[4/4] Rebuilding frontend...${NC}" + sshpass -p "$SERVER_PASS" ssh $SERVER_USER@$SERVER_HOST << 'FRONTEND_EOF' + cd ~/pounce/frontend + + # Build new version + echo " Building..." + npm run build 2>&1 | grep -E '(✓|○|λ|Error|error)' | head -10 | sed 's/^/ /' + + BUILD_EXIT=$? + + if [ $BUILD_EXIT -eq 0 ]; then + # Gracefully restart Next.js + NEXT_PID=$(pgrep -f 'next start' | head -1) + + if [ -n "$NEXT_PID" ]; then + echo " Restarting Next.js (PID: $NEXT_PID)..." + kill $NEXT_PID 2>/dev/null + sleep 1 + fi + + # Start new instance + nohup npm run start > frontend.log 2>&1 & + sleep 2 + + # Verify + NEW_PID=$(pgrep -f 'next start' | head -1) + if [ -n "$NEW_PID" ]; then + echo " ✓ Frontend running (PID: $NEW_PID)" + else + echo " ⚠ Frontend may not have started correctly" + fi + else + echo " ✗ Build failed, keeping old version" + fi +FRONTEND_EOF +else + echo -e "\n${YELLOW}[4/4] Skipping frontend (backend only)${NC}" +fi + +# Summary echo -e "\n${GREEN}═══════════════════════════════════════════════════════════════${NC}" echo -e "${GREEN} ✅ DEPLOY COMPLETE! ${NC}" echo -e "${GREEN}═══════════════════════════════════════════════════════════════${NC}" -echo -e "\nFrontend: ${BLUE}http://$SERVER_HOST:3000${NC}" -echo -e "Backend: ${BLUE}http://$SERVER_HOST:8000${NC}" +echo -e "" +echo -e " Frontend: ${BLUE}https://pounce.ch${NC} / http://$SERVER_HOST:3000" +echo -e " Backend: ${BLUE}https://pounce.ch/api${NC} / http://$SERVER_HOST:8000" +echo -e "" +echo -e "${CYAN}Quick commands:${NC}" +echo -e " ./deploy.sh -q # Quick sync, no git" +echo -e " ./deploy.sh -b # Backend only" +echo -e " ./deploy.sh -f # Frontend only" +echo -e " ./deploy.sh \"message\" # Full deploy with commit message" diff --git a/frontend/src/app/terminal/settings/page.tsx b/frontend/src/app/terminal/settings/page.tsx index 58f45ea..72215a9 100644 --- a/frontend/src/app/terminal/settings/page.tsx +++ b/frontend/src/app/terminal/settings/page.tsx @@ -30,8 +30,15 @@ import { BarChart3, MessageSquare, Database, + Menu, + Eye, + Gavel, + Tag, + Coins, + LogOut, } from 'lucide-react' import Link from 'next/link' +import Image from 'next/image' import clsx from 'clsx' type SettingsTab = 'profile' | 'notifications' | 'billing' | 'security' @@ -111,12 +118,13 @@ const PLANS = [ export default function SettingsPage() { const router = useRouter() - const { user, isAuthenticated, isLoading, checkAuth, subscription } = useStore() + const { user, isAuthenticated, isLoading, checkAuth, subscription, logout } = useStore() const [activeTab, setActiveTab] = useState('profile') const [saving, setSaving] = useState(false) const [success, setSuccess] = useState(null) const [error, setError] = useState(null) + const [menuOpen, setMenuOpen] = useState(false) const [profileForm, setProfileForm] = useState({ name: '', @@ -303,6 +311,41 @@ export default function SettingsPage() { { id: 'security' as const, label: 'Security', icon: Shield }, ] + // Mobile Navigation + const mobileNavItems = [ + { href: '/terminal/radar', label: 'Radar', icon: Target, active: false }, + { href: '/terminal/market', label: 'Market', icon: Gavel, active: false }, + { href: '/terminal/watchlist', label: 'Watch', icon: Eye, active: false }, + { href: '/terminal/settings', label: 'Settings', icon: Settings, active: true }, + ] + + const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap + + const drawerNavSections = [ + { + title: 'Discover', + items: [ + { href: '/terminal/radar', label: 'Radar', icon: Target }, + { href: '/terminal/market', label: 'Market', icon: Gavel }, + { href: '/terminal/intel', label: 'Intel', icon: TrendingUp }, + ] + }, + { + title: 'Manage', + items: [ + { href: '/terminal/watchlist', label: 'Watchlist', icon: Eye }, + { href: '/terminal/sniper', label: 'Sniper', icon: Bell }, + ] + }, + { + title: 'Monetize', + items: [ + { href: '/terminal/yield', label: 'Yield', icon: Coins }, + { href: '/terminal/listing', label: 'For Sale', icon: Tag }, + ] + }, + ] + return ( {/* ═══════════════════════════════════════════════════════════════════════ */} @@ -820,6 +863,110 @@ export default function SettingsPage() { + + {/* Mobile Bottom Padding */} +
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* MOBILE BOTTOM NAVIGATION */} + {/* ═══════════════════════════════════════════════════════════════════════ */} + + + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* NAVIGATION DRAWER */} + {/* ═══════════════════════════════════════════════════════════════════════ */} + {menuOpen && ( + <> + {/* Backdrop */} +
setMenuOpen(false)} + /> + + {/* Drawer */} +
+ {/* Header */} +
+
+ Pounce + Terminal v1.0 +
+ +
+ + {/* Nav Sections */} +
+ {drawerNavSections.map((section) => ( +
+
+ {section.title} +
+ {section.items.map((item) => ( + setMenuOpen(false)} + className="flex items-center gap-3 px-4 py-2.5 text-white/60 hover:text-white hover:bg-white/[0.03] transition-colors" + > + + {item.label} + + ))} +
+ ))} +
+ + {/* User Section */} +
+
+
+ +
+
+

{user?.name || user?.email}

+

{tierName}

+
+
+ +
+
+ + )} ) }