diff --git a/components/Layout/Dashboard/API-Integrations/WebhookManager.tsx b/components/Layout/Dashboard/API-Integrations/WebhookManager.tsx index b854922..6f606d7 100644 --- a/components/Layout/Dashboard/API-Integrations/WebhookManager.tsx +++ b/components/Layout/Dashboard/API-Integrations/WebhookManager.tsx @@ -22,6 +22,9 @@ import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { ScrollArea } from '@/components/ui/scroll-area'; +import { saveWebhookCredentials } from '@/lib/db/content'; +import { toast } from 'sonner'; +import { useContent } from '@/context/GenerationContext'; // Mock Encryption/Decryption Utility (Simulating secure storage/retrieval) const mockEncrypt = (data) => `ENC:${btoa(data)}`; @@ -71,7 +74,9 @@ const WebhookFormDialog = ({ isOpen, onClose, initialData, onSave, status, messa }, [initialData]); useEffect(() => { - setMessageBox({ message, status }) + const timer = setTimeout(() => setMessageBox({ message, status }), 300); + + return () => clearTimeout(timer); }, [status, message]); @@ -81,7 +86,6 @@ const WebhookFormDialog = ({ isOpen, onClose, initialData, onSave, status, messa try { const url = new URL(formData.url.trim()); if (url.protocol !== 'https:') { - // Using console.error instead of alert as per instructions console.error("Validation Error: Only HTTPS URLs are permitted for security."); // setMessageBox('Validation Error: Only HTTPS URLs are permitted for security.', 'error'); return; @@ -221,17 +225,17 @@ const WebhookFormDialog = ({ isOpen, onClose, initialData, onSave, status, messa // --- Main Webhook Manager Component --- export const WebhookManager = () => { - // Initialize state with mock data. We decrypt for display purposes. - const [webhooks, setWebhooks] = useState(initialMockWebhooks.map(h => ({ - ...h, - secret_key: mockDecrypt(h.secret_key), - }))); + const { + webhookCredentials: webhooks, + setWebhookCredentials: setWebhooks, + isWebhookCredentialsLoading + } = useContent(); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingWebhook, setEditingWebhook] = useState(null); // UI Status State (used for global actions like delete or toggle) - const [status, setStatus] = useState('idle'); // 'idle', 'loading', 'success', 'error' + const [status, setStatus] = useState<'idle'| 'loading'| 'success'| 'error'>('idle'); const [message, setMessage] = useState(''); // --- CRUD Handlers (Local State Management) --- @@ -247,12 +251,11 @@ export const WebhookManager = () => { setIsDialogOpen(true); }; - const handleSave = (data) => { + const handleSave = async (data) => { setStatus('loading'); setMessage(data.id ? 'Updating webhook...' : 'Creating new webhook...'); - // Simulate async operation delay - setTimeout(() => { + try { const dataToSave = { ...data, // Re-encrypt the key before saving to mock persistent storage @@ -260,6 +263,8 @@ export const WebhookManager = () => { updated_at: Date.now(), }; + await saveWebhookCredentials(dataToSave); + setWebhooks(prevHooks => { if (data.id) { // Update existing hook @@ -286,7 +291,12 @@ export const WebhookManager = () => { setIsDialogOpen(false); }, 1000); - }, 500); // Mock network delay + }catch (e) { + toast.error(e.message || "Error saving webhook..."); + setMessage("Error saving webhook...") + }finally { + + } }; const handleDelete = (id) => { @@ -353,9 +363,9 @@ export const WebhookManager = () => {

