sync: Auto-sync from Mikes-MacBook-Air.local at 2026-03-09 08:14:13
Synced files: - Session logs updated - Latest context and credentials - Command/directive updates Machine: Mikes-MacBook-Air.local Timestamp: 2026-03-09 08:14:13 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
84
projects/msp-tools/quote-wizard/frontend/src/lib/api.ts
Normal file
84
projects/msp-tools/quote-wizard/frontend/src/lib/api.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import axios from 'axios';
|
||||
import type { QuoteData, QuoteResult } from '@/types/quote';
|
||||
|
||||
/**
|
||||
* API client for MSP Quote Wizard
|
||||
*/
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8001';
|
||||
|
||||
export const apiClient = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
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);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* API endpoints
|
||||
*/
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
};
|
||||
423
projects/msp-tools/quote-wizard/frontend/src/lib/pricing-data.ts
Normal file
423
projects/msp-tools/quote-wizard/frontend/src/lib/pricing-data.ts
Normal file
@@ -0,0 +1,423 @@
|
||||
import type {
|
||||
GPSTier,
|
||||
SupportPlan,
|
||||
BlockTimeOption,
|
||||
VoIPTier,
|
||||
WebHostingTier,
|
||||
EmailTier,
|
||||
VoIPHardware
|
||||
} from '@/types/quote';
|
||||
|
||||
/**
|
||||
* GPS Monitoring Tiers
|
||||
*/
|
||||
export const gpsTiers: GPSTier[] = [
|
||||
{
|
||||
id: 'basic',
|
||||
name: 'Basic',
|
||||
description: 'Essential monitoring for small environments',
|
||||
pricePerEndpoint: 19,
|
||||
features: [
|
||||
'Remote monitoring & management',
|
||||
'8x5 help desk support',
|
||||
'Patch management',
|
||||
'Basic antivirus protection',
|
||||
'Monthly health reports',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'pro',
|
||||
name: 'Pro',
|
||||
description: 'Comprehensive protection for growing businesses',
|
||||
pricePerEndpoint: 26,
|
||||
features: [
|
||||
'Everything in Basic, plus:',
|
||||
'24x7 help desk support',
|
||||
'Advanced endpoint protection',
|
||||
'Backup & disaster recovery',
|
||||
'Network monitoring',
|
||||
'Quarterly business reviews',
|
||||
],
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
name: 'Advanced',
|
||||
description: 'Enterprise-grade security and compliance',
|
||||
pricePerEndpoint: 39,
|
||||
features: [
|
||||
'Everything in Pro, plus:',
|
||||
'Dedicated account manager',
|
||||
'Virtual CIO services',
|
||||
'Compliance management',
|
||||
'Security awareness training',
|
||||
'Advanced threat detection',
|
||||
'Priority response SLA',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Equipment monitoring pricing
|
||||
*/
|
||||
export const equipmentMonitoring = {
|
||||
basePrice: 25, // Up to 10 devices
|
||||
baseDevices: 10,
|
||||
additionalDevicePrice: 3, // Per additional device
|
||||
};
|
||||
|
||||
/**
|
||||
* Support Plans
|
||||
*/
|
||||
export const supportPlans: SupportPlan[] = [
|
||||
{
|
||||
id: 'essential',
|
||||
name: 'Essential',
|
||||
description: 'Basic support for small teams',
|
||||
monthlyPrice: 200,
|
||||
includedHours: 2,
|
||||
effectiveHourlyRate: 100,
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'standard',
|
||||
name: 'Standard',
|
||||
description: 'Balanced support for growing businesses',
|
||||
monthlyPrice: 380,
|
||||
includedHours: 4,
|
||||
effectiveHourlyRate: 95,
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
id: 'premium',
|
||||
name: 'Premium',
|
||||
description: 'Enhanced support with faster response',
|
||||
monthlyPrice: 540,
|
||||
includedHours: 6,
|
||||
effectiveHourlyRate: 90,
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'priority',
|
||||
name: 'Priority',
|
||||
description: 'Top-tier support with dedicated resources',
|
||||
monthlyPrice: 850,
|
||||
includedHours: 10,
|
||||
effectiveHourlyRate: 85,
|
||||
recommended: false,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Block Time Options
|
||||
*/
|
||||
export const blockTimeOptions: BlockTimeOption[] = [
|
||||
{
|
||||
id: 'block-10',
|
||||
hours: 10,
|
||||
price: 1500,
|
||||
effectiveHourlyRate: 150,
|
||||
},
|
||||
{
|
||||
id: 'block-20',
|
||||
hours: 20,
|
||||
price: 2600,
|
||||
effectiveHourlyRate: 130,
|
||||
},
|
||||
{
|
||||
id: 'block-30',
|
||||
hours: 30,
|
||||
price: 3000,
|
||||
effectiveHourlyRate: 100,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* VoIP Tiers
|
||||
*/
|
||||
export const voipTiers: VoIPTier[] = [
|
||||
{
|
||||
id: 'voip-basic',
|
||||
name: 'Basic',
|
||||
description: 'Essential phone features for small teams',
|
||||
pricePerUser: 22,
|
||||
features: [
|
||||
'Unlimited local & long distance',
|
||||
'Voicemail to email',
|
||||
'Basic auto-attendant',
|
||||
'Mobile app',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'voip-standard',
|
||||
name: 'Standard',
|
||||
description: 'Full-featured business phone system',
|
||||
pricePerUser: 28,
|
||||
features: [
|
||||
'Everything in Basic, plus:',
|
||||
'Video conferencing',
|
||||
'Ring groups',
|
||||
'Call recording',
|
||||
'CRM integration',
|
||||
],
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
id: 'voip-pro',
|
||||
name: 'Pro',
|
||||
description: 'Advanced features for power users',
|
||||
pricePerUser: 35,
|
||||
features: [
|
||||
'Everything in Standard, plus:',
|
||||
'Advanced analytics',
|
||||
'Custom IVR',
|
||||
'Supervisor dashboard',
|
||||
'API access',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'voip-callcenter',
|
||||
name: 'Call Center',
|
||||
description: 'Full call center capabilities',
|
||||
pricePerUser: 55,
|
||||
features: [
|
||||
'Everything in Pro, plus:',
|
||||
'Queue management',
|
||||
'Wallboards',
|
||||
'Agent scoring',
|
||||
'Predictive dialing',
|
||||
'Real-time monitoring',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* VoIP Hardware Options
|
||||
*/
|
||||
export const voipHardware: VoIPHardware[] = [
|
||||
{
|
||||
id: 'yealink-t33g',
|
||||
name: 'Yealink T33G',
|
||||
description: 'Entry-level IP phone',
|
||||
oneTimePrice: 89,
|
||||
monthlyRental: 5,
|
||||
},
|
||||
{
|
||||
id: 'yealink-t54w',
|
||||
name: 'Yealink T54W',
|
||||
description: 'Mid-range color screen phone',
|
||||
oneTimePrice: 169,
|
||||
monthlyRental: 8,
|
||||
},
|
||||
{
|
||||
id: 'yealink-t58a',
|
||||
name: 'Yealink T58A',
|
||||
description: 'Executive phone with video',
|
||||
oneTimePrice: 299,
|
||||
monthlyRental: 12,
|
||||
},
|
||||
{
|
||||
id: 'headset-basic',
|
||||
name: 'USB Headset',
|
||||
description: 'Basic USB headset',
|
||||
oneTimePrice: 45,
|
||||
monthlyRental: 3,
|
||||
},
|
||||
{
|
||||
id: 'headset-wireless',
|
||||
name: 'Wireless Headset',
|
||||
description: 'Premium wireless headset',
|
||||
oneTimePrice: 149,
|
||||
monthlyRental: 7,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Web Hosting Tiers
|
||||
*/
|
||||
export const webHostingTiers: WebHostingTier[] = [
|
||||
{
|
||||
id: 'hosting-starter',
|
||||
name: 'Starter',
|
||||
description: 'Perfect for simple business sites',
|
||||
monthlyPrice: 15,
|
||||
storage: '5GB',
|
||||
sites: 1,
|
||||
features: [
|
||||
'5GB SSD storage',
|
||||
'1 website',
|
||||
'Free SSL certificate',
|
||||
'Daily backups',
|
||||
'Email support',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'hosting-business',
|
||||
name: 'Business',
|
||||
description: 'Great for multiple sites and more traffic',
|
||||
monthlyPrice: 35,
|
||||
storage: '25GB',
|
||||
sites: 5,
|
||||
features: [
|
||||
'25GB SSD storage',
|
||||
'5 websites',
|
||||
'Free SSL certificates',
|
||||
'Daily backups',
|
||||
'Staging environment',
|
||||
'Priority support',
|
||||
],
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
id: 'hosting-commerce',
|
||||
name: 'Commerce',
|
||||
description: 'E-commerce ready with unlimited sites',
|
||||
monthlyPrice: 65,
|
||||
storage: '50GB',
|
||||
sites: -1, // Unlimited
|
||||
features: [
|
||||
'50GB SSD storage',
|
||||
'Unlimited websites',
|
||||
'Free SSL certificates',
|
||||
'Real-time backups',
|
||||
'CDN included',
|
||||
'PCI compliance',
|
||||
'Dedicated support',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Email Tiers
|
||||
*/
|
||||
export const emailTiers: EmailTier[] = [
|
||||
// WHM (Self-hosted) Options
|
||||
{
|
||||
id: 'whm-basic',
|
||||
name: 'WHM Basic',
|
||||
description: 'Self-hosted email basics',
|
||||
pricePerMailbox: 2,
|
||||
provider: 'whm',
|
||||
storage: '5GB',
|
||||
features: [
|
||||
'5GB storage per mailbox',
|
||||
'Webmail access',
|
||||
'IMAP/POP3/SMTP',
|
||||
'Spam filtering',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'whm-standard',
|
||||
name: 'WHM Standard',
|
||||
description: 'Enhanced self-hosted email',
|
||||
pricePerMailbox: 4,
|
||||
provider: 'whm',
|
||||
storage: '10GB',
|
||||
features: [
|
||||
'10GB storage per mailbox',
|
||||
'Webmail access',
|
||||
'IMAP/POP3/SMTP',
|
||||
'Advanced spam filtering',
|
||||
'Email aliases',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'whm-pro',
|
||||
name: 'WHM Pro',
|
||||
description: 'Professional self-hosted email',
|
||||
pricePerMailbox: 10,
|
||||
provider: 'whm',
|
||||
storage: '25GB',
|
||||
features: [
|
||||
'25GB storage per mailbox',
|
||||
'Webmail access',
|
||||
'IMAP/POP3/SMTP',
|
||||
'Premium spam filtering',
|
||||
'Email archiving',
|
||||
'Shared calendars',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
// Microsoft 365 Options
|
||||
{
|
||||
id: 'm365-basic',
|
||||
name: 'M365 Basic',
|
||||
description: 'Microsoft 365 essentials',
|
||||
pricePerMailbox: 7,
|
||||
provider: 'm365',
|
||||
storage: '50GB',
|
||||
features: [
|
||||
'50GB mailbox',
|
||||
'Outlook web access',
|
||||
'Mobile apps',
|
||||
'OneDrive 1TB',
|
||||
'Microsoft Teams',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'm365-standard',
|
||||
name: 'M365 Standard',
|
||||
description: 'Full Microsoft 365 experience',
|
||||
pricePerMailbox: 14,
|
||||
provider: 'm365',
|
||||
storage: '50GB',
|
||||
features: [
|
||||
'50GB mailbox',
|
||||
'Desktop Office apps',
|
||||
'OneDrive 1TB',
|
||||
'Microsoft Teams',
|
||||
'SharePoint',
|
||||
'Bookings',
|
||||
],
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
id: 'm365-premium',
|
||||
name: 'M365 Premium',
|
||||
description: 'Enterprise security and compliance',
|
||||
pricePerMailbox: 24,
|
||||
provider: 'm365',
|
||||
storage: '100GB',
|
||||
features: [
|
||||
'100GB mailbox',
|
||||
'Everything in Standard',
|
||||
'Advanced security',
|
||||
'Device management',
|
||||
'Azure AD Premium',
|
||||
'Data loss prevention',
|
||||
],
|
||||
recommended: false,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Industry options for company info
|
||||
*/
|
||||
export const industries = [
|
||||
'Healthcare',
|
||||
'Legal',
|
||||
'Finance',
|
||||
'Manufacturing',
|
||||
'Retail',
|
||||
'Professional Services',
|
||||
'Other',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Contact preference options
|
||||
*/
|
||||
export const contactPreferences = [
|
||||
{ id: 'email', label: 'Email' },
|
||||
{ id: 'phone', label: 'Phone' },
|
||||
{ id: 'either', label: 'Either' },
|
||||
] as const;
|
||||
69
projects/msp-tools/quote-wizard/frontend/src/lib/utils.ts
Normal file
69
projects/msp-tools/quote-wizard/frontend/src/lib/utils.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
|
||||
/**
|
||||
* Utility function to merge class names
|
||||
* Combines clsx for conditional classes
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return clsx(inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format currency value
|
||||
*/
|
||||
export function formatCurrency(value: number): string {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format number with commas
|
||||
*/
|
||||
export function formatNumber(value: number): string {
|
||||
return new Intl.NumberFormat('en-US').format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce function
|
||||
*/
|
||||
export function debounce<T extends (...args: unknown[]) => unknown>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
return function executedFunction(...args: Parameters<T>) {
|
||||
const later = () => {
|
||||
timeout = null;
|
||||
func(...args);
|
||||
};
|
||||
|
||||
if (timeout !== null) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total device count
|
||||
*/
|
||||
export function getTotalDevices(devices: {
|
||||
workstations: number;
|
||||
laptops: number;
|
||||
servers: number;
|
||||
networkDevices: number;
|
||||
mobileDevices: number;
|
||||
}): number {
|
||||
return (
|
||||
devices.workstations +
|
||||
devices.laptops +
|
||||
devices.servers +
|
||||
devices.networkDevices +
|
||||
devices.mobileDevices
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user