UserPanel.tsx152 lines · main
1import { X } from 'lucide-react'
2import { parseAsString, useQueryState } from 'nuqs'
3import { useState } from 'react'
4import {
5 Button,
6 cn,
7 Input,
8 ResizableHandle,
9 ResizablePanel,
10 Tabs_Shadcn_,
11 TabsContent_Shadcn_,
12 TabsList_Shadcn_,
13 TabsTrigger_Shadcn_,
14} from 'ui'
15import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
16import { SimpleCodeBlock } from 'ui-patterns/SimpleCodeBlock'
17
18import { UserLogs } from './UserLogs'
19import { UserOverview } from './UserOverview'
20import { PANEL_PADDING } from './Users.constants'
21import { useUserQuery } from '@/data/auth/user-query'
22import { User } from '@/data/auth/users-infinite-query'
23import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
24
25export const UserPanel = () => {
26 const { data: project } = useSelectedProjectQuery()
27
28 const [selectedId, setSelectedId] = useQueryState(
29 'show',
30 parseAsString.withOptions({ history: 'push', clearOnDefault: true })
31 )
32
33 const [view, setView] = useState<'overview' | 'raw' | 'logs'>('overview')
34 const [searchQuery, setSearchQuery] = useState('')
35
36 const { data: selectedUser, isPending } = useUserQuery({
37 projectRef: project?.ref,
38 connectionString: project?.connectionString,
39 userId: selectedId,
40 })
41
42 const filteredProperties = selectedUser
43 ? Object.entries(selectedUser)
44 .filter(
45 ([key, value]) =>
46 key.toLowerCase().includes(searchQuery.toLowerCase()) ||
47 (typeof value === 'string' && value.toLowerCase().includes(searchQuery.toLowerCase()))
48 )
49 .reduce((obj, [key, value]) => {
50 if (value !== undefined) {
51 obj[key as keyof User] = value as any
52 }
53 return obj
54 }, {} as Partial<User>)
55 : {}
56
57 return (
58 <>
59 <ResizableHandle withHandle />
60 <ResizablePanel defaultSize="35" maxSize="45" minSize="35" className="bg-studio border-t">
61 <Button
62 type="text"
63 className="absolute top-3 right-3 px-1"
64 icon={<X />}
65 onClick={() => setSelectedId(null)}
66 />
67 <Tabs_Shadcn_
68 value={view}
69 className="flex flex-col h-full"
70 onValueChange={(value) => setView(value as 'overview' | 'raw' | 'logs')}
71 >
72 {isPending ? (
73 <div>
74 <div className="min-h-[46px] border-b" />
75 <div className="p-5">
76 <GenericSkeletonLoader />
77 </div>
78 </div>
79 ) : !!selectedUser ? (
80 <>
81 <TabsList_Shadcn_ className="px-5 flex gap-x-4 min-h-[46px]">
82 <TabsTrigger_Shadcn_
83 value="overview"
84 className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent shadow-none!"
85 >
86 Overview
87 </TabsTrigger_Shadcn_>
88 <TabsTrigger_Shadcn_
89 value="logs"
90 className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent shadow-none!"
91 >
92 Logs
93 </TabsTrigger_Shadcn_>
94 <TabsTrigger_Shadcn_
95 value="raw"
96 className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent shadow-none!"
97 >
98 Raw JSON
99 </TabsTrigger_Shadcn_>
100 </TabsList_Shadcn_>
101
102 <TabsContent_Shadcn_
103 value="overview"
104 className={cn('mt-0 grow min-h-0 overflow-y-auto')}
105 >
106 {selectedUser && (
107 <UserOverview user={selectedUser} onDeleteSuccess={() => setSelectedId(null)} />
108 )}
109 </TabsContent_Shadcn_>
110 <TabsContent_Shadcn_ value="logs" className={cn('mt-0 grow min-h-0 overflow-y-auto')}>
111 {selectedUser && <UserLogs user={selectedUser} />}
112 </TabsContent_Shadcn_>
113 <TabsContent_Shadcn_
114 value="raw"
115 className={cn('mt-0 grow min-h-0 overflow-y-auto', PANEL_PADDING)}
116 >
117 <div className="flex items-center mb-2">
118 <Input
119 autoFocus
120 type="text"
121 placeholder="Filter..."
122 value={searchQuery}
123 onChange={(e) => setSearchQuery(e.target.value)}
124 className="mr-2"
125 />
126 <Button
127 type="text"
128 disabled={!searchQuery}
129 onClick={() => setSearchQuery('')}
130 className="text-xs"
131 >
132 Clear
133 </Button>
134 </div>
135 <SimpleCodeBlock className="javascript" parentClassName="[&>*>span]:text-xs">
136 {JSON.stringify(filteredProperties, null, 2)}
137 </SimpleCodeBlock>
138 </TabsContent_Shadcn_>
139 </>
140 ) : (
141 <div className="flex items-center justify-center w-full h-full flex-col gap-y-2">
142 <p className="text-foreground-light text-sm">
143 Unable to find user with the following ID in project
144 </p>
145 <p className="text-foreground-lighter text-xs">ID: {selectedId}</p>
146 </div>
147 )}
148 </Tabs_Shadcn_>
149 </ResizablePanel>
150 </>
151 )
152}