Auto commit: 2025-12-18T06:13:19.239Z
This commit is contained in:
parent
0c361cd8ae
commit
cff6fa6b35
248
admin/links.php
248
admin/links.php
@ -9,6 +9,98 @@ if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'admin') {
|
|||||||
require_once __DIR__ . '/../db/config.php';
|
require_once __DIR__ . '/../db/config.php';
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
|
||||||
|
// CSRF Protection
|
||||||
|
if (empty($_SESSION['csrf_token'])) {
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$response = ['success' => false, 'message' => 'Invalid request.'];
|
||||||
|
|
||||||
|
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||||
|
$response['message'] = 'CSRF token validation failed.';
|
||||||
|
echo json_encode($response);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
$link_id = $_POST['link_id'] ?? null;
|
||||||
|
|
||||||
|
if (!$link_id || !is_numeric($link_id)) {
|
||||||
|
$response['message'] = 'Invalid Link ID.';
|
||||||
|
echo json_encode($response);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'delete':
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM links WHERE id = ?");
|
||||||
|
$stmt->execute([$link_id]);
|
||||||
|
if ($stmt->rowCount()) {
|
||||||
|
$response = ['success' => true, 'message' => 'Link deleted successfully.'];
|
||||||
|
} else {
|
||||||
|
$response['message'] = 'Link not found or could not be deleted.';
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$response['message'] = 'Database error: ' . $e->getMessage();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'toggle_status':
|
||||||
|
$current_status = $_POST['current_status'] ?? '';
|
||||||
|
$new_status = ($current_status === 'paused') ? 'approved' : 'paused'; // Toggle between paused and approved
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("UPDATE links SET status = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$new_status, $link_id]);
|
||||||
|
if ($stmt->rowCount()) {
|
||||||
|
$response = ['success' => true, 'message' => 'Link status updated successfully to ' . $new_status . '.', 'new_status' => $new_status];
|
||||||
|
} else {
|
||||||
|
$response['message'] = 'Link not found or status could not be updated.';
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$response['message'] = 'Database error: ' . $e->getMessage();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Add 'edit' case later
|
||||||
|
case 'edit':
|
||||||
|
$title = trim($_POST['title'] ?? '');
|
||||||
|
$url = trim($_POST['url'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$subcategory_id = $_POST['subcategory_id'] ?? null;
|
||||||
|
$status = $_POST['status'] ?? 'pending';
|
||||||
|
|
||||||
|
if (empty($title) || empty($url) || !filter_var($url, FILTER_VALIDATE_URL) || !is_numeric($subcategory_id)) {
|
||||||
|
$response['message'] = 'Invalid input for editing link.';
|
||||||
|
echo json_encode($response);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("UPDATE links SET title = ?, url = ?, description = ?, subcategory_id = ?, status = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$title, $url, $description, $subcategory_id, $status, $link_id]);
|
||||||
|
if ($stmt->rowCount()) {
|
||||||
|
$response = ['success' => true, 'message' => 'Link updated successfully.'];
|
||||||
|
} else {
|
||||||
|
$response['message'] = 'Link not found or no changes made.';
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$response['message'] = 'Database error: ' . $e->getMessage();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$response['message'] = 'Unknown action.';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name as category_name
|
$links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name as category_name
|
||||||
FROM links l
|
FROM links l
|
||||||
JOIN users u ON l.user_id = u.id
|
JOIN users u ON l.user_id = u.id
|
||||||
@ -16,6 +108,8 @@ $links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name
|
|||||||
JOIN categories c ON s.category_id = c.id
|
JOIN categories c ON s.category_id = c.id
|
||||||
ORDER BY l.created_at DESC")->fetchAll();
|
ORDER BY l.created_at DESC")->fetchAll();
|
||||||
|
|
||||||
|
// Fetch subcategories for the edit form
|
||||||
|
$subcategories = $pdo->query("SELECT sc.id, sc.name AS subcategory_name, c.name AS category_name FROM subcategories sc JOIN categories c ON sc.category_id = c.id ORDER BY c.name, sc.name")->fetchAll();
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -70,15 +164,17 @@ $links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name
|
|||||||
<tr><td colspan="7">No links submitted yet.</td></tr>
|
<tr><td colspan="7">No links submitted yet.</td></tr>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($links as $link): ?>
|
<?php foreach ($links as $link): ?>
|
||||||
<tr>
|
<tr data-id="<?php echo $link['id']; ?>">
|
||||||
<td><?php echo htmlspecialchars($link['title']); ?></td>
|
<td class="link-title"><?php echo htmlspecialchars($link['title']); ?></td>
|
||||||
<td><a href="<?php echo htmlspecialchars($link['url']); ?>" target="_blank"><?php echo htmlspecialchars(substr($link['url'], 0, 50)); ?>...</a></td>
|
<td class="link-url"><a href="<?php echo htmlspecialchars($link['url']); ?>" target="_blank"><?php echo htmlspecialchars(substr($link['url'], 0, 50)); ?>...</a></td>
|
||||||
<td><?php echo htmlspecialchars($link['category_name']); ?> > <?php echo htmlspecialchars($link['subcategory_name']); ?></td>
|
<td><?php echo htmlspecialchars($link['category_name']); ?> > <span class="link-subcategory-name" data-id="<?php echo $link['subcategory_id']; ?>"><?php echo htmlspecialchars($link['subcategory_name']); ?></span></td>
|
||||||
<td><?php echo htmlspecialchars($link['username']); ?></td>
|
<td><?php echo htmlspecialchars($link['username']); ?></td>
|
||||||
<td><span class="badge bg-<?php echo $link['status'] === 'approved' ? 'success' : ($link['status'] === 'pending' ? 'warning' : 'danger'); ?>"><?php echo htmlspecialchars($link['status']); ?></span></td>
|
<td><span class="badge bg-<?php echo $link['status'] === 'approved' ? 'success' : ($link['status'] === 'pending' ? 'warning' : ($link['status'] === 'paused' ? 'info' : 'danger')); ?> link-status"><?php echo htmlspecialchars($link['status']); ?></span></td>
|
||||||
<td><?php echo date("Y-m-d", strtotime($link['created_at'])); ?></td>
|
<td><?php echo date("Y-m-d", strtotime($link['created_at'])); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<!-- Future actions -->
|
<button class="btn btn-sm btn-primary edit-link-btn" data-id="<?php echo $link['id']; ?>" data-bs-toggle="modal" data-bs-target="#editLinkModal">Edit</button>
|
||||||
|
<button class="btn btn-sm btn-<?php echo $link['status'] === 'paused' ? 'success' : 'warning'; ?> toggle-status-btn" data-id="<?php echo $link['id']; ?>" data-status="<?php echo $link['status']; ?>"><?php echo $link['status'] === 'paused' ? 'Unpause' : 'Pause'; ?></button>
|
||||||
|
<button class="btn btn-sm btn-danger delete-link-btn" data-id="<?php echo $link['id']; ?>">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@ -91,9 +187,149 @@ $links = $pdo->query("SELECT l.*, u.username, s.name as subcategory_name, c.name
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Link Modal -->
|
||||||
|
<div class="modal fade" id="editLinkModal" tabindex="-1" aria-labelledby="editLinkModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="editLinkModalLabel">Edit Link</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="editLinkForm">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
|
||||||
|
<input type="hidden" name="action" value="edit">
|
||||||
|
<input type="hidden" name="link_id" id="editLinkId">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editLinkTitle" class="form-label">Title</label>
|
||||||
|
<input type="text" class="form-control" id="editLinkTitle" name="title" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editLinkUrl" class="form-label">URL</label>
|
||||||
|
<input type="url" class="form-control" id="editLinkUrl" name="url" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editLinkDescription" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="editLinkDescription" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editLinkSubcategory" class="form-label">Subcategory</label>
|
||||||
|
<select class="form-select" id="editLinkSubcategory" name="subcategory_id" required>
|
||||||
|
<?php foreach ($subcategories as $sc): ?>
|
||||||
|
<option value="<?php echo $sc['id']; ?>"><?php echo htmlspecialchars($sc['category_name'] . ' > ' . $sc['subcategory_name']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editLinkStatus" class="form-label">Status</label>
|
||||||
|
<select class="form-select" id="editLinkStatus" name="status" required>
|
||||||
|
<option value="pending">Pending</option>
|
||||||
|
<option value="approved">Approved</option>
|
||||||
|
<option value="rejected">Rejected</option>
|
||||||
|
<option value="paused">Paused</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<p>© <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p>
|
<p>© <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
|
||||||
|
|
||||||
|
document.querySelectorAll('.delete-link-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const linkId = this.dataset.id;
|
||||||
|
if (confirm('Are you sure you want to delete this link?')) {
|
||||||
|
sendAction(linkId, 'delete', {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.toggle-status-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const linkId = this.dataset.id;
|
||||||
|
const currentStatus = this.dataset.status;
|
||||||
|
// Determine new status based on current for toggling between paused and approved
|
||||||
|
// If current status is 'paused', next is 'approved'. Otherwise, 'paused'.
|
||||||
|
const newStatus = (currentStatus === 'paused') ? 'approved' : 'paused';
|
||||||
|
if (confirm(`Are you sure you want to ${newStatus === 'paused' ? 'pause' : 'unpause'} this link?`)) {
|
||||||
|
sendAction(linkId, 'toggle_status', { current_status: currentStatus });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.edit-link-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const linkId = this.dataset.id;
|
||||||
|
const row = this.closest('tr');
|
||||||
|
|
||||||
|
document.getElementById('editLinkId').value = linkId;
|
||||||
|
document.getElementById('editLinkTitle').value = row.querySelector('.link-title').textContent;
|
||||||
|
document.getElementById('editLinkUrl').value = row.querySelector('.link-url a').href;
|
||||||
|
document.getElementById('editLinkDescription').value = row.querySelector('.link-description') ? row.querySelector('.link-description').textContent : '';
|
||||||
|
document.getElementById('editLinkStatus').value = row.querySelector('.link-status').textContent.trim();
|
||||||
|
|
||||||
|
// Select the correct subcategory in the dropdown
|
||||||
|
const subcategoryId = row.querySelector('.link-subcategory-name').dataset.id;
|
||||||
|
const subcategorySelect = document.getElementById('editLinkSubcategory');
|
||||||
|
for (let i = 0; i < subcategorySelect.options.length; i++) {
|
||||||
|
if (subcategorySelect.options[i].value == subcategoryId) {
|
||||||
|
subcategorySelect.selectedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('editLinkForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const linkId = document.getElementById('editLinkId').value;
|
||||||
|
const data = {};
|
||||||
|
formData.forEach((value, key) => data[key] = value);
|
||||||
|
sendAction(linkId, 'edit', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendAction(linkId, action, additionalData) {
|
||||||
|
const data = {
|
||||||
|
csrf_token: csrfToken,
|
||||||
|
action: action,
|
||||||
|
link_id: linkId,
|
||||||
|
...additionalData
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch('links.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: new URLSearchParams(data)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert(data.message);
|
||||||
|
window.location.reload(); // Simple reload for now, can be optimized later
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fetch error:', error);
|
||||||
|
alert('An error occurred while processing your request.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,73 +1,87 @@
|
|||||||
/* --- Modern Japanese Retro Theme --- */
|
/* --- Modern Japanese Retro Theme with more Pizazz --- */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #5A8D8D; /* Deep Teal */
|
--primary-color: #007bff; /* Vibrant Blue */
|
||||||
--accent-color: #D65A5A; /* Muted Red */
|
--secondary-color: #ff4081; /* Pink Accent */
|
||||||
--bg-light: #F8F8F8; /* Off-white background */
|
--tertiary-color: #f0f2f5; /* Light Gray Background */
|
||||||
--text-dark: #333333;
|
--text-dark: #212529;
|
||||||
--text-light: #666666;
|
--text-medium: #495057;
|
||||||
--border-color: #E0E0E0;
|
--text-light: #ced4da;
|
||||||
--shadow-light: rgba(0, 0, 0, 0.05);
|
--border-color: #dee2e6;
|
||||||
|
--shadow-light: rgba(0, 0, 0, 0.1);
|
||||||
|
--white: #ffffff;
|
||||||
|
--gradient-start: #e0f2f7; /* Light blue for gradient */
|
||||||
|
--gradient-end: #f0f8ff; /* Lighter blue for gradient */
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Noto Sans JP', sans-serif;
|
font-family: 'Noto Sans JP', sans-serif;
|
||||||
background-color: var(--bg-light);
|
background: linear-gradient(to bottom right, var(--gradient-start), var(--gradient-end));
|
||||||
color: var(--text-dark);
|
color: var(--text-dark);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background: var(--primary-color);
|
background: var(--white);
|
||||||
color: var(--bg-light);
|
color: var(--primary-color);
|
||||||
padding: 20px 25px;
|
padding: 10px 25px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: none;
|
border-bottom: 1px solid var(--border-color);
|
||||||
box-shadow: 0 2px 4px var(--shadow-light);
|
box-shadow: 0 2px 8px var(--shadow-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header h1 {
|
.header h1 {
|
||||||
font-family: 'Zen Old Mincho', serif;
|
font-family: 'Zen Old Mincho', serif;
|
||||||
font-size: 2.8em;
|
font-size: 2.5em;
|
||||||
color: var(--bg-light);
|
color: var(--primary-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-links a {
|
.auth-links a {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
color: var(--bg-light);
|
color: var(--primary-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 300;
|
font-weight: 500;
|
||||||
transition: color 0.3s ease;
|
transition: color 0.3s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-links a:hover {
|
.auth-links a:hover {
|
||||||
color: var(--accent-color);
|
color: var(--secondary-color);
|
||||||
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.main-wrapper {
|
||||||
background-color: #FFFFFF;
|
padding-top: 25px; /* Increased space below the header */
|
||||||
border: 1px solid var(--border-color);
|
}
|
||||||
box-shadow: 0 5px 15px var(--shadow-light);
|
|
||||||
border-radius: 8px;
|
.content-section {
|
||||||
overflow: hidden; /* For inner elements like category-list and content */
|
background-color: var(--white);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 30px; /* Increased space between sections */
|
||||||
|
box-shadow: 0 4px 15px var(--shadow-light);
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-list {
|
.category-list {
|
||||||
background-color: #FAFAFA;
|
background-color: var(--white);
|
||||||
border-right: 1px solid var(--border-color);
|
padding: 25px;
|
||||||
padding: 20px;
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-list h3 {
|
.category-list h3 {
|
||||||
font-family: 'Zen Old Mincho', serif;
|
font-family: 'Zen Old Mincho', serif;
|
||||||
font-size: 1.8rem;
|
font-size: 1.7rem;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
border-bottom: 2px solid var(--accent-color);
|
border-bottom: 3px solid var(--secondary-color);
|
||||||
padding-bottom: 10px;
|
padding-bottom: 12px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,52 +94,56 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.category-list .nav-link:hover {
|
.category-list .nav-link:hover {
|
||||||
color: var(--primary-color);
|
color: var(--secondary-color);
|
||||||
transform: translateX(5px);
|
transform: translateX(5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content h2 {
|
.content h2 {
|
||||||
font-family: 'Zen Old Mincho', serif;
|
font-family: 'Zen Old Mincho', serif;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
border-bottom: 2px solid var(--border-color);
|
font-size: 2rem;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 15px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-item {
|
.link-item {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
background-color: #FFFFFF;
|
background-color: var(--white);
|
||||||
transition: box-shadow 0.3s ease, transform 0.2s ease;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-item:hover {
|
.link-item:hover {
|
||||||
box-shadow: 0 8px 20px var(--shadow-light);
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||||
transform: translateY(-3px);
|
transform: translateY(-5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-item .thumbnail {
|
.link-item .thumbnail {
|
||||||
width: 150px; /* Slightly larger thumbnail */
|
width: 140px;
|
||||||
height: 100px;
|
height: 90px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
float: left;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-item-body {
|
.link-item-body {
|
||||||
overflow: hidden;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-item-title {
|
.link-item-title {
|
||||||
font-size: 1.3rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
@ -137,13 +155,13 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link-item-title a:hover {
|
.link-item-title a:hover {
|
||||||
color: var(--accent-color);
|
color: var(--secondary-color);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-item-url {
|
.link-item-url {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: var(--text-light);
|
color: var(--text-medium);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -157,8 +175,79 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 25px 0;
|
padding: 25px 0;
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
color: var(--bg-light);
|
color: var(--white);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
box-shadow: 0 -2px 4px var(--shadow-light);
|
box-shadow: 0 -2px 8px var(--shadow-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Featured Section Styles */
|
||||||
|
.featured-section {
|
||||||
|
background: linear-gradient(to bottom, #fff5e6, #ffe0b3); /* Warm gradient background */
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 25px;
|
||||||
|
box-shadow: 0 4px 15px rgba(255, 160, 0, 0.1);
|
||||||
|
border: 1px solid #ffcc80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-section h3 {
|
||||||
|
font-family: 'Zen Old Mincho', serif;
|
||||||
|
color: #e65100; /* Darker orange for heading */
|
||||||
|
font-size: 1.6rem;
|
||||||
|
border-bottom: 3px solid #ff9800; /* Orange underline */
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item {
|
||||||
|
background-color: var(--white);
|
||||||
|
border: 1px solid #ffecb3;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item h4 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-medium);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item .btn {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item .btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item .btn-primary:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
border-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item .btn-secondary {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-item .btn-secondary:hover {
|
||||||
|
background-color: #c00c4e;
|
||||||
|
border-color: #c00c4e;
|
||||||
}
|
}
|
||||||
58
db/apply_migrations.php
Normal file
58
db/apply_migrations.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/config.php';
|
||||||
|
|
||||||
|
echo "Applying migrations...
|
||||||
|
";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Create migrations table if it doesn't exist
|
||||||
|
$pdo->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS `migrations` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`migration_name` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`applied_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$migrationsDir = __DIR__ . '/migrations/';
|
||||||
|
$migrationFiles = glob($migrationsDir . '*.sql');
|
||||||
|
sort($migrationFiles);
|
||||||
|
|
||||||
|
foreach ($migrationFiles as $file) {
|
||||||
|
$migrationName = basename($file);
|
||||||
|
|
||||||
|
// Check if migration has already been applied
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM `migrations` WHERE `migration_name` = ?");
|
||||||
|
$stmt->execute([$migrationName]);
|
||||||
|
if ($stmt->fetchColumn() > 0) {
|
||||||
|
echo "Skipping already applied migration: $migrationName
|
||||||
|
";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Applying migration: $migrationName
|
||||||
|
";
|
||||||
|
$sql = file_get_contents($file);
|
||||||
|
$pdo->exec($sql);
|
||||||
|
|
||||||
|
// Record the applied migration
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO `migrations` (`migration_name`) VALUES (?)");
|
||||||
|
$stmt->execute([$migrationName]);
|
||||||
|
echo "Successfully applied migration: $migrationName
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All migrations applied.
|
||||||
|
";
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Database error: " . $e->getMessage() . "
|
||||||
|
";
|
||||||
|
exit(1);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error: " . $e->getMessage() . "
|
||||||
|
";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
1
db/migrations/002_add_paused_status_to_links.sql
Normal file
1
db/migrations/002_add_paused_status_to_links.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `links` MODIFY COLUMN `status` ENUM('pending', 'approved', 'rejected', 'paused') NOT NULL DEFAULT 'pending';
|
||||||
25
index.php
25
index.php
@ -92,9 +92,9 @@ $current_links = $link_stmt->fetchAll();
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="container my-4">
|
<div class="main-wrapper container my-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Categories Sidebar -->
|
<!-- Left Column - Categories -->
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<aside class="category-list">
|
<aside class="category-list">
|
||||||
<h3>Categories</h3>
|
<h3>Categories</h3>
|
||||||
@ -109,8 +109,8 @@ $current_links = $link_stmt->fetchAll();
|
|||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Center Column - Main Content -->
|
||||||
<div class="col-md-9">
|
<div class="col-md-6">
|
||||||
<main class="content">
|
<main class="content">
|
||||||
<h2><?php echo htmlspecialchars($current_category_name); ?></h2>
|
<h2><?php echo htmlspecialchars($current_category_name); ?></h2>
|
||||||
|
|
||||||
@ -151,8 +151,25 @@ $current_links = $link_stmt->fetchAll();
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<aside class="featured-section">
|
||||||
|
<h3>Featured Content</h3>
|
||||||
|
<div class="featured-item">
|
||||||
|
<h4>Special Link 1</h4>
|
||||||
|
<p>A description for a special featured link.</p>
|
||||||
|
<a href="#" class="btn btn-sm btn-primary">View More</a>
|
||||||
|
</div>
|
||||||
|
<div class="featured-item mt-3">
|
||||||
|
<h4>Announcement</h4>
|
||||||
|
<p>Check out our latest updates!</p>
|
||||||
|
<a href="#" class="btn btn-sm btn-secondary">Read Blog</a>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div><!-- /main-wrapper -->
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<p>© <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p>
|
<p>© <?php echo date("Y"); ?> <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'Web Directory'); ?>. All Rights Reserved.</p>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user