Improve the Discord status component
All checks were successful
Deploy Site / docker (ubuntu-latest, 2.44.0) (push) Successful in 2m10s
All checks were successful
Deploy Site / docker (ubuntu-latest, 2.44.0) (push) Successful in 2m10s
This commit is contained in:
parent
65f8d0c189
commit
f623cf4bfc
@ -14,6 +14,7 @@
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"axios": "^1.7.7",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.3.30",
|
||||
|
BIN
public/me.png
BIN
public/me.png
Binary file not shown.
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 172 KiB |
@ -19,7 +19,9 @@ const LandingPage = (): ReactElement => (
|
||||
<BlurFade className="my-7" delay={1.25} inView>
|
||||
<Navigation />
|
||||
</BlurFade>
|
||||
<DiscordStatus />
|
||||
<BlurFade className="my-7" delay={1.85} inView>
|
||||
<DiscordStatus />
|
||||
</BlurFade>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
@ -1,89 +1,152 @@
|
||||
"use client";
|
||||
|
||||
import { ReactElement } from "react";
|
||||
import { Activity, Data, DiscordUser, useLanyardWS } from "use-lanyard";
|
||||
import BlurFade from "@/components/ui/blur-fade";
|
||||
import { ReactElement, useEffect, useState } from "react";
|
||||
import { Data, DiscordUser, useLanyardWS } from "use-lanyard";
|
||||
import DCDNUser from "@/types/dcdn";
|
||||
import axios from "axios";
|
||||
import Image from "next/image";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PlayIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
const statusColors = {
|
||||
online: "bg-green-500",
|
||||
idle: "bg-yellow-500",
|
||||
dnd: "bg-red-500",
|
||||
offline: "bg-zinc-500",
|
||||
};
|
||||
|
||||
const DiscordStatus = (): ReactElement => {
|
||||
const userBadges = {
|
||||
// Nitro
|
||||
"https://cdn.discordapp.com/badge-icons/2ba85e8026a8614b640c2837bcdfe21b.png":
|
||||
(dcdnUser: DCDNUser) => dcdnUser.premiumType,
|
||||
|
||||
// Early Supporter
|
||||
"https://cdn.discordapp.com/badge-icons/7060786766c9c840eb3019e725d2b358.png":
|
||||
(dcdnUser: DCDNUser) => (dcdnUser.flags & (1 << 9)) === 1 << 9,
|
||||
|
||||
// Active Developer
|
||||
"https://cdn.discordapp.com/badge-icons/6bdc42827a38498929a4920da12695d9.png":
|
||||
(dcdnUser: DCDNUser) => (dcdnUser.flags & (1 << 22)) === 1 << 22,
|
||||
};
|
||||
|
||||
const DiscordStatus = (): ReactElement | undefined => {
|
||||
// Data from Lanyard
|
||||
const discordData: Data | undefined = useLanyardWS("504147739131641857");
|
||||
const discordUser: DiscordUser | undefined = discordData?.discord_user;
|
||||
|
||||
// Some data isn't provided by Lanyard, use DCDN for more
|
||||
const [dcdnUser, setDCDNUser] = useState<DCDNUser | undefined>(undefined);
|
||||
|
||||
// When the data changes, update the DCDN user
|
||||
useEffect(() => {
|
||||
if (!discordUser) {
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.get(`https://dcdn.dstn.to/profile/${discordUser.id}`)
|
||||
.then((res) =>
|
||||
setDCDNUser({
|
||||
premiumType: res.data.premium_type,
|
||||
...res.data.user,
|
||||
})
|
||||
);
|
||||
}, [discordData]);
|
||||
|
||||
// Missing required data
|
||||
if (!discordData || !discordUser || !dcdnUser) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="absolute left-4 bottom-4">
|
||||
{discordData && discordUser && (
|
||||
<BlurFade
|
||||
className="flex select-none pointer-events-none"
|
||||
delay={1.65}
|
||||
inView
|
||||
>
|
||||
{/* Avatar & Status */}
|
||||
<div className="relative z-50">
|
||||
<Image
|
||||
className="scale-110 rounded-full border"
|
||||
src={`https://cdn.discordapp.com/avatars/${discordUser.id}/${discordUser.avatar}.webp`}
|
||||
alt={`${discordUser.username}'s Discord Avatar`}
|
||||
width={60}
|
||||
height={60}
|
||||
/>
|
||||
{discordData.discord_status !== "offline" && (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute bottom-1.5 right-1 w-2.5 h-2.5 bg-red-500 rounded-full",
|
||||
statusColors[discordData.discord_status]
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<div className="flex flex-col bg-zinc-900 rounded-xl">
|
||||
<BannerAvatar
|
||||
discordData={discordData}
|
||||
discordUser={discordUser}
|
||||
dcdnUser={dcdnUser}
|
||||
/>
|
||||
|
||||
{/* Username & Basic Activity */}
|
||||
<div className="-translate-x-7 p-2 pl-10 pr-6 flex flex-col justify-center bg-white/55 dark:bg-zinc-800/45 border rounded-r-3xl">
|
||||
<div className="flex gap-1 items-center">
|
||||
<h1 className="text-lg font-bold">
|
||||
{discordUser.display_name}
|
||||
</h1>
|
||||
<h2 className="opacity-65">
|
||||
{discordUser.username}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{discordData.activities.length > 0 && (
|
||||
<BasicActivityDisplay
|
||||
activity={discordData.activities[0]}
|
||||
/>
|
||||
)}
|
||||
<div className="px-3.5 pt-1.5 py-2.5 flex flex-col">
|
||||
<div className="ml-[5.65rem] flex items-center">
|
||||
{dcdnUser.bio && <Bio dcdnUser={dcdnUser} />}
|
||||
<Badges dcdnUser={dcdnUser} />
|
||||
</div>
|
||||
</BlurFade>
|
||||
)}
|
||||
<div className="mt-3">
|
||||
<Username discordUser={discordUser} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BasicActivityDisplay = ({
|
||||
activity,
|
||||
const BannerAvatar = ({
|
||||
discordData,
|
||||
discordUser,
|
||||
dcdnUser,
|
||||
}: {
|
||||
activity: Activity;
|
||||
}): ReactElement => {
|
||||
const prefix =
|
||||
activity.type === 0
|
||||
? "Playing"
|
||||
: activity.type === 1
|
||||
? "Streaming"
|
||||
: "Listening to";
|
||||
|
||||
return (
|
||||
<div className="flex gap-1.5 items-center text-sm">
|
||||
<span className="opacity-80">{prefix}</span> {activity.name}{" "}
|
||||
<PlayIcon className="text-blue-500" width={16} height={16} />
|
||||
discordData: Data;
|
||||
discordUser: DiscordUser;
|
||||
dcdnUser: DCDNUser;
|
||||
}): ReactElement => (
|
||||
<div className="relative select-none">
|
||||
<Image
|
||||
className="rounded-t-xl"
|
||||
src={`https://cdn.discordapp.com/banners/${discordUser.id}/${dcdnUser.banner}.webp?size=1024`}
|
||||
alt={`${discordUser.username}'s Banner`}
|
||||
width={300}
|
||||
height={300}
|
||||
/>
|
||||
<div className="relative">
|
||||
<Image
|
||||
className="absolute left-2 -bottom-12 border-[5px] border-zinc-900 rounded-full"
|
||||
src={`https://cdn.discordapp.com/avatars/${discordUser.id}/${discordUser.avatar}.webp?size=1024`}
|
||||
alt={`${discordUser.username}'s Avatar`}
|
||||
width={96}
|
||||
height={96}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute left-[4.5rem] -bottom-12 w-7 h-7 border-[5px] border-zinc-900 rounded-full",
|
||||
statusColors[discordData.discord_status]
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
|
||||
const Bio = ({ dcdnUser }: { dcdnUser: DCDNUser }): ReactElement => (
|
||||
<div className="p-2 bg-zinc-950/75 text-sm rounded-xl select-none">
|
||||
{dcdnUser.bio}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Badges = ({ dcdnUser }: { dcdnUser: DCDNUser }): ReactElement => (
|
||||
<div className="ml-auto flex gap-1">
|
||||
{Object.entries(userBadges)
|
||||
.filter(([_, predicate]) => predicate(dcdnUser))
|
||||
.map(([badge], index) => (
|
||||
<Image
|
||||
key={index}
|
||||
src={badge}
|
||||
alt="Discord Profile Badge"
|
||||
width={22}
|
||||
height={22}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Username = ({
|
||||
discordUser,
|
||||
}: {
|
||||
discordUser: DiscordUser;
|
||||
}): ReactElement => (
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-lg font-bold leading-none">
|
||||
{discordUser.global_name}
|
||||
</h1>
|
||||
<h2 className="font-light opacity-70">{discordUser.username}</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default DiscordStatus;
|
||||
|
@ -74,7 +74,7 @@ const MyWork = (): ReactElement => {
|
||||
target="_blank"
|
||||
>
|
||||
<MagicCard
|
||||
className="w-[15rem] lg:w-[25rem] p-3.5 cursor-default opacity-95"
|
||||
className="w-[15rem] lg:w-[25rem] p-3.5 opacity-95"
|
||||
gradientColor={
|
||||
theme === "dark" ? "#262626" : "#D9D9D9"
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ const Skills = (): ReactElement => (
|
||||
{skillset.map((skill, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
className="cursor-default hover:opacity-75 transition-all transform-gpu"
|
||||
className="hover:opacity-75 transition-all transform-gpu"
|
||||
href={skill.link}
|
||||
target="_blank"
|
||||
>
|
||||
|
@ -33,7 +33,7 @@ const Navbar = (): ReactElement => (
|
||||
|
||||
const Branding = (): ReactElement => (
|
||||
<Link
|
||||
className="flex gap-3 items-center hover:opacity-75 cursor-default select-none transition-all transform-gpu"
|
||||
className="flex gap-3 items-center hover:opacity-75 select-none transition-all transform-gpu"
|
||||
href="/"
|
||||
>
|
||||
<Image
|
||||
@ -92,7 +92,7 @@ const UsefulLinksContent = (): ReactElement => (
|
||||
<div className="w-[15.5rem] p-3 flex flex-wrap gap-5">
|
||||
{/* Git */}
|
||||
<Link href="https://git.rainnny.club" target="_blank">
|
||||
<Button className="gap-3 cursor-default" variant="ghost">
|
||||
<Button className="gap-3" variant="ghost">
|
||||
<CodeBracketIcon width={24} height={24} />
|
||||
<span>Gitea</span>
|
||||
</Button>
|
||||
@ -100,7 +100,7 @@ const UsefulLinksContent = (): ReactElement => (
|
||||
|
||||
{/* Wiki */}
|
||||
<Link href="https://docs.rainnny.club" target="_blank">
|
||||
<Button className="gap-3 cursor-default" variant="ghost">
|
||||
<Button className="gap-3" variant="ghost">
|
||||
<BookOpenIcon width={24} height={24} />
|
||||
<span>Wiki</span>
|
||||
</Button>
|
||||
@ -108,7 +108,7 @@ const UsefulLinksContent = (): ReactElement => (
|
||||
|
||||
{/* Status Page */}
|
||||
<Link href="https://status.rainnny.club" target="_blank">
|
||||
<Button className="gap-3 cursor-default" variant="ghost">
|
||||
<Button className="gap-3" variant="ghost">
|
||||
<SignalIcon width={24} height={24} />
|
||||
<span>Status</span>
|
||||
</Button>
|
||||
|
@ -46,7 +46,7 @@ const Navigation = (): ReactElement => {
|
||||
<BlurFade key={index} delay={0.9 + 0.3 * index} inView>
|
||||
<Button
|
||||
className={cn(
|
||||
"px-3 sm:px-4 py-6 gap-2 shadow-sm bg-white/75 dark:bg-zinc-800/75 rounded-xl cursor-default hover:opacity-75 transition-all transform-gpu",
|
||||
"px-3 sm:px-4 py-6 gap-2 shadow-sm bg-white/75 dark:bg-zinc-800/75 rounded-xl hover:opacity-75 transition-all transform-gpu",
|
||||
active && "opacity-70"
|
||||
)}
|
||||
variant="ghost"
|
||||
|
@ -12,7 +12,7 @@ const ThemeSwitcher = (): ReactElement => {
|
||||
const isLight = theme === "light";
|
||||
return (
|
||||
<Button
|
||||
className="relative px-5 py-1.5 flex items-center cursor-default hover:opacity-85"
|
||||
className="relative px-5 py-1.5 flex items-center hover:opacity-85"
|
||||
variant="ghost"
|
||||
onClick={() => setTheme(isLight ? "dark" : "light")}
|
||||
>
|
||||
|
@ -41,7 +41,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium cursor-default transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||
);
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef<
|
||||
|
10
src/types/dcdn.ts
Normal file
10
src/types/dcdn.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* The user type from <a href="https://dcdn.dstn.to/profile/504147739131641857">DCDN</a>
|
||||
*/
|
||||
type DCDNUser = {
|
||||
flags: number;
|
||||
banner: string | undefined;
|
||||
bio: string | undefined;
|
||||
premiumType: number | undefined; // 1 = Nitro Classic, 2 = Nitro
|
||||
};
|
||||
export default DCDNUser;
|
Loading…
x
Reference in New Issue
Block a user