2026-02-12 17:30:47 -05:00

373 lines
8.1 KiB
JavaScript

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const apps = [
{
name: 'invoice-dashboard',
title: 'Invoice Dashboard',
desc: 'Manage and track all your invoices',
tool: 'freshbooks_list_invoices',
color: '#10b981'
},
{
name: 'invoice-detail',
title: 'Invoice Detail',
desc: 'View detailed invoice information',
tool: 'freshbooks_get_invoice',
color: '#3b82f6'
},
{
name: 'client-dashboard',
title: 'Client Directory',
desc: 'Manage all your clients',
tool: 'freshbooks_list_clients',
color: '#8b5cf6'
},
{
name: 'client-detail',
title: 'Client Detail',
desc: 'View detailed client information',
tool: 'freshbooks_get_client',
color: '#8b5cf6'
},
{
name: 'expense-tracker',
title: 'Expense Tracker',
desc: 'Track and categorize all expenses',
tool: 'freshbooks_list_expenses',
color: '#ef4444'
},
{
name: 'expense-categories',
title: 'Expense Categories',
desc: 'Manage expense categories',
tool: 'freshbooks_list_expense_categories',
color: '#f59e0b'
},
{
name: 'time-entries',
title: 'Time Entry Log',
desc: 'Log and track time entries',
tool: 'freshbooks_list_time_entries',
color: '#06b6d4'
},
{
name: 'time-tracker',
title: 'Project Timer',
desc: 'Start and stop project timers',
tool: 'freshbooks_list_time_entries',
color: '#06b6d4'
},
{
name: 'project-dashboard',
title: 'Project Overview',
desc: 'View all active projects',
tool: 'freshbooks_list_projects',
color: '#10b981'
},
{
name: 'project-detail',
title: 'Project Detail',
desc: 'Detailed project information',
tool: 'freshbooks_get_project',
color: '#10b981'
},
{
name: 'payment-history',
title: 'Payment History',
desc: 'Track all payments',
tool: 'freshbooks_list_payments',
color: '#10b981'
},
{
name: 'estimate-builder',
title: 'Estimate Builder',
desc: 'Create and send estimates',
tool: 'freshbooks_list_estimates',
color: '#6366f1'
},
{
name: 'tax-summary',
title: 'Tax Summary',
desc: 'View tax reporting and summaries',
tool: 'freshbooks_tax_summary_report',
color: '#f59e0b'
},
{
name: 'recurring-invoices',
title: 'Recurring Templates',
desc: 'Manage recurring invoice templates',
tool: 'freshbooks_list_invoices',
color: '#8b5cf6'
},
{
name: 'profit-loss',
title: 'Profit & Loss',
desc: 'Financial P&L statements',
tool: 'freshbooks_profit_loss_report',
color: '#10b981'
},
{
name: 'revenue-chart',
title: 'Revenue Chart',
desc: 'Visual revenue analytics',
tool: 'freshbooks_list_invoices',
color: '#10b981'
},
{
name: 'aging-report',
title: 'Outstanding Balance',
desc: 'Accounts receivable aging',
tool: 'freshbooks_aging_report',
color: '#ef4444'
},
{
name: 'bill-manager',
title: 'Bill Manager',
desc: 'Manage vendor bills',
tool: 'freshbooks_list_bills',
color: '#f59e0b'
},
{
name: 'credit-note-viewer',
title: 'Credit Note Viewer',
desc: 'View and manage credit notes',
tool: 'freshbooks_list_credit_notes',
color: '#06b6d4'
},
{
name: 'reports-dashboard',
title: 'Report Builder',
desc: 'Build custom financial reports',
tool: 'freshbooks_profit_loss_report',
color: '#8b5cf6'
},
{
name: 'invoice-builder',
title: 'Invoice Builder',
desc: 'Create new invoices',
tool: 'freshbooks_create_invoice',
color: '#10b981'
},
];
const createApp = (app) => {
const appDir = path.join(__dirname, app.name);
if (!fs.existsSync(appDir)) {
fs.mkdirSync(appDir, { recursive: true });
}
// App.tsx
const appTsx = `import { useState, useEffect } from 'react';
import './styles.css';
export default function ${app.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('')}() {
const [data, setData] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
setLoading(true);
// Call MCP tool
const response = await (window as any).mcp?.callTool('${app.tool}', {});
if (response) {
setData(response);
} else {
setData(getSampleData());
}
} catch (err) {
console.error('Error loading data:', err);
setError(err instanceof Error ? err.message : 'Failed to load data');
setData(getSampleData());
} finally {
setLoading(false);
}
};
const getSampleData = () => {
return { message: 'Sample data - MCP tools not available', items: [] };
};
if (loading) {
return (
<div className="container">
<div className="loading">Loading ${app.title.toLowerCase()}...</div>
</div>
);
}
return (
<div className="container">
<div className="header">
<h1>${app.title}</h1>
<p className="subtitle">${app.desc}</p>
</div>
<div className="content">
{error && <div className="error">{error}</div>}
<div className="card">
<h2>Data</h2>
<pre className="data-preview">{JSON.stringify(data, null, 2)}</pre>
</div>
</div>
</div>
);
}
`;
// styles.css
const stylesCss = `* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #111827;
color: #e2e8f0;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.header {
margin-bottom: 2rem;
}
.header h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
color: ${app.color};
}
.subtitle {
color: #94a3b8;
}
.loading {
text-align: center;
padding: 4rem;
font-size: 1.25rem;
color: #94a3b8;
}
.error {
background: #7f1d1d;
color: #fca5a5;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.content {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.card {
background: #1f2937;
border-radius: 8px;
padding: 1.5rem;
border-left: 4px solid ${app.color};
}
.card h2 {
color: #e2e8f0;
margin-bottom: 1rem;
font-size: 1.25rem;
}
.data-preview {
background: #111827;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
font-size: 0.875rem;
color: #94a3b8;
max-height: 600px;
overflow-y: auto;
}
.btn {
background: ${app.color};
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
}
.btn:hover {
opacity: 0.9;
}
`;
// main.tsx
const mainTsx = `import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
`;
// index.html
const indexHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${app.title} - FreshBooks MCP</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/main.tsx"></script>
</body>
</html>
`;
// vite.config.ts
const viteConfig = `import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
emptyOutDir: true,
},
});
`;
// Write files
fs.writeFileSync(path.join(appDir, 'App.tsx'), appTsx);
fs.writeFileSync(path.join(appDir, 'styles.css'), stylesCss);
fs.writeFileSync(path.join(appDir, 'main.tsx'), mainTsx);
fs.writeFileSync(path.join(appDir, 'index.html'), indexHtml);
fs.writeFileSync(path.join(appDir, 'vite.config.ts'), viteConfig);
console.log(`✅ Created ${app.name}`);
};
console.log('🚀 Generating FreshBooks MCP Apps...\n');
apps.forEach(createApp);
console.log(`\n✨ Generated ${apps.length} apps successfully!`);