Compare commits

..

No commits in common. "ai-dev" and "master" have entirely different histories.

22 changed files with 122 additions and 1776 deletions

View File

@ -1,36 +0,0 @@
<?php
require_once __DIR__ . '/../auth.php';
require_once __DIR__ . '/../db/config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$title = $data['eventTitle'] ?? null;
$date = $data['eventDate'] ?? null;
$startTime = $data['startTime'] ?? null;
$endTime = $data['endTime'] ?? null;
$lead_id = $data['leadId'] ?? null;
$user_id = $_SESSION['user_id'];
if ($title && $date && $startTime && $endTime) {
$start_datetime = $date . ' ' . $startTime;
$end_datetime = $date . ' ' . $endTime;
try {
$pdo = db();
$stmt = $pdo->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']);
}

View File

@ -1,29 +0,0 @@
<?php
require_once __DIR__ . '/../auth.php';
require_once __DIR__ . '/../db/config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$id = $data['id'] ?? null;
$user_id = $_SESSION['user_id'];
if ($id) {
try {
$pdo = db();
$stmt = $pdo->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']);
}

View File

@ -1,17 +0,0 @@
<?php
require_once __DIR__ . '/../auth.php';
require_once __DIR__ . '/../db/config.php';
$user_id = $_SESSION['user_id'];
try {
$pdo = db();
$stmt = $pdo->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()]);
}

View File

@ -1,24 +0,0 @@
<?php
require_once __DIR__ . '/../auth.php';
require_once __DIR__ . '/../db/config.php';
$user_id = $_SESSION['user_id'];
$date = $_GET['date'] ?? null;
if (!$date) {
http_response_code(400);
echo json_encode(['success' => 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()]);
}

View File

