Skip to content

Commit

Permalink
Merge pull request Shopify#48 from Shopify/fd-journal
Browse files Browse the repository at this point in the history
Add journal pages
  • Loading branch information
frandiox authored Sep 30, 2022
2 parents 7a28e7b + 147c2e6 commit 80861d9
Show file tree
Hide file tree
Showing 5 changed files with 340 additions and 14 deletions.
99 changes: 99 additions & 0 deletions app/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type {
ProductConnection,
ProductVariant,
SelectedOptionInput,
LanguageCode,
Blog,
PageConnection,
Shop,
} from "@shopify/hydrogen-ui-alpha/storefront-api-types";
Expand Down Expand Up @@ -852,3 +854,100 @@ export async function getSitemap(variables: {
variables,
});
}

const BLOG_QUERY = `#graphql
query Blog(
$language: LanguageCode
$blogHandle: String!
$pageBy: Int!
$cursor: String
) @inContext(language: $language) {
blog(handle: $blogHandle) {
articles(first: $pageBy, after: $cursor) {
edges {
node {
author: authorV2 {
name
}
contentHtml
handle
id
image {
id
altText
url
width
height
}
publishedAt
title
}
}
}
}
}
`;

export async function getBlog({
language,
paginationSize,
blogHandle,
}: {
language: LanguageCode;
blogHandle: string;
paginationSize: number;
}) {
const data = await getStorefrontData<{
blog: Blog;
}>({
query: BLOG_QUERY,
variables: {
language,
blogHandle,
pageBy: paginationSize,
},
});

return data.blog.articles;
}

const ARTICLE_QUERY = `#graphql
query ArticleDetails(
$language: LanguageCode
$blogHandle: String!
$articleHandle: String!
) @inContext(language: $language) {
blog(handle: $blogHandle) {
articleByHandle(handle: $articleHandle) {
title
contentHtml
publishedAt
author: authorV2 {
name
}
image {
id
altText
url
width
height
}
}
}
}
`;

export async function getArticle(variables: {
language: LanguageCode;
blogHandle: string;
articleHandle: string;
}) {
const data = await getStorefrontData<{
blog: Blog;
}>({
query: ARTICLE_QUERY,
variables,
});

return data.blog.articleByHandle;
}
122 changes: 122 additions & 0 deletions app/routes/journal/$journalHandle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
json,
type MetaFunction,
type SerializeFrom,
type LoaderArgs,
type LinksFunction,
} from "@remix-run/cloudflare";
import { useLoaderData } from "@remix-run/react";
import { Image } from "@shopify/hydrogen-ui-alpha";
import invariant from "tiny-invariant";
import { Button, PageHeader, Section, Text } from "~/components";
import { getArticle } from "~/data";
import { ATTR_LOADING_EAGER } from "~/lib/const";
import styles from "../../styles/custom-font.css";

const BLOG_HANDLE = "journal";

export async function loader({ request, params }: LoaderArgs) {
// TODO figure out localization
const languageCode = "EN";
const countryCode = "US";

invariant(params.journalHandle, "Missing journal handle");

const article = await getArticle({
blogHandle: BLOG_HANDLE,
articleHandle: params.journalHandle,
language: languageCode,
});

if (!article) {
throw new Response("Not found", { status: 404 });
}

const formattedDate = new Intl.DateTimeFormat(
`${languageCode}-${countryCode}`,
{
year: "numeric",
month: "long",
day: "numeric",
}
).format(new Date(article.publishedAt));

return json(
{ article, formattedDate },
{
headers: {
// TODO cacheLong()
},
}
);
}

export const meta: MetaFunction = ({
data,
}: {
data: SerializeFrom<typeof loader> | undefined;
}) => {
return {
title: data?.article?.seo?.title ?? "Article",
description: data?.article?.seo?.description,
};
};

export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};

export default function Article() {
const { article, formattedDate } = useLoaderData<typeof loader>();

const { title, image, contentHtml, author } = article;

return (
<>
<PageHeader heading={title} variant="blogPost">
<span>
{formattedDate} &middot; {author.name}
</span>
</PageHeader>
<Section as="article" padding="x">
{image && (
<Image
data={image}
className="w-full mx-auto mt-8 md:mt-16 max-w-7xl"
sizes="90vw"
widths={[400, 800, 1200]}
width="100px"
loading={ATTR_LOADING_EAGER}
loaderOptions={{
scale: 2,
crop: "center",
}}
/>
)}
<div
dangerouslySetInnerHTML={{ __html: contentHtml }}
className="article"
/>
</Section>
</>
);
}

