1 <?php 2 require_once __DIR__ . '/../includes/bootstrap.php'; 3 $uname = get('u'); 4 $profile = DB::row('SELECT * FROM users WHERE username=?', [$uname]); 5 if (!$profile) render_404(); 6 7 $topics = DB::rows(" 8 SELECT t.*,c.name AS cat_name,c.slug AS cat_slug 9 FROM topics t JOIN categories c ON c.id=t.category_id 10 WHERE t.user_id=? ORDER BY t.created_at DESC LIMIT 15 11 ", [$profile['id']]); 12 13 $posts = DB::rows(" 14 SELECT p.*,t.title AS topic_title,t.slug AS topic_slug 15 FROM posts p JOIN topics t ON t.id=p.topic_id 16 WHERE p.user_id=? AND p.deleted=0 ORDER BY p.created_at DESC LIMIT 15 17 ", [$profile['id']]); 18 19 $showFriends = !$profile['friends_hidden'] 20 || ($USER && (int)$USER['id'] === (int)$profile['id']) 21 || ($USER && is_admin()); 22 23 $friendsList = $showFriends ? friends_list((int)$profile['id']) : []; 24 $fStatus = $USER ? friend_status((int)$USER['id'], (int)$profile['id']) : 'none'; 25 $kTier = karma_tier((int)$profile['karma']); 26 27 $PAGE_TITLE = '@' . $profile['username']; 28 include __DIR__ . '/../views/partials/layout.php'; 29 ?> 30 31 <div class="profile-hdr"> 32 <!-- Avatar --> 33 <div class="profile-av"> 34 <?php if ($profile['avatar']): ?> 35 <img src="<?= e($profile['avatar']) ?>" class="av-xl" alt=""> 36 <?php else: ?> 37 <span class="av-xl av-init"><?= strtoupper($profile['username'][0]) ?></span> 38 <?php endif; ?> 39 <!-- Karma tier badge under avatar โ green leaf --> 40 <div class="profile-tier-badge profile-tier-badge-green"> 41 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" width="11" height="11"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z"/><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"/></svg> 42 <?= e($kTier['name']) ?> 43 </div> 44 </div> 45 46 <!-- Info --> 47 <div class="profile-info"> 48 <div class="profile-name-row"> 49 <h1>@<?= e($profile['username']) ?></h1> 50 <span class="role-tag role-<?= e($profile['role']) ?>"><?= e($profile['role']) ?></span> 51 <?php if ($profile['suspended']): ?> 52 <span class="role-tag role-admin">Suspended</span> 53 <?php endif; ?> 54 </div> 55 <?php if ($profile['bio']): ?> 56 <p class="profile-bio"><?= e($profile['bio']) ?></p> 57 <?php endif; ?> 58 <div class="profile-meta"> 59 <span>๐ Joined <span class="ago" data-ts="<?= e($profile['joined_at']) ?>"></span></span> 60 <span>๐ Last seen <span class="ago" data-ts="<?= e($profile['last_seen']) ?>"></span></span> 61 <?php if (!empty($profile['location'])): ?> 62 <span> 63 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="13" height="13" style="vertical-align:middle"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> 64 <?= e($profile['location']) ?> 65 </span> 66 <?php endif; ?> 67 </div> 68 69 <!-- Karma bar โ always green --> 70 <div class="profile-karma-row"> 71 <div class="pkr-head"> 72 <span class="pkr-icon"> 73 <svg viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" width="16" height="16"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z"/><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"/></svg> 74 </span> 75 <span class="pkr-label pkr-label-green"><?= e($kTier['name']) ?></span> 76 <span class="pkr-pts"><?= number_format((int)$profile['karma']) ?> karma</span> 77 </div> 78 <div class="pkr-bar"> 79 <div class="pkr-fill pkr-fill-green" style="width:<?= $kTier['progress'] ?>%"></div> 80 </div> 81 <?php if ($kTier['next'] !== null): ?> 82 <div class="pkr-next"> 83 <?= $kTier['progress'] ?>% ยท <?= number_format($kTier['next'] - (int)$profile['karma']) ?> more to <?= e(karma_tier($kTier['next'])['name']) ?> 84 </div> 85 <?php else: ?> 86 <div class="pkr-next">๐ Maximum tier reached!</div> 87 <?php endif; ?> 88 </div> 89 </div> 90 91 <!-- Stats --> 92 <div class="profile-stats"> 93 <div class="pstat"> 94 <strong><?= number_format($profile['post_count']) ?></strong> 95 <span>Posts</span> 96 </div> 97 <div class="pstat"> 98 <strong><?= number_format($profile['topic_count']) ?></strong> 99 <span>Topics</span> 100 </div> 101 <div class="pstat karma-stat" style="--kc:<?= e($kTier['color']) ?>"> 102 <strong><?= number_format((int)$profile['karma']) ?></strong> 103 <span><?= $kTier['icon'] ?> Karma</span> 104 </div> 105 <div class="pstat"> 106 <strong><?= count($friendsList) ?></strong> 107 <span>Friends</span> 108 </div> 109 </div> 110 111 <!-- Actions --> 112 <div class="profile-actions"> 113 <?php if ($USER && $USER['username'] === $profile['username']): ?> 114 <a href="<?= u('users/edit.php') ?>" class="btn-ghost btn-sm">โ๏ธ Edit Profile</a> 115 <a href="<?= u('messages/') ?>" class="btn-ghost btn-sm">๐ฌ Messages</a> 116 <?php elseif ($USER): ?> 117 <?php if ($fStatus === 'none'): ?> 118 <button class="btn-primary btn-sm" onclick="friendAction('send',<?= $profile['id'] ?>,this)">+ Add Friend</button> 119 <?php elseif ($fStatus === 'pending_sent'): ?> 120 <button class="btn-ghost btn-sm" onclick="friendAction('cancel',<?= $profile['id'] ?>,this)">โ Sent (Cancel)</button> 121 <?php elseif ($fStatus === 'pending_received'): ?> 122 <button class="btn-primary btn-sm" onclick="friendAction('accept',<?= $profile['id'] ?>,this)">โ Accept</button> 123 <button class="btn-ghost btn-sm" onclick="friendAction('decline',<?= $profile['id'] ?>,this)">โ Decline</button> 124 <?php elseif ($fStatus === 'friends'): ?> 125 <button class="btn-ghost btn-sm" onclick="friendAction('remove',<?= $profile['id'] ?>,this)">๐ฅ Friends</button> 126 <?php endif; ?> 127 <a href="<?= u('messages/compose.php?to='.urlencode($profile['username'])) ?>" class="btn-ghost btn-sm">โ๏ธ Message</a> 128 <?php endif; ?> 129 130 <?php if ($USER && is_admin() && (int)$USER['id'] !== (int)$profile['id']): ?> 131 <a href="<?= u('admin/user.php?id='.$profile['id']) ?>" class="btn-ghost btn-sm">๐ก๏ธ Admin</a> 132 <?php endif; ?> 133 </div> 134 </div> 135 136 <!-- Pending friend requests (profile owner only) --> 137 <?php if ($USER && (int)$USER['id'] === (int)$profile['id']): ?> 138 <?php $pending = pending_requests((int)$USER['id']); ?> 139 <?php if ($pending): ?> 140 <div class="pending-requests"> 141 <h3>๐ฅ Friend Requests (<?= count($pending) ?>)</h3> 142 <?php foreach ($pending as $req): ?> 143 <div class="pending-row"> 144 <?php if ($req['avatar']): ?> 145 <img src="<?= e($req['avatar']) ?>" class="av-sm" alt=""> 146 <?php else: ?> 147 <span class="av-sm av-init"><?= strtoupper($req['username'][0]) ?></span> 148 <?php endif; ?> 149 <a href="<?= u('users/profile.php?u='.urlencode($req['username'])) ?>">@<?= e($req['username']) ?></a> 150 <button class="btn-primary btn-sm" onclick="friendAction('accept',<?= $req['id'] ?>,this)">Accept</button> 151 <button class="btn-ghost btn-sm" onclick="friendAction('decline',<?= $req['id'] ?>,this)">Decline</button> 152 </div> 153 <?php endforeach; ?> 154 </div> 155 <?php endif; ?> 156 <?php endif; ?> 157 158 <!-- Tabs --> 159 <div class="profile-tabs"> 160 <button class="tab-btn active" onclick="showTab('topics',this)"> 161 Topics <span class="tab-count"><?= count($topics) ?></span> 162 </button> 163 <button class="tab-btn" onclick="showTab('replies',this)"> 164 Replies <span class="tab-count"><?= count($posts) ?></span> 165 </button> 166 <?php if ($showFriends): ?> 167 <button class="tab-btn" onclick="showTab('friends',this)"> 168 Friends <span class="tab-count"><?= count($friendsList) ?></span> 169 </button> 170 <?php endif; ?> 171 </div> 172 173 <!-- Topics tab --> 174 <div id="tab-topics" class="tab-pane"> 175 <?php foreach ($topics as $t): ?> 176 <div class="topic-row"> 177 <div class="tr-body"> 178 <a href="<?= u('forum/topic.php?slug='.urlencode($t['slug'])) ?>" class="tr-title"><?= e($t['title']) ?></a> 179 <div class="tr-meta"> 180 <a href="<?= u('forum/category.php?slug='.urlencode($t['cat_slug'])) ?>" 181 class="cat-tag" style="--cc:#3b82f6"><?= e($t['cat_name']) ?></a> 182 <span class="ago" data-ts="<?= e($t['created_at']) ?>"></span> 183 </div> 184 </div> 185 <div class="tr-counts"><span>๐ฌ <?= $t['reply_count'] ?></span></div> 186 </div> 187 <?php endforeach; ?> 188 <?php if (!$topics): ?><p class="empty-msg">No topics yet.</p><?php endif; ?> 189 </div> 190 191 <!-- Replies tab --> 192 <div id="tab-replies" class="tab-pane hidden"> 193 <?php foreach ($posts as $p): ?> 194 <div class="reply-card"> 195 <div class="rc-top"> 196 <a href="<?= u('forum/topic.php?slug='.urlencode($p['topic_slug']).'#post-'.$p['id']) ?>"> 197 โฉ <?= e($p['topic_title']) ?> 198 </a> 199 <span class="ago" data-ts="<?= e($p['created_at']) ?>"></span> 200 </div> 201 <div class="rc-body md" data-raw="<?= e(base64_encode($p['content'])) ?>"><?= e($p['content']) ?></div> 202 </div> 203 <?php endforeach; ?> 204 <?php if (!$posts): ?><p class="empty-msg">No replies yet.</p><?php endif; ?> 205 </div> 206 207 <!-- Friends tab --> 208 <?php if ($showFriends): ?> 209 <div id="tab-friends" class="tab-pane hidden"> 210 <?php if ($friendsList): ?> 211 <div class="friends-grid"> 212 <?php foreach ($friendsList as $f): ?> 213 <?php $ft = karma_tier((int)$f['karma']); ?> 214 <a href="<?= u('users/profile.php?u='.urlencode($f['username'])) ?>" class="friend-card"> 215 <?php if ($f['avatar']): ?> 216 <img src="<?= e($f['avatar']) ?>" class="av-md" alt=""> 217 <?php else: ?> 218 <span class="av-md av-init"><?= strtoupper($f['username'][0]) ?></span> 219 <?php endif; ?> 220 <div class="friend-name">@<?= e($f['username']) ?></div> 221 <div class="friend-karma" style="color:<?= e($ft['color']) ?>"><?= $ft['icon'] ?> <?= number_format((int)$f['karma']) ?></div> 222 <div class="friend-role"><span class="role-tag role-<?= e($f['role']) ?>"><?= e($f['role']) ?></span></div> 223 </a> 224 <?php endforeach; ?> 225 </div> 226 <?php else: ?> 227 <p class="empty-msg">No friends yet.</p> 228 <?php endif; ?> 229 </div> 230 <?php endif; ?> 231 232 <script> 233 function showTab(name,btn){ 234 document.querySelectorAll('.tab-pane').forEach(function(p){p.classList.add('hidden');}); 235 document.querySelectorAll('.tab-btn').forEach(function(b){b.classList.remove('active');}); 236 document.getElementById('tab-'+name).classList.remove('hidden'); 237 btn.classList.add('active'); 238 } 239 function friendAction(action,targetId,btn){ 240 var fd=new FormData(); 241 fd.append('action',action);fd.append('target_id',targetId);fd.append('csrf',NX.csrf); 242 fetch(NX.base+'/api/friend.php',{method:'POST',body:fd}) 243 .then(function(r){return r.json();}) 244 .then(function(d){ 245 if(d.ok){toast(d.msg,'ok');setTimeout(function(){location.reload();},800);} 246 else toast(d.error,'err'); 247 }); 248 } 249 </script> 250 <?php include __DIR__ . '/../views/partials/layout_end.php'; ?>