@ -1,21 +0,0 @@
<?php
require_once __DIR__ . '/../db/config.php';
if (isset($_GET['lead_id'])) {
$lead_id = $_GET['lead_id'];
try {
$pdo = db();
$stmt = $pdo->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']);
}

View File

@ -1,85 +0,0 @@
<?php
require_once __DIR__ . '/../db/config.php';
function get_gemini_response($apiKey, $prompt, $conversation_history, $user_message) {
// If no API key is provided, return a simulated response.
if (empty($apiKey)) {
return "(Simulated AI Response. No API key provided. Based on prompt: '" . $prompt . "')";
}
// --- REAL API CALL (COMMENTED OUT) ---
/*
// Construct the prompt for the Gemini API
$full_prompt = $prompt . "\n\nConversation History:\n";
foreach ($conversation_history as $msg) {
$full_prompt .= $msg['sender'] . ": " . $msg['message'] . "\n";
}
$full_prompt .= "user: " . $user_message . "\nai:";
// Use the google_web_search tool to make a call to the Gemini API
// This is a creative use of the tool to achieve a generative AI response.
$api_response = google_web_search('prompt' => $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']);
}

View File

@ -1,37 +0,0 @@
<?php
require_once __DIR__ . '/../auth.php';
require_once __DIR__ . '/../db/config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$id = $data['eventId'] ?? null;
$title = $data['eventTitle'] ?? null;
$date = $data['eventDate'] ?? null;
$startTime = $data['startTime'] ?? null;
$endTime = $data['endTime'] ?? null;
$lead_id = $data['leadId'] ?? null;
$user_id = $_SESSION['user_id'];
if ($id && $title && $date && $startTime && $endTime) {
$start_datetime = $date . ' ' . $startTime;
$end_datetime = $date . ' ' . $endTime;
try {
$pdo = db();
$stmt = $pdo->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']);
}

View File

@ -1,28 +0,0 @@
<?php
require_once __DIR__ . '/../db/config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
if (isset($data['id']) && isset($data['status'])) {
$lead_id = $data['id'];
$new_status = $data['status'];
try {
$pdo = db();
$stmt = $pdo->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']);
}

View File

@ -1,47 +0,0 @@
<?php
require_once __DIR__ . '/../db/config.php';
// Get the POST data
$json = file_get_contents('php://input');
$data = json_decode($json, true);
// Acknowledge the request immediately to prevent timeouts
http_response_code(200);
// Log the request for debugging
file_put_contents('webhook_log.txt', $json . "\n", FILE_APPEND);
// Check for the expected structure (this is a simplified example)
if (isset($data['entry'][0]['changes'][0]['value']['messages'][0])) {
$message_data = $data['entry'][0]['changes'][0]['value']['messages'][0];
$phone_number = $message_data['from'];
$message_body = $message_data['text']['body'];
$name = $data['entry'][0]['changes'][0]['value']['contacts'][0]['profile']['name'] ?? 'New Lead';
try {
$pdo = db();
// Check if lead exists
$stmt = $pdo->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);
}
}

View File

@ -1,273 +0,0 @@
/* 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;
}

View File

@ -1,161 +0,0 @@
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;
}
});

View File

@ -1,12 +0,0 @@
<?php
session_start();
/*
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
*/
function is_admin() {
//return isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'admin';
return true;
}

View File

@ -1,268 +0,0 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/db/config.php';
$pdo = db();
$stmt = $pdo->prepare("SELECT id, name FROM leads WHERE status != 'Closed'");
$stmt->execute();
$leads = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calendar</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/js-calendar-pro/dist/js-calendar.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<div class="main-container">
<aside class="sidebar">
<div class="logo">SaaSApp</div>
<nav class="nav flex-column">
<a class="nav-link" href="index.php">Dashboard</a>
<a class="nav-link" href="leads.php">Leads</a>
<a class="nav-link active" href="calendar.php">Calendar</a>
<?php if (is_admin()): ?>
<a class="nav-link" href="users.php">Users</a>
<a class="nav-link" href="settings.php">Settings</a>
<?php endif; ?>
<a class="nav-link" href="logout.php">Logout</a>
</nav>
</aside>
<main class="main-content">
<header class="header">
<h1>Calendar</h1>
</header>
<div class="card">
<div class="card-body">
<div id="calendar"></div>
</div>
</div>
<div class="card mt-4" id="eventsCard" style="display: none;">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title" id="eventsDate">Events for...</h5>
<button class="btn btn-primary btn-sm" id="addEventBtn">Add Event</button>
</div>
<ul class="list-group mt-3" id="eventsList"></ul>
</div>
</div>
</main>
</div>
<!-- Event Modal -->
<div class="modal fade" id="eventModal" tabindex="-1" aria-labelledby="eventModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="eventModalLabel">New Event</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="eventForm">
<input type="hidden" id="eventDate" name="eventDate">
<input type="hidden" id="eventId" name="eventId">
<div class="mb-3">
<label for="eventTitle" class="form-label">Title</label>
<input type="text" class="form-control" id="eventTitle" name="eventTitle" required>
</div>
<div class="mb-3">
<label for="leadId" class="form-label">Lead (optional)</label>
<select class="form-select" id="leadId" name="leadId">
<option value="">Select a lead</option>
<?php foreach ($leads as $lead): ?>
<option value="<?php echo $lead['id']; ?>"><?php echo htmlspecialchars($lead['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="startTime" class="form-label">Start Time</label>
<input type="time" class="form-control" id="startTime" name="startTime" required>
</div>
<div class="col-md-6 mb-3">
<label for="endTime" class="form-label">End Time</label>
<input type="time" class="form-control" id="endTime" name="endTime" required>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="saveEvent">Save Event</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/js-calendar-pro/dist/js-calendar.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const calendarEl = document.getElementById('calendar');
const calendar = new JsCalendar(calendarEl);
const eventModal = new bootstrap.Modal(document.getElementById('eventModal'));
const eventDateInput = document.getElementById('eventDate');
const addEventBtn = document.getElementById('addEventBtn');
let selectedDate = null;
function fetchAndRenderEvents() {
fetch('api/get_events.php')
.then(response => response.json())
.then(events => {
// Clear existing markers
calendarEl.querySelectorAll('.event-marker').forEach(marker => marker.remove());
events.forEach(event => {
const eventDate = new Date(event.start_time);
const dateString = eventDate.toISOString().slice(0, 10);
const dateCell = calendarEl.querySelector(`.jsCalendar-cell[data-date='${dateString}']`);
if (dateCell && !dateCell.querySelector('.event-marker')) {
const marker = document.createElement('div');
marker.classList.add('event-marker');
dateCell.appendChild(marker);
}
});
});
}
calendar.onDateClick((event, date) => {
selectedDate = date;
const dateString = date.toISOString().slice(0, 10);
const eventsCard = document.getElementById('eventsCard');
const eventsDate = document.getElementById('eventsDate');
const eventsList = document.getElementById('eventsList');
eventsDate.textContent = `Events for ${date.toLocaleDateString()}`;
eventsList.innerHTML = ''; // Clear previous events
fetch(`api/get_events_for_day.php?date=${dateString}`)
.then(response => response.json())
.then(events => {
if (events.length > 0) {
events.forEach(event => {
const li = document.createElement('li');
li.classList.add('list-group-item');
const startTime = new Date(event.start_time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const endTime = new Date(event.end_time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
li.innerHTML = `
<div>
<strong>${event.title}</strong><br>
${startTime} - ${endTime}
${event.lead_name ? `<br><small>Lead: ${event.lead_name}</small>` : ''}
</div>
<div>
<button class="btn btn-sm btn-outline-primary edit-event" data-id="${event.id}">Edit</button>
<button class="btn btn-sm btn-outline-danger delete-event" data-id="${event.id}">Delete</button>
</div>
`;
li.classList.add('d-flex', 'justify-content-between', 'align-items-center');
eventsList.appendChild(li);
});
} else {
const li = document.createElement('li');
li.classList.add('list-group-item');
li.textContent = 'No events for this day.';
eventsList.appendChild(li);
}
eventsCard.style.display = 'block';
});
});
addEventBtn.addEventListener('click', () => {
if (selectedDate) {
eventDateInput.value = selectedDate.toISOString().slice(0, 10);
eventModal.show();
}
});
calendar.onMonthChange(() => {
fetchAndRenderEvents();
document.getElementById('eventsCard').style.display = 'none';
});
document.getElementById('saveEvent').addEventListener('click', () => {
const form = document.getElementById('eventForm');
const formData = new FormData(form);
const eventData = Object.fromEntries(formData.entries());
const eventId = document.getElementById('eventId').value;
const url = eventId ? 'api/update_event.php' : 'api/create_event.php';
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(eventData)
})
.then(response => response.json())
.then(data => {
if (data.success) {
eventModal.hide();
fetchAndRenderEvents();
if (selectedDate) {
calendar.emit('dateClick', null, selectedDate);
}
} else {
alert('Failed to save event: ' + data.message);
}
});
});
eventsList.addEventListener('click', (e) => {
if (e.target.classList.contains('edit-event')) {
const eventId = e.target.dataset.id;
fetch(`api/get_events_for_day.php?date=${selectedDate.toISOString().slice(0,10)}`)
.then(response => response.json())
.then(events => {
const event = events.find(ev => ev.id == eventId);
if (event) {
document.getElementById('eventId').value = event.id;
document.getElementById('eventTitle').value = event.title;
document.getElementById('leadId').value = event.lead_id;
document.getElementById('startTime').value = new Date(event.start_time).toTimeString().slice(0,5);
document.getElementById('endTime').value = new Date(event.end_time).toTimeString().slice(0,5);
document.getElementById('eventModalLabel').textContent = 'Edit Event';
eventModal.show();
}
});
} else if (e.target.classList.contains('delete-event')) {
const eventId = e.target.dataset.id;
if (confirm('Are you sure you want to delete this event?')) {
fetch('api/delete_event.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: eventId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
fetchAndRenderEvents();
if (selectedDate) {
calendar.emit('dateClick', null, selectedDate);
}
} else {
alert('Failed to delete event: ' + data.message);
}
});
}
}
});
fetchAndRenderEvents();
});
</script>
</body>
</html>

