UserDropdown.tsx176 lines · main
1import { useFlag } from 'common'
2import { FlaskConical, Loader2, ScrollText, Settings } from 'lucide-react'
3import { useTheme } from 'next-themes'
4import Link from 'next/link'
5import { useRouter } from 'next/router'
6import {
7 cn,
8 DropdownMenu,
9 DropdownMenuContent,
10 DropdownMenuGroup,
11 DropdownMenuItem,
12 DropdownMenuLabel,
13 DropdownMenuRadioGroup,
14 DropdownMenuRadioItem,
15 DropdownMenuSeparator,
16 DropdownMenuTrigger,
17 singleThemes,
18 Theme,
19} from 'ui'
20
21import { ButtonTooltip } from '../ui/ButtonTooltip'
22import { useFeaturePreviewModal } from './App/FeaturePreview/FeaturePreviewContext'
23import { TimezoneDropdown } from './UserDropdown/TimezoneDropdown'
24import { ProfileImage } from '@/components/ui/ProfileImage'
25import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
26import { IS_PLATFORM } from '@/lib/constants'
27import { useProfileNameAndPicture } from '@/lib/profile'
28import { useTrack } from '@/lib/telemetry/track'
29import { useAppStateSnapshot } from '@/state/app-state'
30
31export function UserDropdown({
32 triggerClassName,
33 contentClassName,
34}: {
35 triggerClassName?: string
36 contentClassName?: string
37}) {
38 const router = useRouter()
39 const { theme, setTheme } = useTheme()
40 const appStateSnapshot = useAppStateSnapshot()
41 const profileShowEmailEnabled = useIsFeatureEnabled('profile:show_email')
42 const timezonePickerEnabled = useFlag('timezonePicker')
43 const { username, avatarUrl, primaryEmail, isLoading } = useProfileNameAndPicture()
44
45 const { toggleFeaturePreviewModal } = useFeaturePreviewModal()
46 const track = useTrack()
47
48 return (
49 <DropdownMenu
50 onOpenChange={(open) => {
51 if (open) track('header_user_dropdown_opened')
52 }}
53 >
54 <DropdownMenuTrigger asChild className={cn('border shrink-0 px-3', triggerClassName)}>
55 <ButtonTooltip
56 type="default"
57 className="[&>span]:flex px-0 py-0 rounded-full overflow-hidden h-8 w-8"
58 tooltip={{ content: { text: 'Account settings' } }}
59 >
60 {isLoading ? (
61 <div className="w-full h-full flex items-center justify-center">
62 <Loader2 className="animate-spin text-foreground-lighter" size={16} />
63 </div>
64 ) : (
65 <ProfileImage alt={username} src={avatarUrl} className="w-8 h-8 rounded-md" />
66 )}
67 </ButtonTooltip>
68 </DropdownMenuTrigger>
69
70 <DropdownMenuContent side="bottom" align="end" className={contentClassName}>
71 {IS_PLATFORM && (
72 <>
73 <div className="px-2 py-1 flex flex-col gap-0 text-sm">
74 {!!username ? (
75 <>
76 <span title={username} className="w-full text-left text-foreground truncate">
77 {username}
78 </span>
79 {primaryEmail !== username && profileShowEmailEnabled && (
80 <span
81 title={primaryEmail}
82 className="w-full text-left text-foreground-light text-xs truncate"
83 >
84 {primaryEmail}
85 </span>
86 )}
87 </>
88 ) : (
89 <span title={primaryEmail} className="w-full text-left text-foreground truncate">
90 {primaryEmail}
91 </span>
92 )}
93 </div>
94 <DropdownMenuSeparator />
95 <DropdownMenuGroup>
96 <DropdownMenuItem className="flex gap-2 cursor-pointer" asChild>
97 <Link
98 href="/account/me"
99 onClick={() => {
100 if (router.pathname !== '/account/me') {
101 appStateSnapshot.setLastRouteBeforeVisitingAccountPage(router.asPath)
102 }
103 }}
104 >
105 <Settings size={14} strokeWidth={1.5} className="text-foreground-lighter" />
106 Account preferences
107 </Link>
108 </DropdownMenuItem>
109 <DropdownMenuItem
110 className="flex gap-2 cursor-pointer"
111 onClick={() => toggleFeaturePreviewModal(true)}
112 // onSelect={() => toggleFeaturePreviewModal(true)}
113 >
114 <FlaskConical size={14} strokeWidth={1.5} className="text-foreground-lighter" />
115 Feature previews
116 </DropdownMenuItem>
117 <DropdownMenuItem className="flex gap-2 cursor-pointer" asChild>
118 <Link
119 href="https://supabase.com/changelog"
120 target="_blank"
121 rel="noopener noreferrer"
122 >
123 <ScrollText size={14} strokeWidth={1.5} className="text-foreground-lighter" />
124 Changelog
125 </Link>
126 </DropdownMenuItem>
127 <DropdownMenuSeparator />
128 </DropdownMenuGroup>
129 </>
130 )}
131 <DropdownMenuGroup>
132 <DropdownMenuLabel>Theme</DropdownMenuLabel>
133 <DropdownMenuRadioGroup
134 value={theme}
135 onValueChange={(value) => {
136 setTheme(value)
137 }}
138 >
139 {singleThemes.map((theme: Theme) => (
140 <DropdownMenuRadioItem
141 key={theme.value}
142 value={theme.value}
143 className="cursor-pointer"
144 >
145 {theme.name}
146 </DropdownMenuRadioItem>
147 ))}
148 </DropdownMenuRadioGroup>
149 </DropdownMenuGroup>
150 {timezonePickerEnabled && (
151 <>
152 <DropdownMenuSeparator />
153 <DropdownMenuGroup>
154 <TimezoneDropdown />
155 </DropdownMenuGroup>
156 </>
157 )}
158 {IS_PLATFORM && (
159 <>
160 <DropdownMenuSeparator />
161 <DropdownMenuGroup>
162 <DropdownMenuItem
163 className="cursor-pointer"
164 onSelect={() => {
165 router.push('/logout')
166 }}
167 >
168 Log out
169 </DropdownMenuItem>
170 </DropdownMenuGroup>
171 </>
172 )}
173 </DropdownMenuContent>
174 </DropdownMenu>
175 )
176}