38873-vm/partners.php
Flatlogic Bot c312777c3a v19
2026-02-28 18:04:19 +00:00

415 lines
25 KiB
PHP

<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
require_once __DIR__ . '/db/config.php';
$current_user_id = $_SESSION['user_id'];
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$current_user_id]);
$user = $stmt->fetch();
if (!$user || $user['role'] !== 'founder') {
header("Location: dashboard.php");
exit;
}
if (!$user['onboarding_completed']) {
header("Location: founder_onboarding.php");
exit;
}
// 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;
$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;
$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;
$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;
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;
$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."]);
}
}
echo json_encode(['status' => 'success', 'match' => $isMatch]);
exit;
}
echo json_encode(['status' => 'error']);
exit;
}
// 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 != ?
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 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 = ?) 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']; });
}
$platformName = defined('PLATFORM_NAME') ? PLATFORM_NAME : 'Gatsby';
?>
<!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>
.page-header { padding: 60px 0 40px; text-align: center; }
.page-header h1 { font-size: 56px; font-weight: 900; 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: 20px; }
.matches-section { margin-bottom: 60px; }
.matches-scroller { display: flex; gap: 24px; overflow-x: auto; padding: 20px 5px; scrollbar-width: none; }
.matches-scroller::-webkit-scrollbar { display: none; }
.match-card { flex: 0 0 85px; text-align: center; cursor: pointer; transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
.match-card:hover { transform: scale(1.1) translateY(-5px); }
.match-avatar { width: 85px; height: 85px; border-radius: 28px; background: var(--surface-color); border: 2px solid var(--accent-blue); display: flex; align-items: center; justify-content: center; font-size: 30px; font-weight: 800; color: #fff; margin-bottom: 12px; box-shadow: 0 15px 30px rgba(0, 242, 255, 0.2); position: relative; overflow: hidden; }
.match-avatar img { width: 100%; height: 100%; object-fit: cover; }
.match-name { font-size: 13px; font-weight: 700; color: var(--text-primary); }
.tabs { display: flex; justify-content: center; gap: 20px; margin-bottom: 50px; }
.tab-btn { padding: 14px 28px; border-radius: 50px; background: var(--surface-color); border: 1px solid var(--border-color); color: var(--text-secondary); font-weight: 700; cursor: pointer; transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
.tab-btn.active { background: var(--gradient-primary); color: #fff; border-color: transparent; box-shadow: 0 15px 30px rgba(0, 122, 255, 0.3); transform: scale(1.05); }
.swipe-container { display: flex; flex-direction: column; align-items: center; padding: 20px 0; }
.card-stack { position: relative; width: 100%; max-width: 440px; height: 600px; perspective: 1000px; }
.swipe-card { position: absolute; width: 100%; height: 100%; background: var(--surface-color); border: 1px solid var(--glass-border); border-radius: 40px; overflow: hidden; box-shadow: 0 30px 60px rgba(0,0,0,0.5); transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.6s ease; cursor: grab; display: flex; flex-direction: column; backdrop-filter: blur(20px); }
.card-header-img { height: 320px; background: var(--gradient-primary); display: flex; align-items: center; justify-content: center; font-size: 120px; color: rgba(255,255,255,0.2); position: relative; }
.match-badge { position: absolute; top: 25px; right: 25px; background: rgba(10, 10, 15, 0.8); backdrop-filter: blur(15px); padding: 10px 18px; border-radius: 50px; color: var(--accent-blue); font-weight: 800; font-size: 14px; border: 1px solid var(--accent-blue); display: flex; align-items: center; gap: 8px; box-shadow: 0 10px 20px rgba(0,0,0,0.3); z-index: 10; }
.card-details { padding: 30px; flex-grow: 1; display: flex; flex-direction: column; background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.4)); }
.card-title { font-size: 28px; font-weight: 900; margin-bottom: 6px; letter-spacing: -0.5px; }
.card-subtitle { font-size: 15px; color: var(--accent-blue); font-weight: 700; margin-bottom: 18px; display: flex; align-items: center; gap: 8px; }
.card-bio { font-size: 16px; color: var(--text-secondary); line-height: 1.6; margin-bottom: 20px; font-weight: 400; }
.card-tags { display: flex; flex-wrap: wrap; gap: 10px; }
.card-tag { font-size: 12px; padding: 6px 14px; background: rgba(255,255,255,0.04); border-radius: 20px; border: 1px solid var(--glass-border); font-weight: 600; color: var(--text-secondary); }
.card-tag.highlight { border-color: var(--accent-blue); color: var(--accent-blue); background: rgba(0, 242, 255, 0.1); }
.browse-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 30px; }
.candidate-card { background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 32px; padding: 30px; transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); cursor: pointer; position: relative; backdrop-filter: blur(20px); }
.candidate-card:hover { transform: translateY(-10px); border-color: var(--accent-blue); box-shadow: 0 20px 40px rgba(0, 242, 255, 0.1); background: rgba(255, 255, 255, 0.05); }
.score-indicator { position: absolute; top: 30px; right: 30px; font-size: 11px; font-weight: 800; color: var(--accent-blue); text-transform: uppercase; letter-spacing: 1px; }
#match-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(5, 5, 8, 0.98); z-index: 2000; display: none; align-items: center; justify-content: center; text-align: center; backdrop-filter: blur(30px); }
.match-popup { max-width: 450px; padding: 60px; position: relative; }
.match-title { font-size: 64px; font-weight: 950; background: var(--gradient-primary); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 24px; letter-spacing: -2px; }
</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" alt="<?= htmlspecialchars($platformName) ?> Logo" class="logo-img">
<span class="logo-text"><?= htmlspecialchars($platformName) ?></span>
</a>
<nav class="nav-links">
<a href="startups.php">My Startups</a>
<a href="partners.php" class="active">Find Partners</a>
<a href="discover.php">Discovery Hub</a>
<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: 20px;">
<i class="fas fa-bell"></i>
</a>
<a href="logout.php" class="btn btn-secondary" style="padding: 10px 20px; font-size: 14px; border-radius: 12px;">Log Out</a>
</div>
</div>
</header>
<main class="container">
<div class="hero-bg">
<div class="hero-blob" style="top: 10%; left: -10%;"></div>
<div class="hero-blob" style="top: 40%; right: -10%; width: 500px; height: 500px; background: radial-gradient(circle, rgba(138, 43, 226, 0.1) 0%, rgba(0, 242, 255, 0.1) 100%);"></div>
</div>
<div class="page-header">
<div class="match-score-pill" style="margin-bottom: 20px;"><i class="fas fa-search-nodes"></i> Discovery Engine Active</div>
<h1>Meet Your Team.</h1>
<p>Connect with high-potential founders and engineers in the university ecosystem.</p>
</div>
<?php if (!empty($matches)): ?>
<section class="matches-section">
<h3 style="margin-bottom: 24px; font-size: 18px; font-weight: 800; color: var(--text-primary); text-transform: uppercase; letter-spacing: 1.5px; opacity: 0.7; display: flex; align-items: center; gap: 12px;">
<i class="fas fa-fire" style="color: #ff4d4d;"></i> Active Connections
</h3>
<div class="matches-scroller">
<?php foreach ($matches as $match): ?>
<div class="match-card" onclick="location.href='messages.php?chat_with=<?= $match['id'] ?>'">
<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"><?= htmlspecialchars(explode(' ', $match['full_name'])[0]) ?></div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<div class="tabs">
<button class="tab-btn active" id="tab-swipe" onclick="setTab('swipe')">
<i class="fas fa-layer-group"></i> Swipe Engine
</button>
<button class="tab-btn" id="tab-browse" onclick="setTab('browse')">
<i class="fas fa-list-ul"></i> Directory
</button>
</div>
<div id="swipe-view" class="swipe-container">
<div class="card-stack" id="card-stack">
<?php if (empty($swipeCandidates)): ?>
<div style="text-align: center; color: var(--text-secondary); padding: 120px 40px; background: var(--glass-bg); border-radius: 48px; border: 1px solid var(--glass-border); width: 100%;">
<div class="card-icon" style="margin: 0 auto 30px; width: 80px; height: 80px; font-size: 32px;"><i class="fas fa-wind"></i></div>
<h3 style="font-size: 28px; font-weight: 900; color: #fff; margin-bottom: 10px;">The well is dry!</h3>
<p style="font-size: 18px;">You've seen everyone for now. 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'] ?>">
<div class="card-header-img">
<?php if ($c['score'] >= 10): ?>
<div class="match-badge"><i class="fas fa-bolt"></i> <?= $c['score'] ?>% Match Score</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">
<div class="card-title"><?= htmlspecialchars($c['full_name']) ?></div>
<div class="card-subtitle"><i class="fas fa-university"></i> <?= htmlspecialchars($c['university']) ?></div>
<p class="card-bio"><?= htmlspecialchars($c['bio'] ?: 'Building the next generation of startup infrastructure.') ?></p>
<div class="card-tags">
<?php
$skills = explode(',', $c['skills']);
$myNeeds = array_map('trim', explode(',', strtolower($user['preferred_co_founder_skills'] ?? '')));
foreach (array_slice($skills, 0, 4) as $skill):
if(empty(trim($skill))) continue;
$isNeeded = in_array(trim(strtolower($skill)), $myNeeds);
?>
<span class="card-tag <?= $isNeeded ? 'highlight' : '' ?>">
<?= $isNeeded ? '<i class="fas fa-check"></i> ' : '' ?>
<?= htmlspecialchars(trim($skill)) ?>
</span>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php if (!empty($swipeCandidates)): ?>
<div class="swipe-actions">
<button class="action-btn btn-dislike" onclick="handleSwipe('dislike')" title="Not for now">
<i class="fas fa-times"></i>
</button>
<button class="action-btn btn-like" onclick="handleSwipe('like')" title="Hell yes!">
<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; position: relative;">
<input type="text" name="q" value="<?= htmlspecialchars($search) ?>" placeholder="Search by skill, university, or industry..." class="form-control" style="flex: 1; background: var(--glass-bg); border: 1px solid var(--glass-border); color: #fff; padding: 18px 25px; border-radius: 20px; font-size: 16px; backdrop-filter: blur(10px);">
<button type="submit" class="btn btn-primary" style="border-radius: 16px;"><i class="fas fa-search"></i> Search</button>
</form>
</div>
<div class="browse-container">
<?php foreach ($browseCandidates as $c): ?>
<div class="candidate-card" onclick="location.href='messages.php?chat_with=<?= $c['id'] ?>'">
<?php if ($c['score'] >= 10): ?>
<div class="score-indicator"><i class="fas fa-sparkles"></i> <?= $c['score'] ?>% Match</div>
<?php endif; ?>
<div class="candidate-header" style="display: flex; gap: 20px; align-items: center; margin-bottom: 25px;">
<div class="candidate-avatar" style="width: 70px; height: 70px; border-radius: 22px; background: var(--gradient-primary); display: flex; align-items: center; justify-content: center; font-weight: 900; color: #fff; font-size: 24px; box-shadow: 0 10px 20px rgba(0,0,0,0.2);">
<?php if ($c['profile_photo']): ?>
<img src="<?= htmlspecialchars($c['profile_photo']) ?>" style="width: 100%; height: 100%; object-fit: cover; border-radius: 20px;">
<?php else: ?>
<?= substr($c['full_name'], 0, 1) ?>
<?php endif; ?>
</div>
<div>
<div style="font-weight: 900; font-size: 20px; letter-spacing: -0.5px;"><?= htmlspecialchars($c['full_name']) ?></div>
<div style="font-size: 14px; color: var(--accent-blue); font-weight: 700; opacity: 0.8;"><?= htmlspecialchars($c['university']) ?></div>
</div>
</div>
<p style="font-size: 15px; color: var(--text-secondary); margin-bottom: 20px; line-height: 1.6; height: 48px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;">
<?= htmlspecialchars($c['bio'] ?: 'A visionary founder building something unique in the ecosystem.') ?>
</p>
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
<?php
$skills = explode(',', $c['skills']);
$myNeeds = array_map('trim', explode(',', strtolower($user['preferred_co_founder_skills'] ?? '')));
foreach (array_slice($skills, 0, 3) as $skill):
if(empty(trim($skill))) continue;
$isNeeded = in_array(trim(strtolower($skill)), $myNeeds);
?>
<span style="font-size: 11px; padding: 5px 12px; background: <?= $isNeeded ? 'rgba(0, 242, 255, 0.12)' : 'rgba(255,255,255,0.05)' ?>; border-radius: 50px; color: <?= $isNeeded ? 'var(--accent-blue)' : 'var(--text-secondary)' ?>; border: 1px solid <?= $isNeeded ? 'var(--accent-blue)' : 'var(--glass-border)' ?>; font-weight: 600;">
<?= htmlspecialchars(trim($skill)) ?>
</span>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</main>
<div id="match-modal">
<div class="match-popup">
<div class="match-score-pill" style="margin-bottom: 20px; font-size: 16px; padding: 10px 24px;"><i class="fas fa-heart"></i> Mutual Interest</div>
<h1 class="match-title">It's a Match!</h1>
<p style="color: var(--text-secondary); margin-bottom: 40px; font-size: 20px; line-height: 1.5;">You both saw something great in each other. Time to build the future together.</p>
<button class="btn btn-primary" style="width: 100%; margin-bottom: 20px; padding: 20px; font-size: 18px; border-radius: 20px;" onclick="location.href='messages.php'">
<i class="fas fa-comment"></i> Send a Message
</button>
<button class="btn btn-secondary" style="width: 100%; padding: 20px; font-size: 18px; border-radius: 20px;" onclick="closeMatch()">
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.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');
const rotate = direction === 'like' ? 30 : -30;
const x = direction === 'like' ? 1200 : -1200;
topCard.style.transform = `translateX(${x}px) rotate(${rotate}deg) scale(0.8)`;
topCard.style.opacity = '0';
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'; }
setTimeout(() => {
topCard.remove();
if (stack.querySelectorAll('.swipe-card').length === 0) { location.reload(); }
}, 600);
});
}
function closeMatch() { document.getElementById('match-modal').style.display = 'none'; }
</script>
</body>
</html>