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-scroll-area": "^1.1.0",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
|
"axios": "^1.7.7",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^11.3.30",
|
"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>
|
<BlurFade className="my-7" delay={1.25} inView>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
</BlurFade>
|
</BlurFade>
|
||||||
<DiscordStatus />
|
<BlurFade className="my-7" delay={1.85} inView>
|
||||||
|
<DiscordStatus />
|
||||||
|
</BlurFade>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
@ -1,89 +1,152 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ReactElement } from "react";
|
import { ReactElement, useEffect, useState } from "react";
|
||||||
import { Activity, Data, DiscordUser, useLanyardWS } from "use-lanyard";
|
import { Data, DiscordUser, useLanyardWS } from "use-lanyard";
|
||||||
import BlurFade from "@/components/ui/blur-fade";
|
import DCDNUser from "@/types/dcdn";
|
||||||
|
import axios from "axios";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PlayIcon } from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
const statusColors = {
|
const statusColors = {
|
||||||
online: "bg-green-500",
|
online: "bg-green-500",
|
||||||
idle: "bg-yellow-500",
|
idle: "bg-yellow-500",
|
||||||
dnd: "bg-red-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 discordData: Data | undefined = useLanyardWS("504147739131641857");
|
||||||
const discordUser: DiscordUser | undefined = discordData?.discord_user;
|
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 (
|
return (
|
||||||
<div className="absolute left-4 bottom-4">
|
<div className="flex justify-center">
|
||||||
{discordData && discordUser && (
|
<div className="flex flex-col bg-zinc-900 rounded-xl">
|
||||||
<BlurFade
|
<BannerAvatar
|
||||||
className="flex select-none pointer-events-none"
|
discordData={discordData}
|
||||||
delay={1.65}
|
discordUser={discordUser}
|
||||||
inView
|
dcdnUser={dcdnUser}
|
||||||
>
|
/>
|
||||||
{/* 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>
|
|
||||||
|
|
||||||
{/* Username & Basic Activity */}
|
<div className="px-3.5 pt-1.5 py-2.5 flex flex-col">
|
||||||
<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="ml-[5.65rem] flex items-center">
|
||||||
<div className="flex gap-1 items-center">
|
{dcdnUser.bio && <Bio dcdnUser={dcdnUser} />}
|
||||||
<h1 className="text-lg font-bold">
|
<Badges dcdnUser={dcdnUser} />
|
||||||
{discordUser.display_name}
|
|
||||||
</h1>
|
|
||||||
<h2 className="opacity-65">
|
|
||||||
{discordUser.username}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{discordData.activities.length > 0 && (
|
|
||||||
<BasicActivityDisplay
|
|
||||||
activity={discordData.activities[0]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</BlurFade>
|
<div className="mt-3">
|
||||||
)}
|
<Username discordUser={discordUser} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const BasicActivityDisplay = ({
|
const BannerAvatar = ({
|
||||||
activity,
|
discordData,
|
||||||
|
discordUser,
|
||||||
|
dcdnUser,
|
||||||
}: {
|
}: {
|
||||||
activity: Activity;
|
discordData: Data;
|
||||||
}): ReactElement => {
|
discordUser: DiscordUser;
|
||||||
const prefix =
|
dcdnUser: DCDNUser;
|
||||||
activity.type === 0
|
}): ReactElement => (
|
||||||
? "Playing"
|
<div className="relative select-none">
|
||||||
: activity.type === 1
|
<Image
|
||||||
? "Streaming"
|
className="rounded-t-xl"
|
||||||
: "Listening to";
|
src={`https://cdn.discordapp.com/banners/${discordUser.id}/${dcdnUser.banner}.webp?size=1024`}
|
||||||
|
alt={`${discordUser.username}'s Banner`}
|
||||||
return (
|
width={300}
|
||||||
<div className="flex gap-1.5 items-center text-sm">
|
height={300}
|
||||||
<span className="opacity-80">{prefix}</span> {activity.name}{" "}
|
/>
|
||||||
<PlayIcon className="text-blue-500" width={16} height={16} />
|
<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>
|
||||||
);
|
</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;
|
export default DiscordStatus;
|
||||||
|
@ -74,7 +74,7 @@ const MyWork = (): ReactElement => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<MagicCard
|
<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={
|
gradientColor={
|
||||||
theme === "dark" ? "#262626" : "#D9D9D9"
|
theme === "dark" ? "#262626" : "#D9D9D9"
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ const Skills = (): ReactElement => (
|
|||||||
{skillset.map((skill, index) => (
|
{skillset.map((skill, index) => (
|
||||||
<Link
|
<Link
|
||||||
key={index}
|
key={index}
|
||||||
className="cursor-default hover:opacity-75 transition-all transform-gpu"
|
className="hover:opacity-75 transition-all transform-gpu"
|
||||||
href={skill.link}
|
href={skill.link}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
@ -33,7 +33,7 @@ const Navbar = (): ReactElement => (
|
|||||||
|
|
||||||
const Branding = (): ReactElement => (
|
const Branding = (): ReactElement => (
|
||||||
<Link
|
<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="/"
|
href="/"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@ -92,7 +92,7 @@ const UsefulLinksContent = (): ReactElement => (
|
|||||||
<div className="w-[15.5rem] p-3 flex flex-wrap gap-5">
|
<div className="w-[15.5rem] p-3 flex flex-wrap gap-5">
|
||||||
{/* Git */}
|
{/* Git */}
|
||||||
<Link href="https://git.rainnny.club" target="_blank">
|
<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} />
|
<CodeBracketIcon width={24} height={24} />
|
||||||
<span>Gitea</span>
|
<span>Gitea</span>
|
||||||
</Button>
|
</Button>
|
||||||
@ -100,7 +100,7 @@ const UsefulLinksContent = (): ReactElement => (
|
|||||||
|
|
||||||
{/* Wiki */}
|
{/* Wiki */}
|
||||||
<Link href="https://docs.rainnny.club" target="_blank">
|
<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} />
|
<BookOpenIcon width={24} height={24} />
|
||||||
<span>Wiki</span>
|
<span>Wiki</span>
|
||||||
</Button>
|
</Button>
|
||||||
@ -108,7 +108,7 @@ const UsefulLinksContent = (): ReactElement => (
|
|||||||
|
|
||||||
{/* Status Page */}
|
{/* Status Page */}
|
||||||
<Link href="https://status.rainnny.club" target="_blank">
|
<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} />
|
<SignalIcon width={24} height={24} />
|
||||||
<span>Status</span>
|
<span>Status</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -46,7 +46,7 @@ const Navigation = (): ReactElement => {
|
|||||||
<BlurFade key={index} delay={0.9 + 0.3 * index} inView>
|
<BlurFade key={index} delay={0.9 + 0.3 * index} inView>
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
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"
|
active && "opacity-70"
|
||||||
)}
|
)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
@ -12,7 +12,7 @@ const ThemeSwitcher = (): ReactElement => {
|
|||||||
const isLight = theme === "light";
|
const isLight = theme === "light";
|
||||||
return (
|
return (
|
||||||
<Button
|
<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"
|
variant="ghost"
|
||||||
onClick={() => setTheme(isLight ? "dark" : "light")}
|
onClick={() => setTheme(isLight ? "dark" : "light")}
|
||||||
>
|
>
|
||||||
|
@ -41,7 +41,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
|||||||
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
||||||
|
|
||||||
const navigationMenuTriggerStyle = cva(
|
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<
|
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