{message}

) : ( - + {webhooks.length === 0 ? ( -
+

No webhooks configured.

diff --git a/context/GenerationContext.tsx b/context/GenerationContext.tsx index 6299514..113f5c6 100644 --- a/context/GenerationContext.tsx +++ b/context/GenerationContext.tsx @@ -6,7 +6,12 @@ import { toast } from 'sonner'; import { ReadonlyURLSearchParams, useParams, useRouter, useSearchParams } from 'next/navigation'; import { createClient } from '@/utils/supabase/client'; import { useAuth } from '@/context/AuthContext'; -import { getGeneratedContents, getScheduledJobs } from '@/lib/db/content'; +import { + getGeneratedContents, + getScheduledJobs, + getWebhookCredentials, + WebhookCredentials, +} from '@/lib/db/content'; import { SystemPromptOption } from '@/components/Layout/Dashboard/Generate/AISystemConfig'; import { predefinedPrompts } from '@/lib/AI/ai.system.prompt'; import { refinePrompt } from '@/lib/AI/ai.actions'; @@ -118,6 +123,11 @@ interface GenerationContextType { onRefinePrompt: () => Promise; triggerWebhookDispatch: () => Promise; + + webhookCredentials: WebhookCredentials[]; + setWebhookCredentials: (webhooks: WebhookCredentials[]) => void; + isWebhookCredentialsLoading: boolean, + setIsWebhookCredentialsLoading: (prev?: boolean) => void; } // --- 2. Create the Context with Default Values --- @@ -150,11 +160,14 @@ export function ContextProvider({ children }: { children: ReactNode }) { const [isViewingGoal, setIsViewingGoal] = useState(true); const [isDialogOpen, setIsDialogOpen] = useState(false); const [prompt, setPrompt] = useState(''); + const [webhookCredentials, setWebhookCredentials] = useState([]); + const [isWebhookCredentialsLoading, setIsWebhookCredentialsLoading] = useState(false); const [selectedPrompt, setSelectedPrompt] = useState(predefinedPrompts[0]); useEffect(() => { fetchContents(); fetchScheduledJobs(); + fetchWebhookCredentials() }, []); const fetchContents = useCallback(async () => { @@ -189,6 +202,22 @@ export function ContextProvider({ children }: { children: ReactNode }) { } }, [setScheduledJobs]); + const fetchWebhookCredentials = useCallback(async () => { + setIsWebhookCredentialsLoading(true); + try { + const credentials = await getWebhookCredentials(); + if (credentials) { + setWebhookCredentials(credentials); + } else { + setWebhookCredentials([]); + } + } catch (e) { + toast.error(e.message || 'Error fetching webhook credentials'); + } finally { + setIsWebhookCredentialsLoading(false); + } + }, [setWebhookCredentials]); + // The function to call the Next.js API Route const generateContent = async ( prompt: string, @@ -494,7 +523,11 @@ export function ContextProvider({ children }: { children: ReactNode }) { onRefinePrompt, isDialogOpen, setIsDialogOpen, - triggerWebhookDispatch + triggerWebhookDispatch, + webhookCredentials, + setWebhookCredentials, + isWebhookCredentialsLoading, + setIsWebhookCredentialsLoading, }; return {children}; diff --git a/lib/db/content.ts b/lib/db/content.ts index 407dcc9..abe8081 100644 --- a/lib/db/content.ts +++ b/lib/db/content.ts @@ -4,6 +4,16 @@ import { db } from '@/db'; import { contents, userContents } from '@/drizzle/schema'; import { and, desc, eq } from 'drizzle-orm'; import { revalidatePath } from 'next/cache'; +export interface WebhookCredentials { + id: string; + user_id: string; + url: string; + secret_key: string; + trigger_event: string; + is_active: boolean; + created_at: string; + updated_at: string; +} interface ScheduledJob { jobType: string; @@ -442,3 +452,79 @@ export async function saveContentFromSchedules( console.error(`Error saving content version for ID ${contentId} with Drizzle:`, error); } } + + +export async function saveWebhookCredentials(hookData: WebhookCredentials) { + try { + const supabase = await createClient(); + + const { data: userData, error: authError } = await supabase.auth.getUser(); + + if (authError || !userData?.user) { + console.error('Authentication Error:', authError?.message || 'User not logged in.'); + throw new Error('User authentication required to save webhooks.'); + } + + const userId = userData.user.id; + + const dataToSave = { + id: hookData.id ? hookData.id : crypto.randomUUID(), + user_id: userId, + url: hookData.url, + secret_key: hookData.secret_key, // Assuming this is already encrypted (mock or real) + trigger_event: hookData.trigger_event, + is_active: hookData.is_active, + updated_at: new Date().toISOString(), + }; + + const { data, error } = await supabase + .from('api_integrations') + .upsert(dataToSave, { + onConflict: 'user_id, id', + ignoreDuplicates: false, + }) + .select(); + + if (error) { + console.error('Supabase Upsert Error:', error.message); + throw new Error(`Database save failed: ${error.message}`); + } + + console.log('Webhook successfully saved/updated:', data[0]); + return data[0]; + + } catch (error) { + console.error('saveWebhookToSupabase execution failed:', error.message); + throw error; + } +} + +export async function getWebhookCredentials() { + try { + const supabase = await createClient(); + + const { data: userData, error: authError } = await supabase.auth.getUser(); + + if (authError || !userData?.user) { + console.error('Authentication Error:', authError?.message || 'User not logged in.'); + throw new Error('User authentication required to save webhooks.'); + } + + const userId = userData.user.id; + + const { data, error } = await supabase + .from('api_integrations') + .select() + .eq('user_id', userId); + + if (error) { + console.error('Supabase select Error:', error.message); + throw new Error(`Database fetch failed: ${error.message}`); + } + + return data; + }catch (error) { + console.error('Webhook fetch execution failed:', error.message); + throw error; + } +}