diff --git a/api/create_event.php b/api/create_event.php new file mode 100644 index 0000000..d57ff1e --- /dev/null +++ b/api/create_event.php @@ -0,0 +1,36 @@ +prepare("INSERT INTO events (user_id, lead_id, title, start_time, end_time) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$user_id, $lead_id, $title, $start_datetime, $end_datetime]); + + echo json_encode(['success' => true]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); + } + } else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Invalid input']); + } +} else { + http_response_code(405); + echo json_encode(['success' => false, 'message' => 'Method not allowed']); +} diff --git a/api/delete_event.php b/api/delete_event.php new file mode 100644 index 0000000..e337085 --- /dev/null +++ b/api/delete_event.php @@ -0,0 +1,29 @@ +prepare("DELETE FROM events WHERE id = ? AND user_id = ?"); + $stmt->execute([$id, $user_id]); + + echo json_encode(['success' => true]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); + } + } else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Invalid input']); + } +} else { + http_response_code(405); + echo json_encode(['success' => false, 'message' => 'Method not allowed']); +} diff --git a/api/get_events.php b/api/get_events.php new file mode 100644 index 0000000..e393e04 --- /dev/null +++ b/api/get_events.php @@ -0,0 +1,17 @@ +prepare("SELECT * FROM events WHERE user_id = ?"); + $stmt->execute([$user_id]); + $events = $stmt->fetchAll(); + + echo json_encode($events); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/get_events_for_day.php b/api/get_events_for_day.php new file mode 100644 index 0000000..37dc8d1 --- /dev/null +++ b/api/get_events_for_day.php @@ -0,0 +1,24 @@ + false, 'message' => 'Invalid input']); + exit; +} + +try { + $pdo = db(); + $stmt = $pdo->prepare("SELECT events.*, leads.name as lead_name FROM events LEFT JOIN leads ON events.lead_id = leads.id WHERE events.user_id = ? AND DATE(events.start_time) = ? ORDER BY events.start_time ASC"); + $stmt->execute([$user_id, $date]); + $events = $stmt->fetchAll(); + + echo json_encode($events); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/get_messages.php b/api/get_messages.php new file mode 100644 index 0000000..652b695 --- /dev/null +++ b/api/get_messages.php @@ -0,0 +1,21 @@ +prepare("SELECT * FROM messages WHERE lead_id = ? ORDER BY created_at ASC"); + $stmt->execute([$lead_id]); + $messages = $stmt->fetchAll(); + + echo json_encode($messages); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); + } +} else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Invalid input']); +} diff --git a/api/send_message.php b/api/send_message.php new file mode 100644 index 0000000..d53d2e6 --- /dev/null +++ b/api/send_message.php @@ -0,0 +1,85 @@ + $full_prompt); + + // Extract the response from the tool's output + // The exact structure of the response may vary. + $ai_message = $api_response['answer'] ?? 'Sorry, I could not get a response from the AI.'; + + return $ai_message; + */ + + // For now, return a simulated response that includes the prompt. + return "(Simulated AI Response with API Key. Based on prompt: '" . $prompt . "')"; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $data = json_decode(file_get_contents('php://input'), true); + + if (isset($data['lead_id']) && isset($data['message'])) { + $lead_id = $data['lead_id']; + $message = $data['message']; + + try { + $pdo = db(); + + // Save user message + $stmt = $pdo->prepare("INSERT INTO messages (lead_id, sender, message) VALUES (?, 'user', ?)"); + $stmt->execute([$lead_id, $message]); + $user_message_id = $pdo->lastInsertId(); + + // Fetch settings + $stmt = $pdo->query("SELECT * FROM settings"); + $settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); + $apiKey = $settings['api_key'] ?? ''; + $aiPrompt = $settings['ai_prompt'] ?? 'You are a helpful assistant.'; + + // Fetch conversation history + $stmt = $pdo->prepare("SELECT * FROM messages WHERE lead_id = ? ORDER BY created_at ASC"); + $stmt->execute([$lead_id]); + $conversation_history = $stmt->fetchAll(); + + // Get AI response + $ai_message = get_gemini_response($apiKey, $aiPrompt, $conversation_history, $message); + + $stmt = $pdo->prepare("INSERT INTO messages (lead_id, sender, message) VALUES (?, 'ai', ?)"); + $stmt->execute([$lead_id, $ai_message]); + $ai_message_id = $pdo->lastInsertId(); + + // Fetch the created messages to return them + $stmt = $pdo->prepare("SELECT * FROM messages WHERE id IN (?, ?)"); + $stmt->execute([$user_message_id, $ai_message_id]); + $new_messages = $stmt->fetchAll(); + + echo json_encode(['success' => true, 'messages' => $new_messages]); + + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); + } + } else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Invalid input']); + } +} else { + http_response_code(405); + echo json_encode(['success' => false, 'message' => 'Method not allowed']); +} diff --git a/api/update_event.php b/api/update_event.php new file mode 100644 index 0000000..fd789b7 --- /dev/null +++ b/api/update_event.php @@ -0,0 +1,37 @@ +prepare("UPDATE events SET lead_id = ?, title = ?, start_time = ?, end_time = ? WHERE id = ? AND user_id = ?"); + $stmt->execute([$lead_id, $title, $start_datetime, $end_datetime, $id, $user_id]); + + echo json_encode(['success' => true]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); + } + } else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Invalid input']); + } +} else { + http_response_code(405); + echo json_encode(['success' => false, 'message' => 'Method not allowed']); +} diff --git a/api/update_lead_status.php b/api/update_lead_status.php new file mode 100644 index 0000000..08f7aab --- /dev/null +++ b/api/update_lead_status.php @@ -0,0 +1,28 @@ +prepare("UPDATE leads SET status = ? WHERE id = ?"); + $stmt->execute([$new_status, $lead_id]); + + echo json_encode(['success' => true]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); + } + } else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Invalid input']); + } +} else { + http_response_code(405); + echo json_encode(['success' => false, 'message' => 'Method not allowed']); +} diff --git a/api/webhook.php b/api/webhook.php new file mode 100644 index 0000000..b8af506 --- /dev/null +++ b/api/webhook.php @@ -0,0 +1,47 @@ +prepare("SELECT * FROM leads WHERE company = ?"); // Using company to store phone number for simplicity + $stmt->execute([$phone_number]); + $lead = $stmt->fetch(); + + if (!$lead) { + // Create new lead + $stmt = $pdo->prepare("INSERT INTO leads (name, company, status) VALUES (?, ?, 'New')"); + $stmt->execute([$name, $phone_number]); + $lead_id = $pdo->lastInsertId(); + } else { + $lead_id = $lead['id']; + } + + // Save the message + $stmt = $pdo->prepare("INSERT INTO messages (lead_id, sender, message) VALUES (?, 'user', ?)"); + $stmt->execute([$lead_id, $message_body]); + + } catch (PDOException $e) { + // Log errors + file_put_contents('webhook_error_log.txt', $e->getMessage() . "\n", FILE_APPEND); + } +} + diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..d88a4b9 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,273 @@ +/* General */ +body { + background-color: #F3F4F6; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + color: #111827; +} + +.main-container { + display: flex; + min-height: 100vh; +} + +/* Sidebar */ +.sidebar { + width: 260px; + background-color: #FFFFFF; + border-right: 1px solid #E5E7EB; + padding: 24px; + display: flex; + flex-direction: column; +} + +.sidebar .logo { + font-weight: 700; + font-size: 1.5rem; + color: #4F46E5; + margin-bottom: 2rem; +} + +.sidebar .nav-link { + color: #374151; + font-size: 1rem; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + margin-bottom: 0.5rem; + transition: all 0.2s ease-in-out; +} + +.sidebar .nav-link .icon { + margin-right: 0.75rem; + width: 24px; + height: 24px; +} + +.sidebar .nav-link.active, +.sidebar .nav-link:hover { + background-color: #EEF2FF; + color: #4F46E5; +} + +/* Main Content */ +.main-content { + flex-grow: 1; + padding: 32px 56px; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2.5rem; +} + +.header h1 { + font-size: 2rem; + font-weight: 700; +} + +/* Kanban Board */ +.kanban-board { + display: flex; + gap: 2rem; + width: 100%; + overflow-x: auto; + padding-bottom: 1.5rem; +} + +.kanban-column { + flex: 1 0 320px; + background-color: #F9FAFB; + border-radius: 0.75rem; + padding: 1.5rem; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +.kanban-column h2 { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 1px solid #E5E7EB; + display: flex; + align-items: center; +} + +.kanban-column .count { + margin-left: 0.75rem; + background-color: #E5E7EB; + color: #4B5563; + font-size: 0.875rem; + font-weight: 600; + padding: 4px 10px; + border-radius: 9999px; +} + +.kanban-cards { + min-height: 200px; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.kanban-card { + background-color: #FFFFFF; + border-radius: 0.75rem; + padding: 1.5rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + cursor: grab; + border-left: 4px solid transparent; + transition: all 0.2s ease-in-out; +} + +.kanban-card:hover { + transform: translateY(-2px); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.kanban-card:active { + cursor: grabbing; + background-color: #F3F4F6; +} + +.kanban-card.dragging { + opacity: 0.6; + transform: rotate(3deg); +} + +.kanban-card .card-title { + font-weight: 600; + font-size: 1.125rem; + margin-bottom: 0.5rem; +} + +.kanban-card .card-subtitle { + font-size: 0.875rem; + color: #6B7280; + margin-bottom: 1rem; +} + +.kanban-card .card-footer { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.875rem; + color: #9CA3AF; +} + +.kanban-card .tag { + padding: 4px 12px; + border-radius: 9999px; + font-weight: 600; + font-size: 0.75rem; +} + +.tag-prospect { + background-color: #DBEAFE; + color: #2563EB; +} + +.tag-deal { + background-color: #D1FAE5; + color: #059669; +} + +.kanban-column.drag-over { + background-color: #EEF2FF; + border: 2px dashed #C7D2FE; +} + +/* Forms */ +.form-control:focus { + border-color: #4F46E5; + box-shadow: 0 0 0 0.25rem rgba(79, 70, 229, 0.25); +} + +/* Modals */ +.modal-content { + border-radius: 0.75rem; +} + +/* Calendar */ +.event-marker { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #4F46E5; + position: absolute; + top: 5px; + right: 5px; +} + +.jsCalendar-cell { + position: relative; +} + +/* Chat Modal */ +.chat-container { + display: flex; + flex-direction: column; + height: 450px; +} + +.chat-messages { + flex-grow: 1; + overflow-y: auto; + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.message { + padding: 0.75rem 1.25rem; + border-radius: 1.25rem; + max-width: 80%; + line-height: 1.5; +} + +.message p { + margin: 0; +} + +.message .timestamp { + font-size: 0.75rem; + color: #9CA3AF; + margin-top: 0.25rem; + display: block; +} + +.message.received { + background-color: #F3F4F6; + align-self: flex-start; +} + +.message.sent { + background-color: #4F46E5; + color: #FFFFFF; + align-self: flex-end; +} + +.message.sent .timestamp { + color: #E0E7FF; +} + +.chat-input { + display: flex; + padding: 1.5rem; + border-top: 1px solid #E5E7EB; +} + +.chat-input input { + flex-grow: 1; + border-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.chat-input button { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..65f17b8 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,161 @@ +document.addEventListener('DOMContentLoaded', () => { + const cards = document.querySelectorAll('.kanban-card'); + const columns = document.querySelectorAll('.kanban-cards'); + const leadModal = new bootstrap.Modal(document.getElementById('leadModal')); + const chatTab = document.getElementById('chat-tab'); + const chatMessages = document.querySelector('.chat-messages'); + const chatInput = document.querySelector('.chat-input input'); + const chatSendButton = document.querySelector('.chat-input button'); + let currentLeadId = null; + + cards.forEach(card => { + card.addEventListener('dragstart', () => { + card.classList.add('dragging'); + }); + + card.addEventListener('dragend', () => { + card.classList.remove('dragging'); + }); + + card.addEventListener('click', () => { + const name = card.dataset.name; + const company = card.dataset.company; + const value = card.dataset.value; + const status = card.dataset.status; + currentLeadId = card.dataset.id; + + document.getElementById('leadName').textContent = name; + document.getElementById('leadCompany').textContent = company; + document.getElementById('leadValue').textContent = '$' + value; + document.getElementById('leadStatus').textContent = status; + document.getElementById('leadModalLabel').textContent = name; + + leadModal.show(); + }); + }); + + columns.forEach(column => { + column.addEventListener('dragover', e => { + e.preventDefault(); + const afterElement = getDragAfterElement(column, e.clientY); + const draggable = document.querySelector('.dragging'); + if (afterElement == null) { + column.appendChild(draggable); + } else { + column.insertBefore(draggable, afterElement); + } + column.parentElement.classList.add('drag-over'); + }); + + column.addEventListener('dragleave', () => { + column.parentElement.classList.remove('drag-over'); + }); + + column.addEventListener('drop', (e) => { + e.preventDefault(); + const draggable = document.querySelector('.dragging'); + const newStatus = column.dataset.status; + const leadId = draggable.dataset.id; + + // Optimistically update the UI + column.appendChild(draggable); + draggable.dataset.status = newStatus; + column.parentElement.classList.remove('drag-over'); + + // Update the status in the database + fetch('api/update_lead_status.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ id: leadId, status: newStatus }) + }) + .then(response => response.json()) + .then(data => { + if (!data.success) { + console.error('Failed to update lead status'); + } + }) + .catch(error => { + console.error('Error updating lead status:', error); + }); + }); + }); + + chatTab.addEventListener('shown.bs.tab', () => { + if (currentLeadId) { + fetch(`api/get_messages.php?lead_id=${currentLeadId}`) + .then(response => response.json()) + .then(messages => { + renderMessages(messages); + }); + } + }); + + chatSendButton.addEventListener('click', () => { + const message = chatInput.value.trim(); + if (message && currentLeadId) { + sendMessage(currentLeadId, message); + chatInput.value = ''; + } + }); + + chatInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + chatSendButton.click(); + } + }); + + function sendMessage(leadId, message) { + fetch('api/send_message.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ lead_id: leadId, message: message }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + renderMessages(data.messages, true); + } + }); + } + + function renderMessages(messages, append = false) { + if (!append) { + chatMessages.innerHTML = ''; + } + messages.forEach(message => { + const messageEl = document.createElement('div'); + messageEl.classList.add('message', message.sender === 'user' ? 'sent' : 'received'); + + const textEl = document.createElement('p'); + textEl.textContent = message.message; + + const timeEl = document.createElement('span'); + timeEl.classList.add('timestamp'); + const messageTime = new Date(message.created_at); + timeEl.textContent = messageTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + + messageEl.appendChild(textEl); + messageEl.appendChild(timeEl); + chatMessages.appendChild(messageEl); + }); + chatMessages.scrollTop = chatMessages.scrollHeight; + } + + function getDragAfterElement(container, y) { + const draggableElements = [...container.querySelectorAll('.kanban-card:not(.dragging)')]; + + return draggableElements.reduce((closest, child) => { + const box = child.getBoundingClientRect(); + const offset = y - box.top - box.height / 2; + if (offset < 0 && offset > closest.offset) { + return { offset: offset, element: child }; + } else { + return closest; + } + }, { offset: Number.NEGATIVE_INFINITY }).element; + } +}); \ No newline at end of file diff --git a/auth.php b/auth.php new file mode 100644 index 0000000..28fe35c --- /dev/null +++ b/auth.php @@ -0,0 +1,11 @@ +prepare("SELECT id, name FROM leads WHERE status != 'Closed'"); +$stmt->execute(); +$leads = $stmt->fetchAll(); + +?> + + + + + + Calendar + + + + + + +
+ + +
+
+

Calendar

+
+ +
+
+
+
+
+ + +
+
+ + + + + + + + + diff --git a/db/migrate.php b/db/migrate.php new file mode 100644 index 0000000..771406e --- /dev/null +++ b/db/migrate.php @@ -0,0 +1,24 @@ +exec($sql); + echo "Database schema updated successfully.\n"; + + // Seed admin user + $stmt = $pdo->query("SELECT COUNT(*) FROM users WHERE email = 'admin@example.com'"); + $user_exists = $stmt->fetchColumn(); + + if (!$user_exists) { + $password_hash = password_hash('password', PASSWORD_DEFAULT); + $stmt = $pdo->prepare("INSERT INTO users (name, email, password, role) VALUES (?, ?, ?, ?)"); + $stmt->execute(['Admin User', 'admin@example.com', $password_hash, 'admin']); + echo "Admin user seeded successfully.\n"; + } + +} catch (PDOException $e) { + die("Database migration failed: " . $e->getMessage() . "\n"); +} + diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..28cad5f --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,53 @@ +CREATE TABLE IF NOT EXISTS leads ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + company VARCHAR(255), + status VARCHAR(50) NOT NULL DEFAULT 'New', + value DECIMAL(10, 2), + tag VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + role ENUM('admin', 'sdr') NOT NULL DEFAULT 'sdr', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS settings ( + id INT AUTO_INCREMENT PRIMARY KEY, + `key` VARCHAR(255) NOT NULL UNIQUE, + `value` TEXT +); + +CREATE TABLE IF NOT EXISTS messages ( + id INT AUTO_INCREMENT PRIMARY KEY, + lead_id INT NOT NULL, + sender ENUM('user', 'ai') NOT NULL, + message TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (lead_id) REFERENCES leads(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS invitations ( + id INT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL, + token VARCHAR(255) NOT NULL UNIQUE, + expires_at DATETIME NOT NULL, + is_registered BOOLEAN NOT NULL DEFAULT false +); + +CREATE TABLE IF NOT EXISTS events ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + lead_id INT, + title VARCHAR(255) NOT NULL, + start_time DATETIME NOT NULL, + end_time DATETIME NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (lead_id) REFERENCES leads(id) ON DELETE SET NULL +); diff --git a/index.php b/index.php index 6f7ffab..7de6047 100644 --- a/index.php +++ b/index.php @@ -1,131 +1,175 @@ 1, 'name' => 'Alice Johnson', 'company' => 'Innovate Corp', 'status' => 'New', 'value' => 5000, 'tag' => 'prospect'], + ['id' => 2, 'name' => 'Bob Williams', 'company' => 'Solutions Inc.', 'status' => 'New', 'value' => 3000, 'tag' => 'prospect'], + ['id' => 3, 'name' => 'Charlie Brown', 'company' => 'Data Systems', 'status' => 'Contacted', 'value' => 7500, 'tag' => 'deal'], + ['id' => 4, 'name' => 'Diana Miller', 'company' => 'Creative Minds', 'status' => 'Qualified', 'value' => 12000, 'tag' => 'deal'], + ['id' => 5, 'name' => 'Ethan Davis', 'company' => 'Tech Forward', 'status' => 'New', 'value' => 2500, 'tag' => 'prospect'], + ['id' => 6, 'name' => 'Fiona Garcia', 'company' => 'Global Connect', 'status' => 'Contacted', 'value' => 6000, 'tag' => 'deal'], +]; + +$pdo = db(); + +// Check if leads table is empty +$stmt = $pdo->query("SELECT COUNT(*) FROM leads"); +$lead_count = $stmt->fetchColumn(); + +if ($lead_count == 0) { + $stmt = $pdo->prepare("INSERT INTO leads (name, company, status, value, tag) VALUES (?, ?, ?, ?, ?)"); + foreach ($sample_leads as $lead) { + $stmt->execute([$lead['name'], $lead['company'], $lead['status'], $lead['value'], $lead['tag']]); + } +} + +$stmt = $pdo->query("SELECT * FROM leads"); +$leads = $stmt->fetchAll(); + +$columns = ['New', 'Contacted', 'Qualified', 'Closed']; + +function getLeadsByStatus($leads, $status) { + $filtered_leads = []; + foreach ($leads as $lead) { + if ($lead['status'] === $status) { + $filtered_leads[] = $lead; + } + } + return $filtered_leads; +} ?> - + - - - New Style - - - - + + + Lead Dashboard + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ +
+ + +
+
+

Leads Dashboard

+
+ +
+ +
+ +

+ + +

+
+ +
+
+
+ +
+ +
+
+ +
+
+
+ +
- + + + + - + \ No newline at end of file diff --git a/leads.php b/leads.php new file mode 100644 index 0000000..ea40f36 --- /dev/null +++ b/leads.php @@ -0,0 +1,96 @@ +query("SELECT * FROM leads ORDER BY created_at DESC"); +$leads = $stmt->fetchAll(); +?> + + + + + + Leads + + + + + + + + +
+ + +
+
+

Leads

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
NameCompanyValueTagStatus
$ + + + +
+
+
+
+
+ + + + diff --git a/login.php b/login.php new file mode 100644 index 0000000..4a0176f --- /dev/null +++ b/login.php @@ -0,0 +1,60 @@ +prepare("SELECT * FROM users WHERE email = ?"); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + $_SESSION['user_id'] = $user['id']; + $_SESSION['user_role'] = $user['role']; + header('Location: index.php'); + exit; + } else { + $error = 'Invalid email or password'; + } + } +} +?> + + + + + + Login + + + + +
+
+
+

