# MSP Quote Wizard Session Log - 2026-03-09 ## Session Summary Major deployment session for the MSP Quote Wizard. Started from code pulled from MacBook Air (commit a1a19f8), reviewed the full project, fixed 15+ backend model/schema mismatches, deployed frontend to azcomputerguru.com/quote on IX cPanel, debugged and fixed PHP reverse proxy, and applied comprehensive responsive design fixes to all wizard components. ### Key Accomplishments 1. Full backend model alignment with MariaDB schema (12+ field/table/enum fixes) 2. Frontend deployed to production at https://azcomputerguru.com/quote/ 3. PHP reverse proxy debugged and fixed (CURLOPT_FOLLOWLOCATION for FastAPI 307 redirects) 4. Comprehensive responsive design fixes across all 9 wizard components 5. End-to-end API flow verified: create -> get -> add item -> submit ### Key Decisions - Used PHP curl reverse proxy instead of direct API exposure (API on 172.16.3.30:8001, frontend on IX 172.16.3.10) - Made contact_name/contact_email nullable in DB to support draft quotes - Wrapped QuoteActivity details in JSON for MariaDB json_valid() CHECK constraint - Used `CURLOPT_FOLLOWLOCATION` to handle FastAPI trailing-slash 307 redirects - SSH to IX requires `-o IdentitiesOnly=yes -i ~/.ssh/id_ed25519` as root (too many keys causes auth failure) --- ## Infrastructure ### Servers - **API Server:** 172.16.3.30:8001 (FastAPI/Uvicorn, production ClaudeTools API) - **IX Server (Hosting):** 172.16.3.10 (cPanel/WHM, Apache, PHP 8.1.33) - SSH: `ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@172.16.3.10` - Root password: Gptf*77ttb!@#!@# - Site path: /home/azcomputerguru/public_html/quote/ - cPanel account: azcomputerguru - **Database:** 172.16.3.30:3306 / MariaDB 10.6.22 - DB: claudetools - User: claudetools - Password: CT_e8fcd5a3952030a79ed6debae6c954ed ### Deployment Architecture ``` Browser -> Cloudflare -> IX (172.16.3.10:443) -> /quote/ -> index.html (SPA) -> /quote/api/* -> .htaccess rewrite -> api-proxy.php -> curl -> 172.16.3.30:8001/api/* ``` ### Files on IX (/home/azcomputerguru/public_html/quote/) - index.html - SPA entry point - assets/ - JS/CSS bundles - api-proxy.php - PHP reverse proxy to API - .htaccess - Rewrite rules (API proxy + SPA routing) --- ## Backend Fixes Applied ### Model Alignment (api/models/quote.py) - Status enum: draft/submitted/viewed/followed_up/converted/expired (was reviewing/approved/rejected) - ServiceCategory enum: gps_monitoring/support_plan/voip/web_hosting/email/hardware/addon - BillingFrequency enum: monthly/yearly/one_time (was quarterly/annual) - NotificationType enum: email/webhook (was email_sent/sms_sent/admin_alert/reminder_sent) - Removed columns: notes, admin_notes, annual_total (don't exist in DB) - Fixed reserved word: metadata -> details (SQLAlchemy reserves metadata) - Fixed table name: quote_activities -> quote_activity - Removed TimestampMixin from QuoteItem/QuoteActivity/QuoteNotification (no updated_at) - Made contact_name/contact_email Optional for draft support - QuoteItem fields: service_name->product_name, setup_fee->setup_price, is_required->is_recommended, added product_code/tier, removed sort_order ### Database ALTERs Applied ```sql ALTER TABLE quotes MODIFY contact_name VARCHAR(255) NULL; ALTER TABLE quotes MODIFY contact_email VARCHAR(255) NULL; ``` ### Service Layer (api/services/quote_service.py) - calculate_totals() returns (monthly, setup) tuple (removed annual) - log_activity() wraps details in json.dumps({"message": details}) for json_valid() constraint - Removed all references to notes/admin_notes/annual_total - Syncro API key moved to env var SYNCRO_API_KEY - Admin email from env var ADMIN_NOTIFICATION_EMAIL ### API Routers - api/routers/quotes.py - 6 public endpoints (create, get, update, add item, remove item, submit) - api/routers/admin_quotes.py - 5 admin endpoints (list, stats, detail, update status, sync-syncro) - Both registered in api/main.py ### Dependencies Installed on Production ```bash pip install email-validator httpx ``` --- ## Frontend Changes ### Vite Config - base: '/quote/' for subdirectory deployment - build.outDir and sourcemap: false ### API Client (src/lib/api.ts) - Complete rewrite to match actual backend endpoints - Exports: createQuote, getQuote, updateQuote, addQuoteItem, removeQuoteItem, submitQuote, getQuotePdf ### Responsive Design Fixes (Applied 2026-03-09) All wizard components updated for mobile-first responsive design: **WizardContainer.tsx:** - Running totals bar: responsive padding (p-2.5 sm:p-4), text sizes (text-lg sm:text-2xl) - Step header: responsive padding (px-4 sm:px-6 md:px-8), icon sizes, truncation - Content area: responsive padding **Step1CompanyProfile.tsx:** - Endpoint count input: flex-col on mobile, w-full sm:w-32 **Step2GPSMonitoring.tsx:** - Tier grid: grid-cols-1 sm:grid-cols-2 md:grid-cols-3 - Equipment section: flex-shrink-0 on toggle, min-w-0 on text, responsive text sizes - Monthly total: responsive text (text-2xl sm:text-3xl), whitespace-nowrap **Step3SupportPlan.tsx:** - Plan grid: grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 - Block time grid: grid-cols-1 sm:grid-cols-3 - Toggle headers: flex-shrink-0, min-w-0, responsive text sizes - Monthly total: responsive sizing **Step4VoIP.tsx:** - Toggle header: responsive icon/text sizes, flex-shrink-0 - User count: flex-col sm:flex-row, w-full sm:w-24 - Tier grid: grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 - Hardware items: completely restructured - stacked layout with flex-wrap controls - Monthly total: responsive sizing **Step5WebEmail.tsx:** - All tier grids: sm:grid-cols-2 md:grid-cols-3 (was md:grid-cols-3 only) - Toggle headers: responsive icon/text/padding, flex-shrink-0 - Mailbox count: flex-col sm:flex-row - Monthly total: responsive sizing **Step6Summary.tsx:** - Grand total: flex-col sm:flex-row for monthly investment header - Text: text-3xl sm:text-4xl - SummarySection header: responsive padding, truncation, flex-shrink-0 **Step7Contact.tsx:** - Quote preview: flex-col sm:flex-row, responsive text - Contact preferences: flex-wrap - Trust indicators: flex-col sm:flex-row (was grid-cols-1 md:grid-cols-3) --- ## PHP Reverse Proxy (api-proxy.php) ### Key Fix: CURLOPT_FOLLOWLOCATION FastAPI returns 307 redirects for trailing-slash URLs. PHP curl doesn't follow redirects by default, causing empty response bodies. Fixed by adding: ```php curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_MAXREDIRS, 5); ``` ### Important: Host Header Required When testing from internal network, must use `Host: azcomputerguru.com` header. Direct IP access (172.16.3.10) hits wrong Apache vhost and PHP doesn't execute. Browser access works fine since it sends correct Host header. ```bash # WORKS: curl -s -H "Host: azcomputerguru.com" "http://172.16.3.10/quote/api/quotes" -X POST -H "Content-Type: application/json" -d '{"employee_count":5}' # FAILS (wrong vhost): curl -s "http://172.16.3.10/quote/api/quotes" -X POST ... ``` --- ## Pending/Next Steps 1. **Frontend polish:** Run through wizard in browser to visually verify responsive fixes 2. **Admin dashboard:** No admin UI yet for viewing submitted quotes (admin API endpoints exist) 3. **Email notifications:** ADMIN_NOTIFICATION_EMAIL env var needs to be set on production 4. **Syncro integration:** SYNCRO_API_KEY env var needs to be set for lead sync 5. **Remove debug endpoint:** Already done (removed _debug path from api-proxy.php) 6. **SSL/CORS:** Currently CORS is wide open (Access-Control-Allow-Origin: *) - consider restricting 7. **Quote PDF generation:** Endpoint exists but likely needs implementation 8. **Production env vars to set:** - ADMIN_NOTIFICATION_EMAIL - SYNCRO_API_KEY - SYNCRO_API_BASE_URL (defaults to computerguru.syncromsp.com) --- ## Commands Reference ### Deploy frontend to IX ```bash cd D:/ClaudeTools/projects/msp-tools/quote-wizard/frontend npm run build scp -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 -r dist/index.html dist/assets/ root@172.16.3.10:/home/azcomputerguru/public_html/quote/ ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@172.16.3.10 'chown -R azcomputerguru:azcomputerguru /home/azcomputerguru/public_html/quote/' ``` ### Deploy api-proxy.php ```bash scp -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 dist/api-proxy.php root@172.16.3.10:/home/azcomputerguru/public_html/quote/api-proxy.php ``` ### Test API through proxy ```bash curl -s -H "Host: azcomputerguru.com" -X POST -H "Content-Type: application/json" -d '{"employee_count":5}' "http://172.16.3.10/quote/api/quotes" ``` ### Test API directly ```bash curl -s -X POST -H "Content-Type: application/json" -d '{"employee_count":5}' "http://172.16.3.30:8001/api/quotes/" ```