diff --git a/assets/css/custom.css b/assets/css/custom.css
index 42bf88b..81c2ced 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,8 +1,256 @@
-body {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+/**
+ * Custom Styles for Compañía
+ */
+
+:root {
+ --brand-primary: #C05761; /* Deep Rose */
+ --brand-secondary: #E3C16F; /* Champagne Gold */
+ --brand-bg: #FDFBF7; /* Cream */
+ --brand-text: #333333; /* Charcoal */
+ --brand-text-muted: #6c757d;
+ --brand-white: #ffffff;
+ --brand-like: #28a745;
+ --brand-dislike: #dc3545;
+
+ --border-radius-lg: 16px;
+ --border-radius-md: 10px;
+ --font-serif: 'Playfair Display', serif;
+ --font-sans: 'Inter', sans-serif;
+ --shadow-soft: 0 4px 20px rgba(0,0,0,0.05);
+ --shadow-card: 0 10px 30px rgba(0,0,0,0.08);
+ --shadow-hover: 0 12px 35px rgba(192, 87, 97, 0.15);
}
-.hero {
- padding: 6rem 1rem;
- background-color: #f8f9fa;
+body {
+ background-color: var(--brand-bg);
+ color: var(--brand-text);
+ font-family: var(--font-sans);
+ line-height: 1.6;
+ padding-top: 80px;
}
+
+h1, h2, h3, h4, h5, h6 {
+ font-family: var(--font-serif);
+ font-weight: 700;
+ color: var(--brand-primary);
+}
+
+.navbar {
+ background-color: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ box-shadow: 0 2px 10px rgba(0,0,0,0.03);
+ padding: 0.75rem 0;
+}
+
+.navbar-brand {
+ font-family: var(--font-serif);
+ font-size: 1.5rem;
+ color: var(--brand-primary) !important;
+ font-weight: 700;
+}
+
+.nav-link {
+ font-weight: 500;
+ color: var(--brand-text) !important;
+ transition: color 0.2s;
+}
+
+.nav-link:hover, .nav-link.active {
+ color: var(--brand-primary) !important;
+}
+
+.btn {
+ border-radius: 50px;
+ font-weight: 600;
+ padding: 0.5rem 1.5rem;
+ transition: all 0.3s ease;
+}
+
+.btn-primary {
+ background-color: var(--brand-primary);
+ border-color: var(--brand-primary);
+}
+
+.btn-primary:hover {
+ background-color: #a6444d;
+ border-color: #a6444d;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 10px rgba(192, 87, 97, 0.2);
+}
+
+.btn-outline-primary {
+ color: var(--brand-primary);
+ border-color: var(--brand-primary);
+}
+
+.btn-outline-primary:hover {
+ background-color: var(--brand-primary);
+ color: var(--brand-white);
+}
+
+
+/* --- Swiping UI --- */
+#swipe-container {
+ perspective: 1000px;
+}
+
+.card-swipe {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ border-radius: var(--border-radius-lg);
+ background: var(--brand-white);
+ box-shadow: var(--shadow-card);
+ will-change: transform, opacity;
+ transition: all 0.3s ease-in-out;
+ display: flex;
+ flex-direction: column;
+}
+
+@keyframes swipe-left-anim {
+ from { transform: translateX(0) rotate(0); opacity: 1; }
+ to { transform: translateX(-150%) rotate(-20deg); opacity: 0; }
+}
+
+@keyframes swipe-right-anim {
+ from { transform: translateX(0) rotate(0); opacity: 1; }
+ to { transform: translateX(150%) rotate(20deg); opacity: 0; }
+}
+
+.swipe-left {
+ animation: swipe-left-anim 0.5s forwards ease-in;
+}
+
+.swipe-right {
+ animation: swipe-right-anim 0.5s forwards ease-in;
+}
+
+.companion-card .card-body {
+ flex-grow: 1;
+ padding-bottom: 0;
+}
+
+.companion-img-wrapper {
+ position: relative;
+ padding-top: 100%;
+ background-color: #f0f0f0;
+ border-top-left-radius: var(--border-radius-lg);
+ border-top-right-radius: var(--border-radius-lg);
+ overflow: hidden;
+}
+
+.companion-img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.verified-badge {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(5px);
+ color: var(--brand-primary);
+ padding: 5px 10px;
+ border-radius: 20px;
+ font-size: 0.75rem;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+}
+
+.companion-name {
+ font-size: 1.5rem;
+ margin-bottom: 0.25rem;
+}
+
+.companion-meta {
+ font-size: 0.9rem;
+ color: var(--brand-text-muted);
+ margin-bottom: 1rem;
+}
+
+.rate-badge {
+ font-weight: 700;
+ color: var(--brand-primary);
+ font-size: 1.1rem;
+}
+
+.card-footer-swipe {
+ border-top: none;
+ background: transparent;
+ padding: 1rem;
+}
+
+.btn-swipe {
+ width: 100%;
+ height: 60px;
+ border: 1px solid #eee;
+ border-radius: 0;
+ background: var(--brand-white);
+ color: #ccc;
+ font-size: 1.5rem;
+ transition: all 0.3s ease;
+}
+
+.btn-swipe:hover {
+ background-color: #f7f7f7;
+ transform: scale(1.1);
+}
+
+.btn-dislike {
+ border-bottom-left-radius: var(--border-radius-lg);
+}
+
+.btn-dislike:hover {
+ color: var(--brand-dislike);
+}
+
+.btn-like {
+ border-left: none;
+ border-bottom-right-radius: var(--border-radius-lg);
+}
+
+.btn-like:hover {
+ color: var(--brand-like);
+}
+
+/* Match Notification Modal */
+#matchNotificationModal .modal-content {
+ border-radius: var(--border-radius-lg);
+ background: linear-gradient(135deg, var(--brand-primary), #a6444d);
+ color: var(--brand-white);
+}
+
+#matchNotificationModal .modal-title {
+ color: var(--brand-white);
+ font-size: 2rem;
+}
+
+#matchNotificationModal .btn-primary {
+ background-color: var(--brand-white);
+ color: var(--brand-primary);
+ border-color: var(--brand-white);
+}
+
+/* Matches Modal */
+#matchesModal .modal-content {
+ background-color: var(--brand-bg);
+ border-radius: var(--border-radius-lg);
+}
+
+/* Footer */
+footer {
+ background-color: #fff;
+ border-top: 1px solid #eee;
+ padding: 2rem 0;
+ color: var(--brand-text-muted);
+ font-size: 0.9rem;
+}
\ No newline at end of file
diff --git a/assets/images/avatar_placeholder.svg b/assets/images/avatar_placeholder.svg
new file mode 100644
index 0000000..a3a56db
--- /dev/null
+++ b/assets/images/avatar_placeholder.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/js/main.js b/assets/js/main.js
index e69de29..c3f1236 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -0,0 +1,50 @@
+function handleSwipe(action, swiped_user_id) {
+ const card = document.querySelector('.card-swipe');
+ if (!card) return;
+
+ // 1. Add class to trigger animation
+ const animationClass = action === 'like' ? 'swipe-right' : 'swipe-left';
+ card.classList.add(animationClass);
+
+ // 2. Wait for animation to end, then fetch
+ card.addEventListener('animationend', () => {
+ fetch('swipe.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ },
+ body: JSON.stringify({
+ action: action,
+ swiped_user_id: swiped_user_id
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ // 3. Replace the card with the next one
+ const swipeContainer = document.getElementById('swipe-container');
+ swipeContainer.innerHTML = data.next_companion_html;
+
+ // 4. If it's a match, show the notification modal
+ if (data.match) {
+ const matchModal = new bootstrap.Modal(document.getElementById('matchNotificationModal'));
+ const matchBody = document.getElementById('match-notification-body');
+ // Note: This relies on a global `translations` object or a dedicated function.
+ // For simplicity, we hardcode a message here, but a robust solution would use the `t()` function.
+ matchBody.textContent = `You and ${data.matched_user_name} have liked each other.`;
+ matchModal.show();
+ }
+ } else {
+ // Handle errors, e.g., show a message to the user
+ console.error('Swipe failed:', data.error);
+ // Re-enable card if swipe failed
+ card.classList.remove(animationClass);
+ }
+ })
+ .catch(error => {
+ console.error('Error during swipe:', error);
+ card.classList.remove(animationClass);
+ });
+ }, { once: true });
+}
diff --git a/db/setup.php b/db/setup.php
new file mode 100644
index 0000000..6cdbbe8
--- /dev/null
+++ b/db/setup.php
@@ -0,0 +1,113 @@
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+ // 1. Users Table
+ $sql_users = "CREATE TABLE IF NOT EXISTS users (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ email VARCHAR(255) NOT NULL UNIQUE,
+ password_hash VARCHAR(255) NOT NULL,
+ role ENUM('client', 'companion') NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )";
+ $pdo->exec($sql_users);
+ echo "- Table 'users' is ready.\n";
+
+ // 2. User Profiles Table
+ $sql_profiles = "CREATE TABLE IF NOT EXISTS user_profiles (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ user_id INT NOT NULL UNIQUE,
+ name VARCHAR(100) NOT NULL,
+ age INT,
+ city VARCHAR(100),
+ bio TEXT,
+ profile_photo_path VARCHAR(255) DEFAULT 'assets/images/avatar_placeholder.svg',
+ hourly_rate DECIMAL(10, 2),
+ is_verified TINYINT(1) DEFAULT 0,
+ rating DECIMAL(3, 2) DEFAULT 5.00,
+ activities TEXT, -- Can store comma-separated values or JSON
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+ )";
+ $pdo->exec($sql_profiles);
+ echo "- Table 'user_profiles' is ready.\n";
+
+ // 3. Swipes Table
+ $sql_swipes = "CREATE TABLE IF NOT EXISTS swipes (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ swiper_user_id INT NOT NULL,
+ swiped_user_id INT NOT NULL,
+ swipe_type ENUM('like', 'dislike') NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (swiper_user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY (swiped_user_id) REFERENCES users(id) ON DELETE CASCADE,
+ UNIQUE KEY (swiper_user_id, swiped_user_id)
+ )";
+ $pdo->exec($sql_swipes);
+ echo "- Table 'swipes' is ready.\n";
+
+ // 4. Matches Table
+ $sql_matches = "CREATE TABLE IF NOT EXISTS matches (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ user1_id INT NOT NULL,
+ user2_id INT NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user1_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY (user2_id) REFERENCES users(id) ON DELETE CASCADE,
+ UNIQUE KEY (user1_id, user2_id)
+ )";
+ $pdo->exec($sql_matches);
+ echo "- Table 'matches' is ready.\n";
+
+
+ // -- SEEDING DATA (if tables are empty) --
+ $stmt = $pdo->query("SELECT COUNT(*) FROM users");
+ if ($stmt->fetchColumn() == 0) {
+ echo "Seeding initial data...\n";
+
+ // Hash for a default password, e.g., 'password123'
+ $default_password_hash = password_hash('password123', PASSWORD_DEFAULT);
+
+ // Companions
+ $companions_to_seed = [
+ [1, 'sofia@email.com', 'Sofia', 28, 'San José', 45.00, 'Art lover and coffee enthusiast. I know the best hidden cafes in Amón.', 1, 4.9, 'assets/images/avatar_placeholder.svg'],
+ [2, 'mateo@email.com', 'Mateo', 32, 'San José', 50.00, 'Local history buff. Let\'s walk through the city center.', 1, 4.8, 'assets/images/avatar_placeholder.svg'],
+ [3, 'elena@email.com', 'Elena', 25, 'Escazú', 60.00, 'Foodie and wine taster. Available for elegant dinners.', 1, 5.0, 'assets/images/avatar_placeholder.svg'],
+ [4, 'alejandro@email.com', 'Alejandro', 29, 'San Pedro', 40.00, 'Musician and movie buff. Great for concerts.', 0, 4.7, 'assets/images/avatar_placeholder.svg']
+ ];
+
+ $user_sql = "INSERT INTO users (id, email, password_hash, role) VALUES (?, ?, ?, 'companion')";
+ $profile_sql = "INSERT INTO user_profiles (user_id, name, age, city, hourly_rate, bio, is_verified, rating, profile_photo_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+ $user_stmt = $pdo->prepare($user_sql);
+ $profile_stmt = $pdo->prepare($profile_sql);
+
+ foreach ($companions_to_seed as $c) {
+ $user_stmt->execute([$c[0], $c[1], $default_password_hash]);
+ $profile_stmt->execute([$c[0], $c[2], $c[3], $c[4], $c[5], $c[6], $c[7], $c[8], $c[9]]);
+ }
+
+ // A client user for testing
+ $client_user_sql = "INSERT INTO users (email, password_hash, role) VALUES ('client@email.com', ?, 'client')";
+ $pdo->prepare($client_user_sql)->execute([$default_password_hash]);
+ $client_user_id = $pdo->lastInsertId();
+
+ $client_profile_sql = "INSERT INTO user_profiles (user_id, name, age, city, bio) VALUES (?, 'Test Client', 30, 'San José', 'Looking for great company.')";
+ $pdo->prepare($client_profile_sql)->execute([$client_user_id]);
+
+ echo "- Seeded companion and client users.\n";
+
+ } else {
+ echo "- Users table already has data. Skipping seed.\n";
+ }
+
+ echo "Database setup completed successfully!\n";
+
+} catch (PDOException $e) {
+ die("DB Setup Error: " . $e->getMessage() . "\n");
+}
\ No newline at end of file
diff --git a/includes/helpers.php b/includes/helpers.php
new file mode 100644
index 0000000..69e5ccc
--- /dev/null
+++ b/includes/helpers.php
@@ -0,0 +1,126 @@
+ [
+ 'page_title' => 'Compañía — Platonic Companion Booking',
+ 'nav_companions' => 'Companions',
+ 'nav_how_it_works' => 'How it Works',
+ 'nav_matches' => 'My Matches',
+ 'nav_logout' => 'Logout',
+ 'hero_title_1' => 'Find your perfect',
+ 'hero_title_2' => 'platonic',
+ 'hero_title_3' => 'companion.',
+ 'hero_lead' => 'Book vetted companions for dinner dates, movie nights, or city tours. Safe, secure, and strictly platonic.',
+ 'search_where' => 'Where?',
+ 'search_city_any' => 'Any City',
+ 'search_activity' => 'Activity / Keyword',
+ 'search_placeholder' => 'e.g. Dinner, Coffee, Casual Dates...',
+ 'search_button' => 'Search Companions',
+ 'featured_companions' => 'Discover Companions',
+ 'no_companions_found' => 'No new companions found. Check back later!',
+ 'clear_filters' => 'Clear Filters',
+ 'verified' => 'Verified',
+ 'rate_per_hour' => '/hr',
+ 'like_button' => 'Like',
+ 'dislike_button' => 'Dislike',
+ 'matches_title' => 'Your Matches',
+ 'matches_empty' => 'No matches yet. Keep swiping!',
+ 'match_modal_title' => 'It\'s a Match!',
+ 'match_modal_body' => 'You and {name} have liked each other.',
+ 'match_modal_button' => 'Keep Swiping',
+ 'footer_copyright' => 'Compañía. All rights reserved.',
+ 'footer_coc' => 'Strict Code of Conduct: Sexual services are strictly prohibited.',
+ 'footer_safety' => 'Read our Safety Guidelines',
+ ],
+ 'es' => [
+ 'page_title' => 'Compañía — Reserva de Compañeros Platónicos',
+ 'nav_companions' => 'Compañeros',
+ 'nav_how_it_works' => 'Cómo Funciona',
+ 'nav_matches' => 'Mis Matches',
+ 'nav_logout' => 'Cerrar Sesión',
+ 'hero_title_1' => 'Encuentra tu compañero',
+ 'hero_title_2' => 'platónico',
+ 'hero_title_3' => 'perfecto.',
+ 'hero_lead' => 'Reserva compañeros verificados para cenas, noches de cine o tours por la ciudad. Seguro, protegido y estrictamente platónico.',
+ 'search_where' => '¿Dónde?',
+ 'search_city_any' => 'Cualquier ciudad',
+ 'search_activity' => 'Actividad / Palabra Clave',
+ 'search_placeholder' => 'Ej: Cena, Café, Citas Casuales...',
+ 'search_button' => 'Buscar Compañeros',
+ 'featured_companions' => 'Descubre Compañeros',
+ 'no_companions_found' => 'No se encontraron nuevos compañeros. ¡Vuelve más tarde!',
+ 'clear_filters' => 'Limpiar Filtros',
+ 'verified' => 'Verificado',
+ 'rate_per_hour' => '/hora',
+ 'like_button' => 'Me gusta',
+ 'dislike_button' => 'No me gusta',
+ 'matches_title' => 'Tus Matches',
+ 'matches_empty' => 'Aún no tienes matches. ¡Sigue deslizando!',
+ 'match_modal_title' => '¡Es un Match!',
+ 'match_modal_body' => 'A ti y a {name} se gustaron.',
+ 'match_modal_button' => 'Seguir Deslizando',
+ 'footer_copyright' => 'Compañía. Todos los derechos reservados.',
+ 'footer_coc' => 'Código de Conducta Estricto: Los servicios sexuales están estrictamente prohibidos.',
+ 'footer_safety' => 'Lee nuestras Guías de Seguridad',
+ ],
+];
+
+function t($key, $replacements = []) {
+ global $lang, $translations;
+ // Fallback to session language if not passed explicitly
+ $current_lang = $lang ?? $_SESSION['lang'] ?? 'en';
+ $text = $translations[$current_lang][$key] ?? $key;
+ foreach ($replacements as $k => $v) {
+ $text = str_replace('{'.$k.'}', $v, $text);
+ }
+ return $text;
+}
+
+function render_companion_card($companion) {
+ if (!$companion) {
+ return '
' . t('no_companions_found') . '
';
+ }
+
+ $companion['img'] = $companion['profile_photo_path'] ?? 'assets/images/avatar_placeholder.svg';
+ $verified_badge = $companion['is_verified'] ? '' : '';
+
+ return '
+
+
+
!['.htmlspecialchars($companion['name']).']('.htmlspecialchars($companion['img']).')
+ '.$verified_badge.'
+
+
+
+
+
'.htmlspecialchars($companion['name']).', '.$companion['age'].'
+
+
+
+
'.number_format((float)($companion['hourly_rate'] ?? 0), 0).t('rate_per_hour').'
+
★ '.$companion['rating'].'
+
+
+
'.htmlspecialchars($companion['bio']).'
+
+
+
';
+}
+
diff --git a/index.php b/index.php
index 7205f3d..543c700 100644
--- a/index.php
+++ b/index.php
@@ -1,150 +1,188 @@
$current_user_id];
+
+if ($filterCity) {
+ $sql .= " AND p.city LIKE :city";
+ $params[':city'] = '%' . $filterCity . '%';
+}
+if ($filterActivity) {
+ $sql .= " AND p.bio LIKE :activity";
+ $params[':activity'] = '%' . $filterActivity . '%';
+}
+
+$sql .= " ORDER BY RAND() LIMIT 1";
+
+$stmt = $pdo->prepare($sql);
+$stmt->execute($params);
+$companion = $stmt->fetch(PDO::FETCH_ASSOC);
+
+// Fetch matches for the logged-in user
+$stmt = $pdo->prepare("
+ SELECT p.*
+ FROM matches m
+ JOIN user_profiles p ON (m.user1_id = p.user_id OR m.user2_id = p.user_id)
+ WHERE (m.user1_id = :current_user_id OR m.user2_id = :current_user_id)
+ AND p.user_id != :current_user_id
+");
+$stmt->execute(['current_user_id' => $current_user_id]);
+$matches = $stmt->fetchAll(PDO::FETCH_ASSOC);
+foreach ($matches as &$m) {
+ $m['img'] = $m['profile_photo_path'] ?? 'assets/images/avatar_placeholder.svg';
+}
+unset($m);
+
+$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Find your perfect vetted companion for platonic experiences.';
+$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+
?>
-
+
-
-
- New Style
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ = t('page_title') ?>
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
+
+
+
+
= t('featured_companions') ?>
+
+
+ = render_companion_card($companion) ?>
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
= t('match_modal_title') ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/login.php b/login.php
new file mode 100644
index 0000000..a6c06df
--- /dev/null
+++ b/login.php
@@ -0,0 +1,67 @@
+prepare('SELECT * FROM users WHERE email = ?');
+ $stmt->execute([$email]);
+ $user = $stmt->fetch();
+
+ if ($user && password_verify($password, $user['password_hash'])) {
+ $_SESSION['user_id'] = $user['id'];
+ $_SESSION['user_role'] = $user['role'];
+ header('Location: index.php');
+ exit;
+ } else {
+ $error = 'Invalid email or password.';
+ }
+ }
+}
+?>
+
+
+
+
+
+
Login
+
+
+
+
+
+
+
diff --git a/logout.php b/logout.php
new file mode 100644
index 0000000..95db42c
--- /dev/null
+++ b/logout.php
@@ -0,0 +1,6 @@
+prepare('SELECT id FROM users WHERE email = ?');
+ $stmt->execute([$email]);
+ if ($stmt->fetch()) {
+ $error = 'An account with this email already exists.';
+ } else {
+ $password_hash = password_hash($password, PASSWORD_DEFAULT);
+ $pdo->prepare('INSERT INTO users (email, password_hash, role) VALUES (?, ?, ?)')
+ ->execute([$email, $password_hash, $role]);
+
+ $user_id = $pdo->lastInsertId();
+
+ // Create a basic profile
+ $pdo->prepare('INSERT INTO user_profiles (user_id, name, bio) VALUES (?, ?, ?)')
+ ->execute([$user_id, 'New User', 'Please update your bio.']);
+
+ $success = 'Registration successful! You can now
log in.';
+ }
+ }
+}
+?>
+
+
+
+
+
+
Register
+
+
+
+
+
+
+
+
Register
+
+
+
+
+
+
+
+
+
Already have an account? Login here
+
+
+
+
+
+
diff --git a/swipe.php b/swipe.php
new file mode 100644
index 0000000..8e63e1d
--- /dev/null
+++ b/swipe.php
@@ -0,0 +1,109 @@
+ false, 'match' => false, 'error' => null];
+
+if (!isset($_SESSION['user_id'])) {
+ $response['error'] = 'User not authenticated.';
+ echo json_encode($response);
+ exit;
+}
+
+$current_user_id = $_SESSION['user_id'];
+$data = json_decode(file_get_contents('php://input'), true);
+
+$swiped_user_id = $data['swiped_user_id'] ?? null;
+$action = $data['action'] ?? null; // 'like' or 'dislike'
+
+if (!$swiped_user_id || !in_array($action, ['like', 'dislike'])) {
+ $response['error'] = 'Invalid input.';
+ echo json_encode($response);
+ exit;
+}
+
+try {
+ $pdo = db();
+
+ // 1. Record the swipe action
+ $stmt = $pdo->prepare(
+ "INSERT INTO swipes (swiper_user_id, swiped_user_id, swipe_type)
+ VALUES (:swiper_user_id, :swiped_user_id, :swipe_type)"
+ );
+ $stmt->execute([
+ ':swiper_user_id' => $current_user_id,
+ ':swiped_user_id' => $swiped_user_id,
+ ':swipe_type' => $action
+ ]);
+
+ // 2. If it was a 'like', check for a mutual match
+ if ($action === 'like') {
+ $stmt = $pdo->prepare(
+ "SELECT COUNT(*) FROM swipes
+ WHERE swiper_user_id = :swiped_user_id AND swiped_user_id = :current_user_id AND swipe_type = 'like'"
+ );
+ $stmt->execute([
+ ':swiped_user_id' => $swiped_user_id,
+ ':current_user_id' => $current_user_id
+ ]);
+ $is_mutual = (int)$stmt->fetchColumn() > 0;
+
+ if ($is_mutual) {
+ // 3. It's a match! Insert into matches table.
+ $user1 = min($current_user_id, $swiped_user_id);
+ $user2 = max($current_user_id, $swiped_user_id);
+
+ $stmt = $pdo->prepare(
+ "INSERT INTO matches (user1_id, user2_id)
+ SELECT :user1_id, :user2_id
+ WHERE NOT EXISTS (
+ SELECT 1 FROM matches
+ WHERE (user1_id = :user1_id AND user2_id = :user2_id) OR (user1_id = :user2_id AND user2_id = :user1_id)
+ )"
+ );
+ $stmt->execute([
+ ':user1_id' => $user1,
+ ':user2_id' => $user2
+ ]);
+
+ $response['match'] = true;
+
+ // Fetch the matched user's name for the notification
+ $stmt = $pdo->prepare("SELECT name FROM user_profiles WHERE user_id = :swiped_user_id");
+ $stmt->execute(['swiped_user_id' => $swiped_user_id]);
+ $matched_user = $stmt->fetch(PDO::FETCH_ASSOC);
+ $response['matched_user_name'] = $matched_user['name'] ?? 'Someone';
+ }
+ }
+
+ $response['success'] = true;
+
+} catch (PDOException $e) {
+ $response['error'] = "Database error: " . $e->getMessage();
+}
+
+// Finally, get the next companion to show
+$stmt = $pdo->prepare(
+ "SELECT p.*
+ FROM user_profiles p
+ JOIN users u ON p.user_id = u.id
+ WHERE u.role = 'companion'
+ AND p.user_id != :current_user_id
+ AND p.user_id NOT IN (
+ SELECT swiped_user_id FROM swipes WHERE swiper_user_id = :current_user_id
+ )
+ ORDER BY RAND()
+ LIMIT 1"
+);
+$stmt->execute(['current_user_id' => $current_user_id]);
+$next_companion = $stmt->fetch(PDO::FETCH_ASSOC);
+
+$response['next_companion_html'] = render_companion_card($next_companion);
+
+echo json_encode($response);
\ No newline at end of file