1<?php
2require_once __DIR__ . '/../includes/bootstrap.php';
3$uname = get('u');
4$profile = DB::row('SELECT * FROM users WHERE username=?', [$uname]);
5if (!$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'];
28include __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>
233function 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}
239function 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'; ?>