sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00

Synced files:
- Quote wizard frontend (all components, hooks, types, config)
- API updates (config, models, routers, schemas, services)
- Client work (bg-builders, gurushow)
- Scripts (BGB Lesley termination, CIPP, Datto, migration)
- Temp files (Bardach contacts, VWP investigation, misc)
- Credentials and session logs
- Email service, PHP API, session logs

Machine: ACG-M-L5090
Timestamp: 2026-03-10 19:11:00

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 19:59:08 -07:00
parent 84fce5a621
commit af72a12e3e
168 changed files with 879909 additions and 1243 deletions

View File

@@ -1,8 +1,17 @@
import axios from 'axios';
import type { QuoteData, QuoteResult } from '@/types/quote';
/**
* API client for MSP Quote Wizard
*
* Proxied via /msp-api/ -> backend /api/ on 172.16.3.30:8001
* Endpoints:
* - POST /quotes - Create quote draft
* - GET /quotes/{access_token} - Get quote
* - PUT /quotes/{access_token} - Update quote
* - POST /quotes/{access_token}/items - Add item
* - DELETE /quotes/{access_token}/items/{item_id} - Remove item
* - POST /quotes/{access_token}/submit - Submit quote
* - GET /quotes/{access_token}/pdf - Get PDF (501 placeholder)
*/
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8001';
@@ -15,70 +24,179 @@ export const apiClient = axios.create({
timeout: 10000,
});
// Request interceptor for adding auth token
apiClient.interceptors.request.use(
(config) => {
const token = localStorage.getItem('quote_wizard_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor for error handling
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('quote_wizard_token');
}
return Promise.reject(error);
}
);
// -- Response types matching backend schemas --
export interface QuoteCreatedResponse {
id: string;
access_token: string;
status: string;
message: string;
}
export interface QuoteItemResponse {
id: string;
quote_id: string;
service_name: string;
service_description: string | null;
category: string;
billing_frequency: string;
unit_price: string;
quantity: number;
setup_fee: string | null;
is_required: boolean;
sort_order: number;
line_total: string;
monthly_amount: string;
created_at: string;
updated_at: string;
}
export interface QuoteResponse {
id: string;
access_token: string;
status: string;
company_name: string | null;
contact_name: string | null;
contact_email: string | null;
contact_phone: string | null;
employee_count: number | null;
notes: string | null;
monthly_total: string;
setup_total: string;
annual_total: string;
expires_at: string | null;
submitted_at: string | null;
created_at: string;
updated_at: string;
items: QuoteItemResponse[];
}
// -- Request types matching backend schemas --
export interface QuoteCreateRequest {
employee_count?: number;
notes?: string;
items?: QuoteItemCreateRequest[];
}
export interface QuoteUpdateRequest {
company_name?: string;
contact_name?: string;
contact_email?: string;
contact_phone?: string;
employee_count?: number;
notes?: string;
items?: QuoteItemCreateRequest[];
}
export interface QuoteItemCreateRequest {
category: string;
product_code: string;
product_name: string;
description?: string;
quantity: number;
unit_price: string;
setup_price?: string;
billing_frequency: string;
tier?: string;
is_recommended?: boolean;
}
export interface QuoteSubmitRequest {
company_name: string;
contact_name: string;
contact_email: string;
contact_phone?: string;
notes?: string;
}
// -- API functions --
/**
* API endpoints
* Create a new quote draft. Returns access token for future operations.
*/
export const quoteApi = {
/**
* Calculate quote based on provided data
*/
calculateQuote: async (data: QuoteData): Promise<QuoteResult> => {
const response = await apiClient.post<QuoteResult>('/api/quotes/calculate', data);
return response.data;
},
export async function createQuote(data: QuoteCreateRequest): Promise<QuoteCreatedResponse> {
const response = await apiClient.post<QuoteCreatedResponse>('/quotes', data);
return response.data;
}
/**
* Save quote for later retrieval
*/
saveQuote: async (data: QuoteData & { email: string }): Promise<{ quoteId: string }> => {
const response = await apiClient.post<{ quoteId: string }>('/api/quotes/save', data);
return response.data;
},
/**
* Get a quote by its access token.
*/
export async function getQuote(accessToken: string): Promise<QuoteResponse> {
const response = await apiClient.get<QuoteResponse>(`/quotes/${accessToken}`);
return response.data;
}
/**
* Retrieve saved quote by ID
*/
getQuote: async (quoteId: string): Promise<QuoteData & QuoteResult> => {
const response = await apiClient.get<QuoteData & QuoteResult>(`/api/quotes/${quoteId}`);
return response.data;
},
/**
* Update a draft quote (wizard progress saves).
*/
export async function updateQuote(
accessToken: string,
data: QuoteUpdateRequest,
): Promise<QuoteResponse> {
const response = await apiClient.put<QuoteResponse>(
`/quotes/${accessToken}`,
data,
);
return response.data;
}
/**
* Submit quote request for sales follow-up
*/
submitQuoteRequest: async (data: QuoteData & {
contactInfo: {
name: string;
email: string;
phone?: string;
}
}): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.post('/api/quotes/submit', data);
return response.data;
},
};
/**
* Add a single item to a quote.
*/
export async function addQuoteItem(
accessToken: string,
item: QuoteItemCreateRequest,
): Promise<QuoteResponse> {
const response = await apiClient.post<QuoteResponse>(
`/quotes/${accessToken}/items`,
item,
);
return response.data;
}
/**
* Remove an item from a quote.
*/
export async function removeQuoteItem(
accessToken: string,
itemId: string,
): Promise<QuoteResponse> {
const response = await apiClient.delete<QuoteResponse>(
`/quotes/${accessToken}/items/${itemId}`,
);
return response.data;
}
/**
* Submit a finalized quote with contact information.
*/
export async function submitQuote(
accessToken: string,
data: QuoteSubmitRequest,
): Promise<QuoteResponse> {
const response = await apiClient.post<QuoteResponse>(
`/quotes/${accessToken}/submit`,
data,
);
return response.data;
}
/**
* Get quote PDF. Currently returns 501 Not Implemented.
*/
export async function getQuotePdf(accessToken: string): Promise<Blob> {
const response = await apiClient.get(`/quotes/${accessToken}/pdf`, {
responseType: 'blob',
});
return response.data;
}