Auto commit: 2025-12-07T05:00:42.823Z
This commit is contained in:
parent
156b441da0
commit
9707f3ddd1
48
add-availability.php
Normal file
48
add-availability.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'coach') {
|
||||||
|
// Redirect non-coaches or non-logged-in users
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
$date = $_POST['date'];
|
||||||
|
$start_time = $_POST['start_time'];
|
||||||
|
$end_time = $_POST['end_time'];
|
||||||
|
|
||||||
|
// Validation (basic)
|
||||||
|
if (empty($date) || empty($start_time) || empty($end_time)) {
|
||||||
|
header('Location: dashboard.php?status=error&message=All+fields+are+required');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$start_datetime = $date . ' ' . $start_time;
|
||||||
|
$end_datetime = $date . ' ' . $end_time;
|
||||||
|
|
||||||
|
// Check if end time is after start time
|
||||||
|
if (strtotime($start_datetime) >= strtotime($end_datetime)) {
|
||||||
|
header('Location: dashboard.php?status=error&message=End+time+must+be+after+start+time');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("INSERT INTO coach_availability (coach_id, start_time, end_time) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$coach_id, $start_datetime, $end_datetime]);
|
||||||
|
|
||||||
|
header('Location: dashboard.php?status=success&message=Availability+added+successfully');
|
||||||
|
exit;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Log error and redirect
|
||||||
|
error_log('Error adding availability: ' . $e->getMessage());
|
||||||
|
header('Location: dashboard.php?status=error&message=Could+not+add+availability');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect if accessed directly without POST
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
exit;
|
||||||
30
add-recurring-availability.php
Normal file
30
add-recurring-availability.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'coach') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
$day_of_week = $_POST['day_of_week'];
|
||||||
|
$start_time = $_POST['start_time'];
|
||||||
|
$end_time = $_POST['end_time'];
|
||||||
|
|
||||||
|
if (isset($day_of_week) && !empty($start_time) && !empty($end_time)) {
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("INSERT INTO coach_recurring_availability (coach_id, day_of_week, start_time, end_time) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$coach_id, $day_of_week, $start_time, $end_time]);
|
||||||
|
header('Location: dashboard.php?status=recurring_added');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
}
|
||||||
|
exit;
|
||||||
74
admin/assign-content.php
Normal file
74
admin/assign-content.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: ../login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
$content_id = $_GET['id'];
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT title FROM content WHERE id = ? AND coach_id = ?");
|
||||||
|
$stmt->execute([$content_id, $coach_id]);
|
||||||
|
$content = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$content) {
|
||||||
|
header('Location: content.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$client_ids = $_POST['client_ids'];
|
||||||
|
if (!empty($client_ids)) {
|
||||||
|
$assigned_count = 0;
|
||||||
|
foreach ($client_ids as $client_id) {
|
||||||
|
// Check if already assigned
|
||||||
|
$check_stmt = db()->prepare("SELECT id FROM client_content WHERE client_id = ? AND content_id = ?");
|
||||||
|
$check_stmt->execute([$client_id, $content_id]);
|
||||||
|
if ($check_stmt->rowCount() == 0) {
|
||||||
|
$assign_stmt = db()->prepare("INSERT INTO client_content (client_id, content_id) VALUES (?, ?)");
|
||||||
|
if ($assign_stmt->execute([$client_id, $content_id])) {
|
||||||
|
$assigned_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$message = "Content assigned to " . $assigned_count . " client(s) successfully!";
|
||||||
|
} else {
|
||||||
|
$error = "Please select at least one client.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch all clients of the coach
|
||||||
|
$clients_stmt = db()->query('SELECT id, name FROM clients ORDER BY name');
|
||||||
|
$clients = $clients_stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Assign Content: <?php echo htmlspecialchars($content['title']); ?></h2>
|
||||||
|
|
||||||
|
<?php if (isset($message)): ?>
|
||||||
|
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="client_ids" class="form-label">Select Clients</label>
|
||||||
|
<select multiple class="form-control" id="client_ids" name="client_ids[]" required>
|
||||||
|
<?php foreach ($clients as $client): ?>
|
||||||
|
<option value="<?php echo $client['id']; ?>"><?php echo htmlspecialchars($client['name']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Assign Content</button>
|
||||||
|
<a href="content.php" class="btn btn-secondary">Back to Content</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
82
admin/assign-package-content.php
Normal file
82
admin/assign-package-content.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: ../login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
$content_id = $_GET['id'];
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT title FROM content WHERE id = ? AND coach_id = ?");
|
||||||
|
$stmt->execute([$content_id, $coach_id]);
|
||||||
|
$content = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$content) {
|
||||||
|
header('Location: content.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$package_id = $_POST['package_id'];
|
||||||
|
$delay_days = $_POST['delay_days'];
|
||||||
|
|
||||||
|
if (!empty($package_id)) {
|
||||||
|
// Check if already assigned
|
||||||
|
$check_stmt = db()->prepare("SELECT id FROM package_content WHERE package_id = ? AND content_id = ?");
|
||||||
|
$check_stmt->execute([$package_id, $content_id]);
|
||||||
|
if ($check_stmt->rowCount() == 0) {
|
||||||
|
$assign_stmt = db()->prepare("INSERT INTO package_content (package_id, content_id, delay_days) VALUES (?, ?, ?)");
|
||||||
|
if ($assign_stmt->execute([$package_id, $content_id, $delay_days])) {
|
||||||
|
$message = "Content assigned to the package successfully!";
|
||||||
|
} else {
|
||||||
|
$error = "Error: Could not assign content to the package.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "This content is already assigned to the selected package.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "Please select a package.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all packages of the coach
|
||||||
|
$packages_stmt = db()->prepare('SELECT id, name FROM service_packages WHERE coach_id = ? ORDER BY name');
|
||||||
|
$packages_stmt->execute([$coach_id]);
|
||||||
|
$packages = $packages_stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Assign Content to Package: <?php echo htmlspecialchars($content['title']); ?></h2>
|
||||||
|
|
||||||
|
<?php if (isset($message)): ?>
|
||||||
|
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="package_id" class="form-label">Select Package</label>
|
||||||
|
<select class="form-control" id="package_id" name="package_id" required>
|
||||||
|
<option value="">-- Select a Package --</option>
|
||||||
|
<?php foreach ($packages as $package): ?>
|
||||||
|
<option value="<?php echo $package['id']; ?>"><?php echo htmlspecialchars($package['name']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="delay_days" class="form-label">Drip Delay (in days)</label>
|
||||||
|
<input type="number" class="form-control" id="delay_days" name="delay_days" value="0" min="0" required>
|
||||||
|
<div class="form-text">Enter 0 for immediate access, or a number of days to delay access after purchase.</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Assign to Package</button>
|
||||||
|
<a href="content.php" class="btn btn-secondary">Back to Content</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
80
admin/assign_survey.php
Normal file
80
admin/assign_survey.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$survey_id = $_GET['id'];
|
||||||
|
|
||||||
|
// Fetch survey details
|
||||||
|
$stmt = db()->prepare('SELECT * FROM surveys WHERE id = ? AND coach_id = ?');
|
||||||
|
$stmt->execute([$survey_id, $coach_id]);
|
||||||
|
$survey = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$survey) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch clients
|
||||||
|
$clients_stmt = db()->query('SELECT id, name FROM clients');
|
||||||
|
$clients = $clients_stmt->fetchAll();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['assign_survey'])) {
|
||||||
|
$client_ids = $_POST['client_ids'] ?? [];
|
||||||
|
if (!empty($client_ids)) {
|
||||||
|
$stmt = db()->prepare('INSERT INTO client_surveys (survey_id, client_id) VALUES (?, ?)');
|
||||||
|
foreach ($client_ids as $client_id) {
|
||||||
|
// Check if already assigned
|
||||||
|
$check_stmt = db()->prepare('SELECT id FROM client_surveys WHERE survey_id = ? AND client_id = ?');
|
||||||
|
$check_stmt->execute([$survey_id, $client_id]);
|
||||||
|
if ($check_stmt->rowCount() === 0) {
|
||||||
|
$stmt->execute([$survey_id, $client_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get already assigned clients
|
||||||
|
$assigned_stmt = db()->prepare('SELECT client_id FROM client_surveys WHERE survey_id = ?');
|
||||||
|
$assigned_stmt->execute([$survey_id]);
|
||||||
|
$assigned_clients = $assigned_stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h3>Assign Survey: <?php echo htmlspecialchars($survey['title']); ?></h3>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Select Clients to Assign Survey</label>
|
||||||
|
<?php foreach ($clients as $client): ?>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="client_ids[]" value="<?php echo $client['id']; ?>" id="client_<?php echo $client['id']; ?>" <?php echo in_array($client['id'], $assigned_clients) ? 'checked disabled' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="client_<?php echo $client['id']; ?>">
|
||||||
|
<?php echo htmlspecialchars($client['name']); ?>
|
||||||
|
<?php echo in_array($client['id'], $assigned_clients) ? '<span class="text-muted">(Already Assigned)</span>' : ''; ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="assign_survey" class="btn btn-primary">Assign Survey</button>
|
||||||
|
<a href="surveys.php" class="btn btn-secondary">Back to Surveys</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
51
admin/broadcast.php
Normal file
51
admin/broadcast.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Broadcast Message</h2>
|
||||||
|
<p>Send a message to all clients.</p>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<form id="broadcast-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="broadcast-message" class="form-label">Message</label>
|
||||||
|
<textarea class="form-control" id="broadcast-message" name="message" rows="5" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-danger">Send Broadcast</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('broadcast-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!confirm('Are you sure you want to send this message to all clients?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = e.target;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch('../api/broadcast_sms.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert('Broadcast sent successfully!');
|
||||||
|
form.reset();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('An unexpected error occurred.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
283
admin/client.php
Normal file
283
admin/client.php
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// Check if client ID is provided in the URL
|
||||||
|
if (!isset($_GET['id']) || empty($_GET['id'])) {
|
||||||
|
echo "<div class='container mt-5'><div class='alert alert-danger'>No client ID specified.</div></div>";
|
||||||
|
require_once '../includes/footer.php';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client_id = $_GET['id'];
|
||||||
|
|
||||||
|
// Handle adding a new note
|
||||||
|
if (isset($_POST['add_note'])) {
|
||||||
|
$note = $_POST['note'];
|
||||||
|
$coach_id = $_SESSION['user_id']; // Assuming the logged-in user is a coach
|
||||||
|
|
||||||
|
$sql = "INSERT INTO client_notes (client_id, coach_id, note) VALUES (?, ?, ?)";
|
||||||
|
$stmt = db()->prepare($sql);
|
||||||
|
$stmt->execute([$client_id, $coach_id, $note]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch client data from the database
|
||||||
|
$sql = "SELECT * FROM clients WHERE id = ?";
|
||||||
|
$stmt = db()->prepare($sql);
|
||||||
|
$stmt->execute([$client_id]);
|
||||||
|
$client = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch notes for the client
|
||||||
|
$notes_sql = "SELECT cn.*, c.name as coach_name FROM client_notes cn JOIN coaches c ON cn.coach_id = c.id WHERE cn.client_id = ? ORDER BY cn.created_at DESC";
|
||||||
|
$notes_stmt = db()->prepare($notes_sql);
|
||||||
|
$notes_stmt->execute([$client_id]);
|
||||||
|
$notes = $notes_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch purchase history for the client
|
||||||
|
$purchases_sql = "SELECT sp.name, sp.price, cp.purchased_at FROM client_packages cp JOIN service_packages sp ON cp.package_id = sp.id WHERE cp.client_id = ? ORDER BY cp.purchased_at DESC";
|
||||||
|
$purchases_stmt = db()->prepare($purchases_sql);
|
||||||
|
$purchases_stmt->execute([$client_id]);
|
||||||
|
$purchases = $purchases_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch signed contracts for the client
|
||||||
|
$contracts_sql = "SELECT c.title, cc.signed_at, cc.docuseal_document_url FROM client_contracts cc JOIN contracts c ON cc.contract_id = c.id WHERE cc.client_id = ? AND cc.status = 'signed' ORDER BY cc.signed_at DESC";
|
||||||
|
$contracts_stmt = db()->prepare($contracts_sql);
|
||||||
|
$contracts_stmt->execute([$client_id]);
|
||||||
|
$contracts = $contracts_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch appointment history for the client
|
||||||
|
$appointments_sql = "SELECT b.start_time, b.end_time, b.status, c.name as coach_name FROM bookings b JOIN coaches c ON b.coach_id = c.id WHERE b.client_id = ? ORDER BY b.start_time DESC";
|
||||||
|
$appointments_stmt = db()->prepare($appointments_sql);
|
||||||
|
$appointments_stmt->execute([$client_id]);
|
||||||
|
$appointments = $appointments_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch SMS logs for the client
|
||||||
|
$sms_logs_sql = "SELECT * FROM sms_logs WHERE client_id = ? ORDER BY created_at DESC";
|
||||||
|
$sms_logs_stmt = db()->prepare($sms_logs_sql);
|
||||||
|
$sms_logs_stmt->execute([$client_id]);
|
||||||
|
$sms_logs = $sms_logs_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// If client not found
|
||||||
|
if (!$client) {
|
||||||
|
echo "<div class='container mt-5'><div class='alert alert-danger'>Client not found.</div></div>";
|
||||||
|
require_once '../includes/footer.php';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Client Details</h2>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h4>Client Information</h4>
|
||||||
|
<p><strong>Name:</strong> <?php echo htmlspecialchars($client['name']); ?></p>
|
||||||
|
<p><strong>Email:</strong> <?php echo htmlspecialchars($client['email']); ?></p>
|
||||||
|
<p><strong>Joined:</strong> <?php echo date("F j, Y", strtotime($client['created_at'])); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#sendSmsModal">
|
||||||
|
Send SMS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h4>Client Activity</h4>
|
||||||
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes" type="button" role="tab" aria-controls="notes" aria-selected="true">Notes</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="sms-logs-tab" data-bs-toggle="tab" data-bs-target="#sms-logs" type="button" role="tab" aria-controls="sms-logs" aria-selected="false">SMS Logs</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="purchases-tab" data-bs-toggle="tab" data-bs-target="#purchases" type="button" role="tab" aria-controls="purchases" aria-selected="false">Purchase History</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="contracts-tab" data-bs-toggle="tab" data-bs-target="#contracts" type="button" role="tab" aria-controls="contracts" aria-selected="false">Signed Contracts</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="surveys-tab" data-bs-toggle="tab" data-bs-target="#surveys" type="button" role="tab" aria-controls="surveys" aria-selected="false">Completed Surveys</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="appointments-tab" data-bs-toggle="tab" data-bs-target="#appointments" type="button" role="tab" aria-controls="appointments" aria-selected="false">Appointment History</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="myTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="notes" role="tabpanel" aria-labelledby="notes-tab">
|
||||||
|
<div class="mt-3">
|
||||||
|
<h5>Add a Note</h5>
|
||||||
|
<form action="" method="post">
|
||||||
|
<div class="mb-3">
|
||||||
|
<textarea class="form-control" name="note" rows="3" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="add_note" class="btn btn-primary">Add Note</button>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
<h5>Notes</h5>
|
||||||
|
<?php if (empty($notes)): ?>
|
||||||
|
<p>No notes found for this client.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<ul class="list-group">
|
||||||
|
<?php foreach ($notes as $note): ?>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<p class="mb-1"><?php echo htmlspecialchars($note['note']); ?></p>
|
||||||
|
<small class="text-muted">By <?php echo htmlspecialchars($note['coach_name']); ?> on <?php echo date("F j, Y, g:i a", strtotime($note['created_at'])); ?></small>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="purchases" role="tabpanel" aria-labelledby="purchases-tab">
|
||||||
|
<div class="mt-3">
|
||||||
|
<?php if (empty($purchases)): ?>
|
||||||
|
<p>No purchase history found for this client.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Package Name</th>
|
||||||
|
<th>Price</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($purchases as $purchase): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($purchase['name']); ?></td>
|
||||||
|
<td>$<?php echo number_format($purchase['price'], 2); ?></td>
|
||||||
|
<td><?php echo date("F j, Y, g:i a", strtotime($purchase['purchased_at'])); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="contracts" role="tabpanel" aria-labelledby="contracts-tab">
|
||||||
|
<div class="mt-3">
|
||||||
|
<?php if (empty($contracts)): ?>
|
||||||
|
<p>No signed contracts found for this client.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Contract Title</th>
|
||||||
|
<th>Date Signed</th>
|
||||||
|
<th>View Contract</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($contracts as $contract): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($contract['title']); ?></td>
|
||||||
|
<td><?php echo date("F j, Y, g:i a", strtotime($contract['signed_at'])); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($contract['docuseal_document_url'])): ?>
|
||||||
|
<a href="<?php echo htmlspecialchars($contract['docuseal_document_url']); ?>" target="_blank" class="btn btn-primary btn-sm">View</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<span>Not available</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="surveys" role="tabpanel" aria-labelledby="surveys-tab">
|
||||||
|
<div class="mt-3">
|
||||||
|
<p>No completed surveys found for this client.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="sms-logs" role="tabpanel" aria-labelledby="sms-logs-tab">
|
||||||
|
<div class="mt-3">
|
||||||
|
<?php if (empty($sms_logs)): ?>
|
||||||
|
<p>No SMS history found for this client.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>From</th>
|
||||||
|
<th>To</th>
|
||||||
|
<th>Message</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($sms_logs as $log): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($log['sender']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($log['recipient']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($log['message']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($log['status']); ?></td>
|
||||||
|
<td><?php echo date("F j, Y, g:i a", strtotime($log['created_at'])); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Send SMS Modal -->
|
||||||
|
<div class="modal fade" id="sendSmsModal" tabindex="-1" aria-labelledby="sendSmsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="sendSmsModalLabel">Send SMS to <?php echo htmlspecialchars($client['name']); ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="send-sms-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sms-message" class="form-label">Message</label>
|
||||||
|
<textarea class="form-control" id="sms-message" name="message" rows="3" required></textarea>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
|
||||||
|
<button type="submit" class="btn btn-primary">Send</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('send-sms-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const form = e.target;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch('../api/send_sms.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert('SMS sent successfully!');
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('An unexpected error occurred.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
62
admin/clients.php
Normal file
62
admin/clients.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// In a real app, you would have a table that links clients to coaches.
|
||||||
|
// For now, we will assume a coach can see all clients.
|
||||||
|
|
||||||
|
$stmt = db()->query('SELECT id, name, email FROM clients ORDER BY name');
|
||||||
|
$clients = $stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Clients</h2>
|
||||||
|
<p>View your clients and their signed contracts.</p>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Signed Contracts</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($clients as $client): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($client['name']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($client['email']); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$contract_stmt = db()->prepare('SELECT cc.id, c.title FROM client_contracts cc JOIN contracts c ON cc.contract_id = c.id WHERE cc.client_id = ?');
|
||||||
|
$contract_stmt->execute([$client['id']]);
|
||||||
|
$signed_contracts = $contract_stmt->fetchAll();
|
||||||
|
?>
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($signed_contracts as $contract): ?>
|
||||||
|
<li><a href="/view-signed-contract.php?id=<?php echo $contract['id']; ?>"><?php echo htmlspecialchars($contract['title']); ?></a></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($signed_contracts)): ?>
|
||||||
|
<li>No signed contracts</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="client.php?id=<?php echo $client['id']; ?>" class="btn btn-primary btn-sm">View</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
90
admin/content.php
Normal file
90
admin/content.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: ../login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_content'])) {
|
||||||
|
$content_id = $_POST['content_id'];
|
||||||
|
|
||||||
|
// Add validation to ensure the coach owns this content
|
||||||
|
$stmt = db()->prepare('SELECT file_path FROM content WHERE id = ? AND coach_id = ?');
|
||||||
|
$stmt->execute([$content_id, $coach_id]);
|
||||||
|
$content = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($content) {
|
||||||
|
// Delete file from server
|
||||||
|
if (file_exists($content['file_path'])) {
|
||||||
|
unlink($content['file_path']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete from database
|
||||||
|
$delete_stmt = db()->prepare('DELETE FROM content WHERE id = ?');
|
||||||
|
$delete_stmt->execute([$content_id]);
|
||||||
|
$message = "Content deleted successfully!";
|
||||||
|
} else {
|
||||||
|
$error = "Error: Content not found or you don't have permission to delete it.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = db()->prepare('SELECT * FROM content WHERE coach_id = ? ORDER BY created_at DESC');
|
||||||
|
$stmt->execute([$coach_id]);
|
||||||
|
$contents = $stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Content</h2>
|
||||||
|
<p>Manage your content library. You can upload PDFs, videos, worksheets, etc.</p>
|
||||||
|
|
||||||
|
<a href="upload-content.php" class="btn btn-primary mb-3">Upload New Content</a>
|
||||||
|
|
||||||
|
<?php if (isset($message)): ?>
|
||||||
|
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>File</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($contents)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">No content uploaded yet.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($contents as $content): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($content['title']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($content['description']); ?></td>
|
||||||
|
<td><a href="../<?php echo htmlspecialchars($content['file_path']); ?>" target="_blank">View Content</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="edit-content.php?id=<?php echo $content['id']; ?>" class="btn btn-secondary btn-sm">Edit</a>
|
||||||
|
<a href="assign-content.php?id=<?php echo $content['id']; ?>" class="btn btn-info btn-sm">Assign to Client</a>
|
||||||
|
<a href="assign-package-content.php?id=<?php echo $content['id']; ?>" class="btn btn-warning btn-sm">Assign to Package</a>
|
||||||
|
<form method="POST" action="" style="display: inline-block;">
|
||||||
|
<input type="hidden" name="content_id" value="<?php echo $content['id']; ?>">
|
||||||
|
<button type="submit" name="delete_content" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this content?');">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
50
admin/contracts.php
Normal file
50
admin/contracts.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// For now, only coaches can manage contracts
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = db()->query('SELECT * FROM contracts ORDER BY created_at DESC');
|
||||||
|
$contracts = $stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Manage Contracts</h2>
|
||||||
|
<p>Create, edit, and delete contract templates for your clients.</p>
|
||||||
|
|
||||||
|
<a href="/admin/edit-contract.php" class="btn btn-primary mb-3">Add New Contract</a>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Created At</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($contracts as $contract): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($contract['title']); ?></td>
|
||||||
|
<td><?php echo date('F j, Y', strtotime($contract['created_at'])); ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/edit-contract.php?id=<?php echo $contract['id']; ?>" class="btn btn-sm btn-secondary">Edit</a>
|
||||||
|
<a href="/admin/delete-contract.php?id=<?php echo $contract['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this contract?');">Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($contracts)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center">No contracts found.</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
52
admin/create_survey.php
Normal file
52
admin/create_survey.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$title = trim($_POST['title']);
|
||||||
|
$description = trim($_POST['description']);
|
||||||
|
|
||||||
|
if (empty($title)) {
|
||||||
|
$error = 'Title is required.';
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare('INSERT INTO surveys (coach_id, title, description) VALUES (?, ?, ?)');
|
||||||
|
if ($stmt->execute([$coach_id, $title, $description])) {
|
||||||
|
$survey_id = db()->lastInsertId();
|
||||||
|
header('Location: edit_survey.php?id=' . $survey_id);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = 'Failed to create survey. Please try again.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Create New Survey</h2>
|
||||||
|
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="title" class="form-label">Survey Title</label>
|
||||||
|
<input type="text" class="form-control" id="title" name="title" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description (Optional)</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Create and Add Questions</button>
|
||||||
|
<a href="surveys.php" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
186
admin/dashboard.php
Normal file
186
admin/dashboard.php
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// Fetch data for the dashboard
|
||||||
|
$clients_sql = "SELECT COUNT(*) as total_clients FROM clients";
|
||||||
|
$clients_result = db()->query($clients_sql);
|
||||||
|
$total_clients = $clients_result->fetch(PDO::FETCH_ASSOC)['total_clients'];
|
||||||
|
|
||||||
|
$coaches_sql = "SELECT COUNT(*) as total_coaches FROM coaches";
|
||||||
|
$coaches_result = db()->query($coaches_sql);
|
||||||
|
$total_coaches = $coaches_result->fetch(PDO::FETCH_ASSOC)['total_coaches'];
|
||||||
|
|
||||||
|
$bookings_sql = "SELECT COUNT(*) as total_bookings FROM bookings";
|
||||||
|
$bookings_result = db()->query($bookings_sql);
|
||||||
|
$total_bookings = $bookings_result->fetch(PDO::FETCH_ASSOC)['total_bookings'];
|
||||||
|
|
||||||
|
$bookings_revenue_sql = "SELECT SUM(price) as total_revenue FROM bookings WHERE status = 'confirmed'";
|
||||||
|
$bookings_revenue_result = db()->query($bookings_revenue_sql);
|
||||||
|
$total_bookings_revenue = $bookings_revenue_result->fetch(PDO::FETCH_ASSOC)['total_revenue'] ?? 0;
|
||||||
|
|
||||||
|
$packages_revenue_sql = "SELECT SUM(p.price) as total_revenue FROM client_packages cp JOIN service_packages p ON cp.package_id = p.id";
|
||||||
|
$packages_revenue_result = db()->query($packages_revenue_sql);
|
||||||
|
$total_packages_revenue = $packages_revenue_result->fetch(PDO::FETCH_ASSOC)['total_revenue'] ?? 0;
|
||||||
|
|
||||||
|
$popular_packages_sql = "SELECT p.name, COUNT(cp.id) as purchase_count FROM client_packages cp JOIN service_packages p ON cp.package_id = p.id GROUP BY p.name ORDER BY purchase_count DESC LIMIT 5";
|
||||||
|
$popular_packages_result = db()->query($popular_packages_sql);
|
||||||
|
$popular_packages = $popular_packages_result->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$client_signups_sql = "SELECT DATE(created_at) as signup_date, COUNT(*) as signup_count FROM clients WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY DATE(created_at) ORDER BY signup_date ASC";
|
||||||
|
$client_signups_result = db()->query($client_signups_sql);
|
||||||
|
$client_signups = $client_signups_result->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$daily_revenue_sql = "SELECT DATE(created_at) as revenue_date, SUM(price) as daily_revenue FROM bookings WHERE status = 'confirmed' AND created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY DATE(created_at) ORDER BY revenue_date ASC";
|
||||||
|
$daily_revenue_result = db()->query($daily_revenue_sql);
|
||||||
|
$daily_revenue = $daily_revenue_result->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
|
||||||
|
// You can add more queries to fetch other business insights
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Business Insights Dashboard</h2>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Total Clients</h5>
|
||||||
|
<p class="card-text"><?php echo $total_clients; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Total Coaches</h5>
|
||||||
|
<p class="card-text"><?php echo $total_coaches; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Total Bookings</h5>
|
||||||
|
<p class="card-text"><?php echo $total_bookings; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Total Revenue (Bookings)</h5>
|
||||||
|
<p class="card-text">$<?php echo number_format($total_bookings_revenue, 2); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Total Revenue (Packages)</h5>
|
||||||
|
<p class="card-text">$<?php echo number_format($total_packages_revenue, 2); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h4>Popular Service Packages</h4>
|
||||||
|
<ul class="list-group">
|
||||||
|
<?php if (!empty($popular_packages)): ?>
|
||||||
|
<?php foreach ($popular_packages as $package): ?>
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<?php echo htmlspecialchars($package['name']); ?>
|
||||||
|
<span class="badge bg-primary rounded-pill"><?php echo $package['purchase_count']; ?></span>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<li class="list-group-item">No packages sold yet.</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h4>Recent Client Signups</h4>
|
||||||
|
<canvas id="clientSignupsChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h4>Daily Revenue (Last 30 Days)</h4>
|
||||||
|
<canvas id="dailyRevenueChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- You can add more cards or charts to display other business insights -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
const signupLabels = <?php echo json_encode(array_column($client_signups, 'signup_date')); ?>;
|
||||||
|
const signupData = <?php echo json_encode(array_column($client_signups, 'signup_count')); ?>;
|
||||||
|
|
||||||
|
const revenueLabels = <?php echo json_encode(array_column($daily_revenue, 'revenue_date')); ?>;
|
||||||
|
const revenueData = <?php echo json_encode(array_column($daily_revenue, 'daily_revenue')); ?>;
|
||||||
|
|
||||||
|
const ctx = document.getElementById('clientSignupsChart').getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: signupLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Client Signups',
|
||||||
|
data: signupData,
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
fill: true,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
maxRotation: 90,
|
||||||
|
minRotation: 45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const रेctx2 = document.getElementById('dailyRevenueChart').getContext('2d');
|
||||||
|
new Chart(रेctx2, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: revenueLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Daily Revenue',
|
||||||
|
data: revenueData,
|
||||||
|
borderColor: 'rgba(255, 99, 132, 1)',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
|
fill: true,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
maxRotation: 90,
|
||||||
|
minRotation: 45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
17
admin/delete-contract.php
Normal file
17
admin/delete-contract.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../db/config.php';
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// For now, only coaches can manage contracts
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['id'])) {
|
||||||
|
$stmt = db()->prepare('DELETE FROM contracts WHERE id = ?');
|
||||||
|
$stmt->execute([$_GET['id']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: /admin/contracts.php');
|
||||||
|
exit;
|
||||||
121
admin/discounts.php
Normal file
121
admin/discounts.php
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// Handle POST requests for creating/updating/deleting discounts
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['add_discount'])) {
|
||||||
|
$code = $_POST['code'];
|
||||||
|
$type = $_POST['type'];
|
||||||
|
$value = $_POST['value'];
|
||||||
|
$start_date = empty($_POST['start_date']) ? null : $_POST['start_date'];
|
||||||
|
$end_date = empty($_POST['end_date']) ? null : $_POST['end_date'];
|
||||||
|
$uses_limit = empty($_POST['uses_limit']) ? null : $_POST['uses_limit'];
|
||||||
|
|
||||||
|
$stmt = db()->prepare('INSERT INTO discounts (code, type, value, start_date, end_date, uses_limit) VALUES (?, ?, ?, ?, ?, ?)');
|
||||||
|
$stmt->execute([$code, $type, $value, $start_date, $end_date, $uses_limit]);
|
||||||
|
} elseif (isset($_POST['update_discount'])) {
|
||||||
|
$id = $_POST['id'];
|
||||||
|
$code = $_POST['code'];
|
||||||
|
$type = $_POST['type'];
|
||||||
|
$value = $_POST['value'];
|
||||||
|
$start_date = empty($_POST['start_date']) ? null : $_POST['start_date'];
|
||||||
|
$end_date = empty($_POST['end_date']) ? null : $_POST['end_date'];
|
||||||
|
$uses_limit = empty($_POST['uses_limit']) ? null : $_POST['uses_limit'];
|
||||||
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
|
||||||
|
$stmt = db()->prepare('UPDATE discounts SET code = ?, type = ?, value = ?, start_date = ?, end_date = ?, uses_limit = ?, is_active = ? WHERE id = ?');
|
||||||
|
$stmt->execute([$code, $type, $value, $start_date, $end_date, $uses_limit, $is_active, $id]);
|
||||||
|
} elseif (isset($_POST['delete_discount'])) {
|
||||||
|
$id = $_POST['id'];
|
||||||
|
$stmt = db()->prepare('DELETE FROM discounts WHERE id = ?');
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
}
|
||||||
|
header('Location: discounts.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all discounts
|
||||||
|
$stmt = db()->query('SELECT * FROM discounts ORDER BY created_at DESC');
|
||||||
|
$discounts = $stmt->fetchAll();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-center my-4">Manage Discounts</h1>
|
||||||
|
|
||||||
|
<!-- Add Discount Form -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">Add New Discount</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="code">Discount Code</label>
|
||||||
|
<input type="text" class="form-control" id="code" name="code" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="type">Type</label>
|
||||||
|
<select class="form-control" id="type" name="type">
|
||||||
|
<option value="percentage">Percentage</option>
|
||||||
|
<option value="fixed">Fixed Amount</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="value">Value</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="value" name="value" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start_date">Start Date (Optional)</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="start_date" name="start_date">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="end_date">End Date (Optional)</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="end_date" name="end_date">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="uses_limit">Usage Limit (Optional)</label>
|
||||||
|
<input type="number" class="form-control" id="uses_limit" name="uses_limit">
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="add_discount" class="btn btn-primary">Add Discount</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Discounts Table -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Existing Discounts</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Value</th>
|
||||||
|
<th>Used/Limit</th>
|
||||||
|
<th>Active</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($discounts as $discount): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= htmlspecialchars($discount['code']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($discount['type']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($discount['value']) ?></td>
|
||||||
|
<td><?= $discount['times_used'] ?> / <?= $discount['uses_limit'] ?? '∞' ?></td>
|
||||||
|
<td><?= $discount['is_active'] ? 'Yes' : 'No' ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="edit-discount.php?id=<?= $discount['id'] ?>" class="btn btn-sm btn-info">Edit</a>
|
||||||
|
<form method="POST" class="d-inline">
|
||||||
|
<input type="hidden" name="id" value="<?= $discount['id'] ?>">
|
||||||
|
<button type="submit" name="delete_discount" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure?')">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
56
admin/edit-content.php
Normal file
56
admin/edit-content.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: ../login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
$content_id = $_GET['id'];
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT * FROM content WHERE id = ? AND coach_id = ?");
|
||||||
|
$stmt->execute([$content_id, $coach_id]);
|
||||||
|
$content = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$content) {
|
||||||
|
header('Location: content.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$title = $_POST['title'];
|
||||||
|
$description = $_POST['description'];
|
||||||
|
|
||||||
|
$update_stmt = db()->prepare("UPDATE content SET title = ?, description = ? WHERE id = ?");
|
||||||
|
if ($update_stmt->execute([$title, $description, $content_id])) {
|
||||||
|
header("Location: content.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = "Error: Could not update content.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Edit Content</h2>
|
||||||
|
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="title" class="form-label">Title</label>
|
||||||
|
<input type="text" class="form-control" id="title" name="title" value="<?php echo htmlspecialchars($content['title']); ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="3"><?php echo htmlspecialchars($content['description']); ?></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
69
admin/edit-contract.php
Normal file
69
admin/edit-contract.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// For now, only coaches can manage contracts
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contract = ['id' => '', 'title' => '', 'content' => '', 'role' => 'Client'];
|
||||||
|
$is_edit = false;
|
||||||
|
|
||||||
|
if (isset($_GET['id'])) {
|
||||||
|
$is_edit = true;
|
||||||
|
$stmt = db()->prepare('SELECT * FROM contracts WHERE id = ?');
|
||||||
|
$stmt->execute([$_GET['id']]);
|
||||||
|
$contract = $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$title = $_POST['title'];
|
||||||
|
$content = $_POST['content'];
|
||||||
|
$role = $_POST['role'];
|
||||||
|
$contract_id = $_POST['id'];
|
||||||
|
|
||||||
|
if ($contract_id) {
|
||||||
|
// Update existing contract
|
||||||
|
$stmt = db()->prepare('UPDATE contracts SET title = ?, content = ?, role = ? WHERE id = ?');
|
||||||
|
$stmt->execute([$title, $content, $role, $contract_id]);
|
||||||
|
} else {
|
||||||
|
// Create new contract
|
||||||
|
$stmt = db()->prepare('INSERT INTO contracts (title, content, role) VALUES (?, ?, ?)');
|
||||||
|
$stmt->execute([$title, $content, $role]);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: /admin/contracts.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2><?php echo $is_edit ? 'Edit Contract' : 'Add New Contract'; ?></h2>
|
||||||
|
|
||||||
|
<form action="" method="POST">
|
||||||
|
<input type="hidden" name="id" value="<?php echo htmlspecialchars($contract['id']); ?>">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="title" class="form-label">Title</label>
|
||||||
|
<input type="text" class="form-control" id="title" name="title" value="<?php echo htmlspecialchars($contract['title']); ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="role" class="form-label">Role</label>
|
||||||
|
<input type="text" class="form-control" id="role" name="role" value="<?php echo htmlspecialchars($contract['role']); ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="content" class="form-label">Content</label>
|
||||||
|
<textarea class="form-control" id="content" name="content" rows="10" required><?php echo htmlspecialchars($contract['content']); ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary"><?php echo $is_edit ? 'Save Changes' : 'Create Contract'; ?></button>
|
||||||
|
<a href="/admin/contracts.php" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
82
admin/edit-discount.php
Normal file
82
admin/edit-discount.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: discounts.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $_GET['id'];
|
||||||
|
$stmt = db()->prepare('SELECT * FROM discounts WHERE id = ?');
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$discount = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$discount) {
|
||||||
|
header('Location: discounts.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$code = $_POST['code'];
|
||||||
|
$type = $_POST['type'];
|
||||||
|
$value = $_POST['value'];
|
||||||
|
$start_date = empty($_POST['start_date']) ? null : $_POST['start_date'];
|
||||||
|
$end_date = empty($_POST['end_date']) ? null : $_POST['end_date'];
|
||||||
|
$uses_limit = empty($_POST['uses_limit']) ? null : $_POST['uses_limit'];
|
||||||
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
|
||||||
|
$stmt = db()->prepare('UPDATE discounts SET code = ?, type = ?, value = ?, start_date = ?, end_date = ?, uses_limit = ?, is_active = ? WHERE id = ?');
|
||||||
|
$stmt->execute([$code, $type, $value, $start_date, $end_date, $uses_limit, $is_active, $id]);
|
||||||
|
|
||||||
|
header('Location: discounts.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-center my-4">Edit Discount</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Edit Discount: <?= htmlspecialchars($discount['code']) ?></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="code">Discount Code</label>
|
||||||
|
<input type="text" class="form-control" id="code" name="code" value="<?= htmlspecialchars($discount['code']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="type">Type</label>
|
||||||
|
<select class="form-control" id="type" name="type">
|
||||||
|
<option value="percentage" <?= $discount['type'] == 'percentage' ? 'selected' : '' ?>>Percentage</option>
|
||||||
|
<option value="fixed" <?= $discount['type'] == 'fixed' ? 'selected' : '' ?>>Fixed Amount</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="value">Value</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="value" name="value" value="<?= htmlspecialchars($discount['value']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start_date">Start Date (Optional)</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="start_date" name="start_date" value="<?= $discount['start_date'] ? date('Y-m-d\TH:i', strtotime($discount['start_date'])) : '' ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="end_date">End Date (Optional)</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="end_date" name="end_date" value="<?= $discount['end_date'] ? date('Y-m-d\TH:i', strtotime($discount['end_date'])) : '' ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="uses_limit">Usage Limit (Optional)</label>
|
||||||
|
<input type="number" class="form-control" id="uses_limit" name="uses_limit" value="<?= htmlspecialchars($discount['uses_limit']) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="is_active" name="is_active" value="1" <?= $discount['is_active'] ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label" for="is_active">Active</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Update Discount</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
134
admin/edit-package.php
Normal file
134
admin/edit-package.php
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: manage-packages.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $_GET['id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = $_POST['id'];
|
||||||
|
$name = $_POST['name'];
|
||||||
|
$coach_id = $_POST['coach_id'];
|
||||||
|
$description = $_POST['description'];
|
||||||
|
$price = $_POST['price'];
|
||||||
|
$payment_type = $_POST['payment_type'];
|
||||||
|
$deposit_amount = $payment_type === 'payment_plan' ? $_POST['deposit_amount'] : null;
|
||||||
|
$installments = $payment_type === 'payment_plan' ? $_POST['installments'] : null;
|
||||||
|
$installment_interval = $payment_type === 'payment_plan' ? $_POST['installment_interval'] : null;
|
||||||
|
$pay_in_full_discount_percentage = $_POST['pay_in_full_discount_percentage'];
|
||||||
|
|
||||||
|
$stmt = db()->prepare(
|
||||||
|
'UPDATE service_packages SET '
|
||||||
|
. 'name = ?, coach_id = ?, description = ?, price = ?, payment_type = ?, '
|
||||||
|
. 'deposit_amount = ?, installments = ?, installment_interval = ?, pay_in_full_discount_percentage = ? '
|
||||||
|
. 'WHERE id = ?'
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
$name, $coach_id, $description, $price, $payment_type,
|
||||||
|
$deposit_amount, $installments, $installment_interval, $pay_in_full_discount_percentage,
|
||||||
|
$id
|
||||||
|
]);
|
||||||
|
|
||||||
|
header('Location: manage-packages.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = db()->prepare('SELECT * FROM service_packages WHERE id = ?');
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$package = $stmt->fetch();
|
||||||
|
|
||||||
|
$coaches = db()->query('SELECT * FROM coaches')->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-center my-4">Edit Package</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Edit Package: <?= htmlspecialchars($package['name']) ?></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="id" value="<?= $package['id'] ?>">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Package Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name" value="<?= htmlspecialchars($package['name']) ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="coach_id">Coach</label>
|
||||||
|
<select class="form-control" id="coach_id" name="coach_id" required>
|
||||||
|
<?php foreach ($coaches as $coach): ?>
|
||||||
|
<option value="<?= $coach['id'] ?>" <?= $package['coach_id'] == $coach['id'] ? 'selected' : '' ?>><?= htmlspecialchars($coach['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description"><?= htmlspecialchars($package['description']) ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="price">Price</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="price" name="price" value="<?= htmlspecialchars($package['price']) ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="payment_type">Payment Type</label>
|
||||||
|
<select class="form-control" id="payment_type" name="payment_type">
|
||||||
|
<option value="one_time" <?= $package['payment_type'] == 'one_time' ? 'selected' : '' ?>>One Time</option>
|
||||||
|
<option value="subscription" <?= $package['payment_type'] == 'subscription' ? 'selected' : '' ?>>Subscription</option>
|
||||||
|
<option value="payment_plan" <?= $package['payment_type'] == 'payment_plan' ? 'selected' : '' ?>>Payment Plan</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="payment_plan_fields" style="display: none;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="deposit_amount">Deposit Amount (Optional)</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="deposit_amount" name="deposit_amount" value="<?= htmlspecialchars($package['deposit_amount']) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="installments">Number of Installments</label>
|
||||||
|
<input type="number" class="form-control" id="installments" name="installments" value="<?= htmlspecialchars($package['installments']) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="installment_interval">Installment Interval</label>
|
||||||
|
<select class="form-control" id="installment_interval" name="installment_interval">
|
||||||
|
<option value="day" <?= $package['installment_interval'] == 'day' ? 'selected' : '' ?>>Day</option>
|
||||||
|
<option value="week" <?= $package['installment_interval'] == 'week' ? 'selected' : '' ?>>Week</option>
|
||||||
|
<option value="month" <?= $package['installment_interval'] == 'month' ? 'selected' : '' ?>>Month</option>
|
||||||
|
<option value="year" <?= $package['installment_interval'] == 'year' ? 'selected' : '' ?>>Year</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pay_in_full_discount_percentage">Pay in Full Discount (%) (Optional)</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="pay_in_full_discount_percentage" name="pay_in_full_discount_percentage" value="<?= htmlspecialchars($package['pay_in_full_discount_percentage']) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" name="update_package" class="btn btn-primary mt-3">Update Package</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('payment_type').addEventListener('change', function() {
|
||||||
|
const payment_plan_fields = document.getElementById('payment_plan_fields');
|
||||||
|
if (this.value === 'payment_plan') {
|
||||||
|
payment_plan_fields.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
payment_plan_fields.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Trigger change on page load
|
||||||
|
document.getElementById('payment_type').dispatchEvent(new Event('change'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
93
admin/edit_question_options.php
Normal file
93
admin/edit_question_options.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!isset($_GET['question_id'])) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$question_id = $_GET['question_id'];
|
||||||
|
|
||||||
|
// Fetch question details to ensure it belongs to the coach
|
||||||
|
$stmt = db()->prepare('SELECT q.*, s.coach_id, s.id as survey_id FROM survey_questions q JOIN surveys s ON q.survey_id = s.id WHERE q.id = ? AND s.coach_id = ?');
|
||||||
|
$stmt->execute([$question_id, $coach_id]);
|
||||||
|
$question = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$question) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle option logic (add/delete)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['add_option'])) {
|
||||||
|
$option_text = trim($_POST['option_text']);
|
||||||
|
if (!empty($option_text)) {
|
||||||
|
$stmt = db()->prepare('INSERT INTO survey_question_options (question_id, option_text) VALUES (?, ?)');
|
||||||
|
$stmt->execute([$question_id, $option_text]);
|
||||||
|
}
|
||||||
|
} elseif (isset($_POST['delete_option'])) {
|
||||||
|
$option_id = $_POST['option_id'];
|
||||||
|
$stmt = db()->prepare('DELETE FROM survey_question_options WHERE id = ? AND question_id = ?');
|
||||||
|
$stmt->execute([$option_id, $question_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch options
|
||||||
|
$options_stmt = db()->prepare('SELECT * FROM survey_question_options WHERE question_id = ?');
|
||||||
|
$options_stmt->execute([$question_id]);
|
||||||
|
$options = $options_stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h3>Manage Options for: <?php echo htmlspecialchars($question['question']); ?></h3>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">Add New Option</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="option_text" class="form-label">Option Text</label>
|
||||||
|
<input type="text" class="form-control" id="option_text" name="option_text" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="add_option" class="btn btn-primary">Add Option</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">Question Options</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($options)): ?>
|
||||||
|
<p>No options have been added for this question yet.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<ul class="list-group">
|
||||||
|
<?php foreach ($options as $option): ?>
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<?php echo htmlspecialchars($option['option_text']); ?>
|
||||||
|
<form method="POST" style="display: inline;">
|
||||||
|
<input type="hidden" name="option_id" value="<?php echo $option['id']; ?>">
|
||||||
|
<button type="submit" name="delete_option" class="btn btn-danger btn-sm">Delete</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<a href="edit_survey.php?id=<?php echo $question['survey_id']; ?>" class="btn btn-secondary">Back to Survey</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
110
admin/edit_survey.php
Normal file
110
admin/edit_survey.php
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$survey_id = $_GET['id'];
|
||||||
|
|
||||||
|
// Fetch survey details
|
||||||
|
$stmt = db()->prepare('SELECT * FROM surveys WHERE id = ? AND coach_id = ?');
|
||||||
|
$stmt->execute([$survey_id, $coach_id]);
|
||||||
|
$survey = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$survey) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle question logic (add/delete)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['add_question'])) {
|
||||||
|
$question = trim($_POST['question']);
|
||||||
|
$type = $_POST['type'];
|
||||||
|
|
||||||
|
if (!empty($question)) {
|
||||||
|
$stmt = db()->prepare('INSERT INTO survey_questions (survey_id, question, type) VALUES (?, ?, ?)');
|
||||||
|
$stmt->execute([$survey_id, $question, $type]);
|
||||||
|
}
|
||||||
|
} elseif (isset($_POST['delete_question'])) {
|
||||||
|
$question_id = $_POST['question_id'];
|
||||||
|
$stmt = db()->prepare('DELETE FROM survey_questions WHERE id = ? AND survey_id = ?');
|
||||||
|
$stmt->execute([$question_id, $survey_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch questions
|
||||||
|
$questions_stmt = db()->prepare('SELECT * FROM survey_questions WHERE survey_id = ? ORDER BY id');
|
||||||
|
$questions_stmt->execute([$survey_id]);
|
||||||
|
$questions = $questions_stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h3>Edit Survey: <?php echo htmlspecialchars($survey['title']); ?></h3>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">Add New Question</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="question" class="form-label">Question Text</label>
|
||||||
|
<input type="text" class="form-control" id="question" name="question" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="type" class="form-label">Question Type</label>
|
||||||
|
<select class="form-select" id="type" name="type">
|
||||||
|
<option value="text">Text (Single Line)</option>
|
||||||
|
<option value="textarea">Textarea (Multi-line)</option>
|
||||||
|
<option value="select">Dropdown</option>
|
||||||
|
<option value="radio">Radio Buttons</option>
|
||||||
|
<option value="checkbox">Checkboxes</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="add_question" class="btn btn-primary">Add Question</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">Survey Questions</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($questions)): ?>
|
||||||
|
<p>No questions have been added to this survey yet.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<ul class="list-group">
|
||||||
|
<?php foreach ($questions as $question): ?>
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<strong><?php echo htmlspecialchars($question['question']); ?></strong>
|
||||||
|
<small class="text-muted">(<?php echo $question['type']; ?>)</small>
|
||||||
|
<?php if (in_array($question['type'], ['select', 'radio', 'checkbox'])): ?>
|
||||||
|
<a href="edit_question_options.php?question_id=<?php echo $question['id']; ?>" class="btn btn-secondary btn-sm ms-2">Manage Options</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<form method="POST" style="display: inline;">
|
||||||
|
<input type="hidden" name="question_id" value="<?php echo $question['id']; ?>">
|
||||||
|
<button type="submit" name="delete_question" class="btn btn-danger btn-sm">Delete</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<a href="surveys.php" class="btn btn-secondary">Back to Surveys</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
97
admin/manage-packages.php
Normal file
97
admin/manage-packages.php
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// Handle POST requests for creating/deleting packages
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['add_package'])) {
|
||||||
|
// Simplified for brevity, will be expanded in edit-package.php
|
||||||
|
$name = $_POST['name'];
|
||||||
|
$price = $_POST['price'];
|
||||||
|
$coach_id = $_POST['coach_id'];
|
||||||
|
|
||||||
|
$stmt = db()->prepare('INSERT INTO service_packages (name, price, coach_id) VALUES (?, ?, ?)');
|
||||||
|
$stmt->execute([$name, $price, $coach_id]);
|
||||||
|
header('Location: manage-packages.php');
|
||||||
|
exit;
|
||||||
|
} elseif (isset($_POST['delete_package'])) {
|
||||||
|
$id = $_POST['id'];
|
||||||
|
$stmt = db()->prepare('DELETE FROM service_packages WHERE id = ?');
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
header('Location: manage-packages.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all packages
|
||||||
|
$packages = db()->query('SELECT sp.*, c.name as coach_name FROM service_packages sp JOIN coaches c ON sp.coach_id = c.id ORDER BY sp.created_at DESC')->fetchAll();
|
||||||
|
$coaches = db()->query('SELECT * FROM coaches')->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-center my-4">Manage Service Packages</h1>
|
||||||
|
|
||||||
|
<!-- Add Package Form -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">Add New Package</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Package Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="price">Price</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="price" name="price" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="coach_id">Coach</label>
|
||||||
|
<select class="form-control" id="coach_id" name="coach_id" required>
|
||||||
|
<?php foreach ($coaches as $coach): ?>
|
||||||
|
<option value="<?= $coach['id'] ?>"><?= htmlspecialchars($coach['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="add_package" class="btn btn-primary">Add Package</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Packages Table -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Existing Packages</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Coach</th>
|
||||||
|
<th>Price</th>
|
||||||
|
<th>Payment Type</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($packages as $package): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= htmlspecialchars($package['name']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($package['coach_name']) ?></td>
|
||||||
|
<td>$<?= htmlspecialchars($package['price']) ?></td>
|
||||||
|
<td><?= htmlspecialchars(ucfirst(str_replace('_', ' ', $package['payment_type']))) ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="edit-package.php?id=<?= $package['id'] ?>" class="btn btn-sm btn-info">Edit</a>
|
||||||
|
<form method="POST" class="d-inline">
|
||||||
|
<input type="hidden" name="id" value="<?= $package['id'] ?>">
|
||||||
|
<button type="submit" name="delete_package" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure?')">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
123
admin/settings.php
Normal file
123
admin/settings.php
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// Placeholder for admin check
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'coach') { // Should be 'admin' in a real app
|
||||||
|
// Redirect non-admins to the homepage
|
||||||
|
header('Location: /index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch settings
|
||||||
|
$settings_stmt = db()->query('SELECT * FROM settings');
|
||||||
|
$settings = $settings_stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
|
$coach_mode = $settings['coach_mode'] ?? 'multi';
|
||||||
|
$single_coach_id = $settings['single_coach_id'] ?? null;
|
||||||
|
|
||||||
|
// Fetch all coaches for the dropdown
|
||||||
|
$coaches_stmt = db()->query('SELECT id, name FROM coaches');
|
||||||
|
$coaches = $coaches_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$success_message = '';
|
||||||
|
$error_message = '';
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$new_coach_mode = $_POST['coach_mode'] ?? 'multi';
|
||||||
|
$new_single_coach_id = $_POST['single_coach_id'] ?? null;
|
||||||
|
|
||||||
|
if ($new_coach_mode === 'single' && empty($new_single_coach_id)) {
|
||||||
|
$error_message = 'Please select a coach for single coach mode.';
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
db()->beginTransaction();
|
||||||
|
$update_mode_stmt = db()->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'coach_mode'");
|
||||||
|
$update_mode_stmt->execute([$new_coach_mode]);
|
||||||
|
|
||||||
|
$update_id_stmt = db()->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'single_coach_id'");
|
||||||
|
$update_id_stmt->execute([$new_single_coach_id]);
|
||||||
|
db()->commit();
|
||||||
|
$success_message = 'Settings updated successfully!';
|
||||||
|
// Refresh settings after update
|
||||||
|
$coach_mode = $new_coach_mode;
|
||||||
|
$single_coach_id = $new_single_coach_id;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
db()->rollBack();
|
||||||
|
$error_message = 'Failed to update settings: ' . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Admin Settings</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-100">
|
||||||
|
<div class="container mx-auto mt-10 p-8 bg-white rounded-lg shadow-md">
|
||||||
|
<h1 class="text-3xl font-bold mb-6">Admin Settings</h1>
|
||||||
|
|
||||||
|
<?php if ($success_message): ?>
|
||||||
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
|
||||||
|
<span class="block sm:inline"><?= $success_message ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($error_message): ?>
|
||||||
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert">
|
||||||
|
<span class="block sm:inline"><?= $error_message ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-6">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">Coach Mode</h2>
|
||||||
|
<p class="text-gray-600 mb-4">Switch between featuring multiple coaches or a single business owner.</p>
|
||||||
|
<div class="flex items-center space-x-6">
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="radio" name="coach_mode" value="multi" <?= $coach_mode === 'multi' ? 'checked' : '' ?> class="form-radio h-5 w-5 text-blue-600">
|
||||||
|
<span class="ml-2 text-gray-700">Multi-Coach Mode</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="radio" name="coach_mode" value="single" <?= $coach_mode === 'single' ? 'checked' : '' ?> class="form-radio h-5 w-5 text-blue-600">
|
||||||
|
<span class="ml-2 text-gray-700">Single-Coach Mode</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="single-coach-settings" class="mb-6 <?= $coach_mode === 'multi' ? 'hidden' : '' ?>">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">Single Coach Settings</h2>
|
||||||
|
<label for="single_coach_id" class="block text-gray-700 mb-2">Select the coach to feature:</label>
|
||||||
|
<select name="single_coach_id" id="single_coach_id" class="block w-full p-2 border border-gray-300 rounded-md">
|
||||||
|
<option value="">-- Select a Coach --</option>
|
||||||
|
<?php foreach ($coaches as $coach): ?>
|
||||||
|
<option value="<?= $coach['id'] ?>" <?= (int)$single_coach_id === $coach['id'] ? 'selected' : '' ?>><?= htmlspecialchars($coach['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="bg-blue-500 text-white font-bold py-2 px-6 rounded hover:bg-blue-600 transition duration-300">Save Settings</button>
|
||||||
|
<a href="/dashboard.php" class="text-gray-600 hover:text-blue-500 ml-4">Back to Dashboard</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const coachModeRadios = document.querySelectorAll('input[name="coach_mode"]');
|
||||||
|
const singleCoachSettings = document.getElementById('single-coach-settings');
|
||||||
|
|
||||||
|
coachModeRadios.forEach(radio => {
|
||||||
|
radio.addEventListener('change', () => {
|
||||||
|
if (radio.value === 'single') {
|
||||||
|
singleCoachSettings.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
singleCoachSettings.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
46
admin/support.php
Normal file
46
admin/support.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// Assuming coaches are admins for now
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: ../login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Fetch all support tickets with client names
|
||||||
|
$stmt = $pdo->prepare('SELECT st.*, c.name as client_name FROM support_tickets st JOIN clients c ON st.client_id = c.id ORDER BY st.updated_at DESC');
|
||||||
|
$stmt->execute();
|
||||||
|
$tickets = $stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Manage Support Tickets</h2>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">All Tickets</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="list-group">
|
||||||
|
<?php if (empty($tickets)): ?>
|
||||||
|
<p>There are no support tickets.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($tickets as $ticket): ?>
|
||||||
|
<a href="view-ticket.php?id=<?php echo $ticket['id']; ?>" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1"><?php echo htmlspecialchars($ticket['subject']); ?></h5>
|
||||||
|
<small>Last updated: <?php echo date('M j, Y, g:i a', strtotime($ticket['updated_at'])); ?></small>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">Client: <?php echo htmlspecialchars($ticket['client_name']); ?></p>
|
||||||
|
<p class="mb-1">Status: <span class="badge bg-<?php echo $ticket['status'] === 'Closed' ? 'secondary' : 'success'; ?>"><?php echo htmlspecialchars($ticket['status']); ?></span></p>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
84
admin/survey_responses.php
Normal file
84
admin/survey_responses.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$survey_id = $_GET['id'];
|
||||||
|
|
||||||
|
// Fetch survey details
|
||||||
|
$stmt = db()->prepare('SELECT * FROM surveys WHERE id = ? AND coach_id = ?');
|
||||||
|
$stmt->execute([$survey_id, $coach_id]);
|
||||||
|
$survey = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$survey) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch completed surveys by clients
|
||||||
|
$completed_stmt = db()->prepare(
|
||||||
|
'SELECT cs.id, c.name, c.email, cs.completed_at ' .
|
||||||
|
'FROM client_surveys cs ' .
|
||||||
|
'JOIN clients c ON cs.client_id = c.id ' .
|
||||||
|
'WHERE cs.survey_id = ? AND cs.status = \'completed\' ' .
|
||||||
|
'ORDER BY cs.completed_at DESC'
|
||||||
|
);
|
||||||
|
$completed_stmt->execute([$survey_id]);
|
||||||
|
$completed_surveys = $completed_stmt->fetchAll();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h3>Responses for: <?php echo htmlspecialchars($survey['title']); ?></h3>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">Completed Surveys</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($completed_surveys)): ?>
|
||||||
|
<p>No clients have completed this survey yet.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Client Name</th>
|
||||||
|
<th>Client Email</th>
|
||||||
|
<th>Completed On</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($completed_surveys as $completed):
|
||||||
|
// Check if completed_at is not null before formatting
|
||||||
|
$completed_date = !is_null($completed['completed_at']) ? date('F j, Y, g:i a', strtotime($completed['completed_at'])) : 'N/A';
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($completed['name']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($completed['email']); ?></td>
|
||||||
|
<td><?php echo $completed_date; ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="view_survey_response.php?id=<?php echo $completed['id']; ?>" class="btn btn-primary btn-sm">View Responses</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<a href="surveys.php" class="btn btn-secondary">Back to Surveys</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
58
admin/surveys.php
Normal file
58
admin/surveys.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
$stmt = db()->prepare('SELECT id, title, description, created_at FROM surveys WHERE coach_id = ? ORDER BY created_at DESC');
|
||||||
|
$stmt->execute([$coach_id]);
|
||||||
|
$surveys = $stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>Surveys</h2>
|
||||||
|
<a href="create_survey.php" class="btn btn-primary">Create New Survey</a>
|
||||||
|
</div>
|
||||||
|
<p>Create and manage surveys for your clients.</p>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Created On</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($surveys as $survey): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($survey['title']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($survey['description']); ?></td>
|
||||||
|
<td><?php echo date('F j, Y', strtotime($survey['created_at'])); ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="view_survey.php?id=<?php echo $survey['id']; ?>" class="btn btn-info btn-sm">View</a>
|
||||||
|
<a href="edit_survey.php?id=<?php echo $survey['id']; ?>" class="btn btn-warning btn-sm">Edit</a>
|
||||||
|
<a href="survey_responses.php?id=<?php echo $survey['id']; ?>" class="btn btn-primary btn-sm">View Responses</a>
|
||||||
|
<a href="assign_survey.php?id=<?php echo $survey['id']; ?>" class="btn btn-success btn-sm">Assign to Client</a>
|
||||||
|
<a href="delete_survey.php?id=<?php echo $survey['id']; ?>" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this survey?');">Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($surveys)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center">No surveys found.</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
69
admin/upload-content.php
Normal file
69
admin/upload-content.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: ../login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$title = $_POST['title'];
|
||||||
|
$description = $_POST['description'];
|
||||||
|
|
||||||
|
if (isset($_FILES['content_file']) && $_FILES['content_file']['error'] == 0) {
|
||||||
|
$target_dir = "../uploads/content/";
|
||||||
|
$file_name = uniqid() . '-' . basename($_FILES["content_file"]["name"]);
|
||||||
|
$target_file = $target_dir . $file_name;
|
||||||
|
$file_type = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
// Allow certain file formats
|
||||||
|
$allowed_types = array("jpg", "png", "jpeg", "gif", "pdf", "mp4", "mov", "avi");
|
||||||
|
if (in_array($file_type, $allowed_types)) {
|
||||||
|
if (move_uploaded_file($_FILES["content_file"]["tmp_name"], $target_file)) {
|
||||||
|
$stmt = db()->prepare("INSERT INTO content (coach_id, title, description, file_path, file_type) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
if ($stmt->execute([$coach_id, $title, $description, 'uploads/content/' . $file_name, $file_type])) {
|
||||||
|
header("Location: content.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = "Error: Could not save content to the database.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "Sorry, there was an error uploading your file.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "Sorry, only JPG, JPEG, PNG, GIF, PDF, MP4, MOV, & AVI files are allowed.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "Please select a file to upload.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Upload New Content</h2>
|
||||||
|
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="" method="POST" enctype="multipart/form-data">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="title" class="form-label">Title</label>
|
||||||
|
<input type="text" class="form-control" id="title" name="title" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="content_file" class="form-label">Content File</label>
|
||||||
|
<input class="form-control" type="file" id="content_file" name="content_file" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Upload</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
142
admin/view-ticket.php
Normal file
142
admin/view-ticket.php
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
// Assuming coaches are admins for now
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: ../login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: admin/support.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ticket_id = $_GET['id'];
|
||||||
|
$admin_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Fetch the ticket details
|
||||||
|
$stmt = $pdo->prepare('SELECT st.*, c.name as client_name FROM support_tickets st JOIN clients c ON st.client_id = c.id WHERE st.id = ?');
|
||||||
|
$stmt->execute([$ticket_id]);
|
||||||
|
$ticket = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$ticket) {
|
||||||
|
header('Location: admin/support.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle new message submission
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['reply_to_ticket'])) {
|
||||||
|
$message = trim($_POST['message']);
|
||||||
|
|
||||||
|
if (!empty($message)) {
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Insert the new message
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO support_ticket_messages (ticket_id, user_id, is_admin, message) VALUES (?, ?, ?, ?)');
|
||||||
|
$stmt->execute([$ticket_id, $admin_id, true, $message]);
|
||||||
|
|
||||||
|
// Update the ticket's updated_at timestamp
|
||||||
|
$stmt = $pdo->prepare("UPDATE support_tickets SET updated_at = CURRENT_TIMESTAMP, status = 'In Progress' WHERE id = ?");
|
||||||
|
$stmt->execute([$ticket_id]);\n\n $pdo->commit();\n\n // Send email notification to client\n require_once \'../mail/MailService.php\';\n $stmt = $pdo->prepare(\'SELECT email FROM clients WHERE id = ?\');\n $stmt->execute([$ticket[\'client_id\']]);\n $client = $stmt->fetch();\n\n if ($client) {\n $email_subject = \"Re: Support Ticket #{$ticket_id}: {$ticket[\'subject\']}\";\n $email_html = \"<p>A support team member has replied to your ticket.</p>\"\n . \"<p><strong>Reply:</strong></p><div>\" . nl2br(htmlspecialchars($message)) . \"</div>\"\n . \"<p>You can view the ticket here: <a href=\'http://{\$GLOBALS[HTTP_HOST]}/view-ticket.php?id={$ticket_id}\'>View Ticket</a></p>\";\n $email_text = \"A support team member has replied to your ticket.\\n\\n\"\n . \"Reply:\\n\" . htmlspecialchars($message) . \"\\n\\n\"\n . \"You can view the ticket here: http://{\$GLOBALS[HTTP_HOST]}/view-ticket.php?id={$ticket_id}\";\n\n MailService::sendMail($client[\'email\'], $email_subject, $email_html, $email_text);\n }\n\n $_SESSION[\'success_message\'] = \'Your reply has been sent.\';\n header(\'Location: view-ticket.php?id=\' . $ticket_id);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
$error_message = 'Failed to send reply. Please try again.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle status change
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['change_status'])) {
|
||||||
|
$status = $_POST['status'];
|
||||||
|
if (in_array($status, ['Open', 'In Progress', 'Closed'])) {
|
||||||
|
$stmt = $pdo->prepare('UPDATE support_tickets SET status = ? WHERE id = ?');
|
||||||
|
$stmt->execute([$status, $ticket_id]);
|
||||||
|
$_SESSION['success_message'] = 'Ticket status updated successfully.';
|
||||||
|
header('Location: view-ticket.php?id=' . $ticket_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all messages for the ticket
|
||||||
|
$stmt = $pdo->prepare("SELECT m.*, c.name as client_name, co.name as coach_name FROM support_ticket_messages m LEFT JOIN clients c ON m.user_id = c.id AND m.is_admin = 0 LEFT JOIN coaches co ON m.user_id = co.id AND m.is_admin = 1 WHERE m.ticket_id = ? ORDER BY m.created_at ASC");
|
||||||
|
$stmt->execute([$ticket_id]);
|
||||||
|
$messages = $stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<a href="support.php" class="btn btn-secondary mb-3">Back to Tickets</a>
|
||||||
|
|
||||||
|
<h3><?php echo htmlspecialchars($ticket['subject']); ?></h3>
|
||||||
|
<p>Client: <?php echo htmlspecialchars($ticket['client_name']); ?></p>
|
||||||
|
<p>Status: <span class="badge bg-<?php echo $ticket['status'] === 'Closed' ? 'secondary' : 'success'; ?>"><?php echo htmlspecialchars($ticket['status']); ?></span></p>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION['success_message'])): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<?php echo $_SESSION['success_message']; unset($_SESSION['success_message']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">Conversation</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php foreach ($messages as $message): ?>
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong>
|
||||||
|
<?php
|
||||||
|
if ($message['is_admin']) {
|
||||||
|
echo 'Support Team (you)';
|
||||||
|
} else {
|
||||||
|
echo htmlspecialchars($ticket['client_name']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</strong> <small class="text-muted"><?php echo date('M j, Y, g:i a', strtotime($message['created_at'])); ?></small>
|
||||||
|
<p><?php echo nl2br(htmlspecialchars($message['message'])); ?></p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Reply</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="view-ticket.php?id=<?php echo $ticket_id; ?>" method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<textarea class="form-control" name="message" rows="4" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="reply_to_ticket" class="btn btn-primary mt-3">Send Reply</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Change Status</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="view-ticket.php?id=<?php echo $ticket_id; ?>" method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<select class="form-control" name="status">
|
||||||
|
<option value="Open" <?php echo $ticket['status'] === 'Open' ? 'selected' : ''; ?>>Open</option>
|
||||||
|
<option value="In Progress" <?php echo $ticket['status'] === 'In Progress' ? 'selected' : ''; ?>>In Progress</option>
|
||||||
|
<option value="Closed" <?php echo $ticket['status'] === 'Closed' ? 'selected' : ''; ?>>Closed</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="change_status" class="btn btn-info mt-3">Change Status</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
67
admin/view_survey_response.php
Normal file
67
admin/view_survey_response.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../includes/header.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client_survey_id = $_GET['id'];
|
||||||
|
|
||||||
|
// Fetch client survey details and verify coach ownership
|
||||||
|
$stmt = db()->prepare(
|
||||||
|
'SELECT cs.*, s.title, s.description, c.name as client_name ' .
|
||||||
|
'FROM client_surveys cs ' .
|
||||||
|
'JOIN surveys s ON cs.survey_id = s.id ' .
|
||||||
|
'JOIN clients c ON cs.client_id = c.id ' .
|
||||||
|
'WHERE cs.id = ? AND s.coach_id = ?'
|
||||||
|
);
|
||||||
|
$stmt->execute([$client_survey_id, $coach_id]);
|
||||||
|
$client_survey = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$client_survey) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch questions and responses
|
||||||
|
$qa_stmt = db()->prepare(
|
||||||
|
'SELECT q.question, qr.response ' .
|
||||||
|
'FROM survey_responses qr ' .
|
||||||
|
'JOIN survey_questions q ON qr.question_id = q.id ' .
|
||||||
|
'WHERE qr.client_survey_id = ? ORDER BY q.id'
|
||||||
|
);
|
||||||
|
$qa_stmt->execute([$client_survey_id]);
|
||||||
|
$qas = $qa_stmt->fetchAll();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h3>Survey Responses from <?php echo htmlspecialchars($client_survey['client_name']); ?></h3>
|
||||||
|
<h4><?php echo htmlspecialchars($client_survey['title']); ?></h4>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<?php foreach ($qas as $qa): ?>
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="font-weight-bold"><strong><?php echo htmlspecialchars($qa['question']); ?></strong></p>
|
||||||
|
<p class="text-muted"><?php echo nl2br(htmlspecialchars($qa['response'])); ?></p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<a href="survey_responses.php?id=<?php echo $client_survey['survey_id']; ?>" class="btn btn-secondary">Back to All Responses</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once '../includes/footer.php'; ?>
|
||||||
107
api/availability.php
Normal file
107
api/availability.php
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_GET['coach_id'])) {
|
||||||
|
echo json_encode(['error' => 'Coach ID is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_GET['coach_id'];
|
||||||
|
$start_date_str = isset($_GET['start']) ? $_GET['start'] : 'first day of this month';
|
||||||
|
$end_date_str = isset($_GET['end']) ? $_GET['end'] : 'last day of this month';
|
||||||
|
|
||||||
|
// Fetch coach data
|
||||||
|
$coach_stmt = db()->prepare("SELECT buffer_time, timezone FROM coaches WHERE id = ?");
|
||||||
|
$coach_stmt->execute([$coach_id]);
|
||||||
|
$coach = $coach_stmt->fetch();
|
||||||
|
$buffer_time = $coach ? (int)$coach['buffer_time'] : 0;
|
||||||
|
$coach_timezone = new DateTimeZone($coach ? $coach['timezone'] : 'UTC');
|
||||||
|
|
||||||
|
// Fetch client timezone
|
||||||
|
$client_timezone_str = 'UTC'; // Default
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
$client_stmt = db()->prepare("SELECT timezone FROM clients WHERE id = ?");
|
||||||
|
$client_stmt->execute([$_SESSION['user_id']]);
|
||||||
|
$client = $client_stmt->fetch();
|
||||||
|
if ($client && $client['timezone']) {
|
||||||
|
$client_timezone_str = $client['timezone'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$client_timezone = new DateTimeZone($client_timezone_str);
|
||||||
|
|
||||||
|
$start_date = new DateTime($start_date_str, $client_timezone);
|
||||||
|
$end_date = new DateTime($end_date_str, $client_timezone);
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'availability' => [],
|
||||||
|
'bookings' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
// Helper function to convert time
|
||||||
|
function convert_time($time_str, $from_tz, $to_tz) {
|
||||||
|
$dt = new DateTime($time_str, $from_tz);
|
||||||
|
$dt->setTimezone($to_tz);
|
||||||
|
return $dt->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch one-off availability
|
||||||
|
$stmt_availability = db()->prepare("SELECT start_time, end_time FROM coach_availability WHERE coach_id = ? AND start_time BETWEEN ? AND ?");
|
||||||
|
$stmt_availability->execute([$coach_id, $start_date->format('Y-m-d H:i:s'), $end_date->format('Y-m-d H:i:s')]);
|
||||||
|
$one_off_availability = $stmt_availability->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($one_off_availability as $slot) {
|
||||||
|
$response['availability'][] = [
|
||||||
|
'start_time' => convert_time($slot['start_time'], $coach_timezone, $client_timezone),
|
||||||
|
'end_time' => convert_time($slot['end_time'], $coach_timezone, $client_timezone)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch recurring availability and generate slots
|
||||||
|
$stmt_recurring = db()->prepare("SELECT day_of_week, start_time, end_time FROM coach_recurring_availability WHERE coach_id = ?");
|
||||||
|
$stmt_recurring->execute([$coach_id]);
|
||||||
|
$recurring_availability = $stmt_recurring->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$interval = new DateInterval('P1D');
|
||||||
|
$period = new DatePeriod($start_date, $interval, $end_date->modify('+1 day'));
|
||||||
|
|
||||||
|
foreach ($period as $date) {
|
||||||
|
$day_of_week = $date->format('w');
|
||||||
|
foreach ($recurring_availability as $rule) {
|
||||||
|
if ((int)$rule['day_of_week'] === (int)$day_of_week) {
|
||||||
|
$start_time_str = $date->format('Y-m-d') . ' ' . $rule['start_time'];
|
||||||
|
$end_time_str = $date->format('Y-m-d') . ' ' . $rule['end_time'];
|
||||||
|
|
||||||
|
$start_dt = new DateTime($start_time_str, $coach_timezone);
|
||||||
|
|
||||||
|
if ($start_dt >= new DateTime('now', $coach_timezone)) {
|
||||||
|
$response['availability'][] = [
|
||||||
|
'start_time' => convert_time($start_time_str, $coach_timezone, $client_timezone),
|
||||||
|
'end_time' => convert_time($end_time_str, $coach_timezone, $client_timezone)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch confirmed and pending bookings
|
||||||
|
$stmt_bookings = db()->prepare("SELECT booking_time FROM bookings WHERE coach_id = ? AND (status = 'confirmed' OR status = 'pending') AND booking_time BETWEEN ? AND ?");
|
||||||
|
$stmt_bookings->execute([$coach_id, $start_date->format('Y-m-d H:i:s'), $end_date->format('Y-m-d H:i:s')]);
|
||||||
|
$bookings = $stmt_bookings->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Assume each booking is for one hour and add buffer
|
||||||
|
foreach ($bookings as $booking) {
|
||||||
|
$start_booking_time = new DateTime($booking['booking_time'], $coach_timezone);
|
||||||
|
$end_booking_time = clone $start_booking_time;
|
||||||
|
$end_booking_time->add(new DateInterval('PT1H')); // Assuming 1-hour bookings
|
||||||
|
$end_booking_time->add(new DateInterval('PT' . $buffer_time . 'M'));
|
||||||
|
|
||||||
|
$response['bookings'][] = [
|
||||||
|
'start_time' => $start_booking_time->setTimezone($client_timezone)->format('Y-m-d H:i:s'),
|
||||||
|
'end_time' => $end_booking_time->setTimezone($client_timezone)->format('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
70
api/broadcast_sms.php
Normal file
70
api/broadcast_sms.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../telnyx/config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Method not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = $_POST['message'] ?? null;
|
||||||
|
|
||||||
|
if (empty($message)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Message is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all clients with phone numbers
|
||||||
|
$stmt = db()->prepare("SELECT id, name, phone FROM clients WHERE phone IS NOT NULL AND phone != ''");
|
||||||
|
$stmt->execute();
|
||||||
|
$clients = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Set up Telnyx API
|
||||||
|
\Telnyx\Telnyx::setApiKey(TELNYX_API_KEY);
|
||||||
|
|
||||||
|
$success_count = 0;
|
||||||
|
$error_count = 0;
|
||||||
|
|
||||||
|
foreach ($clients as $client) {
|
||||||
|
try {
|
||||||
|
$response = \Telnyx\Message::create([
|
||||||
|
'from' => TELNYX_MESSAGING_PROFILE_ID,
|
||||||
|
'to' => $client['phone'],
|
||||||
|
'text' => $message,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Log the message
|
||||||
|
$log_stmt = db()->prepare("INSERT INTO sms_logs (client_id, sender, recipient, message, status) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$log_stmt->execute([
|
||||||
|
$client['id'],
|
||||||
|
TELNYX_MESSAGING_PROFILE_ID,
|
||||||
|
$client['phone'],
|
||||||
|
$message,
|
||||||
|
'sent'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$success_count++;
|
||||||
|
|
||||||
|
} catch (\Telnyx\Exception\ApiErrorException $e) {
|
||||||
|
$error_count++;
|
||||||
|
// Log the failure
|
||||||
|
$log_stmt = db()->prepare("INSERT INTO sms_logs (client_id, sender, recipient, message, status) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$log_stmt->execute([
|
||||||
|
$client['id'],
|
||||||
|
TELNYX_MESSAGING_PROFILE_ID,
|
||||||
|
$client['phone'],
|
||||||
|
$message,
|
||||||
|
'failed'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success_count > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => "Broadcast sent to " . $success_count . " clients. " . $error_count . " failed."]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Failed to send broadcast to any clients.']);
|
||||||
|
}
|
||||||
112
api/messages.php
Normal file
112
api/messages.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || !isset($_SESSION['user_type'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? null;
|
||||||
|
$userId = $_SESSION['user_id'];
|
||||||
|
$userType = $_SESSION['user_type'];
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'get_conversations':
|
||||||
|
$db = db();
|
||||||
|
// This query is complex. It gets the last message for each conversation.
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT
|
||||||
|
other_user.id as user_id,
|
||||||
|
other_user.type as user_type,
|
||||||
|
other_user.name,
|
||||||
|
last_message.message,
|
||||||
|
last_message.created_at,
|
||||||
|
(SELECT COUNT(*) FROM messages WHERE receiver_id = :user_id AND receiver_type = :user_type AND sender_id = other_user.id AND sender_type = other_user.type AND is_read = 0) as unread_count
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
CASE WHEN sender_id = :user_id AND sender_type = :user_type THEN receiver_id ELSE sender_id END as other_id,
|
||||||
|
CASE WHEN sender_id = :user_id AND sender_type = :user_type THEN receiver_type ELSE sender_type END as other_type,
|
||||||
|
MAX(id) as last_message_id
|
||||||
|
FROM messages
|
||||||
|
WHERE (sender_id = :user_id AND sender_type = :user_type) OR (receiver_id = :user_id AND receiver_type = :user_type)
|
||||||
|
GROUP BY other_id, other_type
|
||||||
|
) as conversations
|
||||||
|
JOIN messages as last_message ON last_message.id = conversations.last_message_id
|
||||||
|
JOIN (
|
||||||
|
SELECT id, name, 'coach' as type FROM coaches
|
||||||
|
UNION ALL
|
||||||
|
SELECT id, name, 'client' as type FROM clients
|
||||||
|
) as other_user ON other_user.id = conversations.other_id AND other_user.type = conversations.other_type
|
||||||
|
ORDER BY last_message.created_at DESC
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute(['user_id' => $userId, 'user_type' => $userType]);
|
||||||
|
$conversations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode($conversations);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'get_messages':
|
||||||
|
$peerId = $_GET['user_id'] ?? null;
|
||||||
|
$peerType = $_GET['user_type'] ?? null;
|
||||||
|
|
||||||
|
if (empty($peerId) || empty($peerType)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Missing user_id or user_type']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
// Mark messages as read
|
||||||
|
$updateStmt = $db->prepare("UPDATE messages SET is_read = 1 WHERE sender_id = ? AND sender_type = ? AND receiver_id = ? AND receiver_type = ?");
|
||||||
|
$updateStmt->execute([$peerId, $peerType, $userId, $userType]);
|
||||||
|
|
||||||
|
// Fetch messages
|
||||||
|
$stmt = $db->prepare("SELECT * FROM messages WHERE (sender_id = ? AND sender_type = ? AND receiver_id = ? AND receiver_type = ?) OR (sender_id = ? AND sender_type = ? AND receiver_id = ? AND receiver_type = ?) ORDER BY created_at ASC");
|
||||||
|
$stmt->execute([$userId, $userType, $peerId, $peerType, $peerId, $peerType, $userId, $userType]);
|
||||||
|
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode($messages);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'send_message':
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (empty($data['receiver_id']) || empty($data['receiver_type']) || empty($data['message'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Missing required fields']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$stmt = $db->prepare("INSERT INTO messages (sender_id, sender_type, receiver_id, receiver_type, message) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
if ($stmt->execute([$userId, $userType, $data['receiver_id'], $data['receiver_type'], $data['message']])) {
|
||||||
|
// Send email notification
|
||||||
|
require_once __DIR__ . '/../mail/MailService.php';
|
||||||
|
|
||||||
|
$receiverTable = $data['receiver_type'] === 'coach' ? 'coaches' : 'clients';
|
||||||
|
$stmt = $db->prepare("SELECT email FROM {$receiverTable} WHERE id = ?");
|
||||||
|
$stmt->execute([$data['receiver_id']]);
|
||||||
|
$recipient = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($recipient && !empty($recipient['email'])) {
|
||||||
|
$to = $recipient['email'];
|
||||||
|
$subject = 'You have a new message';
|
||||||
|
$messageBody = 'You have received a new message. Click here to view: <a href="http://' . $_SERVER['HTTP_HOST'] . '/messages.php">View Messages</a>';
|
||||||
|
MailService::sendMail($to, $subject, $messageBody, strip_tags($messageBody));
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Failed to send message']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Invalid action']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
71
api/send_sms.php
Normal file
71
api/send_sms.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../vendor/autoload.php';
|
||||||
|
require_once '../db/config.php';
|
||||||
|
require_once '../telnyx/config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Method not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client_id = $_POST['client_id'] ?? null;
|
||||||
|
$message = $_POST['message'] ?? null;
|
||||||
|
|
||||||
|
if (empty($client_id) || empty($message)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Client ID and message are required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch client phone number
|
||||||
|
$stmt = db()->prepare("SELECT phone FROM clients WHERE id = ?");
|
||||||
|
$stmt->execute([$client_id]);
|
||||||
|
$client = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$client || empty($client['phone'])) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['error' => 'Client not found or phone number is missing']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recipient_phone = $client['phone'];
|
||||||
|
|
||||||
|
// Initialize Telnyx
|
||||||
|
\Telnyx\Telnyx::setApiKey(TELNYX_API_KEY);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = \Telnyx\Message::create([
|
||||||
|
'from' => TELNYX_MESSAGING_PROFILE_ID, // Your Telnyx sending number or messaging profile ID
|
||||||
|
'to' => $recipient_phone,
|
||||||
|
'text' => $message,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Log the message to the database
|
||||||
|
$log_stmt = db()->prepare("INSERT INTO sms_logs (client_id, user_id, sender, recipient, message, status) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$log_stmt->execute([
|
||||||
|
$client_id,
|
||||||
|
$_SESSION['user_id'], // Assuming admin/coach is logged in
|
||||||
|
TELNYX_MESSAGING_PROFILE_ID,
|
||||||
|
$recipient_phone,
|
||||||
|
$message,
|
||||||
|
'sent' // or $response->status
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message_id' => $response->id]);
|
||||||
|
|
||||||
|
} catch (\Telnyx\Exception\ApiErrorException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Telnyx API error: ' . $e->getMessage()]);
|
||||||
|
|
||||||
|
// Log the failure
|
||||||
|
$log_stmt = db()->prepare("INSERT INTO sms_logs (client_id, user_id, sender, recipient, message, status) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$log_stmt->execute([
|
||||||
|
$client_id,
|
||||||
|
$_SESSION['user_id'],
|
||||||
|
TELNYX_MESSAGING_PROFILE_ID,
|
||||||
|
$recipient_phone,
|
||||||
|
$message,
|
||||||
|
'failed'
|
||||||
|
]);
|
||||||
|
}
|
||||||
62
api/validate_coupon.php
Normal file
62
api/validate_coupon.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../db/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$coupon_code = $data['coupon_code'] ?? null;
|
||||||
|
$package_id = $data['package_id'] ?? null;
|
||||||
|
|
||||||
|
if (!$coupon_code || !$package_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing coupon code or package ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch package details
|
||||||
|
$stmt = db()->prepare('SELECT * FROM service_packages WHERE id = ?');
|
||||||
|
$stmt->execute([$package_id]);
|
||||||
|
$package = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$package) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid package']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch coupon details
|
||||||
|
$stmt = db()->prepare('SELECT * FROM discounts WHERE code = ? AND is_active = 1');
|
||||||
|
$stmt->execute([$coupon_code]);
|
||||||
|
$coupon = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$coupon) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid or inactive coupon']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check date validity
|
||||||
|
if (($coupon['start_date'] && strtotime($coupon['start_date']) > time()) || ($coupon['end_date'] && strtotime($coupon['end_date']) < time())) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Coupon is not valid at this time']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check usage limit
|
||||||
|
if ($coupon['uses_limit'] !== null && $coupon['times_used'] >= $coupon['uses_limit']) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Coupon has reached its usage limit']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate discounted price
|
||||||
|
$original_price = $package['price'];
|
||||||
|
$discounted_price = 0;
|
||||||
|
|
||||||
|
if ($coupon['type'] === 'percentage') {
|
||||||
|
$discounted_price = $original_price - ($original_price * ($coupon['value'] / 100));
|
||||||
|
} else { // fixed
|
||||||
|
$discounted_price = $original_price - $coupon['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($discounted_price < 0) {
|
||||||
|
$discounted_price = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'discounted_price' => $discounted_price]);
|
||||||
60
api/validate_subscription_coupon.php
Normal file
60
api/validate_subscription_coupon.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../db/config.php';
|
||||||
|
require_once '../stripe/config.php'; // for $subscriptions
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$coupon_code = $data['coupon_code'] ?? null;
|
||||||
|
$plan_id = $data['plan_id'] ?? null;
|
||||||
|
|
||||||
|
if (!$coupon_code || !$plan_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing coupon code or plan ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $subscriptions;
|
||||||
|
if (!isset($subscriptions[$plan_id])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid plan']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$plan = $subscriptions[$plan_id];
|
||||||
|
|
||||||
|
// Fetch coupon details
|
||||||
|
$stmt = db()->prepare('SELECT * FROM discounts WHERE code = ? AND is_active = 1');
|
||||||
|
$stmt->execute([$coupon_code]);
|
||||||
|
$coupon = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$coupon) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid or inactive coupon']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check date validity
|
||||||
|
if (($coupon['start_date'] && strtotime($coupon['start_date']) > time()) || ($coupon['end_date'] && strtotime($coupon['end_date']) < time())) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Coupon is not valid at this time']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check usage limit
|
||||||
|
if ($coupon['uses_limit'] !== null && $coupon['times_used'] >= $coupon['uses_limit']) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Coupon has reached its usage limit']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate discounted price
|
||||||
|
$original_price = $plan['price'] / 100;
|
||||||
|
$discounted_price = 0;
|
||||||
|
|
||||||
|
if ($coupon['type'] === 'percentage') {
|
||||||
|
$discounted_price = $original_price - ($original_price * ($coupon['value'] / 100));
|
||||||
|
} else { // fixed
|
||||||
|
$discounted_price = $original_price - $coupon['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($discounted_price < 0) {
|
||||||
|
$discounted_price = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'discounted_price' => $discounted_price]);
|
||||||
48
approve-booking.php
Normal file
48
approve-booking.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'mail/MailService.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'coach') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$booking_id = $_GET['id'];
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// Verify the booking belongs to the coach
|
||||||
|
$stmt = db()->prepare("SELECT * FROM bookings WHERE id = ? AND coach_id = ?");
|
||||||
|
$stmt->execute([$booking_id, $coach_id]);
|
||||||
|
$booking = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($booking) {
|
||||||
|
$stmt = db()->prepare("UPDATE bookings SET status = 'confirmed' WHERE id = ?");
|
||||||
|
$stmt->execute([$booking_id]);
|
||||||
|
|
||||||
|
// Notify client
|
||||||
|
$stmt = db()->prepare("SELECT c.email, c.name as client_name, co.name as coach_name, b.booking_time FROM bookings b JOIN clients c ON b.client_id = c.id JOIN coaches co ON b.coach_id = co.id WHERE b.id = ?");
|
||||||
|
$stmt->execute([$booking_id]);
|
||||||
|
$booking_details = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($booking_details) {
|
||||||
|
$to = $booking_details['email'];
|
||||||
|
$subject = 'Booking Confirmed';
|
||||||
|
$body = "<p>Hello " . htmlspecialchars($booking_details['client_name']) . ",</p>";
|
||||||
|
$body .= "<p>Your booking with " . htmlspecialchars($booking_details['coach_name']) . " on " . htmlspecialchars(date('F j, Y, g:i a', strtotime($booking_details['booking_time']))) . " has been confirmed.</p>";
|
||||||
|
$body .= "<p>Thank you for using CoachConnect.</p>";
|
||||||
|
|
||||||
|
MailService::sendMail($to, $subject, $body, strip_tags($body));
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: dashboard.php?status=approved');
|
||||||
|
} else {
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
}
|
||||||
|
|
||||||
|
exit;
|
||||||
85
assets/css/custom.css
Normal file
85
assets/css/custom.css
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
background-color: #F9FAFB;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: Georgia, 'Times New Roman', Times, serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-bottom: 1px solid #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
background: linear-gradient(45deg, #1E3A8A, #3B82F6);
|
||||||
|
color: #FFFFFF;
|
||||||
|
padding: 100px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero p {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #F59E0B;
|
||||||
|
border-color: #F59E0B;
|
||||||
|
padding: 15px 30px;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #D97706;
|
||||||
|
border-color: #D97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-coaches {
|
||||||
|
padding: 80px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coach-card {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coach-card img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 80px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step .icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: #3B82F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: #111827;
|
||||||
|
color: #FFFFFF;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
9
assets/js/main.js
Normal file
9
assets/js/main.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const findCoachBtn = document.querySelector('.find-coach-btn');
|
||||||
|
if (findCoachBtn) {
|
||||||
|
findCoachBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
document.querySelector('#featured-coaches').scrollIntoView({ behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
108
book-session.php
Normal file
108
book-session.php
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'stripe/init.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'client') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$coach_id = $_POST['coach_id'];
|
||||||
|
$client_id = $_SESSION['user_id'];
|
||||||
|
$booking_time = $_POST['booking_time'];
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Check if the client has an active package with this coach
|
||||||
|
$pkg_stmt = $pdo->prepare(
|
||||||
|
"SELECT cp.id, cp.sessions_remaining, sp.type, sp.client_limit FROM client_packages cp JOIN service_packages sp ON cp.package_id = sp.id WHERE cp.client_id = ? AND sp.coach_id = ? AND cp.sessions_remaining > 0 ORDER BY cp.purchase_date ASC LIMIT 1"
|
||||||
|
);
|
||||||
|
$pkg_stmt->execute([$client_id, $coach_id]);
|
||||||
|
$active_package = $pkg_stmt->fetch();
|
||||||
|
|
||||||
|
if ($active_package) {
|
||||||
|
// Use a session from the package
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$is_recurring = isset($_POST['recurring']) && $_POST['recurring'] === 'on';
|
||||||
|
$recurrences = $is_recurring ? (int)$_POST['recurrences'] : 1;
|
||||||
|
$frequency = $is_recurring ? $_POST['frequency'] : 'weekly'; // Default to weekly
|
||||||
|
|
||||||
|
if ($active_package['sessions_remaining'] < $recurrences) {
|
||||||
|
throw new Exception('Not enough sessions remaining in the package.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_booking_time = new DateTime($booking_time);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $recurrences; $i++) {
|
||||||
|
if ($active_package['type'] === 'group') {
|
||||||
|
$count_stmt = $pdo->prepare("SELECT COUNT(*) FROM bookings WHERE coach_id = ? AND booking_time = ? AND status IN ('pending', 'confirmed')");
|
||||||
|
$count_stmt->execute([$coach_id, $current_booking_time->format('Y-m-d H:i:s')]);
|
||||||
|
$current_bookings = $count_stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($current_bookings >= $active_package['client_limit']) {
|
||||||
|
throw new Exception('This group session is already full.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Create the booking
|
||||||
|
$book_stmt = $pdo->prepare(
|
||||||
|
"INSERT INTO bookings (coach_id, client_id, booking_time, status, payment_status) VALUES (?, ?, ?, 'pending', 'paid_with_package')"
|
||||||
|
);
|
||||||
|
$book_stmt->execute([$coach_id, $client_id, $current_booking_time->format('Y-m-d H:i:s')]);
|
||||||
|
|
||||||
|
// 2. Decrement remaining sessions
|
||||||
|
$update_pkg_stmt = $pdo->prepare("UPDATE client_packages SET sessions_remaining = sessions_remaining - 1 WHERE id = ?");
|
||||||
|
$update_pkg_stmt->execute([$active_package['id']]);
|
||||||
|
|
||||||
|
// Calculate next booking time
|
||||||
|
if ($i < $recurrences - 1) {
|
||||||
|
if ($frequency === 'weekly') {
|
||||||
|
$current_booking_time->modify('+1 week');
|
||||||
|
} elseif ($frequency === 'bi-weekly') {
|
||||||
|
$current_booking_time->modify('+2 weeks');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
// Send email notification to coach
|
||||||
|
require_once __DIR__ . '/mail/MailService.php';
|
||||||
|
$coach_stmt = $pdo->prepare("SELECT email FROM coaches WHERE id = ?");
|
||||||
|
$coach_stmt->execute([$coach_id]);
|
||||||
|
$coach = $coach_stmt->fetch();
|
||||||
|
|
||||||
|
$client_stmt = $pdo->prepare("SELECT name FROM clients WHERE id = ?");
|
||||||
|
$client_stmt->execute([$client_id]);
|
||||||
|
$client = $client_stmt->fetch();
|
||||||
|
|
||||||
|
if ($coach && $client) {
|
||||||
|
$to = $coach['email'];
|
||||||
|
$subject = 'New Pending Booking';
|
||||||
|
$message = "You have a new booking from {$client['name']} for {$booking_time}. Please log in to your dashboard to approve or decline it.";
|
||||||
|
MailService::sendMail($to, $subject, $message, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: dashboard.php?booking=pending');
|
||||||
|
exit;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
error_log('Package booking failed: ' . $e->getMessage());
|
||||||
|
header('Location: profile.php?id=' . $coach_id . '&error=booking_failed');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// No active package, redirect to coach's profile to purchase one
|
||||||
|
header('Location: profile.php?id=' . $coach_id . '&error=no_package');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Location: coaches.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
72
cancel-booking.php
Normal file
72
cancel-booking.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'mail/MailService.php';
|
||||||
|
require_once 'stripe/init.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'client') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$booking_id = $_GET['id'];
|
||||||
|
$client_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// Verify the booking belongs to the client
|
||||||
|
$stmt = db()->prepare("SELECT * FROM bookings WHERE id = ? AND client_id = ?");
|
||||||
|
$stmt->execute([$booking_id, $client_id]);
|
||||||
|
$booking = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($booking) {
|
||||||
|
if (!empty($booking['stripe_payment_intent_id'])) {
|
||||||
|
try {
|
||||||
|
$refund = \Stripe\Refund::create([
|
||||||
|
'payment_intent' => $booking['stripe_payment_intent_id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($refund->status == 'succeeded') {
|
||||||
|
// Optionally, you can store the refund ID in your database
|
||||||
|
$stmt = db()->prepare("UPDATE bookings SET status = 'cancelled', payment_status = 'refunded' WHERE id = ?");
|
||||||
|
$stmt->execute([$booking_id]);
|
||||||
|
} else {
|
||||||
|
// Handle refund failure
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||||
|
// Handle Stripe API errors
|
||||||
|
error_log('Stripe API error: ' . $e->getMessage());
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("UPDATE bookings SET status = 'cancelled' WHERE id = ?");
|
||||||
|
$stmt->execute([$booking_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify coach
|
||||||
|
$stmt = db()->prepare("SELECT co.email, co.name as coach_name, c.name as client_name, b.booking_time FROM bookings b JOIN coaches co ON b.coach_id = co.id JOIN clients c ON b.client_id = c.id WHERE b.id = ?");
|
||||||
|
$stmt->execute([$booking_id]);
|
||||||
|
$booking_details = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($booking_details) {
|
||||||
|
$to = $booking_details['email'];
|
||||||
|
$subject = 'Booking Cancellation & Refund';
|
||||||
|
$body = "<p>Hello " . htmlspecialchars($booking_details['coach_name']) . ",</p>";
|
||||||
|
$body .= "<p>The booking with " . htmlspecialchars($booking_details['client_name']) . " on " . htmlspecialchars(date('F j, Y, g:i a', strtotime($booking_details['booking_time']))) . " has been cancelled and a refund has been issued.</p>";
|
||||||
|
$body .= "<p>Thank you for using CoachConnect.</p>";
|
||||||
|
|
||||||
|
MailService::sendMail($to, $subject, $body, strip_tags($body));
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: dashboard.php?status=cancelled');
|
||||||
|
} else {
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
}
|
||||||
|
|
||||||
|
exit;
|
||||||
99
checkout.php
Normal file
99
checkout.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'client') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_GET['package_id'])) {
|
||||||
|
header('Location: coaches.php?error=missing_package');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$package_id = $_GET['package_id'];
|
||||||
|
|
||||||
|
// Fetch package details
|
||||||
|
$stmt = db()->prepare('SELECT * FROM service_packages WHERE id = ?');
|
||||||
|
$stmt->execute([$package_id]);
|
||||||
|
$package = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$package) {
|
||||||
|
header('Location: coaches.php?error=invalid_package');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Checkout</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Package Details
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><?php echo htmlspecialchars($package['name']); ?></h5>
|
||||||
|
<p class="card-text"><?php echo htmlspecialchars($package['description']); ?></p>
|
||||||
|
<p class="card-text"><strong>Price:</strong> <span id="price">$<?php echo htmlspecialchars(number_format($package['price'], 2)); ?></span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Payment
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="checkout-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="coupon">Coupon Code</label>
|
||||||
|
<input type="text" class="form-control" id="coupon" name="coupon">
|
||||||
|
</div>
|
||||||
|
<div class="form-check mt-2">
|
||||||
|
<input type="checkbox" class="form-check-input" id="is_gift" name="is_gift">
|
||||||
|
<label class="form-check-label" for="is_gift">Is this a gift?</label>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="apply-coupon" class="btn btn-secondary mt-2">Apply Coupon</button>
|
||||||
|
<hr>
|
||||||
|
<p>Total: <span id="total-price">$<?php echo htmlspecialchars(number_format($package['price'], 2)); ?></span></p>
|
||||||
|
<button type="submit" class="btn btn-primary">Pay with Stripe</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('apply-coupon').addEventListener('click', function() {
|
||||||
|
const couponCode = document.getElementById('coupon').value;
|
||||||
|
const packageId = <?php echo $package_id; ?>;
|
||||||
|
|
||||||
|
fetch('api/validate_coupon.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ coupon_code: couponCode, package_id: packageId })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
document.getElementById('total-price').innerText = '$' + data.discounted_price.toFixed(2);
|
||||||
|
} else {
|
||||||
|
alert(data.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('checkout-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const couponCode = document.getElementById('coupon').value;
|
||||||
|
const is_gift = document.getElementById('is_gift').checked;
|
||||||
|
window.location.href = 'create-checkout-session.php?package_id=<?php echo $package_id; ?>&coupon=' + couponCode + '&is_gift=' + is_gift;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php require_once 'includes/footer.php'; ?>
|
||||||
183
coaches.php
Normal file
183
coaches.php
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Fetch settings and redirect if in single coach mode
|
||||||
|
$settings_stmt = db()->query('SELECT * FROM settings');
|
||||||
|
$settings = $settings_stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
$coach_mode = $settings['coach_mode'] ?? 'multi';
|
||||||
|
$single_coach_id = $settings['single_coach_id'] ?? null;
|
||||||
|
|
||||||
|
if ($coach_mode === 'single' && !empty($single_coach_id)) {
|
||||||
|
header('Location: profile.php?id=' . $single_coach_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
$limit = 6; // Number of coaches per page
|
||||||
|
$page = isset($_GET['page']) && is_numeric($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
$offset = ($page - 1) * $limit;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get total number of coaches for pagination
|
||||||
|
$total_coaches_stmt = db()->query('SELECT count(*) FROM coaches');
|
||||||
|
$total_coaches = $total_coaches_stmt->fetchColumn();
|
||||||
|
$total_pages = ceil($total_coaches / $limit);
|
||||||
|
|
||||||
|
// Fetch coaches for the current page
|
||||||
|
$stmt = db()->prepare('SELECT id, name, specialties, bio, photo_url FROM coaches ORDER BY name ASC LIMIT :limit OFFSET :offset');
|
||||||
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$coaches = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// It's a good practice to log the error and show a user-friendly message
|
||||||
|
error_log($e->getMessage());
|
||||||
|
$coaches = [];
|
||||||
|
$total_pages = 0;
|
||||||
|
// For a real application, you might want to redirect to an error page
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Browse Coaches</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css">
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-100">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="bg-white shadow">
|
||||||
|
<nav class="container mx-auto px-6 py-3">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<a href="index.php" class="text-2xl font-bold text-gray-800">Coaching Platform</a>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<a href="index.php" class="text-gray-600 hover:text-blue-500">Home</a>
|
||||||
|
<?php if ($coach_mode === 'multi'): ?>
|
||||||
|
<a href="coaches.php" class="text-blue-500 font-semibold">Coaches</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION['user_id'])): ?>
|
||||||
|
<a href="dashboard.php" class="text-gray-600 hover:text-blue-500">Dashboard</a>
|
||||||
|
<a href="messages.php" class="text-gray-600 hover:text-blue-500">Messages</a>
|
||||||
|
<a href="logout.php" class="text-gray-600 hover:text-blue-500">Logout</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="login.php" class="text-gray-600 hover:text-blue-500">Login</a>
|
||||||
|
<a href="register-client.php" class="text-gray-600 hover:text-blue-500">Sign Up</a>
|
||||||
|
<a href="register-coach.php" class="text-gray-600 hover:text-blue-500">Become a Coach</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="container mx-auto px-6 py-12">
|
||||||
|
<h1 class="text-4xl font-bold text-center text-gray-800 mb-12">Meet Our Coaches</h1>
|
||||||
|
|
||||||
|
<?php if (!empty($coaches)): ?>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
<?php foreach ($coaches as $coach): ?>
|
||||||
|
<?php
|
||||||
|
$package_stmt = db()->prepare('SELECT * FROM service_packages WHERE coach_id = ?');
|
||||||
|
$package_stmt->execute([$coach['id']]);
|
||||||
|
$packages = $package_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
|
||||||
|
<img class="w-full h-56 object-cover object-center" src="<?= htmlspecialchars($coach['photo_url'] ?: 'https://via.placeholder.com/400x300.png?text=No+Image') ?>" alt="<?= htmlspecialchars($coach['name']) ?>">
|
||||||
|
<div class="p-6">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-2"><?= htmlspecialchars($coach['name']) ?></h2>
|
||||||
|
<p class="text-sm text-blue-500 font-semibold mb-4"><?= htmlspecialchars($coach['specialties']) ?></p>
|
||||||
|
<?php if (!empty($coach['bio'])): ?>
|
||||||
|
<p class="text-gray-600 leading-relaxed mb-4"><?= htmlspecialchars(substr($coach['bio'], 0, 100)) ?>...</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<a href="profile.php?id=<?= $coach['id'] ?>" class="inline-block bg-blue-500 text-white font-bold py-2 px-4 rounded hover:bg-blue-600 transition duration-300">View Profile</a>
|
||||||
|
</div>
|
||||||
|
<?php if (!empty($packages)): ?>
|
||||||
|
<div class="p-6 border-t border-gray-200">
|
||||||
|
<h3 class="text-xl font-bold text-gray-800 mb-4">Coaching Packages</h3>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<?php foreach ($packages as $package): ?>
|
||||||
|
<?php
|
||||||
|
$items_stmt = db()->prepare('SELECT SUM(quantity) as total_sessions FROM package_service_items WHERE package_id = ? AND service_type IN ("one_on_one", "group_session")');
|
||||||
|
$items_stmt->execute([$package['id']]);
|
||||||
|
$items_result = $items_stmt->fetch();
|
||||||
|
$total_sessions = $items_result['total_sessions'] ?? 0;
|
||||||
|
?>
|
||||||
|
<div class="border rounded-lg p-4">
|
||||||
|
<h4 class="font-bold text-lg"><?= htmlspecialchars($package['name']) ?></h4>
|
||||||
|
<p class="text-gray-600 text-sm mb-2"><?= htmlspecialchars($package['description']) ?></p>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<span class="font-bold text-blue-600">
|
||||||
|
<?php if ($package['payment_type'] === 'one_time'): ?>
|
||||||
|
$<?= htmlspecialchars(number_format($package['price'], 2)) ?>
|
||||||
|
<?php else: ?>
|
||||||
|
$<?= htmlspecialchars(number_format($package['price'], 2)) ?> / <?= htmlspecialchars($package['payment_plan_interval']) ?> for <?= htmlspecialchars($package['payment_plan_installments']) ?> installments
|
||||||
|
<?php endif; ?>
|
||||||
|
</span>
|
||||||
|
<?php if ($total_sessions > 0): ?>
|
||||||
|
<span class="text-gray-600 text-sm">for <?= htmlspecialchars($total_sessions) ?> sessions</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<a href="purchase-package.php?package_id=<?= $package['id'] ?>" class="inline-block bg-green-500 text-white font-bold py-2 px-4 rounded hover:bg-green-600 transition duration-300">Purchase</a>
|
||||||
|
<a href="purchase-gift.php?package_id=<?= $package['id'] ?>" class="inline-block bg-blue-500 text-white font-bold py-2 px-4 rounded hover:bg-blue-600 transition duration-300 ml-2">Purchase as Gift</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500 mt-2">
|
||||||
|
<span>Type: <?= htmlspecialchars(ucfirst(str_replace('_', ' ', $package['type']))) ?></span>
|
||||||
|
<?php if($package['client_limit']): ?>
|
||||||
|
| <span>Client Limit: <?= htmlspecialchars($package['client_limit']) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if($package['start_date']): ?>
|
||||||
|
| <span>Starts: <?= htmlspecialchars(date('M j, Y', strtotime($package['start_date']))) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if($package['end_date']): ?>
|
||||||
|
| <span>Ends: <?= htmlspecialchars(date('M j, Y', strtotime($package['end_date']))) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="mt-12 flex justify-center">
|
||||||
|
<nav class="flex rounded-md shadow">
|
||||||
|
<?php if ($page > 1): ?>
|
||||||
|
<a href="?page=<?= $page - 1 ?>" class="px-4 py-2 text-gray-700 bg-white rounded-l-md hover:bg-gray-50">Previous</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||||
|
<a href="?page=<?= $i ?>" class="px-4 py-2 <?= $i == $page ? 'text-white bg-blue-500' : 'text-gray-700 bg-white' ?> hover:bg-gray-50"><?= $i ?></a>
|
||||||
|
<?php endfor; ?>
|
||||||
|
|
||||||
|
<?php if ($page < $total_pages): ?>
|
||||||
|
<a href="?page=<?= $page + 1 ?>" class="px-4 py-2 text-gray-700 bg-white rounded-r-md hover:bg-gray-50">Next</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-xl text-gray-600">No coaches found.</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-gray-800 text-white py-8">
|
||||||
|
<div class="container mx-auto px-6 text-center">
|
||||||
|
<p>© <?= date("Y") ?> Coaching Platform. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
composer.json
Normal file
7
composer.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"stripe/stripe-php": "^1.8",
|
||||||
|
"docusealco/docuseal-php": "^1.0",
|
||||||
|
"telnyx/telnyx-php": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
365
composer.lock
generated
Normal file
365
composer.lock
generated
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "b63113b587353672ca1085f160a5b41e",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "docusealco/docuseal-php",
|
||||||
|
"version": "1.0.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/docusealco/docuseal-php.git",
|
||||||
|
"reference": "f12a490e95bdb13ef61f46b72dffcdc646d8b0a4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/docusealco/docuseal-php/zipball/f12a490e95bdb13ef61f46b72dffcdc646d8b0a4",
|
||||||
|
"reference": "f12a490e95bdb13ef61f46b72dffcdc646d8b0a4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Docuseal\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "DocuSeal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP bindings for DocuSeal API",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/docusealco/docuseal-php/issues",
|
||||||
|
"source": "https://github.com/docusealco/docuseal-php/tree/1.0.5"
|
||||||
|
},
|
||||||
|
"time": "2025-09-14T21:27:12+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "php-http/discovery",
|
||||||
|
"version": "1.20.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-http/discovery.git",
|
||||||
|
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d",
|
||||||
|
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer-plugin-api": "^1.0|^2.0",
|
||||||
|
"php": "^7.1 || ^8.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"nyholm/psr7": "<1.0",
|
||||||
|
"zendframework/zend-diactoros": "*"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"php-http/async-client-implementation": "*",
|
||||||
|
"php-http/client-implementation": "*",
|
||||||
|
"psr/http-client-implementation": "*",
|
||||||
|
"psr/http-factory-implementation": "*",
|
||||||
|
"psr/http-message-implementation": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"composer/composer": "^1.0.2|^2.0",
|
||||||
|
"graham-campbell/phpspec-skip-example-extension": "^5.0",
|
||||||
|
"php-http/httplug": "^1.0 || ^2.0",
|
||||||
|
"php-http/message-factory": "^1.0",
|
||||||
|
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
|
||||||
|
"sebastian/comparator": "^3.0.5 || ^4.0.8",
|
||||||
|
"symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
|
||||||
|
},
|
||||||
|
"type": "composer-plugin",
|
||||||
|
"extra": {
|
||||||
|
"class": "Http\\Discovery\\Composer\\Plugin",
|
||||||
|
"plugin-optional": true
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Http\\Discovery\\": "src/"
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"src/Composer/Plugin.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Márk Sági-Kazár",
|
||||||
|
"email": "mark.sagikazar@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
|
||||||
|
"homepage": "http://php-http.org",
|
||||||
|
"keywords": [
|
||||||
|
"adapter",
|
||||||
|
"client",
|
||||||
|
"discovery",
|
||||||
|
"factory",
|
||||||
|
"http",
|
||||||
|
"message",
|
||||||
|
"psr17",
|
||||||
|
"psr7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/php-http/discovery/issues",
|
||||||
|
"source": "https://github.com/php-http/discovery/tree/1.20.0"
|
||||||
|
},
|
||||||
|
"time": "2024-10-02T11:20:13+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-client",
|
||||||
|
"version": "1.0.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-client.git",
|
||||||
|
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||||
|
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.0 || ^8.0",
|
||||||
|
"psr/http-message": "^1.0 || ^2.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Client\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP clients",
|
||||||
|
"homepage": "https://github.com/php-fig/http-client",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-client",
|
||||||
|
"psr",
|
||||||
|
"psr-18"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/http-client"
|
||||||
|
},
|
||||||
|
"time": "2023-09-23T14:17:50+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-message",
|
||||||
|
"version": "2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-message.git",
|
||||||
|
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||||
|
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Message\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP messages",
|
||||||
|
"homepage": "https://github.com/php-fig/http-message",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-message",
|
||||||
|
"psr",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/http-message/tree/2.0"
|
||||||
|
},
|
||||||
|
"time": "2023-04-04T09:54:51+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stripe/stripe-php",
|
||||||
|
"version": "v1.18.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/stripe/stripe-php.git",
|
||||||
|
"reference": "022c3f21ec1e4141b46738bd5e7ab730d04f78cc"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/022c3f21ec1e4141b46738bd5e7ab730d04f78cc",
|
||||||
|
"reference": "022c3f21ec1e4141b46738bd5e7ab730d04f78cc",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": ">=5.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"simpletest/simpletest": "*"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"lib/Stripe/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Stripe and contributors",
|
||||||
|
"homepage": "https://github.com/stripe/stripe-php/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Stripe PHP Library",
|
||||||
|
"homepage": "https://stripe.com/",
|
||||||
|
"keywords": [
|
||||||
|
"api",
|
||||||
|
"payment processing",
|
||||||
|
"stripe"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||||
|
"source": "https://github.com/stripe/stripe-php/tree/v1.18.0"
|
||||||
|
},
|
||||||
|
"time": "2015-01-22T05:01:46+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "telnyx/telnyx-php",
|
||||||
|
"version": "v5.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/team-telnyx/telnyx-php.git",
|
||||||
|
"reference": "0e6da9d1afe3bb9844079b163a207c4546b49d36"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/team-telnyx/telnyx-php/zipball/0e6da9d1afe3bb9844079b163a207c4546b49d36",
|
||||||
|
"reference": "0e6da9d1afe3bb9844079b163a207c4546b49d36",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"php-http/discovery": "^1",
|
||||||
|
"psr/http-client": "^1",
|
||||||
|
"psr/http-client-implementation": "^1",
|
||||||
|
"psr/http-factory-implementation": "^1",
|
||||||
|
"psr/http-message": "^1|^2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3",
|
||||||
|
"nyholm/psr7": "^1",
|
||||||
|
"pestphp/pest": "^3",
|
||||||
|
"phpstan/extension-installer": "^1",
|
||||||
|
"phpstan/phpstan": "^2",
|
||||||
|
"phpstan/phpstan-phpunit": "^2",
|
||||||
|
"phpunit/phpunit": "^11",
|
||||||
|
"symfony/http-client": "^7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/Core.php",
|
||||||
|
"src/Client.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Telnyx\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.",
|
||||||
|
"keywords": [
|
||||||
|
"api",
|
||||||
|
"call control",
|
||||||
|
"communications",
|
||||||
|
"connectivity",
|
||||||
|
"emergency services",
|
||||||
|
"esim",
|
||||||
|
"fax",
|
||||||
|
"iot",
|
||||||
|
"mms",
|
||||||
|
"numbers",
|
||||||
|
"porting",
|
||||||
|
"sip",
|
||||||
|
"sms",
|
||||||
|
"telephony",
|
||||||
|
"telnyx",
|
||||||
|
"trunking",
|
||||||
|
"voice",
|
||||||
|
"voip",
|
||||||
|
"whatsapp"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/team-telnyx/telnyx-php/issues",
|
||||||
|
"source": "https://github.com/team-telnyx/telnyx-php/tree/v5.0.0"
|
||||||
|
},
|
||||||
|
"time": "2025-10-27T22:06:49+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": {},
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {},
|
||||||
|
"platform-dev": {},
|
||||||
|
"plugin-api-version": "2.9.0"
|
||||||
|
}
|
||||||
68
content.php
Normal file
68
content.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'client') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// Fetch content assigned directly to the client
|
||||||
|
$direct_content_stmt = db()->prepare(
|
||||||
|
'SELECT c.id, c.title, c.description, c.file_path, c.file_type, 'now()' as available_at FROM content c JOIN client_content cc ON c.id = cc.content_id WHERE cc.client_id = ?'
|
||||||
|
);
|
||||||
|
$direct_content_stmt->execute([$client_id]);
|
||||||
|
$direct_content = $direct_content_stmt->fetchAll();
|
||||||
|
|
||||||
|
// Fetch content assigned via packages
|
||||||
|
$package_content_stmt = db()->prepare(
|
||||||
|
'SELECT c.id, c.title, c.description, c.file_path, c.file_type, DATE_ADD(cp.purchase_date, INTERVAL pc.delay_days DAY) as available_at FROM content c JOIN package_content pc ON c.id = pc.content_id JOIN client_packages cp ON pc.package_id = cp.package_id WHERE cp.client_id = ?'
|
||||||
|
);
|
||||||
|
$package_content_stmt->execute([$client_id]);
|
||||||
|
$package_content = $package_content_stmt->fetchAll();
|
||||||
|
|
||||||
|
$all_content = array_merge($direct_content, $package_content);
|
||||||
|
|
||||||
|
// Filter out duplicates and unavailable content
|
||||||
|
$displayed_content_ids = [];
|
||||||
|
$final_content_list = [];
|
||||||
|
$current_time = new DateTime();
|
||||||
|
|
||||||
|
foreach ($all_content as $content) {
|
||||||
|
$available_at = new DateTime($content['available_at']);
|
||||||
|
if (!in_array($content['id'], $displayed_content_ids) && $current_time >= $available_at) {
|
||||||
|
$final_content_list[] = $content;
|
||||||
|
$displayed_content_ids[] = $content['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>My Content</h2>
|
||||||
|
<p>Here you can find all the materials shared with you by your coach.</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<?php if (empty($final_content_list)): ?>
|
||||||
|
<div class="col-12">
|
||||||
|
<p>No content has been assigned to you yet.</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($final_content_list as $content): ?>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><?php echo htmlspecialchars($content['title']); ?></h5>
|
||||||
|
<p class="card-text"><?php echo htmlspecialchars($content['description']); ?></p>
|
||||||
|
<a href="<?php echo htmlspecialchars($content['file_path']); ?>" target="_blank" class="btn btn-primary">View Content</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once 'includes/footer.php'; ?>
|
||||||
146
create-checkout-session.php
Normal file
146
create-checkout-session.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'stripe/init.php';
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'client') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 1. Get Input Data ---
|
||||||
|
$package_id = $_POST['package_id'] ?? null;
|
||||||
|
$coupon_code = $_POST['coupon_code'] ?? null;
|
||||||
|
$is_gift = isset($_POST['is_gift']) && $_POST['is_gift'] === 'true';
|
||||||
|
$payment_option = $_POST['payment_option'] ?? 'pay_full';
|
||||||
|
$client_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!$package_id) {
|
||||||
|
header('Location: coaches.php?error=missing_package');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. Fetch Package Details ---
|
||||||
|
$stmt = db()->prepare('SELECT * FROM service_packages WHERE id = ?');
|
||||||
|
$stmt->execute([$package_id]);
|
||||||
|
$package = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$package) {
|
||||||
|
header('Location: coaches.php?error=invalid_package');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Determine Price, Mode, and Metadata ---
|
||||||
|
$line_items = [];
|
||||||
|
$metadata = [
|
||||||
|
'package_id' => $package_id,
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'is_gift' => $is_gift ? 'true' : 'false',
|
||||||
|
'payment_option' => $payment_option
|
||||||
|
];
|
||||||
|
$mode = 'payment'; // Default to one-time payment
|
||||||
|
|
||||||
|
$unit_amount = 0;
|
||||||
|
$product_name = $package['name'];
|
||||||
|
|
||||||
|
if ($package['payment_type'] === 'payment_plan' && $payment_option === 'payment_plan') {
|
||||||
|
// --- PAYMENT PLAN ---
|
||||||
|
if ($package['deposit_amount'] > 0) {
|
||||||
|
// A. Plan with a deposit: Create a one-time payment for the deposit.
|
||||||
|
// Webhook will handle creating the subscription for the rest.
|
||||||
|
$unit_amount = $package['deposit_amount'] * 100;
|
||||||
|
$product_name .= ' - Deposit';
|
||||||
|
$metadata['payment_plan_action'] = 'start_subscription_after_deposit';
|
||||||
|
} else {
|
||||||
|
// B. Plan with no deposit: Create a subscription directly.
|
||||||
|
$mode = 'subscription';
|
||||||
|
$unit_amount = ($package['price'] * 100) / $package['installments'];
|
||||||
|
$line_items[] = [
|
||||||
|
'price_data' => [
|
||||||
|
'currency' => 'usd',
|
||||||
|
'product_data' => ['name' => $product_name],
|
||||||
|
'unit_amount' => $unit_amount,
|
||||||
|
'recurring' => [
|
||||||
|
'interval' => $package['installment_interval'],
|
||||||
|
'interval_count' => 1,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'quantity' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// --- PAY IN FULL (or one-time package) ---
|
||||||
|
$unit_amount = $package['price'] * 100;
|
||||||
|
if ($package['payment_type'] === 'payment_plan' && !empty($package['pay_in_full_discount_percentage'])) {
|
||||||
|
$discount = $unit_amount * ($package['pay_in_full_discount_percentage'] / 100);
|
||||||
|
$unit_amount -= $discount;
|
||||||
|
$metadata['discount_applied'] = 'pay_in_full';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the primary line item if not already added (for non-subscription cases)
|
||||||
|
if (empty($line_items)) {
|
||||||
|
$line_items[] = [
|
||||||
|
'price_data' => [
|
||||||
|
'currency' => 'usd',
|
||||||
|
'product_data' => ['name' => $product_name],
|
||||||
|
'unit_amount' => round($unit_amount, 0),
|
||||||
|
],
|
||||||
|
'quantity' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- 4. Handle Coupon ---
|
||||||
|
$discounts = [];
|
||||||
|
if ($coupon_code) {
|
||||||
|
$stmt = db()->prepare('SELECT * FROM discounts WHERE code = ? AND is_active = 1');
|
||||||
|
$stmt->execute([$coupon_code]);
|
||||||
|
$coupon = $stmt->fetch();
|
||||||
|
|
||||||
|
$is_valid = $coupon &&
|
||||||
|
(!$coupon['start_date'] || strtotime($coupon['start_date']) <= time()) &&
|
||||||
|
(!$coupon['end_date'] || strtotime($coupon['end_date']) >= time()) &&
|
||||||
|
($coupon['uses_limit'] === null || $coupon['times_used'] < $coupon['uses_limit']);
|
||||||
|
|
||||||
|
if ($is_valid) {
|
||||||
|
try {
|
||||||
|
$stripe_coupon_params = ['duration' => 'once', 'name' => $coupon['code']];
|
||||||
|
if ($coupon['type'] === 'percentage') {
|
||||||
|
$stripe_coupon_params['percent_off'] = $coupon['value'];
|
||||||
|
} else {
|
||||||
|
$stripe_coupon_params['amount_off'] = $coupon['value'] * 100;
|
||||||
|
$stripe_coupon_params['currency'] = 'usd';
|
||||||
|
}
|
||||||
|
$stripe_coupon = \Stripe\Coupon::create($stripe_coupon_params);
|
||||||
|
$discounts[] = ['coupon' => $stripe_coupon->id];
|
||||||
|
$metadata['coupon_code'] = $coupon_code;
|
||||||
|
} catch (\Exception $e) { /* Ignore Stripe error, proceed without coupon */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 5. Define Success/Cancel URLs ---
|
||||||
|
$success_url = 'http://' . $_SERVER['HTTP_HOST'] . ($is_gift ? '/purchase-gift-success.php' : '/purchase-package-success.php') . '?session_id={CHECKOUT_SESSION_ID}';
|
||||||
|
$cancel_url = 'http://' . $_SERVER['HTTP_HOST'] . '/purchase-package-cancel.php';
|
||||||
|
|
||||||
|
// --- 6. Create and Redirect to Checkout ---
|
||||||
|
try {
|
||||||
|
$checkout_session = \Stripe\Checkout\Session::create([
|
||||||
|
'payment_method_types' => ['card'],
|
||||||
|
'line_items' => $line_items,
|
||||||
|
'mode' => $mode,
|
||||||
|
'success_url' => $success_url,
|
||||||
|
'cancel_url' => $cancel_url,
|
||||||
|
'metadata' => $metadata,
|
||||||
|
'discounts' => $discounts,
|
||||||
|
]);
|
||||||
|
|
||||||
|
header("Location: " . $checkout_session->url);
|
||||||
|
exit;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$error_message = urlencode($e->getMessage());
|
||||||
|
header("Location: purchase-package.php?package_id={$package_id}&error=stripe_error&message={$error_message}");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
31
create-portal-session.php
Normal file
31
create-portal-session.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'stripe/init.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'client') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT stripe_customer_id FROM clients WHERE id = ?");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$client = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$client || !$client['stripe_customer_id']) {
|
||||||
|
// This should not happen if the user has a subscription
|
||||||
|
header('Location: subscribe.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$return_url = 'http://' . $_SERVER['HTTP_HOST'] . '/manage-subscription.php';
|
||||||
|
|
||||||
|
$portalSession = \Stripe\BillingPortal\Session::create([
|
||||||
|
'customer' => $client['stripe_customer_id'],
|
||||||
|
'return_url' => $return_url,
|
||||||
|
]);
|
||||||
|
|
||||||
|
header("Location: " . $portalSession->url);
|
||||||
|
exit();
|
||||||
117
create-subscription-checkout-session.php
Normal file
117
create-subscription-checkout-session.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'stripe/init.php';
|
||||||
|
require_once 'stripe/config.php'; // for $subscriptions
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'client') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_GET['plan_id'])) {
|
||||||
|
header('Location: subscription-plans.php?error=missing_plan');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$plan_id = $_GET['plan_id'];
|
||||||
|
$client_id = $_SESSION['user_id'];
|
||||||
|
$coupon_code = $_GET['coupon'] ?? null;
|
||||||
|
|
||||||
|
global $subscriptions;
|
||||||
|
if (!isset($subscriptions[$plan_id])) {
|
||||||
|
header('Location: subscription-plans.php?error=invalid_plan');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$plan = $subscriptions[$plan_id];
|
||||||
|
|
||||||
|
$final_price = $plan['price'];
|
||||||
|
$stripe_coupon_id = null;
|
||||||
|
|
||||||
|
if ($coupon_code) {
|
||||||
|
// We need to create a coupon in Stripe to apply it to a subscription
|
||||||
|
$stmt = db()->prepare('SELECT * FROM discounts WHERE code = ? AND is_active = 1');
|
||||||
|
$stmt->execute([$coupon_code]);
|
||||||
|
$coupon = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($coupon) {
|
||||||
|
// Check date validity and usage limit (already done in previous step, but good to double check)
|
||||||
|
// ...
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stripe_coupon_params = [];
|
||||||
|
if ($coupon['type'] === 'percentage') {
|
||||||
|
$stripe_coupon_params['percent_off'] = $coupon['value'];
|
||||||
|
} else { // fixed
|
||||||
|
$stripe_coupon_params['amount_off'] = $coupon['value'] * 100;
|
||||||
|
$stripe_coupon_params['currency'] = 'usd';
|
||||||
|
}
|
||||||
|
$stripe_coupon_params['duration'] = 'once'; // Or 'repeating', 'forever'
|
||||||
|
$stripe_coupon_params['name'] = $coupon['code'];
|
||||||
|
|
||||||
|
$stripe_coupon = \Stripe\Coupon::create($stripe_coupon_params);
|
||||||
|
$stripe_coupon_id = $stripe_coupon->id;
|
||||||
|
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||||
|
// Coupon creation failed, proceed without discount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get client's stripe customer id or create a new one
|
||||||
|
$stmt = db()->prepare("SELECT stripe_customer_id, email, name FROM clients WHERE id = ?");
|
||||||
|
$stmt->execute([$client_id]);
|
||||||
|
$client = $stmt->fetch();
|
||||||
|
|
||||||
|
$stripe_customer_id = $client['stripe_customer_id'];
|
||||||
|
if (!$stripe_customer_id) {
|
||||||
|
$customer = \Stripe\Customer::create([
|
||||||
|
'email' => $client['email'],
|
||||||
|
'name' => $client['name'],
|
||||||
|
]);
|
||||||
|
$stripe_customer_id = $customer->id;
|
||||||
|
$update_stmt = db()->prepare("UPDATE clients SET stripe_customer_id = ? WHERE id = ?");
|
||||||
|
$update_stmt->execute([$stripe_customer_id, $client_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a Stripe Checkout Session
|
||||||
|
try {
|
||||||
|
$checkout_params = [
|
||||||
|
'payment_method_types' => ['card'],
|
||||||
|
'line_items' => [[
|
||||||
|
'price_data' => [
|
||||||
|
'currency' => $plan['currency'],
|
||||||
|
'product_data' => [
|
||||||
|
'name' => $plan['name'],
|
||||||
|
],
|
||||||
|
'unit_amount' => $plan['price'],
|
||||||
|
'recurring' => [
|
||||||
|
'interval' => $plan['interval'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'quantity' => 1,
|
||||||
|
]],
|
||||||
|
'mode' => 'subscription',
|
||||||
|
'success_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/subscription-success.php?session_id={CHECKOUT_SESSION_ID}',
|
||||||
|
'cancel_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/subscription-cancel.php',
|
||||||
|
'client_reference_id' => $client_id,
|
||||||
|
'customer' => $stripe_customer_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($stripe_coupon_id) {
|
||||||
|
$checkout_params['discounts'] = [['coupon' => $stripe_coupon_id]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkout_session = \Stripe\Checkout\Session::create($checkout_params);
|
||||||
|
|
||||||
|
header("HTTP/1.1 303 See Other");
|
||||||
|
header("Location: " . $checkout_session->url);
|
||||||
|
exit;
|
||||||
|
|
||||||
|
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||||
|
header('Location: subscribe-checkout.php?plan_id='. $plan_id .'&error=stripe_error&message=' . urlencode($e->getMessage()));
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header('Location: subscribe-checkout.php?plan_id=' . $plan_id .'&error=generic_error&message=' . urlencode($e->getMessage()));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
54
cron/send_reminders.php
Normal file
54
cron/send_reminders.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../telnyx/config.php';
|
||||||
|
|
||||||
|
// Set up Telnyx API
|
||||||
|
\Telnyx\Telnyx::setApiKey(TELNYX_API_KEY);
|
||||||
|
|
||||||
|
// Get all bookings for the next 24 hours
|
||||||
|
$stmt = db()->prepare("SELECT b.*, c.name as client_name, c.phone as client_phone FROM bookings b JOIN clients c ON b.client_id = c.id WHERE b.start_time BETWEEN NOW() AND NOW() + INTERVAL 24 HOUR AND b.status = 'confirmed'");
|
||||||
|
$stmt->execute();
|
||||||
|
$bookings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($bookings as $booking) {
|
||||||
|
if (empty($booking['client_phone'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = "Hi " . $booking['client_name'] . ", this is a reminder for your upcoming session tomorrow at " . date("g:i a", strtotime($booking['start_time'])) . ".";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = \Telnyx\Message::create([
|
||||||
|
'from' => TELNYX_MESSAGING_PROFILE_ID,
|
||||||
|
'to' => $booking['client_phone'],
|
||||||
|
'text' => $message,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Log the message
|
||||||
|
$log_stmt = db()->prepare("INSERT INTO sms_logs (client_id, sender, recipient, message, status) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$log_stmt->execute([
|
||||||
|
$booking['client_id'],
|
||||||
|
TELNYX_MESSAGING_PROFILE_ID,
|
||||||
|
$booking['client_phone'],
|
||||||
|
$message,
|
||||||
|
'sent'
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo "Reminder sent to " . $booking['client_name'] . "\n";
|
||||||
|
|
||||||
|
} catch (\Telnyx\Exception\ApiErrorException $e) {
|
||||||
|
echo "Error sending reminder to " . $booking['client_name'] . ": " . $e->getMessage() . "\n";
|
||||||
|
|
||||||
|
// Log the failure
|
||||||
|
$log_stmt = db()->prepare("INSERT INTO sms_logs (client_id, sender, recipient, message, status) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$log_stmt->execute([
|
||||||
|
$booking['client_id'],
|
||||||
|
TELNYX_MESSAGING_PROFILE_ID,
|
||||||
|
$booking['client_phone'],
|
||||||
|
$message,
|
||||||
|
'failed'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
497
dashboard.php
Normal file
497
dashboard.php
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Fetch settings
|
||||||
|
$settings_stmt = db()->query('SELECT setting_key, setting_value FROM settings');
|
||||||
|
$settings = $settings_stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
$coach_mode = $settings['coach_mode'] ?? 'multi';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$user_role = $_SESSION['user_role'];
|
||||||
|
$bookings = [];
|
||||||
|
$existing_reviews = [];
|
||||||
|
$subscription = null;
|
||||||
|
|
||||||
|
// Update status of past bookings to 'completed'
|
||||||
|
$update_stmt = db()->prepare("UPDATE bookings SET status = 'completed' WHERE status = 'confirmed' AND booking_time < NOW()");
|
||||||
|
$update_stmt->execute();
|
||||||
|
|
||||||
|
if ($user_role === 'client') {
|
||||||
|
$stmt = db()->prepare("SELECT b.*, c.name as coach_name FROM bookings b JOIN coaches c ON b.coach_id = c.id WHERE b.client_id = ? ORDER BY b.booking_time DESC");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$bookings = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$review_stmt = db()->prepare("SELECT booking_id FROM reviews WHERE client_id = ?");
|
||||||
|
$review_stmt->execute([$user_id]);
|
||||||
|
$existing_reviews = $review_stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$sub_stmt = db()->prepare("SELECT * FROM client_subscriptions WHERE client_id = ? AND (status = 'active' OR status = 'trialing') ORDER BY created_at DESC LIMIT 1");
|
||||||
|
$sub_stmt->execute([$user_id]);
|
||||||
|
$subscription = $sub_stmt->fetch();
|
||||||
|
|
||||||
|
$client_stmt = db()->prepare("SELECT timezone FROM clients WHERE id = ?");
|
||||||
|
$client_stmt->execute([$user_id]);
|
||||||
|
$client = $client_stmt->fetch();
|
||||||
|
|
||||||
|
} elseif ($user_role === 'coach') {
|
||||||
|
$stmt = db()->prepare("SELECT b.*, c.name as client_name FROM bookings b JOIN clients c ON b.client_id = c.id WHERE b.coach_id = ? ORDER BY b.booking_time DESC");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$bookings = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$coach_stmt = db()->prepare("SELECT buffer_time, timezone FROM coaches WHERE id = ?");
|
||||||
|
$coach_stmt->execute([$user_id]);
|
||||||
|
$coach = $coach_stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Dashboard - CoachConnect</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css">
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50">
|
||||||
|
|
||||||
|
<header class="bg-white shadow-md">
|
||||||
|
<nav class="container mx-auto px-6 py-4 flex justify-between items-center">
|
||||||
|
<a href="index.php" class="text-2xl font-bold text-gray-800">CoachConnect</a>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<a href="index.php" class="text-gray-600 hover:text-blue-500">Home</a>
|
||||||
|
<?php if ($coach_mode === 'multi'): ?>
|
||||||
|
<a href="coaches.php" class="text-gray-600 hover:text-blue-500">Coaches</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION['user_id'])): ?>
|
||||||
|
<a href="dashboard.php" class="text-gray-600 hover:text-blue-500">Dashboard</a>
|
||||||
|
<a href="messages.php" class="text-gray-600 hover:text-blue-500">Messages</a>
|
||||||
|
<a href="logout.php" class="text-gray-600 hover:text-blue-500">Logout</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="login.php" class="text-gray-600 hover:text-blue-500">Login</a>
|
||||||
|
<a href="register-client.php" class="text-gray-600 hover:text-blue-500">Sign Up</a>
|
||||||
|
<a href="register-coach.php" class="text-gray-600 hover:text-blue-500">Become a Coach</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container mx-auto px-6 py-12">
|
||||||
|
<?php if ($user_role === 'client' && isset($_GET['booking']) && $_GET['booking'] === 'pending'): ?>
|
||||||
|
<div class="bg-blue-100 border-t-4 border-blue-500 rounded-b text-blue-900 px-4 py-3 shadow-md mb-8" role="alert">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="py-1"><svg class="fill-current h-6 w-6 text-blue-500 mr-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 11V9h2v6H9v-4zm0-6h2v2H9V5z"/></svg></div>
|
||||||
|
<div>
|
||||||
|
<p class="font-bold">Your booking is pending approval.</p>
|
||||||
|
<p class="text-sm">You will be notified once the coach confirms the booking.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($user_role === 'coach'): ?>
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Manage Your One-Off Availability</h2>
|
||||||
|
<form action="add-availability.php" method="post">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label for="date" class="block text-sm font-medium text-gray-700">Date</label>
|
||||||
|
<input type="date" name="date" id="date" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="start_time" class="block text-sm font-medium text-gray-700">Start Time</label>
|
||||||
|
<input type="time" name="start_time" id="start_time" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="end_time" class="block text-sm font-medium text-gray-700">End Time</label>
|
||||||
|
<input type="time" name="end_time" id="end_time" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Add Availability
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Manage Your Recurring Weekly Availability</h2>
|
||||||
|
<form action="add-recurring-availability.php" method="post">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label for="day_of_week" class="block text-sm font-medium text-gray-700">Day of Week</label>
|
||||||
|
<select name="day_of_week" id="day_of_week" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" required>
|
||||||
|
<option value="1">Monday</option>
|
||||||
|
<option value="2">Tuesday</option>
|
||||||
|
<option value="3">Wednesday</option>
|
||||||
|
<option value="4">Thursday</option>
|
||||||
|
<option value="5">Friday</option>
|
||||||
|
<option value="6">Saturday</option>
|
||||||
|
<option value="0">Sunday</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="recurring_start_time" class="block text-sm font-medium text-gray-700">Start Time</label>
|
||||||
|
<input type="time" name="start_time" id="recurring_start_time" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="recurring_end_time" class="block text-sm font-medium text-gray-700">End Time</label>
|
||||||
|
<input type="time" name="end_time" id="recurring_end_time" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Add Recurring Availability
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Manage Your Settings</h2>
|
||||||
|
<form action="update-coach-settings.php" method="post">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label for="buffer_time" class="block text-sm font-medium text-gray-700">Buffer Time (minutes)</label>
|
||||||
|
<input type="number" name="buffer_time" id="buffer_time" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" value="<?php echo htmlspecialchars($coach['buffer_time'] ?? 0); ?>" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="timezone" class="block text-sm font-medium text-gray-700">Timezone</label>
|
||||||
|
<select id="timezone" name="timezone" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" required>
|
||||||
|
<?php
|
||||||
|
$timezones = DateTimeZone::listIdentifiers(DateTimeZone::ALL);
|
||||||
|
foreach ($timezones as $tz) {
|
||||||
|
$selected = ($coach['timezone'] ?? 'UTC') === $tz ? 'selected' : '';
|
||||||
|
echo '<option value="' . htmlspecialchars($tz) . '" ' . $selected . '>' . htmlspecialchars($tz) . '</option>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Update Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Manage Service Packages</h2>
|
||||||
|
<p class="text-gray-600 mb-4">Create, view, and manage your coaching packages.</p>
|
||||||
|
<a href="manage-packages.php" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700">Manage Packages</a>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Manage Your Portfolio</h2>
|
||||||
|
<p class="text-gray-600 mb-4">Update your bio, specialties, and upload media to showcase your work.</p>
|
||||||
|
<a href="edit-portfolio.php" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700">Edit Portfolio</a>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Admin Settings</h2>
|
||||||
|
<p class="text-gray-600 mb-4">Configure site-wide settings and modes.</p>
|
||||||
|
<a href="admin/settings.php" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-gray-700 hover:bg-gray-800">Go to Settings</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($user_role === 'client'): ?>
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Manage Your Settings</h2>
|
||||||
|
<form action="update-client-settings.php" method="post">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label for="timezone" class="block text-sm font-medium text-gray-700">Timezone</label>
|
||||||
|
<select id="timezone" name="timezone" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" required>
|
||||||
|
<?php
|
||||||
|
$timezones = DateTimeZone::listIdentifiers(DateTimeZone::ALL);
|
||||||
|
foreach ($timezones as $tz) {
|
||||||
|
$selected = ($client['timezone'] ?? 'UTC') === $tz ? 'selected' : '';
|
||||||
|
echo '<option value="' . htmlspecialchars($tz) . '" ' . $selected . '>' . htmlspecialchars($tz) . '</option>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Update Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Your Packages</h2>
|
||||||
|
<?php
|
||||||
|
$packages_stmt = db()->prepare("SELECT cp.sessions_remaining, sp.name FROM client_packages cp JOIN service_packages sp ON cp.package_id = sp.id WHERE cp.client_id = ?");
|
||||||
|
$packages_stmt->execute([$user_id]);
|
||||||
|
$client_packages = $packages_stmt->fetchAll();
|
||||||
|
?>
|
||||||
|
<?php if ($client_packages): ?>
|
||||||
|
<ul class="list-disc list-inside space-y-2">
|
||||||
|
<?php foreach ($client_packages as $pkg): ?>
|
||||||
|
<li><strong><?= htmlspecialchars($pkg['name']) ?>:</strong> <?= htmlspecialchars($pkg['sessions_remaining']) ?> sessions remaining</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="text-gray-600 mb-4">You have not purchased any packages yet.</p>
|
||||||
|
<a href="coaches.php" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700">Browse Packages</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Your Subscriptions</h2>
|
||||||
|
<?php
|
||||||
|
$subs_stmt = db()->prepare("SELECT cs.*, sp.name FROM client_subscriptions cs JOIN service_packages sp ON cs.package_id = sp.id WHERE cs.client_id = ? ORDER BY cs.created_at DESC");
|
||||||
|
$subs_stmt->execute([$user_id]);
|
||||||
|
$subscriptions = $subs_stmt->fetchAll();
|
||||||
|
?>
|
||||||
|
<?php if ($subscriptions): ?>
|
||||||
|
<ul class="list-disc list-inside space-y-2">
|
||||||
|
<?php foreach ($subscriptions as $sub): ?>
|
||||||
|
<li><strong><?= htmlspecialchars($sub['name']) ?>:</strong> <span class="font-semibold text-green-600"><?= htmlspecialchars(ucfirst($sub['status'])) ?></span></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<a href="manage-subscription.php" class="mt-4 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700">Manage Subscriptions</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="text-gray-600 mb-4">You do not have any active subscriptions.</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<h1 class="text-3xl font-bold text-gray-800 mb-8">Your Bookings</h1>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['status'])):
|
||||||
|
$status = $_GET['status'];
|
||||||
|
$message = '';
|
||||||
|
$alert_type = '';
|
||||||
|
|
||||||
|
switch ($status) {
|
||||||
|
case 'approved':
|
||||||
|
$message = 'Booking approved successfully.';
|
||||||
|
$alert_type = 'green';
|
||||||
|
break;
|
||||||
|
case 'declined':
|
||||||
|
$message = 'Booking declined successfully.';
|
||||||
|
$alert_type = 'yellow';
|
||||||
|
break;
|
||||||
|
case 'cancelled':
|
||||||
|
$message = 'Booking cancelled successfully.';
|
||||||
|
$alert_type = 'yellow';
|
||||||
|
break;
|
||||||
|
case 'review_success':
|
||||||
|
$message = 'Thank you for your review!';
|
||||||
|
$alert_type = 'green';
|
||||||
|
break;
|
||||||
|
case 'already_reviewed':
|
||||||
|
$message = 'You have already reviewed this booking.';
|
||||||
|
$alert_type = 'yellow';
|
||||||
|
break;
|
||||||
|
case 'buffer_updated':
|
||||||
|
$message = 'Buffer time updated successfully.';
|
||||||
|
$alert_type = 'green';
|
||||||
|
break;
|
||||||
|
case 'settings_updated':
|
||||||
|
$message = 'Settings updated successfully.';
|
||||||
|
$alert_type = 'green';
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
$message = 'An error occurred. Please try again.';
|
||||||
|
$alert_type = 'red';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="bg-<?php echo $alert_type; ?>-100 border border-<?php echo $alert_type; ?>-400 text-<?php echo $alert_type; ?>-700 px-4 py-3 rounded relative mb-4" role="alert">
|
||||||
|
<span class="block sm:inline"><?php echo $message; ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['booking']) && $_GET['booking'] === 'success'): ?>
|
||||||
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
|
||||||
|
<strong class="font-bold">Success!</strong>
|
||||||
|
<span class="block sm:inline">Your booking request has been sent.</span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-8">
|
||||||
|
<div class="bg-white shadow-md rounded-lg overflow-x-auto">
|
||||||
|
<table class="min-w-full leading-normal">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Start Time
|
||||||
|
</th>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
End Time
|
||||||
|
</th>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
$availability = [];
|
||||||
|
if ($user_role === 'coach') {
|
||||||
|
$stmt = db()->prepare("SELECT * FROM coach_availability WHERE coach_id = ? ORDER BY start_time DESC");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$availability = $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($availability)):
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-center">No availability set.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($availability as $slot): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
|
<p class="text-gray-900 whitespace-no-wrap"><?php echo htmlspecialchars(date('F j, Y, g:i a', strtotime($slot['start_time']))); ?></p>
|
||||||
|
</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
|
<p class="text-gray-900 whitespace-no-wrap"><?php echo htmlspecialchars(date('F j, Y, g:i a', strtotime($slot['end_time']))); ?></p>
|
||||||
|
</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-right">
|
||||||
|
<a href="delete-availability.php?id=<?php echo $slot['id']; ?>" class="text-red-600 hover:text-red-900">Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-md rounded-lg overflow-x-auto">
|
||||||
|
<h3 class="text-xl font-bold text-gray-800 p-4">Your Recurring Weekly Availability</h3>
|
||||||
|
<table class="min-w-full leading-normal">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Day of Week
|
||||||
|
</th>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Start Time
|
||||||
|
</th>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
End Time
|
||||||
|
</th>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
$recurring_availability = [];
|
||||||
|
if ($user_role === 'coach') {
|
||||||
|
$stmt = db()->prepare("SELECT * FROM coach_recurring_availability WHERE coach_id = ? ORDER BY day_of_week, start_time");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$recurring_availability = $stmt->fetchAll();
|
||||||
|
$days_of_week = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($recurring_availability)):
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-center">No recurring availability set.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($recurring_availability as $slot): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
|
<p class="text-gray-900 whitespace-no-wrap"><?php echo htmlspecialchars($days_of_week[$slot['day_of_week']]); ?></p>
|
||||||
|
</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
|
<p class="text-gray-900 whitespace-no-wrap"><?php echo htmlspecialchars(date('g:i a', strtotime($slot['start_time']))); ?></p>
|
||||||
|
</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
|
<p class="text-gray-900 whitespace-no-wrap"><?php echo htmlspecialchars(date('g:i a', strtotime($slot['end_time']))); ?></p>
|
||||||
|
</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-right">
|
||||||
|
<a href="delete-recurring-availability.php?id=<?php echo $slot['id']; ?>" class="text-red-600 hover:text-red-900">Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-md rounded-lg overflow-x-auto">
|
||||||
|
<table class="min-w-full leading-normal">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
<?php echo $user_role === 'client' ? 'Coach' : 'Client'; ?>
|
||||||
|
</th>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Booking Time
|
||||||
|
</th>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($bookings)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-center">No bookings found.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($bookings as $booking): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
|
<p class="text-gray-900 whitespace-no-wrap">
|
||||||
|
<?php echo htmlspecialchars($user_role === 'client' ? $booking['coach_name'] : $booking['client_name']); ?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
|
<p class="text-gray-900 whitespace-no-wrap"><?php echo htmlspecialchars(date('F j, Y, g:i a', strtotime($booking['booking_time']))); ?></p>
|
||||||
|
</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
|
||||||
|
<span class="relative inline-block px-3 py-1 font-semibold text-green-900 leading-tight">
|
||||||
|
<span aria-hidden class="absolute inset-0 <?php
|
||||||
|
switch ($booking['status']) {
|
||||||
|
case 'pending':
|
||||||
|
echo 'bg-yellow-200';
|
||||||
|
break;
|
||||||
|
case 'confirmed':
|
||||||
|
echo 'bg-green-200';
|
||||||
|
break;
|
||||||
|
case 'completed':
|
||||||
|
echo 'bg-blue-200';
|
||||||
|
break;
|
||||||
|
case 'cancelled':
|
||||||
|
case 'declined':
|
||||||
|
echo 'bg-red-200';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
?> opacity-50 rounded-full"></span>
|
||||||
|
<span class="relative"><?php echo htmlspecialchars(ucfirst($booking['status'])); ?></span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-right">
|
||||||
|
<?php if ($user_role === 'coach' && $booking['status'] === 'pending'): ?>
|
||||||
|
<a href="approve-booking.php?id=<?php echo $booking['id']; ?>" class="text-green-600 hover:text-green-900 mr-3">Approve</a>
|
||||||
|
<a href="decline-booking.php?id=<?php echo $booking['id']; ?>" class="text-red-600 hover:text-red-900">Decline</a>
|
||||||
|
<?php elseif ($user_role === 'client' && ($booking['status'] === 'pending' || $booking['status'] === 'confirmed')): ?>
|
||||||
|
<a href="cancel-booking.php?id=<?php echo $booking['id']; ?>" class="text-red-600 hover:text-red-900">Cancel</a>
|
||||||
|
<?php elseif ($user_role === 'client' && $booking['status'] === 'completed' && !in_array($booking['id'], $existing_reviews)): ?>
|
||||||
|
<a href="review-booking.php?id=<?php echo $booking['id']; ?>" class="text-blue-600 hover:text-blue-900">Review</a>
|
||||||
|
<?php elseif ($user_role === 'coach' && ($booking['status'] === 'confirmed' || $booking['status'] === 'completed') && !empty($booking['stripe_payment_intent_id'])): ?>
|
||||||
|
<a href="refund.php?booking_id=<?php echo $booking['id']; ?>" class="text-blue-600 hover:text-blue-900">Refund</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="bg-gray-800 text-white py-6 mt-12">
|
||||||
|
<div class="container mx-auto px-6 text-center">
|
||||||
|
<p>© <?php echo date("Y"); ?> CoachConnect. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
43
db/migrate.php
Normal file
43
db/migrate.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
function run_migrations() {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Create migrations table if it doesn't exist
|
||||||
|
try {
|
||||||
|
$pdo->exec('CREATE TABLE IF NOT EXISTS migrations (migration VARCHAR(255) PRIMARY KEY)');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Could not create migrations table: " . $e->getMessage() . "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all migration files
|
||||||
|
$migrations_dir = __DIR__ . '/migrations';
|
||||||
|
$files = glob($migrations_dir . '/*.sql');
|
||||||
|
|
||||||
|
// Get already run migrations
|
||||||
|
$ran_migrations_stmt = $pdo->query('SELECT migration FROM migrations');
|
||||||
|
$ran_migrations = $ran_migrations_stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$migration_name = basename($file);
|
||||||
|
|
||||||
|
if (in_array($migration_name, $ran_migrations)) {
|
||||||
|
continue; // Skip already run migration
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = file_get_contents($file);
|
||||||
|
try {
|
||||||
|
$pdo->exec($sql);
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO migrations (migration) VALUES (?)');
|
||||||
|
$stmt->execute([$migration_name]);
|
||||||
|
echo "Migration from $file ran successfully.\n";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Migration failed on file $file: " . $e->getMessage() . "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "All new migrations have been run.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
run_migrations();
|
||||||
|
|
||||||
8
db/migrations/001_create_coaches_table.sql
Normal file
8
db/migrations/001_create_coaches_table.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS coaches (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
specialty VARCHAR(255) NOT NULL,
|
||||||
|
bio TEXT,
|
||||||
|
photo_url VARCHAR(255),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
7
db/migrations/002_create_clients_table.sql
Normal file
7
db/migrations/002_create_clients_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS clients (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
10
db/migrations/003_create_bookings_table.sql
Normal file
10
db/migrations/003_create_bookings_table.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS bookings (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
client_id INT NOT NULL,
|
||||||
|
coach_id INT NOT NULL,
|
||||||
|
booking_time DATETIME NOT NULL,
|
||||||
|
status VARCHAR(255) NOT NULL DEFAULT 'pending',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (client_id) REFERENCES clients(id),
|
||||||
|
FOREIGN KEY (coach_id) REFERENCES coaches(id)
|
||||||
|
);
|
||||||
1
db/migrations/004_add_status_to_bookings.sql
Normal file
1
db/migrations/004_add_status_to_bookings.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `bookings` ADD `status` VARCHAR(255) NOT NULL DEFAULT 'pending';
|
||||||
7
db/migrations/005_create_coach_availability_table.sql
Normal file
7
db/migrations/005_create_coach_availability_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS coach_availability (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
coach_id INT NOT NULL,
|
||||||
|
start_time DATETIME NOT NULL,
|
||||||
|
end_time DATETIME NOT NULL,
|
||||||
|
FOREIGN KEY (coach_id) REFERENCES coaches(id)
|
||||||
|
);
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS coach_recurring_availability (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
coach_id INT NOT NULL,
|
||||||
|
day_of_week INT NOT NULL, -- 0 for Sunday, 1 for Monday, etc.
|
||||||
|
start_time TIME NOT NULL,
|
||||||
|
end_time TIME NOT NULL,
|
||||||
|
FOREIGN KEY (coach_id) REFERENCES coaches(id)
|
||||||
|
);
|
||||||
13
db/migrations/007_create_reviews_table.sql
Normal file
13
db/migrations/007_create_reviews_table.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS reviews (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
booking_id INT NOT NULL,
|
||||||
|
coach_id INT NOT NULL,
|
||||||
|
client_id INT NOT NULL,
|
||||||
|
rating INT NOT NULL,
|
||||||
|
review TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY `uniq_booking_id` (`booking_id`),
|
||||||
|
FOREIGN KEY (booking_id) REFERENCES bookings(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (coach_id) REFERENCES coaches(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
3
db/migrations/008_add_stripe_to_bookings.sql
Normal file
3
db/migrations/008_add_stripe_to_bookings.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE `bookings`
|
||||||
|
ADD COLUMN `stripe_payment_intent_id` VARCHAR(255) DEFAULT NULL,
|
||||||
|
ADD COLUMN `payment_status` VARCHAR(50) DEFAULT 'unpaid';
|
||||||
12
db/migrations/009_create_client_subscriptions_table.sql
Normal file
12
db/migrations/009_create_client_subscriptions_table.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `client_subscriptions` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`client_id` INT NOT NULL,
|
||||||
|
`stripe_subscription_id` VARCHAR(255) NOT NULL,
|
||||||
|
`stripe_product_id` VARCHAR(255) NOT NULL,
|
||||||
|
`status` VARCHAR(50) NOT NULL,
|
||||||
|
`start_date` TIMESTAMP NOT NULL,
|
||||||
|
`end_date` TIMESTAMP NULL,
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
14
db/migrations/010_create_service_packages_table.sql
Normal file
14
db/migrations/010_create_service_packages_table.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
-- 010_create_service_packages_table.sql
|
||||||
|
CREATE TABLE IF NOT EXISTS service_packages (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
coach_id INT NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
price DECIMAL(10, 2) NOT NULL,
|
||||||
|
num_sessions INT NOT NULL,
|
||||||
|
stripe_product_id VARCHAR(255),
|
||||||
|
stripe_price_id VARCHAR(255),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (coach_id) REFERENCES coaches(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
11
db/migrations/011_create_client_packages_table.sql
Normal file
11
db/migrations/011_create_client_packages_table.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-- 011_create_client_packages_table.sql
|
||||||
|
CREATE TABLE IF NOT EXISTS client_packages (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
client_id INT NOT NULL,
|
||||||
|
package_id INT NOT NULL,
|
||||||
|
sessions_remaining INT NOT NULL,
|
||||||
|
purchase_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
stripe_checkout_session_id VARCHAR(255),
|
||||||
|
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (package_id) REFERENCES service_packages(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
1
db/migrations/012_add_portfolio_to_coaches.sql
Normal file
1
db/migrations/012_add_portfolio_to_coaches.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `coaches` ADD `bio` TEXT, ADD `specialties` VARCHAR(255);
|
||||||
9
db/migrations/013_create_coach_portfolio_items_table.sql
Normal file
9
db/migrations/013_create_coach_portfolio_items_table.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `coach_portfolio_items` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`coach_id` INT NOT NULL,
|
||||||
|
`item_type` ENUM('image', 'video') NOT NULL,
|
||||||
|
`url` VARCHAR(255) NOT NULL,
|
||||||
|
`caption` VARCHAR(255),
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`coach_id`) REFERENCES `coaches`(`id`) ON DELETE CASCADE
|
||||||
|
);
|
||||||
8
db/migrations/014_create_settings_table.sql
Normal file
8
db/migrations/014_create_settings_table.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE settings (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
setting_key VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
setting_value VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO settings (setting_key, setting_value) VALUES ('coach_mode', 'multi');
|
||||||
|
INSERT INTO settings (setting_key, setting_value) VALUES ('single_coach_id', NULL);
|
||||||
10
db/migrations/015_create_messages_table.sql
Normal file
10
db/migrations/015_create_messages_table.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
sender_id INT NOT NULL,
|
||||||
|
sender_type ENUM('coach', 'client') NOT NULL,
|
||||||
|
receiver_id INT NOT NULL,
|
||||||
|
receiver_type ENUM('coach', 'client') NOT NULL,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
is_read BOOLEAN DEFAULT FALSE
|
||||||
|
);
|
||||||
1
db/migrations/016_add_stripe_customer_id_to_clients.sql
Normal file
1
db/migrations/016_add_stripe_customer_id_to_clients.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `clients` ADD `stripe_customer_id` VARCHAR(255) NULL;
|
||||||
10
db/migrations/017_create_support_tickets_table.sql
Normal file
10
db/migrations/017_create_support_tickets_table.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `support_tickets` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`client_id` INT NOT NULL,
|
||||||
|
`subject` VARCHAR(255) NOT NULL,
|
||||||
|
`status` ENUM('Open', 'In Progress', 'Closed') NOT NULL DEFAULT 'Open',
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE
|
||||||
|
);
|
||||||
10
db/migrations/018_create_support_ticket_messages_table.sql
Normal file
10
db/migrations/018_create_support_ticket_messages_table.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `support_ticket_messages` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`ticket_id` INT NOT NULL,
|
||||||
|
`user_id` INT NOT NULL COMMENT 'Can be client_id or coach_id/admin_id',
|
||||||
|
`is_admin` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'TRUE if the message is from an admin/coach, FALSE if from a client',
|
||||||
|
`message` TEXT NOT NULL,
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`ticket_id`) REFERENCES `support_tickets`(`id`) ON DELETE CASCADE
|
||||||
|
);
|
||||||
7
db/migrations/019_create_contracts_table.sql
Normal file
7
db/migrations/019_create_contracts_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `contracts` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`title` VARCHAR(255) NOT NULL,
|
||||||
|
`content` TEXT NOT NULL,
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
9
db/migrations/020_create_client_contracts_table.sql
Normal file
9
db/migrations/020_create_client_contracts_table.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `client_contracts` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`client_id` INT NOT NULL,
|
||||||
|
`contract_id` INT NOT NULL,
|
||||||
|
`signature_data` TEXT NOT NULL,
|
||||||
|
`signed_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (`contract_id`) REFERENCES `contracts`(`id`) ON DELETE CASCADE
|
||||||
|
);
|
||||||
3
db/migrations/021_add_docuseal_to_client_contracts.sql
Normal file
3
db/migrations/021_add_docuseal_to_client_contracts.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE `client_contracts`
|
||||||
|
ADD COLUMN `docuseal_submission_id` VARCHAR(255) DEFAULT NULL,
|
||||||
|
DROP COLUMN `signature_data`;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `client_contracts` ADD COLUMN `docuseal_document_url` VARCHAR(255) DEFAULT NULL;
|
||||||
1
db/migrations/023_add_role_to_contracts.sql
Normal file
1
db/migrations/023_add_role_to_contracts.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `contracts` ADD COLUMN `role` VARCHAR(255) NOT NULL DEFAULT 'Client';
|
||||||
9
db/migrations/024_create_client_notes_table.sql
Normal file
9
db/migrations/024_create_client_notes_table.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS client_notes (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
client_id INT NOT NULL,
|
||||||
|
coach_id INT NOT NULL,
|
||||||
|
note TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (coach_id) REFERENCES coaches(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
11
db/migrations/025_create_surveys_table.sql
Normal file
11
db/migrations/025_create_surveys_table.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
CREATE TABLE `surveys` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`coach_id` int(11) NOT NULL,
|
||||||
|
`title` varchar(255) NOT NULL,
|
||||||
|
`description` text,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `coach_id` (`coach_id`),
|
||||||
|
CONSTRAINT `surveys_ibfk_1` FOREIGN KEY (`coach_id`) REFERENCES `coaches` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
11
db/migrations/026_create_survey_questions_table.sql
Normal file
11
db/migrations/026_create_survey_questions_table.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
CREATE TABLE `survey_questions` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`survey_id` int(11) NOT NULL,
|
||||||
|
`question` text NOT NULL,
|
||||||
|
`type` enum('text','textarea','select','radio','checkbox') NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `survey_id` (`survey_id`),
|
||||||
|
CONSTRAINT `survey_questions_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `surveys` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
CREATE TABLE `survey_question_options` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`question_id` int(11) NOT NULL,
|
||||||
|
`option_text` varchar(255) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `question_id` (`question_id`),
|
||||||
|
CONSTRAINT `survey_question_options_ibfk_1` FOREIGN KEY (`question_id`) REFERENCES `survey_questions` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
14
db/migrations/028_create_client_surveys_table.sql
Normal file
14
db/migrations/028_create_client_surveys_table.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
CREATE TABLE `client_surveys` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`client_id` int(11) NOT NULL,
|
||||||
|
`survey_id` int(11) NOT NULL,
|
||||||
|
`status` enum('pending','completed') NOT NULL DEFAULT 'pending',
|
||||||
|
`completed_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `client_id` (`client_id`),
|
||||||
|
KEY `survey_id` (`survey_id`),
|
||||||
|
CONSTRAINT `client_surveys_ibfk_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `client_surveys_ibfk_2` FOREIGN KEY (`survey_id`) REFERENCES `surveys` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
13
db/migrations/029_create_survey_responses_table.sql
Normal file
13
db/migrations/029_create_survey_responses_table.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
CREATE TABLE `survey_responses` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`client_survey_id` int(11) NOT NULL,
|
||||||
|
`question_id` int(11) NOT NULL,
|
||||||
|
`response` text,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `client_survey_id` (`client_survey_id`),
|
||||||
|
KEY `question_id` (`question_id`),
|
||||||
|
CONSTRAINT `survey_responses_ibfk_1` FOREIGN KEY (`client_survey_id`) REFERENCES `client_surveys` (`id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `survey_responses_ibfk_2` FOREIGN KEY (`question_id`) REFERENCES `survey_questions` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
44
db/migrations/030_create_content_table.sql
Normal file
44
db/migrations/030_create_content_table.sql
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
--
|
||||||
|
-- Table structure for table `content`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `content` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`coach_id` int(11) NOT NULL,
|
||||||
|
`title` varchar(255) NOT NULL,
|
||||||
|
`description` text DEFAULT NULL,
|
||||||
|
`file_path` varchar(255) NOT NULL,
|
||||||
|
`file_type` varchar(50) DEFAULT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `content`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `coach_id` (`coach_id`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `content`
|
||||||
|
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for table `content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `content`
|
||||||
|
ADD CONSTRAINT `content_ibfk_1` FOREIGN KEY (`coach_id`) REFERENCES `coaches` (`id`) ON DELETE CASCADE;
|
||||||
43
db/migrations/031_create_client_content_table.sql
Normal file
43
db/migrations/031_create_client_content_table.sql
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
--
|
||||||
|
-- Table structure for table `client_content`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `client_content` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`client_id` int(11) NOT NULL,
|
||||||
|
`content_id` int(11) NOT NULL,
|
||||||
|
`assigned_at` timestamp NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `client_content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `client_content`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `client_id` (`client_id`),
|
||||||
|
ADD KEY `content_id` (`content_id`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `client_content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `client_content`
|
||||||
|
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for table `client_content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `client_content`
|
||||||
|
ADD CONSTRAINT `client_content_ibfk_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE,
|
||||||
|
ADD CONSTRAINT `client_content_ibfk_2` FOREIGN KEY (`content_id`) REFERENCES `content` (`id`) ON DELETE CASCADE;
|
||||||
43
db/migrations/032_create_package_content_table.sql
Normal file
43
db/migrations/032_create_package_content_table.sql
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
--
|
||||||
|
-- Table structure for table `package_content`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `package_content` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`package_id` int(11) NOT NULL,
|
||||||
|
`content_id` int(11) NOT NULL,
|
||||||
|
`delay_days` int(11) DEFAULT 0
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `package_content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `package_content`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `package_id` (`package_id`),
|
||||||
|
ADD KEY `content_id` (`content_id`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `package_content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `package_content`
|
||||||
|
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for table `package_content`
|
||||||
|
--
|
||||||
|
ALTER TABLE `package_content`
|
||||||
|
ADD CONSTRAINT `package_content_ibfk_1` FOREIGN KEY (`package_id`) REFERENCES `service_packages` (`id`) ON DELETE CASCADE,
|
||||||
|
ADD CONSTRAINT `package_content_ibfk_2` FOREIGN KEY (`content_id`) REFERENCES `content` (`id`) ON DELETE CASCADE;
|
||||||
7
db/migrations/033_alter_service_packages_table.sql
Normal file
7
db/migrations/033_alter_service_packages_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
-- 033_alter_service_packages_table.sql
|
||||||
|
ALTER TABLE service_packages
|
||||||
|
ADD COLUMN package_type ENUM('individual', 'group', 'workshop') NOT NULL DEFAULT 'individual',
|
||||||
|
ADD COLUMN max_clients INT,
|
||||||
|
ADD COLUMN start_date DATETIME,
|
||||||
|
ADD COLUMN end_date DATETIME,
|
||||||
|
ADD COLUMN payment_plan TEXT;
|
||||||
10
db/migrations/034_create_package_service_items_table.sql
Normal file
10
db/migrations/034_create_package_service_items_table.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
-- 034_create_package_service_items_table.sql
|
||||||
|
CREATE TABLE IF NOT EXISTS package_service_items (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
package_id INT NOT NULL,
|
||||||
|
service_type ENUM('individual_session', 'group_session') NOT NULL,
|
||||||
|
quantity INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (package_id) REFERENCES service_packages(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `client_subscriptions` ADD `package_id` INT NOT NULL AFTER `client_id`;
|
||||||
|
ALTER TABLE `client_subscriptions` ADD FOREIGN KEY (`package_id`) REFERENCES `service_packages`(`id`) ON DELETE CASCADE;
|
||||||
1
db/migrations/036_add_buffer_time_to_coaches.sql
Normal file
1
db/migrations/036_add_buffer_time_to_coaches.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE coaches ADD COLUMN buffer_time INT NOT NULL DEFAULT 0;
|
||||||
1
db/migrations/037_add_timezone_to_coaches.sql
Normal file
1
db/migrations/037_add_timezone_to_coaches.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE coaches ADD COLUMN timezone VARCHAR(255) NOT NULL DEFAULT 'UTC';
|
||||||
1
db/migrations/038_add_timezone_to_clients.sql
Normal file
1
db/migrations/038_add_timezone_to_clients.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE clients ADD COLUMN timezone VARCHAR(255) NOT NULL DEFAULT 'UTC';
|
||||||
1
db/migrations/039_add_email_to_coaches.sql
Normal file
1
db/migrations/039_add_email_to_coaches.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE coaches ADD COLUMN email VARCHAR(255) NOT NULL UNIQUE;
|
||||||
1
db/migrations/040_add_password_to_coaches.sql
Normal file
1
db/migrations/040_add_password_to_coaches.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE coaches ADD COLUMN password VARCHAR(255) NOT NULL;
|
||||||
14
db/migrations/041_create_discounts_table.sql
Normal file
14
db/migrations/041_create_discounts_table.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `discounts` (
|
||||||
|
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`code` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`type` ENUM('percentage', 'fixed') NOT NULL,
|
||||||
|
`value` DECIMAL(10, 2) NOT NULL,
|
||||||
|
`start_date` DATETIME,
|
||||||
|
`end_date` DATETIME,
|
||||||
|
`uses_limit` INT,
|
||||||
|
`times_used` INT DEFAULT 0,
|
||||||
|
`is_active` BOOLEAN DEFAULT TRUE,
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
14
db/migrations/042_create_gift_codes_table.sql
Normal file
14
db/migrations/042_create_gift_codes_table.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `gift_codes` (
|
||||||
|
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`code` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`package_id` INT NOT NULL,
|
||||||
|
`stripe_checkout_session_id` VARCHAR(255) NOT NULL,
|
||||||
|
`is_redeemed` BOOLEAN DEFAULT FALSE,
|
||||||
|
`redeemed_by_client_id` INT,
|
||||||
|
`redeemed_at` DATETIME,
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`package_id`) REFERENCES `service_packages`(`id`),
|
||||||
|
FOREIGN KEY (`redeemed_by_client_id`) REFERENCES `clients`(`id`)
|
||||||
|
);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
ALTER TABLE `service_packages`
|
||||||
|
ADD COLUMN `payment_type` ENUM('one_time', 'subscription', 'payment_plan') NOT NULL DEFAULT 'one_time',
|
||||||
|
ADD COLUMN `deposit_amount` DECIMAL(10, 2) DEFAULT NULL,
|
||||||
|
ADD COLUMN `installments` INT DEFAULT NULL,
|
||||||
|
ADD COLUMN `installment_interval` ENUM('day', 'week', 'month', 'year') DEFAULT NULL,
|
||||||
|
ADD COLUMN `pay_in_full_discount_percentage` DECIMAL(5, 2) DEFAULT NULL;
|
||||||
13
db/migrations/044_create_sms_logs_table.sql
Normal file
13
db/migrations/044_create_sms_logs_table.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `sms_logs` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`client_id` int(11) DEFAULT NULL,
|
||||||
|
`user_id` int(11) DEFAULT NULL,
|
||||||
|
`sender` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`recipient` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`message` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`status` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `client_id` (`client_id`),
|
||||||
|
KEY `user_id` (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
69
decline-booking.php
Normal file
69
decline-booking.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'mail/MailService.php';
|
||||||
|
require_once 'stripe/init.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'coach') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$booking_id = $_GET['id'];
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// Verify the booking belongs to the coach
|
||||||
|
$stmt = db()->prepare("SELECT * FROM bookings WHERE id = ? AND coach_id = ?");
|
||||||
|
$stmt->execute([$booking_id, $coach_id]);
|
||||||
|
$booking = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($booking) {
|
||||||
|
if (!empty($booking['stripe_payment_intent_id'])) {
|
||||||
|
try {
|
||||||
|
$refund = \Stripe\Refund::create([
|
||||||
|
'payment_intent' => $booking['stripe_payment_intent_id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($refund->status == 'succeeded') {
|
||||||
|
$stmt = db()->prepare("UPDATE bookings SET status = 'declined', payment_status = 'refunded' WHERE id = ?");
|
||||||
|
$stmt->execute([$booking_id]);
|
||||||
|
} else {
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||||
|
error_log('Stripe API error: ' . $e->getMessage());
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("UPDATE bookings SET status = 'declined' WHERE id = ?");
|
||||||
|
$stmt->execute([$booking_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify client
|
||||||
|
$stmt = db()->prepare("SELECT c.email, c.name as client_name, co.name as coach_name, b.booking_time FROM bookings b JOIN clients c ON b.client_id = c.id JOIN coaches co ON b.coach_id = co.id WHERE b.id = ?");
|
||||||
|
$stmt->execute([$booking_id]);
|
||||||
|
$booking_details = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($booking_details) {
|
||||||
|
$to = $booking_details['email'];
|
||||||
|
$subject = 'Booking Declined and Refunded';
|
||||||
|
$body = "<p>Hello " . htmlspecialchars($booking_details['client_name']) . ",</p>";
|
||||||
|
$body .= "<p>We are sorry to inform you that your booking with " . htmlspecialchars($booking_details['coach_name']) . " on " . htmlspecialchars(date('F j, Y, g:i a', strtotime($booking_details['booking_time']))) . " has been declined. A full refund has been issued.</p>";
|
||||||
|
$body .= "<p>Thank you for using CoachConnect.</p>";
|
||||||
|
|
||||||
|
MailService::sendMail($to, $subject, $body, strip_tags($body));
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: dashboard.php?status=declined');
|
||||||
|
} else {
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
}
|
||||||
|
|
||||||
|
exit;
|
||||||
39
delete-availability.php
Normal file
39
delete-availability.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'coach') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: dashboard.php?status=error&message=Invalid+request');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$availability_id = $_GET['id'];
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Verify the availability slot belongs to the coach
|
||||||
|
$stmt = db()->prepare("SELECT * FROM coach_availability WHERE id = ? AND coach_id = ?");
|
||||||
|
$stmt->execute([$availability_id, $coach_id]);
|
||||||
|
$availability = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$availability) {
|
||||||
|
header('Location: dashboard.php?status=error&message=Availability+not+found');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the availability slot
|
||||||
|
$delete_stmt = db()->prepare("DELETE FROM coach_availability WHERE id = ?");
|
||||||
|
$delete_stmt->execute([$availability_id]);
|
||||||
|
|
||||||
|
header('Location: dashboard.php?status=success&message=Availability+deleted+successfully');
|
||||||
|
exit;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log('Error deleting availability: ' . $e->getMessage());
|
||||||
|
header('Location: dashboard.php?status=error&message=Could+not+delete+availability');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
24
delete-recurring-availability.php
Normal file
24
delete-recurring-availability.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'coach') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['id'])) {
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
$availability_id = $_GET['id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("DELETE FROM coach_recurring_availability WHERE id = ? AND coach_id = ?");
|
||||||
|
$stmt->execute([$availability_id, $coach_id]);
|
||||||
|
header('Location: dashboard.php?status=recurring_deleted');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
header('Location: dashboard.php?status=error');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
}
|
||||||
|
exit;
|
||||||
30
docuseal-webhook.php
Normal file
30
docuseal-webhook.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'docuseal/config.php';
|
||||||
|
|
||||||
|
$payload = file_get_contents('php://input');
|
||||||
|
$event = json_decode($payload, true);
|
||||||
|
|
||||||
|
if ($event && isset($event['event'])) {
|
||||||
|
if ($event['event'] === 'form.completed') {
|
||||||
|
$submissionId = $event['submission_id'] ?? null;
|
||||||
|
|
||||||
|
if ($submissionId) {
|
||||||
|
try {
|
||||||
|
// Retrieve the submission to get the document URL
|
||||||
|
$submission = $docuseal->submissions->retrieve($submissionId);
|
||||||
|
$documentUrl = $submission['url'];
|
||||||
|
|
||||||
|
// Update the client_contracts table
|
||||||
|
$stmt = db()->prepare('UPDATE client_contracts SET docuseal_document_url = ? WHERE docuseal_submission_id = ?');
|
||||||
|
$stmt->execute([$documentUrl, $submissionId]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Log the error
|
||||||
|
error_log('DocuSeal webhook error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(200);
|
||||||
7
docuseal/config.php
Normal file
7
docuseal/config.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
define('DOCUSEAL_API_KEY', getenv('DOCUSEAL_API_KEY') ?: 'YOUR_API_KEY');
|
||||||
|
define('DOCUSEAL_TEMPLATE_ID', getenv('DOCUSEAL_TEMPLATE_ID') ?: 'YOUR_TEMPLATE_ID');
|
||||||
|
|
||||||
|
$docuseal = new \Docuseal\Api(DOCUSEAL_API_KEY);
|
||||||
177
edit-portfolio.php
Normal file
177
edit-portfolio.php
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'stripe/config.php';
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'coach') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$coach_id = $_SESSION['user_id'];
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Fetch coach's current portfolio data
|
||||||
|
$stmt = $pdo->prepare("SELECT bio, specialties FROM coaches WHERE id = ?");
|
||||||
|
$stmt->execute([$coach_id]);
|
||||||
|
$coach = $stmt->fetch();
|
||||||
|
|
||||||
|
$bio = $coach['bio'] ?? '';
|
||||||
|
$specialties = $coach['specialties'] ?? '';
|
||||||
|
|
||||||
|
// Handle form submission for bio and specialties
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_profile'])) {
|
||||||
|
$bio = trim($_POST['bio']);
|
||||||
|
$specialties = trim($_POST['specialties']);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE coaches SET bio = ?, specialties = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$bio, $specialties, $coach_id]);
|
||||||
|
|
||||||
|
header('Location: edit-portfolio.php?success=1');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle media upload
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_media'])) {
|
||||||
|
if (isset($_FILES['media']) && $_FILES['media']['error'] == 0) {
|
||||||
|
$caption = trim($_POST['caption']);
|
||||||
|
$allowed = ['jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif'];
|
||||||
|
$filename = $_FILES['media']['name'];
|
||||||
|
$filetype = $_FILES['media']['type'];
|
||||||
|
$filesize = $_FILES['media']['size'];
|
||||||
|
|
||||||
|
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||||
|
if (!array_key_exists($ext, $allowed)) {
|
||||||
|
die("Error: Please select a valid file format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxsize = 5 * 1024 * 1024;
|
||||||
|
if ($filesize > $maxsize) {
|
||||||
|
die("Error: File size is larger than the allowed limit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($filetype, $allowed)) {
|
||||||
|
$new_filename = uniqid() . '.' . $ext;
|
||||||
|
$filepath = 'uploads/portfolio/' . $new_filename;
|
||||||
|
|
||||||
|
$error_message = '';
|
||||||
|
if (move_uploaded_file($_FILES['media']['tmp_name'], $filepath)) {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO coach_portfolio_items (coach_id, item_type, url, caption) VALUES (?, 'image', ?, ?)");
|
||||||
|
$stmt->execute([$coach_id, $filepath, $caption]);
|
||||||
|
header('Location: edit-portfolio.php?success=2');
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$error_message = 'Error: There was a problem uploading your file. Please try again.';
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle media deletion
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_media'])) {
|
||||||
|
$item_id = $_POST['delete_item_id'];
|
||||||
|
|
||||||
|
// First, get the file path to delete the file
|
||||||
|
$stmt = $pdo->prepare("SELECT url FROM coach_portfolio_items WHERE id = ? AND coach_id = ?");
|
||||||
|
$stmt->execute([$item_id, $coach_id]);
|
||||||
|
$item = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($item) {
|
||||||
|
// Delete file from server
|
||||||
|
if (file_exists($item['url'])) {
|
||||||
|
unlink($item['url']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete from database
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM coach_portfolio_items WHERE id = ?");
|
||||||
|
$stmt->execute([$item_id]);
|
||||||
|
|
||||||
|
header('Location: edit-portfolio.php?success=3');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all portfolio items for the coach
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM coach_portfolio_items WHERE coach_id = ? ORDER BY created_at DESC");
|
||||||
|
$stmt->execute([$coach_id]);
|
||||||
|
$portfolio_items = $stmt->fetchAll();
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Edit Portfolio</title>
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include 'includes/header.php'; ?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Edit Portfolio</h2>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['success'])): ?>
|
||||||
|
<div class="alert alert-success">Portfolio updated successfully.</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($error_message)): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Profile Information</h5>
|
||||||
|
<form method="POST" action="edit-portfolio.php">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="bio">Biography</label>
|
||||||
|
<textarea class="form-control" id="bio" name="bio" rows="5"><?php echo htmlspecialchars($bio); ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="specialties">Specialties</label>
|
||||||
|
<input type="text" class="form-control" id="specialties" name="specialties" value="<?php echo htmlspecialchars($specialties); ?>" placeholder="e.g., Nutrition, Strength Training, Yoga">
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="update_profile" class="btn btn-primary">Save Changes</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Portfolio Media</h5>
|
||||||
|
<form method="POST" action="edit-portfolio.php" enctype="multipart/form-data">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="media">Upload Image</label>
|
||||||
|
<input type="file" class="form-control-file" id="media" name="media" accept="image/*">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="caption">Caption</label>
|
||||||
|
<input type="text" class="form-control" id="caption" name="caption" placeholder="Optional caption">
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="upload_media" class="btn btn-secondary">Upload Media</button>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach ($portfolio_items as $item): ?>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card">
|
||||||
|
<img src="<?php echo htmlspecialchars($item['url']); ?>" class="card-img-top" alt="<?php echo htmlspecialchars($item['caption']); ?>">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text"><?php echo htmlspecialchars($item['caption']); ?></p>
|
||||||
|
<form method="POST" action="edit-portfolio.php" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||||
|
<input type="hidden" name="delete_item_id" value="<?php echo $item['id']; ?>">
|
||||||
|
<button type="submit" name="delete_media" class="btn btn-danger btn-sm">Delete</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user