124 lines
3.4 KiB
TypeScript
124 lines
3.4 KiB
TypeScript
import { LoaderFunctionArgs, useLoaderData } from "react-router";
|
|
import { Helmet } from "@dr.pogodin/react-helmet";
|
|
import {
|
|
ICONS_PREPEND,
|
|
KEMONO_SITE,
|
|
SITE_NAME,
|
|
THUMBNAILS_PREPEND,
|
|
} from "#env/env-vars";
|
|
import { fetchFanboxProfileFancards } from "#api/profiles";
|
|
import { PageSkeleton } from "#components/pages";
|
|
import { validatePaysite } from "#entities/paysites";
|
|
import {
|
|
ProfileHeader,
|
|
Tabs,
|
|
IArtistDetails,
|
|
getArtist,
|
|
} from "#entities/profiles";
|
|
import { IFanCard } from "#entities/files";
|
|
|
|
interface IProps {
|
|
profile: IArtistDetails;
|
|
cards: IFanCard[];
|
|
}
|
|
|
|
export function FancardsPage() {
|
|
const { profile, cards } = useLoaderData() as IProps;
|
|
const title = "Fancards";
|
|
const heading = "Fancards";
|
|
|
|
return (
|
|
<PageSkeleton name="use" title={title} heading={heading}>
|
|
<Helmet>
|
|
<meta name="id" content={profile.id} />
|
|
<meta name="service" content={profile.service} />
|
|
<meta name="artist_name" content={profile.name} />
|
|
{/* <link rel="canonical" href="{{ g.canonical_url }}" /> */}
|
|
|
|
<meta property="og:title" content={title} />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:site_name" content={SITE_NAME} />
|
|
<meta
|
|
property="og:image"
|
|
content={`${ICONS_PREPEND ?? KEMONO_SITE}/icons/${profile.service}/${
|
|
profile.id
|
|
}`}
|
|
/>
|
|
{/* <meta property="og:url" content="{{ g.canonical_url }}" /> */}
|
|
</Helmet>
|
|
|
|
<ProfileHeader
|
|
service={profile.service}
|
|
profileID={profile.id}
|
|
profileName={profile.name}
|
|
/>
|
|
|
|
<div className="paginator" id="paginator-top">
|
|
<Tabs
|
|
currentPage="fancards"
|
|
service={profile.service}
|
|
artistID={profile.id}
|
|
/>
|
|
<div id="fancard-container">
|
|
{cards.length === 0 ? (
|
|
<div className="no-results">
|
|
<h2 className="site-section__subheading">
|
|
Nobody here but us chickens!
|
|
</h2>
|
|
<p className="subtitle">There are no uploads for your query.</p>
|
|
</div>
|
|
) : (
|
|
cards.map((card) => (
|
|
<article key={card.id} className="fancard__file">
|
|
<span title={card.added.slice(0, 7)}>
|
|
Added {card.added.slice(0, 7)}
|
|
</span>
|
|
<a
|
|
className="fileThumb"
|
|
href={`${card.server ?? ""}${card.path}`}
|
|
>
|
|
<img
|
|
src={`${THUMBNAILS_PREPEND}/thumbnail/${card.path}`}
|
|
loading="lazy"
|
|
data-src={`${THUMBNAILS_PREPEND}/thumbnail/${card.path}`}
|
|
/>
|
|
</a>
|
|
</article>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</PageSkeleton>
|
|
);
|
|
}
|
|
|
|
export async function loader({ params }: LoaderFunctionArgs): Promise<IProps> {
|
|
const service = params.service?.trim();
|
|
{
|
|
if (!service) {
|
|
throw new Error("Service name is required.");
|
|
}
|
|
|
|
validatePaysite(service);
|
|
|
|
if (service !== "fanbox") {
|
|
throw new Error(`Service must be "fanbox".`);
|
|
}
|
|
}
|
|
|
|
const profileID = params.creator_id?.trim();
|
|
{
|
|
if (!profileID) {
|
|
throw new Error("Artist ID is required.");
|
|
}
|
|
}
|
|
|
|
const profile = await getArtist(service, profileID);
|
|
const cards = await fetchFanboxProfileFancards(profileID);
|
|
|
|
return {
|
|
profile,
|
|
cards,
|
|
};
|
|
}
|