1+ import {
2+ ArrowTrendingUpIcon ,
3+ BoltIcon ,
4+ BuildingLibraryIcon ,
5+ ChatBubbleLeftRightIcon ,
6+ ChatBubbleOvalLeftIcon ,
7+ CheckCircleIcon ,
8+ ClipboardDocumentListIcon ,
9+ EnvelopeIcon ,
10+ HandRaisedIcon ,
11+ PuzzlePieceIcon ,
12+ QuestionMarkCircleIcon ,
13+ ScaleIcon ,
14+ StarIcon ,
15+ UserIcon ,
16+ UsersIcon ,
17+ } from '@heroicons/react/24/outline'
118import clsx from 'clsx'
219import { type Stats } from 'common/stats'
3- import { ReactNode , useEffect , useState } from 'react'
20+ import { ComponentType , ReactNode , SVGProps , useEffect , useState } from 'react'
421import { PageBase } from 'web/components/page-base'
522import { SEO } from 'web/components/SEO'
623import ChartMembers from 'web/components/widgets/charts'
@@ -10,16 +27,18 @@ import {getCount} from 'web/lib/supabase/users'
1027
1128// ─── Types ────────────────────────────────────────────────────────────────────
1229
30+ type IconType = ComponentType < SVGProps < SVGSVGElement > >
31+
1332interface StatCardProps {
1433 value : string | number | null | undefined
1534 label : string
16- icon : string
35+ icon : IconType
1736 accent ?: 'amber' | 'sage' | 'muted'
1837 large ?: boolean
1938}
2039
2140interface StatGroupProps {
22- icon : string
41+ icon : IconType
2342 title : string
2443 children : ReactNode
2544}
@@ -34,7 +53,7 @@ function formatNumber(n: number): string {
3453
3554// ─── Stat Card ────────────────────────────────────────────────────────────────
3655
37- function StatCard ( { value, label, icon, accent = 'amber' , large} : StatCardProps ) {
56+ function StatCard ( { value, label, icon : Icon , accent = 'amber' , large} : StatCardProps ) {
3857 if ( value === null || value === undefined || value === 0 ) return null
3958
4059 const formatted = typeof value === 'number' ? formatNumber ( value ) : value
@@ -56,8 +75,8 @@ function StatCard({value, label, icon, accent = 'amber', large}: StatCardProps)
5675 "
5776 >
5877 < div className = "flex items-start justify-between mb-3" >
59- < div className = "w-9 h-9 rounded-lg bg-canvas-200 border border-canvas-300 flex items-center justify-center text-base flex-shrink-0" >
60- { icon }
78+ < div className = "w-9 h-9 rounded-lg bg-primary-100 border border-primary-200 flex items-center justify-center flex-shrink-0" >
79+ < Icon className = "w-[18px] h-[18px] text-primary-600" strokeWidth = { 1.8 } />
6180 </ div >
6281 </ div >
6382
@@ -80,11 +99,11 @@ function StatCard({value, label, icon, accent = 'amber', large}: StatCardProps)
8099
81100// ─── Stat Group ───────────────────────────────────────────────────────────────
82101
83- function StatGroup ( { icon, title, children} : StatGroupProps ) {
102+ function StatGroup ( { icon : Icon , title, children} : StatGroupProps ) {
84103 return (
85104 < div className = "mb-10" >
86105 < div className = "flex items-center gap-3 mb-4" >
87- < span className = "text-base" > { icon } </ span >
106+ < Icon className = "w-4 h-4 text-primary-500" strokeWidth = { 1.8 } / >
88107 < span className = "text-[11px] font-bold tracking-[1.2px] uppercase text-ink-500" >
89108 { title }
90109 </ span >
@@ -107,8 +126,8 @@ function ChartCard() {
107126 "
108127 >
109128 < div className = "flex items-center gap-2 mb-1" >
110- < div className = "w-8 h-8 rounded-lg bg-canvas-200 border border-canvas-300 flex items-center justify-center text-sm " >
111- 📈
129+ < div className = "w-8 h-8 rounded-lg bg-primary-100 border border-primary-200 flex items-center justify-center" >
130+ < ArrowTrendingUpIcon className = "w-4 h-4 text-primary-600" strokeWidth = { 1.8 } />
112131 </ div >
113132 < div >
114133 < h2 className = "text-sm font-bold text-ink-900 leading-tight" >
@@ -136,23 +155,28 @@ function HighlightRow({
136155 messages : number | null | undefined
137156} ) {
138157 const t = useT ( )
139- const items = [
158+ const items : {
159+ value : number | null | undefined
160+ label : string
161+ icon : IconType
162+ accent : 'amber' | 'sage'
163+ } [ ] = [
140164 {
141165 value : members ,
142166 label : t ( 'stats.highlight.members' , 'Total Members' ) ,
143- icon : '👥' ,
167+ icon : UsersIcon ,
144168 accent : 'amber' as const ,
145169 } ,
146170 {
147171 value : active ,
148172 label : t ( 'stats.highlight.active' , 'Active (last month)' ) ,
149- icon : '⚡' ,
173+ icon : BoltIcon ,
150174 accent : 'sage' as const ,
151175 } ,
152176 {
153177 value : messages ,
154178 label : t ( 'stats.highlight.messages' , 'Messages sent' ) ,
155- icon : '✉️' ,
179+ icon : EnvelopeIcon ,
156180 accent : 'amber' as const ,
157181 } ,
158182 ] . filter ( ( i ) => ! ! i . value )
@@ -161,31 +185,34 @@ function HighlightRow({
161185
162186 return (
163187 < div className = "grid grid-cols-1 sm:grid-cols-3 gap-3 mb-10" >
164- { items . map ( ( item ) => (
165- < div
166- key = { item . label }
167- className = "
188+ { items . map ( ( item ) => {
189+ const Icon = item . icon
190+ return (
191+ < div
192+ key = { item . label }
193+ className = "
168194 relative overflow-hidden
169195 bg-canvas-950 rounded-2xl p-6
170196 border-[1.5px] border-canvas-900
171197 "
172- >
173- < div className = "w-9 h-9 rounded-lg bg-canvas-900 flex items-center justify-center text-base mb-4" >
174- { item . icon }
175- </ div >
176- < div
177- className = { clsx (
178- 'text-4xl font-black tracking-tight leading-none mb-2' ,
179- item . accent === 'sage' ? 'text-green-500' : 'text-primary-400' ,
180- ) }
181198 >
182- { typeof item . value === 'number' ? formatNumber ( item . value ) : item . value }
199+ < div className = "w-9 h-9 rounded-lg bg-canvas-900 flex items-center justify-center mb-4" >
200+ < Icon className = "w-[18px] h-[18px] text-primary-400" strokeWidth = { 1.8 } />
201+ </ div >
202+ < div
203+ className = { clsx (
204+ 'text-4xl font-black tracking-tight leading-none mb-2' ,
205+ item . accent === 'sage' ? 'text-green-500' : 'text-primary-400' ,
206+ ) }
207+ >
208+ { typeof item . value === 'number' ? formatNumber ( item . value ) : item . value }
209+ </ div >
210+ < p className = "text-xs font-semibold text-white/60 uppercase tracking-wide" >
211+ { item . label }
212+ </ p >
183213 </ div >
184- < p className = "text-xs font-semibold text-white/60 uppercase tracking-wide" >
185- { item . label }
186- </ p >
187- </ div >
188- ) ) }
214+ )
215+ } ) }
189216 </ div >
190217 )
191218}
@@ -294,77 +321,83 @@ export default function Stats() {
294321 < ChartCard />
295322
296323 { /* ── Community ── */ }
297- < StatGroup icon = "👥" title = { t ( 'stats.group.community' , 'Community' ) } >
324+ < StatGroup icon = { UsersIcon } title = { t ( 'stats.group.community' , 'Community' ) } >
298325 < StatCard
299326 value = { data . profiles }
300327 label = { t ( 'stats.members' , 'Members' ) }
301- icon = "👤"
328+ icon = { UserIcon }
302329 accent = "amber"
303330 />
304331 < StatCard
305332 value = { data . active_members }
306333 label = { t ( 'stats.active_members' , 'Active (last month)' ) }
307- icon = "⚡"
334+ icon = { BoltIcon }
308335 accent = "sage"
309336 />
310337 < StatCard
311338 value = { genderRatioLabel }
312339 label = { t ( 'stats.gender_ratio' , 'Male / Female' ) }
313- icon = "⚖️"
340+ icon = { ScaleIcon }
314341 accent = "muted"
315342 />
316343 < StatCard
317344 value = { data . profile_comments }
318345 label = { t ( 'stats.endorsements' , 'Endorsements' ) }
319- icon = "⭐"
346+ icon = { StarIcon }
320347 accent = "amber"
321348 />
322349 </ StatGroup >
323350
324351 { /* ── Conversations ── */ }
325- < StatGroup icon = "💬" title = { t ( 'stats.group.conversations' , 'Conversations' ) } >
352+ < StatGroup
353+ icon = { ChatBubbleLeftRightIcon }
354+ title = { t ( 'stats.group.conversations' , 'Conversations' ) }
355+ >
326356 < StatCard
327357 value = { data . private_user_message_channels }
328358 label = { t ( 'stats.discussions' , 'Discussions' ) }
329- icon = "🗨️"
359+ icon = { ChatBubbleOvalLeftIcon }
330360 accent = "amber"
331361 />
332362 < StatCard
333363 value = { statsData ?. messages }
334364 label = { t ( 'stats.messages' , 'Messages' ) }
335- icon = "✉️"
365+ icon = { EnvelopeIcon }
336366 accent = "sage"
337367 />
338368 </ StatGroup >
339369
340370 { /* ── Compatibility ── */ }
341- < StatGroup icon = "🎯" title = { t ( 'stats.group.compatibility' , 'Compatibility' ) } >
371+ < StatGroup
372+ icon = { PuzzlePieceIcon }
373+ title = { t ( 'stats.group.compatibility' , 'Compatibility' ) }
374+ >
342375 < StatCard
343376 value = { data . compatibility_prompts }
344377 label = { t ( 'stats.compatibility_prompts' , 'Prompts Created' ) }
345- icon = "❓"
378+ icon = { QuestionMarkCircleIcon }
346379 accent = "amber"
347380 />
348381 < StatCard
349382 value = { data . compatibility_answers }
350383 label = { t ( 'stats.prompts_answered' , 'Prompts Answered' ) }
351- icon = "✅"
384+ icon = { CheckCircleIcon }
352385 accent = "sage"
353386 />
354387 </ StatGroup >
355388
356389 { /* ── Democracy ── */ }
357- < StatGroup icon = "🗳️" title = { t ( 'stats.group.democracy' , 'Democracy' ) } >
390+ < StatGroup icon = { BuildingLibraryIcon } title = { t ( 'stats.group.democracy' , 'Democracy' ) } >
358391 < StatCard
359392 value = { data . votes }
360393 label = { t ( 'stats.proposals' , 'Proposals' ) }
361- icon = "📋"
394+ icon = { ClipboardDocumentListIcon }
362395 accent = "amber"
363396 />
364397 < StatCard
365398 value = { data . vote_results }
366399 label = { t ( 'stats.votes' , 'Votes Cast' ) }
367- icon = "🗳️"
400+ icon = { HandRaisedIcon }
368401 accent = "sage"
369402 />
370403 { /*<StatCard*/ }
0 commit comments