From 7e16f500cb7bc0cfd8bafbf6bb1555704f771231 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 29 Apr 2022 12:13:34 +0200 Subject: chore: remove old pages, components, helpers and types Since I'm using new components, I will also rewrite the GraphQL queries so it is easier to start from scratch. --- src/ts/types/app.ts | 160 --------------------------------------------- src/ts/types/articles.ts | 102 ----------------------------- src/ts/types/blog.ts | 41 ------------ src/ts/types/comments.ts | 61 ----------------- src/ts/types/contact.ts | 19 ------ src/ts/types/cover.ts | 9 --- src/ts/types/nav.ts | 5 -- src/ts/types/prism.ts | 51 --------------- src/ts/types/repos.ts | 7 -- src/ts/types/seo.ts | 6 -- src/ts/types/taxonomies.ts | 114 -------------------------------- 11 files changed, 575 deletions(-) delete mode 100644 src/ts/types/app.ts delete mode 100644 src/ts/types/articles.ts delete mode 100644 src/ts/types/blog.ts delete mode 100644 src/ts/types/comments.ts delete mode 100644 src/ts/types/contact.ts delete mode 100644 src/ts/types/cover.ts delete mode 100644 src/ts/types/nav.ts delete mode 100644 src/ts/types/prism.ts delete mode 100644 src/ts/types/repos.ts delete mode 100644 src/ts/types/seo.ts delete mode 100644 src/ts/types/taxonomies.ts (limited to 'src/ts') diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts deleted file mode 100644 index 4243762..0000000 --- a/src/ts/types/app.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { NextPage } from 'next'; -import { AppProps } from 'next/app'; -import { ImageProps } from 'next/image'; -import { ReactElement, ReactNode } from 'react'; -import { PostBy, TotalArticles } from './articles'; -import { AllPostsSlug, LastPostCursor, RawPostsList } from './blog'; -import { CommentData, CommentsByPostId, CreateComment } from './comments'; -import { ContactData, SendEmail } from './contact'; -import { - AllTopics, - AllTopicsSlug, - AllThematics, - AllThematicsSlug, - TopicBy, - ThematicBy, -} from './taxonomies'; - -//============================================================================== -// Next -//============================================================================== - -export type NextPageWithLayout

= NextPage

