mirror of
https://github.com/NohamR/lanyard-profile-readme.git
synced 2026-05-26 13:16:42 +00:00
feat(front): use framer-motion and optimize a thing.
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/escape-html": "^1.0.4",
|
"@types/escape-html": "^1.0.4",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
|
"framer-motion": "^11.11.9",
|
||||||
"ioredis": "^5.4.1",
|
"ioredis": "^5.4.1",
|
||||||
"next": "14.2.15",
|
"next": "14.2.15",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
|||||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
|||||||
escape-html:
|
escape-html:
|
||||||
specifier: ^1.0.3
|
specifier: ^1.0.3
|
||||||
version: 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:
|
ioredis:
|
||||||
specifier: ^5.4.1
|
specifier: ^5.4.1
|
||||||
version: 5.4.1
|
version: 5.4.1
|
||||||
@@ -856,6 +859,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
||||||
engines: {node: '>=14'}
|
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:
|
fs.realpath@1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
|
||||||
@@ -2756,6 +2773,13 @@ snapshots:
|
|||||||
cross-spawn: 7.0.3
|
cross-spawn: 7.0.3
|
||||||
signal-exit: 4.1.0
|
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: {}
|
fs.realpath@1.0.0: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
|
|||||||
105
src/app/page.tsx
105
src/app/page.tsx
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Image from "next/image";
|
import React, { useState, useRef } from "react";
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
import { useSmoothCount } from "use-smooth-count";
|
import { useSmoothCount } from "use-smooth-count";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@@ -12,14 +13,17 @@ import Link from "next/link";
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [userId, setUserId] = useState<null | string>(null);
|
const [userId, setUserId] = useState<null | string>(null);
|
||||||
const [userError, setUserError] = useState<string | JSX.Element>();
|
const [userError, setUserError] = useState<string | JSX.Element>();
|
||||||
|
const [userData, setUserData] = useState<{ userId: string } | null>(null);
|
||||||
const [copyState, setCopyState] = useState("Copy");
|
const [copyState, setCopyState] = useState("Copy");
|
||||||
const [outputType, setOutputType] = useState<"markdown" | "html">(
|
const [outputType, setOutputType] = useState<"markdown" | "html">(
|
||||||
"markdown",
|
"markdown",
|
||||||
);
|
);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [onImageLoaded, setOnImageLoaded] = useState(false);
|
||||||
|
|
||||||
const userCount = useSWR("getUserCount", getUserCount);
|
const userCount = useSWR("getUserCount", getUserCount);
|
||||||
const countRef = useRef<HTMLDivElement | null>(null);
|
const countRef = useRef<HTMLDivElement | null>(null);
|
||||||
const counter = useSmoothCount({
|
useSmoothCount({
|
||||||
ref: countRef,
|
ref: countRef,
|
||||||
target: userCount.data || 0,
|
target: userCount.data || 0,
|
||||||
duration: 3,
|
duration: 3,
|
||||||
@@ -28,9 +32,9 @@ export default function Home() {
|
|||||||
|
|
||||||
const outputText = () => {
|
const outputText = () => {
|
||||||
if (outputType === "html") {
|
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 {
|
} else {
|
||||||
return `[](https://discord.com/users/${userId})`;
|
return `[](https://discord.com/users/${userData?.userId})`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const copy = () => {
|
const copy = () => {
|
||||||
@@ -40,17 +44,17 @@ export default function Home() {
|
|||||||
setTimeout(() => setCopyState("Copy"), 1500);
|
setTimeout(() => setCopyState("Copy"), 1500);
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeDiscordId = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const submitDiscordId = async () => {
|
||||||
e.preventDefault();
|
setIsLoading(true);
|
||||||
|
setOnImageLoaded(false);
|
||||||
|
setUserData(null);
|
||||||
setUserError(undefined);
|
setUserError(undefined);
|
||||||
setUserId(e.target.value);
|
|
||||||
if (e.target.value === "") return;
|
|
||||||
|
|
||||||
if (!isSnowflake(e.target.value))
|
if (!userId) return setUserError("Please enter a Discord ID");
|
||||||
return setUserError("Invalid Discord ID");
|
|
||||||
|
|
||||||
if ((await isUserMonitored(e.target.value)) === false)
|
if (!isSnowflake(userId)) return setUserError("Invalid Discord ID");
|
||||||
|
|
||||||
|
if ((await isUserMonitored(userId)) === false)
|
||||||
return setUserError(
|
return setUserError(
|
||||||
<>
|
<>
|
||||||
User is not being monitored by Lanyard, please join{" "}
|
User is not being monitored by Lanyard, please join{" "}
|
||||||
@@ -64,6 +68,9 @@ export default function Home() {
|
|||||||
and try again.
|
and try again.
|
||||||
</>,
|
</>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setUserData({ userId });
|
||||||
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -78,18 +85,48 @@ export default function Home() {
|
|||||||
GitHub Profile
|
GitHub Profile
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
|
<form
|
||||||
|
className="flex w-full gap-2"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
submitDiscordId();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
className="input"
|
className="input"
|
||||||
onChange={changeDiscordId}
|
onChange={(e) => setUserId(e.target.value)}
|
||||||
|
value={userId || ""}
|
||||||
placeholder="Enter your Discord ID"
|
placeholder="Enter your Discord ID"
|
||||||
/>
|
/>
|
||||||
{userError && (
|
<button className="action" type="submit">
|
||||||
<p className="mt-1 text-sm text-red-500">
|
{">>"}
|
||||||
|
</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}
|
* {userError}
|
||||||
</p>
|
</motion.p>
|
||||||
)}
|
<motion.div
|
||||||
{userId && (
|
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">
|
<div className="mb-1 mt-4 flex gap-1">
|
||||||
<button
|
<button
|
||||||
className={`action ${outputType === "markdown" ? "active" : ""}`}
|
className={`action ${outputType === "markdown" ? "active" : ""}`}
|
||||||
@@ -104,27 +141,33 @@ export default function Home() {
|
|||||||
HTML
|
HTML
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="output bg-black">
|
<div className="output bg-black">{outputText()}</div>
|
||||||
{outputText()}
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 flex gap-2">
|
<div className="mt-4 flex gap-2">
|
||||||
<button className="action" onClick={copy}>
|
<button className="action" onClick={copy}>
|
||||||
{copyState}
|
{copyState}
|
||||||
</button>
|
</button>
|
||||||
<button className="action">Option</button>
|
<button className="action">Option</button>
|
||||||
</div>
|
</div>
|
||||||
{!userError && (
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<img
|
<motion.img
|
||||||
src={`/api/${userId}`}
|
className={`${onImageLoaded ? "" : "animate-pulse rounded-md bg-[#3d3d43]"}`}
|
||||||
height={350}
|
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}
|
width={500}
|
||||||
alt="Your Lanyard banner"
|
alt="Your Lanyard banners"
|
||||||
|
onLoad={() => setOnImageLoaded(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</motion.div>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer className="stat">
|
<footer className="stat">
|
||||||
|
|||||||
Reference in New Issue
Block a user