mirror of
https://github.com/NohamR/lanyard-profile-readme.git
synced 2026-05-25 04:17:18 +00:00
feat(front): use framer-motion and optimize a thing.
This commit is contained in:
@@ -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
24
pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
163
src/app/page.tsx
163
src/app/page.tsx
@@ -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 `[](https://discord.com/users/${userId})`;
|
||||
return `[](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">
|
||||
|
||||
Reference in New Issue
Block a user