v15
This commit is contained in:
parent
e904e15720
commit
fc3fe3f0a0
13
db/migrations/09_unmatch_and_block.sql
Normal file
13
db/migrations/09_unmatch_and_block.sql
Normal 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';
|
||||
37
discover.php
37
discover.php
@ -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>
|
||||
128
messages.php
128
messages.php
@ -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): ?>
|
||||
|
||||
298
partners.php
298
partners.php
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user