From 2f6215ae8e0132e1c2ec8e87febd08ee0414fe83 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 28 Feb 2026 16:54:27 +0000 Subject: [PATCH] v6 --- dashboard.php | 30 +++-- db/migrations/04_funding_rounds.sql | 30 +++++ edit_profile.php | 108 ++++++++++++---- start_funding.php | 14 +- startup_details.php | 193 +++++++++++++++++++++++----- startups.php | 41 ++++-- 6 files changed, 337 insertions(+), 79 deletions(-) create mode 100644 db/migrations/04_funding_rounds.sql diff --git a/dashboard.php b/dashboard.php index 9b45cc2..f65427a 100644 --- a/dashboard.php +++ b/dashboard.php @@ -37,7 +37,14 @@ $myStartups = []; $myInvestments = []; if ($user['role'] === 'founder') { - $stmt = db()->prepare("SELECT * FROM startups WHERE founder_id = ? ORDER BY created_at DESC"); + // Fetch startups and their active round if any + $stmt = db()->prepare(" + SELECT s.*, fr.funding_goal as active_goal, fr.funding_raised as active_raised, fr.status as round_status + FROM startups s + LEFT JOIN funding_rounds fr ON s.id = fr.startup_id AND fr.status = 'Active' + WHERE s.founder_id = ? + ORDER BY s.created_at DESC + "); $stmt->execute([$_SESSION['user_id']]); $myStartups = $stmt->fetchAll(); } else { @@ -93,7 +100,7 @@ function number_get_formatted($num) {

My Ventures

- + Launch Round + + Launch New Startup
@@ -104,18 +111,23 @@ function number_get_formatted($num) {
- +
- - Target: £ + + + + Target: £
-
£
-
Raised so far
+
£
+
Raised in round
@@ -145,7 +157,7 @@ function number_get_formatted($num) {
£
-
+
@@ -236,4 +248,4 @@ function number_get_formatted($num) { - \ No newline at end of file + diff --git a/db/migrations/04_funding_rounds.sql b/db/migrations/04_funding_rounds.sql new file mode 100644 index 0000000..54129a8 --- /dev/null +++ b/db/migrations/04_funding_rounds.sql @@ -0,0 +1,30 @@ +-- Migration: Independent Funding Rounds and Pot System + +-- 1. Create funding_rounds table +CREATE TABLE IF NOT EXISTS funding_rounds ( + id INT AUTO_INCREMENT PRIMARY KEY, + startup_id INT NOT NULL, + funding_goal DECIMAL(15, 2) NOT NULL, + funding_raised DECIMAL(15, 2) DEFAULT 0.00, + status ENUM('Active', 'Closed', 'Cancelled') DEFAULT 'Active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (startup_id) REFERENCES startups(id) ON DELETE CASCADE +); + +-- 2. Modify investments table to link to funding_rounds +ALTER TABLE investments ADD COLUMN funding_round_id INT AFTER startup_id; +ALTER TABLE investments ADD FOREIGN KEY (funding_round_id) REFERENCES funding_rounds(id) ON DELETE CASCADE; + +-- 3. Update startups table to allow NULL founder_id (for deleted accounts) +ALTER TABLE startups DROP FOREIGN KEY startups_ibfk_1; +ALTER TABLE startups MODIFY founder_id INT NULL; +ALTER TABLE startups ADD CONSTRAINT startups_founder_fk FOREIGN KEY (founder_id) REFERENCES users(id) ON DELETE SET NULL; + +-- 4. Seed initial funding rounds from existing startup data if any +INSERT INTO funding_rounds (startup_id, funding_goal, funding_raised, status, created_at) +SELECT id, funding_target, funding_raised, 'Active', created_at FROM startups; + +-- 5. Link existing investments to the newly created rounds +UPDATE investments i +JOIN funding_rounds fr ON i.startup_id = fr.startup_id +SET i.funding_round_id = fr.id; diff --git a/edit_profile.php b/edit_profile.php index 0116bd0..70743ec 100644 --- a/edit_profile.php +++ b/edit_profile.php @@ -16,27 +16,54 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby'; $error = ''; $success = ''; -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $full_name = trim($_POST['full_name'] ?? ''); - $bio = trim($_POST['bio'] ?? ''); - $interests = trim($_POST['interests'] ?? ''); - $investment_appetite = trim($_POST['investment_appetite'] ?? ''); +// Check for active funding rounds +$stmt = db()->prepare("SELECT COUNT(*) FROM startups s JOIN funding_rounds fr ON s.id = fr.startup_id WHERE s.founder_id = ? AND fr.status = 'Active'"); +$stmt->execute([$_SESSION['user_id']]); +$activeRoundsCount = $stmt->fetchColumn(); - if (empty($full_name)) { - $error = "Name cannot be empty."; +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_POST['action']) && $_POST['action'] === 'delete_account') { + if ($user['role'] === 'founder' && $activeRoundsCount > 0) { + $error = "You cannot delete your account with active funding rounds. Please finish or cancel them first."; + } else { + // Delete account + db()->beginTransaction(); + try { + // Remove profile (cascades or sets null according to our migration) + $stmt = db()->prepare("DELETE FROM users WHERE id = ?"); + $stmt->execute([$_SESSION['user_id']]); + db()->commit(); + + session_destroy(); + header("Location: login.php?msg=account_deleted"); + exit; + } catch (Exception $e) { + db()->rollBack(); + $error = "Account deletion failed: " . $e->getMessage(); + } + } } else { - $stmt = db()->prepare("UPDATE users SET full_name = ?, bio = ?, interests = ?, investment_appetite = ? WHERE id = ?"); - try { - $stmt->execute([$full_name, $bio, $interests, $investment_appetite, $_SESSION['user_id']]); - $success = "Profile updated successfully!"; - // Update session if name changed - $_SESSION['full_name'] = $full_name; - // Refresh user data - $stmt = db()->prepare("SELECT * FROM users WHERE id = ?"); - $stmt->execute([$_SESSION['user_id']]); - $user = $stmt->fetch(); - } catch (PDOException $e) { - $error = "Update failed: " . $e->getMessage(); + $full_name = trim($_POST['full_name'] ?? ''); + $bio = trim($_POST['bio'] ?? ''); + $interests = trim($_POST['interests'] ?? ''); + $investment_appetite = trim($_POST['investment_appetite'] ?? ''); + + if (empty($full_name)) { + $error = "Name cannot be empty."; + } else { + $stmt = db()->prepare("UPDATE users SET full_name = ?, bio = ?, interests = ?, investment_appetite = ? WHERE id = ?"); + try { + $stmt->execute([$full_name, $bio, $interests, $investment_appetite, $_SESSION['user_id']]); + $success = "Profile updated successfully!"; + // Update session if name changed + $_SESSION['full_name'] = $full_name; + // Refresh user data + $stmt = db()->prepare("SELECT * FROM users WHERE id = ?"); + $stmt->execute([$_SESSION['user_id']]); + $user = $stmt->fetch(); + } catch (PDOException $e) { + $error = "Update failed: " . $e->getMessage(); + } } } } @@ -59,7 +86,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
-
+
@@ -122,8 +149,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ + +
+

Danger Zone

+

Deleting your account is permanent and cannot be undone. All your profile information will be removed, but your startup listings will remain.

+ + + + +
+
+ + + + - + \ No newline at end of file diff --git a/start_funding.php b/start_funding.php index 8effe63..fe23cae 100644 --- a/start_funding.php +++ b/start_funding.php @@ -19,12 +19,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (empty($name) || empty($description) || $target < 50) { $error = "Please fill in all required fields. Minimum target is £50."; } else { - $stmt = db()->prepare("INSERT INTO startups (name, description, founder_id, funding_target, status) VALUES (?, ?, ?, ?, ?)"); + db()->beginTransaction(); try { + // 1. Insert startup (keep target/raised for compatibility/legacy but we'll use funding_rounds) + $stmt = db()->prepare("INSERT INTO startups (name, description, founder_id, funding_target, status) VALUES (?, ?, ?, ?, ?)"); $stmt->execute([$name, $description, $_SESSION['user_id'], $target, $status]); + $startup_id = db()->lastInsertId(); + + // 2. Insert initial funding round + $stmt = db()->prepare("INSERT INTO funding_rounds (startup_id, funding_goal, status) VALUES (?, ?, 'Active')"); + $stmt->execute([$startup_id, $target]); + + db()->commit(); $success = "Startup listed successfully! Your funding round is now active."; header("refresh:2;url=dashboard.php"); } catch (PDOException $e) { + db()->rollBack(); $error = "Database error: " . $e->getMessage(); } } @@ -86,4 +96,4 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
- + \ No newline at end of file diff --git a/startup_details.php b/startup_details.php index a80d9d8..8002fcd 100644 --- a/startup_details.php +++ b/startup_details.php @@ -13,7 +13,8 @@ if (!$startup_id) { exit; } -$stmt = db()->prepare("SELECT s.*, u.full_name as founder_name, u.university as founder_uni, u.graduation_year FROM startups s JOIN users u ON s.founder_id = u.id WHERE s.id = ?"); +// Fetch startup details (handle missing founder due to account deletion) +$stmt = db()->prepare("SELECT s.*, u.full_name as founder_name, u.university as founder_uni, u.graduation_year FROM startups s LEFT JOIN users u ON s.founder_id = u.id WHERE s.id = ?"); $stmt->execute([$startup_id]); $startup = $stmt->fetch(); @@ -26,13 +27,82 @@ $stmt = db()->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $user = $stmt->fetch(); +// Fetch active funding round +$stmt = db()->prepare("SELECT * FROM funding_rounds WHERE startup_id = ? AND status = 'Active' LIMIT 1"); +$stmt->execute([$startup_id]); +$activeRound = $stmt->fetch(); + +// Fetch round history +$stmt = db()->prepare("SELECT * FROM funding_rounds WHERE startup_id = ? ORDER BY created_at DESC"); +$stmt->execute([$startup_id]); +$rounds = $stmt->fetchAll(); + $error = ''; $success = ''; -// Handle Investment +// Handle Round Controls (Founder Only) +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $startup['founder_id'] == $_SESSION['user_id']) { + $action = $_POST['action']; + $round_id = (int)($_POST['round_id'] ?? 0); + + if ($action === 'finish_round_early' && $activeRound && $activeRound['id'] == $round_id) { + $stmt = db()->prepare("UPDATE funding_rounds SET status = 'Closed' WHERE id = ?"); + $stmt->execute([$round_id]); + $success = "Funding round finished early. No new investments allowed."; + $activeRound = null; // Refresh for UI + } elseif ($action === 'cancel_round' && $activeRound && $activeRound['id'] == $round_id) { + db()->beginTransaction(); + try { + // 1. Cancel round + $stmt = db()->prepare("UPDATE funding_rounds SET status = 'Cancelled' WHERE id = ?"); + $stmt->execute([$round_id]); + + // 2. Refund all investors for this round + $stmt = db()->prepare("SELECT * FROM investments WHERE funding_round_id = ? AND status = 'approved'"); + $stmt->execute([$round_id]); + $investmentsToRefund = $stmt->fetchAll(); + + foreach ($investmentsToRefund as $inv) { + // Mark investment as refunded + $upd = db()->prepare("UPDATE investments SET status = 'Refunded' WHERE id = ?"); + $upd->execute([$inv['id']]); + + // Notify investor + $notif = db()->prepare("INSERT INTO notifications (user_id, content) VALUES (?, ?)"); + $notif->execute([$inv['investor_id'], "The funding round for " . $startup['name'] . " has been cancelled. Your investment of £" . number_format($inv['amount']) . " has been refunded."]); + } + + db()->commit(); + $success = "Funding round cancelled and investors refunded."; + $activeRound = null; // Refresh for UI + } catch (Exception $e) { + db()->rollBack(); + $error = "Cancellation failed: " . $e->getMessage(); + } + } elseif ($action === 'start_new_round') { + $newTarget = (float)($_POST['new_target'] ?? 0); + if ($newTarget < 50) { + $error = "Minimum target for a new round is £50."; + } elseif ($activeRound) { + $error = "An active round already exists."; + } else { + $stmt = db()->prepare("INSERT INTO funding_rounds (startup_id, funding_goal, status) VALUES (?, ?, 'Active')"); + $stmt->execute([$startup_id, $newTarget]); + $success = "New funding round launched!"; + // Refresh active round + $stmt = db()->prepare("SELECT * FROM funding_rounds WHERE startup_id = ? AND status = 'Active' LIMIT 1"); + $stmt->execute([$startup_id]); + $activeRound = $stmt->fetch(); + } + } +} + +// Handle Investment (Investor Only) if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'invest') { if ($user['role'] !== 'investor') { $error = "Founders cannot make investments."; + } elseif (!$activeRound) { + $error = "There is no active funding round for this startup."; } else { $amount = (float)($_POST['amount'] ?? 0); if ($amount < 50) { @@ -40,9 +110,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' } else { db()->beginTransaction(); try { - $stmt = db()->prepare("INSERT INTO investments (investor_id, startup_id, amount, status) VALUES (?, ?, ?, 'approved')"); - $stmt->execute([$_SESSION['user_id'], $startup_id, $amount]); + // Use funding_round_id for the investment + $stmt = db()->prepare("INSERT INTO investments (investor_id, startup_id, funding_round_id, amount, status) VALUES (?, ?, ?, ?, 'approved')"); + $stmt->execute([$_SESSION['user_id'], $startup_id, $activeRound['id'], $amount]); + // Update funding_raised in funding_rounds (the dedicated pot) + $stmt = db()->prepare("UPDATE funding_rounds SET funding_raised = funding_raised + ? WHERE id = ?"); + $stmt->execute([$amount, $activeRound['id']]); + + // Update legacy field for backward compatibility in listing pages if needed $stmt = db()->prepare("UPDATE startups SET funding_raised = funding_raised + ? WHERE id = ?"); $stmt->execute([$amount, $startup_id]); @@ -50,8 +126,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' $stmt->execute([$startup['founder_id'], "New investment of £" . number_format($amount) . " in " . $startup['name'] . "!"]); db()->commit(); - $success = "Investment successful! You've successfully backed " . htmlspecialchars($startup['name']) . "."; - header("refresh:2;url=portfolio.php"); + $success = "Investment successful! You've backed the current round."; + // Refresh active round data + $stmt = db()->prepare("SELECT * FROM funding_rounds WHERE id = ?"); + $stmt->execute([$activeRound['id']]); + $activeRound = $stmt->fetch(); } catch (Exception $e) { db()->rollBack(); $error = "Investment failed: " . $e->getMessage(); @@ -105,7 +184,8 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';

- Founded by () + Founded by Account Deleted' ?> + ()
@@ -117,7 +197,7 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';

-
+

Investment Terms (Dividend-Only Returns)

How it works:

@@ -125,31 +205,52 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
  • Investors are entitled to a share of future profits as **dividends**.
  • No voting rights or equity control are granted to investors.
  • Returns are primarily distributed through profit-sharing mechanisms.
  • -
  • This model focuses on sustainable student-led business growth.
  • +
  • Each funding round is independent and maintains its own investment pot.
  • + +
    +

    Funding Round History

    +
    + +
    +
    +
    Round
    +
    Target: £
    +
    +
    +
    £ Raised
    + + + +
    +
    + +
    +
    +
    -

    Funding Progress

    +

    Current Funding Round

    - £ - of £ + £ + of £
    -
    +
    - % Raised + % Raised
    -
    +
    @@ -158,32 +259,56 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
    - - + +
    + +
    + + +
    + +
    + +
    - -
    - - -
    - + + +
    - -
    -

    This is your startup listing. You can't invest in your own project.

    -
    - -
    -

    Only verified investors can participate in funding rounds.

    -
    - +
    + + + +
    +
    + +
    + +

    No Active Round

    +

    This startup is not currently seeking investment.

    + + +
    +

    Start a New Round

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

    Risk Disclosure

    - Student startups carry high risk. Dividends are not guaranteed and depend on the profitability of the venture. This is not financial advice. All student entrepreneurs are verified, but Gatsby does not conduct full financial audits of individual ideas. + Student startups carry high risk. Dividends are not guaranteed and depend on the profitability of the venture. Each round is independent.

    @@ -191,4 +316,4 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby'; - \ No newline at end of file + diff --git a/startups.php b/startups.php index 0fc663c..ba40ff7 100644 --- a/startups.php +++ b/startups.php @@ -15,12 +15,25 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby'; $myStartups = []; if ($user['role'] === 'founder') { - $stmt = db()->prepare("SELECT * FROM startups WHERE founder_id = ? ORDER BY created_at DESC"); + $stmt = db()->prepare(" + SELECT s.*, fr.funding_goal as active_goal, fr.funding_raised as active_raised, fr.status as round_status + FROM startups s + LEFT JOIN funding_rounds fr ON s.id = fr.startup_id AND fr.status = 'Active' + WHERE s.founder_id = ? + ORDER BY s.created_at DESC + "); $stmt->execute([$_SESSION['user_id']]); $myStartups = $stmt->fetchAll(); } else { - // Investors see all public startups - $stmt = db()->prepare("SELECT s.*, u.full_name as founder_name FROM startups s JOIN users u ON s.founder_id = u.id WHERE s.status = 'public' ORDER BY s.created_at DESC"); + // Investors see all public startups and their active round if any + $stmt = db()->prepare(" + SELECT s.*, u.full_name as founder_name, fr.funding_goal as active_goal, fr.funding_raised as active_raised, fr.status as round_status + FROM startups s + LEFT JOIN users u ON s.founder_id = u.id + LEFT JOIN funding_rounds fr ON s.id = fr.startup_id AND fr.status = 'Active' + WHERE s.status = 'public' + ORDER BY s.created_at DESC + "); $stmt->execute(); $myStartups = $stmt->fetchAll(); } @@ -80,17 +93,21 @@ if ($user['role'] === 'founder') { - +

    -
    by
    +
    by
    - +

    @@ -99,20 +116,20 @@ if ($user['role'] === 'founder') {

    Progress - % + %
    -
    +
    Raised
    -
    £
    +
    £
    -
    Target
    -
    £
    +
    Goal
    +
    £
    @@ -146,4 +163,4 @@ if ($user['role'] === 'founder') { - \ No newline at end of file +