SaaSApp

+ +
+ +
+
+ + +
+
+ + +
+ +
+
+
+
+ + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..37bc5ab --- /dev/null +++ b/logout.php @@ -0,0 +1,5 @@ +prepare("SELECT * FROM invitations WHERE token = ? AND expires_at > NOW() AND is_registered = false"); +$stmt->execute([$token]); +$invitation = $stmt->fetch(); + +if (!$invitation) { + die('Invalid or expired invitation token.'); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name = $_POST['name'] ?? ''; + $password = $_POST['password'] ?? ''; + $password_confirm = $_POST['password_confirm'] ?? ''; + + if ($password !== $password_confirm) { + $error = 'Passwords do not match.'; + } else { + try { + // Create user + $password_hash = password_hash($password, PASSWORD_DEFAULT); + $stmt = $pdo->prepare("INSERT INTO users (name, email, password, role) VALUES (?, ?, ?, 'sdr')"); + $stmt->execute([$name, $invitation['email'], $password_hash]); + + // Mark invitation as registered + $stmt = $pdo->prepare("UPDATE invitations SET is_registered = true WHERE id = ?"); + $stmt->execute([$invitation['id']]); + + $success = 'Registration successful! You can now login.'; + + } catch (PDOException $e) { + if ($e->errorInfo[1] == 1062) { // Duplicate entry + $error = 'An account with this email already exists.'; + } else { + $error = 'Database error: ' . $e->getMessage(); + } + } + } +} + +?> + + + + + + Register + + + +
+
+
+

Create Account

+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+
+ + diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..1ec79e5 --- /dev/null +++ b/settings.php @@ -0,0 +1,122 @@ +prepare("INSERT INTO settings (`key`, `value`) VALUES ('api_key', ?) ON DUPLICATE KEY UPDATE `value` = ?"); + $stmt->execute([$apiKey, $apiKey]); + + // Insert or update Meta URL + $stmt = $pdo->prepare("INSERT INTO settings (`key`, `value`) VALUES ('meta_url', ?) ON DUPLICATE KEY UPDATE `value` = ?"); + $stmt->execute([$metaUrl, $metaUrl]); + + // Insert or update AI Prompt + $stmt = $pdo->prepare("INSERT INTO settings (`key`, `value`) VALUES ('ai_prompt', ?) ON DUPLICATE KEY UPDATE `value` = ?"); + $stmt->execute([$aiPrompt, $aiPrompt]); + + $message = 'Settings saved successfully!'; +} + +// Fetch existing settings +$stmt = $pdo->query("SELECT * FROM settings"); +$settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); + +$apiKey = $settings['api_key'] ?? ''; +$metaUrl = $settings['meta_url'] ?? ''; +$aiPrompt = $settings['ai_prompt'] ?? ''; + +?> + + + + + + Settings + + + + + + + + +
+ + +
+
+

Settings

+
+ +
+
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+ + + + diff --git a/users.php b/users.php new file mode 100644 index 0000000..99132e7 --- /dev/null +++ b/users.php @@ -0,0 +1,117 @@ +prepare("INSERT INTO invitations (email, token, expires_at) VALUES (?, ?, ?)"); + $stmt->execute([$email, $token, $expires_at]); + + $invitation_link = 'http://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/register.php?token=' . $token; + $invite_message = 'Invitation link generated successfully. Please send it to the user.'; + } else { + $invite_message = 'Invalid email address.'; + } +} + +$stmt = $pdo->query("SELECT * FROM users ORDER BY created_at DESC"); +$users = $stmt->fetchAll(); + +?> + + + + + + Users + + + + + + + + +
+ + +
+
+

User Management

+
+ +
+
+
Invite User
+ +
+ + +
+

Invitation Link:

+ +
+ +
+
+ + +
+
+
+
+ +
+
+
Existing Users
+ + + + + + + + + + + + + + + + + + + +
NameEmailRoleRegistered On
+
+
+
+
+ + +