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:
dwindown
2025-12-22 19:13:01 +07:00
parent 7f918374f4
commit 3c902aaef5
5 changed files with 324 additions and 235 deletions

40
deploy-edge-functions.sh Executable file
View 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!"

View File

@@ -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; 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) { if (settings.id) {
const { error } = await supabase const { error: platformError } = await supabase
.from('platform_settings') .from('platform_settings')
.update(payload) .update(platformPayload)
.eq('id', settings.id); .eq('id', settings.id);
if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' }); if (platformError) throw platformError;
else toast({ title: 'Berhasil', description: 'Pengaturan integrasi disimpan' }); }
} else {
const { data, error } = await supabase // Save email provider settings to notification_settings
.from('platform_settings') const emailPayload = {
.insert(payload) provider: settings.provider,
.select() api_token: settings.api_token,
.single(); 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;
}
if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' });
else {
setSettings({ ...settings, id: data.id });
toast({ title: 'Berhasil', description: 'Pengaturan integrasi disimpan' }); 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,33 +289,100 @@ 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>
{settings.provider === 'mailketing' && (
<>
<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={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"> <div className="space-y-2">
<Label>API Base URL Provider Email</Label> <Label>API Base URL Provider Email</Label>
<Input <Input
@@ -227,9 +390,12 @@ export function IntegrasiTab() {
onChange={(e) => setSettings({ ...settings, integration_email_api_base_url: e.target.value })} onChange={(e) => setSettings({ ...settings, integration_email_api_base_url: e.target.value })}
placeholder="https://api.resend.com" placeholder="https://api.resend.com"
className="border-2" className="border-2"
disabled={settings.integration_email_provider === 'smtp'}
/> />
<p className="text-sm text-muted-foreground">
Konfigurasi SMTP masih di bagian Notifikasi
</p>
</div> </div>
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -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,61 +108,6 @@ 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;
@@ -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>
Konfigurasi email provider belum lengkap. Email tidak akan terkirim. Konfigurasikan provider email di tab <strong>Integrasi Provider Email</strong> untuk mengirim notifikasi.
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>

View File

@@ -0,0 +1 @@
v2.67.1

41
upload-function-code.sh Executable file
View 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!"