Move email provider settings to Integrasi tab and fix database schema
- Move Mailketing API configuration from NotifikasiTab to IntegrasiTab - Remove email provider settings from NotifikasiTab, add info card - Update IntegrasiTab to save email settings to notification_settings table - Add test email functionality to Integrasi tab - Fix database schema compatibility with new email settings - Remove GitHub remote, keep only Gitea remote - Clean up unused imports and variables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
40
deploy-edge-functions.sh
Executable file
40
deploy-edge-functions.sh
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SUPABASE_URL="https://lovable.backoffice.biz.id"
|
||||||
|
SERVICE_ROLE_KEY="$SUPABASE_ACCESS_TOKEN"
|
||||||
|
|
||||||
|
# Function to deploy edge function
|
||||||
|
deploy_function() {
|
||||||
|
local func_name=$1
|
||||||
|
echo "Deploying $func_name..."
|
||||||
|
|
||||||
|
# Read the function content
|
||||||
|
if [ -f "supabase/functions/$func_name/index.ts" ]; then
|
||||||
|
FUNCTION_CONTENT=$(cat "supabase/functions/$func_name/index.ts")
|
||||||
|
|
||||||
|
# Create the function via API
|
||||||
|
curl -X POST "$SUPABASE_URL/rest/v1/functions" \
|
||||||
|
-H "Authorization: Bearer $SERVICE_ROLE_KEY" \
|
||||||
|
-H "apikey: $SERVICE_ROLE_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"name\": \"$func_name\",
|
||||||
|
\"verify_jwt\": $(cat supabase/config.toml | grep -A 1 "\\[functions.$func_name\\]" | grep verify_jwt | awk '{print $3}')
|
||||||
|
}"
|
||||||
|
|
||||||
|
echo "Function $func_name created/updated"
|
||||||
|
else
|
||||||
|
echo "Function $func_name not found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy all functions
|
||||||
|
deploy_function "pakasir-webhook"
|
||||||
|
deploy_function "send-test-email"
|
||||||
|
deploy_function "create-meet-link"
|
||||||
|
deploy_function "send-consultation-reminder"
|
||||||
|
deploy_function "send-notification"
|
||||||
|
deploy_function "daily-reminders"
|
||||||
|
|
||||||
|
echo "Deployment complete!"
|
||||||
@@ -5,8 +5,9 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { Puzzle, Webhook, MessageSquare, Calendar, Mail, Link as LinkIcon } from 'lucide-react';
|
import { Puzzle, Webhook, MessageSquare, Calendar, Mail, Link as LinkIcon, Key, Send, AlertTriangle } from 'lucide-react';
|
||||||
|
|
||||||
interface IntegrationSettings {
|
interface IntegrationSettings {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -18,6 +19,11 @@ interface IntegrationSettings {
|
|||||||
integration_email_api_base_url: string;
|
integration_email_api_base_url: string;
|
||||||
integration_privacy_url: string;
|
integration_privacy_url: string;
|
||||||
integration_terms_url: string;
|
integration_terms_url: string;
|
||||||
|
// Mailketing specific settings
|
||||||
|
provider: 'mailketing' | 'smtp';
|
||||||
|
api_token: string;
|
||||||
|
from_name: string;
|
||||||
|
from_email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptySettings: IntegrationSettings = {
|
const emptySettings: IntegrationSettings = {
|
||||||
@@ -25,38 +31,55 @@ const emptySettings: IntegrationSettings = {
|
|||||||
integration_whatsapp_number: '',
|
integration_whatsapp_number: '',
|
||||||
integration_whatsapp_url: '',
|
integration_whatsapp_url: '',
|
||||||
integration_google_calendar_id: '',
|
integration_google_calendar_id: '',
|
||||||
integration_email_provider: 'smtp',
|
integration_email_provider: 'mailketing',
|
||||||
integration_email_api_base_url: '',
|
integration_email_api_base_url: '',
|
||||||
integration_privacy_url: '/privacy',
|
integration_privacy_url: '/privacy',
|
||||||
integration_terms_url: '/terms',
|
integration_terms_url: '/terms',
|
||||||
|
provider: 'mailketing',
|
||||||
|
api_token: '',
|
||||||
|
from_name: '',
|
||||||
|
from_email: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function IntegrasiTab() {
|
export function IntegrasiTab() {
|
||||||
const [settings, setSettings] = useState<IntegrationSettings>(emptySettings);
|
const [settings, setSettings] = useState<IntegrationSettings>(emptySettings);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [testEmail, setTestEmail] = useState('');
|
||||||
|
const [sendingTest, setSendingTest] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSettings();
|
fetchSettings();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchSettings = async () => {
|
const fetchSettings = async () => {
|
||||||
const { data, error } = await supabase
|
const { data: platformData } = await supabase
|
||||||
.from('platform_settings')
|
.from('platform_settings')
|
||||||
.select('*')
|
.select('*')
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (data) {
|
// Fetch email provider settings from notification_settings
|
||||||
|
const { data: emailData } = await supabase
|
||||||
|
.from('notification_settings')
|
||||||
|
.select('*')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (platformData) {
|
||||||
setSettings({
|
setSettings({
|
||||||
id: data.id,
|
id: platformData.id,
|
||||||
integration_n8n_base_url: data.integration_n8n_base_url || '',
|
integration_n8n_base_url: platformData.integration_n8n_base_url || '',
|
||||||
integration_whatsapp_number: data.integration_whatsapp_number || '',
|
integration_whatsapp_number: platformData.integration_whatsapp_number || '',
|
||||||
integration_whatsapp_url: data.integration_whatsapp_url || '',
|
integration_whatsapp_url: platformData.integration_whatsapp_url || '',
|
||||||
integration_google_calendar_id: data.integration_google_calendar_id || '',
|
integration_google_calendar_id: platformData.integration_google_calendar_id || '',
|
||||||
integration_email_provider: data.integration_email_provider || 'smtp',
|
integration_email_provider: platformData.integration_email_provider || 'mailketing',
|
||||||
integration_email_api_base_url: data.integration_email_api_base_url || '',
|
integration_email_api_base_url: platformData.integration_email_api_base_url || '',
|
||||||
integration_privacy_url: data.integration_privacy_url || '/privacy',
|
integration_privacy_url: platformData.integration_privacy_url || '/privacy',
|
||||||
integration_terms_url: data.integration_terms_url || '/terms',
|
integration_terms_url: platformData.integration_terms_url || '/terms',
|
||||||
|
// Email settings from notification_settings
|
||||||
|
provider: emailData?.provider || 'mailketing',
|
||||||
|
api_token: emailData?.api_token || '',
|
||||||
|
from_name: emailData?.from_name || platformData.brand_email_from_name || '',
|
||||||
|
from_email: emailData?.from_email || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -64,33 +87,106 @@ export function IntegrasiTab() {
|
|||||||
|
|
||||||
const saveSettings = async () => {
|
const saveSettings = async () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
const payload = { ...settings };
|
|
||||||
delete payload.id;
|
|
||||||
|
|
||||||
if (settings.id) {
|
try {
|
||||||
const { error } = await supabase
|
// Save platform settings
|
||||||
.from('platform_settings')
|
const platformPayload = {
|
||||||
.update(payload)
|
integration_n8n_base_url: settings.integration_n8n_base_url,
|
||||||
.eq('id', settings.id);
|
integration_whatsapp_number: settings.integration_whatsapp_number,
|
||||||
|
integration_whatsapp_url: settings.integration_whatsapp_url,
|
||||||
if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
integration_google_calendar_id: settings.integration_google_calendar_id,
|
||||||
else toast({ title: 'Berhasil', description: 'Pengaturan integrasi disimpan' });
|
integration_email_provider: settings.integration_email_provider,
|
||||||
} else {
|
integration_email_api_base_url: settings.integration_email_api_base_url,
|
||||||
const { data, error } = await supabase
|
integration_privacy_url: settings.integration_privacy_url,
|
||||||
.from('platform_settings')
|
integration_terms_url: settings.integration_terms_url,
|
||||||
.insert(payload)
|
};
|
||||||
.select()
|
|
||||||
.single();
|
if (settings.id) {
|
||||||
|
const { error: platformError } = await supabase
|
||||||
if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
.from('platform_settings')
|
||||||
else {
|
.update(platformPayload)
|
||||||
setSettings({ ...settings, id: data.id });
|
.eq('id', settings.id);
|
||||||
toast({ title: 'Berhasil', description: 'Pengaturan integrasi disimpan' });
|
|
||||||
|
if (platformError) throw platformError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save email provider settings to notification_settings
|
||||||
|
const emailPayload = {
|
||||||
|
provider: settings.provider,
|
||||||
|
api_token: settings.api_token,
|
||||||
|
from_name: settings.from_name,
|
||||||
|
from_email: settings.from_email,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: existingEmailSettings } = await supabase
|
||||||
|
.from('notification_settings')
|
||||||
|
.select('id')
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (existingEmailSettings?.id) {
|
||||||
|
const { error: emailError } = await supabase
|
||||||
|
.from('notification_settings')
|
||||||
|
.update(emailPayload)
|
||||||
|
.eq('id', existingEmailSettings.id);
|
||||||
|
|
||||||
|
if (emailError) throw emailError;
|
||||||
|
} else {
|
||||||
|
const { error: emailError } = await supabase
|
||||||
|
.from('notification_settings')
|
||||||
|
.insert(emailPayload);
|
||||||
|
|
||||||
|
if (emailError) throw emailError;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({ title: 'Berhasil', description: 'Pengaturan integrasi disimpan' });
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
||||||
}
|
}
|
||||||
|
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendTestEmail = async () => {
|
||||||
|
if (!testEmail) return toast({ title: 'Error', description: 'Masukkan email tujuan', variant: 'destructive' });
|
||||||
|
if (!isEmailConfigured) return toast({ title: 'Error', description: 'Lengkapi konfigurasi email provider terlebih dahulu', variant: 'destructive' });
|
||||||
|
|
||||||
|
setSendingTest(true);
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase.functions.invoke('send-email-v2', {
|
||||||
|
body: {
|
||||||
|
to: testEmail,
|
||||||
|
api_token: settings.api_token,
|
||||||
|
from_name: settings.from_name,
|
||||||
|
from_email: settings.from_email,
|
||||||
|
subject: 'Test Email dari Access Hub',
|
||||||
|
html_body: `
|
||||||
|
<h2>Test Email</h2>
|
||||||
|
<p>Ini adalah email uji coba dari aplikasi Access Hub Anda.</p>
|
||||||
|
<p>Jika Anda menerima email ini, konfigurasi Mailketing API sudah berfungsi dengan baik!</p>
|
||||||
|
<p>Kirim ke: ${testEmail}</p>
|
||||||
|
<br>
|
||||||
|
<p>Best regards,<br>Access Hub Team</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
if (data?.success) {
|
||||||
|
toast({ title: 'Berhasil', description: data.message });
|
||||||
|
} else {
|
||||||
|
throw new Error(data?.message || 'Failed to send test email');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Test email error:', error);
|
||||||
|
toast({ title: 'Error', description: error.message || 'Gagal mengirim email uji coba', variant: 'destructive' });
|
||||||
|
} finally {
|
||||||
|
setSendingTest(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEmailConfigured = settings.api_token && settings.from_email;
|
||||||
|
|
||||||
if (loading) return <div className="animate-pulse h-64 bg-muted rounded-md" />;
|
if (loading) return <div className="animate-pulse h-64 bg-muted rounded-md" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -193,43 +289,113 @@ export function IntegrasiTab() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Mail className="w-5 h-5" />
|
<Mail className="w-5 h-5" />
|
||||||
Provider Email (Opsional)
|
Provider Email
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Konfigurasi alternatif selain SMTP
|
Konfigurasi provider email untuk pengiriman notifikasi
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
{!isEmailConfigured && (
|
||||||
|
<Alert variant="destructive" className="border-2">
|
||||||
|
<AlertTriangle className="w-4 h-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
Konfigurasi email provider belum lengkap. Email tidak akan terkirim.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Provider Email Eksternal</Label>
|
<Label>Provider Email</Label>
|
||||||
<Select
|
<Select
|
||||||
value={settings.integration_email_provider}
|
value={settings.provider}
|
||||||
onValueChange={(value) => setSettings({ ...settings, integration_email_provider: value })}
|
onValueChange={(value: 'mailketing' | 'smtp') => setSettings({ ...settings, provider: value })}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="border-2">
|
<SelectTrigger className="border-2">
|
||||||
<SelectValue />
|
<SelectValue placeholder="Pilih provider email" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="smtp">SMTP (Default)</SelectItem>
|
<SelectItem value="mailketing">Mailketing</SelectItem>
|
||||||
<SelectItem value="resend">Resend</SelectItem>
|
<SelectItem value="smtp">SMTP (Legacy)</SelectItem>
|
||||||
<SelectItem value="elasticemail">ElasticEmail</SelectItem>
|
|
||||||
<SelectItem value="mailgun">Mailgun</SelectItem>
|
|
||||||
<SelectItem value="sendgrid">SendGrid</SelectItem>
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
{settings.provider === 'mailketing' && (
|
||||||
<Label>API Base URL Provider Email</Label>
|
<>
|
||||||
<Input
|
<div className="space-y-2">
|
||||||
value={settings.integration_email_api_base_url}
|
<Label className="flex items-center gap-2">
|
||||||
onChange={(e) => setSettings({ ...settings, integration_email_api_base_url: e.target.value })}
|
<Key className="w-4 h-4" />
|
||||||
placeholder="https://api.resend.com"
|
API Token
|
||||||
className="border-2"
|
</Label>
|
||||||
disabled={settings.integration_email_provider === 'smtp'}
|
<Input
|
||||||
/>
|
type="password"
|
||||||
</div>
|
value={settings.api_token}
|
||||||
|
onChange={(e) => setSettings({ ...settings, api_token: e.target.value })}
|
||||||
|
placeholder="Masukkan API token dari Mailketing"
|
||||||
|
className="border-2"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Dapatkan API token dari menu Integration di dashboard Mailketing
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Nama Pengirim</Label>
|
||||||
|
<Input
|
||||||
|
value={settings.from_name}
|
||||||
|
onChange={(e) => setSettings({ ...settings, from_name: e.target.value })}
|
||||||
|
placeholder="Nama Bisnis"
|
||||||
|
className="border-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Email Pengirim</Label>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
value={settings.from_email}
|
||||||
|
onChange={(e) => setSettings({ ...settings, from_email: e.target.value })}
|
||||||
|
placeholder="info@domain.com"
|
||||||
|
className="border-2"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Pastikan email sudah terdaftar di Mailketing
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 pt-4 border-t">
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
value={testEmail}
|
||||||
|
onChange={(e) => setTestEmail(e.target.value)}
|
||||||
|
placeholder="Email uji coba"
|
||||||
|
className="border-2 max-w-xs"
|
||||||
|
/>
|
||||||
|
<Button variant="outline" onClick={sendTestEmail} className="border-2" disabled={sendingTest}>
|
||||||
|
<Send className="w-4 h-4 mr-2" />
|
||||||
|
{sendingTest ? 'Mengirim...' : 'Kirim Email Uji Coba'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{settings.provider === 'smtp' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>API Base URL Provider Email</Label>
|
||||||
|
<Input
|
||||||
|
value={settings.integration_email_api_base_url}
|
||||||
|
onChange={(e) => setSettings({ ...settings, integration_email_api_base_url: e.target.value })}
|
||||||
|
placeholder="https://api.resend.com"
|
||||||
|
className="border-2"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Konfigurasi SMTP masih di bagian Notifikasi
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -2,24 +2,14 @@ import { useEffect, useState } from 'react';
|
|||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { RichTextEditor } from '@/components/RichTextEditor';
|
import { RichTextEditor } from '@/components/RichTextEditor';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { Mail, AlertTriangle, Send, ChevronDown, ChevronUp, Webhook, Key } from 'lucide-react';
|
import { Mail, ChevronDown, ChevronUp, Webhook } from 'lucide-react';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
||||||
|
|
||||||
interface EmailProviderSettings {
|
|
||||||
id?: string;
|
|
||||||
provider: 'mailketing';
|
|
||||||
api_token: string;
|
|
||||||
from_name: string;
|
|
||||||
from_email: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NotificationTemplate {
|
interface NotificationTemplate {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -84,31 +74,16 @@ const DEFAULT_TEMPLATES: { key: string; name: string; defaultSubject: string; de
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const emptyEmailSettings: EmailProviderSettings = {
|
|
||||||
provider: 'mailketing',
|
|
||||||
api_token: '',
|
|
||||||
from_name: '',
|
|
||||||
from_email: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function NotifikasiTab() {
|
export function NotifikasiTab() {
|
||||||
const [emailSettings, setEmailSettings] = useState<EmailProviderSettings>(emptyEmailSettings);
|
|
||||||
const [templates, setTemplates] = useState<NotificationTemplate[]>([]);
|
const [templates, setTemplates] = useState<NotificationTemplate[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
|
||||||
const [testEmail, setTestEmail] = useState('');
|
|
||||||
const [expandedTemplates, setExpandedTemplates] = useState<Set<string>>(new Set());
|
const [expandedTemplates, setExpandedTemplates] = useState<Set<string>>(new Set());
|
||||||
const [sendingTest, setSendingTest] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
// Fetch email provider settings
|
|
||||||
const { data: emailData } = await supabase.from('notification_settings').select('*').single();
|
|
||||||
if (emailData) setEmailSettings(emailData);
|
|
||||||
|
|
||||||
// Fetch templates
|
// Fetch templates
|
||||||
const { data: templatesData } = await supabase.from('notification_templates').select('*').order('key');
|
const { data: templatesData } = await supabase.from('notification_templates').select('*').order('key');
|
||||||
if (templatesData && templatesData.length > 0) {
|
if (templatesData && templatesData.length > 0) {
|
||||||
@@ -133,62 +108,7 @@ export function NotifikasiTab() {
|
|||||||
if (!error && data) setTemplates(data);
|
if (!error && data) setTemplates(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveEmailSettings = async () => {
|
|
||||||
setSaving(true);
|
|
||||||
const payload = { ...emailSettings };
|
|
||||||
delete payload.id;
|
|
||||||
|
|
||||||
if (emailSettings.id) {
|
|
||||||
const { error } = await supabase.from('notification_settings').update(payload).eq('id', emailSettings.id);
|
|
||||||
if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
|
||||||
else toast({ title: 'Berhasil', description: 'Pengaturan email disimpan' });
|
|
||||||
} else {
|
|
||||||
const { data, error } = await supabase.from('notification_settings').insert(payload).select().single();
|
|
||||||
if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
|
||||||
else { setEmailSettings(data); toast({ title: 'Berhasil', description: 'Pengaturan email disimpan' }); }
|
|
||||||
}
|
|
||||||
setSaving(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendTestEmail = async () => {
|
|
||||||
if (!testEmail) return toast({ title: 'Error', description: 'Masukkan email tujuan', variant: 'destructive' });
|
|
||||||
if (!isEmailConfigured) return toast({ title: 'Error', description: 'Lengkapi konfigurasi email provider terlebih dahulu', variant: 'destructive' });
|
|
||||||
|
|
||||||
setSendingTest(true);
|
|
||||||
try {
|
|
||||||
const { data, error } = await supabase.functions.invoke('send-email-v2', {
|
|
||||||
body: {
|
|
||||||
to: testEmail,
|
|
||||||
api_token: emailSettings.api_token,
|
|
||||||
from_name: emailSettings.from_name,
|
|
||||||
from_email: emailSettings.from_email,
|
|
||||||
subject: 'Test Email dari Access Hub',
|
|
||||||
html_body: `
|
|
||||||
<h2>Test Email</h2>
|
|
||||||
<p>Ini adalah email uji coba dari aplikasi Access Hub Anda.</p>
|
|
||||||
<p>Jika Anda menerima email ini, konfigurasi Mailketing API sudah berfungsi dengan baik!</p>
|
|
||||||
<p>Kirim ke: ${testEmail}</p>
|
|
||||||
<br>
|
|
||||||
<p>Best regards,<br>Access Hub Team</p>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) throw error;
|
|
||||||
|
|
||||||
if (data?.success) {
|
|
||||||
toast({ title: 'Berhasil', description: data.message });
|
|
||||||
} else {
|
|
||||||
throw new Error(data?.message || 'Failed to send test email');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Test email error:', error);
|
|
||||||
toast({ title: 'Error', description: error.message || 'Gagal mengirim email uji coba', variant: 'destructive' });
|
|
||||||
} finally {
|
|
||||||
setSendingTest(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateTemplate = async (template: NotificationTemplate) => {
|
const updateTemplate = async (template: NotificationTemplate) => {
|
||||||
const { id, key, name, ...updates } = template;
|
const { id, key, name, ...updates } = template;
|
||||||
const { error } = await supabase.from('notification_templates').update(updates).eq('id', id);
|
const { error } = await supabase.from('notification_templates').update(updates).eq('id', id);
|
||||||
@@ -205,108 +125,29 @@ export function NotifikasiTab() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isEmailConfigured = emailSettings.api_token && emailSettings.from_email;
|
|
||||||
|
|
||||||
if (loading) return <div className="animate-pulse h-64 bg-muted rounded-md" />;
|
if (loading) return <div className="animate-pulse h-64 bg-muted rounded-md" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Email Provider Settings */}
|
{/* Notification Templates Info */}
|
||||||
<Card className="border-2 border-border">
|
<Card className="border-2 border-border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Mail className="w-5 h-5" />
|
<Mail className="w-5 h-5" />
|
||||||
Pengaturan Email Provider
|
Konfigurasi Email
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>Konfigurasi provider email untuk pengiriman notifikasi</CardDescription>
|
<CardDescription>
|
||||||
|
Pengaturan provider email (Mailketing API) ada di tab <strong>Integrasi</strong>
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent>
|
||||||
{!isEmailConfigured && (
|
<Alert>
|
||||||
<Alert variant="destructive" className="border-2">
|
<Mail className="w-4 h-4" />
|
||||||
<AlertTriangle className="w-4 h-4" />
|
<AlertDescription>
|
||||||
<AlertDescription>
|
Konfigurasikan provider email di tab <strong>Integrasi → Provider Email</strong> untuk mengirim notifikasi.
|
||||||
Konfigurasi email provider belum lengkap. Email tidak akan terkirim.
|
Gunakan Mailketing API untuk pengiriman email yang andal.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Provider Email</Label>
|
|
||||||
<Select
|
|
||||||
value={emailSettings.provider}
|
|
||||||
onValueChange={(value: 'mailketing') => setEmailSettings({ ...emailSettings, provider: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="border-2">
|
|
||||||
<SelectValue placeholder="Pilih provider email" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="mailketing">Mailketing</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="flex items-center gap-2">
|
|
||||||
<Key className="w-4 h-4" />
|
|
||||||
API Token
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
type="password"
|
|
||||||
value={emailSettings.api_token}
|
|
||||||
onChange={(e) => setEmailSettings({ ...emailSettings, api_token: e.target.value })}
|
|
||||||
placeholder="Masukkan API token dari Mailketing"
|
|
||||||
className="border-2"
|
|
||||||
/>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Dapatkan API token dari menu Integration di dashboard Mailketing
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Nama Pengirim</Label>
|
|
||||||
<Input
|
|
||||||
value={emailSettings.from_name}
|
|
||||||
onChange={(e) => setEmailSettings({ ...emailSettings, from_name: e.target.value })}
|
|
||||||
placeholder="Nama Bisnis"
|
|
||||||
className="border-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Email Pengirim</Label>
|
|
||||||
<Input
|
|
||||||
type="email"
|
|
||||||
value={emailSettings.from_email}
|
|
||||||
onChange={(e) => setEmailSettings({ ...emailSettings, from_email: e.target.value })}
|
|
||||||
placeholder="info@domain.com"
|
|
||||||
className="border-2"
|
|
||||||
/>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Pastikan email sudah terdaftar di Mailketing
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-4 pt-4 border-t">
|
|
||||||
<Button onClick={saveEmailSettings} disabled={saving}>
|
|
||||||
{saving ? 'Menyimpan...' : 'Simpan'}
|
|
||||||
</Button>
|
|
||||||
<div className="flex gap-2 flex-1">
|
|
||||||
<Input
|
|
||||||
type="email"
|
|
||||||
value={testEmail}
|
|
||||||
onChange={(e) => setTestEmail(e.target.value)}
|
|
||||||
placeholder="Email uji coba"
|
|
||||||
className="border-2 max-w-xs"
|
|
||||||
/>
|
|
||||||
<Button variant="outline" onClick={sendTestEmail} className="border-2" disabled={sendingTest}>
|
|
||||||
<Send className="w-4 h-4 mr-2" />
|
|
||||||
{sendingTest ? 'Mengirim...' : 'Kirim Email Uji Coba'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -336,10 +177,10 @@ export function NotifikasiTab() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs mt-2 p-2 bg-background rounded border">
|
<p className="text-xs mt-2 p-2 bg-background rounded border">
|
||||||
<strong>Penting:</strong> Email dikirim melalui Mailketing API. Pastikan API token valid
|
<strong>Penting:</strong> Email dikirim melalui Mailketing API yang dikonfigurasi di tab Integrasi.
|
||||||
dan domain pengirim sudah terdaftar di Mailketing. Toggle "Aktifkan" hanya mengontrol
|
Pastikan API token valid dan domain pengirim sudah terdaftar di Mailketing.
|
||||||
pengiriman email. Jika <code>webhook_url</code> diisi, sistem tetap akan mengirim payload
|
Toggle "Aktifkan" hanya mengontrol pengiriman email. Jika <code>webhook_url</code> diisi,
|
||||||
ke URL tersebut meskipun email dinonaktifkan.
|
sistem tetap akan mengirim payload ke URL tersebut meskipun email dinonaktifkan.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
1
supabase/.temp/cli-latest
Normal file
1
supabase/.temp/cli-latest
Normal file
@@ -0,0 +1 @@
|
|||||||
|
v2.67.1
|
||||||
41
upload-function-code.sh
Executable file
41
upload-function-code.sh
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SUPABASE_URL="https://lovable.backoffice.biz.id"
|
||||||
|
SERVICE_ROLE_KEY="$SUPABASE_ACCESS_TOKEN"
|
||||||
|
|
||||||
|
# Function to upload code to edge function
|
||||||
|
upload_function() {
|
||||||
|
local func_name=$1
|
||||||
|
echo "Uploading code for $func_name..."
|
||||||
|
|
||||||
|
if [ -f "supabase/functions/$func_name/index.ts" ]; then
|
||||||
|
# Read the function content
|
||||||
|
FUNCTION_CONTENT=$(cat "supabase/functions/$func_name/index.ts")
|
||||||
|
|
||||||
|
# Update the function with code (using base64 encoding)
|
||||||
|
ENCODED_CONTENT=$(echo "$FUNCTION_CONTENT" | base64)
|
||||||
|
|
||||||
|
# Upload the function code
|
||||||
|
curl -X PUT "$SUPABASE_URL/rest/v1/functions/$func_name/body" \
|
||||||
|
-H "Authorization: Bearer $SERVICE_ROLE_KEY" \
|
||||||
|
-H "apikey: $SERVICE_ROLE_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"body\": \"$ENCODED_CONTENT\"
|
||||||
|
}"
|
||||||
|
|
||||||
|
echo "Code uploaded for $func_name"
|
||||||
|
else
|
||||||
|
echo "Function file not found: $func_name"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Upload code for all functions
|
||||||
|
upload_function "pakasir-webhook"
|
||||||
|
upload_function "send-test-email"
|
||||||
|
upload_function "create-meet-link"
|
||||||
|
upload_function "send-consultation-reminder"
|
||||||
|
upload_function "send-notification"
|
||||||
|
upload_function "daily-reminders"
|
||||||
|
|
||||||
|
echo "All function codes uploaded!"
|
||||||
Reference in New Issue
Block a user