Ryanhub - file viewer
filename: views/public/profile.php
branch: main
back to repo
<?php
if (!isLoggedIn()) {
    header('Location: ' . url('signin'));
    exit;
}

$pageTitle = "Your profile – Seven O'Clock Dinner";

$errors = [];
$successMessage = null;
$userId = currentUserId();

// Build a shareable absolute URL to this member's profile card on the members page.
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$profilePath = url('members?user_id=' . $userId . '#member-' . $userId);
if (strpos($profilePath, 'http://') === 0 || strpos($profilePath, 'https://') === 0) {
    $profileShareUrl = $profilePath;
} else {
    $profileShareUrl = $scheme . '://' . $host . $profilePath;
}

// Fetch existing profile data
$stmt = $db->prepare('SELECT u.full_name, u.email, m.display_name, m.photo_url, m.short_bio, m.city, m.links_json FROM users u LEFT JOIN member_profiles m ON m.user_id = u.id WHERE u.id = ?');
$stmt->bind_param('i', $userId);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();

if (!$row) {
    http_response_code(404);
    $errors[] = 'Profile not found.';
} else {
    $displayName = $row['display_name'] ?: $row['full_name'];
    $photoUrl = $row['photo_url'] ?? null;
    $bio = $row['short_bio'] ?? '';
    $city = $row['city'] ?? '';
    $links = [];
    if (!empty($row['links_json'])) {
        $decoded = json_decode($row['links_json'], true);
        if (is_array($decoded)) {
            $links = $decoded;
        }
    }

    if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
        $displayName = trim($_POST['display_name'] ?? $displayName);
        $city = trim($_POST['city'] ?? '');
        $bio = trim($_POST['bio'] ?? '');
        $rawLinks = $_POST['links'] ?? [];
        if (!is_array($rawLinks)) {
            $rawLinks = [$rawLinks];
        }

        $newLinks = [];
        foreach ($rawLinks as $rawLink) {
            $url = trim($rawLink ?? '');
            if ($url === '') {
                continue;
            }
            if (!filter_var($url, FILTER_VALIDATE_URL)) {
                $errors[] = 'Please enter valid URLs for your links.';
                break;
            }
            $newLinks[] = $url;
        }

        // Validate display name uniqueness
        if ($displayName === '') {
            $errors[] = 'Please enter a display name.';
        } else {
            $checkStmt = $db->prepare('SELECT 1 FROM member_profiles WHERE display_name = ? AND user_id <> ? LIMIT 1');
            $checkStmt->bind_param('si', $displayName, $userId);
            $checkStmt->execute();
            $checkResult = $checkStmt->get_result();
            if ($checkResult && $checkResult->num_rows > 0) {
                $errors[] = 'That name is already taken. Please choose another.';
            }
        }

        // Handle photo upload (optional)
        if (!$errors && !empty($_FILES['photo']['name']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
            $file = $_FILES['photo'];
            if ($file['size'] > 20 * 1024 * 1024) {
                $errors[] = 'Profile photos are limited to 20MB.';
            } else {
                $finfo = finfo_open(FILEINFO_MIME_TYPE);
                $mime = finfo_file($finfo, $file['tmp_name']);
                finfo_close($finfo);
                if (in_array($mime, ['image/jpeg', 'image/png'], true)) {
                    $ext = $mime === 'image/png' ? 'png' : 'jpg';
                    $profilesDir = __DIR__ . '/../../uploads/profiles';
                    if (!is_dir($profilesDir)) {
                        mkdir($profilesDir, 0775, true);
                    }
                    $basename = bin2hex(random_bytes(12)) . '.' . $ext;
                    $target = $profilesDir . '/' . $basename;
                    if (move_uploaded_file($file['tmp_name'], $target)) {
                        $photoUrl = url('uploads/profiles/' . $basename);
                    } else {
                        $errors[] = 'We were unable to save your profile photo.';
                    }
                } else {
                    $errors[] = 'Please upload a JPEG or PNG file for your profile photo.';
                }
            }
        }

        if (!$errors) {
            $linksJson = $newLinks ? json_encode($newLinks, JSON_THROW_ON_ERROR) : null;

            // Ensure a member_profiles row exists
            if ($row['display_name'] === null && $row['photo_url'] === null && $row['short_bio'] === null && $row['city'] === null && $row['links_json'] === null) {
                $stmt = $db->prepare('INSERT INTO member_profiles (user_id, display_name, photo_url, short_bio, city, links_json) VALUES (?, ?, ?, ?, ?, ?)');
                $stmt->bind_param('isssss', $userId, $displayName, $photoUrl, $bio, $city, $linksJson);
            } else {
                $stmt = $db->prepare('UPDATE member_profiles SET display_name = ?, photo_url = ?, short_bio = ?, city = ?, links_json = ? WHERE user_id = ?');
                $stmt->bind_param('sssssi', $displayName, $photoUrl, $bio, $city, $linksJson, $userId);
            }
            $stmt->execute();

            $successMessage = 'Your profile has been updated.';
            $links = $newLinks;
        }
    }
}
?>

