add: admin, add: fully customizable information via admin side

This commit is contained in:
ssbg04 committed 2026-06-05 19:39:17 +08:00
1 parent 2d5a7850d5
commit 7eb61827f5
23 files changed
+2160 -178

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header("Location: login.php");
exit();
}
?>
+811
View File
@@ -0,0 +1,811 @@
<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../models/settings.php';
require_once __DIR__ . '/../models/projects.php';
require_once __DIR__ . '/../models/certifications.php';
require_once __DIR__ . '/../models/articles.php';
require_once __DIR__ . '/../models/techstack.php';
// Generate CSRF token if not exists
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$success_msg = '';
$error_msg = '';
if (isset($_SESSION['success'])) {
$success_msg = $_SESSION['success'];
unset($_SESSION['success']);
}
if (isset($_SESSION['error'])) {
$error_msg = $_SESSION['error'];
unset($_SESSION['error']);
}
// Determine current active tab
$active_tab = isset($_GET['tab']) ? trim($_GET['tab']) : 'profile';
if (isset($_GET['edit_project_id'])) $active_tab = 'projects';
if (isset($_GET['edit_cert_id'])) $active_tab = 'certifications';
if (isset($_GET['edit_article_id'])) $active_tab = 'articles';
// Handle Form Submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// CSRF check
$post_token = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $post_token)) {
$_SESSION['error'] = 'Security check failed. CSRF token invalid.';
header("Location: dashboard.php?tab=" . urlencode($active_tab));
exit();
}
$action = $_POST['action'] ?? '';
if ($action === 'update_settings') {
$about_text_1 = trim($_POST['about_text_1'] ?? '');
$about_text_2 = trim($_POST['about_text_2'] ?? '');
$years_coding = trim($_POST['years_coding'] ?? '');
$projects_count = trim($_POST['projects_count'] ?? '');
$certifications_count = trim($_POST['certifications_count'] ?? '');
$tiktok_url = trim($_POST['tiktok_url'] ?? '');
$facebook_url = trim($_POST['facebook_url'] ?? '');
$instagram_url = trim($_POST['instagram_url'] ?? '');
$linkedin_url = trim($_POST['linkedin_url'] ?? '');
$github_url = trim($_POST['github_url'] ?? '');
if (updateSettings($mysqli, $about_text_1, $about_text_2, $years_coding, $projects_count, $certifications_count, $tiktok_url, $facebook_url, $instagram_url, $linkedin_url, $github_url)) {
$_SESSION['success'] = 'Profile settings updated successfully.';
} else {
$_SESSION['error'] = 'Failed to update profile settings.';
}
header("Location: dashboard.php?tab=profile");
exit();
}
// Projects CRUD Handlers
if ($action === 'add_project') {
$title = trim($_POST['title'] ?? '');
$des = trim($_POST['des'] ?? '');
$techstack = trim($_POST['techstack'] ?? '');
$url = trim($_POST['url'] ?? '');
$img_url = trim($_POST['img_url'] ?? '');
if (!empty($title) && !empty($des)) {
if (addProject($mysqli, $title, $des, $techstack, $url, $img_url)) {
$_SESSION['success'] = 'Project added successfully.';
} else {
$_SESSION['error'] = 'Failed to add project.';
}
} else {
$_SESSION['error'] = 'Title and description are required.';
}
header("Location: dashboard.php?tab=projects");
exit();
}
if ($action === 'edit_project') {
$id = intval($_POST['id'] ?? 0);
$title = trim($_POST['title'] ?? '');
$des = trim($_POST['des'] ?? '');
$techstack = trim($_POST['techstack'] ?? '');
$url = trim($_POST['url'] ?? '');
$img_url = trim($_POST['img_url'] ?? '');
if ($id > 0 && !empty($title) && !empty($des)) {
if (editProject($mysqli, $id, $title, $des, $techstack, $url, $img_url)) {
$_SESSION['success'] = 'Project updated successfully.';
} else {
$_SESSION['error'] = 'Failed to update project.';
}
} else {
$_SESSION['error'] = 'Invalid project data.';
}
header("Location: dashboard.php?tab=projects");
exit();
}
if ($action === 'delete_project') {
$id = intval($_POST['id'] ?? 0);
if ($id > 0) {
if (deleteProject($mysqli, $id)) {
$_SESSION['success'] = 'Project deleted successfully.';
} else {
$_SESSION['error'] = 'Failed to delete project.';
}
}
header("Location: dashboard.php?tab=projects");
exit();
}
// Certifications CRUD Handlers
if ($action === 'add_certification') {
$title = trim($_POST['title'] ?? '');
$issuer = trim($_POST['issuer'] ?? '');
$date_issued = trim($_POST['date_issued'] ?? '');
$icon = trim($_POST['icon'] ?? '');
if (!empty($title) && !empty($issuer)) {
if (addCertification($mysqli, $title, $issuer, $date_issued, $icon)) {
$_SESSION['success'] = 'Certification added successfully.';
} else {
$_SESSION['error'] = 'Failed to add certification.';
}
} else {
$_SESSION['error'] = 'Title and issuer are required.';
}
header("Location: dashboard.php?tab=certifications");
exit();
}
if ($action === 'edit_certification') {
$id = intval($_POST['id'] ?? 0);
$title = trim($_POST['title'] ?? '');
$issuer = trim($_POST['issuer'] ?? '');
$date_issued = trim($_POST['date_issued'] ?? '');
$icon = trim($_POST['icon'] ?? '');
if ($id > 0 && !empty($title) && !empty($issuer)) {
if (editCertification($mysqli, $id, $title, $issuer, $date_issued, $icon)) {
$_SESSION['success'] = 'Certification updated successfully.';
} else {
$_SESSION['error'] = 'Failed to update certification.';
}
} else {
$_SESSION['error'] = 'Invalid certification data.';
}
header("Location: dashboard.php?tab=certifications");
exit();
}
if ($action === 'delete_certification') {
$id = intval($_POST['id'] ?? 0);
if ($id > 0) {
if (deleteCertification($mysqli, $id)) {
$_SESSION['success'] = 'Certification deleted successfully.';
} else {
$_SESSION['error'] = 'Failed to delete certification.';
}
}
header("Location: dashboard.php?tab=certifications");
exit();
}
// Articles CRUD Handlers
if ($action === 'add_article') {
$title = trim($_POST['title'] ?? '');
$excerpt = trim($_POST['excerpt'] ?? '');
$date_published = trim($_POST['date_published'] ?? '');
$tag = trim($_POST['tag'] ?? '');
$url = trim($_POST['url'] ?? '#');
if (!empty($title) && !empty($excerpt)) {
if (addArticle($mysqli, $title, $excerpt, $date_published, $tag, $url)) {
$_SESSION['success'] = 'Article added successfully.';
} else {
$_SESSION['error'] = 'Failed to add article.';
}
} else {
$_SESSION['error'] = 'Title and excerpt are required.';
}
header("Location: dashboard.php?tab=articles");
exit();
}
if ($action === 'edit_article') {
$id = intval($_POST['id'] ?? 0);
$title = trim($_POST['title'] ?? '');
$excerpt = trim($_POST['excerpt'] ?? '');
$date_published = trim($_POST['date_published'] ?? '');
$tag = trim($_POST['tag'] ?? '');
$url = trim($_POST['url'] ?? '#');
if ($id > 0 && !empty($title) && !empty($excerpt)) {
if (editArticle($mysqli, $id, $title, $excerpt, $date_published, $tag, $url)) {
$_SESSION['success'] = 'Article updated successfully.';
} else {
$_SESSION['error'] = 'Failed to update article.';
}
} else {
$_SESSION['error'] = 'Invalid article data.';
}
header("Location: dashboard.php?tab=articles");
exit();
}
if ($action === 'delete_article') {
$id = intval($_POST['id'] ?? 0);
if ($id > 0) {
if (deleteArticle($mysqli, $id)) {
$_SESSION['success'] = 'Article deleted successfully.';
} else {
$_SESSION['error'] = 'Failed to delete article.';
}
}
header("Location: dashboard.php?tab=articles");
exit();
}
// Tech Stack Handlers
if ($action === 'add_techstack') {
$tech_name = trim($_POST['tech_name'] ?? '');
if (!empty($tech_name)) {
if (addTechStackItem($mysqli, $tech_name)) {
$_SESSION['success'] = 'Tech stack item added successfully.';
} else {
$_SESSION['error'] = 'Failed to add tech stack item.';
}
} else {
$_SESSION['error'] = 'Tech name cannot be empty.';
}
header("Location: dashboard.php?tab=techstack");
exit();
}
if ($action === 'delete_techstack') {
$id = intval($_POST['id'] ?? 0);
if ($id > 0) {
if (deleteTechStackItem($mysqli, $id)) {
$_SESSION['success'] = 'Tech stack item deleted successfully.';
} else {
$_SESSION['error'] = 'Failed to delete tech stack item.';
}
}
header("Location: dashboard.php?tab=techstack");
exit();
}
}
// Load Data for Display
$settings = getSettings($mysqli);
$projects = getProjects($mysqli);
$certifications = getCertifications($mysqli);
$articles = getArticles($mysqli);
$techstack = getTechStackRaw($mysqli);
// Retrieve individual edit records if requested
$edit_project = null;
if (isset($_GET['edit_project_id'])) {
$edit_project = getProjectById($mysqli, intval($_GET['edit_project_id']));
}
$edit_cert = null;
if (isset($_GET['edit_cert_id'])) {
$edit_cert = getCertificationById($mysqli, intval($_GET['edit_cert_id']));
}
$edit_article = null;
if (isset($_GET['edit_article_id'])) {
$edit_article = getArticleById($mysqli, intval($_GET['edit_article_id']));
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard | Portfolio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Mobile Header -->
<header class="mobile-top-bar" style="display: none; width: 100%; justify-content: space-between; align-items: center; padding: 15px 20px; background: var(--bg-secondary); border-bottom: 1px solid var(--border-color); position: fixed; top: 0; left: 0; z-index: 101;">
<div class="logo-text" style="font-size: 1.25rem;">
<span>Admin Panel</span>
<span class="logo-dot"></span>
</div>
<button class="mobile-nav-toggle" id="mobile-toggle-btn" style="border: none; background: none; font-size: 1.5rem; color: #fff; cursor: pointer;">☰</button>
</header>
<!-- Sidebar Navigation -->
<div class="sidebar" id="sidebar">
<div class="logo-container">
<div class="logo-text">
<span>Admin Panel</span>
<span class="logo-dot"></span>
</div>
</div>
<div class="sidebar-nav">
<button class="nav-link <?php echo $active_tab === 'profile' ? 'active' : ''; ?>" onclick="switchTab('profile')">👤 Profile & Stats</button>
<button class="nav-link <?php echo $active_tab === 'projects' ? 'active' : ''; ?>" onclick="switchTab('projects')">💻 Projects</button>
<button class="nav-link <?php echo $active_tab === 'certifications' ? 'active' : ''; ?>" onclick="switchTab('certifications')">🏆 Certifications</button>
<button class="nav-link <?php echo $active_tab === 'articles' ? 'active' : ''; ?>" onclick="switchTab('articles')">📰 Blog Articles</button>
<button class="nav-link <?php echo $active_tab === 'techstack' ? 'active' : ''; ?>" onclick="switchTab('techstack')">⚡ Tech Stack</button>
</div>
<div class="sidebar-footer">
<a href="logout.php" class="logout-btn">Logout ➔</a>
</div>
</div>
<!-- Main Content -->
<main class="main-content" id="main-content">
<div class="top-bar">
<div class="page-title">
<h1 id="tab-title-text">Profile & Stats Management</h1>
<p>Welcome back, <?php echo htmlspecialchars($_SESSION['admin_username']); ?>.</p>
</div>
<div class="user-badge">
<span class="status-dot"></span>
<span>Active Session</span>
</div>
</div>
<!-- System Alerts -->
<?php if (!empty($success_msg)): ?>
<div class="alert alert-success">
<span>✓</span>
<div><?php echo htmlspecialchars($success_msg); ?></div>
</div>
<?php endif; ?>
<?php if (!empty($error_msg)): ?>
<div class="alert alert-danger">
<span>⚠️</span>
<div><?php echo htmlspecialchars($error_msg); ?></div>
</div>
<?php endif; ?>
<!-- ================= TAB: PROFILE ================= -->
<div id="tab-profile" class="tab-pane <?php echo $active_tab === 'profile' ? 'active' : ''; ?>">
<form action="dashboard.php?tab=profile" method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="update_settings">
<div class="card">
<div class="card-title">📝 About Me Bio Copy</div>
<div class="form-group">
<label for="about_text_1">About Paragraph 1</label>
<textarea name="about_text_1" id="about_text_1" required><?php echo htmlspecialchars($settings['about_text_1'] ?? ''); ?></textarea>
</div>
<div class="form-group">
<label for="about_text_2">About Paragraph 2</label>
<textarea name="about_text_2" id="about_text_2" required><?php echo htmlspecialchars($settings['about_text_2'] ?? ''); ?></textarea>
</div>
</div>
<div class="card">
<div class="card-title">📈 Statistics Counters</div>
<div class="form-grid">
<div class="form-group">
<label for="years_coding">Years Coding Count (e.g. 3+)</label>
<input type="text" name="years_coding" id="years_coding" value="<?php echo htmlspecialchars($settings['years_coding'] ?? ''); ?>" required>
</div>
<div class="form-group">
<label for="projects_count">Projects Built Count (e.g. 10+)</label>
<input type="text" name="projects_count" id="projects_count" value="<?php echo htmlspecialchars($settings['projects_count'] ?? ''); ?>" required>
</div>
<div class="form-group">
<label for="certifications_count">Certifications Count (e.g. 6+)</label>
<input type="text" name="certifications_count" id="certifications_count" value="<?php echo htmlspecialchars($settings['certifications_count'] ?? ''); ?>" required>
</div>
</div>
</div>
<div class="card">
<div class="card-title">🔗 Social Links</div>
<div class="form-grid">
<div class="form-group">
<label for="tiktok_url">TikTok URL</label>
<input type="url" name="tiktok_url" id="tiktok_url" value="<?php echo htmlspecialchars($settings['tiktok_url'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="github_url">GitHub URL</label>
<input type="url" name="github_url" id="github_url" value="<?php echo htmlspecialchars($settings['github_url'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="linkedin_url">LinkedIn URL</label>
<input type="url" name="linkedin_url" id="linkedin_url" value="<?php echo htmlspecialchars($settings['linkedin_url'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="facebook_url">Facebook URL</label>
<input type="url" name="facebook_url" id="facebook_url" value="<?php echo htmlspecialchars($settings['facebook_url'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="instagram_url">Instagram URL</label>
<input type="url" name="instagram_url" id="instagram_url" value="<?php echo htmlspecialchars($settings['instagram_url'] ?? ''); ?>">
</div>
</div>
</div>
<button type="submit" class="btn primary">Save Changes</button>
</form>
</div>
<!-- ================= TAB: PROJECTS ================= -->
<div id="tab-projects" class="tab-pane <?php echo $active_tab === 'projects' ? 'active' : ''; ?>">
<!-- Add or Edit Project Form -->
<div class="card <?php echo $edit_project ? 'form-overlay-card' : ''; ?>">
<div class="card-title">
<?php echo $edit_project ? '✏️ Edit Project' : '➕ Add New Project'; ?>
</div>
<form action="dashboard.php?tab=projects" method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="<?php echo $edit_project ? 'edit_project' : 'add_project'; ?>">
<?php if ($edit_project): ?>
<input type="hidden" name="id" value="<?php echo $edit_project['id']; ?>">
<?php endif; ?>
<div class="form-grid">
<div class="form-group">
<label for="proj_title">Project Title</label>
<input type="text" name="title" id="proj_title" value="<?php echo htmlspecialchars($edit_project['title'] ?? ''); ?>" required placeholder="e.g. UniMarket E-Commerce">
</div>
<div class="form-group">
<label for="proj_tech">Tech Stack (comma-separated list)</label>
<input type="text" name="techstack" id="proj_tech" value="<?php echo htmlspecialchars($edit_project['techstack'] ?? ''); ?>" placeholder="e.g. React, Node.js, Firebase">
</div>
<div class="form-group">
<label for="proj_url">Project Link / URL</label>
<input type="text" name="url" id="proj_url" value="<?php echo htmlspecialchars($edit_project['url'] ?? ''); ?>" placeholder="e.g. https://github.com/...">
</div>
<div class="form-group">
<label for="proj_img">Preview Image URL</label>
<input type="text" name="img_url" id="proj_img" value="<?php echo htmlspecialchars($edit_project['img_url'] ?? ''); ?>" placeholder="e.g. https://images.unsplash.com/...">
</div>
<div class="form-group form-group-full">
<label for="proj_des">Project Description</label>
<textarea name="des" id="proj_des" required placeholder="Describe the application features, challenges and stacks used..."><?php echo htmlspecialchars($edit_project['des'] ?? ''); ?></textarea>
</div>
</div>
<div style="display:flex; gap: 12px; margin-top: 10px;">
<button type="submit" class="btn primary"><?php echo $edit_project ? 'Update Project' : 'Add Project'; ?></button>
<?php if ($edit_project): ?>
<a href="dashboard.php?tab=projects" class="btn secondary">Cancel</a>
<?php endif; ?>
</div>
</form>
</div>
<!-- Projects List -->
<div class="card">
<div class="card-title">📁 Current Projects</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>Image</th>
<th>Title</th>
<th>Tech Stack</th>
<th>Description</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($projects)): ?>
<tr>
<td colspan="5" class="text-center" style="color: var(--text-muted);">No projects found. Add one above.</td>
</tr>
<?php else: ?>
<?php foreach ($projects as $proj): ?>
<tr>
<td>
<?php if (!empty($proj['img_url'])): ?>
<img src="<?php echo htmlspecialchars($proj['img_url']); ?>" alt="" style="width: 50px; height: 35px; object-fit: cover; border-radius: 4px; border: 1px solid var(--border-color);">
<?php else: ?>
<span style="font-size: 1.2rem;">🖼️</span>
<?php endif; ?>
</td>
<td style="font-weight: 600; color: #fff;"><?php echo htmlspecialchars($proj['title']); ?></td>
<td style="font-size: 0.85rem; color: var(--accent);"><?php echo htmlspecialchars($proj['techstack']); ?></td>
<td style="font-size: 0.85rem; color: var(--text-muted); max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"><?php echo htmlspecialchars($proj['des']); ?></td>
<td>
<div class="btn-actions">
<a href="dashboard.php?edit_project_id=<?php echo $proj['id']; ?>" class="btn secondary btn-sm">Edit</a>
<form action="dashboard.php?tab=projects" method="POST" onsubmit="return confirm('Are you sure you want to delete this project?');" style="display:inline;">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="delete_project">
<input type="hidden" name="id" value="<?php echo $proj['id']; ?>">
<button type="submit" class="btn danger btn-sm">Delete</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- ================= TAB: CERTIFICATIONS ================= -->
<div id="tab-certifications" class="tab-pane <?php echo $active_tab === 'certifications' ? 'active' : ''; ?>">
<!-- Add or Edit Cert Form -->
<div class="card <?php echo $edit_cert ? 'form-overlay-card' : ''; ?>">
<div class="card-title">
<?php echo $edit_cert ? '✏️ Edit Certification' : '➕ Add New Certification'; ?>
</div>
<form action="dashboard.php?tab=certifications" method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="<?php echo $edit_cert ? 'edit_certification' : 'add_certification'; ?>">
<?php if ($edit_cert): ?>
<input type="hidden" name="id" value="<?php echo $edit_cert['id']; ?>">
<?php endif; ?>
<div class="form-grid">
<div class="form-group">
<label for="cert_title">Certification Title</label>
<input type="text" name="title" id="cert_title" value="<?php echo htmlspecialchars($edit_cert['title'] ?? ''); ?>" required placeholder="e.g. Responsive Web Design">
</div>
<div class="form-group">
<label for="cert_issuer">Issuer / Provider</label>
<input type="text" name="issuer" id="cert_issuer" value="<?php echo htmlspecialchars($edit_cert['issuer'] ?? ''); ?>" required placeholder="e.g. FreeCodeCamp">
</div>
<div class="form-group">
<label for="cert_date">Date Issued</label>
<input type="text" name="date_issued" id="cert_date" value="<?php echo htmlspecialchars($edit_cert['date_issued'] ?? ''); ?>" placeholder="e.g. Dec 2024">
</div>
<div class="form-group">
<label for="cert_icon">Icon (Single Emoji)</label>
<input type="text" name="icon" id="cert_icon" value="<?php echo htmlspecialchars($edit_cert['icon'] ?? '🏆'); ?>" placeholder="e.g. 🏆">
</div>
</div>
<div style="display:flex; gap: 12px; margin-top: 10px;">
<button type="submit" class="btn primary"><?php echo $edit_cert ? 'Update Certification' : 'Add Certification'; ?></button>
<?php if ($edit_cert): ?>
<a href="dashboard.php?tab=certifications" class="btn secondary">Cancel</a>
<?php endif; ?>
</div>
</form>
</div>
<!-- Certs List -->
<div class="card">
<div class="card-title">🏆 Current Certifications</div>
<div class="table-container">
<table>
<thead>
<tr>
<th style="width: 60px;" class="text-center">Icon</th>
<th>Title</th>
<th>Issuer</th>
<th>Date</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($certifications)): ?>
<tr>
<td colspan="5" class="text-center" style="color: var(--text-muted);">No certifications found. Add one above.</td>
</tr>
<?php else: ?>
<?php foreach ($certifications as $cert): ?>
<tr>
<td class="text-center" style="font-size: 1.5rem;"><?php echo htmlspecialchars($cert['icon']); ?></td>
<td style="font-weight: 600; color: #fff;"><?php echo htmlspecialchars($cert['title']); ?></td>
<td style="color: var(--text-muted);"><?php echo htmlspecialchars($cert['issuer']); ?></td>
<td><?php echo htmlspecialchars($cert['date_issued']); ?></td>
<td>
<div class="btn-actions">
<a href="dashboard.php?edit_cert_id=<?php echo $cert['id']; ?>" class="btn secondary btn-sm">Edit</a>
<form action="dashboard.php?tab=certifications" method="POST" onsubmit="return confirm('Are you sure you want to delete this certification?');" style="display:inline;">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="delete_certification">
<input type="hidden" name="id" value="<?php echo $cert['id']; ?>">
<button type="submit" class="btn danger btn-sm">Delete</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- ================= TAB: ARTICLES ================= -->
<div id="tab-articles" class="tab-pane <?php echo $active_tab === 'articles' ? 'active' : ''; ?>">
<!-- Add or Edit Article Form -->
<div class="card <?php echo $edit_article ? 'form-overlay-card' : ''; ?>">
<div class="card-title">
<?php echo $edit_article ? '✏️ Edit Article' : '➕ Add New Blog Article'; ?>
</div>
<form action="dashboard.php?tab=articles" method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="<?php echo $edit_article ? 'edit_article' : 'add_article'; ?>">
<?php if ($edit_article): ?>
<input type="hidden" name="id" value="<?php echo $edit_article['id']; ?>">
<?php endif; ?>
<div class="form-grid">
<div class="form-group">
<label for="art_title">Article Title</label>
<input type="text" name="title" id="art_title" value="<?php echo htmlspecialchars($edit_article['title'] ?? ''); ?>" required placeholder="e.g. Guide to React Custom Hooks">
</div>
<div class="form-group">
<label for="art_date">Publish Date</label>
<input type="text" name="date_published" id="art_date" value="<?php echo htmlspecialchars($edit_article['date_published'] ?? ''); ?>" placeholder="e.g. Feb 10, 2026">
</div>
<div class="form-group">
<label for="art_tag">Tag / Category</label>
<input type="text" name="tag" id="art_tag" value="<?php echo htmlspecialchars($edit_article['tag'] ?? ''); ?>" placeholder="e.g. React">
</div>
<div class="form-group">
<label for="art_url">Article Link / URL (external or '#')</label>
<input type="text" name="url" id="art_url" value="<?php echo htmlspecialchars($edit_article['url'] ?? '#'); ?>" placeholder="e.g. # or external blog URL">
</div>
<div class="form-group form-group-full">
<label for="art_excerpt">Short Excerpt</label>
<textarea name="excerpt" id="art_excerpt" required placeholder="A brief description of what this article is about..."><?php echo htmlspecialchars($edit_article['excerpt'] ?? ''); ?></textarea>
</div>
</div>
<div style="display:flex; gap: 12px; margin-top: 10px;">
<button type="submit" class="btn primary"><?php echo $edit_article ? 'Update Article' : 'Add Article'; ?></button>
<?php if ($edit_article): ?>
<a href="dashboard.php?tab=articles" class="btn secondary">Cancel</a>
<?php endif; ?>
</div>
</form>
</div>
<!-- Articles List -->
<div class="card">
<div class="card-title">📰 Current Blog Articles</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>Title</th>
<th>Tag</th>
<th>Published Date</th>
<th>Excerpt</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($articles)): ?>
<tr>
<td colspan="5" class="text-center" style="color: var(--text-muted);">No articles found. Add one above.</td>
</tr>
<?php else: ?>
<?php foreach ($articles as $art): ?>
<tr>
<td style="font-weight: 600; color: #fff;"><?php echo htmlspecialchars($art['title']); ?></td>
<td><span style="font-size: 0.85rem; padding: 4px 8px; background-color: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.15); border-radius: 4px; color: var(--accent);"><?php echo htmlspecialchars($art['tag']); ?></span></td>
<td><?php echo htmlspecialchars($art['date_published']); ?></td>
<td style="font-size: 0.85rem; color: var(--text-muted); max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"><?php echo htmlspecialchars($art['excerpt']); ?></td>
<td>
<div class="btn-actions">
<a href="dashboard.php?edit_article_id=<?php echo $art['id']; ?>" class="btn secondary btn-sm">Edit</a>
<form action="dashboard.php?tab=articles" method="POST" onsubmit="return confirm('Are you sure you want to delete this article?');" style="display:inline;">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="delete_article">
<input type="hidden" name="id" value="<?php echo $art['id']; ?>">
<button type="submit" class="btn danger btn-sm">Delete</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- ================= TAB: TECH STACK ================= -->
<div id="tab-techstack" class="tab-pane <?php echo $active_tab === 'techstack' ? 'active' : ''; ?>">
<!-- Add Tech Item -->
<div class="card">
<div class="card-title">➕ Add Technology</div>
<form action="dashboard.php?tab=techstack" method="POST" style="display: flex; gap: 12px; align-items: flex-end;">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="add_techstack">
<div class="form-group" style="flex-grow: 1; margin-bottom: 0;">
<label for="tech_name">Technology Name</label>
<input type="text" name="tech_name" id="tech_name" required placeholder="e.g. Next.js, GraphQL, PostgreSQL">
</div>
<button type="submit" class="btn primary">Add Tech</button>
</form>
</div>
<!-- Badges List -->
<div class="card">
<div class="card-title">⚡ Current Technologies</div>
<?php if (empty($techstack)): ?>
<p style="color: var(--text-muted);">No technologies configured. Add one above.</p>
<?php else: ?>
<div class="tech-badges-grid">
<?php foreach ($techstack as $tech): ?>
<div class="tech-badge-item">
<span><?php echo htmlspecialchars($tech['tech_name']); ?></span>
<form action="dashboard.php?tab=techstack" method="POST" style="display: inline;" onsubmit="return confirm('Delete this technology?');">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="hidden" name="action" value="delete_techstack">
<input type="hidden" name="id" value="<?php echo $tech['id']; ?>">
<button type="submit" class="tech-badge-delete" title="Delete tag">×</button>
</form>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</main>
<script>
// Simple JS tab switching logic
const tabs = {
'profile': 'Profile & Stats Management',
'projects': 'Projects Management',
'certifications': 'Certifications Management',
'articles': 'Blog Articles Management',
'techstack': 'Tech Stack Management'
};
function switchTab(tabId) {
// Hide all tab panes
document.querySelectorAll('.tab-pane').forEach(el => {
el.classList.remove('active');
});
// Show selected tab pane
const activePane = document.getElementById('tab-' + tabId);
if (activePane) {
activePane.classList.add('active');
}
// Remove active class from nav links
document.querySelectorAll('.nav-link').forEach(el => {
el.classList.remove('active');
});
// Add active class to corresponding link (both desktop and mobile sidebar click)
const activeLinks = document.querySelectorAll('.sidebar-nav button');
activeLinks.forEach(link => {
if (link.getAttribute('onclick') === `switchTab('${tabId}')`) {
link.classList.add('active');
}
});
// Update title text
const titleEl = document.getElementById('tab-title-text');
if (titleEl && tabs[tabId]) {
titleEl.textContent = tabs[tabId];
}
// Set URL parameter without reloading page for consistency on refresh
const url = new URL(window.location);
url.searchParams.set('tab', tabId);
// Clear edit params on tab switch
url.searchParams.delete('edit_project_id');
url.searchParams.delete('edit_cert_id');
url.searchParams.delete('edit_article_id');
window.history.pushState({}, '', url);
// Close sidebar on mobile
closeSidebar();
}
// Mobile Sidebar Toggling logic
const sidebar = document.getElementById('sidebar');
const mobileToggleBtn = document.getElementById('mobile-toggle-btn');
if (mobileToggleBtn) {
mobileToggleBtn.addEventListener('click', toggleSidebar);
}
function toggleSidebar(e) {
if (e) e.stopPropagation();
sidebar.classList.toggle('open');
}
function closeSidebar() {
sidebar.classList.remove('open');
}
// Close sidebar if user clicks outside of it on mobile
document.addEventListener('click', function(event) {
const isClickInsideSidebar = sidebar.contains(event.target);
const isClickOnToggle = mobileToggleBtn && mobileToggleBtn.contains(event.target);
if (!isClickInsideSidebar && !isClickOnToggle && window.innerWidth <= 1024) {
closeSidebar();
}
});
</script>
</body>
</html>
+148
View File
@@ -0,0 +1,148 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Redirect if already logged in
if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) {
header("Location: dashboard.php");
exit();
}
require_once __DIR__ . '/../config/db.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$password = trim($_POST['password'] ?? '');
if (!empty($username) && !empty($password)) {
$stmt = $mysqli->prepare("SELECT * FROM admin WHERE username = ?");
if ($stmt) {
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
if ($result && $result->num_rows > 0) {
$user = $result->fetch_assoc();
if (password_verify($password, $user['password'])) {
// Login successful
$_SESSION['admin_logged_in'] = true;
$_SESSION['admin_username'] = $user['username'];
header("Location: dashboard.php");
exit();
}
}
$error = 'Invalid username or password.';
$stmt->close();
} else {
$error = 'Database error. Please try again.';
}
} else {
$error = 'Please enter both username and password.';
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login | Portfolio</title>
<link rel="stylesheet" href="../assets/css/style.css">
<style>
.login-wrapper {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 20px;
background: #090d16;
}
.login-card {
background: rgba(30, 41, 59, 0.4);
border: 1px solid rgba(148, 163, 184, 0.12);
border-radius: 16px;
padding: 40px;
width: 100%;
max-width: 400px;
backdrop-filter: blur(10px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
}
.login-card h2 {
font-family: 'Outfit', sans-serif;
font-size: 1.8rem;
font-weight: 800;
color: #f8fafc;
margin-bottom: 8px;
text-align: center;
}
.login-card p.subtitle {
color: #94a3b8;
font-size: 0.9rem;
text-align: center;
margin-bottom: 24px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
color: #f8fafc;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 6px;
}
.error-message {
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.25);
padding: 10px;
border-radius: 8px;
font-size: 0.85rem;
margin-bottom: 20px;
text-align: center;
}
.back-home {
display: block;
text-align: center;
margin-top: 20px;
color: #64748b;
text-decoration: none;
font-size: 0.85rem;
transition: color 0.2s;
}
.back-home:hover {
color: #3b82f6;
}
</style>
</head>
<body class="login-wrapper">
<div class="login-card">
<h2>Admin Panel</h2>
<p class="subtitle">Enter your credentials to continue</p>
<?php if (!empty($error)): ?>
<div class="error-message"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form action="login.php" method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" name="username" id="username" placeholder="admin" required autocomplete="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" name="password" id="password" placeholder="••••••••" required autocomplete="current-password">
</div>
<button type="submit" class="btn primary full-width">Login</button>
</form>
<a href="../index.php" class="back-home">← Back to Portfolio</a>
</div>
</body>
</html>
+19
View File
@@ -0,0 +1,19 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$_SESSION = array();
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
header("Location: login.php");
exit();
?>
+516
View File
@@ -0,0 +1,516 @@
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap');
:root {
--bg-primary: #090d16;
--bg-secondary: #0f172a;
--bg-card: rgba(30, 41, 59, 0.4);
--border-color: rgba(148, 163, 184, 0.08);
--text-main: #f8fafc;
--text-muted: #94a3b8;
--accent: #3b82f6;
--accent-hover: #2563eb;
--accent-glow: rgba(59, 130, 246, 0.15);
--success: #10b981;
--danger: #ef4444;
--transition-speed: 0.25s;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(--bg-primary);
color: var(--text-main);
font-family: 'Plus Jakarta Sans', sans-serif;
min-height: 100vh;
display: flex;
overflow-x: hidden;
}
/* Sidebar Styling */
.sidebar {
width: 260px;
background-color: var(--bg-secondary);
border-right: 1px solid var(--border-color);
padding: 24px;
display: flex;
flex-direction: column;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 100;
transition: transform var(--transition-speed) ease;
}
.logo-container {
padding: 12px 8px 32px;
border-bottom: 1px solid var(--border-color);
margin-bottom: 24px;
}
.logo-text {
font-family: 'Outfit', sans-serif;
font-size: 1.5rem;
font-weight: 800;
color: #fff;
display: flex;
align-items: center;
gap: 8px;
}
.logo-dot {
width: 8px;
height: 8px;
background-color: var(--accent);
border-radius: 50%;
box-shadow: 0 0 10px var(--accent);
}
.sidebar-nav {
display: flex;
flex-direction: column;
gap: 8px;
flex-grow: 1;
}
.nav-link {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
color: var(--text-muted);
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
border-radius: 8px;
cursor: pointer;
background: transparent;
border: none;
text-align: left;
width: 100%;
transition: all var(--transition-speed) ease;
}
.nav-link:hover, .nav-link.active {
color: var(--text-main);
background-color: rgba(59, 130, 246, 0.08);
}
.nav-link.active {
border-left: 3px solid var(--accent);
padding-left: 13px;
font-weight: 600;
}
.sidebar-footer {
padding-top: 16px;
border-top: 1px solid var(--border-color);
}
.logout-btn {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
color: var(--danger);
background-color: rgba(239, 68, 68, 0.05);
border: 1px solid rgba(239, 68, 68, 0.1);
border-radius: 8px;
text-decoration: none;
font-weight: 600;
font-size: 0.95rem;
transition: all var(--transition-speed) ease;
justify-content: center;
}
.logout-btn:hover {
background-color: rgba(239, 68, 68, 0.15);
box-shadow: 0 0 12px rgba(239, 68, 68, 0.1);
}
/* Main Content Area */
.main-content {
flex-grow: 1;
margin-left: 260px;
padding: 40px;
min-height: 100vh;
transition: margin var(--transition-speed) ease;
}
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
border-bottom: 1px solid var(--border-color);
padding-bottom: 20px;
}
.page-title h1 {
font-family: 'Outfit', sans-serif;
font-size: 2rem;
font-weight: 800;
letter-spacing: -0.5px;
}
.page-title p {
color: var(--text-muted);
font-size: 0.95rem;
margin-top: 4px;
}
.user-badge {
background: var(--bg-card);
border: 1px solid var(--border-color);
padding: 8px 16px;
border-radius: 30px;
font-size: 0.85rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.user-badge .status-dot {
width: 6px;
height: 6px;
background-color: var(--success);
border-radius: 50%;
}
/* Tabs & Panes */
.tab-pane {
display: none;
animation: fadeIn 0.35s ease;
}
.tab-pane.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Forms and Inputs */
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 32px;
backdrop-filter: blur(10px);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.15);
margin-bottom: 32px;
}
.card-title {
font-family: 'Outfit', sans-serif;
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 10px;
color: #fff;
border-bottom: 1px solid var(--border-color);
padding-bottom: 12px;
}
.form-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.form-group-full {
grid-column: span 2;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
color: #f8fafc;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="url"],
textarea {
width: 100%;
padding: 12px 16px;
background-color: rgba(15, 23, 42, 0.6);
border: 1px solid rgba(148, 163, 184, 0.15);
border-radius: 8px;
color: var(--text-main);
font-family: inherit;
font-size: 0.95rem;
transition: all var(--transition-speed) ease;
}
input:focus, textarea:focus {
outline: none;
border-color: var(--accent);
background-color: rgba(15, 23, 42, 0.9);
box-shadow: 0 0 0 3px var(--accent-glow);
}
textarea {
resize: vertical;
min-height: 120px;
}
/* Alert Banners */
.alert {
padding: 16px 20px;
border-radius: 10px;
font-size: 0.95rem;
font-weight: 500;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 12px;
animation: slideIn 0.3s ease;
}
.alert-success {
background-color: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.2);
color: var(--success);
}
.alert-danger {
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.2);
color: var(--danger);
}
@keyframes slideIn {
from { transform: translateY(-10px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 24px;
font-size: 0.95rem;
font-weight: 600;
border-radius: 8px;
border: none;
cursor: pointer;
transition: all var(--transition-speed) ease;
text-decoration: none;
}
.btn.primary {
background-color: var(--accent);
color: #fff;
}
.btn.primary:hover {
background-color: var(--accent-hover);
box-shadow: 0 0 16px var(--accent-glow);
transform: translateY(-1px);
}
.btn.secondary {
background-color: rgba(148, 163, 184, 0.1);
color: var(--text-main);
border: 1px solid rgba(148, 163, 184, 0.15);
}
.btn.secondary:hover {
background-color: rgba(148, 163, 184, 0.18);
transform: translateY(-1px);
}
.btn.danger {
background-color: rgba(239, 68, 68, 0.1);
color: #fca5a5;
border: 1px solid rgba(239, 68, 68, 0.2);
}
.btn.danger:hover {
background-color: var(--danger);
color: #fff;
transform: translateY(-1px);
}
.btn-sm {
padding: 8px 14px;
font-size: 0.85rem;
}
.btn-actions {
display: flex;
gap: 8px;
}
/* Management Tables / List Views */
.table-container {
overflow-x: auto;
margin-top: 16px;
border-radius: 8px;
border: 1px solid var(--border-color);
}
table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: 0.95rem;
}
th, td {
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
}
th {
background-color: rgba(15, 23, 42, 0.4);
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 0.5px;
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background-color: rgba(148, 163, 184, 0.02);
}
.text-center {
text-align: center;
}
/* Tech Stack Specifics */
.tech-badges-grid {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 24px;
}
.tech-badge-item {
background: rgba(30, 41, 59, 0.6);
border: 1px solid var(--border-color);
padding: 8px 16px;
border-radius: 20px;
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
font-weight: 500;
}
.tech-badge-delete {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 1.1rem;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
width: 16px;
height: 16px;
border-radius: 50%;
transition: color 0.2s;
}
.tech-badge-delete:hover {
color: var(--danger);
}
/* Hamburger for Mobile Dashboard */
.mobile-nav-toggle {
display: none;
background: none;
border: none;
color: var(--text-main);
font-size: 1.5rem;
cursor: pointer;
}
/* Form panel overlays (Modals / Edit forms) */
.form-overlay-card {
border: 1.5px solid rgba(59, 130, 246, 0.25);
background: rgba(15, 23, 42, 0.8);
position: relative;
animation: zoomIn 0.3s ease;
}
.form-overlay-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--accent), #8b5cf6);
border-radius: 16px 16px 0 0;
}
@keyframes zoomIn {
from { transform: scale(0.97); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
/* Responsiveness */
@media (max-width: 1024px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.open {
transform: translateX(0);
}
.main-content {
margin-left: 0;
padding: 24px;
}
.mobile-nav-toggle {
display: block;
}
}
@media (max-width: 768px) {
.form-grid {
grid-template-columns: 1fr;
}
.form-group-full {
grid-column: span 1;
}
.top-bar {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.user-badge {
align-self: flex-end;
}
}
+10 -10
View File
@@ -43,7 +43,7 @@ html, body { overflow-x: hidden; max-width: 100%; }
}
/* --- Light Mode Overrides --- */
.toggle-checkbox:not(:checked) ~ .site-wrapper {
body:has(#dark-mode-toggle:not(:checked)) {
--bg: #f8fafc;
--bg-secondary: #f1f5f9;
--card-bg: #ffffff;
@@ -232,10 +232,10 @@ nav {
}
.mode-btn:hover { border-color: var(--border-hover); transform: scale(1.05); }
.toggle-checkbox:checked ~ .site-wrapper .icon-sun { display: none; }
.toggle-checkbox:checked ~ .site-wrapper .icon-moon { display: flex; }
.toggle-checkbox:not(:checked) ~ .site-wrapper .icon-moon { display: none; }
.toggle-checkbox:not(:checked) ~ .site-wrapper .icon-sun { display: flex; }
body:has(#dark-mode-toggle:checked) .icon-sun { display: none; }
body:has(#dark-mode-toggle:checked) .icon-moon { display: flex; }
body:has(#dark-mode-toggle:not(:checked)) .icon-moon { display: none; }
body:has(#dark-mode-toggle:not(:checked)) .icon-sun { display: flex; }
/* Hamburger */
.hamburger {
@@ -429,11 +429,11 @@ h1 {
.hero-img-container:hover .img-night-normal { opacity: 0; }
/* Light mode image states */
.toggle-checkbox:not(:checked) ~ .site-wrapper .img-night-normal,
.toggle-checkbox:not(:checked) ~ .site-wrapper .img-night-hover { opacity: 0 !important; }
.toggle-checkbox:not(:checked) ~ .site-wrapper .img-day-normal { opacity: 1; z-index: 3; cursor: default; }
.toggle-checkbox:not(:checked) ~ .site-wrapper .hero-img-container:hover .img-day-hover { opacity: 1; z-index: 4; cursor: default; }
.toggle-checkbox:not(:checked) ~ .site-wrapper .hero-img-container:hover .img-day-normal { opacity: 0; }
body:has(#dark-mode-toggle:not(:checked)) .img-night-normal,
body:has(#dark-mode-toggle:not(:checked)) .img-night-hover { opacity: 0 !important; }
body:has(#dark-mode-toggle:not(:checked)) .img-day-normal { opacity: 1; z-index: 3; cursor: default; }
body:has(#dark-mode-toggle:not(:checked)) .hero-img-container:hover .img-day-hover { opacity: 1; z-index: 4; cursor: default; }
body:has(#dark-mode-toggle:not(:checked)) .hero-img-container:hover .img-day-normal { opacity: 0; }
.glow-effect {
position: absolute;
+50 -44
View File
@@ -20,10 +20,21 @@
<div class="site-wrapper">
<nav>
<nav id="main-nav">
<div class="logo">CG.</div>
<div class="nav-links">
<a href="index.php">← Home</a>
<div class="hamburger" id="hamburger" aria-label="Toggle menu" aria-expanded="false" role="button" tabindex="0">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</div>
<div class="nav-links" id="nav-links" role="navigation">
<a href="index.php#about" class="nav-item">About</a>
<a href="index.php#projects" class="nav-item">Work</a>
<a href="index.php#certificates" class="nav-item">Certs</a>
<a href="blog.php" class="nav-item">Blog</a>
<a href="index.php#contact" class="nav-item">Contact</a>
<label for="dark-mode-toggle" class="mode-btn" title="Toggle Theme" aria-label="Toggle dark/light mode">
<span class="icon-moon">🌙</span>
<span class="icon-sun">☀️</span>
@@ -41,47 +52,7 @@
<section class="section-pad reveal">
<div class="blog-grid">
<article class="blog-card reveal reveal-delay-1">
<div class="blog-meta">
<span class="blog-date">Feb 10, 2026</span>
<span class="blog-tag">React</span>
</div>
<h3 class="blog-title">Guide to React Custom Hooks</h3>
<p class="blog-excerpt">Learn how to write clean, reusable React components.</p>
<a href="#" class="read-more">Read Article →</a>
</article>
<article class="blog-card reveal reveal-delay-2">
<div class="blog-meta">
<span class="blog-date">Jan 22, 2026</span>
<span class="blog-tag">DevOps</span>
</div>
<h3 class="blog-title">Using Docker for PHP Development</h3>
<p class="blog-excerpt">How to set up PHP and Node.js projects using Docker.</p>
<a href="#" class="read-more">Read Article →</a>
</article>
<article class="blog-card reveal reveal-delay-3">
<div class="blog-meta">
<span class="blog-date">Dec 05, 2025</span>
<span class="blog-tag">Career</span>
</div>
<h3 class="blog-title">My Experience as an IT Student</h3>
<p class="blog-excerpt">Balancing school projects, freelance work, and learning new skills.</p>
<a href="#" class="read-more">Read Article →</a>
</article>
<article class="blog-card reveal reveal-delay-4">
<div class="blog-meta">
<span class="blog-date">Nov 15, 2025</span>
<span class="blog-tag">CSS</span>
</div>
<h3 class="blog-title">Implementing Dark Mode with CSS</h3>
<p class="blog-excerpt">How to build a simple dark mode toggle using pure CSS.</p>
<a href="#" class="read-more">Read Article →</a>
</article>
<?php include('views/articles.php'); ?>
</div>
</section>
@@ -111,6 +82,41 @@
const themeToggle = document.getElementById("dark-mode-toggle");
themeToggle.checked = localStorage.getItem("theme") !== "light";
themeToggle.addEventListener("change", () => localStorage.setItem("theme", themeToggle.checked ? "dark" : "light"));
// HAMBURGER MENU
const hamburger = document.getElementById("hamburger");
const navLinks = document.getElementById("nav-links");
const navItems = document.querySelectorAll(".nav-item");
function closeMenu() {
hamburger.classList.remove("active");
navLinks.classList.remove("active");
hamburger.setAttribute("aria-expanded", "false");
}
hamburger.addEventListener("click", (e) => {
e.stopPropagation();
const isOpen = hamburger.classList.toggle("active");
navLinks.classList.toggle("active");
hamburger.setAttribute("aria-expanded", isOpen);
});
// Close on nav link click
navItems.forEach(item => item.addEventListener("click", closeMenu));
// Close when clicking outside the menu
document.addEventListener("click", (e) => {
if (navLinks.classList.contains("active") && !navLinks.contains(e.target) && !hamburger.contains(e.target)) {
closeMenu();
}
});
// Close on scroll
window.addEventListener('scroll', () => {
if (navLinks.classList.contains('active')) {
closeMenu();
}
});
});
</script>
</body>
+50 -59
View File
@@ -20,11 +20,21 @@
<div class="site-wrapper">
<nav>
<nav id="main-nav">
<div class="logo">CG.</div>
<div class="nav-links">
<a href="index.php">← Home</a>
<a href="projects.php">Projects</a>
<div class="hamburger" id="hamburger" aria-label="Toggle menu" aria-expanded="false" role="button" tabindex="0">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</div>
<div class="nav-links" id="nav-links" role="navigation">
<a href="index.php#about" class="nav-item">About</a>
<a href="index.php#projects" class="nav-item">Work</a>
<a href="index.php#certificates" class="nav-item">Certs</a>
<a href="blog.php" class="nav-item">Blog</a>
<a href="index.php#contact" class="nav-item">Contact</a>
<label for="dark-mode-toggle" class="mode-btn" title="Toggle Theme" aria-label="Toggle dark/light mode">
<span class="icon-moon">🌙</span>
<span class="icon-sun">☀️</span>
@@ -42,61 +52,7 @@
<section class="section-pad">
<div class="cert-grid">
<div class="cert-card reveal reveal-delay-1">
<div class="cert-icon">🏆</div>
<div class="cert-info">
<h3>Responsive Web Design</h3>
<p class="issuer">FreeCodeCamp</p>
<span class="date">Issued Dec 2024</span>
</div>
</div>
<div class="cert-card reveal reveal-delay-2">
<div class="cert-icon">📜</div>
<div class="cert-info">
<h3>JavaScript Algorithms</h3>
<p class="issuer">HackerRank</p>
<span class="date">Issued Nov 2024</span>
</div>
</div>
<div class="cert-card reveal reveal-delay-3">
<div class="cert-icon">☁️</div>
<div class="cert-info">
<h3>Cloud Fundamentals</h3>
<p class="issuer">AWS</p>
<span class="date">Issued Oct 2024</span>
</div>
</div>
<div class="cert-card reveal reveal-delay-1">
<div class="cert-icon">🐍</div>
<div class="cert-info">
<h3>Python for Data Science</h3>
<p class="issuer">Coursera</p>
<span class="date">Issued Sept 2024</span>
</div>
</div>
<div class="cert-card reveal reveal-delay-2">
<div class="cert-icon">🔒</div>
<div class="cert-info">
<h3>Cybersecurity Essentials</h3>
<p class="issuer">Cisco</p>
<span class="date">Issued Aug 2024</span>
</div>
</div>
<div class="cert-card reveal reveal-delay-3">
<div class="cert-icon">⚛️</div>
<div class="cert-info">
<h3>React Advanced Patterns</h3>
<p class="issuer">Udemy</p>
<span class="date">Issued July 2024</span>
</div>
</div>
<?php include('views/certifications.php'); ?>
</div>
</section>
@@ -126,6 +82,41 @@
const themeToggle = document.getElementById("dark-mode-toggle");
themeToggle.checked = localStorage.getItem("theme") !== "light";
themeToggle.addEventListener("change", () => localStorage.setItem("theme", themeToggle.checked ? "dark" : "light"));
// HAMBURGER MENU
const hamburger = document.getElementById("hamburger");
const navLinks = document.getElementById("nav-links");
const navItems = document.querySelectorAll(".nav-item");
function closeMenu() {
hamburger.classList.remove("active");
navLinks.classList.remove("active");
hamburger.setAttribute("aria-expanded", "false");
}
hamburger.addEventListener("click", (e) => {
e.stopPropagation();
const isOpen = hamburger.classList.toggle("active");
navLinks.classList.toggle("active");
hamburger.setAttribute("aria-expanded", isOpen);
});
// Close on nav link click
navItems.forEach(item => item.addEventListener("click", closeMenu));
// Close when clicking outside the menu
document.addEventListener("click", (e) => {
if (navLinks.classList.contains("active") && !navLinks.contains(e.target) && !hamburger.contains(e.target)) {
closeMenu();
}
});
// Close on scroll
window.addEventListener('scroll', () => {
if (navLinks.classList.contains('active')) {
closeMenu();
}
});
});
</script>
</body>
+11
View File
@@ -0,0 +1,11 @@
<?php
require_once __DIR__ . "/../models/articles.php";
function showArticles($mysqli) {
return getArticles($mysqli);
}
function showThreeArticles($mysqli) {
return getArticles($mysqli, 3);
}
?>
+11
View File
@@ -0,0 +1,11 @@
<?php
require_once __DIR__ . "/../models/certifications.php";
function showCertifications($mysqli) {
return getCertifications($mysqli);
}
function showThreeCertifications($mysqli) {
return getCertifications($mysqli, 3);
}
?>
+7
View File
@@ -0,0 +1,7 @@
<?php
require_once __DIR__ . "/../models/settings.php";
function showSettings($mysqli) {
return getSettings($mysqli);
}
?>
+52 -61
View File
@@ -17,6 +17,25 @@ if (!function_exists('loadEnv')) {
}
loadEnv(__DIR__ . '/.env');
require_once __DIR__ . '/config/db.php';
require_once __DIR__ . '/controllers/SettingsController.php';
$settings = showSettings($mysqli);
if (!$settings) {
$settings = [
'about_text_1' => 'I am a senior IT student majoring in Software Development. I write code to solve real-world problems.',
'about_text_2' => 'My primary stack includes PHP, React, Node.js, and MySQL. I enjoy database design, front-end development, and bug fixing.',
'years_coding' => '3+',
'projects_count' => '10+',
'certifications_count' => '6+',
'tiktok_url' => 'https://www.tiktok.com/@crischarlesgarcia',
'facebook_url' => 'https://facebook.com/kristyarls345',
'instagram_url' => 'https://instagram.com/crischarlesgarcia',
'linkedin_url' => 'https://www.linkedin.com/in/cris-charles-garcia-187415303',
'github_url' => 'https://github.com/ssbg04'
];
}
$recaptchaSiteKey = getenv('RECAPTCHA_SITE_KEY') ?: ($_ENV['RECAPTCHA_SITE_KEY'] ?? '');
$contactEmail = getenv('CONTACT_EMAIL') ?: ($_ENV['CONTACT_EMAIL'] ?? '');
?>
@@ -101,8 +120,8 @@ $contactEmail = getenv('CONTACT_EMAIL') ?: ($_ENV['CONTACT_EMAIL']
</div>
<div class="about-content">
<div class="about-text">
<p>I am a senior <strong>IT student majoring in Software Development</strong>. I write code to solve real-world problems.</p>
<p>My primary stack includes <strong>PHP</strong>, <strong>React</strong>, <strong>Node.js</strong>, and <strong>MySQL</strong>. I enjoy database design, front-end development, and bug fixing.</p>
<p><?php echo nl2br(htmlspecialchars($settings['about_text_1'])); ?></p>
<p><?php echo nl2br(htmlspecialchars($settings['about_text_2'])); ?></p>
</div>
<div class="tech-stack">
<h3>Tech Stack</h3>
@@ -112,15 +131,15 @@ $contactEmail = getenv('CONTACT_EMAIL') ?: ($_ENV['CONTACT_EMAIL']
<div class="stats-row reveal reveal-delay-2">
<div class="stat-item">
<div class="stat-number">3+</div>
<div class="stat-number"><?php echo htmlspecialchars($settings['years_coding']); ?></div>
<div class="stat-label">Years Coding</div>
</div>
<div class="stat-item">
<div class="stat-number">10+</div>
<div class="stat-number"><?php echo htmlspecialchars($settings['projects_count']); ?></div>
<div class="stat-label">Projects Built</div>
</div>
<div class="stat-item">
<div class="stat-number">5+</div>
<div class="stat-number"><?php echo htmlspecialchars($settings['certifications_count']); ?></div>
<div class="stat-label">Certifications</div>
</div>
</div>
@@ -151,30 +170,7 @@ $contactEmail = getenv('CONTACT_EMAIL') ?: ($_ENV['CONTACT_EMAIL']
<h2 class="section-title">My Certifications</h2>
</div>
<div class="cert-grid">
<div class="cert-card reveal reveal-delay-1">
<div class="cert-icon">🏆</div>
<div class="cert-info">
<h3>Responsive Web Design</h3>
<p class="issuer">FreeCodeCamp</p>
<span class="date">Issued Dec 2024</span>
</div>
</div>
<div class="cert-card reveal reveal-delay-2">
<div class="cert-icon">📜</div>
<div class="cert-info">
<h3>JavaScript Algorithms</h3>
<p class="issuer">HackerRank</p>
<span class="date">Issued Nov 2024</span>
</div>
</div>
<div class="cert-card reveal reveal-delay-3">
<div class="cert-icon">☁️</div>
<div class="cert-info">
<h3>Cloud Fundamentals</h3>
<p class="issuer">AWS</p>
<span class="date">Issued Oct 2024</span>
</div>
</div>
<?php include('views/tcertifications.php'); ?>
</div>
<div class="view-all-container">
<a href="certifications.php" class="btn secondary" id="btn-all-certs">All Certifications →</a>
@@ -190,33 +186,7 @@ $contactEmail = getenv('CONTACT_EMAIL') ?: ($_ENV['CONTACT_EMAIL']
<h2 class="section-title">Latest Articles</h2>
</div>
<div class="blog-grid">
<article class="blog-card reveal reveal-delay-1">
<div class="blog-meta">
<span class="blog-date">Feb 10, 2026</span>
<span class="blog-tag">React</span>
</div>
<h3 class="blog-title">Guide to React Custom Hooks</h3>
<p class="blog-excerpt">Learn how to write clean, reusable React components.</p>
<a href="#" class="read-more">Read Article →</a>
</article>
<article class="blog-card reveal reveal-delay-2">
<div class="blog-meta">
<span class="blog-date">Jan 22, 2026</span>
<span class="blog-tag">DevOps</span>
</div>
<h3 class="blog-title">Using Docker for PHP Development</h3>
<p class="blog-excerpt">How to set up PHP and Node.js projects using Docker.</p>
<a href="#" class="read-more">Read Article →</a>
</article>
<article class="blog-card reveal reveal-delay-3">
<div class="blog-meta">
<span class="blog-date">Dec 05, 2025</span>
<span class="blog-tag">Career</span>
</div>
<h3 class="blog-title">My Experience as an IT Student</h3>
<p class="blog-excerpt">Balancing school projects, freelance work, and learning new skills.</p>
<a href="#" class="read-more">Read Article →</a>
</article>
<?php include('views/tarticles.php'); ?>
</div>
<div class="view-all-container">
<a href="blog.php" class="btn secondary" id="btn-all-articles">All Articles →</a>
@@ -234,22 +204,43 @@ $contactEmail = getenv('CONTACT_EMAIL') ?: ($_ENV['CONTACT_EMAIL']
<div class="contact-container">
<p>Feel free to reach out if you have a project idea, a job opportunity, or any questions.</p>
<?php if (isset($_GET['status'])): ?>
<?php if ($_GET['status'] === 'success'): ?>
<p style="color: #22c55e; font-weight: 500; background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.2); padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: center;">Your message has been sent successfully!</p>
<?php elseif ($_GET['status'] === 'error'): ?>
<p style="color: #ef4444; font-weight: 500; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.2); padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: center;"><?php echo htmlspecialchars($_GET['msg'] ?? 'An error occurred. Please try again.'); ?></p>
<?php endif; ?>
<?php endif; ?>
<div class="social-links">
<a href="https://github.com/ssbg04" target="_blank" class="social-btn" aria-label="GitHub" id="link-github">
<?php if (!empty($settings['github_url'])): ?>
<a href="<?php echo htmlspecialchars($settings['github_url']); ?>" target="_blank" class="social-btn" aria-label="GitHub" id="link-github">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
</a>
<a href="https://www.linkedin.com/in/cris-charles-garcia-187415303" target="_blank" class="social-btn" aria-label="LinkedIn" id="link-linkedin">
<?php endif; ?>
<?php if (!empty($settings['linkedin_url'])): ?>
<a href="<?php echo htmlspecialchars($settings['linkedin_url']); ?>" target="_blank" class="social-btn" aria-label="LinkedIn" id="link-linkedin">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
</a>
<a href="https://facebook.com/kristyarls345" target="_blank" class="social-btn" aria-label="Facebook" id="link-facebook">
<?php endif; ?>
<?php if (!empty($settings['facebook_url'])): ?>
<a href="<?php echo htmlspecialchars($settings['facebook_url']); ?>" target="_blank" class="social-btn" aria-label="Facebook" id="link-facebook">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg>
</a>
<a href="https://instagram.com/crischarlesgarcia" target="_blank" class="social-btn" aria-label="Instagram" id="link-instagram">
<?php endif; ?>
<?php if (!empty($settings['instagram_url'])): ?>
<a href="<?php echo htmlspecialchars($settings['instagram_url']); ?>" target="_blank" class="social-btn" aria-label="Instagram" id="link-instagram">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line></svg>
</a>
<?php endif; ?>
<?php if (!empty($settings['tiktok_url'])): ?>
<a href="<?php echo htmlspecialchars($settings['tiktok_url']); ?>" target="_blank" class="social-btn" aria-label="TikTok" id="link-tiktok">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 12a4 4 0 1 0 4 4V4a5 5 0 0 0 5 5"></path></svg>
</a>
<?php endif; ?>
</div>
<form action="https://formsubmit.co/<?php echo htmlspecialchars($contactEmail); ?>" method="POST" class="email-form" id="contact-form">
<form action="send_contact.php" method="POST" class="email-form" id="contact-form">
<input type="hidden" name="_captcha" value="false">
<input type="text" name="name" id="form-name" placeholder="Name" required>
<input type="text" name="subject" id="form-subject" placeholder="Subject" required>
+65
View File
@@ -0,0 +1,65 @@
<?php
require_once __DIR__ . "/../config/db.php";
function getArticles($mysqli, $limit = null) {
$articles = [];
$sql = "SELECT * FROM articles ORDER BY id DESC";
if ($limit !== null) {
$sql .= " LIMIT " . intval($limit);
}
$result = $mysqli->query($sql);
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$articles[] = $row;
}
}
return $articles;
}
// Function alias for compatibility with any controllers using showThreeArticles
function getThreeArticles($mysqli) {
return getArticles($mysqli, 3);
}
function getArticleById($mysqli, $id) {
$sql = "SELECT * FROM articles WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_assoc();
}
return null;
}
function addArticle($mysqli, $title, $excerpt, $date_published, $tag, $url) {
$sql = "INSERT INTO articles (title, excerpt, date_published, tag, url) VALUES (?, ?, ?, ?, ?)";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("sssss", $title, $excerpt, $date_published, $tag, $url);
return $stmt->execute();
}
return false;
}
function editArticle($mysqli, $id, $title, $excerpt, $date_published, $tag, $url) {
$sql = "UPDATE articles SET title = ?, excerpt = ?, date_published = ?, tag = ?, url = ? WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("sssssi", $title, $excerpt, $date_published, $tag, $url, $id);
return $stmt->execute();
}
return false;
}
function deleteArticle($mysqli, $id) {
$sql = "DELETE FROM articles WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("i", $id);
return $stmt->execute();
}
return false;
}
?>
+60
View File
@@ -0,0 +1,60 @@
<?php
require_once __DIR__ . "/../config/db.php";
function getCertifications($mysqli, $limit = null) {
$certs = [];
$sql = "SELECT * FROM certifications ORDER BY id DESC";
if ($limit !== null) {
$sql .= " LIMIT " . intval($limit);
}
$result = $mysqli->query($sql);
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$certs[] = $row;
}
}
return $certs;
}
function getCertificationById($mysqli, $id) {
$sql = "SELECT * FROM certifications WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_assoc();
}
return null;
}
function addCertification($mysqli, $title, $issuer, $date_issued, $icon) {
$sql = "INSERT INTO certifications (title, issuer, date_issued, icon) VALUES (?, ?, ?, ?)";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("ssss", $title, $issuer, $date_issued, $icon);
return $stmt->execute();
}
return false;
}
function editCertification($mysqli, $id, $title, $issuer, $date_issued, $icon) {
$sql = "UPDATE certifications SET title = ?, issuer = ?, date_issued = ?, icon = ? WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("ssssi", $title, $issuer, $date_issued, $icon, $id);
return $stmt->execute();
}
return false;
}
function deleteCertification($mysqli, $id) {
$sql = "DELETE FROM certifications WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("i", $id);
return $stmt->execute();
}
return false;
}
?>
+41
View File
@@ -31,4 +31,45 @@ function getThreeProjects($mysqli){
return $projects;
}
function getProjectById($mysqli, $id) {
$sql = "SELECT * FROM projects WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_assoc();
}
return null;
}
function addProject($mysqli, $title, $des, $techstack, $url, $img_url) {
$sql = "INSERT INTO projects (title, des, techstack, url, img_url) VALUES (?, ?, ?, ?, ?)";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("sssss", $title, $des, $techstack, $url, $img_url);
return $stmt->execute();
}
return false;
}
function editProject($mysqli, $id, $title, $des, $techstack, $url, $img_url) {
$sql = "UPDATE projects SET title = ?, des = ?, techstack = ?, url = ?, img_url = ? WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("sssssi", $title, $des, $techstack, $url, $img_url, $id);
return $stmt->execute();
}
return false;
}
function deleteProject($mysqli, $id) {
$sql = "DELETE FROM projects WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("i", $id);
return $stmt->execute();
}
return false;
}
?>
+33
View File
@@ -0,0 +1,33 @@
<?php
require_once __DIR__ . "/../config/db.php";
function getSettings($mysqli) {
$sql = "SELECT * FROM settings WHERE id = 1";
$result = $mysqli->query($sql);
if ($result && $result->num_rows > 0) {
return $result->fetch_assoc();
}
return null;
}
function updateSettings($mysqli, $about_text_1, $about_text_2, $years_coding, $projects_count, $certifications_count, $tiktok_url, $facebook_url, $instagram_url, $linkedin_url, $github_url) {
$sql = "UPDATE settings SET
about_text_1 = ?,
about_text_2 = ?,
years_coding = ?,
projects_count = ?,
certifications_count = ?,
tiktok_url = ?,
facebook_url = ?,
instagram_url = ?,
linkedin_url = ?,
github_url = ?
WHERE id = 1";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("ssssssssss", $about_text_1, $about_text_2, $years_coding, $projects_count, $certifications_count, $tiktok_url, $facebook_url, $instagram_url, $linkedin_url, $github_url);
return $stmt->execute();
}
return false;
}
?>
+32
View File
@@ -15,4 +15,36 @@ function getTechStack($mysqli){
return $techs;
}
function getTechStackRaw($mysqli) {
$sql = "SELECT * FROM techstack ORDER BY id ASC";
$result = $mysqli->query($sql);
$techs = [];
if ($result) {
while ($row = $result->fetch_assoc()) {
$techs[] = $row;
}
}
return $techs;
}
function addTechStackItem($mysqli, $tech_name) {
$sql = "INSERT INTO techstack (tech_name) VALUES (?)";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("s", $tech_name);
return $stmt->execute();
}
return false;
}
function deleteTechStackItem($mysqli, $id) {
$sql = "DELETE FROM techstack WHERE id = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$stmt->bind_param("i", $id);
return $stmt->execute();
}
return false;
}
?>
+49 -4
View File
@@ -20,11 +20,21 @@
<div class="site-wrapper">
<nav>
<nav id="main-nav">
<div class="logo">CG.</div>
<div class="nav-links">
<a href="index.php">← Home</a>
<a href="certifications.php">Certs</a>
<div class="hamburger" id="hamburger" aria-label="Toggle menu" aria-expanded="false" role="button" tabindex="0">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</div>
<div class="nav-links" id="nav-links" role="navigation">
<a href="index.php#about" class="nav-item">About</a>
<a href="index.php#projects" class="nav-item">Work</a>
<a href="index.php#certificates" class="nav-item">Certs</a>
<a href="blog.php" class="nav-item">Blog</a>
<a href="index.php#contact" class="nav-item">Contact</a>
<label for="dark-mode-toggle" class="mode-btn" title="Toggle Theme" aria-label="Toggle dark/light mode">
<span class="icon-moon">🌙</span>
<span class="icon-sun">☀️</span>
@@ -72,6 +82,41 @@
const themeToggle = document.getElementById("dark-mode-toggle");
themeToggle.checked = localStorage.getItem("theme") !== "light";
themeToggle.addEventListener("change", () => localStorage.setItem("theme", themeToggle.checked ? "dark" : "light"));
// HAMBURGER MENU
const hamburger = document.getElementById("hamburger");
const navLinks = document.getElementById("nav-links");
const navItems = document.querySelectorAll(".nav-item");
function closeMenu() {
hamburger.classList.remove("active");
navLinks.classList.remove("active");
hamburger.setAttribute("aria-expanded", "false");
}
hamburger.addEventListener("click", (e) => {
e.stopPropagation();
const isOpen = hamburger.classList.toggle("active");
navLinks.classList.toggle("active");
hamburger.setAttribute("aria-expanded", isOpen);
});
// Close on nav link click
navItems.forEach(item => item.addEventListener("click", closeMenu));
// Close when clicking outside the menu
document.addEventListener("click", (e) => {
if (navLinks.classList.contains("active") && !navLinks.contains(e.target) && !hamburger.contains(e.target)) {
closeMenu();
}
});
// Close on scroll
window.addEventListener('scroll', () => {
if (navLinks.classList.contains('active')) {
closeMenu();
}
});
});
</script>
</body>
+71
View File
@@ -0,0 +1,71 @@
<?php
require_once __DIR__ . '/config/db.php';
// Retrieve client IP address
function getClientIp() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// In case of multiple IPs, get the first one
$ipList = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ipList[0]);
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header("Location: index.php");
exit();
}
$clientIp = getClientIp();
$rateLimit = intval(getenv('RATE_LIMIT') ?: ($_ENV['RATE_LIMIT'] ?? 10));
// Query submissions count from this IP address in the last hour
$stmt = $mysqli->prepare("SELECT COUNT(*) as count FROM email_submissions WHERE ip_address = ? AND submitted_at >= NOW() - INTERVAL 1 HOUR");
if ($stmt) {
$stmt->bind_param("s", $clientIp);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
$submissionCount = $result['count'] ?? 0;
$stmt->close();
} else {
$submissionCount = 0;
}
if ($submissionCount >= $rateLimit) {
header("Location: index.php?status=error&msg=Rate limit exceeded. You can only send $rateLimit emails per hour.#contact");
exit();
}
// Log submission to database
$logStmt = $mysqli->prepare("INSERT INTO email_submissions (ip_address) VALUES (?)");
if ($logStmt) {
$logStmt->bind_param("s", $clientIp);
$logStmt->execute();
$logStmt->close();
}
// Forward submission to FormSubmit via cURL
$contactEmail = getenv('CONTACT_EMAIL') ?: ($_ENV['CONTACT_EMAIL'] ?? 'crischarlesgarcia345@gmail.com');
$url = "https://formsubmit.co/" . $contactEmail;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($_POST));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $httpCode >= 400) {
header("Location: index.php?status=error&msg=Failed to send message via mail service. Please try again later.#contact");
} else {
header("Location: index.php?status=success#contact");
}
exit();
?>
+29
View File
@@ -0,0 +1,29 @@
<?php
require_once __DIR__ . "/../config/db.php";
require_once __DIR__ . "/../controllers/ArticlesController.php";
$articles = showArticles($mysqli);
?>
<?php if (!empty($articles)): ?>
<?php
$delay = 1;
foreach ($articles as $art):
$delayClass = "reveal-delay-" . (($delay % 3 === 0) ? 3 : ($delay % 3));
?>
<article class="blog-card reveal <?php echo $delayClass; ?>">
<div class="blog-meta">
<span class="blog-date"><?php echo htmlspecialchars($art['date_published']); ?></span>
<span class="blog-tag"><?php echo htmlspecialchars($art['tag']); ?></span>
</div>
<h3 class="blog-title"><?php echo htmlspecialchars($art['title']); ?></h3>
<p class="blog-excerpt"><?php echo htmlspecialchars($art['excerpt']); ?></p>
<a href="<?php echo htmlspecialchars($art['url']); ?>" class="read-more">Read Article →</a>
</article>
<?php
$delay++;
endforeach;
?>
<?php else: ?>
<p style="color: var(--text-muted); grid-column: 1/-1; text-align:center; padding: 40px 0;">No articles found.</p>
<?php endif; ?>
+28
View File
@@ -0,0 +1,28 @@
<?php
require_once __DIR__ . "/../config/db.php";
require_once __DIR__ . "/../controllers/CertificationsController.php";
$certifications = showCertifications($mysqli);
?>
<?php if (!empty($certifications)): ?>
<?php
$delay = 1;
foreach ($certifications as $cert):
$delayClass = "reveal-delay-" . (($delay % 3 === 0) ? 3 : ($delay % 3));
?>
<div class="cert-card reveal <?php echo $delayClass; ?>">
<div class="cert-icon"><?php echo htmlspecialchars($cert['icon']); ?></div>
<div class="cert-info">
<h3><?php echo htmlspecialchars($cert['title']); ?></h3>
<p class="issuer"><?php echo htmlspecialchars($cert['issuer']); ?></p>
<span class="date">Issued <?php echo htmlspecialchars($cert['date_issued']); ?></span>
</div>
</div>
<?php
$delay++;
endforeach;
?>
<?php else: ?>
<p style="color: var(--text-muted); grid-column: 1/-1; text-align:center; padding: 40px 0;">No certifications found.</p>
<?php endif; ?>
+29
View File
@@ -0,0 +1,29 @@
<?php
require_once __DIR__ . "/../config/db.php";
require_once __DIR__ . "/../controllers/ArticlesController.php";
$articles = showThreeArticles($mysqli);
?>
<?php if (!empty($articles)): ?>
<?php
$delay = 1;
foreach ($articles as $art):
$delayClass = "reveal-delay-" . (($delay % 3 === 0) ? 3 : ($delay % 3));
?>
<article class="blog-card reveal <?php echo $delayClass; ?>">
<div class="blog-meta">
<span class="blog-date"><?php echo htmlspecialchars($art['date_published']); ?></span>
<span class="blog-tag"><?php echo htmlspecialchars($art['tag']); ?></span>
</div>
<h3 class="blog-title"><?php echo htmlspecialchars($art['title']); ?></h3>
<p class="blog-excerpt"><?php echo htmlspecialchars($art['excerpt']); ?></p>
<a href="<?php echo htmlspecialchars($art['url']); ?>" class="read-more">Read Article →</a>
</article>
<?php
$delay++;
endforeach;
?>
<?php else: ?>
<p style="color: var(--text-muted); grid-column: 1/-1; text-align:center; padding: 40px 0;">No articles found.</p>
<?php endif; ?>
+28
View File
@@ -0,0 +1,28 @@
<?php
require_once __DIR__ . "/../config/db.php";
require_once __DIR__ . "/../controllers/CertificationsController.php";
$certifications = showThreeCertifications($mysqli);
?>
<?php if (!empty($certifications)): ?>
<?php
$delay = 1;
foreach ($certifications as $cert):
$delayClass = "reveal-delay-" . (($delay % 3 === 0) ? 3 : ($delay % 3));
?>
<div class="cert-card reveal <?php echo $delayClass; ?>">
<div class="cert-icon"><?php echo htmlspecialchars($cert['icon']); ?></div>
<div class="cert-info">
<h3><?php echo htmlspecialchars($cert['title']); ?></h3>
<p class="issuer"><?php echo htmlspecialchars($cert['issuer']); ?></p>
<span class="date">Issued <?php echo htmlspecialchars($cert['date_issued']); ?></span>
</div>
</div>
<?php
$delay++;
endforeach;
?>
<?php else: ?>
<p style="color: var(--text-muted); grid-column: 1/-1; text-align:center; padding: 40px 0;">No certifications found.</p>
<?php endif; ?>