<?php
declare(strict_types=1);
require_once 'includes/config.php';
require_once 'includes/functions.php';
$cur_user = get_session_user();
$view_user = strtolower(trim($_GET['user'] ?? ($cur_user['username'] ?? '')));
if ($view_user === '') {
redirect($cur_user ? 'profile.php?user=' . urlencode($cur_user['username']) : 'index.php');
}
$profile = get_user_profile($view_user);
if (!$profile) {
flash('error', 'User not found.');
redirect('directory.php');
}
$is_own = $cur_user && $cur_user['username'] === $view_user;
$lists = get_lists_for_user($view_user);
// Filter lists visible to current user
$visible = array_filter($lists, function($l) use ($cur_user, $view_user) {
if ($l['public'] ?? false) return true;
return $cur_user && $cur_user['username'] === $view_user;
});
/* ── Handle profile updates ───────────────────────────────────── */
if ($is_own && $_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verify_csrf()) {
flash('error', 'Invalid request.');
redirect('profile.php?user=' . urlencode($view_user));
}
$action = $_POST['action'] ?? '';
if ($action === 'update_display_name') {
$dn = trim($_POST['display_name'] ?? '');
if ($dn === '') $dn = $view_user;
update_display_name($view_user, $dn);
// Update session
if ($cur_user) {
$cur_user['display_name'] = $dn;
login_user($cur_user);
}
flash('success', 'Display name updated.');
redirect('profile.php?user=' . urlencode($view_user));
}
if ($action === 'upload_avatar') {
$file = $_FILES['avatar'] ?? null;
if (!$file || $file['error'] === UPLOAD_ERR_NO_FILE) {
flash('error', 'No file selected.');
} else {
$result = save_user_avatar($view_user, $file);
if ($result === false) {
flash('error', 'Upload failed. Use JPG, PNG, GIF or WebP, max 2 MB.');
} else {
flash('success', 'Profile logo updated.');
}
}
redirect('profile.php?user=' . urlencode($view_user));
}
if ($action === 'remove_avatar') {
remove_user_avatar($view_user);
flash('success', 'Profile logo removed.');
redirect('profile.php?user=' . urlencode($view_user));
}
if ($action === 'change_password') {
$old = $_POST['old_password'] ?? '';
$new = $_POST['new_password'] ?? '';
$new2= $_POST['new_password2'] ?? '';
if ($new !== $new2) {
flash('error', 'New passwords do not match.');
} else {
$result = update_password($view_user, $old, $new);
if ($result === true) {
flash('success', 'Password changed.');
} else {
flash('error', $result);
}
}
redirect('profile.php?user=' . urlencode($view_user));
}
}
$page_title = ($is_own ? 'My Profile' : $profile['display_name'] . '\'s Profile');
$og_image_url = get_user_avatar_url($view_user) ?: get_og_logo_url();
require_once 'includes/header.php';
?>
<div class="page-wrap" style="max-width:860px; margin-top:12px;">
<div style="display:flex; gap:10px; flex-wrap:wrap; align-items:flex-start;">
<!-- ── Profile card ─────────────────────────────── -->
<div class="window" style="min-width:240px; flex:0 0 240px;">
<div class="window-title"><span>👤 Profile</span></div>
<div class="window-body">
<?php $p_avatar = get_user_avatar_url($view_user); ?>
<?php if ($p_avatar !== ''): ?>
<div style="margin-bottom:8px;">
<img src="<?= h($p_avatar) ?>" alt="Profile logo" class="avatar-lg">
</div>
<?php endif; ?>
<div style="font-size:18px; font-weight:bold; margin-bottom:4px;">
<?= h($profile['display_name']) ?>
</div>
<div class="text-muted">@<?= h($profile['username']) ?></div>
<div class="sep"></div>
<div class="text-muted">Member since <?= date('F Y', strtotime($profile['created'])) ?></div>
<div class="text-muted" style="margin-top:4px;">
<?= count(array_filter($lists, fn($l) => $l['public'])) ?> public list<?= count(array_filter($lists, fn($l) => $l['public'])) !== 1 ? 's' : '' ?>
<?php if ($is_own): ?>
· <?= count($lists) ?> total
<?php endif; ?>
</div>
</div>
</div>
<!-- ── Right column ─────────────────────────────── -->
<div style="flex:1; min-width:0; display:flex; flex-direction:column; gap:10px;">
<?php if ($is_own): ?>
<!-- Profile logo upload -->
<div class="window">
<div class="window-title"><span>📷 Profile Logo</span></div>
<div class="window-body">
<p style="font-size:12px; color:var(--chrome-darker); margin-bottom:8px;">
Your profile logo is used as the OG share image on your lists and profile page,
overriding the site logo. JPG, PNG, GIF or WebP, max 2 MB.
</p>
<?php if ($p_avatar !== ''): ?>
<div style="display:flex; align-items:flex-start; gap:10px; margin-bottom:10px;">
<img src="<?= h($p_avatar) ?>" alt="Profile logo" class="avatar-lg">
<form method="post" action="profile.php?user=<?= h($view_user) ?>">
<?= csrf_field() ?>
<input type="hidden" name="action" value="remove_avatar">
<button type="submit" class="btn btn-sm btn-danger">Remove</button>
</form>
</div>
<?php endif; ?>
<form method="post" action="profile.php?user=<?= h($view_user) ?>"
enctype="multipart/form-data">
<?= csrf_field() ?>
<input type="hidden" name="action" value="upload_avatar">
<div class="form-row-inline" style="margin:0;">
<div class="stretch">
<input type="file" name="avatar"
accept=".jpg,.jpeg,.png,.gif,.webp,image/*"
style="border:none; background:transparent; padding:2px 0; width:100%;">
</div>
<div>
<button type="submit" class="btn btn-primary btn-sm">Upload</button>
</div>
</div>
</form>
</div>
</div>
<!-- Edit profile -->
<div class="window">
<div class="window-title"><span>Edit Profile</span></div>
<div class="window-body">
<form method="post" action="profile.php?user=<?= h($view_user) ?>">
<?= csrf_field() ?>
<input type="hidden" name="action" value="update_display_name">
<div class="form-row-inline">
<div class="stretch">
<label for="dn_input">Display Name</label>
<input type="text" id="dn_input" name="display_name"
value="<?= h($profile['display_name']) ?>" maxlength="64">
</div>
<div style="padding-top:18px;">
<button type="submit" class="btn btn-primary btn-sm">Save</button>
</div>
</div>
</form>
</div>
</div>
<!-- Change password -->
<div class="window">
<div class="window-title"><span>Change Password</span></div>
<div class="window-body">
<form method="post" action="profile.php?user=<?= h($view_user) ?>">
<?= csrf_field() ?>
<input type="hidden" name="action" value="change_password">
<div class="form-row-inline">
<div>
<label for="old_pw">Current Password</label>
<input type="password" id="old_pw" name="old_password" maxlength="128" required>
</div>
<div>
<label for="new_pw">New Password</label>
<input type="password" id="new_pw" name="new_password"
minlength="<?= MIN_PASSWORD_LEN ?>" maxlength="128" required>
</div>
<div>
<label for="new_pw2">Confirm New</label>
<input type="password" id="new_pw2" name="new_password2" maxlength="128" required>
</div>
<div style="padding-top:18px;">
<button type="submit" class="btn btn-primary btn-sm">Change</button>
</div>
</div>
</form>
</div>
</div>
<?php endif; ?>
</div>
</div>
<!-- ── Visible lists ──────────────────────────────── -->
<div class="window mt-10" style="margin-bottom:16px;">
<div class="window-title">
<span><?= $is_own ? 'My Lists' : h($profile['display_name']) . '\'s Lists' ?></span>
<span style="font-size:11px;opacity:.8;"><?= count($visible) ?> visible</span>
</div>
<div class="window-body">
<?php if (empty($visible)): ?>
<div class="text-center" style="padding:16px; color:var(--chrome-dark);">No lists to show.</div>
<?php else: ?>
<div class="list-grid">
<?php foreach ($visible as $lst): ?>
<div class="list-card">
<div class="list-card-title">
<a href="<?= h(list_url($view_user, $lst['id'])) ?>"><?= h($lst['title']) ?></a>
<?php if ($lst['public']): ?>
<span class="badge badge-pub">PUBLIC</span>
<?php else: ?>
<span class="badge badge-priv">PRIVATE</span>
<?php endif; ?>
<?php if (!empty($lst['password_hash'])): ?>
<span class="badge" style="background:#774400;color:#fff;" title="Password protected">🔒</span>
<?php endif; ?>
</div>
<div class="list-card-body">
<?= $lst['description'] ? h($lst['description']) : '<em style="color:var(--chrome-dark)">No description</em>' ?>
</div>
<div class="list-card-footer">
<span class="task-counts">
<span title="Active" style="color:var(--normal);">■ <?= $lst['active_count'] ?></span>
<span title="Completed" style="color:var(--low);">✓ <?= $lst['completed_count'] ?></span>
</span>
<div class="d-flex gap-4">
<a href="<?= h(list_url($view_user, $lst['id'])) ?>" class="btn btn-sm">Open</a>
<a href="print.php?id=<?= h($lst['id']) ?>&owner=<?= h($view_user) ?>"
class="btn btn-sm" target="_blank">Print</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>