392 lines
15 KiB
TypeScript
392 lines
15 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { Badge } from '@/components/ui/Badge';
|
|
import { Input } from '@/components/ui/Input';
|
|
import { Dialog } from '@/components/ui/Dialog';
|
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
|
|
interface Integration {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
icon: string;
|
|
status: 'active' | 'inactive' | 'coming_soon';
|
|
category: string;
|
|
features: string[];
|
|
}
|
|
|
|
export default function IntegrationsPage() {
|
|
const { t } = useTranslation();
|
|
const [selectedIntegration, setSelectedIntegration] = useState<Integration | null>(null);
|
|
const [showSetupDialog, setShowSetupDialog] = useState(false);
|
|
const [apiKey, setApiKey] = useState('');
|
|
const [webhookUrl, setWebhookUrl] = useState('');
|
|
|
|
const integrations: Integration[] = [
|
|
{
|
|
id: 'zapier',
|
|
name: 'Zapier',
|
|
description: 'Connect QR Master with 5,000+ apps',
|
|
icon: '⚡',
|
|
status: 'active',
|
|
category: 'Automation',
|
|
features: [
|
|
'Trigger actions when QR codes are scanned',
|
|
'Create QR codes from other apps',
|
|
'Update QR destinations automatically',
|
|
'Sync analytics to spreadsheets',
|
|
],
|
|
},
|
|
{
|
|
id: 'airtable',
|
|
name: 'Airtable',
|
|
description: 'Sync QR codes with your Airtable bases',
|
|
icon: '📊',
|
|
status: 'inactive',
|
|
category: 'Database',
|
|
features: [
|
|
'Two-way sync with Airtable',
|
|
'Bulk import from bases',
|
|
'Auto-update QR content',
|
|
'Analytics dashboard integration',
|
|
],
|
|
},
|
|
{
|
|
id: 'google-sheets',
|
|
name: 'Google Sheets',
|
|
description: 'Manage QR codes from spreadsheets',
|
|
icon: '📈',
|
|
status: 'inactive',
|
|
category: 'Spreadsheet',
|
|
features: [
|
|
'Import QR codes from sheets',
|
|
'Export analytics data',
|
|
'Real-time sync',
|
|
'Collaborative QR management',
|
|
],
|
|
},
|
|
{
|
|
id: 'slack',
|
|
name: 'Slack',
|
|
description: 'Get QR scan notifications in Slack',
|
|
icon: '💬',
|
|
status: 'coming_soon',
|
|
category: 'Communication',
|
|
features: [
|
|
'Real-time scan notifications',
|
|
'Daily analytics summaries',
|
|
'Team collaboration',
|
|
'Custom alert rules',
|
|
],
|
|
},
|
|
{
|
|
id: 'webhook',
|
|
name: 'Webhooks',
|
|
description: 'Send data to any URL',
|
|
icon: '🔗',
|
|
status: 'active',
|
|
category: 'Developer',
|
|
features: [
|
|
'Custom webhook endpoints',
|
|
'Real-time event streaming',
|
|
'Retry logic',
|
|
'Event filtering',
|
|
],
|
|
},
|
|
{
|
|
id: 'api',
|
|
name: 'REST API',
|
|
description: 'Full programmatic access',
|
|
icon: '🔧',
|
|
status: 'active',
|
|
category: 'Developer',
|
|
features: [
|
|
'Complete CRUD operations',
|
|
'Bulk operations',
|
|
'Analytics API',
|
|
'Rate limiting: 1000 req/hour',
|
|
],
|
|
},
|
|
];
|
|
|
|
const stats = {
|
|
totalQRCodes: 234,
|
|
activeIntegrations: 2,
|
|
syncStatus: 'Synced',
|
|
availableServices: 6,
|
|
};
|
|
|
|
const handleActivate = (integration: Integration) => {
|
|
setSelectedIntegration(integration);
|
|
setShowSetupDialog(true);
|
|
};
|
|
|
|
const handleTestConnection = async () => {
|
|
// Simulate API test
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
alert('Connection successful!');
|
|
};
|
|
|
|
const handleSaveIntegration = () => {
|
|
setShowSetupDialog(false);
|
|
// Update integration status
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* Header */}
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900">{t('integrations.title')}</h1>
|
|
<p className="text-gray-600 mt-2">{t('integrations.subtitle')}</p>
|
|
</div>
|
|
|
|
{/* Stats */}
|
|
<div className="grid md:grid-cols-4 gap-6">
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600 mb-1">QR Codes Total</p>
|
|
<p className="text-2xl font-bold text-gray-900">{stats.totalQRCodes}</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-primary-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600 mb-1">Active Integrations</p>
|
|
<p className="text-2xl font-bold text-gray-900">{stats.activeIntegrations}</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-success-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-success-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600 mb-1">Sync Status</p>
|
|
<p className="text-2xl font-bold text-gray-900">{stats.syncStatus}</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-info-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-info-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600 mb-1">Available Services</p>
|
|
<p className="text-2xl font-bold text-gray-900">{stats.availableServices}</p>
|
|
</div>
|
|
<div className="w-12 h-12 bg-warning-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-warning-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Integration Cards */}
|
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{integrations.map((integration) => (
|
|
<Card key={integration.id} className="hover:shadow-lg transition-shadow">
|
|
<CardHeader>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="text-3xl">{integration.icon}</div>
|
|
<div>
|
|
<CardTitle className="text-lg">{integration.name}</CardTitle>
|
|
<Badge
|
|
variant={
|
|
integration.status === 'active' ? 'success' :
|
|
integration.status === 'coming_soon' ? 'warning' :
|
|
'default'
|
|
}
|
|
className="mt-1"
|
|
>
|
|
{integration.status === 'active' ? 'Active' :
|
|
integration.status === 'coming_soon' ? 'Coming Soon' :
|
|
'Inactive'}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-gray-600 mb-4">{integration.description}</p>
|
|
|
|
<div className="space-y-2 mb-4">
|
|
{integration.features.slice(0, 3).map((feature, index) => (
|
|
<div key={index} className="flex items-start space-x-2">
|
|
<svg className="w-4 h-4 text-success-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
<span className="text-sm text-gray-700">{feature}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{integration.status === 'active' ? (
|
|
<Button variant="outline" className="w-full">
|
|
Configure
|
|
</Button>
|
|
) : integration.status === 'coming_soon' ? (
|
|
<Button variant="outline" className="w-full" disabled>
|
|
Coming Soon
|
|
</Button>
|
|
) : (
|
|
<Button className="w-full" onClick={() => handleActivate(integration)}>
|
|
Activate & Configure
|
|
</Button>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
{/* Setup Dialog */}
|
|
{showSetupDialog && selectedIntegration && (
|
|
<Dialog
|
|
open={showSetupDialog}
|
|
onOpenChange={setShowSetupDialog}
|
|
>
|
|
<div className="bg-white rounded-xl shadow-lg border border-gray-200 p-6 max-w-lg mx-auto">
|
|
<h2 className="text-lg font-semibold mb-4">Setup {selectedIntegration.name}</h2>
|
|
<div className="space-y-4">
|
|
{selectedIntegration.id === 'zapier' && (
|
|
<>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Webhook URL
|
|
</label>
|
|
<Input
|
|
value="https://hooks.zapier.com/hooks/catch/123456/abcdef/"
|
|
readOnly
|
|
className="font-mono text-sm"
|
|
/>
|
|
<p className="text-sm text-gray-500 mt-1">
|
|
Copy this URL to your Zapier trigger
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Events to Send
|
|
</label>
|
|
<div className="space-y-2">
|
|
<label className="flex items-center">
|
|
<input type="checkbox" className="mr-2" defaultChecked />
|
|
<span className="text-sm">QR Code Scanned</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input type="checkbox" className="mr-2" defaultChecked />
|
|
<span className="text-sm">QR Code Created</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input type="checkbox" className="mr-2" />
|
|
<span className="text-sm">QR Code Updated</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-4 bg-gray-50 rounded-lg">
|
|
<h4 className="font-medium text-gray-900 mb-2">Sample Payload</h4>
|
|
<pre className="text-xs text-gray-600 overflow-x-auto">
|
|
{`{
|
|
"event": "qr_scanned",
|
|
"qr_id": "abc123",
|
|
"title": "Product Page",
|
|
"timestamp": "2025-01-01T12:00:00Z",
|
|
"location": "United States",
|
|
"device": "mobile"
|
|
}`}
|
|
</pre>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{selectedIntegration.id === 'airtable' && (
|
|
<>
|
|
<Input
|
|
label="API Key"
|
|
type="password"
|
|
value={apiKey}
|
|
onChange={(e) => setApiKey(e.target.value)}
|
|
placeholder="key..."
|
|
/>
|
|
<Input
|
|
label="Base ID"
|
|
value=""
|
|
placeholder="app..."
|
|
/>
|
|
<Input
|
|
label="Table Name"
|
|
value=""
|
|
placeholder="QR Codes"
|
|
/>
|
|
<Button variant="outline" onClick={handleTestConnection}>
|
|
Test Connection
|
|
</Button>
|
|
</>
|
|
)}
|
|
|
|
{selectedIntegration.id === 'google-sheets' && (
|
|
<>
|
|
<div className="text-center p-6">
|
|
<Button>
|
|
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
|
|
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
|
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
|
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
|
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
|
</svg>
|
|
Connect Google Account
|
|
</Button>
|
|
</div>
|
|
<Input
|
|
label="Spreadsheet URL"
|
|
value=""
|
|
placeholder="https://docs.google.com/spreadsheets/..."
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
<div className="flex justify-end space-x-3 pt-4">
|
|
<Button variant="outline" onClick={() => setShowSetupDialog(false)}>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={handleSaveIntegration}>
|
|
Save Integration
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
)}
|
|
</div>
|
|
);
|
|
} |