export function CatchBoundary() {
const type = "article";
const heading = `We’ve lost this ${type}`;
const description = `We couldn’t find the ${type} you’re looking for. Try checking the URL or heading back to the home page.`;

return (
<>
<PageHeader heading={heading}>
<Text width="narrow" as="p">
{description}
</Text>
<Button width="auto" variant="secondary" to={"/"}>
Take me to the home page
</Button>
</PageHeader>
</>
);
}
106 changes: 106 additions & 0 deletions app/routes/journal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { json, type MetaFunction } from "@remix-run/cloudflare";
import { Link, useLoaderData } from "@remix-run/react";
import { flattenConnection, Image } from "@shopify/hydrogen-ui-alpha";
import type { Article } from "@shopify/hydrogen-ui-alpha/storefront-api-types";
import { Grid, PageHeader, Section } from "~/components";
import { getBlog } from "~/data";
import { getImageLoadingPriority, PAGINATION_SIZE } from "~/lib/const";

const BLOG_HANDLE = "Journal";

export const loader = async () => {
// TODO figure out localization
const languageCode = "EN";
const countryCode = "US";

const journals = await getBlog({
language: languageCode,
blogHandle: BLOG_HANDLE,
paginationSize: PAGINATION_SIZE,
});

const articles = flattenConnection(journals).map((article) => {
const { publishedAt } = article;
return {
...article,
publishedAt: new Intl.DateTimeFormat(`${languageCode}-${countryCode}`, {
year: "numeric",
month: "long",
day: "numeric",
}).format(new Date(publishedAt!)),
};
});

return json(
{ articles },
{
headers: {
// TODO cacheLong()
},
}
);
};

export const meta: MetaFunction = () => {
return {
title: "All Journals",
};
};

export default function Journals() {
const { articles } = useLoaderData<typeof loader>();

return (
<>
<PageHeader heading={BLOG_HANDLE} />
<Section>
<Grid as="ol" layout="blog" gap="blog">
{articles.map((article, i) => (
<ArticleCard
blogHandle={BLOG_HANDLE.toLowerCase()}
article={article as Article}
key={article.id}
loading={getImageLoadingPriority(i, 2)}
/>
))}
</Grid>
</Section>
</>
);
}

function ArticleCard({
blogHandle,
article,
loading,
}: {
blogHandle: string;
article: Article;
loading?: HTMLImageElement["loading"];
}) {
return (
<li key={article.id}>
<Link to={`/${blogHandle}/${article.handle}`}>
{article.image && (
<div className="card-image aspect-[3/2]">
<Image
alt={article.image.altText || article.title}
className="object-cover w-full"
data={article.image}
height={400}
loading={loading}
sizes="(min-width: 768px) 50vw, 100vw"
width={600}
loaderOptions={{
scale: 2,
crop: "center",
}}
/>
</div>
)}
<h2 className="mt-4 font-medium">{article.title}</h2>
<span className="block mt-1">{article.publishedAt}</span>
</Link>
</li>
);
}
13 changes: 13 additions & 0 deletions app/styles/custom-font.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@font-face {
font-family: 'IBMPlexSerif';
font-display: swap;
font-weight: 400;
src: url('/fonts/IBMPlexSerif-Text.woff2') format('woff2');
}
@font-face {
font-family: 'IBMPlexSerif';
font-display: swap;
font-weight: 400;
font-style: italic;
src: url('/fonts/IBMPlexSerif-TextItalic.woff2') format('woff2');
}
14 changes: 0 additions & 14 deletions styles/app.css
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
@font-face {
font-family: "IBMPlexSerif";
font-display: swap;
font-weight: 400;
src: url("/fonts/IBMPlexSerif-Text.woff2") format("woff2");
}
@font-face {
font-family: "IBMPlexSerif";
font-display: swap;
font-weight: 400;
font-style: italic;
src: url("/fonts/IBMPlexSerif-TextItalic.woff2") format("woff2");
}

@tailwind base;
@tailwind components;
@tailwind utilities;
Expand Down

0 comments on commit 80861d9

Please sign in to comment.