Auto commit: 2025-11-22T13:52:11.419Z
This commit is contained in:
parent
ca7cc5dbed
commit
9f05f404f3
47
apply.php
Normal file
47
apply.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/shared/header.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['task_id'])) {
|
||||||
|
$task_id = $_POST['task_id'];
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// Check if user has already applied
|
||||||
|
$stmt = db()->prepare("SELECT id FROM applications WHERE task_id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$task_id, $user_id]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
// User has already applied
|
||||||
|
header('Location: index.php?message=already_applied');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is the task owner
|
||||||
|
$stmt = db()->prepare("SELECT id FROM tasks WHERE id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$task_id, $user_id]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
// User is the task owner
|
||||||
|
header('Location: index.php?message=owner_cannot_apply');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("INSERT INTO applications (task_id, user_id) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$task_id, $user_id]);
|
||||||
|
header('Location: index.php?message=applied_successfully');
|
||||||
|
exit();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log($e->getMessage());
|
||||||
|
header('Location: index.php?message=application_failed');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to index if accessed directly without POST
|
||||||
|
header('Location: index.php');
|
||||||
308
assets/css/main.css
Normal file
308
assets/css/main.css
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;700&display=swap');
|
||||||
|
|
||||||
|
/* General Body & Typography */
|
||||||
|
body {
|
||||||
|
font-family: 'Be Vietnam Pro', sans-serif;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #212529;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header & Navigation */
|
||||||
|
.header {
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.04);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-buttons .btn {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message {
|
||||||
|
margin-right: 1rem;
|
||||||
|
color: #495057;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(45deg, #0d6efd, #0a58ca);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 6px 12px rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background-color: #198754;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
.form-container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2.5rem;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 1rem;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
font-size: 2.25rem;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input, .form-group textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.85rem;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus, .form-group textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Form */
|
||||||
|
.search-form {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form input {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Task Cards */
|
||||||
|
.task-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 1rem;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
padding: 1.75rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta a {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-payout {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #198754;
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Headers */
|
||||||
|
.page-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 2.75rem;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alerts & Status Badges */
|
||||||
|
.alert {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
color: #0f5132;
|
||||||
|
background-color: #d1e7dd;
|
||||||
|
border-color: #badbcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
color: #664d03;
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border-color: #ffecb5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
color: #842029;
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-color: #f5c2c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
padding: 0.3em 0.7em;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-open {
|
||||||
|
background-color: #cfe2ff;
|
||||||
|
color: #052c65;
|
||||||
|
}
|
||||||
|
.status-pending {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
color: #664d03;
|
||||||
|
}
|
||||||
|
.status-assigned, .status-accepted {
|
||||||
|
background-color: #d1e7dd;
|
||||||
|
color: #0f5132;
|
||||||
|
}
|
||||||
|
.status-rejected {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #842029;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Management & Profile Pages */
|
||||||
|
.task-manage-card, .application-card, .applicant-card, .task-card-profile {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.applicant-card {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-page .page-header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-section {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-section h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
9
db/migrations/001_create_users_table.sql
Normal file
9
db/migrations/001_create_users_table.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
-- Create the users table
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
full_name VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
phone VARCHAR(50) NOT NULL,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
11
db/migrations/002_create_tasks_table.sql
Normal file
11
db/migrations/002_create_tasks_table.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS tasks (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
category VARCHAR(100),
|
||||||
|
location VARCHAR(255),
|
||||||
|
payout DECIMAL(10, 2) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
9
db/migrations/003_create_applications_table.sql
Normal file
9
db/migrations/003_create_applications_table.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS applications (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
task_id INT NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'applied',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
314
index.php
314
index.php
@ -1,150 +1,180 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
$pageTitle = 'Welcome to CashTask';
|
||||||
@ini_set('display_errors', '1');
|
require_once __DIR__ . '/shared/header.php';
|
||||||
@error_reporting(E_ALL);
|
require_once __DIR__ . '/db/config.php';
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
$message = $_GET['message'] ?? null;
|
||||||
$now = date('Y-m-d H:i:s');
|
$message_map = [
|
||||||
?>
|
'already_applied' => [
|
||||||
<!doctype html>
|
'text' => 'You have already applied for this task.',
|
||||||
<html lang="en">
|
'type' => 'warning'
|
||||||
<head>
|
],
|
||||||
<meta charset="utf-8" />
|
'owner_cannot_apply' => [
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
'text' => 'You cannot apply for your own task.',
|
||||||
<title>New Style</title>
|
'type' => 'warning'
|
||||||
<?php
|
],
|
||||||
// Read project preview data from environment
|
'applied_successfully' => [
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
'text' => 'You have successfully applied for the task.',
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
'type' => 'success'
|
||||||
?>
|
],
|
||||||
<?php if ($projectDescription): ?>
|
'application_failed' => [
|
||||||
<!-- Meta description -->
|
'text' => 'There was an error submitting your application.',
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
'type' => 'danger'
|
||||||
<!-- Open Graph meta tags -->
|
]
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
];
|
||||||
<!-- Twitter meta tags -->
|
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
// Fetch tasks from the database
|
||||||
<?php endif; ?>
|
try {
|
||||||
<?php if ($projectImageUrl): ?>
|
$search = $_GET['search'] ?? '';
|
||||||
<!-- Open Graph image -->
|
$category = $_GET['category'] ?? '';
|
||||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
$location = $_GET['location'] ?? '';
|
||||||
<!-- Twitter image -->
|
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
$sql = "SELECT t.*, u.id as user_id_poster, u.full_name as user_name FROM tasks t JOIN users u ON t.user_id = u.id WHERE t.status = 'open'";
|
||||||
<?php endif; ?>
|
$params = [];
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
if (!empty($search)) {
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
$sql .= " AND (t.title LIKE :search OR t.description LIKE :search)";
|
||||||
<style>
|
$params[':search'] = '%' . $search . '%';
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
margin: 0;
|
if (!empty($category)) {
|
||||||
font-family: 'Inter', sans-serif;
|
$sql .= " AND t.category LIKE :category";
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
$params[':category'] = '%' . $category . '%';
|
||||||
color: var(--text-color);
|
}
|
||||||
|
|
||||||
|
if (!empty($location)) {
|
||||||
|
$sql .= " AND t.location LIKE :location";
|
||||||
|
$params[':location'] = '%' . $location . '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY t.created_at DESC";
|
||||||
|
|
||||||
|
$stmt = db()->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$tasks = $stmt->fetchAll();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Handle database error gracefully
|
||||||
|
error_log($e->getMessage());
|
||||||
|
$tasks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.alert {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
.alert-success {
|
||||||
|
color: #155724;
|
||||||
|
background-color: #d4edda;
|
||||||
|
border-color: #c3e6cb;
|
||||||
|
}
|
||||||
|
.alert-warning {
|
||||||
|
color: #856404;
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border-color: #ffeeba;
|
||||||
|
}
|
||||||
|
.alert-danger {
|
||||||
|
color: #721c24;
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-color: #f5c6cb;
|
||||||
|
}
|
||||||
|
.search-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.search-form input {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
.task-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
padding: 1.5rem;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
margin-top: 1rem;
|
||||||
text-align: center;
|
color: #6c757d;
|
||||||
overflow: hidden;
|
}
|
||||||
position: relative;
|
|
||||||
}
|
.task-payout {
|
||||||
body::before {
|
font-size: 1.5rem;
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% { background-position: 0% 0%; }
|
|
||||||
100% { background-position: 100% 100%; }
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin: 0 0 1rem;
|
color: #14b8a6;
|
||||||
letter-spacing: -1px;
|
}
|
||||||
}
|
</style>
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
<?php if (isset($message_map[$message])): ?>
|
||||||
font-size: 1.1rem;
|
<div class="alert alert-<?= $message_map[$message]['type'] ?>">
|
||||||
}
|
<?= htmlspecialchars($message_map[$message]['text']) ?>
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<div class="card">
|
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
|
||||||
<span class="sr-only">Loading…</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
<?php endif; ?>
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
<div class="hero-section" style="text-align: center; padding: 4rem 0;">
|
||||||
|
<h1 style="font-size: 3rem; font-weight: 700;">Get anything done, today.</h1>
|
||||||
|
<p style="font-size: 1.25rem; color: #6c757d; margin-bottom: 2rem;">The best place to find quick help for your daily tasks.</p>
|
||||||
|
<a href="/signup.php" class="btn btn-primary">Post a Task</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Available Tasks</h2>
|
||||||
|
|
||||||
|
<form action="index.php" method="GET" class="search-form">
|
||||||
|
<input type="text" name="search" placeholder="Keywords..." value="<?= htmlspecialchars($_GET['search'] ?? '') ?>">
|
||||||
|
<input type="text" name="category" placeholder="Category..." value="<?= htmlspecialchars($_GET['category'] ?? '') ?>">
|
||||||
|
<input type="text" name="location" placeholder="Location..." value="<?= htmlspecialchars($_GET['location'] ?? '') ?>">
|
||||||
|
<button type="submit" class="btn">Search</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="task-grid">
|
||||||
|
<?php if (empty($tasks)): ?>
|
||||||
|
<p>No tasks found matching your criteria. Try broadening your search!</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($tasks as $task): ?>
|
||||||
|
<div class="task-card">
|
||||||
|
<h3><?= htmlspecialchars($task['title']) ?></h3>
|
||||||
|
<div class="task-meta">
|
||||||
|
<span><?= htmlspecialchars($task['category']) ?> • <?= htmlspecialchars($task['location']) ?></span>
|
||||||
|
<span>Posted by: <a href="profile.php?id=<?= $task['user_id_poster'] ?>"><strong><?= htmlspecialchars($task['user_name']) ?></strong></a></span>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
<div class="task-payout">$<?= htmlspecialchars((string)$task['payout']) ?></div>
|
||||||
<footer>
|
<?php if (isset($_SESSION['user_id']) && $_SESSION['user_id'] != $task['user_id']): ?>
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<form action="apply.php" method="POST" style="margin-top: 1rem;">
|
||||||
</footer>
|
<input type="hidden" name="task_id" value="<?= $task['id'] ?>">
|
||||||
</body>
|
<button type="submit" class="btn btn-primary">Apply</button>
|
||||||
</html>
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/shared/footer.php';
|
||||||
|
?>
|
||||||
57
login.php
Normal file
57
login.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'shared/header.php';
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||||
|
$email = $_POST['email'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if (empty($email) || empty($password)) {
|
||||||
|
$error = 'Please fill in all fields.';
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user['password'])) {
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['user_name'] = $user['full_name'];
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = 'Invalid email or password.';
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error = "Database error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="card">
|
||||||
|
<h2>Login</h2>
|
||||||
|
<form action="login.php" method="post">
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert error"><?php echo htmlspecialchars($error); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn">Login</button>
|
||||||
|
</form>
|
||||||
|
<p class="switch-form">Don't have an account? <a href="signup.php">Sign up</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once 'shared/footer.php'; ?>
|
||||||
6
logout.php
Normal file
6
logout.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
155
manage-task.php
Normal file
155
manage-task.php
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Check if user is logged in
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $_SESSION['user_id'];
|
||||||
|
$taskId = $_GET['id'] ?? null;
|
||||||
|
$message = $_GET['message'] ?? '';
|
||||||
|
$message_type = $_GET['type'] ?? 'info';
|
||||||
|
|
||||||
|
if (!$taskId) {
|
||||||
|
header("Location: manage-tasks.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Verify task ownership
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare('SELECT * FROM tasks WHERE id = :task_id AND user_id = :user_id');
|
||||||
|
$stmt->execute([':task_id' => $taskId, ':user_id' => $userId]);
|
||||||
|
$task = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$task) {
|
||||||
|
// If the user does not own this task, redirect them
|
||||||
|
header("Location: manage-tasks.php?message=access_denied");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database error: Could not verify task ownership.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle POST request to update application status
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['application_id']) && isset($_POST['action'])) {
|
||||||
|
$applicationId = $_POST['application_id'];
|
||||||
|
$action = $_POST['action'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
if ($action === 'accept') {
|
||||||
|
// 1. Set this application to 'accepted'
|
||||||
|
$stmt = $pdo->prepare('UPDATE applications SET status = 'accepted' WHERE id = :application_id AND task_id = :task_id');
|
||||||
|
$stmt->execute([':application_id' => $applicationId, ':task_id' => $taskId]);
|
||||||
|
|
||||||
|
// 2. Set the task status to 'assigned'
|
||||||
|
$stmt = $pdo->prepare('UPDATE tasks SET status = 'assigned' WHERE id = :task_id');
|
||||||
|
$stmt->execute([':task_id' => $taskId]);
|
||||||
|
|
||||||
|
// 3. Set all other pending applications for this task to 'rejected'
|
||||||
|
$stmt = $pdo->prepare('UPDATE applications SET status = 'rejected' WHERE task_id = :task_id AND id != :application_id AND status = 'pending'');
|
||||||
|
$stmt->execute([':task_id' => $taskId, ':application_id' => $applicationId]);
|
||||||
|
|
||||||
|
$message = 'Application accepted! The task is now assigned.';
|
||||||
|
$message_type = 'success';
|
||||||
|
|
||||||
|
} elseif ($action === 'reject') {
|
||||||
|
// Just reject this single application
|
||||||
|
$stmt = $pdo->prepare('UPDATE applications SET status = 'rejected' WHERE id = :application_id AND task_id = :task_id');
|
||||||
|
$stmt->execute([':application_id' => $applicationId, ':task_id' => $taskId]);
|
||||||
|
$message = 'Application rejected.';
|
||||||
|
$message_type = 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
header("Location: manage-task.php?id=$taskId&message=" . urlencode($message) . "&type=" . $message_type);
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
// Log error instead of dying in production
|
||||||
|
die("Database error: Could not update application status. " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch all applications for this task
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'SELECT a.id, a.status, u.username, u.email
|
||||||
|
FROM applications a
|
||||||
|
JOIN users u ON a.user_id = u.id
|
||||||
|
WHERE a.task_id = :task_id
|
||||||
|
ORDER BY a.created_at DESC'
|
||||||
|
);
|
||||||
|
$stmt->execute([':task_id' => $taskId]);
|
||||||
|
$applications = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database error: Could not retrieve applications.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$pageTitle = "Manage Task: " . htmlspecialchars($task['title']);
|
||||||
|
include 'shared/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Manage Task</h1>
|
||||||
|
<h2><?php echo htmlspecialchars($task['title']); ?></h2>
|
||||||
|
<p>Posted on: <?php echo date('F j, Y', strtotime($task['created_at'])); ?></p>
|
||||||
|
<p class="task-status-manage">Status: <span class="status-badge status-<?php echo strtolower(htmlspecialchars($task['status'])); ?>"><?php echo htmlspecialchars($task['status']); ?></span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($message): ?>
|
||||||
|
<div class="alert alert-<?php echo htmlspecialchars($message_type); ?>"><?php echo htmlspecialchars($message); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="applicants-section">
|
||||||
|
<h3>Applicants</h3>
|
||||||
|
<?php if (empty($applications)): ?>
|
||||||
|
<div class="alert alert-info">There are no applicants for this task yet.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="applicants-list">
|
||||||
|
<?php foreach ($applications as $app): ?>
|
||||||
|
<div class="applicant-card">
|
||||||
|
<div class="applicant-info">
|
||||||
|
<strong><?php echo htmlspecialchars($app['username']); ?></strong>
|
||||||
|
(<?php echo htmlspecialchars($app['email']); ?>)
|
||||||
|
</div>
|
||||||
|
<div class="applicant-status">
|
||||||
|
Status: <span class="status-badge status-<?php echo strtolower(htmlspecialchars($app['status'])); ?>"><?php echo htmlspecialchars($app['status']); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php if ($app['status'] === 'accepted'): ?>
|
||||||
|
<div class="applicant-actions">
|
||||||
|
<p class="text-success">This applicant was awarded the task.</p>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($task['status'] === 'open' && $app['status'] === 'pending'): ?>
|
||||||
|
<div class="applicant-actions">
|
||||||
|
<form action="manage-task.php?id=<?php echo $taskId; ?>" method="POST" style="display: inline;">
|
||||||
|
<input type="hidden" name="application_id" value="<?php echo $app['id']; ?>">
|
||||||
|
<button type="submit" name="action" value="accept" class="btn btn-success">Accept</button>
|
||||||
|
</form>
|
||||||
|
<form action="manage-task.php?id=<?php echo $taskId; ?>" method="POST" style="display: inline;">
|
||||||
|
<input type="hidden" name="application_id" value="<?php echo $app['id']; ?>">
|
||||||
|
<button type="submit" name="action" value="reject" class="btn btn-danger">Reject</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="back-link">
|
||||||
|
<a href="manage-tasks.php">Back to All My Tasks</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'shared/footer.php'; ?>
|
||||||
56
manage-tasks.php
Normal file
56
manage-tasks.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'SELECT id, title, description, status, created_at
|
||||||
|
FROM tasks
|
||||||
|
WHERE user_id = :user_id
|
||||||
|
ORDER BY created_at DESC'
|
||||||
|
);
|
||||||
|
$stmt->bindParam(':user_id', $userId, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database error: Could not retrieve your tasks.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = "Manage Your Tasks";
|
||||||
|
include 'shared/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Manage Your Tasks</h1>
|
||||||
|
<p>Here are the tasks you've posted. Click on a task to view and manage applications.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($tasks)): ?>
|
||||||
|
<div class="alert alert-info">You have not posted any tasks yet. <a href="post-task.php">Post one now!</a></div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="tasks-list">
|
||||||
|
<?php foreach ($tasks as $task): ?>
|
||||||
|
<a href="manage-task.php?id=<?php echo $task['id']; ?>" class="task-manage-card">
|
||||||
|
<div class="task-info">
|
||||||
|
<h2><?php echo htmlspecialchars($task['title']); ?></h2>
|
||||||
|
<p><?php echo htmlspecialchars(substr($task['description'], 0, 100)); ?>...</p>
|
||||||
|
</div>
|
||||||
|
<div class="task-status-manage">
|
||||||
|
Status: <span class="status-badge status-<?php echo strtolower(htmlspecialchars($task['status'])); ?>"><?php echo htmlspecialchars($task['status']); ?></span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'shared/footer.php'; ?>
|
||||||
58
my-applications.php
Normal file
58
my-applications.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'SELECT t.title, t.description, a.status, u.username AS poster_username
|
||||||
|
FROM applications a
|
||||||
|
JOIN tasks t ON a.task_id = t.id
|
||||||
|
JOIN users u ON t.user_id = u.id
|
||||||
|
WHERE a.user_id = :user_id
|
||||||
|
ORDER BY a.created_at DESC'
|
||||||
|
);
|
||||||
|
$stmt->bindParam(':user_id', $userId, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$applications = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// In a real application, you would log this error
|
||||||
|
die("Database error: Could not retrieve your applications.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = "My Applications";
|
||||||
|
include 'shared/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>My Applications</h1>
|
||||||
|
<p>Here are the tasks you've applied for and their current status.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($applications)): ?>
|
||||||
|
<div class="alert alert-info">You have not applied for any tasks yet.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="applications-list">
|
||||||
|
<?php foreach ($applications as $app): ?>
|
||||||
|
<div class="application-card">
|
||||||
|
<h2><?php echo htmlspecialchars($app['title']); ?></h2>
|
||||||
|
<p class="task-poster">Posted by: <strong><?php echo htmlspecialchars($app['poster_username']); ?></strong></p>
|
||||||
|
<p><?php echo nl2br(htmlspecialchars($app['description'])); ?></p>
|
||||||
|
<div class="application-status">
|
||||||
|
Status: <span class="status-badge status-<?php echo strtolower(htmlspecialchars($app['status'])); ?>"><?php echo htmlspecialchars($app['status']); ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'shared/footer.php'; ?>
|
||||||
62
post-task.php
Normal file
62
post-task.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'shared/header.php';
|
||||||
|
|
||||||
|
// Redirect if not logged in
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$title = $_POST['title'] ?? '';
|
||||||
|
$description = $_POST['description'] ?? '';
|
||||||
|
$category = $_POST['category'] ?? '';
|
||||||
|
$location = $_POST['location'] ?? '';
|
||||||
|
$payout = $_POST['payout'] ?? '';
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (empty($title) || empty($description) || empty($payout)) {
|
||||||
|
$error = 'Title, description, and payout are required.';
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("INSERT INTO tasks (user_id, title, description, category, location, payout) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$user_id, $title, $description, $category, $location, $payout]);
|
||||||
|
header('Location: index.php');
|
||||||
|
exit();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error = "Database error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-center mt-5">Post a New Task</h1>
|
||||||
|
<form action="post-task.php" method="POST" class="mt-4 p-4 border rounded bg-light">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="title" class="form-label">Task Title</label>
|
||||||
|
<input type="text" class="form-control" id="title" name="title" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="3" required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="category" class="form-label">Category</label>
|
||||||
|
<input type="text" class="form-control" id="category" name="category">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="location" class="form-label">Location</label>
|
||||||
|
<input type="text" class="form-control" id="location" name="location">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="payout" class="form-label">Payout</label>
|
||||||
|
<input type="number" class="form-control" id="payout" name="payout" step="0.01" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Post Task</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once 'shared/footer.php'; ?>
|
||||||
101
profile.php
Normal file
101
profile.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$profileId = $_GET['id'] ?? null;
|
||||||
|
|
||||||
|
if (!$profileId) {
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Fetch user information
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare('SELECT id, full_name, email, created_at FROM users WHERE id = :id');
|
||||||
|
$stmt->execute([':id' => $profileId]);
|
||||||
|
$profileUser = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database error: Could not retrieve user profile.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$profileUser) {
|
||||||
|
// Handle user not found
|
||||||
|
header("Location: index.php?message=user_not_found");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch tasks posted by the user
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare('SELECT * FROM tasks WHERE user_id = :user_id ORDER BY created_at DESC');
|
||||||
|
$stmt->execute([':user_id' => $profileId]);
|
||||||
|
$postedTasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database error: Could not retrieve posted tasks.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch tasks the user has completed (i.e., their application was accepted)
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'SELECT t.* FROM tasks t JOIN applications a ON t.id = a.task_id WHERE a.user_id = :user_id AND a.status = \'accepted\' ORDER BY t.created_at DESC'
|
||||||
|
);
|
||||||
|
$stmt->execute([':user_id' => $profileId]);
|
||||||
|
$completedTasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database error: Could not retrieve completed tasks.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = htmlspecialchars($profileUser['full_name']) . "'s Profile";
|
||||||
|
include 'shared/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container profile-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1><?php echo htmlspecialchars($profileUser['full_name']); ?></h1>
|
||||||
|
<p><strong>Member Since:</strong> <?php echo date('F j, Y', strtotime($profileUser['created_at'])); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="profile-section">
|
||||||
|
<h2>Posted Tasks</h2>
|
||||||
|
<?php if (empty($postedTasks)): ?>
|
||||||
|
<p><?php echo htmlspecialchars($profileUser['name']); ?> has not posted any tasks yet.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="task-list">
|
||||||
|
<?php foreach ($postedTasks as $task):
|
||||||
|
// Check if task status is not null before using it in strtolower
|
||||||
|
$taskStatus = $task['status'] ?? 'unknown';
|
||||||
|
?>
|
||||||
|
<div class="task-card-profile">
|
||||||
|
<h4><a href="manage-task.php?id=<?php echo $task['id']; ?>"><?php echo htmlspecialchars($task['title']); ?></a></h4>
|
||||||
|
<p><strong>Status:</strong> <span class="status-badge status-<?php echo strtolower(htmlspecialchars($taskStatus)); ?>"><?php echo htmlspecialchars($taskStatus); ?></span></p>
|
||||||
|
<p><strong>Payout:</strong> $<?php echo htmlspecialchars($task['payout']); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-section">
|
||||||
|
<h2>Completed Tasks</h2>
|
||||||
|
<?php if (empty($completedTasks)): ?>
|
||||||
|
<p><?php echo htmlspecialchars($profileUser['name']); ?> has not completed any tasks yet.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="task-list">
|
||||||
|
<?php foreach ($completedTasks as $task):
|
||||||
|
// Check if task status is not null before using it in strtolower
|
||||||
|
$taskStatus = $task['status'] ?? 'unknown';
|
||||||
|
?>
|
||||||
|
<div class="task-card-profile">
|
||||||
|
<h4><?php echo htmlspecialchars($task['title']); ?></h4>
|
||||||
|
<p><strong>Status:</strong> <span class="status-badge status-<?php echo strtolower(htmlspecialchars($taskStatus)); ?>"><?php echo htmlspecialchars($taskStatus); ?></span></p>
|
||||||
|
<p><strong>Payout:</strong> $<?php echo htmlspecialchars($task['payout']); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'shared/footer.php'; ?>
|
||||||
6
shared/footer.php
Normal file
6
shared/footer.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
</main>
|
||||||
|
<footer class="footer">
|
||||||
|
<p>© <?= date('Y') ?> CashTask. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
55
shared/header.php
Normal file
55
shared/header.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
declare(strict_types=1);
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// Read project preview data from environment
|
||||||
|
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||||
|
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title><?= htmlspecialchars($pageTitle ?? 'CashTask') ?></title>
|
||||||
|
|
||||||
|
<?php if ($projectDescription): ?>
|
||||||
|
<!-- Meta description -->
|
||||||
|
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||||
|
<!-- Open Graph meta tags -->
|
||||||
|
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||||
|
<!-- Twitter meta tags -->
|
||||||
|
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($projectImageUrl): ?>
|
||||||
|
<!-- Open Graph image -->
|
||||||
|
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||||
|
<!-- Twitter image -->
|
||||||
|
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<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;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/assets/css/main.css?v=<?= time() ?>">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="header">
|
||||||
|
<a href="/" class="logo">CashTask</a>
|
||||||
|
<div class="nav-buttons">
|
||||||
|
<?php if (isset($_SESSION['user_id'])): ?>
|
||||||
|
<span class="welcome-message">Welcome, <?php echo htmlspecialchars($_SESSION['user_name']); ?>!</span>
|
||||||
|
<a href="/profile.php?id=<?php echo $_SESSION['user_id']; ?>" class="btn btn-secondary">My Profile</a>
|
||||||
|
<a href="/my-applications.php" class="btn btn-secondary">My Applications</a>
|
||||||
|
<a href="/manage-tasks.php" class="btn btn-secondary">Manage Tasks</a>
|
||||||
|
<a href="/post-task.php" class="btn btn-primary">Post a Task</a>
|
||||||
|
<a href="/logout.php" class="btn btn-secondary">Log Out</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="/login.php" class="btn btn-secondary">Log In</a>
|
||||||
|
<a href="/signup.php" class="btn btn-primary">Sign Up</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="container">
|
||||||
109
signup.php
Normal file
109
signup.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
$full_name = '';
|
||||||
|
$email = '';
|
||||||
|
$phone = '';
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||||
|
$full_name = trim($_POST['full_name']);
|
||||||
|
$email = trim($_POST['email']);
|
||||||
|
$phone = trim($_POST['phone']);
|
||||||
|
$password = $_POST['password'];
|
||||||
|
$confirm_password = $_POST['confirm_password'];
|
||||||
|
|
||||||
|
if (empty($full_name)) {
|
||||||
|
$errors[] = 'Full name is required.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($email)) {
|
||||||
|
$errors[] = 'Email is required.';
|
||||||
|
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$errors[] = 'Invalid email format.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($phone)) {
|
||||||
|
$errors[] = 'Phone number is required.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($password)) {
|
||||||
|
$errors[] = 'Password is required.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($password !== $confirm_password) {
|
||||||
|
$errors[] = 'Passwords do not match.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($errors)) {
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
$existing_user = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($existing_user) {
|
||||||
|
$errors[] = 'A user with this email already exists.';
|
||||||
|
} else {
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO users (full_name, email, phone, password) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$full_name, $email, $phone, $hashed_password]);
|
||||||
|
|
||||||
|
header("Location: login.php?registration=success");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// In a real app, you would log this error, not show it to the user.
|
||||||
|
$errors[] = "Database error. Please try again later.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = 'Sign Up';
|
||||||
|
require_once __DIR__ . '/shared/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="form-container">
|
||||||
|
<h1>Create an Account</h1>
|
||||||
|
|
||||||
|
<?php if (!empty($errors)): ?>
|
||||||
|
<div class="errors">
|
||||||
|
<?php foreach ($errors as $error): ?>
|
||||||
|
<p><?php echo htmlspecialchars($error); ?></p>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="signup.php" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="full_name">Full Name</label>
|
||||||
|
<input type="text" id="full_name" name="full_name" required value="<?php echo htmlspecialchars($full_name); ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email Address</label>
|
||||||
|
<input type="email" id="email" name="email" required value="<?php echo htmlspecialchars($email); ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="phone">Phone Number</label>
|
||||||
|
<input type="tel" id="phone" name="phone" required value="<?php echo htmlspecialchars($phone); ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_password">Confirm Password</label>
|
||||||
|
<input type="password" id="confirm_password" name="confirm_password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary" style="width: 100%;">Sign Up</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/shared/footer.php';
|
||||||
|
?>
|
||||||
Loading…
x
Reference in New Issue
Block a user