Version 2

This commit is contained in:
Flatlogic Bot 2025-10-26 13:25:51 +00:00
parent f657a61923
commit f59042f511
8 changed files with 385 additions and 59 deletions

View File

@ -1,34 +1,42 @@
body { body {
font-family: 'Inter', sans-serif; font-family: 'Roboto', 'Inter', sans-serif;
background-color: #F8F9FA; background-color: #f4f7f6;
color: #212529;
} }
.gradient-text { .app-header {
background: linear-gradient(45deg, #0D6EFD, #549BFF); background: #fff;
-webkit-background-clip: text; padding: 1rem 0;
-webkit-text-fill-color: transparent; border-bottom: 1px solid #e9ecef;
background-clip: text; }
text-fill-color: transparent;
.user-initials {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(45deg, #0EA5E9, #06B6D4);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
} }
.kpi-card { .kpi-card {
background-color: #FFFFFF;
border: none; border: none;
border-radius: 0.75rem; border-radius: 1rem; /* rounded-2xl */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; background-color: #fff;
transition: all 0.3s ease;
} }
.kpi-card:hover { .kpi-card:hover {
transform: translateY(-5px); transform: translateY(-4px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08); box-shadow: 0 8px 20px rgba(0,0,0,0.08);
} }
.kpi-icon { .kpi-icon {
width: 48px; width: 36px;
height: 48px; height: 36px;
stroke-width: 1.5; stroke-width: 1.5;
} }
@ -37,32 +45,59 @@ body {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.8);
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08); backdrop-filter: blur(10px);
border-top-left-radius: 1.25rem; -webkit-backdrop-filter: blur(10px);
border-top-right-radius: 1.25rem; display: flex;
padding-top: 0.5rem; justify-content: space-around;
padding-bottom: 0.5rem; padding: 0.75rem 0.5rem;
padding-left: 1rem; border-top: 1px solid rgba(0,0,0,0.05);
padding-right: 1rem; box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
} }
.nav-link { .nav-item {
color: #6C757D; display: flex;
transition: color 0.2s; flex-direction: column;
align-items: center;
color: #6c757d;
text-decoration: none;
transition: all 0.2s ease;
} }
.nav-link.active { .nav-item.active {
color: #0D6EFD; color: #0EA5E9;
font-weight: 600;
} }
.nav-link .icon { .nav-item i {
width: 28px; width: 24px;
height: 28px; height: 24px;
stroke-width: 2; margin-bottom: 4px;
margin-bottom: 0.125rem;
} }
.nav-link .label { .nav-item span {
font-size: 0.75rem; font-size: 0.75rem;
} }
.task-card {
border: none;
border-radius: 1rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
background-color: #fff;
}
.task-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #eef8ff;
color: #0EA5E9;
}
.task-icon i {
width: 24px;
height: 24px;
}

View File

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
reward DECIMAL(10, 2) NOT NULL,
status ENUM('open', 'in_progress', 'completed', 'approved', 'rejected') NOT NULL DEFAULT 'open',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,6 @@
INSERT INTO tasks (title, description, reward, status) VALUES
('Complete your profile', 'Fill out all the details in your user profile to get a reward.', 5.00, 'open'),
('Watch a video ad', 'Watch a 30-second video ad to earn a reward.', 0.50, 'open'),
('Refer a friend', 'Invite a friend to join the platform and get a bonus when they complete their first task.', 10.00, 'open'),
('Daily check-in', 'Visit the app every day to claim your daily reward.', 0.25, 'open'),
('Write a review', 'Write a review for our app on the app store.', 2.00, 'open');

View File

@ -0,0 +1,3 @@
ALTER TABLE tasks
ADD COLUMN completion_notes TEXT,
ADD COLUMN proof_file_path VARCHAR(255);

31
includes/nav.php Normal file
View File

@ -0,0 +1,31 @@
<?php
// A simple helper to determine the active page
$current_page = basename($_SERVER['PHP_SELF']);
$nav_items = [
['id' => 'home', 'file' => 'index.php', 'icon' => 'home', 'label' => 'Home'],
['id' => 'ads', 'file' => '#', 'icon' => 'gem', 'label' => 'Ads'],
['id' => 'tasks', 'file' => 'tasks.php', 'icon' => 'check-square', 'label' => 'Tasks'],
['id' => 'withdrawal', 'file' => '#', 'icon' => 'landmark', 'label' => 'Withdrawal'],
['id' => 'profile', 'file' => '#', 'icon' => 'user', 'label' => 'Profile'],
];
?>
<nav class="nav-bottom">
<?php foreach ($nav_items as $item): ?>
<?php
// Determine if the current item is active.
// It's active if the current page matches the item's file name.
// For index.php, we also check if the page variable is 'home'.
$is_active = ($current_page == $item['file']);
if ($item['file'] == 'index.php' && isset($page) && $page == 'home') {
$is_active = true;
} elseif ($item['file'] == 'tasks.php' && isset($page) && $page == 'tasks') {
$is_active = true;
}
?>
<a href="<?php echo $item['file']; ?>" class="nav-item <?php echo $is_active ? 'active' : ''; ?>" id="nav-<?php echo $item['id']; ?>">
<i data-lucide="<?php echo $item['icon']; ?>"></i>
<span><?php echo $item['label']; ?></span>
</a>
<?php endforeach; ?>
</nav>

