diff options
| author | Armand Philippot <git@armandphilippot.com> | 2021-12-16 18:22:08 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2021-12-16 18:22:08 +0100 |
| commit | 0ef4f77954ba54b52b96c70a8bffe96804bd222d (patch) | |
| tree | 7fe68b05271625f72f750786e1a94b71942d2cd5 | |
| parent | a4f865224e0e395c7e29bb3b7279a5101b554d2c (diff) | |
chore: display featuredImage and meta on posts list
| -rw-r--r-- | next.config.js | 5 | ||||
| -rw-r--r-- | src/components/PostMeta/PostMeta.module.scss | 17 | ||||
| -rw-r--r-- | src/components/PostPreview/PostPreview.module.scss | 125 | ||||
| -rw-r--r-- | src/components/PostPreview/PostPreview.tsx | 61 | ||||
| -rw-r--r-- | src/components/PostsList/PostsList.module.scss | 6 | ||||
| -rw-r--r-- | src/components/PostsList/PostsList.tsx | 47 | ||||
| -rw-r--r-- | src/services/graphql/blog.ts | 3 | ||||
| -rw-r--r-- | src/services/graphql/post.ts | 6 |
8 files changed, 236 insertions, 34 deletions
diff --git a/next.config.js b/next.config.js index 2faf64a..8492d0d 100644 --- a/next.config.js +++ b/next.config.js @@ -1,12 +1,17 @@ const path = require('path'); const { locales } = require('./lingui.config'); +const backendDomain = process.env.BACKEND_URL.split('//')[1]; + /** @type {import('next').NextConfig} */ module.exports = { i18n: { locales, defaultLocale: 'fr', }, + images: { + domains: [backendDomain, 'secure.gravatar.com'], + }, poweredByHeader: false, reactStrictMode: true, sassOptions: { diff --git a/src/components/PostMeta/PostMeta.module.scss b/src/components/PostMeta/PostMeta.module.scss new file mode 100644 index 0000000..f997ffa --- /dev/null +++ b/src/components/PostMeta/PostMeta.module.scss @@ -0,0 +1,17 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; + +.wrapper { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + font-size: var(--font-size-sm); + + @include mix.media("screen") { + @include mix.dimensions("sm") { + display: flex; + flex-flow: column nowrap; + margin: 0; + composes: meta from "@components/PostPreview/PostPreview.module.scss"; + } + } +} diff --git a/src/components/PostPreview/PostPreview.module.scss b/src/components/PostPreview/PostPreview.module.scss new file mode 100644 index 0000000..d188b18 --- /dev/null +++ b/src/components/PostPreview/PostPreview.module.scss @@ -0,0 +1,125 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; + +.wrapper { + padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-md); + border: fun.convert-px(1) solid var(--color-border); + border-radius: fun.convert-px(3); + box-shadow: fun.convert-px(1) fun.convert-px(1) 0 0 var(--color-shadow), + fun.convert-px(1) fun.convert-px(1) fun.convert-px(3) 0 var(--color-shadow); + transition: all 0.2s ease-in-out 0s, border 0s; + + &:hover, + &:focus-within { + box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 + var(--color-shadow-light), + fun.convert-px(3) fun.convert-px(3) fun.convert-px(3) 0 + var(--color-shadow-light), + fun.convert-px(3) fun.convert-px(3) fun.convert-px(5) fun.convert-px(1) + var(--color-shadow-lighter); + transform: scale(1.01); + } +} + +.cover { + width: auto; + height: fun.convert-px(100); + margin: 0 auto var(--spacing-sm); + position: relative; + border: fun.convert-px(1) solid var(--color-border); +} + +.read-more { + display: block; + width: max-content; + margin: var(--spacing-md) auto var(--spacing-lg); + padding: var(--spacing-2xs) var(--spacing-sm); + background: var(--color-bg); + border: fun.convert-px(3) solid var(--color-primary); + border-radius: fun.convert-px(3); + box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) + var(--color-shadow-light), + fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) + var(--color-shadow-light), + fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) + var(--color-shadow-light); + color: var(--color-primary); + font-weight: 600; + text-decoration: none; + transition: all 0.3s ease-in-out 0s; + + .icon { + width: fun.convert-px(20); + margin-left: var(--spacing-2xs); + fill: var(--color-primary); + transition: all 0.25s ease-in-out 0s; + } + + &:hover, + &:focus { + text-decoration: underline var(--color-primary) fun.convert-px(2); + + .icon { + margin-left: var(--spacing-xs); + transform: scaleX(1.3); + } + } + + &:active { + color: var(--color-primary-dark); + text-decoration: none; + + .icon { + margin-left: 0; + transform: scaleX(0); + width: 0; + } + } +} + +@include mix.media("screen") { + @include mix.dimensions("xs") { + .read-more { + font-size: var(--font-size-sm); + } + } + + @include mix.dimensions("sm") { + .wrapper { + display: grid; + grid-template-columns: minmax(0, 3fr) minmax(0, 1fr); + grid-template-rows: repeat(3, max-content); + column-gap: var(--spacing-md); + } + + .cover { + grid-column: 2; + grid-row: 1; + margin: 0 0 var(--spacing-sm); + } + + .header { + grid-column: 1; + grid-row: 1; + } + + .meta { + grid-column: 2; + grid-row: 2 / 4; + } + + .body { + grid-column: 1; + grid-row: 2; + } + + .footer { + grid-column: 1; + grid-row: 3; + } + + .read-more { + margin: 0; + } + } +} diff --git a/src/components/PostPreview/PostPreview.tsx b/src/components/PostPreview/PostPreview.tsx new file mode 100644 index 0000000..8f3e0da --- /dev/null +++ b/src/components/PostPreview/PostPreview.tsx @@ -0,0 +1,61 @@ +import PostMeta from '@components/PostMeta/PostMeta'; +import { t } from '@lingui/macro'; +import { ArticlePreview } from '@ts/types/articles'; +import Link from 'next/link'; +import ArrowRightIcon from '@assets/images/icon-arrow-right.svg'; +import styles from './PostPreview.module.scss'; +import Image from 'next/image'; + +const PostPreview = ({ + post, + TitleTag, +}: { + post: ArticlePreview; + TitleTag: keyof JSX.IntrinsicElements; +}) => { + return ( + <article className={styles.wrapper}> + {post.featuredImage && ( + <div className={styles.cover}> + <Image + src={post.featuredImage.sourceUrl} + alt={post.featuredImage.altText} + layout="fill" + objectFit="contain" + /> + </div> + )} + <header className={styles.header}> + <TitleTag> + <Link href={`/article/${post.slug}`}> + <a>{post.title}</a> + </Link> + </TitleTag> + </header> + <div + className={styles.body} + dangerouslySetInnerHTML={{ __html: post.content }} + ></div> + <footer className={styles.footer}> + <Link href={post.slug}> + <a className={styles['read-more']}> + {t`Read more`} + <span className="screen-reader-text"> + {' '} + {t({ message: `about ${post.title}`, comment: 'Post title' })} + </span> + <ArrowRightIcon className={styles.icon} /> + </a> + </Link> + </footer> + <PostMeta + commentCount={post.commentCount} + publicationDate={post.date.publication} + updateDate={post.date.update} + thematics={post.thematics} + /> + </article> + ); +}; + +export default PostPreview; diff --git a/src/components/PostsList/PostsList.module.scss b/src/components/PostsList/PostsList.module.scss index 9f78f68..fe63cbc 100644 --- a/src/components/PostsList/PostsList.module.scss +++ b/src/components/PostsList/PostsList.module.scss @@ -2,4 +2,10 @@ .wrapper { @extend %reset-ordered-list; + + margin: var(--spacing-md) auto; +} + +li.item { + margin: var(--spacing-md) 0; } diff --git a/src/components/PostsList/PostsList.tsx b/src/components/PostsList/PostsList.tsx index 55ca232..31cdbf6 100644 --- a/src/components/PostsList/PostsList.tsx +++ b/src/components/PostsList/PostsList.tsx @@ -1,23 +1,18 @@ -import Link from 'next/link'; import useSWRInfinite from 'swr/infinite'; import { t } from '@lingui/macro'; import { config } from '@config/website'; import { getPublishedPosts } from '@services/graphql/blog'; -import { ArticlePreview } from '@ts/types/articles'; -import { PageInfo } from '@ts/types/pagination'; +import { PostsList as PostsListData } from '@ts/types/blog'; import styles from './PostsList.module.scss'; +import PostPreview from '@components/PostPreview/PostPreview'; +import { Button } from '@components/Buttons'; type TitleLevel = 2 | 3 | 4 | 5 | 6; -type DataType = { - posts: ArticlePreview; - pageInfo: PageInfo; -}; - const PostsList = ({ titleLevel }: { titleLevel: TitleLevel }) => { const TitleTag = `h${titleLevel}` as keyof JSX.IntrinsicElements; - const getKey = (pageIndex: number, previousData: DataType) => { + const getKey = (pageIndex: number, previousData: PostsListData) => { if (previousData && !previousData.posts) return null; const args = @@ -41,27 +36,18 @@ const PostsList = ({ titleLevel }: { titleLevel: TitleLevel }) => { isLoadingInitialData || (size > 0 && data !== undefined && typeof data[size - 1] === 'undefined'); - const getPostsList = () => { - if (error) return <div>{t`Failed to load.`}</div>; - if (!data) return <div>{t`Loading...`}</div>; + if (error) return <div>{t`Failed to load.`}</div>; + if (!data) return <div>{t`Loading...`}</div>; + const getPostsList = () => { return data.map((page) => { if (page.posts.length === 0) { return t`No results found.`; } else { return page.posts.map((post) => { return ( - <li key={post.id}> - <article> - <header> - <TitleTag> - <Link href={`/article/${post.slug}`}> - <a>{post.title}</a> - </Link> - </TitleTag> - </header> - <div dangerouslySetInnerHTML={{ __html: post.content }}></div> - </article> + <li key={post.id} className={styles.item}> + <PostPreview post={post} TitleTag={TitleTag} /> </li> ); }); @@ -72,16 +58,15 @@ const PostsList = ({ titleLevel }: { titleLevel: TitleLevel }) => { const hasNextPage = data && data[data.length - 1].pageInfo.hasNextPage; return ( - <ol className={styles.wrapper}> - {getPostsList()} + <> + <ol className={styles.wrapper}>{getPostsList()}</ol> {hasNextPage && ( - <button - disabled={isLoadingMore} - type="button" - onClick={() => setSize(size + 1)} - >{t`Load more?`}</button> + <Button + isDisabled={isLoadingMore} + clickHandler={() => setSize(size + 1)} + >{t`Load more?`}</Button> )} - </ol> + </> ); }; diff --git a/src/services/graphql/blog.ts b/src/services/graphql/blog.ts index 6b92b5a..27b972b 100644 --- a/src/services/graphql/blog.ts +++ b/src/services/graphql/blog.ts @@ -99,12 +99,14 @@ export const getPublishedPosts: GetPostsListReturn = async ({ contentParts, databaseId, date, + featuredImage, id, modified, slug, title, } = post.node; const content = contentParts.beforeMore; + const cover = featuredImage ? featuredImage.node : null; const dates = { publication: date, update: modified }; const subjects = acfPosts.postsInSubject && acfPosts.postsInSubject?.length > 0 @@ -120,6 +122,7 @@ export const getPublishedPosts: GetPostsListReturn = async ({ content, databaseId, date: dates, + featuredImage: cover, id, slug, subjects, diff --git a/src/services/graphql/post.ts b/src/services/graphql/post.ts index c7144fc..2ce918b 100644 --- a/src/services/graphql/post.ts +++ b/src/services/graphql/post.ts @@ -108,7 +108,7 @@ export const getPostBySlug: GetPostByReturn = async (slug: string) => { const content = rawPost.postBy.contentParts.afterMore; const featuredImage = rawPost.postBy.featuredImage ? rawPost.postBy.featuredImage.node - : {}; + : null; const date = { publication: rawPost.postBy.date, update: rawPost.postBy.modified, @@ -117,8 +117,8 @@ export const getPostBySlug: GetPostByReturn = async (slug: string) => { const subjects = rawPost.postBy.acfPosts.postsInSubject ? rawPost.postBy.acfPosts.postsInSubject : []; - const thematics = rawPost.postBy.acfPosts.postsInThematics - ? rawPost.postBy.acfPosts.postsInThematics + const thematics = rawPost.postBy.acfPosts.postsInThematic + ? rawPost.postBy.acfPosts.postsInThematic : []; const formattedPost: Article = { |
