Skip to content

Commit 2e3f911

Browse files
committed
chore: profile
1 parent 940d3a1 commit 2e3f911

12 files changed

Lines changed: 1949 additions & 98 deletions

File tree

web/package-lock.json

Lines changed: 450 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"deploy": "gh-pages -d dist"
1515
},
1616
"dependencies": {
17+
"@radix-ui/react-avatar": "^1.1.11",
1718
"@radix-ui/react-dropdown-menu": "^2.1.16",
1819
"@radix-ui/react-label": "^2.1.8",
1920
"@radix-ui/react-navigation-menu": "^1.2.14",
@@ -35,6 +36,8 @@
3536
"react-dom": "19.2.3",
3637
"react-router": "^7.13.0",
3738
"react-use-measure": "^2.1.7",
39+
"recharts": "^2.15.4",
40+
"simple-icons": "^16.9.0",
3841
"tailwindcss-animate": "^1.0.7"
3942
},
4043
"devDependencies": {

web/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import SignIn from "./pages/SignIn";
66
import AuthCallback from "./pages/AuthCallback";
77
import CompanyProblems from "./pages/CompanyProblems";
88
import AllProblems from "./pages/AllProblems";
9+
import Profile from "./pages/Profile";
910

1011
const router = createBrowserRouter(
1112
[
@@ -38,6 +39,11 @@ const router = createBrowserRouter(
3839
element: <AllProblems />,
3940
errorElement: <ErrorPage />,
4041
},
42+
{
43+
path: "/profile",
44+
element: <Profile />,
45+
errorElement: <ErrorPage />,
46+
},
4147
{
4248
path: "/error",
4349
element: <ErrorPage />,
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { motion } from "framer-motion";
2+
import {
3+
siZomato,
4+
siApple,
5+
siUber,
6+
siCoinbase,
7+
siGithub,
8+
siCloudflare,
9+
siRoblox,
10+
siMeta,
11+
siVmware,
12+
siDropbox,
13+
siExpedia,
14+
siNetflix,
15+
siDoordash,
16+
} from "simple-icons";
17+
18+
const logos = [
19+
<span
20+
key="zomato"
21+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-42"
22+
dangerouslySetInnerHTML={{ __html: siZomato.svg }}
23+
/>,
24+
<span
25+
key="apple"
26+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-14"
27+
dangerouslySetInnerHTML={{ __html: siApple.svg }}
28+
/>,
29+
<span
30+
key="uber"
31+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-20"
32+
dangerouslySetInnerHTML={{ __html: siUber.svg }}
33+
/>,
34+
<span
35+
key="coinbase"
36+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-42"
37+
dangerouslySetInnerHTML={{ __html: siCoinbase.svg }}
38+
/>,
39+
<span
40+
key="github"
41+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-14"
42+
dangerouslySetInnerHTML={{ __html: siGithub.svg }}
43+
/>,
44+
<span
45+
key="microsoft"
46+
className="[&>img]:w-46 [&>img]:opacity-50 [&>img]:grayscale [&>img]:dark:invert"
47+
>
48+
<img
49+
src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/3840px-Microsoft_logo_%282012%29.svg.png"
50+
alt="Microsoft"
51+
/>
52+
</span>,
53+
<span
54+
key="vmware"
55+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-36"
56+
dangerouslySetInnerHTML={{ __html: siVmware.svg }}
57+
/>,
58+
<span
59+
key="netflix"
60+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-11"
61+
dangerouslySetInnerHTML={{ __html: siNetflix.svg }}
62+
/>,
63+
<span
64+
key="doordash"
65+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-14"
66+
dangerouslySetInnerHTML={{ __html: siDoordash.svg }}
67+
/>,
68+
69+
<span
70+
key="dropbox"
71+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-12"
72+
dangerouslySetInnerHTML={{ __html: siDropbox.svg }}
73+
/>,
74+
<span
75+
key="google"
76+
className="[&>img]:w-38 [&>img]:opacity-50 [&>img]:grayscale [&>img]:dark:invert"
77+
>
78+
<img
79+
src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/3840px-Google_2015_logo.svg.png"
80+
alt="Google"
81+
/>
82+
</span>,
83+
<span
84+
key="expedia"
85+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-12"
86+
dangerouslySetInnerHTML={{ __html: siExpedia.svg }}
87+
/>,
88+
<span
89+
key="amazon"
90+
className="[&>img]:w-34 [&>img]:opacity-30 [&>img]:grayscale [&>img]:dark:invert"
91+
>
92+
<img
93+
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Amazon_logo.svg/3840px-Amazon_logo.svg.png"
94+
alt="Amazon"
95+
/>
96+
</span>,
97+
<span
98+
key="cloudflare"
99+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-18"
100+
dangerouslySetInnerHTML={{ __html: siCloudflare.svg }}
101+
/>,
102+
<span
103+
key="roblox"
104+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-14"
105+
dangerouslySetInnerHTML={{ __html: siRoblox.svg }}
106+
/>,
107+
<span
108+
key="meta"
109+
className="text-muted-foreground [&>svg]:fill-muted-foreground/50 [&>svg]:w-16"
110+
dangerouslySetInnerHTML={{ __html: siMeta.svg }}
111+
/>,
112+
];
113+
114+
export function CompanyCarousel() {
115+
// Duplicate the list so the seam is invisible
116+
const track = [...logos, ...logos];
117+
118+
return (
119+
<div className="relative w-full overflow-hidden pt-3">
120+
{/* Fade edges */}
121+
<div className="from-background pointer-events-none absolute inset-y-0 left-0 z-10 w-24 bg-gradient-to-r to-transparent" />
122+
<div className="from-background pointer-events-none absolute inset-y-0 right-0 z-10 w-24 bg-gradient-to-l to-transparent" />
123+
124+
<motion.div
125+
className="flex items-center gap-8 sm:gap-16"
126+
style={{ width: "max-content" }}
127+
animate={{ x: ["0%", "-50%"] }}
128+
transition={{
129+
duration: 70,
130+
ease: "linear",
131+
repeat: Infinity,
132+
}}
133+
>
134+
{track.map((logo, i) => (
135+
<div key={i} className="flex shrink-0 items-center">
136+
{logo}
137+
</div>
138+
))}
139+
</motion.div>
140+
</div>
141+
);
142+
}

web/src/components/Navbar.tsx

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,20 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
1717
import { Spinner } from "./ui/spinner";
1818
import SearchCompany from "./SearchCompany";
1919
import GithubSvg from "~/assets/GithubSvg";
20+
import {
21+
DropdownMenu,
22+
DropdownMenuContent,
23+
DropdownMenuGroup,
24+
DropdownMenuItem,
25+
DropdownMenuSeparator,
26+
DropdownMenuTrigger,
27+
} from "./ui/dropdown-menu";
28+
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
2029

2130
export default function Navbar() {
2231
return (
2332
<div className="bg-background font-geist sticky top-0 z-50 flex h-(--header-height) w-full">
24-
<div className="flex w-full items-center justify-between px-6">
33+
<div className="flex w-full items-center justify-between px-2 sm:px-6">
2534
<div className="hidden h-full items-center justify-between md:flex">
2635
<NavigationMenu>
2736
<NavigationMenuList>
@@ -37,6 +46,7 @@ export default function Navbar() {
3746
<Link
3847
to="https://github.com/hitarth-gg/visor-leetcode"
3948
target="_blank"
49+
className="hidden sm:flex"
4050
>
4151
<Button
4252
variant={"ghost"}
@@ -60,7 +70,7 @@ function MobileNav() {
6070

6171
return (
6272
<>
63-
<div className="flex md:hidden">
73+
<div className="mr-2 flex md:hidden">
6474
<Button
6575
variant={"ghost"}
6676
size={"icon"}
@@ -122,17 +132,23 @@ export function AuthButton() {
122132
const navigate = useNavigate();
123133
const [session, setSession] = useState<Session | null>(null);
124134
const [isSigningOut, setIsSigningOut] = useState(false);
135+
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
136+
console.log(avatarUrl);
125137

126138
useEffect(() => {
127139
// Get initial session
128-
supabase.auth.getSession().then(({ data }) => setSession(data.session));
140+
supabase.auth.getSession().then(({ data }) => {
141+
setSession(data.session);
142+
setAvatarUrl(data.session?.user?.user_metadata?.avatar_url ?? null);
143+
});
129144

130145
// Keep in sync on tab focus, sign in/out events
131146
const {
132147
data: { subscription },
133148
} = supabase.auth.onAuthStateChange(
134149
(_event: string, session: Session | null) => {
135150
setSession(session);
151+
setAvatarUrl(session?.user?.user_metadata?.avatar_url ?? null);
136152
},
137153
);
138154

@@ -148,22 +164,50 @@ export function AuthButton() {
148164
return (
149165
<Tooltip>
150166
<TooltipTrigger asChild>
151-
<Button
152-
variant={"outline"}
153-
size={"icon"}
154-
className="h-8 cursor-pointer px-3 shadow-none"
155-
onClick={async () => {
156-
setIsSigningOut(true);
157-
try {
158-
await handleSignOut();
159-
} finally {
160-
setIsSigningOut(false);
161-
}
162-
}}
163-
disabled={isSigningOut}
164-
>
165-
{isSigningOut ? <Spinner /> : <LogOut />}
166-
</Button>
167+
{isSigningOut ? (
168+
<Spinner />
169+
) : avatarUrl ? (
170+
<DropdownMenu>
171+
<DropdownMenuTrigger asChild>
172+
<Button variant="ghost" size="icon" className="rounded-full">
173+
<Avatar className="h-8 w-8">
174+
<AvatarImage src={avatarUrl} alt="" />
175+
<AvatarFallback>CN</AvatarFallback>
176+
</Avatar>
177+
</Button>
178+
</DropdownMenuTrigger>
179+
<DropdownMenuContent className="w-32">
180+
<DropdownMenuGroup>
181+
<DropdownMenuItem
182+
onClick={() => {
183+
navigate("/profile");
184+
}}
185+
>
186+
Profile
187+
</DropdownMenuItem>
188+
</DropdownMenuGroup>
189+
<DropdownMenuSeparator />
190+
<DropdownMenuGroup>
191+
<DropdownMenuItem
192+
className="text-destructive focus:text-destructive"
193+
onClick={async () => {
194+
setIsSigningOut(true);
195+
try {
196+
await handleSignOut();
197+
} finally {
198+
setIsSigningOut(false);
199+
}
200+
}}
201+
disabled={isSigningOut}
202+
>
203+
Log out
204+
</DropdownMenuItem>
205+
</DropdownMenuGroup>
206+
</DropdownMenuContent>
207+
</DropdownMenu>
208+
) : (
209+
<LogOut />
210+
)}
167211
</TooltipTrigger>
168212
<TooltipContent>
169213
<p>Sign out</p>

web/src/components/ui/avatar.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as React from "react"
2+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
3+
4+
import { cn } from "~/lib/utils"
5+
6+
const Avatar = React.forwardRef<
7+
React.ElementRef<typeof AvatarPrimitive.Root>,
8+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
9+
>(({ className, ...props }, ref) => (
10+
<AvatarPrimitive.Root
11+
ref={ref}
12+
className={cn(
13+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
14+
className
15+
)}
16+
{...props}
17+
/>
18+
))
19+
Avatar.displayName = AvatarPrimitive.Root.displayName
20+
21+
const AvatarImage = React.forwardRef<
22+
React.ElementRef<typeof AvatarPrimitive.Image>,
23+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
24+
>(({ className, ...props }, ref) => (
25+
<AvatarPrimitive.Image
26+
ref={ref}
27+
className={cn("aspect-square h-full w-full", className)}
28+
{...props}
29+
/>
30+
))
31+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
32+
33+
const AvatarFallback = React.forwardRef<
34+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
35+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
36+
>(({ className, ...props }, ref) => (
37+
<AvatarPrimitive.Fallback
38+
ref={ref}
39+
className={cn(
40+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
41+
className
42+
)}
43+
{...props}
44+
/>
45+
))
46+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47+
48+
export { Avatar, AvatarImage, AvatarFallback }

0 commit comments

Comments
 (0)