Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f179c130f | |||
|
|
64b701287d |
36
api/create_event.php
Normal file
36
api/create_event.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?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']);
|
||||
}
|
||||
29
api/delete_event.php
Normal file
29
api/delete_event.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?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']);
|
||||
}
|
||||
17
api/get_events.php
Normal file
17
api/get_events.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?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()]);
|
||||
}
|
||||
24
api/get_events_for_day.php
Normal file
24
api/get_events_for_day.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?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()]);
|
||||
}
|
||||
21
api/get_messages.php
Normal file
21
api/get_messages.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?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']);
|
||||
}
|
||||
85
api/send_message.php
Normal file
85
api/send_message.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?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']);
|
||||
}
|
||||
37
api/update_event.php
Normal file
37
api/update_event.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?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']);
|
||||
}
|
||||
28
api/update_lead_status.php
Normal file
28
api/update_lead_status.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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']);
|
||||
}
|
||||
47
api/webhook.php
Normal file
47
api/webhook.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
|
||||
273
assets/css/custom.css
Normal file
273
assets/css/custom.css
Normal file
@ -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;
|
||||
}
|
||||
161
assets/js/main.js
Normal file
161
assets/js/main.js
Normal file
@ -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;
|
||||
}
|
||||
});
|
||||
12
auth.php
Normal file
12
auth.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?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;
|
||||
}
|
||||
268
calendar.php
Normal file
268
calendar.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?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>
|
||||
24
db/migrate.php
Normal file
24
db/migrate.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?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");
|
||||
}
|
||||
|
||||
53
db/schema.sql
Normal file
53
db/schema.sql
Normal file
@ -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
|
||||
);
|
||||
278
index.php
278
index.php
@ -1,131 +1,175 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
require_once __DIR__ . '/auth.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
// 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;
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<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;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>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<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 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>
|
||||
<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>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</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>
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
||||
96
leads.php
Normal file
96
leads.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?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>
|
||||
60
login.php
Normal file
60
login.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?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>
|
||||
5
logout.php
Normal file
5
logout.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_destroy();
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
95
register.php
Normal file
95
register.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?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>
|
||||
122
settings.php
Normal file
122
settings.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?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
Normal file
117
users.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?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>
|
||||
Loading…
x
Reference in New Issue
Block a user