diff --git a/deploy.sh b/deploy.sh
index cff5d9e..477aad4 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -1,156 +1,79 @@
#!/bin/bash
-#
-# POUNCE Deployment Script
-# Usage: ./deploy.sh [dev|prod]
-#
+
+# ============================================================================
+# POUNCE DEPLOY SCRIPT
+# Commits all changes and deploys to server
+# ============================================================================
set -e
-MODE=${1:-dev}
-echo "================================================"
-echo " POUNCE Deployment - Mode: $MODE"
-echo "================================================"
-echo ""
-
# Colors
-RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+RED='\033[0;31m'
NC='\033[0m' # No Color
-# Functions
-log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
-log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
-log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
+# Server config
+SERVER_USER="user"
+SERVER_HOST="10.42.0.73"
+SERVER_PATH="/home/user/pounce"
+SERVER_PASS="user"
-# ============================================
-# 1. Check prerequisites
-# ============================================
-log_info "Checking prerequisites..."
+echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
+echo -e "${BLUE} POUNCE DEPLOY SCRIPT ${NC}"
+echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
-if ! command -v python3 &> /dev/null; then
- log_error "Python3 not found. Please install Python 3.10+"
- exit 1
-fi
-
-if ! command -v node &> /dev/null; then
- log_error "Node.js not found. Please install Node.js 18+"
- exit 1
-fi
-
-if ! command -v npm &> /dev/null; then
- log_error "npm not found. Please install npm"
- exit 1
-fi
-
-log_info "Prerequisites OK!"
-echo ""
-
-# ============================================
-# 2. Setup Backend
-# ============================================
-log_info "Setting up Backend..."
-
-cd backend
-
-# Create virtual environment if not exists
-if [ ! -d "venv" ]; then
- log_info "Creating Python virtual environment..."
- python3 -m venv venv
-fi
-
-# Activate venv
-source venv/bin/activate
-
-# Install dependencies
-log_info "Installing Python dependencies..."
-pip install -q --upgrade pip
-pip install -q -r requirements.txt
-
-# Create .env if not exists
-if [ ! -f ".env" ]; then
- log_warn ".env file not found, copying from env.example..."
- if [ -f "env.example" ]; then
- cp env.example .env
- log_warn "Please edit backend/.env with your settings!"
- else
- log_error "env.example not found!"
- fi
-fi
-
-# Initialize database
-log_info "Initializing database..."
-python scripts/init_db.py
-
-cd ..
-log_info "Backend setup complete!"
-echo ""
-
-# ============================================
-# 3. Setup Frontend
-# ============================================
-log_info "Setting up Frontend..."
-
-cd frontend
-
-# Install dependencies
-log_info "Installing npm dependencies..."
-npm install --silent
-
-# Create .env.local if not exists
-if [ ! -f ".env.local" ]; then
- log_warn ".env.local not found, creating..."
- echo "NEXT_PUBLIC_API_URL=http://localhost:8000" > .env.local
-fi
-
-# Build for production
-if [ "$MODE" == "prod" ]; then
- log_info "Building for production..."
- npm run build
-fi
-
-cd ..
-log_info "Frontend setup complete!"
-echo ""
-
-# ============================================
-# 4. Start Services
-# ============================================
-if [ "$MODE" == "dev" ]; then
- echo ""
- echo "================================================"
- echo " Development Setup Complete!"
- echo "================================================"
- echo ""
- echo "To start the services:"
- echo ""
- echo " Backend (Terminal 1):"
- echo " cd backend && source venv/bin/activate"
- echo " uvicorn app.main:app --reload --host 0.0.0.0 --port 8000"
- echo ""
- echo " Frontend (Terminal 2):"
- echo " cd frontend && npm run dev"
- echo ""
- echo "Then open: http://localhost:3000"
- echo ""
+# Get commit message
+if [ -z "$1" ]; then
+ COMMIT_MSG="Deploy: $(date '+%Y-%m-%d %H:%M')"
else
- echo ""
- echo "================================================"
- echo " Production Setup Complete!"
- echo "================================================"
- echo ""
- echo "To start with PM2:"
- echo ""
- echo " # Backend"
- echo " cd backend && source venv/bin/activate"
- echo " pm2 start 'uvicorn app.main:app --host 0.0.0.0 --port 8000' --name pounce-backend"
- echo ""
- echo " # Frontend"
- echo " cd frontend"
- echo " pm2 start 'npm start' --name pounce-frontend"
- echo ""
- echo "Or use Docker:"
- echo " docker-compose up -d"
- echo ""
+ COMMIT_MSG="$1"
fi
+echo -e "\n${YELLOW}[1/5] Staging changes...${NC}"
+git add -A
+
+echo -e "\n${YELLOW}[2/5] Committing: ${COMMIT_MSG}${NC}"
+git commit -m "$COMMIT_MSG" || echo "Nothing to commit"
+
+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
+sshpass -p "$SERVER_PASS" rsync -avz --delete \
+ --exclude '__pycache__' \
+ --exclude '.pytest_cache' \
+ --exclude 'venv' \
+ --exclude '.git' \
+ --exclude '*.pyc' \
+ 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
+
+ # Build frontend
+ echo "Building frontend..."
+ cd frontend
+ npm run build 2>&1 | tail -10
+ cd ..
+
+ # Restart services
+ echo "Restarting services..."
+ ./start.sh 2>&1 | tail -5
+EOF
+
+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}"
diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx
index 4c700c0..1105ed3 100644
--- a/frontend/src/app/login/page.tsx
+++ b/frontend/src/app/login/page.tsx
@@ -102,18 +102,11 @@ function LoginForm() {
try {
await login(email, password)
- // Check if email is verified
- const user = await api.getMe()
- if (!user.is_verified) {
- // Redirect to verify-email page if not verified
- router.push(`/verify-email?email=${encodeURIComponent(email)}`)
- return
- }
-
// Clear stored redirect (was set during registration)
localStorage.removeItem('pounce_redirect_after_login')
// Redirect to intended destination or dashboard
+ // Note: Email verification is enforced by the backend if REQUIRE_EMAIL_VERIFICATION=true
router.push(sanitizeRedirect(redirectTo))
} catch (err: unknown) {
console.error('Login error:', err)
diff --git a/frontend/src/app/terminal/listing/page.tsx b/frontend/src/app/terminal/listing/page.tsx
index e195184..d1bd1b7 100755
--- a/frontend/src/app/terminal/listing/page.tsx
+++ b/frontend/src/app/terminal/listing/page.tsx
@@ -4,29 +4,14 @@ import { useEffect, useState, useCallback } from 'react'
import { useSearchParams } from 'next/navigation'
import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
-import { CommandCenterLayout } from '@/components/CommandCenterLayout'
+import { Sidebar } from '@/components/Sidebar'
import {
- Plus,
- Shield,
- Eye,
- MessageSquare,
- ExternalLink,
- Loader2,
- Trash2,
- CheckCircle,
- AlertCircle,
- Copy,
- DollarSign,
- X,
- Tag,
- Sparkles,
- ArrowRight,
- TrendingUp,
- Globe,
- Crown,
- Check
+ Plus, Shield, Eye, MessageSquare, ExternalLink, Loader2, Trash2,
+ CheckCircle, AlertCircle, Copy, DollarSign, X, Tag, Sparkles,
+ TrendingUp, Gavel, Target, Menu, Settings, LogOut, Crown, Zap, Coins, Check
} from 'lucide-react'
import Link from 'next/link'
+import Image from 'next/image'
import clsx from 'clsx'
// ============================================================================
@@ -44,39 +29,13 @@ interface Listing {
currency: string
price_type: string
pounce_score: number | null
- estimated_value: number | null
verification_status: string
is_verified: boolean
status: string
- show_valuation: boolean
- allow_offers: boolean
view_count: number
inquiry_count: number
public_url: string
created_at: string
- published_at: string | null
-}
-
-interface VerificationInfo {
- verification_code: string
- dns_record_type: string
- dns_record_name: string
- dns_record_value: string
- instructions: string
- status: string
-}
-
-interface Inquiry {
- id: number
- name: string
- email: string
- phone: string | null
- company: string | null
- message: string
- offer_amount: number | null
- status: string
- created_at: string
- read_at: string | null
}
// ============================================================================
@@ -84,683 +43,370 @@ interface Inquiry {
// ============================================================================
export default function MyListingsPage() {
- const { subscription } = useStore()
+ const { subscription, user, logout, checkAuth } = useStore()
const searchParams = useSearchParams()
const prefillDomain = searchParams.get('domain')
const [listings, setListings] = useState
([])
const [loading, setLoading] = useState(true)
-
const [showCreateModal, setShowCreateModal] = useState(false)
- const [showVerifyModal, setShowVerifyModal] = useState(false)
- const [showInquiriesModal, setShowInquiriesModal] = useState(false)
- const [selectedListing, setSelectedListing] = useState(null)
- const [verificationInfo, setVerificationInfo] = useState(null)
- const [inquiries, setInquiries] = useState([])
- const [loadingInquiries, setLoadingInquiries] = useState(false)
- const [verifying, setVerifying] = useState(false)
- const [creating, setCreating] = useState(false)
- const [error, setError] = useState(null)
- const [success, setSuccess] = useState(null)
- const [copied, setCopied] = useState(false)
+ const [deletingId, setDeletingId] = useState(null)
+ const [menuOpen, setMenuOpen] = useState(false)
- const [newListing, setNewListing] = useState({
- domain: '',
- title: '',
- description: '',
- asking_price: '',
- price_type: 'negotiable',
- allow_offers: true,
- })
-
const tier = subscription?.tier || 'scout'
- const limits: Record = { scout: 0, trader: 5, tycoon: 50 }
- const maxListings = limits[tier] || 0
- const canList = tier !== 'scout'
+ const listingLimits: Record = { scout: 3, trader: 25, tycoon: 100 }
+ const maxListings = listingLimits[tier] || 3
+ const canAddMore = listings.length < maxListings
const isTycoon = tier === 'tycoon'
- const activeCount = listings.filter(l => l.status === 'active').length
+ const activeListings = listings.filter(l => l.status === 'active').length
const totalViews = listings.reduce((sum, l) => sum + l.view_count, 0)
const totalInquiries = listings.reduce((sum, l) => sum + l.inquiry_count, 0)
+ useEffect(() => { checkAuth() }, [checkAuth])
+ useEffect(() => { if (prefillDomain) setShowCreateModal(true) }, [prefillDomain])
+
const loadListings = useCallback(async () => {
setLoading(true)
try {
- const data = await api.request('/listings/my')
+ const data = await api.getMyListings()
setListings(data)
- } catch (err: any) {
- console.error('Failed to load listings:', err)
- } finally {
- setLoading(false)
- }
+ } catch (err) { console.error(err) }
+ finally { setLoading(false) }
}, [])
- useEffect(() => {
- loadListings()
- }, [loadListings])
+ useEffect(() => { loadListings() }, [loadListings])
- useEffect(() => {
- if (prefillDomain) {
- setNewListing(prev => ({ ...prev, domain: prefillDomain }))
- setShowCreateModal(true)
- }
- }, [prefillDomain])
-
- const handleCreate = async (e: React.FormEvent) => {
- e.preventDefault()
- setCreating(true)
- setError(null)
-
+ const handleDelete = async (id: number, domain: string) => {
+ if (!confirm(`Delete listing for ${domain}?`)) return
+ setDeletingId(id)
try {
- await api.request('/listings', {
- method: 'POST',
- body: JSON.stringify({
- domain: newListing.domain,
- title: newListing.title || null,
- description: newListing.description || null,
- asking_price: newListing.asking_price ? parseFloat(newListing.asking_price) : null,
- price_type: newListing.price_type,
- allow_offers: newListing.allow_offers,
- }),
- })
- setSuccess('Listing created! Verify ownership to publish.')
- setShowCreateModal(false)
- setNewListing({ domain: '', title: '', description: '', asking_price: '', price_type: 'negotiable', allow_offers: true })
- loadListings()
- } catch (err: any) {
- setError(err.message)
- } finally {
- setCreating(false)
- }
+ await api.deleteListing(id)
+ await loadListings()
+ } catch (err: any) { alert(err.message || 'Failed') }
+ finally { setDeletingId(null) }
}
- const handleStartVerification = async (listing: Listing) => {
- setSelectedListing(listing)
- setVerifying(true)
-
- try {
- const info = await api.request(`/listings/${listing.id}/verify-dns`, {
- method: 'POST',
- })
- setVerificationInfo(info)
- setShowVerifyModal(true)
- } catch (err: any) {
- setError(err.message)
- } finally {
- setVerifying(false)
- }
- }
-
- const handleViewInquiries = async (listing: Listing) => {
- setSelectedListing(listing)
- setLoadingInquiries(true)
- setShowInquiriesModal(true)
-
- try {
- const data = await api.request(`/listings/${listing.id}/inquiries`)
- setInquiries(data)
- } catch (err: any) {
- setError(err.message)
- setShowInquiriesModal(false)
- } finally {
- setLoadingInquiries(false)
- }
- }
-
- const handleCheckVerification = async () => {
- if (!selectedListing) return
- setVerifying(true)
-
- try {
- const result = await api.request<{ verified: boolean; message: string }>(
- `/listings/${selectedListing.id}/verify-dns/check`
- )
-
- if (result.verified) {
- setSuccess('Domain verified! You can now publish.')
- setShowVerifyModal(false)
- loadListings()
- } else {
- setError(result.message)
- }
- } catch (err: any) {
- setError(err.message)
- } finally {
- setVerifying(false)
- }
- }
-
- const handlePublish = async (listing: Listing) => {
- try {
- await api.request(`/listings/${listing.id}`, {
- method: 'PUT',
- body: JSON.stringify({ status: 'active' }),
- })
- setSuccess('Listing published!')
- loadListings()
- } catch (err: any) {
- setError(err.message)
- }
- }
-
- const handleDelete = async (listing: Listing) => {
- if (!confirm(`Delete listing for ${listing.domain}?`)) return
-
- try {
- await api.request(`/listings/${listing.id}`, { method: 'DELETE' })
- setSuccess('Listing deleted')
- loadListings()
- } catch (err: any) {
- setError(err.message)
- }
- }
-
- const copyToClipboard = (text: string) => {
- navigator.clipboard.writeText(text)
- setCopied(true)
- setTimeout(() => setCopied(false), 2000)
- }
-
- const formatPrice = (price: number | null, currency: string) => {
- if (!price) return 'Make Offer'
- return new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency,
- minimumFractionDigits: 0,
- }).format(price)
- }
+ 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/intel', label: 'Intel', icon: TrendingUp, active: false },
+ ]
+
+ const tierName = subscription?.tier_name || subscription?.tier || 'Scout'
+ 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: Target },
+ ]},
+ { title: 'Monetize', items: [
+ { href: '/terminal/yield', label: 'Yield', icon: Coins },
+ { href: '/terminal/listing', label: 'For Sale', icon: Tag, active: true },
+ ]}
+ ]
return (
-
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {/* HEADER */}
- {/* ═══════════════════════════════════════════════════════════════════════ */}
-
-
-
-
-
- Marketplace
-
-
-
- For Sale
- {listings.length}/{maxListings}
-
-
-
- List domains on Pounce Marketplace. 0% commission.
-
-
-
-
- {canList && (
-
-
-
{activeCount}
-
Active
-
-
-
-
{totalInquiries}
-
Inquiries
-
-
- )}
-
-
-
-
-
-
- {/* Messages */}
- {error && (
-
-
-
{error}
-
-
- )}
+
+
- {success && (
-
-
-
{success}
-
-
- )}
-
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {/* PAYWALL */}
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {!canList && (
-
-
-
-
Unlock Domain Selling
-
- List your domains with 0% commission on Pounce Marketplace.
-
-
-
-
-
-
Trader
-
$9/mo
-
5 Listings
+
+ {/* MOBILE HEADER */}
+