diff --git a/deploy-edge-functions.sh b/deploy-edge-functions.sh new file mode 100755 index 0000000..b4fb62e --- /dev/null +++ b/deploy-edge-functions.sh @@ -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!" diff --git a/src/components/admin/settings/IntegrasiTab.tsx b/src/components/admin/settings/IntegrasiTab.tsx index c048860..1290198 100644 --- a/src/components/admin/settings/IntegrasiTab.tsx +++ b/src/components/admin/settings/IntegrasiTab.tsx @@ -5,8 +5,9 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Alert, AlertDescription } from '@/components/ui/alert'; 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 { id?: string; @@ -18,6 +19,11 @@ interface IntegrationSettings { integration_email_api_base_url: string; integration_privacy_url: string; integration_terms_url: string; + // Mailketing specific settings + provider: 'mailketing' | 'smtp'; + api_token: string; + from_name: string; + from_email: string; } const emptySettings: IntegrationSettings = { @@ -25,38 +31,55 @@ const emptySettings: IntegrationSettings = { integration_whatsapp_number: '', integration_whatsapp_url: '', integration_google_calendar_id: '', - integration_email_provider: 'smtp', + integration_email_provider: 'mailketing', integration_email_api_base_url: '', integration_privacy_url: '/privacy', integration_terms_url: '/terms', + provider: 'mailketing', + api_token: '', + from_name: '', + from_email: '', }; export function IntegrasiTab() { const [settings, setSettings] = useState(emptySettings); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); + const [testEmail, setTestEmail] = useState(''); + const [sendingTest, setSendingTest] = useState(false); useEffect(() => { fetchSettings(); }, []); const fetchSettings = async () => { - const { data, error } = await supabase + const { data: platformData } = await supabase .from('platform_settings') .select('*') .single(); - - if (data) { + + // Fetch email provider settings from notification_settings + const { data: emailData } = await supabase + .from('notification_settings') + .select('*') + .single(); + + if (platformData) { setSettings({ - id: data.id, - integration_n8n_base_url: data.integration_n8n_base_url || '', - integration_whatsapp_number: data.integration_whatsapp_number || '', - integration_whatsapp_url: data.integration_whatsapp_url || '', - integration_google_calendar_id: data.integration_google_calendar_id || '', - integration_email_provider: data.integration_email_provider || 'smtp', - integration_email_api_base_url: data.integration_email_api_base_url || '', - integration_privacy_url: data.integration_privacy_url || '/privacy', - integration_terms_url: data.integration_terms_url || '/terms', + id: platformData.id, + integration_n8n_base_url: platformData.integration_n8n_base_url || '', + integration_whatsapp_number: platformData.integration_whatsapp_number || '', + integration_whatsapp_url: platformData.integration_whatsapp_url || '', + integration_google_calendar_id: platformData.integration_google_calendar_id || '', + integration_email_provider: platformData.integration_email_provider || 'mailketing', + integration_email_api_base_url: platformData.integration_email_api_base_url || '', + integration_privacy_url: platformData.integration_privacy_url || '/privacy', + 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); @@ -64,33 +87,106 @@ export function IntegrasiTab() { const saveSettings = async () => { setSaving(true); - const payload = { ...settings }; - delete payload.id; - if (settings.id) { - const { error } = await supabase - .from('platform_settings') - .update(payload) - .eq('id', settings.id); - - if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' }); - else toast({ title: 'Berhasil', description: 'Pengaturan integrasi disimpan' }); - } else { - const { data, error } = await supabase - .from('platform_settings') - .insert(payload) - .select() - .single(); - - if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' }); - else { - setSettings({ ...settings, id: data.id }); - toast({ title: 'Berhasil', description: 'Pengaturan integrasi disimpan' }); + try { + // Save platform settings + const platformPayload = { + integration_n8n_base_url: settings.integration_n8n_base_url, + integration_whatsapp_number: settings.integration_whatsapp_number, + integration_whatsapp_url: settings.integration_whatsapp_url, + integration_google_calendar_id: settings.integration_google_calendar_id, + integration_email_provider: settings.integration_email_provider, + integration_email_api_base_url: settings.integration_email_api_base_url, + integration_privacy_url: settings.integration_privacy_url, + integration_terms_url: settings.integration_terms_url, + }; + + if (settings.id) { + const { error: platformError } = await supabase + .from('platform_settings') + .update(platformPayload) + .eq('id', settings.id); + + 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); }; + 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: ` +

Test Email

+

Ini adalah email uji coba dari aplikasi Access Hub Anda.

+

Jika Anda menerima email ini, konfigurasi Mailketing API sudah berfungsi dengan baik!

+

Kirim ke: ${testEmail}

+
+

Best regards,
Access Hub Team

