This commit is contained in:
Flatlogic Bot 2026-02-28 17:48:09 +00:00
parent e904e15720
commit fc3fe3f0a0
5 changed files with 206 additions and 370 deletions

View File

@ -0,0 +1,13 @@
-- Migration: Unmatch and Block Features
CREATE TABLE IF NOT EXISTS blocked_users (
id INT AUTO_INCREMENT PRIMARY KEY,
blocker_id INT NOT NULL,
blocked_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (blocker_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (blocked_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY unique_block (blocker_id, blocked_id)
);
-- Add status to matches to allow unmatching without deleting history
ALTER TABLE matches ADD COLUMN status ENUM('active', 'unmatched') DEFAULT 'active';

View File

@ -20,39 +20,55 @@ if (!$user) {
$user_role = $user['role'];
// Blocked users filter
$stmt = db()->prepare("SELECT blocked_id FROM blocked_users WHERE blocker_id = ?");
$stmt->execute([$current_user_id]);
$blocked_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$stmt = db()->prepare("SELECT blocker_id FROM blocked_users WHERE blocked_id = ?");
$stmt->execute([$current_user_id]);
$blocked_by_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$all_blocked = array_unique(array_merge($blocked_ids, $blocked_by_ids));
$placeholders = empty($all_blocked) ? "0" : implode(',', array_fill(0, count($all_blocked), '?'));
// Leaderboard: Most Followed This Week
$stmt = db()->prepare("
$sql = "
SELECT s.id, s.name, s.description, u.full_name as founder_name, COUNT(sf.id) as followers_count
FROM startups s
JOIN users u ON s.founder_id = u.id
JOIN startup_followers sf ON s.id = sf.startup_id
WHERE sf.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
AND u.id NOT IN ($placeholders)
GROUP BY s.id
ORDER BY followers_count DESC
LIMIT 10
");
$stmt->execute();
";
$stmt = db()->prepare($sql);
$stmt->execute($all_blocked ?: []);
$mostFollowed = $stmt->fetchAll();
// Leaderboard: Most Funded This Week
$stmt = db()->prepare("
$sql = "
SELECT s.id, s.name, s.description, u.full_name as founder_name, SUM(i.amount) as funded_amount
FROM startups s
JOIN users u ON s.founder_id = u.id
JOIN investments i ON s.id = i.startup_id
WHERE i.status = 'approved' AND i.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
AND u.id NOT IN ($placeholders)
GROUP BY s.id
ORDER BY funded_amount DESC
LIMIT 10
");
$stmt->execute();
";
$stmt = db()->prepare($sql);
$stmt->execute($all_blocked ?: []);
$mostFunded = $stmt->fetchAll();
// Search logic
$q = $_GET['q'] ?? '';
$browseStartups = [];
$where = "s.status = 'public'";
$params = [];
$where = "s.status = 'public' AND u.id NOT IN ($placeholders)";
$params = $all_blocked ?: [];
if ($q) {
$where .= " AND (s.name LIKE ? OR s.description LIKE ? OR u.full_name LIKE ?)";
$params[] = "%$q%";
@ -85,10 +101,8 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
.hub-header { padding: 60px 0 40px; text-align: center; }
.hub-header h1 { font-size: 48px; font-weight: 800; background: var(--gradient-primary); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 12px; }
.hub-header p { color: var(--text-secondary); font-size: 18px; }
.leaderboard-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 60px; }
@media (max-width: 900px) { .leaderboard-grid { grid-template-columns: 1fr; } }
.lb-card { background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 24px; padding: 30px; backdrop-filter: blur(20px); }
.lb-title { display: flex; align-items: center; gap: 12px; margin-bottom: 25px; font-size: 20px; font-weight: 700; }
.lb-item { display: flex; align-items: center; gap: 15px; padding: 15px; background: rgba(255,255,255,0.03); border-radius: 16px; margin-bottom: 12px; transition: all 0.3s; text-decoration: none; color: inherit; }
@ -97,7 +111,6 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
.rank-1 { background: gold; color: #000; }
.rank-2 { background: silver; color: #000; }
.rank-3 { background: #cd7f32; color: #000; }
.startup-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 25px; }
.startup-card { background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 24px; padding: 30px; transition: all 0.3s; }
.startup-card:hover { transform: translateY(-5px); border-color: var(--accent-blue); }
@ -210,4 +223,4 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
</style>
</body>
</html>
</html>

View File

@ -14,7 +14,7 @@ $user = $stmt->fetch();
$platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
// Handle Accept/Reject Actions
// Handle Actions (Accept/Reject/Unmatch/Block)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && isset($_POST['other_user_id'])) {
$other_id = (int)$_POST['other_user_id'];
$action = $_POST['action'];
@ -22,11 +22,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && isset($_
if ($action === 'accept') {
$stmt = db()->prepare("UPDATE messages SET status = 'accepted' WHERE sender_id = ? AND receiver_id = ? AND status = 'pending'");
$stmt->execute([$other_id, $user_id]);
// Notify the sender
$stmt = db()->prepare("INSERT INTO notifications (user_id, content) VALUES (?, ?)");
$stmt->execute([$other_id, "Your message request to " . $user['full_name'] . " has been accepted."]);
header("Location: messages.php?chat_with=$other_id");
exit;
} elseif ($action === 'reject') {
@ -34,13 +31,38 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && isset($_
$stmt->execute([$other_id, $user_id]);
header("Location: messages.php");
exit;
} elseif ($action === 'unmatch') {
$stmt = db()->prepare("UPDATE matches SET status = 'unmatched' WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)");
$stmt->execute([$user_id, $other_id, $other_id, $user_id]);
header("Location: messages.php");
exit;
} elseif ($action === 'block') {
$stmt = db()->prepare("INSERT IGNORE INTO blocked_users (blocker_id, blocked_id) VALUES (?, ?)");
$stmt->execute([$user_id, $other_id]);
// Also unmatch if they were matched
$stmt = db()->prepare("UPDATE matches SET status = 'unmatched' WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)");
$stmt->execute([$user_id, $other_id, $other_id, $user_id]);
header("Location: messages.php");
exit;
}
}
// Fetch Matches for Founders
// Fetch Blocked Users to filter them out
$stmt = db()->prepare("SELECT blocked_id FROM blocked_users WHERE blocker_id = ?");
$stmt->execute([$user_id]);
$blocked_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$stmt = db()->prepare("SELECT blocker_id FROM blocked_users WHERE blocked_id = ?");
$stmt->execute([$user_id]);
$blocked_by_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$all_blocked = array_unique(array_merge($blocked_ids, $blocked_by_ids));
$placeholders = empty($all_blocked) ? "0" : implode(',', array_fill(0, count($all_blocked), '?'));
// Fetch Matches for Founders (active only, not blocked)
$matches = [];
if ($user['role'] === 'founder') {
$stmt = db()->prepare("
$sql = "
SELECT m.*,
u.full_name as match_name,
u.id as match_id,
@ -50,21 +72,28 @@ if ($user['role'] === 'founder') {
JOIN users u ON (m.user1_id = u.id OR m.user2_id = u.id)
WHERE (m.user1_id = ? OR m.user2_id = ?)
AND u.id != ?
");
$stmt->execute([$user_id, $user_id, $user_id]);
AND m.status = 'active'
AND u.id NOT IN ($placeholders)
";
$stmt = db()->prepare($sql);
$params = array_merge([$user_id, $user_id, $user_id], $all_blocked ?: []);
$stmt->execute($params);
$matches = $stmt->fetchAll();
}
// Fetch Conversations (accepted or pending from others)
$stmt = db()->prepare("
// Fetch Conversations (accepted or pending from others, excluding blocked)
$sql = "
SELECT DISTINCT u.id as other_user_id, u.full_name as other_user_name, u.role as other_role
FROM messages m
JOIN users u ON (m.sender_id = u.id OR m.receiver_id = u.id)
WHERE ((m.sender_id = ? OR m.receiver_id = ?) AND u.id != ?)
AND (m.status = 'accepted' OR (m.status = 'pending' AND m.receiver_id = ?))
AND u.id NOT IN ($placeholders)
ORDER BY (SELECT MAX(created_at) FROM messages WHERE (sender_id = m.sender_id AND receiver_id = m.receiver_id) OR (sender_id = m.receiver_id AND receiver_id = m.sender_id)) DESC
");
$stmt->execute([$user_id, $user_id, $user_id, $user_id]);
";
$stmt = db()->prepare($sql);
$params = array_merge([$user_id, $user_id, $user_id, $user_id], $all_blocked ?: []);
$stmt->execute($params);
$conversations = $stmt->fetchAll();
$active_chat_id = isset($_GET['chat_with']) ? (int)$_GET['chat_with'] : null;
@ -73,14 +102,20 @@ $active_chat_user = null;
$chat_messages = [];
$can_reply = true;
$needs_acceptance = false;
$is_currently_matched = false;
if ($active_chat_id) {
// Check if blocked
if (in_array($active_chat_id, $all_blocked)) {
header("Location: messages.php");
exit;
}
$stmt = db()->prepare("SELECT id, full_name, role FROM users WHERE id = ?");
$stmt->execute([$active_chat_id]);
$active_chat_user = $stmt->fetch();
if ($active_chat_user) {
// Fetch messages (only show accepted or pending ones)
$stmt = db()->prepare("
SELECT * FROM messages
WHERE ((sender_id = ? AND receiver_id = ?)
@ -91,21 +126,19 @@ if ($active_chat_id) {
$stmt->execute([$user_id, $active_chat_id, $active_chat_id, $user_id]);
$chat_messages = $stmt->fetchAll();
// Check if there are any accepted messages or a match
$stmt = db()->prepare("SELECT 1 FROM messages WHERE ((sender_id = ? AND receiver_id = ?) OR (sender_id = ? AND receiver_id = ?)) AND status = 'accepted'");
$stmt->execute([$user_id, $active_chat_id, $active_chat_id, $user_id]);
$has_accepted = (bool)$stmt->fetchColumn();
$stmt = db()->prepare("SELECT 1 FROM matches WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)");
$stmt = db()->prepare("SELECT 1 FROM matches WHERE ((user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)) AND status = 'active'");
$stmt->execute([$user_id, $active_chat_id, $active_chat_id, $user_id]);
$has_match = (bool)$stmt->fetchColumn();
$is_currently_matched = (bool)$stmt->fetchColumn();
// If receiver of pending messages, can't reply yet
$stmt = db()->prepare("SELECT 1 FROM messages WHERE sender_id = ? AND receiver_id = ? AND status = 'pending'");
$stmt->execute([$active_chat_id, $user_id]);
$has_pending_from_other = (bool)$stmt->fetchColumn();
if (!$has_accepted && !$has_match && $has_pending_from_other) {
if (!$has_accepted && !$is_currently_matched && $has_pending_from_other) {
$can_reply = false;
$needs_acceptance = true;
}
@ -116,29 +149,22 @@ if ($active_chat_id) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && $active_chat_id && $can_reply) {
$content = trim($_POST['content']);
if (!empty($content)) {
// Determine status
$status = 'accepted';
// Check if a match exists
$stmt = db()->prepare("SELECT 1 FROM matches WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)");
$stmt = db()->prepare("SELECT 1 FROM matches WHERE ((user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)) AND status = 'active'");
$stmt->execute([$user_id, $active_chat_id, $active_chat_id, $user_id]);
$is_match = (bool)$stmt->fetchColumn();
// Check if previous accepted exists
$stmt = db()->prepare("SELECT 1 FROM messages WHERE ((sender_id = ? AND receiver_id = ?) OR (sender_id = ? AND receiver_id = ?)) AND status = 'accepted'");
$stmt->execute([$user_id, $active_chat_id, $active_chat_id, $user_id]);
$is_accepted = (bool)$stmt->fetchColumn();
if (!$is_match && !$is_accepted) {
// Check startup status
if ($startup_id) {
$stmt = db()->prepare("SELECT status, name FROM startups WHERE id = ?");
$stmt->execute([$startup_id]);
$s = $stmt->fetch();
if ($s && $s['status'] === 'private') {
$status = 'pending';
// Notify receiver about the request
$stmt = db()->prepare("INSERT INTO notifications (user_id, content) VALUES (?, ?)");
$stmt->execute([$active_chat_id, "New message request from " . $user['full_name'] . " regarding " . $s['name'] . "."]);
}
@ -199,7 +225,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && $active
width: 40px; height: 40px; border-radius: 12px; background: var(--gradient-primary);
display: flex; align-items: center; justify-content: center; font-weight: 700; color: #fff;
}
.chat-header { padding: 15px 25px; border-bottom: 1px solid var(--border-color); display: flex; align-items: center; gap: 15px; }
.chat-header { padding: 15px 25px; border-bottom: 1px solid var(--border-color); display: flex; align-items: center; justify-content: space-between; }
.chat-messages { flex: 1; overflow-y: auto; padding: 25px; display: flex; flex-direction: column; gap: 15px; }
.msg-bubble {
max-width: 70%; padding: 12px 18px; border-radius: 18px; font-size: 14px; line-height: 1.5;
@ -219,6 +245,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && $active
padding: 20px; background: rgba(255, 204, 0, 0.1); border-top: 1px solid rgba(255, 204, 0, 0.2);
text-align: center;
}
/* Action Menu */
.action-menu { position: relative; }
.action-menu-content {
display: none; position: absolute; right: 0; top: 100%;
background: var(--surface-color); border: 1px solid var(--border-color);
border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.5);
z-index: 100; min-width: 150px;
}
.action-menu:hover .action-menu-content { display: block; }
.action-link {
display: block; padding: 10px 15px; color: var(--text-secondary);
text-decoration: none; font-size: 13px; transition: all 0.2s;
background: none; border: none; width: 100%; text-align: left; cursor: pointer;
}
.action-link:hover { background: rgba(255,255,255,0.05); color: #fff; }
.action-link.danger { color: #ff3b30; }
.action-link.danger:hover { background: rgba(255, 59, 48, 0.1); }
</style>
</head>
<body>
@ -253,7 +297,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && $active
<div style="padding: 10px 20px; font-size: 11px; text-transform: uppercase; color: var(--text-secondary); letter-spacing: 1px;">New Matches</div>
<?php foreach ($matches as $match): ?>
<?php
// Check if already has conversation
$hasConv = false;
foreach ($conversations as $c) { if ($c['other_user_id'] == $match['match_id']) $hasConv = true; }
if ($hasConv) continue;
@ -274,7 +317,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && $active
<?php else: ?>
<?php foreach ($conversations as $conv): ?>
<?php
// Check if has pending from others
$stmt = db()->prepare("SELECT 1 FROM messages WHERE sender_id = ? AND receiver_id = ? AND status = 'pending'");
$stmt->execute([$conv['other_user_id'], $user_id]);
$isPending = (bool)$stmt->fetchColumn();
@ -298,10 +340,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && $active
<div class="chat-main">
<?php if ($active_chat_user): ?>
<div class="chat-header">
<div class="avatar-sm"><?= substr($active_chat_user['full_name'], 0, 1) ?></div>
<div>
<div style="font-weight: 700;"><?= htmlspecialchars($active_chat_user['full_name']) ?></div>
<div style="font-size: 12px; color: var(--text-secondary);"><?= ucfirst($active_chat_user['role']) ?></div>
<div style="display: flex; align-items: center; gap: 15px;">
<div class="avatar-sm"><?= substr($active_chat_user['full_name'], 0, 1) ?></div>
<div>
<div style="font-weight: 700;"><?= htmlspecialchars($active_chat_user['full_name']) ?></div>
<div style="font-size: 12px; color: var(--text-secondary);"><?= ucfirst($active_chat_user['role']) ?></div>
</div>
</div>
<div class="action-menu">
<button class="btn btn-secondary" style="padding: 5px 12px;"><i class="fas fa-ellipsis-v"></i></button>
<div class="action-menu-content">
<form method="POST" onsubmit="return confirm('Unmatch with this founder?')">
<input type="hidden" name="other_user_id" value="<?= $active_chat_id ?>">
<input type="hidden" name="action" value="unmatch">
<button type="submit" class="action-link">Unmatch</button>
</form>
<form method="POST" onsubmit="return confirm('Block this user? They will not be able to see you or message you.')">
<input type="hidden" name="other_user_id" value="<?= $active_chat_id ?>">
<input type="hidden" name="action" value="block">
<button type="submit" class="action-link danger">Block User</button>
</form>
</div>
</div>
</div>
<div class="chat-messages" id="chat-box">
@ -309,9 +368,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && $active
<div style="text-align: center; color: var(--text-secondary); margin-top: 50px;">
<i class="fas fa-hand-sparkles" style="font-size: 40px; margin-bottom: 20px; opacity: 0.3;"></i>
<p>This is the start of your conversation with <?= htmlspecialchars(explode(' ', $active_chat_user['full_name'])[0]) ?>.</p>
<?php if ($startup_id): ?>
<p style="font-size: 12px;">Regarding their private startup. Your first message will be a request.</p>
<?php endif; ?>
</div>
<?php else: ?>
<?php foreach ($chat_messages as $msg): ?>

View File

@ -17,39 +17,38 @@ if (!$user || $user['role'] !== 'founder') {
exit;
}
// Onboarding check
if (!$user['onboarding_completed']) {
header("Location: founder_onboarding.php");
exit;
}
// Skill-based Matching Helper
// Blocked users filter
$stmt = db()->prepare("SELECT blocked_id FROM blocked_users WHERE blocker_id = ?");
$stmt->execute([$current_user_id]);
$blocked_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$stmt = db()->prepare("SELECT blocker_id FROM blocked_users WHERE blocked_id = ?");
$stmt->execute([$current_user_id]);
$blocked_by_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$all_blocked = array_unique(array_merge($blocked_ids, $blocked_by_ids));
$placeholders = empty($all_blocked) ? "0" : implode(',', array_fill(0, count($all_blocked), '?'));
function calculateMatchScore($me, $them) {
$score = 0;
// 1. My preferred skills vs Their skills
$myNeeds = array_filter(array_map('trim', explode(',', strtolower($me['preferred_co_founder_skills'] ?? ''))));
$theirSkills = array_filter(array_map('trim', explode(',', strtolower($them['skills'] ?? ''))));
$skillMatches = array_intersect($myNeeds, $theirSkills);
$score += count($skillMatches) * 10;
// 2. Their preferred skills vs My skills
$theirNeeds = array_filter(array_map('trim', explode(',', strtolower($them['preferred_co_founder_skills'] ?? ''))));
$mySkills = array_filter(array_map('trim', explode(',', strtolower($me['skills'] ?? ''))));
$reciprocalMatches = array_intersect($theirNeeds, $mySkills);
$score += count($reciprocalMatches) * 10;
// 3. Industry overlap
$myIndustries = array_filter(array_map('trim', explode(',', strtolower($me['startup_industries'] ?? ''))));
$theirIndustries = array_filter(array_map('trim', explode(',', strtolower($them['startup_industries'] ?? ''))));
$industryMatches = array_intersect($myIndustries, $theirIndustries);
$score += count($industryMatches) * 5;
// 4. University match (bonus)
if (!empty($me['university']) && $me['university'] === $them['university']) {
$score += 5;
}
if (!empty($me['university']) && $me['university'] === $them['university']) { $score += 5; }
return [
'total' => $score,
'skillMatches' => array_values($skillMatches),
@ -57,27 +56,23 @@ function calculateMatchScore($me, $them) {
];
}
// Handle Swipe Logic
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'swipe') {
header('Content-Type: application/json');
$swiped_id = (int)$_POST['swiped_id'];
$direction = $_POST['direction']; // 'like' or 'dislike'
$direction = $_POST['direction'];
if ($swiped_id > 0 && in_array($direction, ['like', 'dislike'])) {
$stmt = db()->prepare("INSERT IGNORE INTO swipes (swiper_id, swiped_id, direction) VALUES (?, ?, ?)");
$stmt->execute([$current_user_id, $swiped_id, $direction]);
$isMatch = false;
if ($direction === 'like') {
$stmt = db()->prepare("SELECT id FROM swipes WHERE swiper_id = ? AND swiped_id = ? AND direction = 'like'");
$stmt->execute([$swiped_id, $current_user_id]);
if ($stmt->fetch()) {
$isMatch = true;
$stmt = db()->prepare("INSERT IGNORE INTO matches (user1_id, user2_id) VALUES (?, ?)");
$stmt = db()->prepare("INSERT INTO matches (user1_id, user2_id, status) VALUES (?, ?, 'active') ON DUPLICATE KEY UPDATE status = 'active'");
$u1 = min($current_user_id, $swiped_id);
$u2 = max($current_user_id, $swiped_id);
$stmt->execute([$u1, $u2]);
$stmt = db()->prepare("INSERT INTO notifications (user_id, content) VALUES (?, ?)");
$stmt->execute([$swiped_id, "You have a new match! Start a conversation now."]);
$stmt->execute([$current_user_id, "You have a new match! Start a conversation now."]);
@ -90,26 +85,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
exit;
}
// Fetch matches
$stmt = db()->prepare("
// Fetch active matches not blocked
$sql = "
SELECT u.*, m.created_at as matched_at FROM matches m
JOIN users u ON (m.user1_id = u.id OR m.user2_id = u.id)
WHERE (m.user1_id = ? OR m.user2_id = ?) AND u.id != ?
WHERE (m.user1_id = ? OR m.user2_id = ?)
AND u.id != ?
AND m.status = 'active'
AND u.id NOT IN ($placeholders)
ORDER BY m.created_at DESC
");
$stmt->execute([$current_user_id, $current_user_id, $current_user_id]);
";
$stmt = db()->prepare($sql);
$stmt->execute(array_merge([$current_user_id, $current_user_id, $current_user_id], $all_blocked ?: []));
$matches = $stmt->fetchAll();
// Fetch swipe candidates with scoring
$stmt = db()->prepare("
// Fetch swipe candidates
$sql = "
SELECT * FROM users
WHERE role = 'founder'
AND id != ?
AND onboarding_completed = 1
AND id NOT IN (SELECT swiped_id FROM swipes WHERE swiper_id = ?)
AND id NOT IN ($placeholders)
LIMIT 50
");
$stmt->execute([$current_user_id, $current_user_id]);
";
$stmt = db()->prepare($sql);
$stmt->execute(array_merge([$current_user_id, $current_user_id], $all_blocked ?: []));
$allCandidates = $stmt->fetchAll();
foreach ($allCandidates as &$c) {
@ -121,8 +122,8 @@ $swipeCandidates = array_slice($allCandidates, 0, 10);
// Fetch partners for Browse view
$search = $_GET['q'] ?? '';
$where = "role = 'founder' AND id != ? AND onboarding_completed = 1 AND id NOT IN (SELECT swiped_id FROM swipes WHERE swiper_id = ?)";
$params = [$current_user_id, $current_user_id];
$where = "role = 'founder' AND id != ? AND onboarding_completed = 1 AND id NOT IN (SELECT swiped_id FROM swipes WHERE swiper_id = ?) AND id NOT IN ($placeholders)";
$params = array_merge([$current_user_id, $current_user_id], $all_blocked ?: []);
if ($search) {
$where .= " AND (full_name LIKE ? OR skills LIKE ? OR university LIKE ? OR startup_industries LIKE ?)";
$params[] = "%$search%";
@ -154,138 +155,25 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.page-header {
padding: 60px 0 40px;
text-align: center;
}
.page-header h1 {
font-size: 48px;
font-weight: 800;
margin-bottom: 12px;
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.page-header p {
color: var(--text-secondary);
font-size: 18px;
}
/* Matches Scroller */
.matches-section {
margin-bottom: 50px;
}
.matches-scroller {
display: flex;
gap: 20px;
overflow-x: auto;
padding: 15px 5px;
scrollbar-width: none;
}
.page-header { padding: 60px 0 40px; text-align: center; }
.page-header h1 { font-size: 48px; font-weight: 800; margin-bottom: 12px; background: var(--gradient-primary); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.page-header p { color: var(--text-secondary); font-size: 18px; }
.matches-section { margin-bottom: 50px; }
.matches-scroller { display: flex; gap: 20px; overflow-x: auto; padding: 15px 5px; scrollbar-width: none; }
.matches-scroller::-webkit-scrollbar { display: none; }
.match-card {
flex: 0 0 80px;
text-align: center;
cursor: pointer;
transition: transform 0.2s;
}
.match-card { flex: 0 0 80px; text-align: center; cursor: pointer; transition: transform 0.2s; }
.match-card:hover { transform: scale(1.05); }
.match-avatar {
width: 80px;
height: 80px;
border-radius: 24px;
background: var(--surface-color);
border: 2px solid var(--accent-blue);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
font-weight: 700;
color: #fff;
margin-bottom: 8px;
box-shadow: 0 10px 20px rgba(0, 242, 255, 0.2);
position: relative;
}
.match-avatar { width: 80px; height: 80px; border-radius: 24px; background: var(--surface-color); border: 2px solid var(--accent-blue); display: flex; align-items: center; justify-content: center; font-size: 28px; font-weight: 700; color: #fff; margin-bottom: 8px; box-shadow: 0 10px 20px rgba(0, 242, 255, 0.2); position: relative; }
.match-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 22px; }
.match-name { font-size: 12px; font-weight: 600; color: var(--text-primary); }
/* Tabs */
.tabs {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 40px;
}
.tab-btn {
padding: 12px 24px;
border-radius: 50px;
background: var(--surface-color);
border: 1px solid var(--border-color);
color: var(--text-secondary);
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.tab-btn.active {
background: var(--gradient-primary);
color: #fff;
border-color: transparent;
box-shadow: 0 10px 20px rgba(0, 122, 255, 0.3);
}
/* Swipe UI */
.swipe-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
}
.card-stack {
position: relative;
width: 100%;
max-width: 400px;
height: 550px;
}
.swipe-card {
position: absolute;
width: 100%;
height: 100%;
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 32px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0,0,0,0.4);
transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.5s ease;
cursor: grab;
display: flex;
flex-direction: column;
}
.card-header-img {
height: 280px;
background: var(--gradient-primary);
display: flex;
align-items: center;
justify-content: center;
font-size: 100px;
color: rgba(255,255,255,0.2);
position: relative;
}
.match-badge {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(10px);
padding: 8px 16px;
border-radius: 50px;
color: var(--accent-blue);
font-weight: 700;
font-size: 14px;
border: 1px solid var(--accent-blue);
display: flex;
align-items: center;
gap: 8px;
}
.tabs { display: flex; justify-content: center; gap: 15px; margin-bottom: 40px; }
.tab-btn { padding: 12px 24px; border-radius: 50px; background: var(--surface-color); border: 1px solid var(--border-color); color: var(--text-secondary); font-weight: 600; cursor: pointer; transition: all 0.3s; }
.tab-btn.active { background: var(--gradient-primary); color: #fff; border-color: transparent; box-shadow: 0 10px 20px rgba(0, 122, 255, 0.3); }
.swipe-container { display: flex; flex-direction: column; align-items: center; padding: 20px 0; }
.card-stack { position: relative; width: 100%; max-width: 400px; height: 550px; }
.swipe-card { position: absolute; width: 100%; height: 100%; background: var(--surface-color); border: 1px solid var(--border-color); border-radius: 32px; overflow: hidden; box-shadow: 0 20px 40px rgba(0,0,0,0.4); transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.5s ease; cursor: grab; display: flex; flex-direction: column; }
.card-header-img { height: 280px; background: var(--gradient-primary); display: flex; align-items: center; justify-content: center; font-size: 100px; color: rgba(255,255,255,0.2); position: relative; }
.match-badge { position: absolute; top: 20px; right: 20px; background: rgba(0,0,0,0.6); backdrop-filter: blur(10px); padding: 8px 16px; border-radius: 50px; color: var(--accent-blue); font-weight: 700; font-size: 14px; border: 1px solid var(--accent-blue); display: flex; align-items: center; gap: 8px; }
.card-details { padding: 25px; flex-grow: 1; display: flex; flex-direction: column; }
.card-title { font-size: 26px; font-weight: 800; margin-bottom: 5px; }
.card-subtitle { font-size: 14px; color: var(--accent-blue); font-weight: 600; margin-bottom: 15px; }
@ -293,52 +181,18 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
.card-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.card-tag { font-size: 11px; padding: 5px 12px; background: rgba(255,255,255,0.05); border-radius: 20px; border: 1px solid var(--border-color); }
.card-tag.highlight { border-color: var(--accent-blue); color: var(--accent-blue); background: rgba(0, 242, 255, 0.05); }
.swipe-actions { display: flex; gap: 20px; margin-top: 30px; }
.action-btn {
width: 70px; height: 70px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
font-size: 28px; cursor: pointer; border: none; transition: transform 0.2s; box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
.action-btn { width: 70px; height: 70px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 28px; cursor: pointer; border: none; transition: transform 0.2s; box-shadow: 0 10px 20px rgba(0,0,0,0.2); }
.action-btn:hover { transform: scale(1.1); }
.btn-dislike { background: #1a1a24; color: #ff3b30; border: 1px solid rgba(255, 39, 48, 0.2); }
.btn-like { background: var(--gradient-primary); color: #fff; }
/* Browse UI */
.browse-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 25px;
}
.candidate-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 24px;
padding: 25px;
transition: transform 0.3s;
cursor: pointer;
position: relative;
}
.browse-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 25px; }
.candidate-card { background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 24px; padding: 25px; transition: transform 0.3s; cursor: pointer; position: relative; }
.candidate-card:hover { transform: translateY(-5px); border-color: var(--accent-blue); }
.candidate-header { display: flex; gap: 15px; align-items: center; margin-bottom: 20px; }
.candidate-avatar { width: 60px; height: 60px; border-radius: 18px; background: var(--gradient-primary); display: flex; align-items: center; justify-content: center; font-weight: 700; color: #fff; font-size: 20px; }
.score-indicator {
position: absolute;
top: 25px;
right: 25px;
font-size: 11px;
font-weight: 700;
color: var(--accent-blue);
text-transform: uppercase;
}
/* Match Modal */
#match-modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(10, 10, 15, 0.95); z-index: 2000; display: none;
align-items: center; justify-content: center; text-align: center;
backdrop-filter: blur(20px);
}
.score-indicator { position: absolute; top: 25px; right: 25px; font-size: 11px; font-weight: 700; color: var(--accent-blue); text-transform: uppercase; }
#match-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 10, 15, 0.95); z-index: 2000; display: none; align-items: center; justify-content: center; text-align: center; backdrop-filter: blur(20px); }
.match-popup { max-width: 400px; padding: 40px; }
.match-title { font-size: 56px; font-weight: 900; background: var(--gradient-primary); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 20px; }
</style>
@ -514,72 +368,38 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
document.getElementById('tab-swipe').classList.toggle('active', tab === 'swipe');
document.getElementById('tab-browse').classList.toggle('active', tab === 'browse');
}
function handleSwipe(direction) {
const stack = document.getElementById('card-stack');
const cards = stack.querySelectorAll('.swipe-card');
if (cards.length === 0) return;
const topCard = cards[cards.length - 1];
const swipedId = topCard.getAttribute('data-id');
// Animation
const rotate = direction === 'like' ? 30 : -30;
const x = direction === 'like' ? 1000 : -1000;
topCard.style.transform = `translateX(${x}px) rotate(${rotate}deg)`;
topCard.style.opacity = '0';
// POST request
const fd = new FormData();
fd.append('action', 'swipe');
fd.append('swiped_id', swipedId);
fd.append('direction', direction);
fetch('partners.php', { method: 'POST', body: fd })
.then(r => r.json())
.then(data => {
if (data.match) {
document.getElementById('match-modal').style.display = 'flex';
}
if (data.match) { document.getElementById('match-modal').style.display = 'flex'; }
setTimeout(() => {
topCard.remove();
if (stack.querySelectorAll('.swipe-card').length === 0) {
location.reload();
}
if (stack.querySelectorAll('.swipe-card').length === 0) { location.reload(); }
}, 500);
});
}
function closeMatch() {
document.getElementById('match-modal').style.display = 'none';
}
function closeMatch() { document.getElementById('match-modal').style.display = 'none'; }
</script>
<style>
header {
background: rgba(10, 10, 15, 0.8);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border-color);
padding: 15px 0;
position: sticky;
top: 0;
z-index: 1000;
}
.nav-links a {
color: var(--text-secondary);
text-decoration: none;
margin: 0 15px;
font-size: 14px;
font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover, .nav-links a.active {
color: #fff;
}
.form-control:focus {
outline: none;
border-color: var(--accent-blue);
}
header { background: rgba(10, 10, 15, 0.8); backdrop-filter: blur(20px); border-bottom: 1px solid var(--border-color); padding: 15px 0; position: sticky; top: 0; z-index: 1000; }
.nav-links a { color: var(--text-secondary); text-decoration: none; margin: 0 15px; font-size: 14px; font-weight: 500; transition: color 0.2s; }
.nav-links a:hover, .nav-links a.active { color: #fff; }
.form-control:focus { outline: none; border-color: var(--accent-blue); }
</style>
</body>

View File

@ -23,6 +23,14 @@ if (!$startup) {
exit;
}
// Check if blocked or blocker
$stmt = db()->prepare("SELECT 1 FROM blocked_users WHERE (blocker_id = ? AND blocked_id = ?) OR (blocker_id = ? AND blocked_id = ?)");
$stmt->execute([$user_id, $startup['founder_id'], $startup['founder_id'], $user_id]);
if ($stmt->fetchColumn()) {
header('Location: discover.php');
exit;
}
// Check if current user is following
$stmt = db()->prepare("SELECT 1 FROM startup_followers WHERE startup_id = ? AND user_id = ?");
$stmt->execute([$startup_id, $user_id]);
@ -40,7 +48,7 @@ $success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
// Follow/Unfollow Actions (Available to all logged in users except the founder of this startup)
// Follow/Unfollow Actions
if ($action === 'follow' && $startup['founder_id'] != $user_id) {
$stmt = db()->prepare("INSERT IGNORE INTO startup_followers (startup_id, user_id) VALUES (?, ?)");
$stmt->execute([$startup_id, $user_id]);
@ -56,37 +64,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
// Founder Actions
if ($user['role'] === 'founder' && $startup['founder_id'] == $user_id) {
$round_id = isset($_POST['round_id']) ? (int)$_POST['round_id'] : 0;
if ($action === 'finish_round' && $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
$activeRound = null;
} 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
$activeRound = null;
} catch (Exception $e) {
db()->rollBack();
$error = "Cancellation failed: " . $e->getMessage();
@ -101,7 +100,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$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();
@ -109,17 +107,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
} elseif ($action === 'post_update') {
$title = trim($_POST['update_title'] ?? '');
$content = trim($_POST['update_content'] ?? '');
if (empty($title) || empty($content)) {
$error = "Update title and content are required.";
} else {
db()->beginTransaction();
try {
// 1. Insert Update
$stmt = db()->prepare("INSERT INTO startup_updates (startup_id, founder_id, title, content) VALUES (?, ?, ?, ?)");
$stmt->execute([$startup_id, $user_id, $title, $content]);
// 2. Identify All Unique Investors AND Followers for this startup
$stmt = db()->prepare("
SELECT DISTINCT u.id, u.email, u.full_name
FROM users u
@ -129,20 +123,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
");
$stmt->execute([$startup_id, $startup_id]);
$peopleToNotify = $stmt->fetchAll();
// 3. Notify them
foreach ($peopleToNotify as $targetUser) {
// DB Notification
$notif = db()->prepare("INSERT INTO notifications (user_id, content) VALUES (?, ?)");
$notif->execute([$targetUser['id'], "New progress update from " . $startup['name'] . ": " . $title]);
// Email Notification
$subject = "New Update: " . $startup['name'];
$emailHtml = "<h1>New Update: " . htmlspecialchars($title) . "</h1><p>Hi " . htmlspecialchars($targetUser['full_name']) . ", <strong>" . htmlspecialchars($startup['name']) . "</strong> has posted a new progress update:</p><hr><p>" . nl2br(htmlspecialchars($content)) . "</p>";
$emailText = "New Update: " . $title . "\n\nHi " . $targetUser['full_name'] . ", " . $startup['name'] . " has posted a new progress update: " . $content;
MailService::sendMail($targetUser['email'], $subject, $emailHtml, $emailText);
}
db()->commit();
$success = "Update posted and " . count($peopleToNotify) . " backers/followers notified.";
} catch (Exception $e) {
@ -166,53 +154,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
} else {
db()->beginTransaction();
try {
// 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]);
$stmt = db()->prepare("INSERT INTO notifications (user_id, content) VALUES (?, ?)");
$stmt->execute([$startup['founder_id'], "New investment of £" . number_format($amount) . " in " . $startup['name'] . "!"]);
// Check if goal reached for automated notification system
$stmt = db()->prepare("SELECT * FROM funding_rounds WHERE id = ?");
$stmt->execute([$activeRound['id']]);
$updatedRound = $stmt->fetch();
if ($updatedRound['funding_raised'] >= $updatedRound['funding_goal']) {
// Update status to 'Closed'
$stmt = db()->prepare("UPDATE funding_rounds SET status = 'Closed' WHERE id = ?");
$stmt->execute([$updatedRound['id']]);
// Notify Founder (DB)
$stmt = db()->prepare("INSERT INTO notifications (user_id, content) VALUES (?, ?)");
$stmt->execute([$startup['founder_id'], "Congratulations! The funding round for " . $startup['name'] . " has reached its goal of £" . number_format($updatedRound['funding_goal']) . "!"]);
// Notify All Investors for this round (DB + Email)
$stmt = db()->prepare("SELECT DISTINCT u.id, u.email, u.full_name FROM investments i JOIN users u ON i.investor_id = u.id WHERE i.funding_round_id = ? AND i.status = 'approved'");
$stmt->execute([$updatedRound['id']]);
$investorsToNotify = $stmt->fetchAll();
foreach ($investorsToNotify as $invUser) {
// DB Notification
$stmt = db()->prepare("INSERT INTO notifications (user_id, content) VALUES (?, ?)");
$stmt->execute([$invUser['id'], "Great news! The funding round for " . $startup['name'] . " that you invested in has reached its goal!"]);
// Email Notification
$subject = "Funding Goal Reached for " . $startup['name'];
$html = "<h1>Goal Reached!</h1><p>Hi " . htmlspecialchars($invUser['full_name']) . ", the funding round for <strong>" . htmlspecialchars($startup['name']) . "</strong> has successfully reached its goal. Thank you for being a part of this journey!</p>";
$text = "Goal Reached! Hi " . $invUser['full_name'] . ", the funding round for " . $startup['name'] . " has successfully reached its goal. Thank you for being a part of this journey!";
MailService::sendMail($invUser['email'], $subject, $html, $text);
}
// Email Founder
$stmt = db()->prepare("SELECT email, full_name FROM users WHERE id = ?");
$stmt->execute([$startup['founder_id']]);
$founderUser = $stmt->fetch();
@ -223,10 +191,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
MailService::sendMail($founderUser['email'], $subject, $html, $text);
}
}
db()->commit();
$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();
@ -314,7 +280,6 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
<p style="font-size: 18px; line-height: 1.6; color: var(--text-secondary); white-space: pre-wrap;"><?= htmlspecialchars($startup['description']) ?></p>
</section>
<!-- Public Updates Section -->
<section class="card" style="margin-bottom: 40px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">Public Updates</h2>
@ -322,7 +287,6 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
<button class="btn btn-outline" onclick="document.getElementById('postUpdateForm').style.display='block'">Post Update</button>
<?php endif; ?>
</div>
<?php if ($user['role'] === 'founder' && $startup['founder_id'] == $user_id): ?>
<div id="postUpdateForm" style="display: none; background: rgba(255,255,255,0.02); padding: 25px; border-radius: 15px; margin-bottom: 30px; border: 1px solid var(--border-color);">
<h4 style="margin-top: 0;">New Progress Report</h4>
@ -341,13 +305,11 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
</form>
</div>
<?php endif; ?>
<?php
$stmt = db()->prepare("SELECT * FROM startup_updates WHERE startup_id = ? ORDER BY created_at DESC");
$stmt->execute([$startup_id]);
$updates = $stmt->fetchAll();
?>
<?php if (empty($updates)): ?>
<p style="color: var(--text-secondary); font-style: italic;">No updates have been posted yet.</p>
<?php else: ?>
@ -367,9 +329,7 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
<section class="card" style="border-left: 5px solid var(--accent-blue);">
<h3 style="margin-top: 0;">Active Funding Round</h3>
<div style="margin: 20px 0;">
<?php
$percent = ($activeRound['funding_goal'] > 0) ? min(100, ($activeRound['funding_raised'] / $activeRound['funding_goal']) * 100) : 0;
?>
<?php $percent = ($activeRound['funding_goal'] > 0) ? min(100, ($activeRound['funding_raised'] / $activeRound['funding_goal']) * 100) : 0; ?>
<div style="width: 100%; height: 10px; background: var(--border-color); border-radius: 5px; overflow: hidden;">
<div style="width: <?= $percent ?>%; height: 100%; background: var(--gradient-primary);"></div>
</div>
@ -378,7 +338,6 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
<span style="font-size: 24px;">£<?= number_format($activeRound['funding_raised']) ?> raised</span>
<span style="color: var(--text-secondary);">Target: £<?= number_format($activeRound['funding_goal']) ?></span>
</div>
<?php if ($user['role'] === 'founder' && $startup['founder_id'] == $user_id): ?>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<form method="POST" onsubmit="return confirm('Finish this round early? No more investments will be accepted.');">
@ -400,7 +359,6 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
<input type="number" name="amount" min="50" step="10" value="100" class="form-control" style="flex: 1; background: var(--surface-color); color: #fff; border: 1px solid var(--border-color);">
<button type="submit" class="btn btn-primary">Back this Venture</button>
</div>
<p style="margin-top: 10px; font-size: 14px; color: var(--text-secondary);">Minimum investment is £50. Your support helps students grow.</p>
</form>
<?php endif; ?>
</section>
@ -408,18 +366,15 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
<section class="card" style="background: rgba(255,255,255,0.02); text-align: center; padding: 40px; border: 1px dashed var(--border-color);">
<div style="font-size: 48px; color: var(--text-secondary); margin-bottom: 20px;"><i class="fas fa-lock"></i></div>
<h3>No Active Funding Round</h3>
<p style="color: var(--text-secondary);">This startup is not currently raising funds, or the previous round has closed.</p>
<?php if ($user['role'] === 'founder' && $startup['founder_id'] == $user_id): ?>
<button class="btn btn-primary" style="margin-top: 20px;" onclick="document.getElementById('newRoundForm').style.display='block'">Launch New Funding Round</button>
<div id="newRoundForm" style="display: none; margin-top: 30px; text-align: left; background: var(--surface-color); padding: 25px; border-radius: 15px; border: 1px solid var(--border-color);">
<h4>Round Details</h4>
<form method="POST">
<input type="hidden" name="action" value="start_new_round">
<div class="form-group" style="margin-bottom: 20px;">
<label>New Funding Target (£)</label>
<input type="number" name="new_target" min="50" step="50" class="form-control" placeholder="e.g. 5000" required style="background: var(--bg-color); color: #fff; border: 1px solid var(--border-color);">
<input type="number" name="new_target" min="50" step="50" class="form-control" required style="background: var(--bg-color); color: #fff; border: 1px solid var(--border-color);">
</div>
<button type="submit" class="btn btn-primary">Start Round</button>
<button type="button" class="btn btn-secondary" onclick="document.getElementById('newRoundForm').style.display='none'">Cancel</button>
@ -475,14 +430,10 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
</div>
<div>
<div style="font-weight: 600;"><?= htmlspecialchars($founder['full_name']) ?></div>
<div style="font-size: 13px; color: var(--text-secondary);">
<?= htmlspecialchars($founder['university']) ?>
</div>
<div style="font-size: 13px; color: var(--text-secondary);"><?= htmlspecialchars($founder['university']) ?></div>
</div>
</div>
<a href="messages.php?chat_with=<?= $founder['id'] ?>&startup_id=<?= $startup_id ?>" class="btn btn-outline" style="width: 100%; text-align: center; display: block; border-radius: 12px; border: 1px solid var(--border-color);">Send Message</a>
<?php else: ?>
<div style="color: var(--text-secondary); font-style: italic;">Founder account deleted. Venture is community-managed.</div>
<?php endif; ?>
</section>
</div>
@ -490,26 +441,9 @@ $platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
</main>
<style>
header {
background: rgba(10, 10, 15, 0.8);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border-color);
padding: 15px 0;
position: sticky;
top: 0;
z-index: 1000;
}
.nav-links a {
color: var(--text-secondary);
text-decoration: none;
margin: 0 15px;
font-size: 14px;
font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover {
color: #fff;
}
header { background: rgba(10, 10, 15, 0.8); backdrop-filter: blur(20px); border-bottom: 1px solid var(--border-color); padding: 15px 0; position: sticky; top: 0; z-index: 1000; }
.nav-links a { color: var(--text-secondary); text-decoration: none; margin: 0 15px; font-size: 14px; font-weight: 500; transition: color 0.2s; }
.nav-links a:hover { color: #fff; }
</style>
</body>