diff --git a/add_job.php b/add_job.php new file mode 100644 index 0000000..81e667a --- /dev/null +++ b/add_job.php @@ -0,0 +1,76 @@ + false, 'message' => 'An unknown error occurred.']; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Basic server-side validation + $required_fields = ['title', 'description', 'category', 'skills', 'budget', 'deadline']; + $errors = []; + foreach ($required_fields as $field) { + if (empty(trim($_POST[$field]))) { + $errors[] = ucfirst($field) . ' is a required field.'; + } + } + + if (!empty($errors)) { + $response['message'] = implode(' ', $errors); + echo json_encode($response); + exit; + } + + $title = trim($_POST['title']); + $description = trim($_POST['description']); + $category = trim($_POST['category']); + $skills = trim($_POST['skills']); + $budget = filter_var($_POST['budget'], FILTER_VALIDATE_FLOAT); + $deadline = $_POST['deadline']; // Basic validation, can be improved + $client_id = $_POST['client_id']; + + if ($budget === false || $budget <= 0) { + $errors[] = 'Please enter a valid budget.'; + } + + // A simple check for date format, can be made more robust + if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $deadline)) { + $errors[] = 'Invalid deadline format.'; + } + + if (empty($client_id) || !filter_var($client_id, FILTER_VALIDATE_INT)) { + $errors[] = 'Invalid client ID.'; + } + + if (!empty($errors)) { + $response['message'] = implode(' ', $errors); + echo json_encode($response); + exit; + } + + try { + // Run the migration first to ensure the table exists. + $sql_migration = file_get_contents(__DIR__ . '/db/migrations/001_create_jobs_table.sql'); + if ($sql_migration) { + db()->exec($sql_migration); + } + + $stmt = db()->prepare( + "INSERT INTO jobs (client_id, title, description, category, skills, budget, deadline) VALUES (?, ?, ?, ?, ?, ?, ?)" + ); + + $stmt->execute([$client_id, $title, $description, $category, $skills, $budget, $deadline]); + + $response['success'] = true; + $response['message'] = 'Job posted successfully!'; + + } catch (PDOException $e) { + // In a real app, log this error instead of echoing it. + error_log('Database Error: ' . $e->getMessage()); + $response['message'] = 'Database error occurred. Please try again later.'; + } +} else { + $response['message'] = 'Invalid request method.'; +} + +echo json_encode($response); diff --git a/apply-for-job.php b/apply-for-job.php new file mode 100644 index 0000000..db7c2ef --- /dev/null +++ b/apply-for-job.php @@ -0,0 +1,63 @@ +prepare("SELECT id FROM job_applications WHERE job_id = :job_id AND worker_id = :worker_id"); + $stmt_check->bindParam(':job_id', $job_id, PDO::PARAM_INT); + $stmt_check->bindParam(':worker_id', $current_worker_id, PDO::PARAM_INT); + $stmt_check->execute(); + + if ($stmt_check->fetch()) { + // The worker has already applied + header("Location: " . $redirect_url . "&application_status=error"); + exit(); + } + + // 2. Insert the new application + $stmt_insert = $pdo->prepare("INSERT INTO job_applications (job_id, worker_id, status) VALUES (:job_id, :worker_id, 'pending')"); + $stmt_insert->bindParam(':job_id', $job_id, PDO::PARAM_INT); + $stmt_insert->bindParam(':worker_id', $current_worker_id, PDO::PARAM_INT); + + if ($stmt_insert->execute()) { + // Success + header("Location: " . $redirect_url . "&application_status=success"); + exit(); + } else { + // Failure + header("Location: " . $redirect_url . "&application_status=error"); + exit(); + } + + } catch (PDOException $e) { + // In a real app, log this error + // error_log("Application error: " . $e->getMessage()); + header("Location: " . $redirect_url . "&application_status=error"); + exit(); + } + +} else { + // If accessed directly, redirect to the jobs page + header("Location: jobs.php"); + exit(); +} diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..824c355 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,57 @@ +:root { + --bs-primary: #0052FF; + --bs-primary-rgb: 0, 82, 255; + --bs-secondary: #F0F6FF; + --bs-success: #34C759; + --bs-font-sans-serif: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; +} + +body { + background-color: #F8F9FA; +} + +.navbar { + box-shadow: 0 2px 4px rgba(0,0,0,.05); +} + +.navbar-brand { + font-weight: 700; + font-size: 1.5rem; + color: var(--bs-primary); +} + +.btn-primary { + padding: 0.75rem 1.5rem; + font-weight: 600; + transition: all 0.2s ease-in-out; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(var(--bs-primary-rgb), 0.2); +} + +.card { + border: 1px solid #EAECEF; + border-radius: 0.75rem; + box-shadow: 0 4px 6px rgba(0,0,0,.04); +} + +.form-control, .form-select { + border-radius: 0.5rem; + padding: 0.75rem 1rem; +} + +.form-control:focus, .form-select:focus { + border-color: var(--bs-primary); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25); +} + +.input-group-text { + border-radius: 0.5rem 0 0 0.5rem; +} + +.needs-validation .form-control:invalid, +.needs-validation .form-select:invalid { + border-color: var(--bs-danger); +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..ec84dc6 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,59 @@ + +document.addEventListener('DOMContentLoaded', function () { + // Bootstrap form validation + const form = document.querySelector('.needs-validation'); + if (form) { + form.addEventListener('submit', function (event) { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + } + + // AJAX form submission for post-job-form + const postJobForm = document.getElementById('post-job-form'); + if (postJobForm) { + postJobForm.addEventListener('submit', function (event) { + event.preventDefault(); + event.stopPropagation(); + + if (form.checkValidity()) { + const formData = new FormData(postJobForm); + const submitButton = postJobForm.querySelector('button[type="submit"]'); + const originalButtonText = submitButton.innerHTML; + + submitButton.disabled = true; + submitButton.innerHTML = ' Posting...'; + + fetch('add_job.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + const confirmationAlert = document.getElementById('confirmation-alert'); + if (data.success) { + postJobForm.reset(); + postJobForm.classList.remove('was-validated'); + confirmationAlert.classList.remove('d-none'); + window.scrollTo(0, 0); + } else { + // You can implement a more specific error message display here + alert('Error: ' + data.message); + } + }) + .catch(error => { + console.error('Error:', error); + alert('An unexpected error occurred. Please try again.'); + }) + .finally(() => { + submitButton.disabled = false; + submitButton.innerHTML = originalButtonText; + }); + } + postJobForm.classList.add('was-validated'); + }); + } +}); diff --git a/dashboard-client.php b/dashboard-client.php new file mode 100644 index 0000000..a530b83 --- /dev/null +++ b/dashboard-client.php @@ -0,0 +1,128 @@ +prepare($sql_jobs); +$stmt_jobs->execute([$client_id]); +$jobs = $stmt_jobs->fetchAll(); + +// Fetch applications for the client's jobs +$sql_apps = "SELECT ja.*, j.title, w.name AS worker_name, w.email AS worker_email + FROM job_applications ja + JOIN jobs j ON ja.job_id = j.id + JOIN workers w ON ja.worker_id = w.id + WHERE j.client_id = ? + ORDER BY ja.created_at DESC"; +$stmt_apps = db()->prepare($sql_apps); +$stmt_apps->execute([$client_id]); +$applications = $stmt_apps->fetchAll(); + +?> + + + + + + Client Dashboard - SkillRunner + + + + + + + +
+
+