<section class="page-grid">
    <div class="card" data-animate-initial style="position: relative;">
        <a href="<?= url('logout') ?>" class="pill" style="position: absolute; top: 12px; right: 12px; font-size: 11px; padding: 5px 12px; white-space: nowrap;" onclick="return confirm('Are you sure you want to log out?');">
            Log out
        </a>
        <div class="muted" style="font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; margin-bottom: 10px;">
            Your profile
        </div>
        <h1 style="font-family: 'Georgia', 'Times New Roman', serif; font-weight: 400; font-size: 26px; margin: 0 0 12px;">
            Everyone is going to see this.
        </h1>
        <?php if ($errors): ?>
            <ul style="margin-top: 12px; padding-left: 18px; color: #9b2c2c; font-size: 13px;">
                <?php foreach ($errors as $err): ?>
                    <li><?= htmlspecialchars($err, ENT_QUOTES, 'UTF-8') ?></li>
                <?php endforeach; ?>
            </ul>
        <?php endif; ?>
        <?php if ($successMessage): ?>
            <p style="margin-top: 12px; font-size: 13px; color: #22543d;">
                <?= htmlspecialchars($successMessage, ENT_QUOTES, 'UTF-8') ?>
            </p>
        <?php endif; ?>
        <div style="margin-top: 14px; text-align: center;">
            <button
                type="button"
                class="pill"
                style="font-size: 11px; padding: 6px 12px;"
                onclick="(function(){try{var url='<?= htmlspecialchars($profileShareUrl, ENT_QUOTES, 'UTF-8') ?>';navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(url).then(function(){alert('Profile link copied to clipboard.');}).catch(function(){prompt('Copy this profile link:',url);}):prompt('Copy this profile link:',url);}catch(e){}})();"
            >
                Copy profile link
            </button>
        </div>
    </div>

    <?php if (!$row): ?>
        <div class="card" data-animate>
            <p class="muted" style="font-size: 13px;">
                We could not find your profile.
            </p>
        </div>
    <?php else: ?>
        <div class="card" data-animate>
            <form method="post" enctype="multipart/form-data" style="display: grid; gap: 10px; font-size: 13px;">
                <label>
                    Name<br>
                    <input name="display_name" type="text" value="<?= htmlspecialchars($displayName, ENT_QUOTES, 'UTF-8') ?>" style="width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); background: rgba(255,255,255,0.8);">
                </label>
                <label>
                    Home Town (optional)<br>
                    <input name="city" type="text" value="<?= htmlspecialchars($city, ENT_QUOTES, 'UTF-8') ?>" style="width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); background: rgba(255,255,255,0.8);">
                </label>
                <label>
                    Links (optional)<br>
                    <div id="link-inputs" style="display: grid; gap: 6px; margin-top: 4px;">
                        <?php if ($links): ?>
                            <?php foreach ($links as $url): ?>
                                <div class="link-row" style="display: flex; gap: 6px;">
                                    <input name="links[]" type="url" value="<?= htmlspecialchars($url, ENT_QUOTES, 'UTF-8') ?>" style="flex: 1; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); background: rgba(255,255,255,0.8);">
                                </div>
                            <?php endforeach; ?>
                        <?php else: ?>
                            <div class="link-row" style="display: flex; gap: 6px;">
                                <input name="links[]" type="url" placeholder="Website, portfolio, etc." style="flex: 1; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); background: rgba(255,255,255,0.8);">
                            </div>
                        <?php endif; ?>
                    </div>
                    <button type="button" id="add-link-btn" class="pill" style="margin-top: 6px; font-size: 11px; padding: 4px 10px;">
                        + Add another link
                    </button>
                </label>
                <label>
                    Profile photo (optional)<br>
                    <?php if ($photoUrl): ?>
                        <div style="margin-bottom: 6px;">
                            <button type="button" data-image-full="<?= htmlspecialchars($photoUrl, ENT_QUOTES, 'UTF-8') ?>" style="all: unset; cursor: zoom-in;">
                                <img src="<?= htmlspecialchars($photoUrl, ENT_QUOTES, 'UTF-8') ?>" alt="Current profile photo" style="width: 64px; height: 64px; border-radius: 999px; object-fit: cover;">
                            </button>
                        </div>
                    <?php endif; ?>
                    <input name="photo" type="file" accept="image/jpeg,image/png" style="width: 100%; padding: 6px 0;">
                </label>
                <label>
                    Bio (optional)<br>
                    <textarea name="bio" rows="3" style="width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); background: rgba(255,255,255,0.8);"><?= htmlspecialchars($bio, ENT_QUOTES, 'UTF-8') ?></textarea>
                </label>
                <button type="submit" class="pill pill-accent" style="margin-top: 6px; justify-content: center;">
                    Save profile
                </button>
            </form>
            <script>
                (function () {
                    var container = document.getElementById('link-inputs');
                    var addBtn = document.getElementById('add-link-btn');
                    if (!container || !addBtn) return;

                    function createLinkRow() {
                        var row = document.createElement('div');
                        row.className = 'link-row';
                        row.style.display = 'flex';
                        row.style.gap = '6px';

                        var input = document.createElement('input');
                        input.type = 'url';
                        input.name = 'links[]';
                        input.placeholder = 'Another link (optional)';
                        input.style.flex = '1';
                        input.style.padding = '8px 10px';
                        input.style.borderRadius = '8px';
                        input.style.border = '1px solid rgba(0,0,0,0.12)';
                        input.style.background = 'rgba(255,255,255,0.8)';

                        var removeBtn = document.createElement('button');
                        removeBtn.type = 'button';
                        removeBtn.textContent = '×';
                        removeBtn.title = 'Remove';
                        removeBtn.style.border = 'none';
                        removeBtn.style.background = 'transparent';
                        removeBtn.style.cursor = 'pointer';
                        removeBtn.style.fontSize = '16px';
                        removeBtn.style.lineHeight = '1';
                        removeBtn.style.padding = '0 4px';
                        removeBtn.style.color = 'rgba(0,0,0,0.45)';

                        removeBtn.addEventListener('mouseover', function () {
                            removeBtn.style.color = 'rgba(0,0,0,0.7)';
                        });
                        removeBtn.addEventListener('mouseout', function () {
                            removeBtn.style.color = 'rgba(0,0,0,0.45)';
                        });

                        removeBtn.addEventListener('click', function () {
                            container.removeChild(row);
                        });

                        row.appendChild(input);
                        row.appendChild(removeBtn);
                        return row;
                    }

                    addBtn.addEventListener('click', function () {
                        container.appendChild(createLinkRow());
                    });
                })();
            </script>
        </div>
    <?php endif; ?>
</section>