The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf: improve LCP (#48)

* perf: improve LCP

* fix: lint

* fix: AvatarFallback.displayName

authored by

Luna Seemann and committed by
GitHub
a409505e f6757de0

+80 -60
+31
app/(home)/nsfw-banner.component.tsx
··· 1 + "use client"; 2 + 3 + import { Badge } from "@/components/ui/badge"; 4 + import { useSyncExternalStore } from "react"; 5 + import { HiFire } from "react-icons/hi"; 6 + 7 + export function NsfwBanner() { 8 + const isEmbedded = useSyncExternalStore( 9 + () => () => {}, 10 + () => window.self !== window.top, 11 + () => false 12 + ); 13 + 14 + if (isEmbedded) return null; 15 + 16 + return ( 17 + <div className="p-4 pb-3 border border-divider rounded-lg my-8"> 18 + <Badge 19 + className="mb-2" 20 + variant="flat" 21 + radius="rounded" 22 + > 23 + <HiFire /> 24 + Supports NSFW 25 + </Badge> 26 + <div className="text-base"> 27 + Find spicy nekos, waifus, and more in nsfw marked channels. 28 + </div> 29 + </div> 30 + ); 31 + }
+8 -25
app/(home)/page.tsx
··· 23 23 import { actor } from "@/utils/tts"; 24 24 import { getCanonicalUrl } from "@/utils/urls"; 25 25 import { Montserrat, Patrick_Hand } from "next/font/google"; 26 - import { headers } from "next/headers"; 27 26 import Image from "next/image"; 28 27 import Link from "next/link"; 29 28 import { Suspense } from "react"; 30 29 import { BsDiscord, BsYoutube } from "react-icons/bs"; 31 - import { HiArrowNarrowRight, HiArrowRight, HiCash, HiCheck, HiFire, HiLockOpen, HiUserAdd } from "react-icons/hi"; 30 + import { HiArrowNarrowRight, HiArrowRight, HiCash, HiCheck, HiLockOpen, HiUserAdd } from "react-icons/hi"; 32 31 33 32 import { CallToAction } from "./cta.component"; 34 33 import { Faq } from "./faq.component"; 34 + import { NsfwBanner } from "./nsfw-banner.component"; 35 35 import { Ratings } from "./ratings.component"; 36 36 import { TTSDemo } from "./tts-demo.component"; 37 37 38 - const montserrat = Montserrat({ subsets: ["latin"] }); 39 - const handwritten = Patrick_Hand({ subsets: ["latin"], weight: "400" }); 38 + const montserrat = Montserrat({ subsets: ["latin"], display: "swap" }); 39 + const handwritten = Patrick_Hand({ subsets: ["latin"], weight: "400", display: "swap" }); 40 40 41 41 export const revalidate = 43_200; 42 42 ··· 62 62 } 63 63 }); 64 64 65 - export default async function Home() { 66 - const heads = await headers(); 67 - const isEmbedded = heads.get("sec-fetch-dest") === "iframe"; 68 - 65 + export default function Home() { 69 66 return ( 70 67 <div className="flex items-center flex-col w-full"> 71 68 ··· 83 80 {" for "} 84 81 <span className="break-keep inline-flex items-center"> 85 82 Discord 86 - <DiscordAppBadge className="mt-1 scale-[250%] md:scale-[300%] lg:scale-[360%] relative left-12 md:left-14 lg:left-20" /> 83 + <DiscordAppBadge variant="large" className="mt-1 ml-4 relative" /> 87 84 </span> 88 85 </h1> 89 86 ··· 131 128 </div> 132 129 133 130 <span className={cn("lg:ml-auto flex gap-2 text-neutral-500 font-medium opacity-80 pl-20 lg:pr-20 rotate-2 scale-110 relative pt-0.5", handwritten.className)}> 134 - <Image src={ArrowPic} width={24} height={24} alt="arrow up" className="size-5" draggable={false} /> 131 + <Image src={ArrowPic} width={24} height={24} alt="arrow up" className="size-5" draggable={false} priority /> 135 132 Get started here in seconds 136 133 </span> 137 134 ··· 341 338 Dive into a world of adorable nekos, charming waifus, and much more, all at your fingertips. 342 339 Whether it{"'"}s sharing the cutest characters or discovering stunning artwork, bring the joy of anime directly to your community, making your server a hub for all things anime-related. 343 340 </div> 344 - {!isEmbedded && ( 345 - <div className="p-4 pb-3 border border-divider rounded-lg my-8"> 346 - <Badge 347 - className="mb-2" 348 - variant="flat" 349 - radius="rounded" 350 - > 351 - <HiFire /> 352 - Supports NSFW 353 - </Badge> 354 - <div className="text-base"> 355 - Find spicy nekos, waifus, and more in nsfw marked channels. 356 - </div> 357 - </div> 358 - )} 341 + <NsfwBanner /> 359 342 <div className="flex gap-2 mt-6"> 360 343 <Invite /> 361 344 </div>
+1
app/(home)/ratings.component.tsx
··· 43 43 height={230 / 16.428} 44 44 src={TopggIcon} 45 45 width={338 / 16.428} 46 + priority 46 47 /> 47 48 .gg 48 49 </span>
+1 -1
app/(home)/tts-demo.component.tsx
··· 112 112 {/* Chat input */} 113 113 <div className="bg-[#383a40] rounded-lg p-3 w-full border border-white/5 backdrop-blur-sm"> 114 114 <div className="flex items-center gap-2"> 115 - <Image src="/luna.webp" alt="" width={64} height={64} className="size-6 rounded-full" /> 115 + <Image src="/luna.webp" alt="" width={64} height={64} className="size-6 rounded-full" priority /> 116 116 <div className="flex-1 text-sm text-white min-h-5"> 117 117 {displayedText} 118 118 {isTyping && (
+10 -5
components/discord/app-badge.tsx
··· 1 1 import { cn } from "@/utils/cn"; 2 - import type { HTMLProps } from "react"; 3 2 import { HiCheck } from "react-icons/hi"; 4 3 4 + interface Props extends React.HTMLAttributes<HTMLDivElement> { 5 + variant?: "default" | "large"; 6 + } 7 + 5 8 export default function DiscordAppBadge({ 6 9 className, 7 10 children, 11 + variant = "default", 8 12 ...props 9 - }: HTMLProps<HTMLDivElement>) { 13 + }: Props) { 10 14 return ( 11 15 <div 12 16 className={cn( 13 - "text-xxs! text-white bg-blurple rounded-sm py-px px-1 h-4 inline-flex items-center", 17 + "text-xxs! text-white bg-blurple rounded-sm py-px px-1 h-4 inline-flex items-center", 18 + variant === "large" && "h-10 px-3 text-lg! rounded-md", 14 19 className 15 20 )} 16 21 {...props} 17 22 > 18 - <HiCheck /> 19 - <span className="ml-1 font-semibold"> 23 + <HiCheck className={cn(variant === "large" && "size-6")} /> 24 + <span className={cn("ml-1 font-semibold", variant === "large" && "ml-2")}> 20 25 {children || "APP"} 21 26 </span> 22 27 </div>
+10 -13
components/header.tsx
··· 4 4 import { userStore } from "@/common/user"; 5 5 import { webStore } from "@/common/webstore"; 6 6 import { LoginButton } from "@/components/login-button"; 7 - import { Avatar, AvatarImage } from "@/components/ui/avatar"; 7 + import { UserAvatar } from "@/components/ui/avatar"; 8 8 import { 9 9 DropdownMenu, 10 10 DropdownMenuContent, ··· 68 68 <button 69 69 className="ml-auto truncate flex hover:bg-wamellow py-2 px-4 rounded-lg duration-200 items-center data-[state=open]:bg-wamellow outline-none" 70 70 > 71 - <Avatar className="size-[30px] mr-2"> 72 - <AvatarImage 73 - alt={user.username} 74 - src={user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}?size=96` : "/discord.webp"} 75 - /> 76 - </Avatar> 71 + <UserAvatar 72 + className="size-[30px] mr-2" 73 + alt={user.username} 74 + src={user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}?size=96` : "/discord.webp"} 75 + /> 77 76 78 77 <p className="mr-1 relative bottom-px truncate block text-primary-foreground font-medium tracking-tight">{user.globalName || user.username}</p> 79 78 <HiChevronDown /> ··· 81 80 </DropdownMenuTrigger> 82 81 <DropdownMenuContent className='w-56 scale-120 relative top-7 right-5' align="end"> 83 82 <DropdownMenuLabel className='flex items-center gap-3'> 84 - <Avatar> 85 - <AvatarImage 86 - alt={user.username} 87 - src={user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}?size=96` : "/discord.webp"} 88 - /> 89 - </Avatar> 83 + <UserAvatar 84 + alt={user.username} 85 + src={user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}?size=96` : "/discord.webp"} 86 + /> 90 87 <div className='flex flex-col pb-0.5 truncate'> 91 88 <span className='text-popover-foreground truncate'>{user.globalName || user.username}</span> 92 89 <span className='text-muted-foreground text-xs truncate'>{user.email}</span>
+19 -16
components/ui/avatar.tsx
··· 2 2 3 3 import { cn } from "@/utils/cn"; 4 4 import * as AvatarPrimitive from "@radix-ui/react-avatar"; 5 - import type Image from "next/image"; 5 + import Image from "next/image"; 6 6 import * as React from "react"; 7 7 8 8 const Avatar = React.forwardRef< ··· 20 20 )); 21 21 Avatar.displayName = AvatarPrimitive.Root.displayName; 22 22 23 - const AvatarImage = React.forwardRef< 24 - React.ElementRef<typeof AvatarPrimitive.Image>, 25 - React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> 26 - >(({ className, ...props }, ref) => ( 27 - <AvatarPrimitive.Image 28 - ref={ref} 29 - className={cn("aspect-square h-full w-full", className)} 30 - {...props} 31 - /> 32 - )); 33 - AvatarImage.displayName = AvatarPrimitive.Image.displayName; 34 - 35 23 const AvatarFallback = React.forwardRef< 36 24 React.ElementRef<typeof AvatarPrimitive.Fallback>, 37 25 React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> ··· 52 40 username, 53 41 className, 54 42 alt, 43 + priority, 55 44 ...props 56 45 }: { 57 46 src: string; 58 47 username?: string | null; 59 48 alt: string; 49 + priority?: boolean; 60 50 } & React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>) { 51 + const [isError, setIsError] = React.useState(false); 52 + 61 53 return ( 62 54 <Avatar 63 55 className={cn("rounded-full", className)} 64 56 {...props} 65 57 > 66 - <AvatarImage alt={alt} src={src} /> 67 - <AvatarFallback>{(username || "Wamellow").toLowerCase().slice(0, 2)}</AvatarFallback> 58 + {isError ? ( 59 + <AvatarFallback>{(username || "Wamellow").toLowerCase().slice(0, 2)}</AvatarFallback> 60 + ) : ( 61 + <Image 62 + alt={alt} 63 + src={src} 64 + fill 65 + className="aspect-square h-full w-full object-cover" 66 + priority={priority} 67 + onError={() => setIsError(true)} 68 + sizes="(max-width: 768px) 32px, 64px" 69 + /> 70 + )} 68 71 </Avatar> 69 72 ); 70 73 } ··· 105 108 ); 106 109 } 107 110 108 - export { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarImage, UserAvatar }; 111 + export { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, UserAvatar };