Client Dashboard

+

Welcome, !

+
+ +
+

Your Posted Jobs

+
+
+ +

You have not posted any jobs yet. Post one now!

+ +
    + +
  • + + +
  • + +
+ +
+
+
+ +
+

Recent Job Applications

+
+
+ +

No applications received yet.

+ +
+ + + + + + + + + + + + + + + + + + + +
Job TitleApplicantApplicant EmailApplied On
+
+ +
+
+
+ +
+ + + + + + diff --git a/dashboard.php b/dashboard.php new file mode 100644 index 0000000..692f7a0 --- /dev/null +++ b/dashboard.php @@ -0,0 +1,114 @@ +prepare($sql); +$stmt->execute([$worker_id]); +$applications = $stmt->fetchAll(); + +?> + + + + + + My Dashboard - SkillRunner + + + + + + + +
+
+

Welcome, !

+ Browse More Jobs +
+ +

My Job Applications

+ +
+
+ +

You have not applied for any jobs yet.

+ +
+ + + + + + + + + + + + + + + + + + + + + +
Job TitleBudgetDate AppliedStatusAction
$ + + + + + View Job +
+
+ +
+
+
+ + + + + + diff --git a/db/migrations/001_create_jobs_table.sql b/db/migrations/001_create_jobs_table.sql new file mode 100644 index 0000000..932b1ca --- /dev/null +++ b/db/migrations/001_create_jobs_table.sql @@ -0,0 +1,15 @@ +-- 001_create_jobs_table.sql +-- This script creates the main 'jobs' table for the SkillRunner platform. + +CREATE TABLE IF NOT EXISTS `jobs` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `title` VARCHAR(255) NOT NULL, + `description` TEXT NOT NULL, + `category` VARCHAR(100) NOT NULL, + `skills` VARCHAR(255) NOT NULL COMMENT 'Comma-separated list of skills', + `budget` DECIMAL(10, 2) NOT NULL, + `deadline` DATE NOT NULL, + `status` VARCHAR(50) NOT NULL DEFAULT 'open' COMMENT 'e.g., open, reserved, in_progress, completed', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `client_id` INT COMMENT 'Foreign key to users table, can be added later' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/db/migrations/002_create_workers_table.sql b/db/migrations/002_create_workers_table.sql new file mode 100644 index 0000000..ea7d123 --- /dev/null +++ b/db/migrations/002_create_workers_table.sql @@ -0,0 +1,10 @@ +-- db/migrations/002_create_workers_table.sql +CREATE TABLE IF NOT EXISTS workers ( + id INT(11) NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY (email) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/db/migrations/003_create_job_applications_table.sql b/db/migrations/003_create_job_applications_table.sql new file mode 100644 index 0000000..6a89a4c --- /dev/null +++ b/db/migrations/003_create_job_applications_table.sql @@ -0,0 +1,11 @@ +-- db/migrations/003_create_job_applications_table.sql +CREATE TABLE IF NOT EXISTS job_applications ( + id INT(11) NOT NULL AUTO_INCREMENT, + job_id INT(11) NOT NULL, + worker_id INT(11) NOT NULL, + application_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + status ENUM('pending', 'viewed', 'accepted', 'rejected') DEFAULT 'pending', + PRIMARY KEY (id), + FOREIGN KEY (job_id) REFERENCES jobs(id) ON DELETE CASCADE, + FOREIGN KEY (worker_id) REFERENCES workers(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/db/migrations/005_add_password_to_workers.sql b/db/migrations/005_add_password_to_workers.sql new file mode 100644 index 0000000..6281ee2 --- /dev/null +++ b/db/migrations/005_add_password_to_workers.sql @@ -0,0 +1,2 @@ + +ALTER TABLE `workers` ADD `password` VARCHAR(255) NOT NULL; diff --git a/db/migrations/006_create_clients_table.sql b/db/migrations/006_create_clients_table.sql new file mode 100644 index 0000000..00f4960 --- /dev/null +++ b/db/migrations/006_create_clients_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS clients ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/db/migrations/007_add_client_id_to_jobs.sql b/db/migrations/007_add_client_id_to_jobs.sql new file mode 100644 index 0000000..d39dcbe --- /dev/null +++ b/db/migrations/007_add_client_id_to_jobs.sql @@ -0,0 +1 @@ +ALTER TABLE jobs ADD COLUMN client_id INT NOT NULL AFTER id; \ No newline at end of file diff --git a/index.php b/index.php index 7205f3d..4d75585 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,105 @@ - - New Style + SkillRunner - Find & Hire Verified Experts - - - - + + - + + - - - - - - - + + + - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- + + + + +
+
+

Get any job done, securely.

+

Post a task, fund the escrow with confidence, and release payment only when you're satisfied.
Powered by a community of verified, skilled professionals.

+ +
+
+ +
+
+
+
+
+
+

Verified Experts

+
+

Our workers undergo a verification process, so you hire with peace of mind.

+
+
+
+
+

Secure Escrow

+
+

Your funds are held safely in escrow and are only released upon your approval.

+
+
+
+
+

Dispute Resolution

+
+

Admins are available to mediate and resolve any disputes that may arise.

+
+
+
+
+ + + + + + diff --git a/job-detail.php b/job-detail.php new file mode 100644 index 0000000..247010c --- /dev/null +++ b/job-detail.php @@ -0,0 +1,153 @@ +prepare("SELECT * FROM jobs WHERE id = :id"); + $stmt->bindParam(':id', $job_id, PDO::PARAM_INT); + $stmt->execute(); + $job = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$job) { + // Or show a "Job not found" message + header("Location: jobs.php"); + exit(); + } +} catch (PDOException $e) { + // In a real app, log this error instead of displaying it + die("Database error: " . $e->getMessage()); +} + +?> + + + + + + <?php echo htmlspecialchars($job['title']); ?> - SkillRunner + + + + + + + +
+
+
+ + + + + +
+
+

+
+
+

+ +
+
Required Skills
+ + + +
+ +
+
+
Budget
+

$

+
+
+
Deadline
+

+
+
+ +
+ +
+ + +
+ +
+ Please login or register to apply for this job. +
+ +
+
+
+ +
+
+
+ + + + + + + diff --git a/jobs.php b/jobs.php new file mode 100644 index 0000000..4777e01 --- /dev/null +++ b/jobs.php @@ -0,0 +1,124 @@ +query('SELECT title, category, budget, description, skills FROM jobs ORDER BY created_at DESC'); + $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC); +} catch (PDOException $e) { + // In a real application, you would log this error and show a user-friendly message. + die("Could not connect to the database and fetch jobs: " . $e->getMessage()); +} +?> + + + + + + Browse Jobs - SkillRunner + + + + + + + + + + + +
+
+
+

Open Opportunities

+

Find your next project and start earning.

+
+
+ +
+ +
+
+
+
No Jobs Posted Yet
+

Check back soon for new opportunities or be the first to post a job!

+ Post a Job +
+
+
+ + +
+
+
+
+
+

...

+
+ ' . htmlspecialchars(trim($skill)) . ''; + } + ?> +
+
+ $ + View Details +
+
+
+
+ + +
+
+ + + + + + diff --git a/login-client.php b/login-client.php new file mode 100644 index 0000000..b214ae8 --- /dev/null +++ b/login-client.php @@ -0,0 +1,120 @@ +prepare($sql); + $stmt->execute([$email]); + $client = $stmt->fetch(); + + if ($client && password_verify($password, $client['password'])) { + $_SESSION['client_id'] = $client['id']; + $_SESSION['client_name'] = $client['name']; + header("Location: dashboard-client.php"); + exit; + } else { + $errors[] = "Invalid email or password."; + } + } +} +?> + + + + + + Client Login - SkillRunner + + + + + + + +
+
+
+
+
+

