Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement author profiles #1366

Merged
merged 13 commits into from
Jan 26, 2025
4 changes: 3 additions & 1 deletion src/components/Card.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ export type Props = {
background?: 'neutral' | 'blue-purple' | 'blue-green' | 'red-pink';
isLink?: boolean;
className?: string;
labelledby?: string;
};

const { background = 'neutral', isLink = false, className } = Astro.props;
const { background = 'neutral', isLink = false, className, labelledby } = Astro.props;
---

<article
aria-labelledby={labelledby}
class:list={[
'panel relative flex min-w-0 flex-col',
background === 'blue-purple' && 'bg-blue-purple-gradient',
Expand Down
54 changes: 54 additions & 0 deletions src/pages/themes/_components/ThemeAuthorProfile.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
import { Image } from 'astro:assets';
import LeftArrowIcon from '~/icons/LeftArrowIcon.tsx'
import type { Author } from '../_types/index.ts';

interface Props {
author: Author;
}

const { author } = Astro.props;
---

<a
href="/themes/"
data-astro-prefetch
class="flex items-center gap-2 self-start text-sm text-astro-gray-200 mb-4"
>
<LeftArrowIcon aria-hidden="true" />
<span class="pr-2">Back to all themes</span>
</a>
<div class="flex lg:flex-col flex-wrap items-center gap-4">
<Image
inferSize
src={author.avatar ?? ''}
height={48}
width={48}
class="size-12 lg:w-full h-fit"
alt=""
/>
<h1 class="border-astro-gray-500 lg:hidden heading-3">{author.name}</h1>
<div class="flex flex-col gap-4 pt-2 lg:pt-8 w-full">
<div class="flex items-baseline justify-between">
<small class="code text-astro-gray-200">Joined</small>
{
new Date(author.createdAt).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
</div>
{
author.url && (
<hr class="border-astro-gray-500" />
<div class="flex items-baseline justify-between">
<small class="code text-astro-gray-200">Website</small>
<a class="link" href={author.url}>
{author.url.replace(/^https?:\/\/|\/$/g, '')}
</a>
</div>
)
}
</div>
</div>
11 changes: 6 additions & 5 deletions src/pages/themes/_components/ThemeCard.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { Image } from 'astro:assets';
import type { HTMLTag } from 'astro/types'
import Card from '~/components/Card.astro';
import CardBody from '~/components/CardBody.astro';
import CardFooter from '~/components/CardFooter.astro';
Expand All @@ -8,15 +9,16 @@ import Avatar from './Avatar.astro';
import Badge from './Badge.astro';

export type Props = {
as?: HTMLTag;
theme: ThemeAndAuthor;
};

const { theme } = Astro.props;
const { as: Tag = 'h3', theme } = Astro.props;

const image = theme.Theme.image;
---

<Card isLink>
<Card isLink labelledby={theme.Theme.slug}>
<!-- {!!theme.data.badge && <CardBadge>{theme.data.badge}</CardBadge>} -->
<a class="flex h-full flex-col" href={`/themes/details/${theme.Theme.slug}/`} data-astro-prefetch>
<Image
Expand All @@ -30,10 +32,9 @@ const image = theme.Theme.image;
/>
<CardBody>
<div>
<!-- <h3 class:list={["heading-5 mb-2 text-white", { official: theme.data.official }]}> -->
<h3 class:list={['heading-5 mb-2 text-white']}>
<Tag class:list={['heading-5 mb-2 text-white']} id={theme.Theme.slug}>
{theme.Theme.title}
</h3>
</Tag>
<p class="line-clamp-3">{theme.Theme.description}</p>
</div>
<CardFooter
Expand Down
2 changes: 1 addition & 1 deletion src/pages/themes/_components/ThemeStats.astro
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ if (theme.Theme.repoUrl && typeof theme.Theme.stars !== 'undefined') {
<hr class="border-astro-gray-500" />
<div class="flex items-center justify-between">
<small class="code text-astro-gray-200">Created by</small>
<a href={theme.Author.url} target="_blank" class="link">
<a href={`/themes/author/${theme.Author.id}`} class="link">
<Avatar theme={theme} />
</a>
</div>
Expand Down
82 changes: 82 additions & 0 deletions src/pages/themes/author/[id]/[...page].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
import CardGrid from '~/components/CardGrid.astro';
import CardGridGroup from '~/components/CardGridGroup.astro';
import Pagination from '~/components/Pagination.astro';
import SidebarLayout from '~/components/SidebarLayout.astro';
import SidebarPanel from '~/components/SidebarPanel.astro';
import { THEMES_API_URL } from '~/helpers/constants.ts';
import { paginate } from '~/helpers/paginate.js';
import MainLayout from '~/layouts/MainLayout.astro';
import SubmitTheme from '../../_components/SubmitTheme.astro';
import Profile from '../../_components/ThemeAuthorProfile.astro';
import ThemeCard from '../../_components/ThemeCard.astro';
import type { ThemeAndAuthor } from '../../_types/index.ts';

export const prerender = false;

// with '[...page]' rest routes we'll get undefined for the first page, default that to 1
// otherwise, try to parse the page number from the URL
const currentPage =
typeof Astro.params.page === 'undefined' ? 1 : Number.parseInt(Astro.params.page);

// invalid page number!
if (!currentPage || Number.isNaN(currentPage)) {
return Astro.redirect('/404', 404);
}

const res = await fetch(
`${THEMES_API_URL}/api/themes/author?id=${Astro.params.id}`,
);

if (res.status !== 200) return Astro.redirect('/404', 404);

const allThemes: ThemeAndAuthor[] = await res.json();

// take all matching themes and create a paginated list of results
const paginatedResults = paginate({
data: allThemes,
pageSize: 12,
currentPage,
route: `/themes/author/${Astro.params.id}/[...page]`,
});

const { page, allPages } = paginatedResults;

// make sure the requested page number is valid, if not redirect to the first page of results
if (allPages.length && !page) {
return Astro.redirect(allPages[0]);
}

const themes = page.data;
---

<MainLayout title="Themes" image={{ src: '/og/themes.jpg', alt: 'Explore the possibilities' }}>
<SidebarLayout>
<Fragment slot="sidebar">
<SidebarPanel>
<Profile author={themes[0].Author} />
</SidebarPanel>
<SubmitTheme class="hidden lg:block" />
</Fragment>

<div
class="flex flex-col items-center gap-8 pb-10 pt-8 sm:px-4 sm:pb-12 md:gap-10 md:pb-16 lg:gap-12 lg:px-8 lg:pb-20 lg:pt-10 xl:px-10"
>
<CardGridGroup>
<header class="w-full">
<h1 class="heading-3 max-lg:hidden">{themes[0].Author.name}</h1>
</header>
<CardGrid class="w-full max-w-screen-xl sm:grid-cols-2 xl:grid-cols-3">
{themes.map((theme) => <ThemeCard as="h2" theme={theme} />)}
</CardGrid>
</CardGridGroup>
{
allPages.length > 1 && (
<Pagination restRoute page={page} allPages={allPages} class="mx-auto" />
)
}
</div>

<SubmitTheme class="lg:hidden" />
</SidebarLayout>
</MainLayout>
Loading