View File

@ -137,28 +137,10 @@
</div> </div>
</main> </main>
<nav class="bottom-nav d-flex justify-content-around"> <?php
<a href="#" class="nav-link active text-center"> $page = 'home';
<i data-lucide="home" class="icon"></i> include 'includes/nav.php';
<span class="label d-block">Home</span> ?>
</a>
<a href="#" class="nav-link text-center">
<i data-lucide="tv-2" class="icon"></i>
<span class="label d-block">Ads</span>
</a>
<a href="#" class="nav-link text-center">
<i data-lucide="clipboard-list" class="icon"></i>
<span class="label d-block">Tasks</span>
</a>
<a href="#" class="nav-link text-center">
<i data-lucide="arrow-down-up" class="icon"></i>
<span class="label d-block">Withdraw</span>
</a>
<a href="#" class="nav-link text-center">
<i data-lucide="user" class="icon"></i>
<span class="label d-block">Profile</span>
</a>
</nav>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script> <script src="assets/js/main.js?v=<?php echo time(); ?>"></script>

157
task-view.php Normal file
View File

@ -0,0 +1,157 @@
<?php
require_once 'db/config.php';
session_start();
$notification = null;
// Get task ID from URL, with basic validation
$task_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['task_id'])) {
$submitted_task_id = (int)$_POST['task_id'];
$completion_notes = trim($_POST['completion_notes'] ?? '');
$proof_file_path = null;
// Handle file upload
if (isset($_FILES['proof_file']) && $_FILES['proof_file']['error'] === UPLOAD_ERR_OK) {
$upload_dir = 'assets/uploads/';
// Create a unique filename to prevent overwriting
$file_ext = pathinfo($_FILES['proof_file']['name'], PATHINFO_EXTENSION);
$unique_name = uniqid('proof_', true) . '.' . $file_ext;
$target_file = $upload_dir . $unique_name;
if (move_uploaded_file($_FILES['proof_file']['tmp_name'], $target_file)) {
$proof_file_path = $target_file;
} else {
$notification = ['type' => 'danger', 'message' => 'Error: Could not upload the file.'];
}
}
// If file upload was successful (or no file was uploaded), update the database
if ($notification === null) {
try {
$pdo = db();
$stmt = $pdo->prepare(
"UPDATE tasks SET status = 'in review', completion_notes = ?, proof_file_path = ? WHERE id = ?"
);
$stmt->execute([$completion_notes, $proof_file_path, $submitted_task_id]);
$_SESSION['notification'] = ['type' => 'success', 'message' => 'Task submitted successfully for review!'];
header("Location: tasks.php");
exit;
} catch (PDOException $e) {
// For development: error_log($e->getMessage());
$notification = ['type' => 'danger', 'message' => 'Error: Could not update the task.'];
}
}
}
// Fetch task details for display
$task = null;
if ($task_id > 0) {
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM tasks WHERE id = ?");
$stmt->execute([$task_id]);
$task = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("Error: Could not connect to the database or fetch task.");
}
}
// If task not found, redirect
if (!$task) {
header("Location: tasks.php");
exit;
}
// Check for notifications from previous page (e.g., after redirect)
if (isset($_SESSION['notification'])) {
$notification = $_SESSION['notification'];
unset($_SESSION['notification']);
}
$pageTitle = htmlspecialchars($task['title']);
$page = 'tasks';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $pageTitle; ?> - Task App</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<script src="https://cdn.jsdelivr.net/npm/lucide@latest/dist/umd/lucide.js"></script>
</head>
<body class="bg-light">
<div class="container-fluid">
<header class="d-flex justify-content-between align-items-center py-3 mb-4 border-bottom">
<h1 class="h4"><?php echo $pageTitle; ?></h1>
<a href="tasks.php" class="btn btn-sm btn-outline-secondary">
<i data-lucide="arrow-left" class="align-middle"></i> Back to Tasks
</a>
</header>
<main class="pb-5">
<?php if ($notification): ?>
<div class="alert alert-<?php echo $notification['type']; ?> alert-dismissible fade show" role="alert">
<?php echo htmlspecialchars($notification['message']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="card rounded-4 border-0 shadow-sm mb-4">
<div class="card-body p-4">
<h5 class="card-title"><?php echo htmlspecialchars($task['title']); ?></h5>
<p class="card-text text-muted"><?php echo htmlspecialchars($task['description']); ?></p>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-success-subtle text-success-emphasis rounded-pill px-3 py-2">
Reward: <?php echo htmlspecialchars($task['reward']); ?> BDT
</span>
<span class="badge bg-info-subtle text-info-emphasis rounded-pill px-3 py-2">
Status: <?php echo ucfirst(htmlspecialchars($task['status'])); ?>
</span>
</div>
</div>
</div>
<div class="card rounded-4 border-0 shadow-sm">
<div class="card-body p-4">
<h5 class="card-title mb-3">Submit for Review</h5>
<form action="task-view.php?id=<?php echo $task['id']; ?>" method="POST" enctype="multipart/form-data">
<input type="hidden" name="task_id" value="<?php echo $task['id']; ?>">
<div class="mb-3">
<label for="completion_notes" class="form-label">Completion Notes</label>
<textarea class="form-control" id="completion_notes" name="completion_notes" rows="4" placeholder="Provide details about your work, links, etc."></textarea>
</div>
<div class="mb-3">
<label for="proof_file" class="form-label">Upload Screenshot</label>
<input class="form-control" type="file" id="proof_file" name="proof_file">
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg" <?php echo $task['status'] !== 'pending' ? 'disabled' : ''; ?>>
<?php echo $task['status'] !== 'pending' ? 'Already Submitted' : 'Submit Task'; ?>
</button>
</div>
</form>
</div>
</div>
</main>
</div>
<?php include 'includes/nav.php'; ?>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
lucide.createIcons();
</script>
</body>
</html>

103
tasks.php Normal file
View File

@ -0,0 +1,103 @@
<?php
session_start();
// Check for notifications
$notification = null;
if (isset($_SESSION['notification'])) {
$notification = $_SESSION['notification'];
unset($_SESSION['notification']);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tasks - EarnMobile</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<script src="https://unpkg.com/lucide@latest"></script>
</head>
<body>
<header class="app-header">
<div class="container">
<div class="d-flex justify-content-between align-items-center">
<div>
<h1 class="h4 mb-0">Tasks</h1>
<p class="text-muted mb-0">Complete tasks to earn rewards</p>
</div>
<div class="user-initials">
<span>U</span>
</div>
</div>
</div>
</header>
<main class="container mt-4 pb-5 mb-5">
<?php if ($notification): ?>
<div class="alert alert-<?php echo $notification['type']; ?> alert-dismissible fade show" role="alert">
<?php echo htmlspecialchars($notification['message']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="task-list">
<?php
require_once 'db/config.php';
$pdo = db();
$stmt = $pdo->query('SELECT * FROM tasks ORDER BY created_at DESC');
$tasks = $stmt->fetchAll();
$icons = ['video', 'smartphone', 'file-text', 'mail', 'user-plus'];
foreach ($tasks as $key => $task) {
$status_class = 'bg-primary';
if ($task['status'] == 'in_progress') {
$status_class = 'bg-warning text-dark';
} elseif ($task['status'] == 'completed' || $task['status'] == 'approved') {
$status_class = 'bg-success';
} elseif ($task['status'] == 'rejected') {
$status_class = 'bg-danger';
} elseif ($task['status'] == 'in review') {
$status_class = 'bg-info';
}
$icon = $icons[$key % count($icons)];
?>
<a href="task-view.php?id=<?php echo $task['id']; ?>" class="card task-card mb-3 text-decoration-none text-dark">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="task-icon me-3">
<i data-lucide="<?php echo htmlspecialchars($icon); ?>"></i>
</div>
<div class="flex-grow-1">
<h5 class="card-title mb-1"><?php echo htmlspecialchars($task['title']); ?></h5>
<p class="card-text text-success mb-1 fw-bold">+ <?php echo htmlspecialchars($task['reward']); ?> BDT</p>
<span class="badge <?php echo $status_class; ?>"><?php echo htmlspecialchars(ucfirst(str_replace('_', ' ', $task['status']))); ?></span>
</div>
<div>
<i data-lucide="chevron-right"></i>
</div>
</div>
</div>
</a>
<?php } ?>
</div>
</main>
<?php
$page = 'tasks';
include 'includes/nav.php';
?>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
lucide.createIcons();
</script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>