feat: user avatar images

- added user avatars for each user
- it will fetch user images from github or google and if there is no image then it will show initials
This commit is contained in:
Akhileshrangani4 2024-11-10 23:40:10 -05:00
parent 2262adca74
commit 30c9da559f
11 changed files with 64 additions and 18 deletions

View File

@ -15,6 +15,20 @@
"when": 1731290863632, "when": 1731290863632,
"tag": "0001_opposite_newton_destine", "tag": "0001_opposite_newton_destine",
"breakpoints": true "breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1731296235880,
"tag": "0002_rainy_fantastic_four",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1731297339306,
"tag": "0003_lying_snowbird",
"breakpoints": true
} }
] ]
} }

View File

@ -169,6 +169,7 @@ export default {
name: sb.name, name: sb.name,
type: sb.type, type: sb.type,
author: sb.author.name, author: sb.author.name,
authorAvatarUrl: sb.author.avatarUrl,
sharedOn: r.sharedOn, sharedOn: r.sharedOn,
} }
}) })

View File

@ -51,7 +51,7 @@ const getSharedUsers = async (usersToSandboxes: UsersToSandboxes[]) => {
} }
) )
const userData: User = await userRes.json() const userData: User = await userRes.json()
return { id: userData.id, name: userData.name } return { id: userData.id, name: userData.name, avatarUrl: userData.avatarUrl }
}) })
) )

View File

@ -35,6 +35,7 @@ export default async function DashboardPage() {
type: "react" | "node" type: "react" | "node"
author: string author: string
sharedOn: Date sharedOn: Date
authorAvatarUrl: string
}[] }[]
return ( return (

View File

@ -20,6 +20,7 @@ export default function DashboardSharedWithMe({
name: string name: string
type: "react" | "node" type: "react" | "node"
author: string author: string
authorAvatarUrl: string
sharedOn: Date sharedOn: Date
}[] }[]
}) { }) {
@ -58,7 +59,11 @@ export default function DashboardSharedWithMe({
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="flex items-center"> <div className="flex items-center">
<Avatar name={sandbox.author} className="mr-2" /> <Avatar
name={sandbox.author}
avatarUrl={sandbox.authorAvatarUrl}
className="mr-2"
/>
{sandbox.author} {sandbox.author}
</div> </div>
</TableCell> </TableCell>

View File

@ -23,7 +23,7 @@ export default function Navbar({
}: { }: {
userData: User userData: User
sandboxData: Sandbox sandboxData: Sandbox
shared: { id: string; name: string }[] shared: { id: string; name: string; avatarUrl: string }[]
}) { }) {
const [isEditOpen, setIsEditOpen] = useState(false) const [isEditOpen, setIsEditOpen] = useState(false)
const [isShareOpen, setIsShareOpen] = useState(false) const [isShareOpen, setIsShareOpen] = useState(false)

View File

@ -43,6 +43,7 @@ export default function ShareSandboxModal({
shared: { shared: {
id: string id: string
name: string name: string
avatarUrl: string
}[] }[]
}) { }) {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@ -142,7 +143,11 @@ export default function ShareSandboxModal({
</DialogHeader> </DialogHeader>
<div className="space-y-2"> <div className="space-y-2">
{shared.map((user) => ( {shared.map((user) => (
<SharedUser key={user.id} user={user} sandboxId={data.id} /> <SharedUser
key={user.id}
user={user}
sandboxId={data.id}
/>
))} ))}
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@ export default function SharedUser({
user, user,
sandboxId, sandboxId,
}: { }: {
user: { id: string; name: string } user: { id: string; name: string; avatarUrl: string }
sandboxId: string sandboxId: string
}) { }) {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@ -24,7 +24,7 @@ export default function SharedUser({
return ( return (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center"> <div className="flex items-center">
<Avatar name={user.name} className="mr-2" /> <Avatar name={user.name} avatarUrl={user.avatarUrl} className="mr-2" />
{user.name} {user.name}
</div> </div>
<Button <Button

View File

@ -1,23 +1,42 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import Image from "next/image"
export default function Avatar({ export default function Avatar({
name, name,
avatarUrl,
className, className,
}: { }: {
name: string name: string
avatarUrl?: string | null
className?: string className?: string
}) { }) {
// Generate initials from name if no avatarUrl is provided
const initials = name
? name
.split(" ")
.slice(0, 2)
.map((letter) => letter[0].toUpperCase())
.join("")
: "?"
return ( return (
<div <div
className={cn( className={cn(
className, className,
"w-5 h-5 font-mono rounded-full overflow-hidden bg-gradient-to-t from-neutral-800 to-neutral-600 flex items-center justify-center text-[0.5rem] font-medium" "w-9 h-9 font-mono rounded-full overflow-hidden bg-gradient-to-t from-neutral-800 to-neutral-600 flex items-center justify-center text-sm font-medium"
)} )}
> >
{name {avatarUrl ? (
.split(" ") <Image
.slice(0, 2) src={avatarUrl}
.map((letter) => letter[0].toUpperCase())} alt={name || "User"}
width={20}
height={20}
className="w-full h-full object-cover"
/>
) : (
initials
)}
</div> </div>
) )
} }

View File

@ -11,6 +11,7 @@ import { User } from "@/lib/types"
import { useClerk } from "@clerk/nextjs" import { useClerk } from "@clerk/nextjs"
import { LogOut, Sparkles } from "lucide-react" import { LogOut, Sparkles } from "lucide-react"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import Avatar from "./avatar"
export default function UserButton({ userData }: { userData: User }) { export default function UserButton({ userData }: { userData: User }) {
if (!userData) return null if (!userData) return null
@ -21,13 +22,7 @@ export default function UserButton({ userData }: { userData: User }) {
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<div className="w-9 h-9 font-mono rounded-full overflow-hidden bg-gradient-to-t from-neutral-800 to-neutral-600 flex items-center justify-center text-sm font-medium"> <Avatar name={userData.name} avatarUrl={userData.avatarUrl} />
{userData.name &&
userData.name
.split(" ")
.slice(0, 2)
.map((name) => name[0].toUpperCase())}
</div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-48" align="end"> <DropdownMenuContent className="w-48" align="end">
<div className="py-1.5 px-2 w-full"> <div className="py-1.5 px-2 w-full">

View File

@ -5,6 +5,12 @@ const nextConfig = {
{ {
hostname: "cdn.simpleicons.org", hostname: "cdn.simpleicons.org",
}, },
{
hostname: "img.clerk.com",
},
{
hostname: "images.clerk.dev",
},
], ],
}, },
} }