+ `, + }, + }); + + 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
; return ( @@ -193,43 +289,113 @@ export function IntegrasiTab() { - Provider Email (Opsional) + Provider Email - Konfigurasi alternatif selain SMTP + Konfigurasi provider email untuk pengiriman notifikasi -
+ {!isEmailConfigured && ( + + + + Konfigurasi email provider belum lengkap. Email tidak akan terkirim. + + + )} + +
- +
-
- - setSettings({ ...settings, integration_email_api_base_url: e.target.value })} - placeholder="https://api.resend.com" - className="border-2" - disabled={settings.integration_email_provider === 'smtp'} - /> -
+ {settings.provider === 'mailketing' && ( + <> +
+ + setSettings({ ...settings, api_token: e.target.value })} + placeholder="Masukkan API token dari Mailketing" + className="border-2" + /> +

+ Dapatkan API token dari menu Integration di dashboard Mailketing +

+
+ +
+
+ + setSettings({ ...settings, from_name: e.target.value })} + placeholder="Nama Bisnis" + className="border-2" + /> +
+
+ + setSettings({ ...settings, from_email: e.target.value })} + placeholder="info@domain.com" + className="border-2" + /> +

+ Pastikan email sudah terdaftar di Mailketing +

+
+
+ +
+ setTestEmail(e.target.value)} + placeholder="Email uji coba" + className="border-2 max-w-xs" + /> + +
+ + )} + + {settings.provider === 'smtp' && ( +
+ + setSettings({ ...settings, integration_email_api_base_url: e.target.value })} + placeholder="https://api.resend.com" + className="border-2" + /> +

+ Konfigurasi SMTP masih di bagian Notifikasi +

+
+ )}
diff --git a/src/components/admin/settings/NotifikasiTab.tsx b/src/components/admin/settings/NotifikasiTab.tsx index 99a7893..23c3e5b 100644 --- a/src/components/admin/settings/NotifikasiTab.tsx +++ b/src/components/admin/settings/NotifikasiTab.tsx @@ -2,24 +2,14 @@ import { useEffect, useState } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { RichTextEditor } from '@/components/RichTextEditor'; 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 { 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 { 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() { - const [emailSettings, setEmailSettings] = useState(emptyEmailSettings); const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [testEmail, setTestEmail] = useState(''); const [expandedTemplates, setExpandedTemplates] = useState>(new Set()); - const [sendingTest, setSendingTest] = useState(false); useEffect(() => { fetchData(); }, []); const fetchData = async () => { - // Fetch email provider settings - const { data: emailData } = await supabase.from('notification_settings').select('*').single(); - if (emailData) setEmailSettings(emailData); - // Fetch templates const { data: templatesData } = await supabase.from('notification_templates').select('*').order('key'); if (templatesData && templatesData.length > 0) { @@ -133,62 +108,7 @@ export function NotifikasiTab() { 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: ` -

Test Email

-

Ini adalah email uji coba dari aplikasi Access Hub Anda.

-

Jika Anda menerima email ini, konfigurasi Mailketing API sudah berfungsi dengan baik!

-

Kirim ke: ${testEmail}

-
-

Best regards,
Access Hub Team

- `, - }, - }); - - 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 { id, key, name, ...updates } = template; 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
; return (
- {/* Email Provider Settings */} + {/* Notification Templates Info */} - Pengaturan Email Provider + Konfigurasi Email - Konfigurasi provider email untuk pengiriman notifikasi + + Pengaturan provider email (Mailketing API) ada di tab Integrasi + - - {!isEmailConfigured && ( - - - - Konfigurasi email provider belum lengkap. Email tidak akan terkirim. - - - )} - -
-
- - -
- -
- - setEmailSettings({ ...emailSettings, api_token: e.target.value })} - placeholder="Masukkan API token dari Mailketing" - className="border-2" - /> -

- Dapatkan API token dari menu Integration di dashboard Mailketing -

-
- -
-
- - setEmailSettings({ ...emailSettings, from_name: e.target.value })} - placeholder="Nama Bisnis" - className="border-2" - /> -
-
- - setEmailSettings({ ...emailSettings, from_email: e.target.value })} - placeholder="info@domain.com" - className="border-2" - /> -

- Pastikan email sudah terdaftar di Mailketing -

-
-
-
- -
- -
- setTestEmail(e.target.value)} - placeholder="Email uji coba" - className="border-2 max-w-xs" - /> - -
-
+ + + + + Konfigurasikan provider email di tab Integrasi → Provider Email untuk mengirim notifikasi. + Gunakan Mailketing API untuk pengiriman email yang andal. + +
@@ -336,10 +177,10 @@ export function NotifikasiTab() {

- Penting: Email dikirim melalui Mailketing API. Pastikan API token valid - dan domain pengirim sudah terdaftar di Mailketing. Toggle "Aktifkan" hanya mengontrol - pengiriman email. Jika webhook_url diisi, sistem tetap akan mengirim payload - ke URL tersebut meskipun email dinonaktifkan. + Penting: Email dikirim melalui Mailketing API yang dikonfigurasi di tab Integrasi. + Pastikan API token valid dan domain pengirim sudah terdaftar di Mailketing. + Toggle "Aktifkan" hanya mengontrol pengiriman email. Jika webhook_url diisi, + sistem tetap akan mengirim payload ke URL tersebut meskipun email dinonaktifkan.

diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest new file mode 100644 index 0000000..8c68db7 --- /dev/null +++ b/supabase/.temp/cli-latest @@ -0,0 +1 @@ +v2.67.1 \ No newline at end of file diff --git a/upload-function-code.sh b/upload-function-code.sh new file mode 100755 index 0000000..2cd8ef6 --- /dev/null +++ b/upload-function-code.sh @@ -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!"