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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user