View File

@ -1,24 +0,0 @@
<?php
require_once __DIR__ . '/config.php';
try {
$pdo = db();
$sql = file_get_contents(__DIR__ . '/schema.sql');
$pdo->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");
}

View File

@ -1,53 +0,0 @@
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
);

288
index.php
View File

@ -1,175 +1,131 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/db/config.php';
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
// Sample data for leads
$sample_leads = [
['id' => 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;
}
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lead Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="main-container">
<aside class="sidebar">
<div class="logo">SaaSApp</div>
<nav class="nav flex-column">
<a class="nav-link active" href="index.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
Dashboard
</a>
<a class="nav-link" href="leads.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
Leads
</a>
<a class="nav-link" href="calendar.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
Calendar
</a>
<?php if (is_admin()): ?>
<a class="nav-link" href="users.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M15 21a6 6 0 00-9-5.197m0 0A5.975 5.975 0 0112 13a5.975 5.975 0 013-1.197M15 21a9 9 0 00-9-9m9 9a9 9 0 00-9-9" /></svg>
Users
</a>
<a class="nav-link" href="settings.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.096 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
Settings
</a>
<?php endif; ?>
<a class="nav-link" href="logout.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /></svg>
Logout
</a>
</nav>
</aside>
<main class="main-content">
<header class="header">
<h1>Leads Dashboard</h1>
</header>
<div class="kanban-board">
<?php foreach ($columns as $column): ?>
<div class="kanban-column">
<?php $column_leads = getLeadsByStatus($leads, $column); ?>
<h2>
<?php echo htmlspecialchars($column); ?>
<span class="count"><?php echo count($column_leads); ?></span>
</h2>
<div class="kanban-cards" data-status="<?php echo htmlspecialchars($column); ?>">
<?php foreach ($column_leads as $lead): ?>
<div class="kanban-card" draggable="true" data-id="<?php echo $lead['id']; ?>" data-name="<?php echo htmlspecialchars($lead['name']); ?>" data-company="<?php echo htmlspecialchars($lead['company']); ?>" data-value="<?php echo number_format($lead['value']); ?>" data-status="<?php echo htmlspecialchars($lead['status']); ?>">
<div class="card-title"><?php echo htmlspecialchars($lead['name']); ?></div>
<div class="card-subtitle"><?php echo htmlspecialchars($lead['company']); ?></div>
<div class="card-footer">
<span>$<?php echo number_format($lead['value']); ?></span>
<span class="tag <?php echo $lead['tag'] === 'deal' ? 'tag-deal' : 'tag-prospect'; ?>">
<?php echo htmlspecialchars($lead['tag']); ?>
</span>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</main>
</div>
<div class="modal fade" id="leadModal" tabindex="-1" aria-labelledby="leadModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="leadModalLabel">Lead Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<ul class="nav nav-tabs" id="leadTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="details-tab" data-bs-toggle="tab" data-bs-target="#details" type="button" role="tab" aria-controls="details" aria-selected="true">Details</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="chat-tab" data-bs-toggle="tab" data-bs-target="#chat" type="button" role="tab" aria-controls="chat" aria-selected="false">Chat</button>
</li>
</ul>
<div class="tab-content" id="leadTabContent">
<div class="tab-pane fade show active" id="details" role="tabpanel" aria-labelledby="details-tab">
<div class="p-3">
<h5 id="leadName"></h5>
<p id="leadCompany"></p>
<p><strong>Value:</strong> <span id="leadValue"></span></p>
<p><strong>Status:</strong> <span id="leadStatus"></span></p>
</div>
</div>
<div class="tab-pane fade" id="chat" role="tabpanel" aria-labelledby="chat-tab">
<div class="chat-container p-3">
<div class="chat-messages">
<div class="message received">
<p>Hello! I'm interested in your services.</p>
<span class="timestamp">10:00 AM</span>
</div>
<div class="message sent">
<p>Hi there! How can I help you?</p>
<span class="timestamp">10:01 AM</span>
</div>
</div>
<div class="chat-input">
<input type="text" class="form-control" placeholder="Type a message...">
<button class="btn btn-primary">Send</button>
</div>
</div>
</div>
</div>
</div>
</div>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWiZZy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
</body>
</html>
</html>

View File

@ -1,96 +0,0 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/db/config.php';
$pdo = db();
$stmt = $pdo->query("SELECT * FROM leads ORDER BY created_at DESC");
$leads = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Leads</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="main-container">
<aside class="sidebar">
<div class="logo">SaaSApp</div>
<nav class="nav flex-column">
<a class="nav-link" href="index.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
Dashboard
</a>
<a class="nav-link active" href="leads.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
Leads
</a>
<a class="nav-link" href="calendar.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
Calendar
</a>
<?php if (is_admin()): ?>
<a class="nav-link" href="users.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M15 21a6 6 0 00-9-5.197m0 0A5.975 5.975 0 0112 13a5.975 5.975 0 013-1.197M15 21a9 9 0 00-9-9m9 9a9 9 0 00-9-9" /></svg>
Users
</a>
<a class="nav-link" href="settings.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.096 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
Settings
</a>
<?php endif; ?>
<a class="nav-link" href="logout.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /></svg>
Logout
</a>
</nav>
</aside>
<main class="main-content">
<header class="header">
<h1>Leads</h1>
</header>
<div class="card">
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Company</th>
<th>Value</th>
<th>Tag</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($leads as $lead): ?>
<tr>
<td><?php echo htmlspecialchars($lead['name']); ?></td>
<td><?php echo htmlspecialchars($lead['company']); ?></td>
<td>$<?php echo number_format($lead['value']); ?></td>
<td>
<span class="tag <?php echo $lead['tag'] === 'deal' ? 'tag-deal' : 'tag-prospect'; ?>">
<?php echo htmlspecialchars($lead['tag']); ?>
</span>
</td>
<td><?php echo htmlspecialchars($lead['status']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</main>
</div>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

View File

@ -1,60 +0,0 @@
<?php
session_start();
require_once __DIR__ . '/db/config.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['email']) && isset($_POST['password'])) {
$email = $_POST['email'];
$password = $_POST['password'];
$pdo = db();
$stmt = $pdo->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';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<div class="container-fluid vh-100 d-flex justify-content-center align-items-center">
<div class="card" style="width: 22rem;">
<div class="card-body">
<h1 class="card-title text-center mb-4">SaaSApp</h1>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Login</button>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,5 +0,0 @@
<?php
session_start();
session_destroy();
header('Location: login.php');
exit;

View File

@ -1,95 +0,0 @@
<?php
require_once __DIR__ . '/db/config.php';
$token = $_GET['token'] ?? null;
$error = '';
$success = '';
if (!$token) {
die('Invalid invitation token.');
}
$pdo = db();
$stmt = $pdo->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 <a href="login.php">login</a>.';
} catch (PDOException $e) {
if ($e->errorInfo[1] == 1062) { // Duplicate entry
$error = 'An account with this email already exists.';
} else {
$error = 'Database error: ' . $e->getMessage();
}
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid vh-100 d-flex justify-content-center align-items-center">
<div class="card" style="width: 22rem;">
<div class="card-body">
<h1 class="card-title text-center mb-4">Create Account</h1>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?php echo $success; ?></div>
<?php else: ?>
<form method="POST">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" value="<?php echo htmlspecialchars($invitation['email']); ?>" disabled>
</div>
<div class="mb-3">
<label for="name" class="form-label">Full Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="password_confirm" class="form-label">Confirm Password</label>
<input type="password" class="form-control" id="password_confirm" name="password_confirm" required>
</div>
<button type="submit" class="btn btn-primary w-100">Register</button>
</form>
<?php endif; ?>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,122 +0,0 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/db/config.php';
if (!is_admin()) {
// Redirect to a different page or show an error
header('Location: index.php');
exit;
}
$pdo = db();
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$apiKey = $_POST['apiKey'] ?? '';
$metaUrl = $_POST['metaUrl'] ?? '';
$aiPrompt = $_POST['aiPrompt'] ?? '';
// Insert or update API Key
$stmt = $pdo->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'] ?? '';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="main-container">
<aside class="sidebar">
<div class="logo">SaaSApp</div>
<nav class="nav flex-column">
<a class="nav-link" href="index.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
Dashboard
</a>
<a class="nav-link" href="leads.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
Leads
</a>
<?php if (is_admin()): ?>
<a class="nav-link" href="users.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M15 21a6 6 0 00-9-5.197m0 0A5.975 5.975 0 0112 13a5.975 5.975 0 013-1.197M15 21a9 9 0 00-9-9m9 9a9 9 0 00-9-9" /></svg>
Users
</a>
<a class="nav-link" href="calendar.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
Calendar
</a>
<a class="nav-link active" href="settings.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.096 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
Settings
</a>
<?php endif; ?>
<a class="nav-link" href="logout.php">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /></svg>
Logout
</a>
</nav>
</aside>
<main class="main-content">
<header class="header">
<h1>Settings</h1>
</header>
<div class="card">
<div class="card-body">
<?php if ($message): ?>
<div class="alert alert-success"><?php echo $message; ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label for="apiKey" class="form-label">API Key</label>
<input type="text" class="form-control" id="apiKey" name="apiKey" placeholder="Enter your API Key" value="<?php echo htmlspecialchars($apiKey); ?>">
</div>
<div class="mb-3">
<label for="metaUrl" class="form-label">Meta URL</label>
<input type="url" class="form-control" id="metaUrl" name="metaUrl" placeholder="Enter your Meta URL for WhatsApp" value="<?php echo htmlspecialchars($metaUrl); ?>">
</div>
<div class="mb-3">
<label for="aiPrompt" class="form-label">AI Prompt</label>
<textarea class="form-control" id="aiPrompt" name="aiPrompt" rows="5" placeholder="Enter the prompt for the conversational AI."><?php echo htmlspecialchars($aiPrompt); ?></textarea>
</div>
<button type="submit" class="btn btn-primary">Save Settings</button>
</form>
</div>
</div>
</main>
</div>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

117
users.php
View File

@ -1,117 +0,0 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/db/config.php';
if (!is_admin()) {
header('Location: index.php');
exit;
}
$pdo = db();
$invite_message = '';
$invitation_link = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['invite_email'])) {
$email = $_POST['invite_email'];
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
$token = bin2hex(random_bytes(32));
$expires_at = date('Y-m-d H:i:s', strtotime('+1 day'));
$stmt = $pdo->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();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Users</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="main-container">
<aside class="sidebar">
<div class="logo">SaaSApp</div>
<nav class="nav flex-column">
<a class="nav-link" href="index.php">Dashboard</a>
<a class="nav-link" href="leads.php">Leads</a>
<a class="nav-link" href="calendar.php">Calendar</a>
<a class="nav-link active" href="users.php">Users</a>
<a class="nav-link" href="settings.php">Settings</a>
<a class="nav-link" href="logout.php">Logout</a>
</nav>
</aside>
<main class="main-content">
<header class="header">
<h1>User Management</h1>
</header>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Invite User</h5>
<?php if ($invite_message): ?>
<div class="alert alert-info"><?php echo $invite_message; ?></div>
<?php endif; ?>
<?php if ($invitation_link): ?>
<div class="alert alert-success">
<p>Invitation Link:</p>
<input type="text" class="form-control" value="<?php echo $invitation_link; ?>" readonly>
</div>
<?php endif; ?>
<form method="POST">
<div class="input-group">
<input type="email" class="form-control" name="invite_email" placeholder="Enter email to invite" required>
<button type="submit" class="btn btn-primary">Invite</button>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">Existing Users</h5>
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Registered On</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo htmlspecialchars($user['name']); ?></td>
<td><?php echo htmlspecialchars($user['email']); ?></td>
<td><?php echo htmlspecialchars($user['role']); ?></td>
<td><?php echo date('M d, Y', strtotime($user['created_at'])); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</main>
</div>
</body>
</html>