Version 2
This commit is contained in:
parent
f657a61923
commit
f59042f511
@ -1,34 +1,42 @@
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #F8F9FA;
|
||||
color: #212529;
|
||||
font-family: 'Roboto', 'Inter', sans-serif;
|
||||
background-color: #f4f7f6;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background: linear-gradient(45deg, #0D6EFD, #549BFF);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
.app-header {
|
||||
background: #fff;
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.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 {
|
||||
background-color: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
border-radius: 1rem; /* rounded-2xl */
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
background-color: #fff;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.kpi-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
@ -37,32 +45,59 @@ body {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08);
|
||||
border-top-left-radius: 1.25rem;
|
||||
border-top-right-radius: 1.25rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 0.75rem 0.5rem;
|
||||
border-top: 1px solid rgba(0,0,0,0.05);
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #6C757D;
|
||||
transition: color 0.2s;
|
||||
.nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #6c757d;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: #0D6EFD;
|
||||
.nav-item.active {
|
||||
color: #0EA5E9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-link .icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
stroke-width: 2;
|
||||
margin-bottom: 0.125rem;
|
||||
.nav-item i {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.nav-link .label {
|
||||
.nav-item span {
|
||||
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;
|
||||
}
|
||||
9
db/migrations/001_create_tasks_table.sql
Normal file
9
db/migrations/001_create_tasks_table.sql
Normal 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
|
||||
);
|
||||
6
db/migrations/002_seed_tasks_table.sql
Normal file
6
db/migrations/002_seed_tasks_table.sql
Normal 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');
|
||||
3
db/migrations/003_add_submission_fields_to_tasks.sql
Normal file
3
db/migrations/003_add_submission_fields_to_tasks.sql
Normal 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
31
includes/nav.php
Normal 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>
|
||||
26
index.php
26
index.php
@ -137,28 +137,10 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<nav class="bottom-nav d-flex justify-content-around">
|
||||
<a href="#" class="nav-link active text-center">
|
||||
<i data-lucide="home" class="icon"></i>
|
||||
<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>
|
||||
<?php
|
||||
$page = 'home';
|
||||
include 'includes/nav.php';
|
||||
?>
|
||||
|
||||
<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>
|
||||
|
||||
157
task-view.php
Normal file
157
task-view.php
Normal 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
103
tasks.php
Normal 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>
|
||||
Loading…
x
Reference in New Issue
Block a user