feat(front): use framer-motion and optimize a thing.

This commit is contained in:
Hexagonn
2024-10-21 09:12:32 +00:00
committed by GitHub
parent 4c0dc41aa8
commit 5bb65e575c
3 changed files with 128 additions and 60 deletions

View File

@@ -12,6 +12,7 @@
"dependencies": {
"@types/escape-html": "^1.0.4",
"escape-html": "^1.0.3",
"framer-motion": "^11.11.9",
"ioredis": "^5.4.1",
"next": "14.2.15",
"react": "^18",

24
pnpm-lock.yaml generated
View File

@@ -14,6 +14,9 @@ importers:
escape-html:
specifier: ^1.0.3
version: 1.0.3
framer-motion:
specifier: ^11.11.9
version: 11.11.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ioredis:
specifier: ^5.4.1
version: 5.4.1
@@ -856,6 +859,20 @@ packages:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
framer-motion@11.11.9:
resolution: {integrity: sha512-XpdZseuCrZehdHGuW22zZt3SF5g6AHJHJi7JwQIigOznW4Jg1n0oGPMJQheMaKLC+0rp5gxUKMRYI6ytd3q4RQ==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0
react-dom: ^18.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -2756,6 +2773,13 @@ snapshots:
cross-spawn: 7.0.3
signal-exit: 4.1.0
framer-motion@11.11.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
tslib: 2.8.0
optionalDependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
fs.realpath@1.0.0: {}
fsevents@2.3.3:

View File

@@ -1,6 +1,7 @@
"use client";
import Image from "next/image";
import React, { useState, useEffect, useRef } from "react";
import React, { useState, useRef } from "react";
import { motion } from "framer-motion";
import { useSmoothCount } from "use-smooth-count";
import useSWR from "swr";
@@ -12,14 +13,17 @@ import Link from "next/link";
export default function Home() {
const [userId, setUserId] = useState<null | string>(null);
const [userError, setUserError] = useState<string | JSX.Element>();
const [userData, setUserData] = useState<{ userId: string } | null>(null);
const [copyState, setCopyState] = useState("Copy");
const [outputType, setOutputType] = useState<"markdown" | "html">(
"markdown",
);
const [isLoading, setIsLoading] = useState(true);
const [onImageLoaded, setOnImageLoaded] = useState(false);
const userCount = useSWR("getUserCount", getUserCount);
const countRef = useRef<HTMLDivElement | null>(null);
const counter = useSmoothCount({
useSmoothCount({
ref: countRef,
target: userCount.data || 0,
duration: 3,
@@ -28,9 +32,9 @@ export default function Home() {
const outputText = () => {
if (outputType === "html") {
return `<a href="https://discord.com/users/${userId}"><img src="https://lanyard-profile-readme.vercel.app/api/${userId}" /></a>`;
return `<a href="https://discord.com/users/${userData?.userId}"><img src="${window.location.origin}/api/${userData?.userId}" /></a>`;
} else {
return `[![Discord Presence](https://lanyard-profile-readme.vercel.app/api/${userId})](https://discord.com/users/${userId})`;
return `[![Discord Presence](${window.location.origin}/api/${userData?.userId})](https://discord.com/users/${userData?.userId})`;
}
};
const copy = () => {
@@ -40,17 +44,17 @@ export default function Home() {
setTimeout(() => setCopyState("Copy"), 1500);
};
const changeDiscordId = async (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
const submitDiscordId = async () => {
setIsLoading(true);
setOnImageLoaded(false);
setUserData(null);
setUserError(undefined);
setUserId(e.target.value);
if (e.target.value === "") return;
if (!isSnowflake(e.target.value))
return setUserError("Invalid Discord ID");
if (!userId) return setUserError("Please enter a Discord ID");
if ((await isUserMonitored(e.target.value)) === false)
if (!isSnowflake(userId)) return setUserError("Invalid Discord ID");
if ((await isUserMonitored(userId)) === false)
return setUserError(
<>
User is not being monitored by Lanyard, please join{" "}
@@ -64,6 +68,9 @@ export default function Home() {
and try again.
</>,
);
setUserData({ userId });
setIsLoading(false);
};
return (
@@ -78,53 +85,89 @@ export default function Home() {
GitHub Profile
</p>
<br />
<input
className="input"
onChange={changeDiscordId}
placeholder="Enter your Discord ID"
/>
{userError && (
<p className="mt-1 text-sm text-red-500">
* {userError}
</p>
)}
{userId && (
<>
<div className="mb-1 mt-4 flex gap-1">
<button
className={`action ${outputType === "markdown" ? "active" : ""}`}
onClick={() => setOutputType("markdown")}
>
Markdown
</button>
<button
className={`action ${outputType === "html" ? "active" : ""}`}
onClick={() => setOutputType("html")}
>
HTML
</button>
</div>
<div className="output bg-black">
{outputText()}
</div>
<div className="mt-4 flex gap-2">
<button className="action" onClick={copy}>
{copyState}
</button>
<button className="action">Option</button>
</div>
{!userError && (
<div className="mt-2">
<img
src={`/api/${userId}`}
height={350}
width={500}
alt="Your Lanyard banner"
/>
</div>
)}
</>
)}
<form
className="flex w-full gap-2"
onSubmit={(e) => {
e.preventDefault();
submitDiscordId();
}}
>
<input
className="input"
onChange={(e) => setUserId(e.target.value)}
value={userId || ""}
placeholder="Enter your Discord ID"
/>
<button className="action" type="submit">
{">>"}
</button>
</form>
<motion.p
variants={{
open: { opacity: 1 },
closed: { opacity: 0 },
}}
initial="closed"
animate={userError ? "open" : "closed"}
className="mt-1 text-sm text-red-500"
>
* {userError}
</motion.p>
<motion.div
variants={{
open: {
opacity: 1,
},
closed: {
opacity: 0,
},
}}
initial="closed"
animate={!isLoading ? "open" : "closed"}
transition={{ duration: 0.5 }}
>
<div className="mb-1 mt-4 flex gap-1">
<button
className={`action ${outputType === "markdown" ? "active" : ""}`}
onClick={() => setOutputType("markdown")}
>
Markdown
</button>
<button
className={`action ${outputType === "html" ? "active" : ""}`}
onClick={() => setOutputType("html")}
>
HTML
</button>
</div>
<div className="output bg-black">{outputText()}</div>
<div className="mt-4 flex gap-2">
<button className="action" onClick={copy}>
{copyState}
</button>
<button className="action">Option</button>
</div>
<div className="mt-2">
<motion.img
className={`${onImageLoaded ? "" : "animate-pulse rounded-md bg-[#3d3d43]"}`}
initial={{
opacity: 0,
"aria-hidden": true,
}}
animate={{
opacity: onImageLoaded ? 1 : 0,
"aria-hidden": onImageLoaded ? false : true,
}}
transition={{ duration: 0.5 }}
src={`/api/${userData?.userId}`}
height={280}
width={500}
alt="Your Lanyard banners"
onLoad={() => setOnImageLoaded(true)}
/>
</div>
</motion.div>
</div>
</main>
<footer className="stat">