-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
181 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,64 @@ | ||
import NiceAvatar, { genConfig } from 'react-nice-avatar' | ||
'use client' | ||
|
||
import { ChangeEvent } from 'react' | ||
|
||
import { cn } from '@/lib/utils' | ||
import { useMe } from '@/lib/hooks/use-me' | ||
import { graphql } from '@/lib/gql/generates' | ||
import { useMutation } from '@/lib/tabby/gql' | ||
import { toast } from 'sonner' | ||
|
||
import { buttonVariants } from "@/components/ui/button" | ||
import { UserAvatar, mutateAvatar } from '@/components/user-avatar' | ||
|
||
const uploadUserAvatarMutation = graphql(/* GraphQL */ ` | ||
mutation uploadUserAvatarBase64($id: ID!, $avatarBase64: String!) { | ||
uploadUserAvatarBase64(id: $id, avatarBase64: $avatarBase64) | ||
} | ||
`) | ||
|
||
export const Avatar = () => { | ||
const [{ data }] = useMe() | ||
|
||
const uploadUserAvatar = useMutation(uploadUserAvatarMutation) | ||
if (!data?.me?.email) return null | ||
|
||
const config = genConfig(data?.me?.email) | ||
const onUploadAvatar = (e: ChangeEvent<HTMLInputElement>) => { | ||
const file = e.target.files ? e.target.files[0] : null; | ||
if (file) { | ||
const reader = new FileReader(); | ||
|
||
reader.onloadend = async () => { | ||
try { | ||
const imageString = reader.result as string; | ||
const mimeHeaderMatcher = new RegExp("^data:image/.+;base64,") | ||
const avatarBase64 = imageString.replace(mimeHeaderMatcher, "") | ||
|
||
const response = await uploadUserAvatar({ | ||
avatarBase64, | ||
id: data.me.id | ||
}) | ||
if (response?.error) throw response.error | ||
if (response?.data?.uploadUserAvatarBase64 === false) throw new Error('Upload failed') | ||
mutateAvatar(data.me.id) | ||
toast.success('Avatar uploaded successfully.') | ||
} catch (err: any) { | ||
toast.error(err.message || 'Upload failed') | ||
} | ||
}; | ||
|
||
reader.readAsDataURL(file); | ||
} | ||
} | ||
|
||
return ( | ||
<div className="flex h-16 w-16 rounded-full border"> | ||
<NiceAvatar className="w-full" {...config} /> | ||
<div className="flex items-center"> | ||
<UserAvatar className="h-16 w-16 border" /> | ||
|
||
<div className="ml-3"> | ||
<label htmlFor="avatar-file" className={cn("relative cursor-pointer", buttonVariants({ variant: 'outline' }))}>Upload new picture</label> | ||
<input id="avatar-file" type="file" accept='image/*' className="hidden" onChange={onUploadAvatar} /> | ||
<p className="mt-1.5 text-xs text-muted-foreground">Recommended: Square JPG, PNG, at least 1,000 pixels per side.</p> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
"use client" | ||
|
||
import * as React from "react" | ||
import * as AvatarPrimitive from "@radix-ui/react-avatar" | ||
|
||
import { cn } from "@/lib/utils" | ||
|
||
const Avatar = React.forwardRef< | ||
React.ElementRef<typeof AvatarPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> | ||
>(({ className, ...props }, ref) => ( | ||
<AvatarPrimitive.Root | ||
ref={ref} | ||
className={cn( | ||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", | ||
className | ||
)} | ||
{...props} | ||
/> | ||
)) | ||
Avatar.displayName = AvatarPrimitive.Root.displayName | ||
|
||
const AvatarImage = React.forwardRef< | ||
React.ElementRef<typeof AvatarPrimitive.Image>, | ||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> | ||
>(({ className, ...props }, ref) => ( | ||
<AvatarPrimitive.Image | ||
ref={ref} | ||
className={cn("aspect-square h-full w-full", className)} | ||
{...props} | ||
/> | ||
)) | ||
AvatarImage.displayName = AvatarPrimitive.Image.displayName | ||
|
||
const AvatarFallback = React.forwardRef< | ||
React.ElementRef<typeof AvatarPrimitive.Fallback>, | ||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> | ||
>(({ className, ...props }, ref) => ( | ||
<AvatarPrimitive.Fallback | ||
ref={ref} | ||
className={cn( | ||
"flex h-full w-full items-center justify-center rounded-full bg-muted", | ||
className | ||
)} | ||
{...props} | ||
/> | ||
)) | ||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName | ||
|
||
export { Avatar, AvatarImage, AvatarFallback } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
'use client' | ||
|
||
import NiceAvatar, { genConfig } from 'react-nice-avatar' | ||
import { mutate } from 'swr'; | ||
import useSWRImmutable from 'swr/immutable' | ||
|
||
import { useMe } from '@/lib/hooks/use-me' | ||
import fetcher from '@/lib/tabby/fetcher' | ||
import { cn } from '@/lib/utils'; | ||
|
||
import { | ||
Avatar as AvatarComponent, | ||
AvatarFallback, | ||
AvatarImage, | ||
} from "@/components/ui/avatar" | ||
import { Skeleton } from '@/components/ui/skeleton' | ||
|
||
export function UserAvatar ({ | ||
className | ||
}: { | ||
className?: string; | ||
}) { | ||
const [{ data }] = useMe() | ||
const avatarUrl = !data?.me?.email ? null : `/avatar/${data.me.id}` | ||
const { | ||
data: avatarImageSrc, | ||
isLoading | ||
} = useSWRImmutable(avatarUrl, (url: string) => { | ||
return fetcher(url, { | ||
responseFormatter: async response => { | ||
if (!response.ok) return undefined | ||
const blob = await response.blob() | ||
const buffer = Buffer.from(await blob.arrayBuffer()) | ||
return `data:image/png;base64,${buffer.toString('base64')}` | ||
} | ||
}) | ||
}) | ||
|
||
if (!data?.me?.email) return null | ||
|
||
if (isLoading) { | ||
return ( | ||
<Skeleton className={cn('h-16 w-16 rounded-full', className)} /> | ||
) | ||
} | ||
|
||
if (!avatarImageSrc) { | ||
const config = genConfig(data.me.email) | ||
return ( | ||
<NiceAvatar className={cn("h-16 w-16", className)} {...config} /> | ||
) | ||
} | ||
|
||
return ( | ||
<AvatarComponent className={cn("h-16 w-16", className)}> | ||
<AvatarImage src={avatarImageSrc} alt={data.me.email} className="object-cover" /> | ||
<AvatarFallback>{data.me?.email.substring(0, 2)}</AvatarFallback> | ||
</AvatarComponent> | ||
) | ||
} | ||
|
||
export const mutateAvatar = (userId: string) => { | ||
mutate(`/avatar/${userId}`) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters