299 lines
20 KiB
PHP
299 lines
20 KiB
PHP
<?php
|
|
require_once 'db/config.php';
|
|
session_start();
|
|
|
|
$user_id = $_SESSION['user_id'] ?? null;
|
|
if (!$user_id) { header('Location: login.php'); exit; }
|
|
|
|
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
|
|
$stmt->execute([$user_id]);
|
|
$user = $stmt->fetch();
|
|
if (!$user) { header('Location: login.php'); exit; }
|
|
|
|
$current_user_id = $user_id;
|
|
$platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
|
|
|
|
// Blocked users logic
|
|
$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;
|
|
$mySkills = array_map('trim', explode(',', strtolower($me['skills'] ?? '')));
|
|
$theirSkills = array_map('trim', explode(',', strtolower($them['skills'] ?? '')));
|
|
$myPreferred = array_map('trim', explode(',', strtolower($me['preferred_co_founder_skills'] ?? '')));
|
|
$theirPreferred = array_map('trim', explode(',', strtolower($them['preferred_co_founder_skills'] ?? '')));
|
|
$myIndustries = array_map('trim', explode(',', strtolower($me['startup_industries'] ?? '')));
|
|
$theirIndustries = array_map('trim', explode(',', strtolower($them['startup_industries'] ?? '')));
|
|
|
|
$skillMatches = array_intersect($theirSkills, $myPreferred);
|
|
$score += count($skillMatches) * 5;
|
|
$industryMatches = array_intersect($myIndustries, $theirIndustries);
|
|
$score += count($industryMatches) * 3;
|
|
if (!empty($me['university']) && $me['university'] === $them['university']) { $score += 5; }
|
|
return ['total' => $score, 'skillMatches' => array_values($skillMatches), 'industryMatches' => array_values($industryMatches)];
|
|
}
|
|
|
|
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'];
|
|
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;
|
|
$u1 = min($current_user_id, $swiped_id);
|
|
$u2 = max($current_user_id, $swiped_id);
|
|
$stmt = db()->prepare("INSERT INTO matches (user1_id, user2_id, status) VALUES (?, ?, 'active') ON DUPLICATE KEY UPDATE status = 'active'");
|
|
$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."]);
|
|
}
|
|
}
|
|
echo json_encode(['status' => 'success', 'match' => $isMatch]);
|
|
exit;
|
|
}
|
|
echo json_encode(['status' => 'error']);
|
|
exit;
|
|
}
|
|
|
|
// Fetch active matches
|
|
$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 != ? AND m.status = 'active' AND u.id NOT IN ($placeholders) ORDER BY m.created_at DESC";
|
|
$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
|
|
$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 = db()->prepare($sql);
|
|
$stmt->execute(array_merge([$current_user_id, $current_user_id], $all_blocked ?: []));
|
|
$allCandidates = $stmt->fetchAll();
|
|
|
|
foreach ($allCandidates as &$c) { $c['match_data'] = calculateMatchScore($user, $c); $c['score'] = $c['match_data']['total']; }
|
|
usort($allCandidates, function($a, $b) { return $b['score'] <=> $a['score']; });
|
|
$swipeCandidates = array_slice($allCandidates, 0, 10);
|
|
|
|
// Fetch partners for Browse
|
|
$search = $_GET['q'] ?? '';
|
|
$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%"; $params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%"; }
|
|
$stmt = db()->prepare("SELECT * FROM users WHERE $where LIMIT 40");
|
|
$stmt->execute($params);
|
|
$browseCandidates = $stmt->fetchAll();
|
|
foreach ($browseCandidates as &$c) { $c['match_data'] = calculateMatchScore($user, $c); $c['score'] = $c['match_data']['total']; }
|
|
if (!$search) { usort($browseCandidates, function($a, $b) { return $b['score'] <=> $a['score']; }); }
|
|
?>
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Find Partners — <?= htmlspecialchars($platformName) ?></title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
<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>
|
|
.match-avatar {
|
|
width: 85px;
|
|
height: 85px;
|
|
border-radius: 12px;
|
|
background: var(--surface-color);
|
|
border: 2px solid var(--accent-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 30px;
|
|
font-weight: 800;
|
|
color: var(--text-primary);
|
|
margin-bottom: 12px;
|
|
box-shadow: var(--shadow-soft);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.match-avatar img {
|
|
width: 100%; height: 100%; object-fit: cover;
|
|
}
|
|
.tab-btn.active {
|
|
background: var(--accent-primary) !important;
|
|
color: #000 !important;
|
|
border-color: var(--accent-primary) !important;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<div class="container" style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
|
|
<a href="dashboard.php" class="logo-container">
|
|
<img src="assets/images/logo.svg?v=<?php echo time(); ?>" alt="<?= htmlspecialchars($platformName) ?> Logo" class="logo-img">
|
|
<span class="logo-text"><?= htmlspecialchars($platformName) ?></span>
|
|
</a>
|
|
<nav class="nav-links">
|
|
<?php if ($user['role'] === 'founder'): ?>
|
|
<a href="startups.php">My Startups</a>
|
|
<a href="partners.php" class="active">Find Partners</a>
|
|
<?php else: ?>
|
|
<a href="funding_rounds.php">Founding Rounds</a>
|
|
<a href="portfolio.php">Portfolio</a>
|
|
<a href="discover.php">Discovery Hub</a>
|
|
<?php endif; ?>
|
|
<a href="messages.php">Messages</a>
|
|
</nav>
|
|
<div style="display: flex; align-items: center; gap: 15px;">
|
|
<a href="notifications.php" style="color: var(--text-secondary); position: relative; font-size: 18px;">
|
|
<i class="fas fa-bell"></i>
|
|
</a>
|
|
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; background: var(--surface-color); border-radius: 50px; border: 1px solid var(--border-color);">
|
|
<div style="width: 24px; height: 24px; background: var(--accent-primary); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #000; font-weight: 800;">
|
|
<?= substr($user['full_name'], 0, 1) ?>
|
|
</div>
|
|
<span style="font-size: 13px; font-weight: 600;"><?= htmlspecialchars(explode(' ', $user['full_name'])[0]) ?></span>
|
|
</div>
|
|
<a href="logout.php" class="btn btn-secondary" style="padding: 8px 16px; font-size: 12px;">Log Out</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="container">
|
|
<div class="page-header" style="padding: 60px 0 40px; text-align: center;">
|
|
<h1 style="font-size: 56px; font-weight: 900; color: var(--text-primary);">Meet Your <span style="color: var(--accent-primary);">Team.</span></h1>
|
|
<p style="color: var(--text-secondary); font-size: 20px;">Connect with high-potential founders and engineers in the university ecosystem.</p>
|
|
</div>
|
|
|
|
<?php if (!empty($matches)): ?>
|
|
<section class="matches-section" style="margin-bottom: 60px;">
|
|
<h3 style="margin-bottom: 24px; font-size: 14px; font-weight: 800; color: var(--accent-primary); text-transform: uppercase; letter-spacing: 1.5px; display: flex; align-items: center; gap: 12px;">
|
|
<i class="fas fa-fire"></i> Active Connections
|
|
</h3>
|
|
<div class="matches-scroller" style="display: flex; gap: 24px; overflow-x: auto; padding: 20px 5px; scrollbar-width: none;">
|
|
<?php foreach ($matches as $match): ?>
|
|
<div class="match-card" onclick="location.href='messages.php?chat_with=<?= $match['id'] ?>'" style="flex: 0 0 85px; text-align: center; cursor: pointer;">
|
|
<div class="match-avatar">
|
|
<?php if ($match['profile_photo']): ?><img src="<?= htmlspecialchars($match['profile_photo']) ?>"><?php else: ?><?= substr($match['full_name'], 0, 1) ?><?php endif; ?>
|
|
</div>
|
|
<div class="match-name" style="font-size: 13px; font-weight: 700; color: var(--text-primary);"><?= htmlspecialchars(explode(' ', $match['full_name'])[0]) ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<div class="tabs" style="display: flex; justify-content: center; gap: 20px; margin-bottom: 50px;">
|
|
<button class="tab-btn active" id="tab-swipe" onclick="setTab('swipe')" style="padding: 14px 28px; border-radius: 999px; background: var(--surface-color); border: 1px solid var(--border-color); color: var(--text-secondary); font-weight: 700; cursor: pointer; transition: all 0.4s; font-size: 13px; text-transform: uppercase;">
|
|
<i class="fas fa-layer-group"></i> Swipe Engine
|
|
</button>
|
|
<button class="tab-btn" id="tab-browse" onclick="setTab('browse')" style="padding: 14px 28px; border-radius: 999px; background: var(--surface-color); border: 1px solid var(--border-color); color: var(--text-secondary); font-weight: 700; cursor: pointer; transition: all 0.4s; font-size: 13px; text-transform: uppercase;">
|
|
<i class="fas fa-list-ul"></i> Directory
|
|
</button>
|
|
</div>
|
|
|
|
<div id="swipe-view" class="swipe-container" style="display: flex; flex-direction: column; align-items: center; padding: 20px 0;">
|
|
<div class="card-stack" id="card-stack" style="position: relative; width: 100%; max-width: 440px; height: 600px; perspective: 1000px;">
|
|
<?php if (empty($swipeCandidates)): ?>
|
|
<div style="text-align: center; color: var(--text-secondary); padding: 120px 40px; background: var(--surface-color); border-radius: 24px; border: 1px dashed var(--border-color); width: 100%;">
|
|
<h3>The well is dry!</h3>
|
|
<p>Check back tomorrow for more visionaries.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<?php foreach (array_reverse($swipeCandidates) as $c): ?>
|
|
<div class="swipe-card" data-id="<?= $c['id'] ?>" style="position: absolute; width: 100%; height: 100%; background: var(--surface-color); border: 1px solid var(--border-color); border-radius: 24px; overflow: hidden; box-shadow: var(--shadow-soft); transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.6s ease; cursor: grab; display: flex; flex-direction: column;">
|
|
<div class="card-header-img" style="height: 320px; background: var(--elevated-color); display: flex; align-items: center; justify-content: center; font-size: 120px; color: rgba(255,255,255,0.05); position: relative;">
|
|
<?php if ($c['score'] >= 10): ?><div class="match-badge" style="position: absolute; top: 25px; right: 25px; background: var(--accent-primary); padding: 8px 16px; border-radius: 50px; color: #000; font-weight: 800; font-size: 12px; display: flex; align-items: center; gap: 8px; z-index: 10; text-transform: uppercase;"><i class="fas fa-bolt"></i> <?= $c['score'] ?>% Match</div><?php endif; ?>
|
|
<?php if ($c['profile_photo']): ?><img src="<?= htmlspecialchars($c['profile_photo']) ?>" style="width: 100%; height: 100%; object-fit: cover;"><?php else: ?><i class="fas fa-user-astronaut"></i><?php endif; ?>
|
|
</div>
|
|
<div class="card-details" style="padding: 30px; flex-grow: 1; display: flex; flex-direction: column;">
|
|
<div class="card-title" style="font-size: 28px; font-weight: 900; margin-bottom: 6px;"><?= htmlspecialchars($c['full_name']) ?></div>
|
|
<div class="card-subtitle" style="font-size: 15px; color: var(--accent-primary); font-weight: 700; margin-bottom: 18px;"><i class="fas fa-university"></i> <?= htmlspecialchars($c['university']) ?></div>
|
|
<p class="card-bio" style="font-size: 16px; color: var(--text-secondary); line-height: 1.6; margin-bottom: 20px;"><?= htmlspecialchars($c['bio'] ?: 'Building the next generation of startup infrastructure.') ?></p>
|
|
<div class="card-tags" style="display: flex; flex-wrap: wrap; gap: 10px;">
|
|
<?php foreach (array_slice(explode(',', $c['skills']), 0, 4) as $skill): if(empty(trim($skill))) continue; ?><span class="card-tag" style="font-size: 11px; padding: 6px 14px; background: var(--bg-color); border-radius: 50px; border: 1px solid var(--border-color); font-weight: 600; color: var(--text-secondary);"><?= htmlspecialchars(trim($skill)) ?></span><?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php if (!empty($swipeCandidates)): ?>
|
|
<div class="swipe-actions" style="margin-top: 40px; display: flex; gap: 30px;">
|
|
<button class="action-btn btn-dislike" onclick="handleSwipe('dislike')"><i class="fas fa-times"></i></button>
|
|
<button class="action-btn btn-like" onclick="handleSwipe('like')" style="width: 80px; height: 80px; font-size: 32px;"><i class="fas fa-heart"></i></button>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div id="browse-view" style="display: none;">
|
|
<div style="margin-bottom: 40px;">
|
|
<form method="GET" style="display: flex; gap: 15px; max-width: 700px; margin: 0 auto;">
|
|
<input type="text" name="q" value="<?= htmlspecialchars($search) ?>" placeholder="Search by skill, university..." class="form-control" style="flex: 1;">
|
|
<button type="submit" class="btn btn-primary">Search</button>
|
|
</form>
|
|
</div>
|
|
<div class="browse-container" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 30px;">
|
|
<?php foreach ($browseCandidates as $c): ?>
|
|
<div class="card" onclick="location.href='messages.php?chat_with=<?= $c['id'] ?>'" style="cursor: pointer; position: relative;">
|
|
<?php if ($c['score'] >= 10): ?><div style="position: absolute; top: 20px; right: 20px; font-size: 11px; font-weight: 800; color: var(--accent-primary); text-transform: uppercase;"><i class="fas fa-bolt"></i> <?= $c['score'] ?>%</div><?php endif; ?>
|
|
<div style="display: flex; gap: 15px; align-items: center; margin-bottom: 20px;">
|
|
<div style="width: 50px; height: 50px; border-radius: 8px; background: var(--accent-primary); display: flex; align-items: center; justify-content: center; font-weight: 900; color: #000;"><?= substr($c['full_name'], 0, 1) ?></div>
|
|
<div><div style="font-weight: 800; color: var(--text-primary);"><?= htmlspecialchars($c['full_name']) ?></div><div style="font-size: 12px; color: var(--text-secondary);"><?= htmlspecialchars($c['university']) ?></div></div>
|
|
</div>
|
|
<p style="font-size: 14px; color: var(--text-secondary); height: 40px; overflow: hidden;"><?= htmlspecialchars($c['bio'] ?: 'Building the future.') ?></p>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<div id="match-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); z-index: 2000; display: none; align-items: center; justify-content: center; backdrop-filter: blur(10px);">
|
|
<div style="text-align: center;">
|
|
<h1 style="font-size: 80px; font-weight: 950; color: var(--accent-primary); margin-bottom: 30px;">It's a Match!</h1>
|
|
<p style="font-size: 24px; color: #fff; margin-bottom: 40px;">You and this founder are ready to build together.</p>
|
|
<button class="btn btn-primary" onclick="location.href='messages.php'" style="padding: 16px 32px; font-size: 18px;">Start Conversation</button>
|
|
<button class="btn btn-secondary" style="margin-left: 15px; padding: 16px 32px; font-size: 18px;" onclick="document.getElementById('match-modal').style.display='none'">Keep Swiping</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function setTab(tab) {
|
|
document.getElementById('swipe-view').style.display = tab === 'swipe' ? 'flex' : 'none';
|
|
document.getElementById('browse-view').style.display = tab === 'browse' ? 'block' : 'none';
|
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
document.getElementById('tab-' + tab).classList.add('active');
|
|
}
|
|
function handleSwipe(direction) {
|
|
const stack = document.getElementById('card-stack');
|
|
const card = stack.querySelector('.swipe-card:last-child');
|
|
if (!card) return;
|
|
const swiped_id = card.dataset.id;
|
|
card.style.transform = `translateX(${direction === 'like' ? '1000px' : '-1000px'}) rotate(${direction === 'like' ? '30deg' : '-30deg'})`;
|
|
card.style.opacity = '0';
|
|
setTimeout(() => {
|
|
card.remove();
|
|
fetch('partners.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: `action=swipe&swiped_id=${swiped_id}&direction=${direction}`
|
|
}).then(r => r.json()).then(data => {
|
|
if (data.match) { document.getElementById('match-modal').style.display = 'flex'; }
|
|
});
|
|
}, 300);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|