Client Login

+
+
+ +
+ +

+ +
+ +
+
+ + +
+
+ + +
+
+ +
+
+

Don't have a client account? Register here.

+
+
+
+
+
+ + + + + + diff --git a/login.php b/login.php new file mode 100644 index 0000000..e9e04ae --- /dev/null +++ b/login.php @@ -0,0 +1,123 @@ +prepare($sql); + $stmt->execute([$email]); + $worker = $stmt->fetch(); + + if ($worker && password_verify($password, $worker['password'])) { + session_regenerate_id(); + $_SESSION['worker_id'] = $worker['id']; + $_SESSION['worker_name'] = $worker['name']; + header("Location: dashboard.php"); + exit; + } else { + $errors[] = "Invalid email or password."; + } + } +} +?> + + + + + + Worker Login - SkillRunner + + + + + + + +
+
+
+
+
+

Worker Login

+
+
+ +
+ +

+ +
+ +
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+
+
+
+ + + + + + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..1220edd --- /dev/null +++ b/logout.php @@ -0,0 +1,16 @@ + + + + + + Post a New Job - SkillRunner + + + + + + + + +
+
+
+
+

Post a New Job

+

Fill out the details below to find the perfect skilled worker.

+
+ + + +
+ +
+
Job Details
+
+ + +
Please provide a job title.
+
+ +
+ + +
Please provide a job description.
+
+ +
+
+ + +
Please select a category.
+
+
+ + +
Enter skills separated by commas.
+
+
+
+ +
+
Budget & Deadline
+
+
+ +
+ $ + +
Please enter a valid budget.
+
+
+
+ + +
Please set a deadline.
+
+
+
+ +
+ +
+
+
+
+
+ + + + + + + diff --git a/register-client.php b/register-client.php new file mode 100644 index 0000000..b7dd544 --- /dev/null +++ b/register-client.php @@ -0,0 +1,141 @@ +prepare($sql); + $stmt->execute([$email]); + if ($stmt->fetch()) { + $errors[] = "Email is already registered."; + } + } + + if (empty($password)) { + $errors[] = "Password is required."; + } elseif (strlen($password) < 8) { + $errors[] = "Password must be at least 8 characters long."; + } + + if ($password !== $password_confirm) { + $errors[] = "Passwords do not match."; + } + + if (empty($errors)) { + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $sql = "INSERT INTO clients (name, email, password) VALUES (?, ?, ?)"; + $stmt = db()->prepare($sql); + if ($stmt->execute([$name, $email, $hashed_password])) { + // $_SESSION['client_id'] = db()->lastInsertId(); + // $_SESSION['client_name'] = $name; + header("Location: login-client.php"); + exit; + } else { + $errors[] = "Something went wrong. Please try again later."; + } + } +} +?> + + + + + + Client Registration - SkillRunner + + + + + + + +
+
+
+
+
+

Create Your Client Account

+
+
+ +
+ +

+ +
+ +
+
+ + +
+
+ + +
+
+ + +
Must be at least 8 characters long.
+
+
+ + +
+
+ +
+
+
+
+
+
+
+ + + + + + diff --git a/register.php b/register.php new file mode 100644 index 0000000..a8a01a5 --- /dev/null +++ b/register.php @@ -0,0 +1,141 @@ +prepare($sql); + $stmt->execute([$email]); + if ($stmt->fetch()) { + $errors[] = "Email is already registered."; + } + } + + if (empty($password)) { + $errors[] = "Password is required."; + } elseif (strlen($password) < 8) { + $errors[] = "Password must be at least 8 characters long."; + } + + if ($password !== $password_confirm) { + $errors[] = "Passwords do not match."; + } + + if (empty($errors)) { + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $sql = "INSERT INTO workers (name, email, password) VALUES (?, ?, ?)"; + $stmt = db()->prepare($sql); + if ($stmt->execute([$name, $email, $hashed_password])) { + // $_SESSION['worker_id'] = db()->lastInsertId(); + // $_SESSION['worker_name'] = $name; + header("Location: login.php"); + exit; + } else { + $errors[] = "Something went wrong. Please try again later."; + } + } +} +?> + + + + + + Worker Registration - SkillRunner + + + + + + + +
+
+
+
+
+

Create Your Worker Account

+
+
+ +
+ +

+ +
+ +
+
+ + +
+
+ + +
+
+ + +
Must be at least 8 characters long.
+
+
+ + +
+
+ +
+
+
+
+
+
+
+ + + + + +