+
+ {settings.integration_n8n_test_mode && (
+
+
+
+ Mode test aktif: Webhook akan menggunakan path /webhook-test/
+
+
+ )}
diff --git a/src/lib/email-templates/master-template.ts b/src/lib/email-templates/master-template.ts
index 1e0ceaf..5f40876 100644
--- a/src/lib/email-templates/master-template.ts
+++ b/src/lib/email-templates/master-template.ts
@@ -357,7 +357,7 @@ export class ShortcodeProcessor {
total: 'Rp 1.500.000',
metode_pembayaran: 'Transfer Bank',
status_pesanan: 'Diproses',
- invoice_url: 'https://example.com/invoice/ORD-123456',
+ invoice_url: 'https://with.dwindi.com/orders/ORD-123456',
// Product information
produk: 'Digital Marketing Masterclass',
@@ -366,7 +366,7 @@ export class ShortcodeProcessor {
deskripsi_produk: 'Kelas lengkap digital marketing dari pemula hingga mahir',
// Access information
- link_akses: 'https://example.com/access',
+ link_akses: 'https://with.dwindi.com/access',
username_akses: 'john.doe',
password_akses: 'Temp123!',
kadaluarsa_akses: '22 Desember 2026',
@@ -383,7 +383,7 @@ export class ShortcodeProcessor {
judul_event: 'Workshop Digital Marketing',
tanggal_event: '25 Desember 2025',
jam_event: '19:00',
- link_event: 'https://event.example.com',
+ link_event: 'https://with.dwindi.com/events',
lokasi_event: 'Zoom Online',
kapasitas_event: '100 peserta',
@@ -392,12 +392,12 @@ export class ShortcodeProcessor {
progres_bootcamp: '75%',
modul_selesai: '15 dari 20 modul',
modul_selanjutnya: 'Final Assessment',
- link_progress: 'https://example.com/progress',
+ link_progress: 'https://with.dwindi.com/bootcamp/progress',
// Company information
nama_perusahaan: 'ACCESS HUB',
- website_perusahaan: 'https://accesshub.example.com',
- email_support: 'support@accesshub.example.com',
+ website_perusahaan: 'https://with.dwindi.com',
+ email_support: 'support@with.dwindi.com',
telepon_support: '+62 812-3456-7890',
// Payment information
@@ -406,8 +406,8 @@ export class ShortcodeProcessor {
atas_nama: 'PT Access Hub Indonesia',
jumlah_pembayaran: 'Rp 1.500.000',
batas_pembayaran: '22 Desember 2025 23:59',
- payment_link: 'https://accesshub.example.com/checkout',
- thank_you_page: 'https://accesshub.example.com/orders/{order_id}'
+ payment_link: 'https://with.dwindi.com/checkout',
+ thank_you_page: 'https://with.dwindi.com/orders/{order_id}'
};
static process(content: string, customData: Record = {}): string {
diff --git a/supabase/config.toml b/supabase/config.toml
index a6c232c..ecd7019 100644
--- a/supabase/config.toml
+++ b/supabase/config.toml
@@ -32,3 +32,6 @@ verify_jwt = false
[functions.daily-reminders]
verify_jwt = false
+
+[functions.send-email-v2]
+verify_jwt = false
diff --git a/supabase/functions/create-meet-link/index.ts b/supabase/functions/create-meet-link/index.ts
index ddc5c5a..1becdd0 100644
--- a/supabase/functions/create-meet-link/index.ts
+++ b/supabase/functions/create-meet-link/index.ts
@@ -52,16 +52,21 @@ serve(async (req: Request): Promise => {
// For now, this is a placeholder that returns a message
// In production, you would integrate with Google Calendar API via OAuth or service account
// Or call an n8n webhook to handle the calendar creation
-
+
const { data: integrationSettings } = await supabase
.from("platform_settings")
- .select("integration_n8n_base_url")
+ .select("integration_n8n_base_url, integration_n8n_test_mode")
.single();
if (integrationSettings?.integration_n8n_base_url) {
- // Call n8n webhook if configured
- const n8nUrl = `${integrationSettings.integration_n8n_base_url}/webhook/create-meet`;
-
+ // Check if we're in test mode (controlled by the integration_n8n_test_mode setting)
+ const isTestMode = integrationSettings.integration_n8n_test_mode || false;
+
+ const webhookPath = isTestMode ? "/webhook-test/" : "/webhook/";
+ const n8nUrl = `${integrationSettings.integration_n8n_base_url}${webhookPath}create-meet`;
+
+ console.log(`Calling n8n webhook: ${n8nUrl} (Test mode: ${isTestMode})`);
+
try {
const n8nResponse = await fetch(n8nUrl, {
method: "POST",
@@ -77,6 +82,7 @@ serve(async (req: Request): Promise => {
notes: body.notes,
calendar_id: calendarId,
brand_name: brandName,
+ test_mode: isTestMode, // Add test_mode flag for n8n to use
}),
});
diff --git a/supabase/functions/daily-reminders/index.ts b/supabase/functions/daily-reminders/index.ts
index 8d49452..7d3ac39 100644
--- a/supabase/functions/daily-reminders/index.ts
+++ b/supabase/functions/daily-reminders/index.ts
@@ -69,7 +69,63 @@ serve(async (req: Request): Promise => {
if (!profile?.email) continue;
// Call send-notification function
- const { error: notifyError } = await supabase.functions.invoke("send-notification", {
+ // Get notification template and settings to send via send-email-v2
+ const { data: template } = await supabase
+ .from("notification_templates")
+ .select("*")
+ .eq("key", "consulting_reminder")
+ .eq("is_active", true)
+ .single();
+
+ const { data: emailSettings } = await supabase
+ .from("notification_settings")
+ .select("*")
+ .single();
+
+ let notifyError = null;
+
+ if (template && emailSettings?.api_token) {
+ // Build payload with proper shortcode mapping
+ const payload = {
+ nama: profile.full_name,
+ email: profile.email,
+ tanggal_konsultasi: new Date(slot.date).toLocaleDateString('id-ID', {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ }),
+ jam_konsultasi: `${slot.start_time.substring(0, 5)} - ${slot.end_time.substring(0, 5)} WIB`,
+ link_meet: slot.meet_link || "Akan diinformasikan",
+ jenis_konsultasi: slot.topic_category,
+ };
+
+ // Process shortcodes in template
+ let emailBody = template.email_body_html || "";
+ let emailSubject = template.email_subject || "Reminder Konsultasi";
+
+ Object.entries(payload).forEach(([key, value]) => {
+ const regex = new RegExp(`\\{${key}\\}`, "g");
+ emailBody = emailBody.replace(regex, String(value));
+ emailSubject = emailSubject.replace(regex, String(value));
+ });
+
+ // Send via send-email-v2 (Mailketing API)
+ const { error: emailError } = await supabase.functions.invoke("send-email-v2", {
+ body: {
+ to: profile.email,
+ api_token: emailSettings.api_token,
+ from_name: emailSettings.from_name || "Access Hub",
+ from_email: emailSettings.from_email || "noreply@with.dwindi.com",
+ subject: emailSubject,
+ html_body: emailBody,
+ },
+ });
+
+ notifyError = emailError;
+ } else {
+ notifyError = { message: "Template not active or email not configured" };
+ }
body: {
template_key: "consultation_reminder",
recipient_email: profile.email,
diff --git a/supabase/functions/send-consultation-reminder/index.ts b/supabase/functions/send-consultation-reminder/index.ts
index 1de6bc6..4cdd15f 100644
--- a/supabase/functions/send-consultation-reminder/index.ts
+++ b/supabase/functions/send-consultation-reminder/index.ts
@@ -127,22 +127,39 @@ serve(async (req: Request): Promise => {
}
}
- // Send email if template is active and SMTP is configured
- if (template?.is_active && smtpSettings?.smtp_host && profile?.email) {
- // Replace shortcodes in email body
- let emailBody = template.email_body_html || "";
- let emailSubject = template.email_subject || "Reminder Konsultasi";
+ // Send email if template is active and Mailketing is configured
+ if (template?.is_active && smtpSettings?.api_token && profile?.email) {
+ try {
+ // Replace shortcodes in email body using master template system
+ let emailBody = template.email_body_html || "";
+ let emailSubject = template.email_subject || "Reminder Konsultasi";
- Object.entries(payload).forEach(([key, value]) => {
- const regex = new RegExp(`\\{${key}\\}`, "g");
- emailBody = emailBody.replace(regex, String(value));
- emailSubject = emailSubject.replace(regex, String(value));
- });
+ Object.entries(payload).forEach(([key, value]) => {
+ const regex = new RegExp(`\\{${key}\\}`, "g");
+ emailBody = emailBody.replace(regex, String(value));
+ emailSubject = emailSubject.replace(regex, String(value));
+ });
- // Here you would send the actual email
- // For now, log that we would send it
- console.log("Would send reminder email to:", profile.email);
- console.log("Subject:", emailSubject);
+ // Send via send-email-v2 (Mailketing API)
+ const { error: emailError } = await supabase.functions.invoke("send-email-v2", {
+ body: {
+ to: profile.email,
+ api_token: smtpSettings.api_token,
+ from_name: smtpSettings.from_name || platformSettings?.brand_name || "Access Hub",
+ from_email: smtpSettings.from_email || "noreply@with.dwindi.com",
+ subject: emailSubject,
+ html_body: emailBody,
+ },
+ });
+
+ if (emailError) {
+ console.error("Failed to send reminder email:", emailError);
+ } else {
+ console.log("Reminder email sent to:", profile.email);
+ }
+ } catch (emailError) {
+ console.error("Error sending reminder email:", emailError);
+ }
}
results.push({
diff --git a/supabase/functions/send-email-v2/index.ts b/supabase/functions/send-email-v2/index.ts
new file mode 100644
index 0000000..afbeca5
--- /dev/null
+++ b/supabase/functions/send-email-v2/index.ts
@@ -0,0 +1,99 @@
+import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
+
+const corsHeaders = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
+};
+
+interface EmailRequest {
+ to: string;
+ api_token: string;
+ from_name: string;
+ from_email: string;
+ subject: string;
+ html_body: string;
+}
+
+// Send via Mailketing API
+async function sendViaMailketing(request: EmailRequest): Promise<{ success: boolean; message: string }> {
+ const { to, api_token, from_name, from_email, subject, html_body } = request;
+
+ const formData = new FormData();
+ formData.append('to', to);
+ formData.append('from_name', from_name);
+ formData.append('from_email', from_email);
+ formData.append('subject', subject);
+ formData.append('html_body', html_body);
+
+ console.log(`Sending email via Mailketing to ${to}`);
+
+ const response = await fetch('https://api.mailketing.co/v1/send', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${api_token}`,
+ },
+ body: formData,
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error('Mailketing API error:', response.status, errorText);
+ throw new Error(`Mailketing API error: ${response.status} ${errorText}`);
+ }
+
+ const result = await response.json();
+ console.log('Mailketing API response:', result);
+
+ return {
+ success: true,
+ message: result.message || 'Email sent successfully via Mailketing'
+ };
+}
+
+serve(async (req: Request): Promise => {
+ if (req.method === "OPTIONS") {
+ return new Response(null, { headers: corsHeaders });
+ }
+
+ try {
+ const body: EmailRequest = await req.json();
+
+ // Validate required fields
+ if (!body.to || !body.api_token || !body.from_name || !body.from_email || !body.subject || !body.html_body) {
+ return new Response(
+ JSON.stringify({ success: false, message: "Missing required fields: to, api_token, from_name, from_email, subject, html_body" }),
+ { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
+ );
+ }
+
+ // Basic email validation
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ if (!emailRegex.test(body.to) || !emailRegex.test(body.from_email)) {
+ return new Response(
+ JSON.stringify({ success: false, message: "Invalid email format" }),
+ { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
+ );
+ }
+
+ console.log(`Attempting to send email to: ${body.to}`);
+ console.log(`From: ${body.from_name} <${body.from_email}>`);
+ console.log(`Subject: ${body.subject}`);
+
+ const result = await sendViaMailketing(body);
+
+ return new Response(
+ JSON.stringify(result),
+ { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }
+ );
+
+ } catch (error: any) {
+ console.error("Error sending email:", error);
+ return new Response(
+ JSON.stringify({
+ success: false,
+ message: error.message || "Failed to send email"
+ }),
+ { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
+ );
+ }
+});
\ No newline at end of file
diff --git a/test-edge-functions.sh b/test-edge-functions.sh
new file mode 100755
index 0000000..4db4799
--- /dev/null
+++ b/test-edge-functions.sh
@@ -0,0 +1,143 @@
+#!/bin/bash
+
+# Test script for Supabase Edge Functions
+# Make sure to set: export SUPABASE_ACCESS_TOKEN=your_service_role_key
+
+# Configuration
+SUPABASE_URL="https://lovable.backoffice.biz.id"
+SERVICE_ROLE_KEY="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTc2NjAzNzEyMCwiZXhwIjo0OTIxNzEwNzIwLCJyb2xlIjoic2VydmljZV9yb2xlIn0.t6D9VwaukYGq4c_VbW1bkd3ZkKgldpCKRR13nN14XXc"
+
+if [ -z "$SERVICE_ROLE_KEY" ]; then
+ echo "❌ Error: SUPABASE_ACCESS_TOKEN environment variable not set"
+ echo "Run: export SUPABASE_ACCESS_TOKEN=your_service_role_key"
+ exit 1
+fi
+
+echo "🚀 Testing Supabase Edge Functions"
+echo "=================================="
+echo "URL: $SUPABASE_URL"
+echo ""
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Function to test API
+test_function() {
+ local func_name=$1
+ local payload=$2
+ local expected_status=${3:-200}
+
+ echo -e "${YELLOW}Testing: $func_name${NC}"
+ echo "Payload: $payload"
+
+ response=$(curl -s -w "%{http_code}" \
+ -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\": false
+ }")
+
+ # Extract status code (last 3 characters)
+ status_code="${response: -3}"
+ # Extract response body (everything except last 3 characters)
+ response_body="${response%???}"
+
+ if [ "$status_code" = "200" ] || [ "$status_code" = "201" ]; then
+ echo -e "${GREEN}✅ Success: $func_name${NC}"
+ echo "Response: $response_body"
+ else
+ echo -e "${RED}❌ Error: $func_name (Status: $status_code)${NC}"
+ echo "Response: $response_body"
+ fi
+ echo "----------------------------------------"
+ echo ""
+}
+
+# Test function invocation
+test_invoke_function() {
+ local func_name=$1
+ local payload=$2
+
+ echo -e "${YELLOW}Invoking: $func_name${NC}"
+
+ response=$(curl -s -w "%{http_code}" \
+ -X POST "$SUPABASE_URL/functions/v1/$func_name" \
+ -H "Authorization: Bearer $SERVICE_ROLE_KEY" \
+ -H "Content-Type: application/json" \
+ -d "$payload")
+
+ # Extract status code (last 3 characters)
+ status_code="${response: -3}"
+ # Extract response body (everything except last 3 characters)
+ response_body="${response%???}"
+
+ if [ "$status_code" = "200" ]; then
+ echo -e "${GREEN}✅ Function executed successfully${NC}"
+ echo "Response: $response_body"
+ else
+ echo -e "${RED}❌ Function invocation failed (Status: $status_code)${NC}"
+ echo "Response: $response_body"
+ fi
+ echo "----------------------------------------"
+ echo ""
+}
+
+# 1. Test creating/updating functions
+echo "📋 Testing Function Creation/Update"
+echo ""
+
+test_function "send-email-v2"
+test_function "create-meet-link"
+test_function "send-consultation-reminder"
+test_function "daily-reminders"
+test_function "pakasir-webhook"
+
+# 2. Test function invocations
+echo "🔧 Testing Function Invocations"
+echo ""
+
+# Test send-email-v2 (will fail without proper Mailketing token, but should reach the function)
+test_invoke_function "send-email-v2" '{
+ "to": "dwinx.ramz@gmail.com",
+ "api_token": "b4d1beb302282f411d196146f6806eab",
+ "from_name": "Test Hub",
+ "from_email": "with@dwindi.com",
+ "subject": "Test Email",
+ "html_body": "
Test Email
This is a test from send-email-v2
"
+}'
+
+# Test create-meet-link (will fail without proper n8n webhook, but should reach the function)
+# Note: Will use webhook path based on integration_n8n_test_mode setting
+test_invoke_function "create-meet-link" '{
+ "slot_id": "test-slot-123",
+ "date": "2025-12-23",
+ "start_time": "14:00:00",
+ "end_time": "15:00:00",
+ "client_name": "Test Client",
+ "client_email": "dewe.pw@gmail.com",
+ "topic": "Test Topic",
+ "notes": "Test notes"
+}'
+
+# Test daily-reminders (should work with database queries)
+test_invoke_function "daily-reminders" '{}'
+
+echo "✨ Testing Complete!"
+echo ""
+echo "📝 Notes:"
+echo "- send-email-v2: Needs valid Mailketing API token to actually send emails"
+echo "- create-meet-link: Needs n8n webhook configured in platform_settings"
+echo " - Will use /webhook-test/ if integration_n8n_test_mode is enabled"
+echo " - Will use /webhook/ if integration_n8n_test_mode is disabled"
+echo "- Functions are deployed and accessible via the functions/v1/ endpoint"
+echo ""
+echo "🌐 Function URLs:"
+echo "- https://lovable.backoffice.biz.id/functions/v1/send-email-v2"
+echo "- https://lovable.backoffice.biz.id/functions/v1/create-meet-link"
+echo "- https://lovable.backoffice.biz.id/functions/v1/daily-reminders"
\ No newline at end of file