& { - getLayout?: (page: ReactElement) => ReactNode; -}; - -export type AppPropsWithLayout = AppProps & { - Component: NextPageWithLayout; -}; - -//============================================================================== -// API -//============================================================================== - -export type VariablesType = T extends PostBy | TopicBy | ThematicBy - ? Slug - : T extends RawPostsList - ? CursorPagination - : T extends CommentsByPostId - ? { id: number } - : T extends CreateComment - ? CommentData - : T extends LastPostCursor - ? { first: number } - : T extends SendEmail - ? ContactData - : null; - -export type RequestType = - | AllPostsSlug - | AllTopics - | AllTopicsSlug - | AllThematics - | AllThematicsSlug - | CommentsByPostId - | CreateComment - | LastPostCursor - | PostBy - | RawPostsList - | SendEmail - | ThematicBy - | TopicBy - | TotalArticles; - -//============================================================================== -// Globals -//============================================================================== - -export type ButtonKind = 'primary' | 'secondary' | 'tertiary'; - -export type ButtonPosition = 'left' | 'right' | 'center'; - -export type ContentInfo = { - readingTime: number; - wordsCount: number; -}; - -export type ContentParts = { - afterMore: string; - beforeMore: string; -}; - -export type CursorPagination = { - first: number; - after: string; -}; - -export type Dates = { - publication: string; - update: string; -}; - -export type Heading = { - depth: number; - id: string; - children: Heading[]; - title: string; -}; - -export type Meta = { - title: string; - publishedOn: string; - updatedOn: string; -}; - -export type MetaKind = 'article' | 'list'; - -export type NoticeType = 'error' | 'info' | 'success' | 'warning'; - -export type PageInfo = { - endCursor: string; - hasNextPage: boolean; - total: number; -}; - -export type ParamsIds = { - params: { id: string }; -}; - -export type ParamsSlug = { - params: { slug: string }; -}; - -export type Project = { - cover?: string; - id: string; - intro: string; - meta: ProjectMeta; - slug: string; - tagline?: string; - title: string; - seo: { - title: string; - description: string; - }; -}; - -export type ProjectMeta = Omit & { - hasCover: boolean; - license: string; - repos?: { - github?: string; - gitlab?: string; - }; - technologies?: string[]; -}; - -export type ProjectProps = { - project: Project; -}; - -export type ResponsiveImageProps = ImageProps & { - caption?: string; - linkTarget?: string; -}; - -export type Slug = { - slug: string; -}; - -export type TitleLevel = 2 | 3 | 4 | 5 | 6; diff --git a/src/ts/types/articles.ts b/src/ts/types/articles.ts deleted file mode 100644 index 64d2860..0000000 --- a/src/ts/types/articles.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { ContentInfo, ContentParts, Dates } from './app'; -import { Comment } from './comments'; -import { Cover, RawCover } from './cover'; -import { SEO } from './seo'; -import { RawTopicPreview, TopicPreview, ThematicPreview } from './taxonomies'; - -export type ArticleAuthor = { - firstName: string; - lastName: string; - name: string; -}; - -export type RawACFPosts = { - postsInTopic: RawTopicPreview[] | null; - postsInThematic: ThematicPreview[] | null; -}; - -export type ACFPosts = { - postsInTopic: TopicPreview[] | null; - postsInThematic: ThematicPreview[] | null; -}; - -export type ArticleMeta = { - author?: ArticleAuthor; - commentCount?: number; - dates?: Dates; - readingTime?: number; - results?: number; - topics?: TopicPreview[]; - thematics?: ThematicPreview[]; - website?: string; - wordsCount?: number; -}; - -export type Article = { - author: ArticleAuthor; - commentCount: number | null; - content: string; - databaseId: number; - dates: Dates; - featuredImage: Cover; - id: string; - info: ContentInfo; - intro: string; - seo: SEO; - topics: TopicPreview[] | []; - thematics: ThematicPreview[] | []; - title: string; -}; - -export type RawArticle = Pick< - Article, - 'commentCount' | 'databaseId' | 'id' | 'info' | 'seo' | 'title' -> & { - acfPosts: RawACFPosts; - author: { node: ArticleAuthor }; - contentParts: ContentParts; - date: string; - featuredImage: RawCover; - modified: string; -}; - -export type ArticlePreview = Pick< - Article, - | 'commentCount' - | 'dates' - | 'id' - | 'info' - | 'intro' - | 'topics' - | 'thematics' - | 'title' -> & { featuredImage: Cover; slug: string }; - -export type RawArticlePreview = Pick< - Article, - 'commentCount' | 'id' | 'info' | 'title' -> & { - acfPosts: ACFPosts; - contentParts: Pick; - date: string; - featuredImage: RawCover; - modified: string; - slug: string; -}; - -export type PostBy = { - post: RawArticle; -}; - -export type ArticleProps = { - comments: Comment[]; - post: Article; -}; - -export type TotalArticles = { - posts: { - pageInfo: { - total: number; - }; - }; -}; diff --git a/src/ts/types/blog.ts b/src/ts/types/blog.ts deleted file mode 100644 index 05bdd1f..0000000 --- a/src/ts/types/blog.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { PageInfo, Slug } from './app'; -import { ArticlePreview, RawArticlePreview } from './articles'; -import { ThematicPreview, TopicPreview } from './taxonomies'; - -export type PostsList = { - posts: ArticlePreview[]; - pageInfo: PageInfo; -}; - -export type PostsListEdges = { - cursor: string; - node: RawArticlePreview; -}; - -export type RawPostsList = { - posts: { - edges: PostsListEdges[]; - pageInfo: PageInfo; - }; -}; - -export type LastPostCursor = { - posts: { - pageInfo: { - endCursor: string; - }; - }; -}; - -export type AllPostsSlug = { - posts: { - nodes: Slug[]; - }; -}; - -export type BlogPageProps = { - allThematics: ThematicPreview[]; - allTopics: TopicPreview[]; - posts: PostsList; - totalPosts: number; -}; diff --git a/src/ts/types/comments.ts b/src/ts/types/comments.ts deleted file mode 100644 index aa3fac3..0000000 --- a/src/ts/types/comments.ts +++ /dev/null @@ -1,61 +0,0 @@ -//============================================================================== -// Comments query -//============================================================================== - -export type CommentAuthor = { - name: string; - gravatarUrl: string; - url: string; -}; - -export type RawCommentAuthor = { - node: CommentAuthor; -}; - -export type Comment = { - approved: ''; - author: CommentAuthor; - databaseId: number; - content: string; - date: string; - parentDatabaseId: number; - replies: Comment[]; -}; - -export type RawComment = Omit & { - author: RawCommentAuthor; -}; - -export type CommentsNode = { - nodes: RawComment[]; -}; - -export type CommentsByPostId = { - comments: CommentsNode; -}; - -//============================================================================== -// Comment mutations -//============================================================================== - -export type CommentData = { - author: string; - authorEmail: string; - authorUrl: string; - content: string; - parent: number; - commentOn: number; - mutationId: string; -}; - -export type CreatedComment = { - clientMutationId: string; - success: boolean; - comment: null | { - approved: boolean; - }; -}; - -export type CreateComment = { - createComment: CreatedComment; -}; diff --git a/src/ts/types/contact.ts b/src/ts/types/contact.ts deleted file mode 100644 index ef6847a..0000000 --- a/src/ts/types/contact.ts +++ /dev/null @@ -1,19 +0,0 @@ -export type ContactData = { - body: string; - mutationId: string; - replyTo: string; - subject: string; -}; - -export type SentEmail = { - clientMutationId: string; - message: string; - origin: string; - replyTo: string; - sent: boolean; - to: string; -}; - -export type SendEmail = { - sendEmail: SentEmail; -}; diff --git a/src/ts/types/cover.ts b/src/ts/types/cover.ts deleted file mode 100644 index 4df898e..0000000 --- a/src/ts/types/cover.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type Cover = { - altText: string; - sourceUrl: string; - title: string; -} | null; - -export type RawCover = { - node: Cover; -} | null; diff --git a/src/ts/types/nav.ts b/src/ts/types/nav.ts deleted file mode 100644 index 7cfc46b..0000000 --- a/src/ts/types/nav.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type NavItem = { - id: string; - name: string; - slug: string; -}; diff --git a/src/ts/types/prism.ts b/src/ts/types/prism.ts deleted file mode 100644 index 663bc08..0000000 --- a/src/ts/types/prism.ts +++ /dev/null @@ -1,51 +0,0 @@ -export type PrismLanguages = - | 'apacheconf' - | 'bash' - | 'css' - | 'diff' - | 'docker' - | 'editorconfig' - | 'ejs' - | 'git' - | 'graphql' - | 'html' - | 'ignore' - | 'ini' - | 'javascript' - | 'jsdoc' - | 'json' - | 'jsx' - | 'makefile' - | 'markup' - | 'php' - | 'phpdoc' - | 'regex' - | 'scss' - | 'shell-session' - | 'smarty' - | 'tcl' - | 'toml' - | 'tsx' - | 'twig' - | 'yaml'; - -export type PrismDefaultPlugins = - | 'autoloader' - | 'color-scheme' - | 'copy-to-clipboard' - | 'match-braces' - | 'normalize-whitespace' - | 'show-language' - | 'toolbar'; - -export type PrismPlugins = - | 'command-line' - | 'diff-highlight' - | 'inline-color' - | 'line-highlight' - | 'line-numbers'; - -export type PrismProviderProps = { - language: PrismLanguages; - plugins: PrismPlugins[]; -}; diff --git a/src/ts/types/repos.ts b/src/ts/types/repos.ts deleted file mode 100644 index 7dacacc..0000000 --- a/src/ts/types/repos.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type RepoData = { - created_at: string; - updated_at: string; - stargazers_count: number; -}; - -export type RepoAPI = 'github'; diff --git a/src/ts/types/seo.ts b/src/ts/types/seo.ts deleted file mode 100644 index 18e3c95..0000000 --- a/src/ts/types/seo.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type SEO = { - title: string; - metaDesc: string; - metaRobotsNofollow: string; - metaRobotsNoindex: string; -}; diff --git a/src/ts/types/taxonomies.ts b/src/ts/types/taxonomies.ts deleted file mode 100644 index 17fc022..0000000 --- a/src/ts/types/taxonomies.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { ContentInfo, ContentParts, Dates, Slug } from './app'; -import { ArticlePreview, RawArticlePreview } from './articles'; -import { Cover, RawCover } from './cover'; -import { SEO } from './seo'; - -//============================================================================== -// Taxonomies base -//============================================================================== - -type Taxonomy = { - content: string; - databaseId: number; - dates: Dates; - id: string; - info: ContentInfo; - intro: string; - posts: ArticlePreview[]; - seo: SEO; - title: string; -}; - -type TaxonomyPreview = Pick< - Taxonomy, - 'databaseId' | 'id' | 'info' | 'seo' | 'title' -> & { - slug: string; -}; - -//============================================================================== -// Topics -//============================================================================== - -export type Topic = Taxonomy & { - featuredImage: Cover; - officialWebsite: string; -}; - -export type RawTopicPreview = TaxonomyPreview & { - featuredImage: RawCover; -}; - -export type TopicPreview = TaxonomyPreview & { - featuredImage: Cover; -}; - -export type AllTopics = { - topics: { - nodes: TopicPreview[]; - }; -}; - -export type RawTopic = TopicPreview & { - acfTopics: { - officialWebsite: string; - postsInTopic: RawArticlePreview[]; - }; - contentParts: ContentParts; - date: string; - featuredImage: RawCover; - modified: string; -}; - -export type TopicBy = { - topic: RawTopic; -}; - -export type AllTopicsSlug = { - topics: { - nodes: Slug[]; - }; -}; - -export type TopicProps = { - allTopics: TopicPreview[]; - topic: Topic; -}; - -//============================================================================== -// Thematics -//============================================================================== - -export type Thematic = Taxonomy; - -export type ThematicPreview = TaxonomyPreview; - -export type AllThematics = { - thematics: { - nodes: ThematicPreview[]; - }; -}; - -export type RawThematic = TaxonomyPreview & { - acfThematics: { - postsInThematic: RawArticlePreview[]; - }; - contentParts: ContentParts; - date: string; - modified: string; -}; - -export type ThematicBy = { - thematic: RawThematic; -}; - -export type AllThematicsSlug = { - thematics: { - nodes: Slug[]; - }; -}; - -export type ThematicProps = { - allThematics: ThematicPreview[]; - thematic: Thematic; -}; -- cgit v1.2.3 From ca921d7536cfe950b5a7d442977bbf900b48faf4 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Mon, 2 May 2022 18:36:09 +0200 Subject: chore: fetch posts for rss feed --- src/services/graphql/articles.query.ts | 1 + src/services/graphql/articles.ts | 104 +++++++++++++++++++++++++++++++++ src/ts/types/app.ts | 86 +++++++++++++++++++++++++++ src/ts/types/raw-data.ts | 103 ++++++++++++++++++++++++++++++++ src/utils/helpers/author.ts | 32 ++++++++++ src/utils/helpers/dates.ts | 55 +++++++++++++++++ src/utils/helpers/images.ts | 18 ++++++ src/utils/helpers/pages.ts | 26 +++++++++ src/utils/helpers/rss.ts | 44 ++++++++------ 9 files changed, 450 insertions(+), 19 deletions(-) create mode 100644 src/services/graphql/articles.ts create mode 100644 src/ts/types/app.ts create mode 100644 src/ts/types/raw-data.ts create mode 100644 src/utils/helpers/author.ts create mode 100644 src/utils/helpers/dates.ts create mode 100644 src/utils/helpers/images.ts create mode 100644 src/utils/helpers/pages.ts (limited to 'src/ts') diff --git a/src/services/graphql/articles.query.ts b/src/services/graphql/articles.query.ts index e384aba..e62835d 100644 --- a/src/services/graphql/articles.query.ts +++ b/src/services/graphql/articles.query.ts @@ -89,6 +89,7 @@ export const articlesQuery = `query Articles($after: String = "", $first: Int = beforeMore } databaseId + date featuredImage { node { altText diff --git a/src/services/graphql/articles.ts b/src/services/graphql/articles.ts new file mode 100644 index 0000000..e5ce7a5 --- /dev/null +++ b/src/services/graphql/articles.ts @@ -0,0 +1,104 @@ +import { Article } from '@ts/types/app'; +import { RawArticle, TotalItems } from '@ts/types/raw-data'; +import { getAuthorFromRawData } from '@utils/helpers/author'; +import { getImageFromRawData } from '@utils/helpers/images'; +import { getPageLinkFromRawData } from '@utils/helpers/pages'; +import { EdgesVars, fetchAPI, getAPIUrl, PageInfo } from './api'; +import { articlesQuery, totalArticlesQuery } from './articles.query'; + +/** + * Retrieve the total number of articles. + * + * @returns {Promise} - The articles total number. + */ +export const getTotalArticles = async (): Promise => { + const response = await fetchAPI({ + api: getAPIUrl(), + query: totalArticlesQuery, + }); + + return response.posts.pageInfo.total; +}; + +export type GetArticlesReturn = { + articles: Article[]; + pageInfo: PageInfo; +}; + +/** + * Convert raw data to an Article object. + * + * @param {RawArticle} data - The page raw data. + * @returns {Article} The page data. + */ +export const getArticleFromRawData = (data: RawArticle): Article => { + const { + acfPosts, + author, + commentCount, + contentParts, + databaseId, + date, + featuredImage, + info, + modified, + slug, + title, + seo, + } = data; + + return { + content: contentParts.afterMore, + id: databaseId, + intro: contentParts.beforeMore, + meta: { + author: getAuthorFromRawData(author.node, 'page'), + commentsCount: commentCount || 0, + cover: featuredImage?.node + ? getImageFromRawData(featuredImage.node) + : undefined, + dates: { + publication: date, + update: modified, + }, + readingTime: info.readingTime, + seo: { + description: seo?.metaDesc || '', + title: seo?.title || '', + }, + thematics: acfPosts.postsInThematic?.map((thematic) => + getPageLinkFromRawData(thematic) + ), + topics: acfPosts.postsInTopic?.map((topic) => + getPageLinkFromRawData(topic) + ), + wordsCount: info.wordsCount, + }, + slug, + title, + }; +}; + +/** + * Retrieve the given number of articles from API. + * + * @param {EdgesVars} obj - An object. + * @param {number} obj.first - The number of articles. + * @returns {Promise} - The articles data. + */ +export const getArticles = async ({ + first, +}: EdgesVars): Promise => { + const response = await fetchAPI({ + api: getAPIUrl(), + query: articlesQuery, + variables: { first }, + }); + + return { + articles: response.posts.edges.map((edge) => + getArticleFromRawData(edge.node) + ), + pageInfo: response.posts.pageInfo, + }; +}; diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts new file mode 100644 index 0000000..b09f3d5 --- /dev/null +++ b/src/ts/types/app.ts @@ -0,0 +1,86 @@ +export type AuthorKind = 'page' | 'comment'; + +export type Author = { + avatar?: Image; + description?: T extends 'page' ? string | undefined : never; + name: string; + website?: string; +}; + +export type CommentMeta = { + author: Author<'comment'>; + date: string; +}; + +export type Comment = { + approved: boolean; + content: string; + id: number; + meta: CommentMeta; + parentId: number; + replies: Comment[]; +}; + +export type Dates = { + publication: string; + update: string; +}; + +export type Image = { + alt: string; + height: number; + src: string; + title?: string; + width: number; +}; + +export type Repos = { + github?: string; + gitlab?: string; +}; + +export type SEO = { + description: string; + title: string; +}; + +export type PageKind = 'article' | 'project' | 'thematic' | 'topic'; + +export type Meta = { + articles?: T extends 'thematic' | 'topic' ? Article[] : never; + author: Author<'page'>; + commentsCount?: T extends 'article' ? number : never; + cover?: Image; + dates: Dates; + license?: T extends 'projects' ? string : never; + readingTime: number; + repos?: T extends 'projects' ? Repos : never; + seo: SEO; + technologies?: T extends 'projects' ? string[] : never; + thematics?: T extends 'article' | 'topic' ? PageLink[] : never; + topics?: T extends 'article' | 'thematic' ? PageLink[] : never; + website?: T extends 'topic' ? string : never; + wordsCount: number; +}; + +export type Page = { + content: string; + id: number; + intro: string; + meta?: Meta; + slug: string; + title: string; +}; + +export type PageLink = { + id: number; + name: string; + slug: string; +}; + +export type Article = Page<'article'>; +export type ArticleCard = Pick & + Pick, 'cover' | 'dates'>; +export type Project = Page<'project'>; +export type Thematic = Page<'thematic'>; +export type Topic = Page<'topic'>; diff --git a/src/ts/types/raw-data.ts b/src/ts/types/raw-data.ts new file mode 100644 index 0000000..43a2453 --- /dev/null +++ b/src/ts/types/raw-data.ts @@ -0,0 +1,103 @@ +/** + * Types for raw data coming from GraphQL API. + */ + +import { NodeResponse, PageInfo } from '@services/graphql/api'; +import { AuthorKind } from './app'; + +export type ACFPosts = { + postsInThematic?: RawThematicPreview[]; + postsInTopic?: RawTopicPreview[]; +}; + +export type ACFThematics = { + postsInThematic: RawArticle[]; +}; + +export type ACFTopics = { + officialWebsite: string; + postsInTopic: RawArticle[]; +}; + +export type ContentParts = { + afterMore: string; + beforeMore: string; +}; + +export type Info = { + readingTime: number; + wordsCount: number; +}; + +export type RawAuthor = { + description?: T extends 'page' ? string | undefined : never; + gravatarUrl?: string; + name: string; + url?: string; +}; + +export type RawComment = { + approved: boolean; + author: NodeResponse>; + content: string; + databaseId: number; + date: string; + parentDatabaseId: number; +}; + +export type RawCover = { + altText: string; + mediaDetails: { + width: number; + height: number; + }; + sourceUrl: string; + title?: string; +}; + +export type RawArticle = RawPage & { + acfPosts: ACFPosts; + commentCount: number | null; +}; + +export type RawArticlePreview = Pick< + RawArticle, + 'databaseId' | 'date' | 'featuredImage' | 'slug' | 'title' +>; + +export type RawPage = { + author: NodeResponse>; + contentParts: ContentParts; + databaseId: number; + date: string; + featuredImage: NodeResponse | null; + info: Info; + modified: string; + seo?: RawSEO; + slug: string; + title: string; +}; + +export type RawSEO = { + metaDesc: string; + title: string; +}; + +export type RawThematic = RawPage & { + acfThematics: ACFThematics; +}; + +export type RawThematicPreview = Pick< + RawThematic, + 'databaseId' | 'slug' | 'title' +>; + +export type RawTopic = RawPage & { + acfTopics: ACFTopics; +}; + +export type RawTopicPreview = Pick; + +export type TotalItems = { + pageInfo: Pick; +}; diff --git a/src/utils/helpers/author.ts b/src/utils/helpers/author.ts new file mode 100644 index 0000000..cf125fc --- /dev/null +++ b/src/utils/helpers/author.ts @@ -0,0 +1,32 @@ +import { type Author, type AuthorKind } from '@ts/types/app'; +import { type RawAuthor } from '@ts/types/raw-data'; + +/** + * Convert author raw data to regular data. + * + * @param {RawAuthor} data - The author raw data. + * @param {AuthorKind} kind - The author kind. Either `page` or `comment`. + * @param {number} [avatarSize] - The author avatar size. + * @returns {Author} The author data. + */ +export const getAuthorFromRawData = ( + data: RawAuthor, + kind: AuthorKind, + avatarSize: number = 80 +): Author => { + const { name, description, gravatarUrl, url } = data; + + return { + name, + avatar: gravatarUrl + ? { + alt: `${name} avatar`, + height: avatarSize, + src: gravatarUrl, + width: avatarSize, + } + : undefined, + description, + website: url, + }; +}; diff --git a/src/utils/helpers/dates.ts b/src/utils/helpers/dates.ts new file mode 100644 index 0000000..fa167a7 --- /dev/null +++ b/src/utils/helpers/dates.ts @@ -0,0 +1,55 @@ +import { Dates } from '@ts/types/app'; +import { settings } from '@utils/config'; + +/** + * Format a date based on a locale. + * + * @param {string} date - The date. + * @param {string} [locale] - A locale. + * @returns {string} The locale date string. + */ +export const getFormattedDate = ( + date: string, + locale: string = settings.locales.defaultLocale +): string => { + const dateOptions: Intl.DateTimeFormatOptions = { + day: 'numeric', + month: 'long', + year: 'numeric', + }; + + return new Date(date).toLocaleDateString(locale, dateOptions); +}; + +/** + * Format a time based on a locale. + * + * @param {string} time - The time. + * @param {string} [locale] - A locale. + * @returns {string} The locale time string. + */ +export const getFormattedTime = ( + time: string, + locale: string = settings.locales.defaultLocale +): string => { + const formattedTime = new Date(time).toLocaleTimeString(locale, { + hour: 'numeric', + minute: 'numeric', + }); + + return locale === 'fr' ? formattedTime.replace(':', 'h') : formattedTime; +}; + +/** + * Retrieve a Dates object. + * + * @param publication - The publication date. + * @param update - The update date. + * @returns {Dates} A Dates object. + */ +export const getDates = (publication: string, update: string): Dates => { + return { + publication: getFormattedDate(publication), + update: getFormattedDate(update), + }; +}; diff --git a/src/utils/helpers/images.ts b/src/utils/helpers/images.ts new file mode 100644 index 0000000..30bb8be --- /dev/null +++ b/src/utils/helpers/images.ts @@ -0,0 +1,18 @@ +import { Image } from '@ts/types/app'; +import { RawCover } from '@ts/types/raw-data'; + +/** + * Retrieve an Image object from raw data. + * + * @param image - The cover raw data. + * @returns {Image} - An Image object. + */ +export const getImageFromRawData = (image: RawCover): Image => { + return { + alt: image.altText, + height: image.mediaDetails.height, + src: image.sourceUrl, + title: image.title, + width: image.mediaDetails.width, + }; +}; diff --git a/src/utils/helpers/pages.ts b/src/utils/helpers/pages.ts new file mode 100644 index 0000000..d757f8c --- /dev/null +++ b/src/utils/helpers/pages.ts @@ -0,0 +1,26 @@ +import { type PageLink } from '@ts/types/app'; +import { + type RawThematicPreview, + type RawTopicPreview, +} from '@ts/types/raw-data'; + +/** + * Convert raw data to a Link object. + * + * @param data - An object. + * @param {number} data.databaseId - The data id. + * @param {string} data.slug - The data slug. + * @param {string} data.title - The data name. + * @returns {PageLink} The link data (id, slug and title). + */ +export const getPageLinkFromRawData = ( + data: RawThematicPreview | RawTopicPreview +): PageLink => { + const { databaseId, slug, title } = data; + + return { + id: databaseId, + name: title, + slug, + }; +}; diff --git a/src/utils/helpers/rss.ts b/src/utils/helpers/rss.ts index 10a8e77..95d3b7b 100644 --- a/src/utils/helpers/rss.ts +++ b/src/utils/helpers/rss.ts @@ -1,20 +1,26 @@ -import { getPostsTotal, getPublishedPosts } from '@services/graphql/queries'; -import { ArticlePreview } from '@ts/types/articles'; -import { PostsList } from '@ts/types/blog'; +import { getArticles, getTotalArticles } from '@services/graphql/articles'; +import { Article } from '@ts/types/app'; import { settings } from '@utils/config'; import { Feed } from 'feed'; -const getAllPosts = async (): Promise => { - const totalPosts = await getPostsTotal(); - const posts: ArticlePreview[] = []; +/** + * Retrieve the data for all the articles. + * + * @returns {Promise} - All the articles. + */ +const getAllArticles = async (): Promise => { + const totalArticles = await getTotalArticles(); + const { articles } = await getArticles({ first: totalArticles }); - const postsList: PostsList = await getPublishedPosts({ first: totalPosts }); - posts.push(...postsList.posts); - - return posts; + return articles; }; -export const generateFeed = async () => { +/** + * Generate a new feed. + * + * @returns {Promise} - The feed. + */ +export const generateFeed = async (): Promise => { const author = { name: settings.name, email: process.env.APP_AUTHOR_EMAIL, @@ -38,16 +44,16 @@ export const generateFeed = async () => { title, }); - const posts = await getAllPosts(); + const articles = await getAllArticles(); - posts.forEach((post) => { + articles.forEach((article) => { feed.addItem({ - content: post.intro, - date: new Date(post.dates.publication), - description: post.intro, - id: post.id, - link: `${settings.url}/article/${post.slug}`, - title: post.title, + content: article.intro, + date: new Date(article.meta!.dates.publication), + description: article.intro, + id: `${article.id}`, + link: `${settings.url}/article/${article.slug}`, + title: article.title, }); }); -- cgit v1.2.3 From 83a029084f1bbfd78b7099d9bea3371d4533c6d9 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 3 May 2022 16:51:22 +0200 Subject: chore: add a LegalNotice page --- mdx.d.ts | 10 +- .../molecules/buttons/heading-button.module.scss | 4 +- src/components/molecules/layout/meta.module.scss | 8 +- .../molecules/layout/page-header.module.scss | 7 +- .../widgets/links-list-widget.module.scss | 10 +- .../organisms/widgets/links-list-widget.tsx | 5 +- .../widgets/table-of-contents.module.scss | 4 + .../organisms/widgets/table-of-contents.tsx | 2 + src/pages/mentions-legales.tsx | 140 +++++++++++++++++++++ src/ts/types/app.ts | 16 ++- src/ts/types/mdx.ts | 20 +++ src/ts/types/raw-data.ts | 6 +- src/utils/helpers/author.ts | 10 +- 13 files changed, 209 insertions(+), 33 deletions(-) create mode 100644 src/components/organisms/widgets/table-of-contents.module.scss create mode 100644 src/pages/mentions-legales.tsx create mode 100644 src/ts/types/mdx.ts (limited to 'src/ts') diff --git a/mdx.d.ts b/mdx.d.ts index f3a9a90..b4e333d 100644 --- a/mdx.d.ts +++ b/mdx.d.ts @@ -1,13 +1,9 @@ declare module '*.mdx' { + import { MDXData, MDXPageMeta, MDXProjectMeta } from '@ts/types/mdx'; import { MDXProps } from 'mdx/types'; - import { Meta } from '@ts/types/app'; let MDXComponent: (props: MDXProps) => JSX.Element; export default MDXComponent; - export const cover: string; - export const image: string; - export const intro: string; - export const meta: Meta; - export const pdf: string; - export const seo: { title: string; description: string }; + export const data: MDXData; + export const meta: MDXPageMeta | MDXProjectMeta; } diff --git a/src/components/molecules/buttons/heading-button.module.scss b/src/components/molecules/buttons/heading-button.module.scss index 1d16410..9c278e4 100644 --- a/src/components/molecules/buttons/heading-button.module.scss +++ b/src/components/molecules/buttons/heading-button.module.scss @@ -36,6 +36,8 @@ } .heading { - background: none; padding: var(--spacing-2xs) 0; + background: none; + font-size: var(--font-size-xl); + text-align: left; } diff --git a/src/components/molecules/layout/meta.module.scss b/src/components/molecules/layout/meta.module.scss index f7cc55b..0485545 100644 --- a/src/components/molecules/layout/meta.module.scss +++ b/src/components/molecules/layout/meta.module.scss @@ -2,12 +2,18 @@ .list { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(1, minmax(0, 1fr)); + gap: var(--spacing-sm); @include mix.media("screen") { + @include mix.dimensions("2xs") { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + @include mix.dimensions("sm") { display: flex; flex-flow: column nowrap; + gap: var(--spacing-2xs); } } } diff --git a/src/components/molecules/layout/page-header.module.scss b/src/components/molecules/layout/page-header.module.scss index 93f7595..4c7df5f 100644 --- a/src/components/molecules/layout/page-header.module.scss +++ b/src/components/molecules/layout/page-header.module.scss @@ -1,5 +1,4 @@ @use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; @use "@styles/abstracts/placeholders"; .wrapper { @@ -55,9 +54,5 @@ } .meta { - @include mix.media("screen") { - @include mix.dimensions("xs") { - font-size: var(--font-size-sm); - } - } + font-size: var(--font-size-sm); } diff --git a/src/components/organisms/widgets/links-list-widget.module.scss b/src/components/organisms/widgets/links-list-widget.module.scss index cbad83e..4444df4 100644 --- a/src/components/organisms/widgets/links-list-widget.module.scss +++ b/src/components/organisms/widgets/links-list-widget.module.scss @@ -3,6 +3,12 @@ .widget { .list { + .list { + > *:first-child { + border-top: fun.convert-px(1) solid var(--color-primary); + } + } + &__link { display: block; padding: var(--spacing-2xs) var(--spacing-xs); @@ -50,9 +56,7 @@ &__item { &:not(:last-child) { - .list__link { - border-bottom: fun.convert-px(1) solid var(--color-primary); - } + border-bottom: fun.convert-px(1) solid var(--color-primary); } > .list { diff --git a/src/components/organisms/widgets/links-list-widget.tsx b/src/components/organisms/widgets/links-list-widget.tsx index 559d0b6..37a20fc 100644 --- a/src/components/organisms/widgets/links-list-widget.tsx +++ b/src/components/organisms/widgets/links-list-widget.tsx @@ -24,7 +24,7 @@ export type LinksListItems = { }; export type LinksListWidgetProps = Pick & - Pick & { + Pick & { /** * An array of name/url couple. */ @@ -37,6 +37,7 @@ export type LinksListWidgetProps = Pick & * Render a list of links inside a widget. */ const LinksListWidget: FC = ({ + className = '', items, kind = 'unordered', ...props @@ -74,7 +75,7 @@ const LinksListWidget: FC = ({ items={getListItems(items)} kind={kind} withMargin={false} - className={`${styles.list} ${styles[listKindClass]}`} + className={`${styles.list} ${styles[listKindClass]} ${className}`} itemsClassName={styles.list__item} /> diff --git a/src/components/organisms/widgets/table-of-contents.module.scss b/src/components/organisms/widgets/table-of-contents.module.scss new file mode 100644 index 0000000..36217ed --- /dev/null +++ b/src/components/organisms/widgets/table-of-contents.module.scss @@ -0,0 +1,4 @@ +.list { + font-size: var(--font-size-sm); + font-weight: 500; +} diff --git a/src/components/organisms/widgets/table-of-contents.tsx b/src/components/organisms/widgets/table-of-contents.tsx index 3778e02..800ff58 100644 --- a/src/components/organisms/widgets/table-of-contents.tsx +++ b/src/components/organisms/widgets/table-of-contents.tsx @@ -2,6 +2,7 @@ import useHeadingsTree, { type Heading } from '@utils/hooks/use-headings-tree'; import { FC } from 'react'; import { useIntl } from 'react-intl'; import LinksListWidget, { type LinksListItems } from './links-list-widget'; +import styles from './table-of-contents.module.scss'; type TableOfContentsProps = { /** @@ -46,6 +47,7 @@ const TableOfContents: FC = ({ wrapper }) => { title={title} level={2} items={getItems(headingsTree)} + className={styles.list} /> ); }; diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx new file mode 100644 index 0000000..8dd0a1d --- /dev/null +++ b/src/pages/mentions-legales.tsx @@ -0,0 +1,140 @@ +import Link from '@components/atoms/links/link'; +import ResponsiveImage from '@components/molecules/images/responsive-image'; +import { type BreadcrumbItem } from '@components/molecules/nav/breadcrumb'; +import PageLayout, { + type PageLayoutProps, +} from '@components/templates/page/page-layout'; +import LegalNoticeContent, { meta } from '@content/pages/legal-notice.mdx'; +import { getFormattedDate } from '@utils/helpers/dates'; +import { loadTranslation } from '@utils/helpers/i18n'; +import useSettings from '@utils/hooks/use-settings'; +import { NestedMDXComponents } from 'mdx/types'; +import { GetStaticProps, NextPage } from 'next'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import Script from 'next/script'; +import { useIntl } from 'react-intl'; +import { Article, Graph, WebPage } from 'schema-dts'; + +/** + * Legal Notice page. + */ +const LegalNoticePage: NextPage = () => { + const intl = useIntl(); + const { dates, intro, seo, title } = meta; + const homeLabel = intl.formatMessage({ + defaultMessage: 'Home', + description: 'Breadcrumb: home label', + id: 'j5k9Fe', + }); + const breadcrumb: BreadcrumbItem[] = [ + { id: 'home', name: homeLabel, url: '/' }, + { id: 'legal-notice', name: title, url: '/mentions-legales' }, + ]; + + const publicationLabel = intl.formatMessage({ + defaultMessage: 'Published on:', + description: 'Meta: publication date label', + id: 'QGi5uD', + }); + + const updateLabel = intl.formatMessage({ + defaultMessage: 'Updated on:', + description: 'Meta: update date label', + id: 'tLC7bh', + }); + + const headerMeta: PageLayoutProps['headerMeta'] = { + publication: { + name: publicationLabel, + value: getFormattedDate(dates.publication), + }, + update: { name: updateLabel, value: getFormattedDate(dates.update) }, + }; + + const components: NestedMDXComponents = { + Image: (props) => , + Link: (props) => , + }; + + const { website } = useSettings(); + const { asPath } = useRouter(); + const pageUrl = `${website.url}${asPath}`; + const pagePublicationDate = new Date(dates.publication); + const pageUpdateDate = new Date(dates.update); + + const webpageSchema: WebPage = { + '@id': `${pageUrl}`, + '@type': 'WebPage', + breadcrumb: { '@id': `${website.url}/#breadcrumb` }, + name: seo.title, + description: seo.description, + inLanguage: website.locales.default, + license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', + reviewedBy: { '@id': `${website.url}/#branding` }, + url: `${pageUrl}`, + isPartOf: { + '@id': `${website.url}`, + }, + }; + + const articleSchema: Article = { + '@id': `${website.url}/#legal-notice`, + '@type': 'Article', + name: title, + description: intro, + author: { '@id': `${website.url}/#branding` }, + copyrightYear: pagePublicationDate.getFullYear(), + creator: { '@id': `${website.url}/#branding` }, + dateCreated: pagePublicationDate.toISOString(), + dateModified: pageUpdateDate.toISOString(), + datePublished: pagePublicationDate.toISOString(), + editor: { '@id': `${website.url}/#branding` }, + headline: title, + inLanguage: website.locales.default, + license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', + mainEntityOfPage: { '@id': `${pageUrl}` }, + }; + + const schemaJsonLd: Graph = { + '@context': 'https://schema.org', + '@graph': [webpageSchema, articleSchema], + }; + + return ( + + + {`${seo.title} - ${website.name}`} + + + + + + +