@@ -4,13 +4,15 @@ import { ArrowLeft, ExternalLinkIcon, Search, X } from "lucide-react"
44import Link from "next/link"
55import { useState } from "react"
66import { toast } from "sonner"
7+ import { Badge } from "@/components/ui/badge"
78import { Button } from "@/components/ui/button"
89import { Card , CardContent , CardFooter , CardHeader , CardTitle } from "@/components/ui/card"
910import { Input } from "@/components/ui/input"
1011import { Label } from "@/components/ui/label"
1112import { useTRPC } from "@/lib/trpc/client"
1213import type { ApiOutput } from "@/lib/trpc/types"
1314import { stripChatId } from "@/lib/utils/telegram"
15+ import { AddRole } from "./add-role"
1416import { DeleteGroupAdmin } from "./delete-group-admin"
1517import { NewGroupAdmin } from "./new-group-admin"
1618
@@ -48,6 +50,7 @@ export default function TgUsers() {
4850 < Link href = "/dashboard/telegram" className = "flex gap-1 items-center text-muted-foreground mb-2 hover:underline" >
4951 < ArrowLeft size = { 16 } /> Back
5052 </ Link >
53+
5154 < form onSubmit = { search } className = "pt-2 gap-y-4 flex flex-col justify-start items-start" >
5255 < div className = "flex gap-2 flex-col items-start justify-start" >
5356 < Label htmlFor = "email" className = "text-base" >
@@ -75,109 +78,131 @@ export default function TgUsers() {
7578 </ Button >
7679 ) }
7780 </ div >
78- < span className = "text-muted-foreground text-xs" > Max results: 20</ span >
7981 </ div >
8082 </ form >
8183
8284 < br />
8385
8486 { user && (
8587 < >
86- < Card >
87- < CardHeader >
88- < CardTitle > User ID: { user . id } </ CardTitle >
89- </ CardHeader >
90- < CardContent >
91- < p >
92- < span className = "text-muted-foreground" > Name: </ span >
93- { user . firstName } { user . lastName }
94- </ p >
95- < p >
96- < span className = "text-muted-foreground" > Username: </ span >
97- { user . username }
98- </ p >
99- < p >
100- < span className = "text-muted-foreground" > Roles: </ span >
101- { userData ?. roles ? userData . roles . join ( ", " ) : 0 }
102- </ p >
103- </ CardContent >
104- </ Card >
105-
88+ < UserInfoCard user = { user } roles = { userData ?. roles } />
10689 < div className = "pt-6 flex gap-4 items-center" >
10790 < p > Admin in groups:</ p >
10891 < NewGroupAdmin user = { user } alreadyIn = { userData ?. groupAdmin . map ( ( g ) => g ?. group . id ?? 0 ) ?? [ ] } />
10992 </ div >
11093 < div className = "grid grid-cols-3 py-2 gap-4" >
111- { userData ?. groupAdmin . map (
112- ( m , idx ) =>
113- m && (
114- < Card key = { m . group . id ?? `ga-${ idx } ` } >
115- < CardContent >
116- < p >
117- { " " }
118- < span className = "text-muted-foreground" > Chat: </ span >
119- { m . group && < span > { m . group . title } </ span > } [{ m . group . id } ]
120- </ p >
121- < p >
122- < span className = "text-muted-foreground" > Added By: </ span >
123- { m . addedBy . firstName } { m . addedBy . username ? `@${ m . addedBy . username } ` : "" }
124- </ p >
125- </ CardContent >
126- < CardFooter className = "justify-end gap-2" >
127- < DeleteGroupAdmin userId = { user . id } chatId = { m . group . id } />
128- </ CardFooter >
129- </ Card >
130- )
131- ) }
94+ { userData ?. groupAdmin
95+ . filter ( ( m ) => m !== null && m !== undefined )
96+ . map ( ( m ) => (
97+ < GroupAdminCard groupAdminInfo = { m } user = { user } key = { m . group . id } />
98+ ) ) }
13299 </ div >
133100
134101 < p className = "pt-6" > Last messages:</ p >
135102 < div className = "grid grid-cols-3 py-2 gap-4" >
136103 { messages ?. messages ?. map ( ( m ) => (
137- < Card key = { `${ m . messageId } -${ m . chatId } ` } >
138- < CardContent >
139- < p >
140- { " " }
141- < span className = "text-muted-foreground" > Chat: </ span >
142- { m . group && < span > { m . group . title } </ span > } [{ m . chatId } ]
143- </ p >
144- < p >
145- { " " }
146- < span className = "text-muted-foreground" > Message ID: </ span >
147- { m . messageId }
148- </ p >
149- < p >
150- { " " }
151- < span className = "text-muted-foreground" > Timestamp: </ span >
152- { m . timestamp . toLocaleString ( ) }
153- </ p >
154- < span className = "text-muted-foreground" > Content:</ span >
155- < p className = "pl-3" > { m . message } </ p >
156- </ CardContent >
157- < CardFooter className = "justify-end gap-2" >
158- { m . group ?. inviteLink && (
159- < a href = { m . group . inviteLink } rel = "noopener noreferral" target = "_blank" aria-label = "Join group" >
160- < Button variant = "outline" >
161- < ExternalLinkIcon size = { 20 } /> Join Chat
162- </ Button >
163- </ a >
164- ) }
165- < a
166- href = { `https://t.me/c/${ stripChatId ( m . chatId ) } /${ m . messageId } ` }
167- rel = "noopener noreferral"
168- target = "_blank"
169- aria-label = "Open message in chat"
170- >
171- < Button variant = "outline" >
172- < ExternalLinkIcon size = { 20 } /> Open
173- </ Button >
174- </ a >
175- </ CardFooter >
176- </ Card >
104+ < MessageCard message = { m } key = { `${ m . chatId } -${ m . messageId } ` } />
177105 ) ) }
178106 </ div >
179107 </ >
180108 ) }
181109 </ div >
182110 )
183111}
112+
113+ type UserRoles = ApiOutput [ "tg" ] [ "permissions" ] [ "getRoles" ] [ "roles" ]
114+ function UserInfoCard ( { user, roles } : { user : NonNullable < User > ; roles : UserRoles } ) {
115+ return (
116+ < Card >
117+ { " " }
118+ < CardHeader >
119+ < CardTitle > User ID: { user . id } </ CardTitle >
120+ </ CardHeader >
121+ < CardContent className = "space-y-2" >
122+ < p >
123+ < span className = "text-muted-foreground" > Name: </ span >
124+ { user . firstName } { user . lastName }
125+ </ p >
126+ < p >
127+ < span className = "text-muted-foreground" > Username: </ span >
128+ { user . username }
129+ </ p >
130+ < div className = "flex items-center gap-2" >
131+ < span className = "text-muted-foreground" > Roles: </ span >
132+ { roles ? roles . map ( ( r ) => < Badge key = { r } > { r } </ Badge > ) : "N/A" }
133+ </ div >
134+ </ CardContent >
135+ < CardFooter >
136+ < AddRole alreadyRoles = { roles ?? [ ] } user = { user } />
137+ </ CardFooter >
138+ </ Card >
139+ )
140+ }
141+
142+ type GroupAdminSingle = NonNullable < ApiOutput [ "tg" ] [ "permissions" ] [ "getRoles" ] [ "groupAdmin" ] [ number ] >
143+ function GroupAdminCard ( { user, groupAdminInfo : m } : { user : NonNullable < User > ; groupAdminInfo : GroupAdminSingle } ) {
144+ return (
145+ < Card key = { m . group . id } >
146+ < CardContent >
147+ < p >
148+ { " " }
149+ < span className = "text-muted-foreground" > Chat: </ span >
150+ { m . group && < span > { m . group . title } </ span > } [{ m . group . id } ]
151+ </ p >
152+ < p >
153+ < span className = "text-muted-foreground" > Added By: </ span >
154+ { m . addedBy . firstName } { m . addedBy . username ? `@${ m . addedBy . username } ` : "" }
155+ </ p >
156+ </ CardContent >
157+ < CardFooter className = "justify-end gap-2" >
158+ < DeleteGroupAdmin userId = { user . id } chatId = { m . group . id } />
159+ </ CardFooter >
160+ </ Card >
161+ )
162+ }
163+
164+ type Message = NonNullable < ApiOutput [ "tg" ] [ "messages" ] [ "getLastByUser" ] [ "messages" ] > [ number ]
165+ function MessageCard ( { message : m } : { message : Message } ) {
166+ return (
167+ < Card key = { `${ m . messageId } -${ m . chatId } ` } >
168+ < CardContent >
169+ < p >
170+ { " " }
171+ < span className = "text-muted-foreground" > Chat: </ span >
172+ { m . group && < span > { m . group . title } </ span > } [{ m . chatId } ]
173+ </ p >
174+ < p >
175+ { " " }
176+ < span className = "text-muted-foreground" > Message ID: </ span >
177+ { m . messageId }
178+ </ p >
179+ < p >
180+ { " " }
181+ < span className = "text-muted-foreground" > Timestamp: </ span >
182+ { m . timestamp . toLocaleString ( ) }
183+ </ p >
184+ < span className = "text-muted-foreground" > Content:</ span >
185+ < p className = "pl-3" > { m . message } </ p >
186+ </ CardContent >
187+ < CardFooter className = "justify-end gap-2" >
188+ { m . group ?. inviteLink && (
189+ < a href = { m . group . inviteLink } rel = "noopener noreferral" target = "_blank" aria-label = "Join group" >
190+ < Button variant = "outline" >
191+ < ExternalLinkIcon size = { 20 } /> Join Chat
192+ </ Button >
193+ </ a >
194+ ) }
195+ < a
196+ href = { `https://t.me/c/${ stripChatId ( m . chatId ) } /${ m . messageId } ` }
197+ rel = "noopener noreferral"
198+ target = "_blank"
199+ aria-label = "Open message in chat"
200+ >
201+ < Button variant = "outline" >
202+ < ExternalLinkIcon size = { 20 } /> Open
203+ </ Button >
204+ </ a >
205+ </ CardFooter >
206+ </ Card >
207+ )
208+ }
0 commit comments