Move to Tether
This commit is contained in:
parent
e31841a923
commit
195ff7f7bd
@ -2,7 +2,7 @@ name: Deploy Site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
branches: [ "master" ]
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- LICENSE
|
||||
@ -12,8 +12,8 @@ jobs:
|
||||
docker:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["ubuntu-latest"]
|
||||
git-version: ["2.44.0"]
|
||||
arch: [ "ubuntu-latest" ]
|
||||
git-version: [ "2.44.0" ]
|
||||
runs-on: ${{ matrix.arch }}
|
||||
|
||||
# Steps to run
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
# RainnnyCLUB
|
||||
|
||||
My personal portfolio website hosted [here](https://rainnny.club)
|
@ -1,29 +1,29 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "cdn.rainnny.club",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "cdn.discordapp.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "i.scdn.co",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "bonfire.wtf",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "img.icons8.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
output: "standalone",
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "cdn.rainnny.club",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "cdn.discordapp.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "i.scdn.co",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "bonfire.wtf",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "img.icons8.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
export default nextConfig;
|
||||
|
79
package.json
79
package.json
@ -1,42 +1,41 @@
|
||||
{
|
||||
"name": "rainnny.club",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbo",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.1.5",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.0",
|
||||
"@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",
|
||||
"lucide-react": "^0.439.0",
|
||||
"moment": "^2.30.1",
|
||||
"next": "^14.2.7",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"sharp": "^0.33.5",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"use-lanyard": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.9"
|
||||
}
|
||||
"name": "rainnny.club",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbo",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.1.5",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.0",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.3.30",
|
||||
"lucide-react": "^0.439.0",
|
||||
"moment": "^2.30.1",
|
||||
"next": "^14.2.7",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"sharp": "^0.33.5",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"usetether": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.9"
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
/** @type {import("postcss-load-config").Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
@ -1,27 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { ReactElement, useEffect, useState } from "react";
|
||||
import {
|
||||
Activity,
|
||||
Data,
|
||||
DiscordUser,
|
||||
Spotify,
|
||||
useLanyardWS,
|
||||
} from "use-lanyard";
|
||||
import DCDNUser from "@/types/dcdn";
|
||||
import axios from "axios";
|
||||
import { DiscordUser, SpotifyActivity, useTetherWS } from "usetether";
|
||||
import Image from "next/image";
|
||||
import { cn, truncateText } from "@/lib/utils";
|
||||
import moment from "moment";
|
||||
import { PuzzlePieceIcon } from "@heroicons/react/24/outline";
|
||||
import SimpleTooltip from "@/components/ui/simple-tooltip";
|
||||
import Link from "next/link";
|
||||
import moment from "moment";
|
||||
|
||||
const statusColors = {
|
||||
online: "bg-green-500",
|
||||
idle: "bg-yellow-500",
|
||||
dnd: "bg-red-500",
|
||||
offline: "bg-zinc-500",
|
||||
ONLINE: "bg-green-500",
|
||||
IDLE: "bg-yellow-500",
|
||||
DO_NOT_DISTURB: "bg-red-500",
|
||||
OFFLINE: "bg-zinc-500",
|
||||
};
|
||||
|
||||
const userBadges = {
|
||||
@ -29,86 +20,51 @@ const userBadges = {
|
||||
"https://cdn.discordapp.com/badge-icons/2ba85e8026a8614b640c2837bcdfe21b.png":
|
||||
{
|
||||
name: "Nitro Subscriber",
|
||||
predicate: (dcdnUser: DCDNUser) => dcdnUser.premiumType,
|
||||
predicate: (discordUser: DiscordUser) => true, // TODO: Add Nitro predicate
|
||||
},
|
||||
|
||||
// Early Supporter
|
||||
"https://cdn.discordapp.com/badge-icons/7060786766c9c840eb3019e725d2b358.png":
|
||||
{
|
||||
name: "Early Supporter",
|
||||
predicate: (dcdnUser: DCDNUser) =>
|
||||
(dcdnUser.flags & (1 << 9)) === 1 << 9,
|
||||
predicate: (discordUser: DiscordUser) =>
|
||||
discordUser.flags.list.includes("EARLY_SUPPORTER"),
|
||||
},
|
||||
|
||||
// Active Developer
|
||||
"https://cdn.discordapp.com/badge-icons/6bdc42827a38498929a4920da12695d9.png":
|
||||
{
|
||||
name: "Active Developer",
|
||||
predicate: (dcdnUser: DCDNUser) =>
|
||||
(dcdnUser.flags & (1 << 22)) === 1 << 22,
|
||||
predicate: (discordUser: DiscordUser) =>
|
||||
discordUser.flags.list.includes("ACTIVE_DEVELOPER"),
|
||||
},
|
||||
};
|
||||
|
||||
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) {
|
||||
const discordUser: DiscordUser | undefined =
|
||||
useTetherWS("504147739131641857");
|
||||
if (!discordUser) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-center select-none">
|
||||
<div className="flex flex-col bg-zinc-300 dark:bg-zinc-900 rounded-xl">
|
||||
<BannerAvatar
|
||||
discordData={discordData}
|
||||
discordUser={discordUser}
|
||||
dcdnUser={dcdnUser}
|
||||
/>
|
||||
<BannerAvatar user={discordUser} />
|
||||
|
||||
<div className="px-3 pt-1.5 py-2.5 flex flex-col">
|
||||
<div className="ml-[5.65rem] flex items-start">
|
||||
{dcdnUser.bio && <Bio bio={dcdnUser.bio} />}
|
||||
<Badges dcdnUser={dcdnUser} />
|
||||
<Bio bio="7th ward" /> {/* TODO: Add bio */}
|
||||
<Badges discordUser={discordUser} />
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Username discordUser={discordUser} />
|
||||
|
||||
{/* Activity */}
|
||||
{discordData.activities.length > 0 && (
|
||||
{discordUser.spotify && (
|
||||
<div className="mt-2 p-2 bg-zinc-100 dark:bg-zinc-950/65 rounded-lg">
|
||||
{discordData.activities[0].name !==
|
||||
"Spotify" ? (
|
||||
<GameActivity
|
||||
activity={discordData.activities[0]}
|
||||
/>
|
||||
) : (
|
||||
discordData.spotify && (
|
||||
<SpotifyActivity
|
||||
spotify={discordData.spotify}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<SpotifyActivityContent
|
||||
spotify={discordUser.spotify}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -118,35 +74,29 @@ const DiscordStatus = (): ReactElement | undefined => {
|
||||
);
|
||||
};
|
||||
|
||||
const BannerAvatar = ({
|
||||
discordData,
|
||||
discordUser,
|
||||
dcdnUser,
|
||||
}: {
|
||||
discordData: Data;
|
||||
discordUser: DiscordUser;
|
||||
dcdnUser: DCDNUser;
|
||||
}): ReactElement => (
|
||||
const BannerAvatar = ({ user }: { user: DiscordUser }): ReactElement => (
|
||||
<div className="relative pointer-events-none">
|
||||
<Image
|
||||
className="border-2 border-zinc-300 dark:border-zinc-900 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}
|
||||
/>
|
||||
{user.banner && (
|
||||
<Image
|
||||
className="border-2 border-zinc-300 dark:border-zinc-900 rounded-t-xl"
|
||||
src={user.banner.url}
|
||||
alt={`${user.username}'s Banner`}
|
||||
width={300}
|
||||
height={300}
|
||||
/>
|
||||
)}
|
||||
<div className="relative">
|
||||
<Image
|
||||
className="absolute left-2 -bottom-12 border-[5px] border-zinc-300 dark:border-zinc-900 rounded-full"
|
||||
src={`https://cdn.discordapp.com/avatars/${discordUser.id}/${discordUser.avatar}.webp?size=1024`}
|
||||
alt={`${discordUser.username}'s Avatar`}
|
||||
src={user.avatar.url}
|
||||
alt={`${user.username}'s Avatar`}
|
||||
width={96}
|
||||
height={96}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute left-[4.5rem] -bottom-12 w-7 h-7 border-[5px] border-zinc-300 dark:border-zinc-900 rounded-full",
|
||||
statusColors[discordData.discord_status]
|
||||
statusColors[user.onlineStatus]
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@ -161,10 +111,14 @@ const Bio = ({ bio }: { bio: string }): ReactElement => (
|
||||
</SimpleTooltip>
|
||||
);
|
||||
|
||||
const Badges = ({ dcdnUser }: { dcdnUser: DCDNUser }): ReactElement => (
|
||||
const Badges = ({
|
||||
discordUser,
|
||||
}: {
|
||||
discordUser: DiscordUser;
|
||||
}): ReactElement => (
|
||||
<div className="ml-auto flex gap-1">
|
||||
{Object.entries(userBadges)
|
||||
.filter(([_, badge]) => badge.predicate(dcdnUser))
|
||||
.filter(([_, badge]) => badge.predicate(discordUser))
|
||||
.map(([badgeIcon, badge], index) => (
|
||||
<SimpleTooltip key={index} content={badge.name}>
|
||||
<Image
|
||||
@ -186,17 +140,20 @@ const Username = ({
|
||||
}): ReactElement => (
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-lg font-bold leading-none">
|
||||
{discordUser.global_name}
|
||||
{discordUser.displayName}
|
||||
</h1>
|
||||
<h2 className="font-light opacity-70">{discordUser.username}</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SpotifyActivity = ({ spotify }: { spotify: Spotify }): ReactElement => {
|
||||
const trackUrl: string = `https://open.spotify.com/track/${spotify.track_id}`;
|
||||
const SpotifyActivityContent = ({
|
||||
spotify,
|
||||
}: {
|
||||
spotify: SpotifyActivity;
|
||||
}): ReactElement => {
|
||||
const trackArtist: string = spotify.artist.replace(";", ",");
|
||||
const startTimestamp: number = spotify.timestamps.start;
|
||||
const endTimestamp: number = spotify.timestamps.end;
|
||||
const startTimestamp: number = spotify.started;
|
||||
const endTimestamp: number = spotify.ends;
|
||||
const [trackProgress, setTrackProgress] = useState<string | undefined>();
|
||||
const trackDuration: number = endTimestamp - startTimestamp;
|
||||
|
||||
@ -216,10 +173,10 @@ const SpotifyActivity = ({ spotify }: { spotify: Spotify }): ReactElement => {
|
||||
<div className="flex items-start">
|
||||
<div className="flex gap-2 items-center">
|
||||
{/* Artwork */}
|
||||
<Link href={trackUrl} target="_blank">
|
||||
<Link href={spotify.trackUrl} target="_blank">
|
||||
<Image
|
||||
className="rounded-lg"
|
||||
src={spotify.album_art_url as string}
|
||||
src={spotify.albumArtUrl}
|
||||
alt={`Track artwork of ${spotify.song} by ${spotify.artist}`}
|
||||
width={54}
|
||||
height={54}
|
||||
@ -228,7 +185,7 @@ const SpotifyActivity = ({ spotify }: { spotify: Spotify }): ReactElement => {
|
||||
|
||||
{/* Track Info */}
|
||||
<div className="flex flex-col text-sm">
|
||||
<Link href={trackUrl} target="_blank">
|
||||
<Link href={spotify.trackUrl} target="_blank">
|
||||
<SimpleTooltip content={spotify.song}>
|
||||
<h1 className="font-bold leading-none">
|
||||
{truncateText(spotify.song, 22)}
|
||||
@ -258,53 +215,53 @@ const SpotifyActivity = ({ spotify }: { spotify: Spotify }): ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
const GameActivity = ({ activity }: { activity: Activity }): ReactElement => {
|
||||
const startTimestamp: number = activity.timestamps?.start || Date.now();
|
||||
const [activityProgress, setActivityProgress] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
|
||||
// Update the activity progress every second
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setActivityProgress(
|
||||
moment(Date.now() - startTimestamp).format("h:m:ss")
|
||||
);
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [startTimestamp]);
|
||||
|
||||
return (
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex gap-2 items-center">
|
||||
{/* Artwork */}
|
||||
<Image
|
||||
className="rounded-lg pointer-events-none"
|
||||
src={`https://cdn.discordapp.com/app-assets/${activity.application_id}/${activity.assets?.large_image || activity.assets?.small_image}.png?size=1024`}
|
||||
alt={`Game artwork of ${activity.name}`}
|
||||
width={54}
|
||||
height={54}
|
||||
/>
|
||||
|
||||
{/* Activity Info */}
|
||||
<div className="flex flex-col text-sm">
|
||||
<h1 className="font-bold leading-none">{activity.name}</h1>
|
||||
<h2 className="font-light opacity-70">
|
||||
{activity.details}
|
||||
</h2>
|
||||
<p className="text-xs font-light opacity-70">
|
||||
{activity.state}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Activity Progress & Logo */}
|
||||
<div className="absolute top-0 right-0 flex gap-1 text-green-500/80">
|
||||
<p className="text-xs font-light">{activityProgress}</p>
|
||||
<PuzzlePieceIcon width={18} height={18} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// const GameActivity = ({ activity }: { activity: Activity }): ReactElement => {
|
||||
// const startTimestamp: number = activity.timestamps?.start || Date.now();
|
||||
// const [activityProgress, setActivityProgress] = useState<
|
||||
// string | undefined
|
||||
// >();
|
||||
//
|
||||
// // Update the activity progress every second
|
||||
// useEffect(() => {
|
||||
// const interval = setInterval(() => {
|
||||
// setActivityProgress(
|
||||
// moment(Date.now() - startTimestamp).format("h:m:ss")
|
||||
// );
|
||||
// }, 1000);
|
||||
// return () => clearInterval(interval);
|
||||
// }, [startTimestamp]);
|
||||
//
|
||||
// return (
|
||||
// <div className="relative flex items-start">
|
||||
// <div className="flex gap-2 items-center">
|
||||
// {/* Artwork */}
|
||||
// <Image
|
||||
// className="rounded-lg pointer-events-none"
|
||||
// src={`https://cdn.discordapp.com/app-assets/${activity.application_id}/${activity.assets?.large_image || activity.assets?.small_image}.png?size=1024`}
|
||||
// alt={`Game artwork of ${activity.name}`}
|
||||
// width={54}
|
||||
// height={54}
|
||||
// />
|
||||
//
|
||||
// {/* Activity Info */}
|
||||
// <div className="flex flex-col text-sm">
|
||||
// <h1 className="font-bold leading-none">{activity.name}</h1>
|
||||
// <h2 className="font-light opacity-70">
|
||||
// {activity.details}
|
||||
// </h2>
|
||||
// <p className="text-xs font-light opacity-70">
|
||||
// {activity.state}
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
//
|
||||
// {/* Activity Progress & Logo */}
|
||||
// <div className="absolute top-0 right-0 flex gap-1 text-green-500/80">
|
||||
// <p className="text-xs font-light">{activityProgress}</p>
|
||||
// <PuzzlePieceIcon width={18} height={18} />
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
export default DiscordStatus;
|
||||
|
@ -1,6 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@ -18,9 +22,18 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user