Introduce DataHandler
to remove duplicate code
It allows every `Website` (from any `Info`) to get data It tries to get updated data periodically If there's an error getting data, it tries to get data more often If `error -> success`, the website displays the data, not the error If `success -> error`, the website displays the old data, not the error I find this system to be pretty elegant
This commit is contained in:
parent
1e71c12f60
commit
48051690a3
10 changed files with 127 additions and 189 deletions
|
@ -1,6 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import ButtonLink from "#parts/ButtonLink.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export type AnilistInfo = {
|
||||
title: string;
|
||||
|
@ -17,50 +18,39 @@ export type AnilistInfo = {
|
|||
} | undefined;
|
||||
|
||||
export default function Anilist() {
|
||||
const [anilist, setAnilist]: [AnilistInfo, React.Dispatch<React.SetStateAction<AnilistInfo>>] = useState();
|
||||
const {data, error, setError} = DataHandler<AnilistInfo>("/.netlify/functions/anilist", 60 * 30);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const getAnilist = async () => {
|
||||
setAnilist(await fetch("/.netlify/functions/anilist").then(async r => r.json()));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getAnilist().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (anilist) {
|
||||
if (data) {
|
||||
try {
|
||||
setElements([
|
||||
<div key={"data"} className="flex mb-4">
|
||||
<img className="m-auto w-16 h-22" alt="anime cover" src={anilist.cover} />
|
||||
<img className="m-auto w-16 h-22" alt="anime cover" src={data.cover} />
|
||||
<div className="m-auto pl-2">
|
||||
<p className="font-bold">{anilist.title}</p>
|
||||
<p className="mt-4">Started: <strong>{anilist.startDate}</strong></p>
|
||||
<p className="font-bold">{data.title}</p>
|
||||
<p className="mt-4">Started: <strong>{data.startDate}</strong></p>
|
||||
{
|
||||
anilist.episodes.watched >= anilist.episodes.total ?
|
||||
<p>Finished: <strong>{anilist.endDate}</strong></p> :
|
||||
<p>Ep. {anilist.episodes.watched}: <strong>{anilist.updateDate}</strong></p>
|
||||
data.episodes.watched >= data.episodes.total ?
|
||||
<p>Finished: <strong>{data.endDate}</strong></p> :
|
||||
<p>Ep. {data.episodes.watched}: <strong>{data.updateDate}</strong></p>
|
||||
}
|
||||
</div>
|
||||
</div>,
|
||||
<>
|
||||
{
|
||||
anilist.episodes.watched >= anilist.episodes.total ?
|
||||
<p>I gave it a <strong>{anilist.score}/10</strong></p> :
|
||||
<p><strong>{anilist.episodes.watched}/{anilist.episodes.total}</strong> episodes watched</p>
|
||||
data.episodes.watched >= data.episodes.total ?
|
||||
<p>I gave it a <strong>{data.score}/10</strong></p> :
|
||||
<p><strong>{data.episodes.watched}/{data.episodes.total}</strong> episodes watched</p>
|
||||
}
|
||||
</>,
|
||||
<ButtonLink key={"more"} link={anilist.url} text="Anime Link" />,
|
||||
<ButtonLink key={"more"} link={data.url} text="Anime Link" />,
|
||||
]);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [anilist]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import ButtonLink from "#parts/ButtonLink.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export interface GithubInfo {
|
||||
public?: {
|
||||
|
@ -13,37 +14,26 @@ export interface GithubInfo {
|
|||
}
|
||||
|
||||
export default function GitHub() {
|
||||
const [github, setGithub]: [GithubInfo, React.Dispatch<React.SetStateAction<GithubInfo>>] = useState({});
|
||||
const {data, error, setError} = DataHandler<GithubInfo>("/.netlify/functions/github", 60 * 20);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const getGithub = async () => {
|
||||
setGithub(await fetch("/.netlify/functions/github").then(async r => r.json()));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getGithub().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (github.private ?? github.public) {
|
||||
if (data && (data.private ?? data.public)) {
|
||||
try {
|
||||
const elms: React.JSX.Element[] = [];
|
||||
|
||||
if (github.private) {
|
||||
if (data.private) {
|
||||
elms.push(
|
||||
<p key={"github-date-private"} className={github.public ? "mb-2" : ""}>Latest <strong>private</strong> push: <strong>{github.private.date}</strong></p>,
|
||||
<p key={"github-date-private"} className={data.public ? "mb-2" : ""}>Latest <strong>private</strong> push: <strong>{data.private.date}</strong></p>,
|
||||
);
|
||||
}
|
||||
|
||||
if (github.public) {
|
||||
if (data.public) {
|
||||
elms.push(
|
||||
<p key={"github-date-public"}>Latest <strong>public</strong> push: <strong>{github.public.date} on {github.public.repo}</strong></p>,
|
||||
<p key={"github-date-public"}>Latest <strong>public</strong> push: <strong>{data.public.date} on {data.public.repo}</strong></p>,
|
||||
);
|
||||
elms.push(
|
||||
<ButtonLink key={"more"} link={`https://github.com/${github.public.repo}`} text="Repo Link" />,
|
||||
<ButtonLink key={"more"} link={`https://github.com/${data.public.repo}`} text="Repo Link" />,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -52,7 +42,7 @@ export default function GitHub() {
|
|||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [github]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
|
|
|
@ -1,36 +1,26 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export type GitlabInfo = {
|
||||
date: string;
|
||||
} | undefined;
|
||||
|
||||
export default function GitLab() {
|
||||
const [gitlab, setGitlab]: [GitlabInfo, React.Dispatch<React.SetStateAction<GitlabInfo>>] = useState();
|
||||
const {data, error, setError} = DataHandler<GitlabInfo>("/.netlify/functions/gitlab", 60 * 20);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const getGitlab = async () => {
|
||||
setGitlab(await fetch("/.netlify/functions/gitlab").then(async r => r.json()));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getGitlab().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (gitlab) {
|
||||
if (data) {
|
||||
try {
|
||||
setElements([
|
||||
<p key={"gitlab-date"}>Latest push: <strong>{gitlab.date}</strong></p>,
|
||||
<p key={"gitlab-date"}>Latest push: <strong>{data.date}</strong></p>,
|
||||
]);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [gitlab]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
|
|
40
src/Infos/DataHandler.tsx
Normal file
40
src/Infos/DataHandler.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
|
||||
/** Takes care of getting data regularly */
|
||||
export default function DataHandler<T extends unknown | undefined>(url: string, updateEveryXSeconds: number) {
|
||||
const [data, setData]: [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] = useState();
|
||||
const [error, setError] = useState(false);
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
// Try to get and set data
|
||||
const updateData = async () => {
|
||||
try {
|
||||
setData(await fetch(url).then(async r => r.json()));
|
||||
setError(false);
|
||||
} catch {
|
||||
setError(true);
|
||||
} finally {
|
||||
setCount(count + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// After the number of requests done goes up, set a timeout for a new request
|
||||
useEffect(() => {
|
||||
// After a certain amount of time has passed, make a new request
|
||||
const interval = (error || !data ? 60 : updateEveryXSeconds) * 1000;
|
||||
const timeout = setTimeout(() => updateData(), interval);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [count]);
|
||||
|
||||
// Make the first request
|
||||
useEffect(() => {
|
||||
updateData();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data,
|
||||
setData,
|
||||
error,
|
||||
setError,
|
||||
};
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export type KitsuclubInfo = {
|
||||
id: string
|
||||
|
@ -11,43 +12,32 @@ export type KitsuclubInfo = {
|
|||
} | undefined;
|
||||
|
||||
export default function KitsuClub() {
|
||||
const [kitsuclub, setKitsuclub]: [KitsuclubInfo, React.Dispatch<React.SetStateAction<KitsuclubInfo>>] = useState();
|
||||
const {data, error, setError} = DataHandler<KitsuclubInfo>("/.netlify/functions/kitsuclub", 60 * 20);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const getKitsuclub = async () => {
|
||||
setKitsuclub(await fetch("/.netlify/functions/kitsuclub").then(async r => r.json()));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getKitsuclub().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (kitsuclub) {
|
||||
if (data) {
|
||||
try {
|
||||
const date = new Date(kitsuclub.date).toISOString();
|
||||
const date = new Date(data.date).toISOString();
|
||||
setElements([
|
||||
<div key={"kitsuclub-details"} className="text-left mb-2">
|
||||
<img key={"kitsuclub-avatar"} src={kitsuclub.avatar} alt="avatar" className="float-left rounded-lg w-12 mr-2"/>
|
||||
<strong key={"kitsuclub-username"} className="inline-flex">{...emojify(kitsuclub.username, kitsuclub.emojis)}</strong>
|
||||
<img key={"kitsuclub-avatar"} src={data.avatar} alt="avatar" className="float-left rounded-lg w-12 mr-2"/>
|
||||
<strong key={"kitsuclub-username"} className="inline-flex">{...emojify(data.username, data.emojis)}</strong>
|
||||
<br/>
|
||||
<strong key={"kitsuclub-date"} className="inline-flex text-sm">{date.substring(0, date.indexOf("T"))}</strong>
|
||||
</div>,
|
||||
<p key={"kitsuclub-text"} className="text-left">{...emojify(kitsuclub.text, kitsuclub.emojis)}</p>, // emojis that are only in the post aren't in the response yet :(
|
||||
<p key={"kitsuclub-text"} className="text-left">{...emojify(data.text, data.emojis)}</p>, // emojis that are only in the post aren't in the response yet :(
|
||||
]);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [kitsuclub]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
name="KitsuClub"
|
||||
link={`https://kitsunes.club/@${kitsuclub?.id ?? "taevas"}`}
|
||||
link={`https://kitsunes.club/@${data?.id ?? "taevas"}`}
|
||||
elements={elements}
|
||||
error={error}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import ButtonLink from "#parts/ButtonLink.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export type HacktheboxInfo = {
|
||||
id: string;
|
||||
|
@ -13,46 +14,34 @@ export type HacktheboxInfo = {
|
|||
} | undefined;
|
||||
|
||||
export default function Hackthebox() {
|
||||
const [hackthebox, setHackthebox]: [HacktheboxInfo, React.Dispatch<React.SetStateAction<HacktheboxInfo>>] = useState();
|
||||
const {data, error, setError} = DataHandler<HacktheboxInfo>("/.netlify/functions/hackthebox", 60 * 60);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const getHackthebox = async () => {
|
||||
setHackthebox(await fetch("/.netlify/functions/hackthebox").then(async r => r.json()));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getHackthebox().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (hackthebox) {
|
||||
if (data) {
|
||||
try {
|
||||
setElements([
|
||||
<div key={"data"} className="flex">
|
||||
{
|
||||
hackthebox.type === "user" ?
|
||||
<img className="m-auto h-16 w-16" alt="machine thumbnail" src={hackthebox.machine_avatar}/> :
|
||||
<a className="m-auto h-16 w-16" href={`https://www.hackthebox.com/achievement/machine/1063999/${hackthebox.id}`} target="_blank" rel="noreferrer">
|
||||
<img alt="machine thumbnail" src={hackthebox.machine_avatar}/>
|
||||
data.type === "user" ?
|
||||
<img className="m-auto h-16 w-16" alt="machine thumbnail" src={data.machine_avatar}/> :
|
||||
<a className="m-auto h-16 w-16" href={`https://www.hackthebox.com/achievement/machine/1063999/${data.id}`} target="_blank" rel="noreferrer">
|
||||
<img alt="machine thumbnail" src={data.machine_avatar}/>
|
||||
</a>
|
||||
}
|
||||
<div className="m-auto pl-4">
|
||||
<p className="font-bold">{hackthebox.name}</p>
|
||||
<p>({hackthebox.type})</p>
|
||||
<p className="font-bold">{data.name}</p>
|
||||
<p>({data.type})</p>
|
||||
</div>
|
||||
</div>,
|
||||
<p key={"date"} className="mt-2 font-bold">{hackthebox.date}</p>,
|
||||
<ButtonLink key={"more"} link={`https://app.hackthebox.com/machines/${hackthebox.name}`} text="Machine Link" />,
|
||||
<p key={"date"} className="mt-2 font-bold">{data.date}</p>,
|
||||
<ButtonLink key={"more"} link={`https://app.hackthebox.com/machines/${data.name}`} text="Machine Link" />,
|
||||
]);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [hackthebox]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import { WKLevelProgression, WKReset } from "@bachmacintosh/wanikani-api-types";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export type WanikaniInfo = {
|
||||
progression: {
|
||||
|
@ -36,27 +37,16 @@ function Button(item: Item) {
|
|||
}
|
||||
|
||||
export default function Wanikani() {
|
||||
const [wanikani, setWanikani]: [WanikaniInfo, React.Dispatch<React.SetStateAction<WanikaniInfo>>] = useState();
|
||||
const {data, error, setError} = DataHandler<WanikaniInfo>("/.netlify/functions/wanikani", 60 * 60);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const getWanikani = async () => {
|
||||
setWanikani(await fetch("/.netlify/functions/wanikani").then(async r => r.json()));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getWanikani().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (wanikani) {
|
||||
if (data) {
|
||||
try {
|
||||
const now = new Date();
|
||||
|
||||
let level = <></>;
|
||||
const unlockedLevels = wanikani.progression.data.filter(d => typeof d.data.unlocked_at === "string");
|
||||
const unlockedLevels = data.progression.data.filter(d => typeof d.data.unlocked_at === "string");
|
||||
if (unlockedLevels.length) {
|
||||
const arr = unlockedLevels.sort((a, b) => new Date(b.data.unlocked_at!).getTime() - new Date(a.data.unlocked_at!).getTime());
|
||||
level = <p className="mb-4"><b>Level {arr[0].data.level}</b> reached!<br/>
|
||||
|
@ -64,9 +54,9 @@ export default function Wanikani() {
|
|||
}
|
||||
|
||||
let resets = <></>;
|
||||
if (wanikani.resets.length) {
|
||||
if (data.resets.length) {
|
||||
const allResets: React.JSX.Element[] = [];
|
||||
for (const dataWrapper of wanikani.resets) {
|
||||
for (const dataWrapper of data.resets) {
|
||||
const data = dataWrapper.data;
|
||||
allResets.push(<p><b>{`${new Date(data.created_at).toISOString().substring(0, 10)}`}</b>{`: Reset my progress from level ${data.original_level} to level ${data.target_level}`}</p>);
|
||||
}
|
||||
|
@ -75,7 +65,7 @@ export default function Wanikani() {
|
|||
}
|
||||
|
||||
const lessons: React.JSX.Element[] = [];
|
||||
const filteredLessons = wanikani.lessons.filter(lesson => new Date(lesson.available_at) < now);
|
||||
const filteredLessons = data.lessons.filter(lesson => new Date(lesson.available_at) < now);
|
||||
for (const lesson of filteredLessons) {
|
||||
lessons.push(Button(lesson));
|
||||
}
|
||||
|
@ -85,7 +75,7 @@ export default function Wanikani() {
|
|||
</div> : <p>No lesson available for now!</p>;
|
||||
|
||||
const reviews: React.JSX.Element[] = [];
|
||||
const filteredReviews = wanikani.reviews.filter(review => new Date(review.available_at) < now);
|
||||
const filteredReviews = data.reviews.filter(review => new Date(review.available_at) < now);
|
||||
for (const review of filteredReviews) {
|
||||
reviews.push(Button(review));
|
||||
}
|
||||
|
@ -95,9 +85,9 @@ export default function Wanikani() {
|
|||
</div> : <p>No review available for now!</p>;
|
||||
|
||||
let whenNextToReview = <></>;
|
||||
if (wanikani.moreThingsToReviewAt) {
|
||||
if (data.moreThingsToReviewAt) {
|
||||
const rtf = new Intl.RelativeTimeFormat("en", {style: "long", numeric: "always"});
|
||||
const timeDifference = new Date(Math.abs(new Date(wanikani.moreThingsToReviewAt).getTime() - now.getTime()));
|
||||
const timeDifference = new Date(Math.abs(new Date(data.moreThingsToReviewAt).getTime() - now.getTime()));
|
||||
const howManyHours = (timeDifference.getUTCHours() + 1) + ((24 * (timeDifference.getUTCDate() - 1)) * (timeDifference.getUTCMonth() + 1));
|
||||
whenNextToReview = <p className="mt-2">{`There will be more stuff to review ${rtf.format(howManyHours, "hour")}!`}</p>;
|
||||
}
|
||||
|
@ -115,7 +105,7 @@ export default function Wanikani() {
|
|||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [wanikani]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, {useState, useEffect} from "react";
|
|||
import {format} from "timeago.js";
|
||||
import Website from "../Website.js";
|
||||
import Link from "#parts/Link.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export type LastfmInfo = {
|
||||
artist: string;
|
||||
|
@ -14,56 +15,34 @@ export type LastfmInfo = {
|
|||
} | undefined;
|
||||
|
||||
export default function Lastfm() {
|
||||
const [lastfm, setLastfm]: [LastfmInfo, React.Dispatch<React.SetStateAction<LastfmInfo>>] = useState();
|
||||
const {data, error, setError} = DataHandler<LastfmInfo>("/.netlify/functions/lastfm", 60 * 2);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const getLastfm = async () => {
|
||||
setLastfm(await fetch("/.netlify/functions/lastfm").then(async r => r.json()));
|
||||
};
|
||||
|
||||
const updateLastFm = () => {
|
||||
getLastfm().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateLastFm();
|
||||
|
||||
const timer = setInterval(() => {
|
||||
updateLastFm();
|
||||
}, 2 * 60 * 1000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastfm) {
|
||||
if (data) {
|
||||
try {
|
||||
const date = new Date(Number(lastfm.date) * 1000);
|
||||
const dateParagraph = !lastfm.listening ? <p key="date" className="text-left mt-1">
|
||||
const date = new Date(Number(data.date) * 1000);
|
||||
const dateParagraph = !data.listening ? <p key="date" className="text-left mt-1">
|
||||
When: <span className="font-bold">{format(date)}</span>
|
||||
</p> : <></>;
|
||||
|
||||
setElements([
|
||||
<div key="data" className="flex leading-[18px]">
|
||||
<img alt="album thumbnail" src={lastfm.image} className="m-auto h-24 w-24"/>
|
||||
<img alt="album thumbnail" src={data.image} className="m-auto h-24 w-24"/>
|
||||
<div className="m-auto pl-4 w-fit">
|
||||
<p className="mb-2">{lastfm.listening ? "I'm currently listening to" : "Last listened to"}</p>
|
||||
<Link classes="mt-1 px-1 py-2 inline-block w-full font-bold leading-[18px] bg-white text-blue-800" link={lastfm.url} text={lastfm.name}/>
|
||||
<p className="mb-2">{data.listening ? "I'm currently listening to" : "Last listened to"}</p>
|
||||
<Link classes="mt-1 px-1 py-2 inline-block w-full font-bold leading-[18px] bg-white text-blue-800" link={data.url} text={data.name}/>
|
||||
</div>
|
||||
</div>,
|
||||
<p key="artist" className="text-left mt-4">Artist: <span className="font-bold">{lastfm.artist}</span></p>,
|
||||
<p key="album" className="text-left mt-1">Album: <span className="font-bold">{lastfm.album}</span></p>,
|
||||
<p key="artist" className="text-left mt-4">Artist: <span className="font-bold">{data.artist}</span></p>,
|
||||
<p key="album" className="text-left mt-1">Album: <span className="font-bold">{data.album}</span></p>,
|
||||
dateParagraph,
|
||||
]);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [lastfm]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import { Ruleset } from "osu-api-v2-js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export type OsuInfo = {
|
||||
country: string;
|
||||
|
@ -11,9 +12,8 @@ export type OsuInfo = {
|
|||
} | undefined;
|
||||
|
||||
export default function Osu(args: {ruleset: Ruleset}) {
|
||||
const [osu, setOsu]: [OsuInfo, React.Dispatch<React.SetStateAction<OsuInfo>>] = useState();
|
||||
const {data, error, setError} = DataHandler<OsuInfo>(`/.netlify/functions/osu?ruleset=${args.ruleset}`, 60 * 45);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const ruleset = Ruleset[args.ruleset];
|
||||
let name = ruleset;
|
||||
|
@ -23,25 +23,15 @@ export default function Osu(args: {ruleset: Ruleset}) {
|
|||
name = "catch";
|
||||
}
|
||||
|
||||
const getOsu = async () => {
|
||||
setOsu(await fetch(`/.netlify/functions/osu?ruleset=${args.ruleset}`).then(async r => r.json()));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getOsu().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (osu) {
|
||||
if (data) {
|
||||
try {
|
||||
setElements([
|
||||
<div key={`osu-${ruleset}`} className="flex">
|
||||
<img className="m-auto w-16 h-16" alt={`${ruleset} mode logo`} src={`/osu_rulesets/${ruleset}.png`}/>
|
||||
<div className="m-auto">
|
||||
<p>Global: <strong>#{osu.ranks.global}</strong></p>
|
||||
<p>{osu.country}: <strong>#{osu.ranks.country}</strong></p>
|
||||
<p>Global: <strong>#{data.ranks.global}</strong></p>
|
||||
<p>{data.country}: <strong>#{data.ranks.country}</strong></p>
|
||||
</div>
|
||||
</div>,
|
||||
]);
|
||||
|
@ -49,7 +39,7 @@ export default function Osu(args: {ruleset: Ruleset}) {
|
|||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [osu]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import ButtonLink from "#parts/ButtonLink.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
|
||||
export type SpeedruncomInfo = {
|
||||
place: number;
|
||||
|
@ -14,40 +15,29 @@ export type SpeedruncomInfo = {
|
|||
} | undefined;
|
||||
|
||||
export default function Speedruncom() {
|
||||
const [speedruncom, setSpeedruncom]: [SpeedruncomInfo, React.Dispatch<React.SetStateAction<SpeedruncomInfo>>] = useState();
|
||||
const {data, error, setError} = DataHandler<SpeedruncomInfo>("/.netlify/functions/speedruncom", 60 * 60);
|
||||
const [elements, setElements] = useState([] as React.JSX.Element[]);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const getSpeedruncom = async () => {
|
||||
setSpeedruncom(await fetch("/.netlify/functions/speedruncom").then(async r => r.json()));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSpeedruncom().catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (speedruncom) {
|
||||
if (data) {
|
||||
try {
|
||||
setElements([
|
||||
<div key={"data"} className="flex pb-2">
|
||||
<img alt="game thumbnail" src={speedruncom.thumbnail} className="h-32 m-auto" />
|
||||
<img alt="game thumbnail" src={data.thumbnail} className="h-32 m-auto" />
|
||||
<div className="m-auto pl-2">
|
||||
<p className="mb-2">Placed <strong>#{speedruncom.place}</strong> on:</p>
|
||||
<p className="font-bold">{speedruncom.game}</p>
|
||||
{speedruncom.details.map((d, i) => <p key={`detail-${i}`}>{d}</p>)}
|
||||
<p className="mb-2">Placed <strong>#{data.place}</strong> on:</p>
|
||||
<p className="font-bold">{data.game}</p>
|
||||
{data.details.map((d, i) => <p key={`detail-${i}`}>{d}</p>)}
|
||||
</div>
|
||||
</div>,
|
||||
<p key={"date"} className="mt-2 font-bold">{speedruncom.date}</p>,
|
||||
<ButtonLink key={"more"} link={speedruncom.link} text="Run Details" />,
|
||||
<p key={"date"} className="mt-2 font-bold">{data.date}</p>,
|
||||
<ButtonLink key={"more"} link={data.link} text="Run Details" />,
|
||||
]);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [speedruncom]);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Website
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue