enzostvs HF Staff commited on
Commit
4699c39
·
1 Parent(s): 08d43b0

new header

Browse files
assets/pro.svg ADDED
components/editor/commits.tsx CHANGED
@@ -12,6 +12,8 @@ import {
12
  import { Commit } from "@/lib/type";
13
  import { cn, humanizeNumber } from "@/lib/utils";
14
 
 
 
15
  export const Commits = function ({ commits }: { commits?: Commit[] }) {
16
  const searchParams = useSearchParams();
17
  const commitParam = searchParams.get("commit");
@@ -65,14 +67,8 @@ export const Commits = function ({ commits }: { commits?: Commit[] }) {
65
  <Popover open={open} onOpenChange={setOpen}>
66
  <form>
67
  <PopoverTrigger asChild>
68
- <Button variant={open ? "default" : "bordered"} size="xs">
69
  <HistoryIcon className="size-3.5" />
70
- <span>
71
- History <span className="max-lg:hidden">version</span>
72
- </span>
73
- <span className="py-0.5 px-1 text-[10px] flex items-center justify-center rounded font-semibold bg-accent text-primary font-mono">
74
- {humanizeNumber(commits ? commits.length : 0)}
75
- </span>
76
  </Button>
77
  </PopoverTrigger>
78
  <PopoverContent
 
12
  import { Commit } from "@/lib/type";
13
  import { cn, humanizeNumber } from "@/lib/utils";
14
 
15
+ // todo: when there is a commit query, highlight the selected commit in the list.
16
+
17
  export const Commits = function ({ commits }: { commits?: Commit[] }) {
18
  const searchParams = useSearchParams();
19
  const commitParam = searchParams.get("commit");
 
67
  <Popover open={open} onOpenChange={setOpen}>
68
  <form>
69
  <PopoverTrigger asChild>
70
+ <Button variant={open ? "default" : "ghost"} size="icon-xs">
71
  <HistoryIcon className="size-3.5" />
 
 
 
 
 
 
72
  </Button>
73
  </PopoverTrigger>
74
  <PopoverContent
components/editor/header.tsx CHANGED
@@ -22,6 +22,7 @@ import { UserMenu } from "@/components/user-menu";
22
  import { useProject } from "@/components/projects/useProject";
23
  import { cn } from "@/lib/utils";
24
  import { Commits } from "./commits";
 
25
 
26
  export function AppEditorHeader({
27
  currentActivity,
@@ -65,25 +66,7 @@ export function AppEditorHeader({
65
  mobileTab !== "left-sidebar" ? "max-lg:hidden" : "max-lg:w-full!"
66
  )}
67
  >
68
- <Button variant="ghost" className="pl-2.5! pr-3! py-1.5! h-auto!">
69
- <Image
70
- src="/logo.svg"
71
- alt="DeepSite"
72
- width={100}
73
- height={100}
74
- className="size-8"
75
- />
76
- <div className="flex flex-col -space-y-1 items-start">
77
- <p className="text-sm font-bold text-primary">
78
- {project?.cardData?.title ?? "New DeepSite website"}{" "}
79
- {project?.cardData?.emoji}
80
- </p>
81
- <p className="text-xs text-muted-foreground">
82
- Live preview of your app
83
- </p>
84
- </div>
85
- {/* <ChevronDown /> */}
86
- </Button>
87
  <div className="flex items-center justify-end gap-2 max-lg:hidden">
88
  {(project?.commits?.length ?? 0) > 0 && (
89
  <Commits commits={project?.commits} />
 
22
  import { useProject } from "@/components/projects/useProject";
23
  import { cn } from "@/lib/utils";
24
  import { Commits } from "./commits";
25
+ import { ProjectSettings } from "./project-settings";
26
 
27
  export function AppEditorHeader({
28
  currentActivity,
 
66
  mobileTab !== "left-sidebar" ? "max-lg:hidden" : "max-lg:w-full!"
67
  )}
68
  >
69
+ {project && <ProjectSettings project={project} />}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  <div className="flex items-center justify-end gap-2 max-lg:hidden">
71
  {(project?.commits?.length ?? 0) > 0 && (
72
  <Commits commits={project?.commits} />
components/editor/project-settings.tsx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+ import Link from "next/link";
3
+ import { useTheme } from "next-themes";
4
+ import { Sun, Moon, Settings, Contrast, Check } from "lucide-react";
5
+ import { useSession } from "next-auth/react";
6
+ import { ChevronDown, ChevronLeft, Edit } from "lucide-react";
7
+
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuLabel,
13
+ DropdownMenuPortal,
14
+ DropdownMenuSeparator,
15
+ DropdownMenuSub,
16
+ DropdownMenuSubContent,
17
+ DropdownMenuSubTrigger,
18
+ DropdownMenuTrigger,
19
+ } from "@/components/ui/dropdown-menu";
20
+ import { Button } from "@/components/ui/button";
21
+ import { ProjectWithCommits } from "@/actions/projects";
22
+ import { cn } from "@/lib/utils";
23
+ import ProIcon from "@/assets/pro.svg";
24
+ import { useParams } from "next/navigation";
25
+
26
+ export const ProjectSettings = ({
27
+ project,
28
+ }: {
29
+ project: ProjectWithCommits;
30
+ }) => {
31
+ const { repoId, owner } = useParams();
32
+ const { theme, setTheme } = useTheme();
33
+ const { data: session } = useSession();
34
+ return (
35
+ // <Popover open={open} onOpenChange={setOpen}>
36
+ // <PopoverTrigger asChild>
37
+ <DropdownMenu>
38
+ <DropdownMenuTrigger asChild>
39
+ <Button variant="ghost" className="pl-2.5! pr-3! py-1.5! h-auto!">
40
+ <Image
41
+ src="/logo.svg"
42
+ alt="DeepSite"
43
+ width={100}
44
+ height={100}
45
+ className="size-8"
46
+ />
47
+ <div className="flex flex-col -space-y-1 items-start">
48
+ <p className="text-sm font-bold text-primary">
49
+ {project?.cardData?.title ?? "New DeepSite website"}{" "}
50
+ {project?.cardData?.emoji}
51
+ </p>
52
+ <p className="text-xs text-muted-foreground">
53
+ Live preview of your app
54
+ </p>
55
+ </div>
56
+ <ChevronDown />
57
+ </Button>
58
+ </DropdownMenuTrigger>
59
+ <DropdownMenuContent className="w-64" align="start">
60
+ <DropdownMenuItem>
61
+ <Link href="/" className="flex items-center gap-1.5">
62
+ <ChevronLeft className="size-3.5" />
63
+ Go to Projects
64
+ </Link>
65
+ </DropdownMenuItem>
66
+ <DropdownMenuSeparator />
67
+ {!session?.user?.isPro && (
68
+ <>
69
+ <DropdownMenuItem>
70
+ <Link
71
+ href="https://huggingface.co/pro"
72
+ className="flex items-center gap-1.5 bg-linear-to-r from-pink-500 via-green-500 to-amber-500 text-transparent bg-clip-text font-semibold"
73
+ target="_blank"
74
+ >
75
+ <Image alt="Pro" src={ProIcon} className="size-3.5" />
76
+ Subscribe to Pro
77
+ </Link>
78
+ </DropdownMenuItem>
79
+ <DropdownMenuSeparator />
80
+ </>
81
+ )}
82
+ <DropdownMenuItem>
83
+ <Edit className="size-3.5" />
84
+ Rename the project
85
+ </DropdownMenuItem>
86
+ <DropdownMenuItem>
87
+ <Link
88
+ href={`https://huggingface.co/${owner}/${repoId}/settings`}
89
+ className="flex items-center gap-1.5"
90
+ >
91
+ <Settings className="size-3.5" />
92
+ Project settings
93
+ </Link>
94
+ </DropdownMenuItem>
95
+ <DropdownMenuSub>
96
+ <DropdownMenuSubTrigger className="flex items-center justify-start gap-1.5">
97
+ <Contrast className="size-3.5" />
98
+ Appearance
99
+ </DropdownMenuSubTrigger>
100
+ <DropdownMenuPortal>
101
+ <DropdownMenuSubContent>
102
+ <DropdownMenuItem onClick={() => setTheme("light")}>
103
+ Light
104
+ {theme === "light" && <Check />}
105
+ </DropdownMenuItem>
106
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
107
+ Dark
108
+ {theme === "dark" && <Check />}
109
+ </DropdownMenuItem>
110
+ <DropdownMenuItem onClick={() => setTheme("system")}>
111
+ System
112
+ {theme === "system" && <Check />}
113
+ </DropdownMenuItem>
114
+ </DropdownMenuSubContent>
115
+ </DropdownMenuPortal>
116
+ </DropdownMenuSub>
117
+ </DropdownMenuContent>
118
+ </DropdownMenu>
119
+ );
120
+ };
components/ui/dropdown-menu.tsx CHANGED
@@ -1,15 +1,15 @@
1
- "use client"
2
 
3
- import * as React from "react"
4
- import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
- import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
6
 
7
- import { cn } from "@/lib/utils"
8
 
9
  function DropdownMenu({
10
  ...props
11
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
- return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
13
  }
14
 
15
  function DropdownMenuPortal({
@@ -17,7 +17,7 @@ function DropdownMenuPortal({
17
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
  return (
19
  <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
- )
21
  }
22
 
23
  function DropdownMenuTrigger({
@@ -28,7 +28,7 @@ function DropdownMenuTrigger({
28
  data-slot="dropdown-menu-trigger"
29
  {...props}
30
  />
31
- )
32
  }
33
 
34
  function DropdownMenuContent({
@@ -48,7 +48,7 @@ function DropdownMenuContent({
48
  {...props}
49
  />
50
  </DropdownMenuPrimitive.Portal>
51
- )
52
  }
53
 
54
  function DropdownMenuGroup({
@@ -56,7 +56,7 @@ function DropdownMenuGroup({
56
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
  return (
58
  <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
- )
60
  }
61
 
62
  function DropdownMenuItem({
@@ -65,8 +65,8 @@ function DropdownMenuItem({
65
  variant = "default",
66
  ...props
67
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
- inset?: boolean
69
- variant?: "default" | "destructive"
70
  }) {
71
  return (
72
  <DropdownMenuPrimitive.Item
@@ -74,12 +74,12 @@ function DropdownMenuItem({
74
  data-inset={inset}
75
  data-variant={variant}
76
  className={cn(
77
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78
  className
79
  )}
80
  {...props}
81
  />
82
- )
83
  }
84
 
85
  function DropdownMenuCheckboxItem({
@@ -105,7 +105,7 @@ function DropdownMenuCheckboxItem({
105
  </span>
106
  {children}
107
  </DropdownMenuPrimitive.CheckboxItem>
108
- )
109
  }
110
 
111
  function DropdownMenuRadioGroup({
@@ -116,7 +116,7 @@ function DropdownMenuRadioGroup({
116
  data-slot="dropdown-menu-radio-group"
117
  {...props}
118
  />
119
- )
120
  }
121
 
122
  function DropdownMenuRadioItem({
@@ -140,7 +140,7 @@ function DropdownMenuRadioItem({
140
  </span>
141
  {children}
142
  </DropdownMenuPrimitive.RadioItem>
143
- )
144
  }
145
 
146
  function DropdownMenuLabel({
@@ -148,7 +148,7 @@ function DropdownMenuLabel({
148
  inset,
149
  ...props
150
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151
- inset?: boolean
152
  }) {
153
  return (
154
  <DropdownMenuPrimitive.Label
@@ -160,7 +160,7 @@ function DropdownMenuLabel({
160
  )}
161
  {...props}
162
  />
163
- )
164
  }
165
 
166
  function DropdownMenuSeparator({
@@ -173,7 +173,7 @@ function DropdownMenuSeparator({
173
  className={cn("bg-border -mx-1 my-1 h-px", className)}
174
  {...props}
175
  />
176
- )
177
  }
178
 
179
  function DropdownMenuShortcut({
@@ -189,13 +189,13 @@ function DropdownMenuShortcut({
189
  )}
190
  {...props}
191
  />
192
- )
193
  }
194
 
195
  function DropdownMenuSub({
196
  ...props
197
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198
- return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
199
  }
200
 
201
  function DropdownMenuSubTrigger({
@@ -204,7 +204,7 @@ function DropdownMenuSubTrigger({
204
  children,
205
  ...props
206
  }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207
- inset?: boolean
208
  }) {
209
  return (
210
  <DropdownMenuPrimitive.SubTrigger
@@ -219,7 +219,7 @@ function DropdownMenuSubTrigger({
219
  {children}
220
  <ChevronRightIcon className="ml-auto size-4" />
221
  </DropdownMenuPrimitive.SubTrigger>
222
- )
223
  }
224
 
225
  function DropdownMenuSubContent({
@@ -235,7 +235,7 @@ function DropdownMenuSubContent({
235
  )}
236
  {...props}
237
  />
238
- )
239
  }
240
 
241
  export {
@@ -254,4 +254,4 @@ export {
254
  DropdownMenuSub,
255
  DropdownMenuSubTrigger,
256
  DropdownMenuSubContent,
257
- }
 
1
+ "use client";
2
 
3
+ import * as React from "react";
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
6
 
7
+ import { cn } from "@/lib/utils";
8
 
9
  function DropdownMenu({
10
  ...props
11
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
13
  }
14
 
15
  function DropdownMenuPortal({
 
17
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
  return (
19
  <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
+ );
21
  }
22
 
23
  function DropdownMenuTrigger({
 
28
  data-slot="dropdown-menu-trigger"
29
  {...props}
30
  />
31
+ );
32
  }
33
 
34
  function DropdownMenuContent({
 
48
  {...props}
49
  />
50
  </DropdownMenuPrimitive.Portal>
51
+ );
52
  }
53
 
54
  function DropdownMenuGroup({
 
56
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
  return (
58
  <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
+ );
60
  }
61
 
62
  function DropdownMenuItem({
 
65
  variant = "default",
66
  ...props
67
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
+ inset?: boolean;
69
+ variant?: "default" | "destructive";
70
  }) {
71
  return (
72
  <DropdownMenuPrimitive.Item
 
74
  data-inset={inset}
75
  data-variant={variant}
76
  className={cn(
77
+ "focus:bg-accent cursor-pointer! focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78
  className
79
  )}
80
  {...props}
81
  />
82
+ );
83
  }
84
 
85
  function DropdownMenuCheckboxItem({
 
105
  </span>
106
  {children}
107
  </DropdownMenuPrimitive.CheckboxItem>
108
+ );
109
  }
110
 
111
  function DropdownMenuRadioGroup({
 
116
  data-slot="dropdown-menu-radio-group"
117
  {...props}
118
  />
119
+ );
120
  }
121
 
122
  function DropdownMenuRadioItem({
 
140
  </span>
141
  {children}
142
  </DropdownMenuPrimitive.RadioItem>
143
+ );
144
  }
145
 
146
  function DropdownMenuLabel({
 
148
  inset,
149
  ...props
150
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151
+ inset?: boolean;
152
  }) {
153
  return (
154
  <DropdownMenuPrimitive.Label
 
160
  )}
161
  {...props}
162
  />
163
+ );
164
  }
165
 
166
  function DropdownMenuSeparator({
 
173
  className={cn("bg-border -mx-1 my-1 h-px", className)}
174
  {...props}
175
  />
176
+ );
177
  }
178
 
179
  function DropdownMenuShortcut({
 
189
  )}
190
  {...props}
191
  />
192
+ );
193
  }
194
 
195
  function DropdownMenuSub({
196
  ...props
197
  }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
199
  }
200
 
201
  function DropdownMenuSubTrigger({
 
204
  children,
205
  ...props
206
  }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207
+ inset?: boolean;
208
  }) {
209
  return (
210
  <DropdownMenuPrimitive.SubTrigger
 
219
  {children}
220
  <ChevronRightIcon className="ml-auto size-4" />
221
  </DropdownMenuPrimitive.SubTrigger>
222
+ );
223
  }
224
 
225
  function DropdownMenuSubContent({
 
235
  )}
236
  {...props}
237
  />
238
+ );
239
  }
240
 
241
  export {
 
254
  DropdownMenuSub,
255
  DropdownMenuSubTrigger,
256
  DropdownMenuSubContent,
257
+ };
components/user-menu/index.tsx CHANGED
@@ -1,6 +1,13 @@
1
  import Link from "next/link";
2
  import { useSession, signIn, signOut } from "next-auth/react";
3
- import { ArrowRight, Folder, LogOut, Moon, Plus, Sun } from "lucide-react";
 
 
 
 
 
 
 
4
  import { useTheme } from "next-themes";
5
  import { useEffect } from "react";
6
  import { FaDiscord } from "react-icons/fa6";
@@ -11,15 +18,20 @@ import {
11
  DropdownMenuContent,
12
  DropdownMenuItem,
13
  DropdownMenuLabel,
 
14
  DropdownMenuSeparator,
 
 
 
15
  DropdownMenuTrigger,
16
  } from "@/components/ui/dropdown-menu";
17
  import { Button } from "@/components/ui/button";
18
  import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
19
  import { useProjects } from "@/components/projects/useProjects";
20
  import { ProjectCard } from "@/components/projects/project-card";
21
- import { cn, DISCORD_URL } from "@/lib/utils";
22
  import HFLogo from "@/assets/hf-logo.svg";
 
23
 
24
  export function UserMenu() {
25
  const { data: session, status } = useSession();
@@ -107,6 +119,21 @@ export function UserMenu() {
107
  <DropdownMenuContent className="w-64" align="start">
108
  <DropdownMenuLabel>My Account</DropdownMenuLabel>
109
  <DropdownMenuSeparator />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  <DropdownMenuItem>
111
  <Link
112
  href={`https://huggingface.co/${session.user.username}`}
@@ -123,6 +150,28 @@ export function UserMenu() {
123
  Settings
124
  </Link>
125
  </DropdownMenuItem>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  <>
127
  <DropdownMenuSeparator />
128
  <DropdownMenuLabel>Projects</DropdownMenuLabel>
@@ -170,61 +219,27 @@ export function UserMenu() {
170
  </div>
171
  )}
172
  </>
173
- <>
174
- <DropdownMenuSeparator />
175
- <DropdownMenuLabel>Social</DropdownMenuLabel>
176
- <DropdownMenuItem>
177
- <Link
178
- href={DISCORD_URL}
179
- target="_blank"
180
- className="flex items-center justify-start gap-2"
181
- >
182
- <FaDiscord className="size-4 text-indigo-500" />
183
- Discord
184
- </Link>
185
- </DropdownMenuItem>
186
- <DropdownMenuItem>
187
- <Link
188
- href="https://huggingface.co/enzostvs"
189
- target="_blank"
190
- className="flex items-center justify-start gap-2"
191
- >
192
- <Image src={HFLogo} alt="HF" className="size-4" />
193
- Hugging Face
194
- </Link>
195
- </DropdownMenuItem>
196
- </>
197
  <DropdownMenuSeparator />
198
- <DropdownMenuLabel>Theme</DropdownMenuLabel>
199
- <div className="flex items-center justify-between gap-2 px-2 pb-2.5">
200
- <Button
201
- variant="outline"
202
- size="icon-xs"
203
- onClick={() => setTheme("light")}
204
- className={cn(
205
- "flex-1",
206
- theme === "light" && "border-amber-300! bg-amber-500/10!"
207
- )}
208
  >
209
- <Sun
210
- className={cn("size-3.5", theme === "light" && "text-amber-500")}
211
- />
212
- </Button>
213
- <Button
214
- variant="outline"
215
- size="icon-xs"
216
- onClick={() => setTheme("dark")}
217
- className={cn(
218
- "flex-1",
219
- theme === "dark" && "border-indigo-500/50! bg-indigo-500/20!"
220
- )}
221
  >
222
- <Moon
223
- className={cn("size-3.5", theme === "dark" && "text-indigo-500")}
224
- />
225
- </Button>
226
- </div>
227
- <DropdownMenuSeparator />
228
  <DropdownMenuItem onClick={handleSignOut}>
229
  <LogOut className="size-4" />
230
  Sign out
 
1
  import Link from "next/link";
2
  import { useSession, signIn, signOut } from "next-auth/react";
3
+ import {
4
+ ArrowRight,
5
+ Check,
6
+ Contrast,
7
+ Folder,
8
+ LogOut,
9
+ Plus,
10
+ } from "lucide-react";
11
  import { useTheme } from "next-themes";
12
  import { useEffect } from "react";
13
  import { FaDiscord } from "react-icons/fa6";
 
18
  DropdownMenuContent,
19
  DropdownMenuItem,
20
  DropdownMenuLabel,
21
+ DropdownMenuPortal,
22
  DropdownMenuSeparator,
23
+ DropdownMenuSub,
24
+ DropdownMenuSubContent,
25
+ DropdownMenuSubTrigger,
26
  DropdownMenuTrigger,
27
  } from "@/components/ui/dropdown-menu";
28
  import { Button } from "@/components/ui/button";
29
  import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
30
  import { useProjects } from "@/components/projects/useProjects";
31
  import { ProjectCard } from "@/components/projects/project-card";
32
+ import { DISCORD_URL } from "@/lib/utils";
33
  import HFLogo from "@/assets/hf-logo.svg";
34
+ import ProIcon from "@/assets/pro.svg";
35
 
36
  export function UserMenu() {
37
  const { data: session, status } = useSession();
 
119
  <DropdownMenuContent className="w-64" align="start">
120
  <DropdownMenuLabel>My Account</DropdownMenuLabel>
121
  <DropdownMenuSeparator />
122
+ {!session?.user?.isPro && (
123
+ <>
124
+ <DropdownMenuItem>
125
+ <Link
126
+ href="https://huggingface.co/pro"
127
+ className="flex items-center gap-1.5 bg-linear-to-r from-pink-500 via-green-500 to-amber-500 text-transparent bg-clip-text font-semibold"
128
+ target="_blank"
129
+ >
130
+ <Image alt="Pro" src={ProIcon} className="size-3.5" />
131
+ Subscribe to Pro
132
+ </Link>
133
+ </DropdownMenuItem>
134
+ <DropdownMenuSeparator />
135
+ </>
136
+ )}
137
  <DropdownMenuItem>
138
  <Link
139
  href={`https://huggingface.co/${session.user.username}`}
 
150
  Settings
151
  </Link>
152
  </DropdownMenuItem>
153
+ <DropdownMenuSub>
154
+ <DropdownMenuSubTrigger className="flex items-center justify-start gap-1.5">
155
+ <Contrast className="size-3.5" />
156
+ Appearance
157
+ </DropdownMenuSubTrigger>
158
+ <DropdownMenuPortal>
159
+ <DropdownMenuSubContent>
160
+ <DropdownMenuItem onClick={() => setTheme("light")}>
161
+ Light
162
+ {theme === "light" && <Check />}
163
+ </DropdownMenuItem>
164
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
165
+ Dark
166
+ {theme === "dark" && <Check />}
167
+ </DropdownMenuItem>
168
+ <DropdownMenuItem onClick={() => setTheme("system")}>
169
+ System
170
+ {theme === "system" && <Check />}
171
+ </DropdownMenuItem>
172
+ </DropdownMenuSubContent>
173
+ </DropdownMenuPortal>
174
+ </DropdownMenuSub>
175
  <>
176
  <DropdownMenuSeparator />
177
  <DropdownMenuLabel>Projects</DropdownMenuLabel>
 
219
  </div>
220
  )}
221
  </>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  <DropdownMenuSeparator />
223
+ <DropdownMenuItem>
224
+ <Link
225
+ href={DISCORD_URL}
226
+ target="_blank"
227
+ className="flex items-center justify-start gap-2"
 
 
 
 
 
228
  >
229
+ <FaDiscord className="size-4" />
230
+ Discord
231
+ </Link>
232
+ </DropdownMenuItem>
233
+ <DropdownMenuItem>
234
+ <Link
235
+ href="https://huggingface.co/enzostvs"
236
+ target="_blank"
237
+ className="flex items-center justify-start gap-2"
 
 
 
238
  >
239
+ <Image src={HFLogo} alt="HF" className="size-4 grayscale" />
240
+ Hugging Face
241
+ </Link>
242
+ </DropdownMenuItem>
 
 
243
  <DropdownMenuItem onClick={handleSignOut}>
244
  <LogOut className="size-4" />
245
  Sign out
lib/auth.ts CHANGED
@@ -24,28 +24,28 @@ export const authConfig = {
24
  id: profile.sub,
25
  name: profile.name || profile.preferred_username,
26
  username: profile.preferred_username,
27
- email: profile.email,
28
  image: profile.picture,
 
29
  };
30
  },
31
  },
32
  ],
33
  callbacks: {
34
  async jwt({ token, account, user }) {
35
- // Persist the OAuth access_token and user info to the token right after signin
36
  if (account) {
37
  token.accessToken = account.access_token;
38
  }
39
  if (user) {
40
  token.username = user.username;
 
41
  }
42
  return token;
43
  },
44
  async session({ session, token }) {
45
- // Send properties to the client, like an access_token from a provider
46
  session.accessToken = token.accessToken as string;
47
  if (session.user) {
48
  session.user.username = token.username as string;
 
49
  }
50
  return session;
51
  },
 
24
  id: profile.sub,
25
  name: profile.name || profile.preferred_username,
26
  username: profile.preferred_username,
 
27
  image: profile.picture,
28
+ isPro: profile.isPro || false,
29
  };
30
  },
31
  },
32
  ],
33
  callbacks: {
34
  async jwt({ token, account, user }) {
 
35
  if (account) {
36
  token.accessToken = account.access_token;
37
  }
38
  if (user) {
39
  token.username = user.username;
40
+ token.isPro = user.isPro;
41
  }
42
  return token;
43
  },
44
  async session({ session, token }) {
 
45
  session.accessToken = token.accessToken as string;
46
  if (session.user) {
47
  session.user.username = token.username as string;
48
+ session.user.isPro = token.isPro as boolean;
49
  }
50
  return session;
51
  },
next-auth.d.ts CHANGED
@@ -3,10 +3,12 @@ import NextAuth, { DefaultSession } from "next-auth";
3
  declare module "next-auth" {
4
  interface Session {
5
  accessToken?: string;
 
6
  }
7
-
8
  interface User {
9
  username?: string;
 
10
  }
11
  }
12
 
@@ -14,6 +16,6 @@ declare module "next-auth/jwt" {
14
  interface JWT {
15
  accessToken?: string;
16
  username?: string;
 
17
  }
18
  }
19
-
 
3
  declare module "next-auth" {
4
  interface Session {
5
  accessToken?: string;
6
+ isPro?: boolean;
7
  }
8
+
9
  interface User {
10
  username?: string;
11
+ isPro?: boolean;
12
  }
13
  }
14
 
 
16
  interface JWT {
17
  accessToken?: string;
18
  username?: string;
19
+ isPro?: boolean;
20
  }
21
  }