Skip to content

Commit c6a112f

Browse files
refactor: move tg users cards into separated files
1 parent a578904 commit c6a112f

5 files changed

Lines changed: 185 additions & 155 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useQuery } from "@tanstack/react-query"
2+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
3+
import { useTRPC } from "@/lib/trpc/client"
4+
import type { ApiOutput } from "@/lib/trpc/types"
5+
import { fmtUser } from "@/lib/utils/telegram"
6+
7+
type Log = NonNullable<ApiOutput["tg"]["auditLog"]["getById"]>[number]
8+
export function AuditLogCard({ log: m }: { log: Log }) {
9+
const trpc = useTRPC()
10+
11+
const { data: admin } = useQuery(trpc.tg.users.get.queryOptions({ userId: m.adminId }))
12+
13+
return (
14+
<Card>
15+
<CardHeader>
16+
<CardTitle>{m.type}</CardTitle>
17+
</CardHeader>
18+
<CardContent className="grid grid-cols-[auto_1fr] gap-x-2">
19+
<span className="text-muted-foreground">Chat: </span>
20+
<p>
21+
{m.groupTitle && <span>{m.groupTitle}</span>} [{m.groupId}]
22+
</p>
23+
<span className="text-muted-foreground">Admin ID: </span>
24+
<p>{admin?.user && fmtUser(admin.user)}</p>
25+
26+
{m.createdAt && (
27+
<>
28+
<span className="text-muted-foreground">Created: </span>
29+
<p>{m.createdAt.toLocaleString()}</p>
30+
</>
31+
)}
32+
33+
{m.until && (
34+
<>
35+
<span className="text-muted-foreground">Until: </span>
36+
<p>{m.until.toLocaleString()}</p>
37+
</>
38+
)}
39+
<span className="text-muted-foreground">Reason:</span>
40+
{m.reason ? <p>{m.reason}</p> : <p className="text-muted-foreground">N/A</p>}
41+
</CardContent>
42+
</Card>
43+
)
44+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Code } from "@/components/code"
2+
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
3+
import type { ApiOutput } from "@/lib/trpc/types"
4+
import { stripChatId } from "@/lib/utils/telegram"
5+
import { DeleteGroupAdmin } from "./delete-group-admin"
6+
7+
type User = ApiOutput["tg"]["users"]["getByUsername"]["user"]
8+
type GroupAdminSingle = NonNullable<ApiOutput["tg"]["permissions"]["getRoles"]["groupAdmin"][number]>
9+
10+
export function GroupAdminCard({
11+
user,
12+
groupAdminInfo: m,
13+
}: {
14+
user: NonNullable<User>
15+
groupAdminInfo: GroupAdminSingle
16+
}) {
17+
return (
18+
<Card>
19+
<CardHeader>
20+
<CardTitle>{m.group.title}</CardTitle>
21+
</CardHeader>
22+
<CardContent className="space-y-2">
23+
<p>
24+
<span className="text-muted-foreground">Chat ID: </span>
25+
<Code copyOnClick>{m.group.id}</Code> / <Code copyOnClick>{stripChatId(m.group.id)}</Code>
26+
</p>
27+
<p>
28+
<span className="text-muted-foreground">Added By: </span>
29+
{m.addedBy.firstName} {m.addedBy.username ? `@${m.addedBy.username}` : ""}
30+
</p>
31+
</CardContent>
32+
<CardFooter className="justify-end gap-2">
33+
<DeleteGroupAdmin userId={user.id} chatId={m.group.id} />
34+
</CardFooter>
35+
</Card>
36+
)
37+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { ExternalLinkIcon } from "lucide-react"
2+
import { Button } from "@/components/ui/button"
3+
import { Card, CardContent, CardFooter } from "@/components/ui/card"
4+
import type { ApiOutput } from "@/lib/trpc/types"
5+
import { stripChatId } from "@/lib/utils/telegram"
6+
7+
type Message = NonNullable<ApiOutput["tg"]["messages"]["getLastByUser"]["messages"]>[number]
8+
export function MessageCard({ message: m }: { message: Message }) {
9+
return (
10+
<Card>
11+
<CardContent className="space-y-1">
12+
<p>
13+
<span className="text-muted-foreground">Chat: </span>
14+
{m.group && <span>{m.group.title}</span>} [{m.chatId}]
15+
</p>
16+
<p>
17+
<span className="text-muted-foreground">Message ID: </span>
18+
{m.messageId}
19+
</p>
20+
<p>
21+
<span className="text-muted-foreground">Timestamp: </span>
22+
{m.timestamp.toLocaleString()}
23+
</p>
24+
<span className="text-muted-foreground">Content:</span>
25+
<p className="pl-3" title={m.message}>
26+
{m.message.slice(0, 40)} {m.message.length >= 40 && "[...]"}
27+
</p>
28+
</CardContent>
29+
<CardFooter className="justify-end gap-2">
30+
{m.group?.inviteLink && (
31+
<a href={m.group.inviteLink} rel="noopener noreferral" target="_blank" aria-label="Join group">
32+
<Button variant="outline">
33+
<ExternalLinkIcon size={20} /> Join Chat
34+
</Button>
35+
</a>
36+
)}
37+
<a
38+
href={`https://t.me/c/${stripChatId(m.chatId)}/${m.messageId}`}
39+
rel="noopener noreferral"
40+
target="_blank"
41+
aria-label="Open message in chat"
42+
>
43+
<Button variant="outline">
44+
<ExternalLinkIcon size={20} /> Open
45+
</Button>
46+
</a>
47+
</CardFooter>
48+
</Card>
49+
)
50+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Star } from "lucide-react"
2+
import { Badge } from "@/components/ui/badge"
3+
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
4+
import { useSession } from "@/lib/auth"
5+
import type { ApiOutput } from "@/lib/trpc/types"
6+
import { AddRole } from "./add-role"
7+
import { RemoveRole } from "./remove-role"
8+
9+
type User = ApiOutput["tg"]["users"]["getByUsername"]["user"]
10+
type UserRoles = ApiOutput["tg"]["permissions"]["getRoles"]["roles"]
11+
12+
export function UserInfoCard({ user, roles }: { user: NonNullable<User>; roles: UserRoles }) {
13+
const sesh = useSession()
14+
const seshUserId = sesh.data?.user.telegramId
15+
const isSelf = seshUserId && seshUserId === user.id
16+
17+
return (
18+
<Card className="w-fit min-w-120">
19+
<CardHeader>
20+
<CardTitle>
21+
User ID: {user.id}{" "}
22+
{isSelf && (
23+
<Badge>
24+
<Star /> You
25+
</Badge>
26+
)}
27+
</CardTitle>
28+
</CardHeader>
29+
<CardContent className="space-y-2">
30+
<p>
31+
<span className="text-muted-foreground">Name: </span>
32+
{user.firstName} {user.lastName ?? ""}
33+
</p>
34+
<p>
35+
<span className="text-muted-foreground">Username: </span>
36+
{user.username}
37+
</p>
38+
<div className="flex items-center gap-2">
39+
<span className="text-muted-foreground">Roles: </span>
40+
{roles ? roles.map((r) => <Badge key={r}>{r}</Badge>) : "N/A"}
41+
</div>
42+
</CardContent>
43+
<CardFooter className="gap-2">
44+
<AddRole alreadyRoles={roles ?? []} user={user} />
45+
<RemoveRole alreadyRoles={roles ?? []} user={user} />
46+
</CardFooter>
47+
</Card>
48+
)
49+
}
Lines changed: 5 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
"use client"
22
import { useQuery, useQueryClient } from "@tanstack/react-query"
3-
import { ArrowLeft, ExternalLinkIcon, Search, Star, X } from "lucide-react"
3+
import { ArrowLeft, Search, X } from "lucide-react"
44
import Link from "next/link"
55
import { useState } from "react"
66
import { toast } from "sonner"
7-
import { Code } from "@/components/code"
8-
import { Badge } from "@/components/ui/badge"
97
import { Button } from "@/components/ui/button"
10-
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
118
import { Input } from "@/components/ui/input"
129
import { Label } from "@/components/ui/label"
13-
import { useSession } from "@/lib/auth"
1410
import { useTRPC } from "@/lib/trpc/client"
1511
import type { ApiOutput } from "@/lib/trpc/types"
16-
import { fmtUser, stripChatId } from "@/lib/utils/telegram"
17-
import { AddRole } from "./add-role"
18-
import { DeleteGroupAdmin } from "./delete-group-admin"
12+
import { AuditLogCard } from "./card-audit-log"
13+
import { GroupAdminCard } from "./card-group-admin"
14+
import { MessageCard } from "./card-message"
15+
import { UserInfoCard } from "./card-user-info"
1916
import { NewGroupAdmin } from "./new-group-admin"
20-
import { RemoveRole } from "./remove-role"
2117

2218
type User = ApiOutput["tg"]["users"]["getByUsername"]["user"]
2319

@@ -137,149 +133,3 @@ export default function TgUsers() {
137133
</div>
138134
)
139135
}
140-
141-
type UserRoles = ApiOutput["tg"]["permissions"]["getRoles"]["roles"]
142-
function UserInfoCard({ user, roles }: { user: NonNullable<User>; roles: UserRoles }) {
143-
const sesh = useSession()
144-
const seshUserId = sesh.data?.user.telegramId
145-
const isSelf = seshUserId && seshUserId === user.id
146-
147-
return (
148-
<Card className="w-fit min-w-120">
149-
<CardHeader>
150-
<CardTitle>
151-
User ID: {user.id}{" "}
152-
{isSelf && (
153-
<Badge>
154-
<Star /> You
155-
</Badge>
156-
)}
157-
</CardTitle>
158-
</CardHeader>
159-
<CardContent className="space-y-2">
160-
<p>
161-
<span className="text-muted-foreground">Name: </span>
162-
{user.firstName} {user.lastName ?? ""}
163-
</p>
164-
<p>
165-
<span className="text-muted-foreground">Username: </span>
166-
{user.username}
167-
</p>
168-
<div className="flex items-center gap-2">
169-
<span className="text-muted-foreground">Roles: </span>
170-
{roles ? roles.map((r) => <Badge key={r}>{r}</Badge>) : "N/A"}
171-
</div>
172-
</CardContent>
173-
<CardFooter className="gap-2">
174-
<AddRole alreadyRoles={roles ?? []} user={user} />
175-
<RemoveRole alreadyRoles={roles ?? []} user={user} />
176-
</CardFooter>
177-
</Card>
178-
)
179-
}
180-
181-
type GroupAdminSingle = NonNullable<ApiOutput["tg"]["permissions"]["getRoles"]["groupAdmin"][number]>
182-
function GroupAdminCard({ user, groupAdminInfo: m }: { user: NonNullable<User>; groupAdminInfo: GroupAdminSingle }) {
183-
return (
184-
<Card>
185-
<CardHeader>
186-
<CardTitle>{m.group.title}</CardTitle>
187-
</CardHeader>
188-
<CardContent className="space-y-2">
189-
<p>
190-
<span className="text-muted-foreground">Chat ID: </span>
191-
<Code copyOnClick>{m.group.id}</Code> / <Code copyOnClick>{stripChatId(m.group.id)}</Code>
192-
</p>
193-
<p>
194-
<span className="text-muted-foreground">Added By: </span>
195-
{m.addedBy.firstName} {m.addedBy.username ? `@${m.addedBy.username}` : ""}
196-
</p>
197-
</CardContent>
198-
<CardFooter className="justify-end gap-2">
199-
<DeleteGroupAdmin userId={user.id} chatId={m.group.id} />
200-
</CardFooter>
201-
</Card>
202-
)
203-
}
204-
205-
type Message = NonNullable<ApiOutput["tg"]["messages"]["getLastByUser"]["messages"]>[number]
206-
function MessageCard({ message: m }: { message: Message }) {
207-
return (
208-
<Card>
209-
<CardContent className="space-y-1">
210-
<p>
211-
<span className="text-muted-foreground">Chat: </span>
212-
{m.group && <span>{m.group.title}</span>} [{m.chatId}]
213-
</p>
214-
<p>
215-
<span className="text-muted-foreground">Message ID: </span>
216-
{m.messageId}
217-
</p>
218-
<p>
219-
<span className="text-muted-foreground">Timestamp: </span>
220-
{m.timestamp.toLocaleString()}
221-
</p>
222-
<span className="text-muted-foreground">Content:</span>
223-
<p className="pl-3">{m.message}</p>
224-
</CardContent>
225-
<CardFooter className="justify-end gap-2">
226-
{m.group?.inviteLink && (
227-
<a href={m.group.inviteLink} rel="noopener noreferral" target="_blank" aria-label="Join group">
228-
<Button variant="outline">
229-
<ExternalLinkIcon size={20} /> Join Chat
230-
</Button>
231-
</a>
232-
)}
233-
<a
234-
href={`https://t.me/c/${stripChatId(m.chatId)}/${m.messageId}`}
235-
rel="noopener noreferral"
236-
target="_blank"
237-
aria-label="Open message in chat"
238-
>
239-
<Button variant="outline">
240-
<ExternalLinkIcon size={20} /> Open
241-
</Button>
242-
</a>
243-
</CardFooter>
244-
</Card>
245-
)
246-
}
247-
248-
type Log = NonNullable<ApiOutput["tg"]["auditLog"]["getById"]>[number]
249-
function AuditLogCard({ log: m }: { log: Log }) {
250-
const trpc = useTRPC()
251-
252-
const { data: admin } = useQuery(trpc.tg.users.get.queryOptions({ userId: m.adminId }))
253-
254-
return (
255-
<Card>
256-
<CardHeader>
257-
<CardTitle>{m.type}</CardTitle>
258-
</CardHeader>
259-
<CardContent className="grid grid-cols-[auto_1fr] gap-x-2">
260-
<span className="text-muted-foreground">Chat: </span>
261-
<p>
262-
{m.groupTitle && <span>{m.groupTitle}</span>} [{m.groupId}]
263-
</p>
264-
<span className="text-muted-foreground">Admin ID: </span>
265-
<p>{admin?.user && fmtUser(admin.user)}</p>
266-
267-
{m.createdAt && (
268-
<>
269-
<span className="text-muted-foreground">Created: </span>
270-
<p>{m.createdAt.toLocaleString()}</p>
271-
</>
272-
)}
273-
274-
{m.until && (
275-
<>
276-
<span className="text-muted-foreground">Until: </span>
277-
<p>{m.until.toLocaleString()}</p>
278-
</>
279-
)}
280-
<span className="text-muted-foreground">Reason:</span>
281-
{m.reason ? <p>{m.reason}</p> : <p className="text-muted-foreground">N/A</p>}
282-
</CardContent>
283-
</Card>
284-
)
285-
}

0 commit comments

Comments
 (0)