Add calendar event lifecycle management and "Add to Calendar" feature
- Migrate consulting_slots to consulting_sessions structure - Add calendar_event_id to track Google Calendar events - Create delete-calendar-event edge function for auto-cleanup - Add "Tambah ke Kalender" button for members (OrderDetail, ConsultingHistory) - Update create-google-meet-event to store calendar event ID - Update handle-order-paid to use consulting_sessions table - Remove deprecated create-meet-link function - Add comprehensive documentation (CALENDAR_INTEGRATION.md, MIGRATION_GUIDE.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ serve(async (req: Request): Promise<Response> => {
|
||||
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||
|
||||
// Get full order details with items AND consulting slots
|
||||
// Get full order details with items AND consulting sessions
|
||||
// Use maybeSingle() in case there are no related records
|
||||
const { data: order, error: orderError } = await supabase
|
||||
.from("orders")
|
||||
@@ -41,12 +41,13 @@ serve(async (req: Request): Promise<Response> => {
|
||||
product_id,
|
||||
product:products (title, type)
|
||||
),
|
||||
consulting_slots (
|
||||
consulting_sessions (
|
||||
id,
|
||||
date,
|
||||
session_date,
|
||||
start_time,
|
||||
end_time,
|
||||
status
|
||||
status,
|
||||
topic_category
|
||||
)
|
||||
`)
|
||||
.eq("id", order_id)
|
||||
@@ -72,8 +73,8 @@ serve(async (req: Request): Promise<Response> => {
|
||||
id: order.id,
|
||||
payment_status: order.payment_status,
|
||||
order_items_count: order.order_items?.length || 0,
|
||||
consulting_slots_count: order.consulting_slots?.length || 0,
|
||||
consulting_slots: order.consulting_slots
|
||||
consulting_sessions_count: order.consulting_sessions?.length || 0,
|
||||
consulting_sessions: order.consulting_sessions
|
||||
}));
|
||||
|
||||
const userEmail = order.profiles?.email || "";
|
||||
@@ -83,49 +84,45 @@ serve(async (req: Request): Promise<Response> => {
|
||||
product: { title: string; type: string };
|
||||
}>;
|
||||
|
||||
// Check if this is a consulting order by checking consulting_slots
|
||||
const consultingSlots = order.consulting_slots as Array<{
|
||||
// Check if this is a consulting order by checking consulting_sessions
|
||||
const consultingSessions = order.consulting_sessions as Array<{
|
||||
id: string;
|
||||
date: string;
|
||||
session_date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
status: string;
|
||||
topic_category?: string;
|
||||
meet_link?: string;
|
||||
}>;
|
||||
const isConsultingOrder = consultingSlots && consultingSlots.length > 0;
|
||||
const isConsultingOrder = consultingSessions && consultingSessions.length > 0;
|
||||
|
||||
console.log("[HANDLE-PAID] isConsultingOrder:", isConsultingOrder, "consultingSlots:", consultingSlots);
|
||||
console.log("[HANDLE-PAID] isConsultingOrder:", isConsultingOrder, "consultingSessions:", consultingSessions);
|
||||
|
||||
if (isConsultingOrder) {
|
||||
console.log("[HANDLE-PAID] Consulting order detected, processing slots");
|
||||
console.log("[HANDLE-PAID] Consulting order detected, processing sessions");
|
||||
|
||||
// Sort slots by start_time to ensure correct ordering
|
||||
consultingSlots.sort((a, b) => a.start_time.localeCompare(b.start_time));
|
||||
|
||||
// Update consulting slots status from pending_payment to confirmed
|
||||
// Update consulting sessions status from pending_payment to confirmed
|
||||
const { error: updateError } = await supabase
|
||||
.from("consulting_slots")
|
||||
.from("consulting_sessions")
|
||||
.update({ status: "confirmed" })
|
||||
.eq("order_id", order_id)
|
||||
.in("status", ["pending_payment"]);
|
||||
|
||||
console.log("[HANDLE-PAID] Slot update result:", { updateError, order_id });
|
||||
console.log("[HANDLE-PAID] Session update result:", { updateError, order_id });
|
||||
|
||||
if (updateError) {
|
||||
console.error("[HANDLE-PAID] Failed to update slots:", updateError);
|
||||
console.error("[HANDLE-PAID] Failed to update sessions:", updateError);
|
||||
}
|
||||
|
||||
if (consultingSlots && consultingSlots.length > 0) {
|
||||
if (consultingSessions && consultingSessions.length > 0) {
|
||||
try {
|
||||
console.log("[HANDLE-PAID] Creating Google Meet for order:", order_id);
|
||||
|
||||
// Group slots by order - use first slot's start time and last slot's end time
|
||||
const firstSlot = consultingSlots[0];
|
||||
const lastSlot = consultingSlots[consultingSlots.length - 1];
|
||||
const topic = "Konsultasi 1-on-1";
|
||||
// Use the first session for Meet creation
|
||||
const session = consultingSessions[0];
|
||||
const topic = session.topic_category || "Konsultasi 1-on-1";
|
||||
|
||||
console.log("[HANDLE-PAID] Time slots:", consultingSlots.map(s => `${s.start_time}-${s.end_time}`).join(', '));
|
||||
console.log("[HANDLE-PAID] Event will be:", `${firstSlot.start_time} - ${lastSlot.end_time}`);
|
||||
console.log("[HANDLE-PAID] Session time:", `${session.start_time} - ${session.end_time}`);
|
||||
|
||||
const meetResponse = await fetch(
|
||||
`${supabaseUrl}/functions/v1/create-google-meet-event`,
|
||||
@@ -136,14 +133,14 @@ serve(async (req: Request): Promise<Response> => {
|
||||
"Authorization": `Bearer ${Deno.env.get("SUPABASE_ANON_KEY")}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
slot_id: firstSlot.id, // Use first slot ID
|
||||
date: firstSlot.date,
|
||||
start_time: firstSlot.start_time,
|
||||
end_time: lastSlot.end_time, // Use last slot's end time for continuous block
|
||||
slot_id: session.id,
|
||||
date: session.session_date,
|
||||
start_time: session.start_time,
|
||||
end_time: session.end_time,
|
||||
client_name: userName,
|
||||
client_email: userEmail,
|
||||
topic: topic,
|
||||
notes: `${consultingSlots.length} sesi: ${consultingSlots.map(s => s.start_time.substring(0, 5)).join(', ')}`,
|
||||
notes: `Session ID: ${session.id}`,
|
||||
}),
|
||||
}
|
||||
);
|
||||
@@ -157,16 +154,16 @@ serve(async (req: Request): Promise<Response> => {
|
||||
if (meetData.success) {
|
||||
console.log("[HANDLE-PAID] Meet created:", meetData.meet_link);
|
||||
|
||||
// Update all slots with the same meet link
|
||||
// Update session with meet link
|
||||
const { error: updateError } = await supabase
|
||||
.from("consulting_slots")
|
||||
.from("consulting_sessions")
|
||||
.update({ meet_link: meetData.meet_link })
|
||||
.eq("order_id", order_id);
|
||||
|
||||
if (updateError) {
|
||||
console.error("[HANDLE-PAID] Failed to update meet_link:", updateError);
|
||||
} else {
|
||||
console.log("[HANDLE-PAID] Meet link updated for all slots in order:", order_id);
|
||||
console.log("[HANDLE-PAID] Meet link updated for session:", order_id);
|
||||
}
|
||||
} else {
|
||||
console.error("[HANDLE-PAID] Meet creation returned success=false:", meetData);
|
||||
@@ -182,7 +179,7 @@ serve(async (req: Request): Promise<Response> => {
|
||||
}
|
||||
}
|
||||
|
||||
// Send consulting notification with the consultingSlots data
|
||||
// Send consulting notification with the consultingSessions data
|
||||
await sendNotification(supabase, "consulting_scheduled", {
|
||||
nama: userName,
|
||||
email: userEmail,
|
||||
@@ -190,14 +187,14 @@ serve(async (req: Request): Promise<Response> => {
|
||||
tanggal_pesanan: new Date().toLocaleDateString("id-ID"),
|
||||
total: `Rp ${order.total_amount.toLocaleString("id-ID")}`,
|
||||
metode_pembayaran: order.payment_method || "Unknown",
|
||||
tanggal_konsultasi: consultingSlots[0]?.date || "",
|
||||
jam_konsultasi: consultingSlots.map(s => s.start_time.substring(0, 5)).join(", "),
|
||||
link_meet: consultingSlots[0]?.meet_link || "Akan dikirim terpisah",
|
||||
tanggal_konsultasi: consultingSessions[0]?.session_date || "",
|
||||
jam_konsultasi: consultingSessions.map(s => `${s.start_time.substring(0, 5)} - ${s.end_time.substring(0, 5)}`).join(", "),
|
||||
link_meet: consultingSessions[0]?.meet_link || "Akan dikirim terpisah",
|
||||
event: "consulting_scheduled",
|
||||
order_id,
|
||||
user_id: order.user_id,
|
||||
user_name: userName,
|
||||
slots: consultingSlots,
|
||||
slots: consultingSessions,
|
||||
});
|
||||
} else {
|
||||
// Regular product order - grant access
|
||||
|
||||
Reference in New Issue
Block a user