From 81094d001c62b62a02ad00e111fc1241b82ad284 Mon Sep 17 00:00:00 2001 From: Manuel Garcia Genta Date: Wed, 13 Apr 2022 09:35:37 -0300 Subject: [PATCH] Blog Post Content (#37) --- src/components/common/card/card.module.scss | 137 ++++- src/components/common/card/index.tsx | 10 +- .../blog/post-content/content.module.scss | 1 + .../sections/blog/posts-grid/index.tsx | 4 +- .../blog/posts-grid/posts-grid.module.scss | 18 +- .../sections/blog/search/search.module.scss | 1 + .../sections/blog/shares/icons/discord.js | 16 + .../sections/blog/shares/icons/facebook.js | 16 + .../sections/blog/shares/icons/instagram.js | 20 + .../sections/blog/shares/icons/reddit.js | 18 + .../sections/blog/shares/icons/telegram.js | 18 + .../sections/blog/shares/icons/twitter.js | 16 + src/components/sections/blog/shares/index.tsx | 64 +++ .../sections/blog/shares/shares.module.scss | 47 ++ src/lib/blog.ts | 7 - src/lib/cms/fragments/blog-post.gql | 17 + src/lib/cms/generated.ts | 81 ++- src/lib/cms/graphql.schema.json | 521 +++++++++++++++++- src/lib/renderer/blog.module.scss | 81 +++ src/lib/renderer/index.js | 57 +- src/pages/blog/[slug].tsx | 9 +- 21 files changed, 1096 insertions(+), 63 deletions(-) create mode 100644 src/components/sections/blog/shares/icons/discord.js create mode 100644 src/components/sections/blog/shares/icons/facebook.js create mode 100644 src/components/sections/blog/shares/icons/instagram.js create mode 100644 src/components/sections/blog/shares/icons/reddit.js create mode 100644 src/components/sections/blog/shares/icons/telegram.js create mode 100644 src/components/sections/blog/shares/icons/twitter.js create mode 100644 src/components/sections/blog/shares/index.tsx create mode 100644 src/components/sections/blog/shares/shares.module.scss diff --git a/src/components/common/card/card.module.scss b/src/components/common/card/card.module.scss index 5fe6e25..1aff0e3 100644 --- a/src/components/common/card/card.module.scss +++ b/src/components/common/card/card.module.scss @@ -1,16 +1,139 @@ @import '~/css/helpers'; .card { + cursor: pointer; display: flex; flex-direction: column; - cursor: pointer; padding-bottom: tovw(2px, 'default', 2px); + white-space: normal; width: 100%; &-blog { + border-bottom: none; + + &:hover, + &:focus { + .read span { + &::after { + opacity: 0; + } + + &::before { + opacity: 1; + background-image: repeating-linear-gradient( + 0deg, + currentcolor, + currentcolor tovw(4px, 'default', 3px), + transparent tovw(4px, 'default', 3px), + transparent tovw(8px, 'default', 6px), + currentcolor tovw(8px, 'default', 6px) + ), + repeating-linear-gradient( + 90deg, + currentcolor, + currentcolor tovw(4px, 'default', 3px), + transparent tovw(4px, 'default', 3px), + transparent tovw(8px, 'default', 6px), + currentcolor tovw(8px, 'default', 6px) + ), + repeating-linear-gradient( + currentcolor, + currentcolor tovw(4px, 'default', 3px), + transparent tovw(4px, 'default', 3px), + transparent tovw(8px, 'default', 6px), + currentcolor tovw(8px, 'default', 6px) + ), + repeating-linear-gradient( + 270deg, + currentcolor, + currentcolor tovw(4px, 'default', 3px), + transparent tovw(4px, 'default', 3px), + transparent tovw(8px, 'default', 6px), + currentcolor tovw(8px, 'default', 6px) + ); + } + } + } + + &::after, + &::before { + content: none; + } + .content { height: auto; } + + .read { + span { + position: relative; + + &::before { + position: absolute; + top: 100%; + left: 0; + opacity: 0; + background-image: repeating-linear-gradient( + 0deg, + currentcolor, + currentcolor 100%, + transparent 100%, + transparent 100%, + currentcolor 100% + ), + repeating-linear-gradient( + 90deg, + currentcolor, + currentcolor 100%, + transparent 100%, + transparent 100%, + currentcolor 100% + ), + repeating-linear-gradient( + currentcolor, + currentcolor 100%, + transparent 100%, + transparent 100%, + currentcolor 100% + ), + repeating-linear-gradient( + 270deg, + currentcolor, + currentcolor 100%, + transparent 100%, + transparent 100%, + currentcolor 100% + ); + background-position: 0 0, 0 0, 100% 0, 0 100%; + background-repeat: no-repeat; + background-size: 0 100%, 100% 0, 0 100%, 100% 2px; + width: 100%; + height: tovw(1px, 'default', 1px); + animation: border var(--duration-normal) linear infinite; + content: ''; + pointer-events: none; + } + + &::after { + position: absolute; + top: 100%; + left: 0; + background: currentcolor; + width: 100%; + height: tovw(1px); + content: ''; + } + } + + svg { + height: 10px; + width: 10px; + } + } + + > svg { + display: none; + } } &__header { @@ -170,3 +293,15 @@ font-size: tovw(18px, 'default', 15px); } } + +@keyframes border { + from { + background-position: 0 0, tovw(8px, 'default', 6px) 0, + 100% tovw(8px, 'default', 6px), 0 100%; + } + + to { + background-position: 0 tovw(8px, 'default', 6px), 0 0, 100% 0, + tovw(8px, 'default', 6px) 100%; + } +} diff --git a/src/components/common/card/index.tsx b/src/components/common/card/index.tsx index 04c3645..dd6347f 100644 --- a/src/components/common/card/index.tsx +++ b/src/components/common/card/index.tsx @@ -1,6 +1,7 @@ import clsx from 'clsx' import NextLink from 'next/link' +import { ArrowLink } from '~/components/icons/arrow' import { Calendar, Clock } from '~/components/icons/events' import Heading from '~/components/primitives/heading' import Link from '~/components/primitives/link' @@ -74,7 +75,8 @@ export const BlogCard = ({ horizontal = false }: BlogCardProps) => { return ( -
{horizontal &&

{data && getDescription(data)}

} - READ ARTICLE +
+ READ ARTICLE +
- + ) } diff --git a/src/components/sections/blog/post-content/content.module.scss b/src/components/sections/blog/post-content/content.module.scss index e5de448..05d0bab 100644 --- a/src/components/sections/blog/post-content/content.module.scss +++ b/src/components/sections/blog/post-content/content.module.scss @@ -10,6 +10,7 @@ $img-height-mobile: 200px; @media screen and (max-width: 800px) { transform: none; + padding-bottom: tovw(56px, 'default', 56px); } .image__container { diff --git a/src/components/sections/blog/posts-grid/index.tsx b/src/components/sections/blog/posts-grid/index.tsx index 8e49d3b..74c727c 100644 --- a/src/components/sections/blog/posts-grid/index.tsx +++ b/src/components/sections/blog/posts-grid/index.tsx @@ -5,11 +5,13 @@ import s from './posts-grid.module.scss' interface PostsGridProps { children: React.ReactNode + title?: string } -const PostsGrid = ({ children }: PostsGridProps) => { +const PostsGrid = ({ children, title }: PostsGridProps) => { return (
+ {title &&

{title}

} {children}
) diff --git a/src/components/sections/blog/posts-grid/posts-grid.module.scss b/src/components/sections/blog/posts-grid/posts-grid.module.scss index d4b4c2e..a0aa6fc 100644 --- a/src/components/sections/blog/posts-grid/posts-grid.module.scss +++ b/src/components/sections/blog/posts-grid/posts-grid.module.scss @@ -9,6 +9,22 @@ row-gap: tovw(104px, 'default', 81px); @media screen and (max-width: 800px) { - grid-template-columns: 1fr; + display: flex; + flex-direction: column; + gap: tovw(80px, 'default', 80px); + } +} + +.title { + font-family: var(--font-arthemys); + font-size: tovw(76px, 'default', 42px); + font-weight: 400; + line-height: tovw(84px, 'default', 48px); + margin-top: 0; + margin-bottom: tovw(88px, 'default', 44px); + text-align: center; + + @media screen and (max-width: 800px) { + letter-spacing: -0.02em; } } diff --git a/src/components/sections/blog/search/search.module.scss b/src/components/sections/blog/search/search.module.scss index 6905ed1..c10390b 100644 --- a/src/components/sections/blog/search/search.module.scss +++ b/src/components/sections/blog/search/search.module.scss @@ -26,6 +26,7 @@ text-decoration: none; white-space: nowrap; font-size: tovw(12px, 'default', 12px); + line-height: tovw(16px, 'default', 16px); } } } diff --git a/src/components/sections/blog/shares/icons/discord.js b/src/components/sections/blog/shares/icons/discord.js new file mode 100644 index 0000000..ec0b4d2 --- /dev/null +++ b/src/components/sections/blog/shares/icons/discord.js @@ -0,0 +1,16 @@ +const Discord = (props) => ( + + + +) + +export default Discord diff --git a/src/components/sections/blog/shares/icons/facebook.js b/src/components/sections/blog/shares/icons/facebook.js new file mode 100644 index 0000000..933dcd0 --- /dev/null +++ b/src/components/sections/blog/shares/icons/facebook.js @@ -0,0 +1,16 @@ +const Facebook = (props) => ( + + + +) + +export default Facebook diff --git a/src/components/sections/blog/shares/icons/instagram.js b/src/components/sections/blog/shares/icons/instagram.js new file mode 100644 index 0000000..bd10f76 --- /dev/null +++ b/src/components/sections/blog/shares/icons/instagram.js @@ -0,0 +1,20 @@ +const Instagram = (props) => ( + + + + +) + +export default Instagram diff --git a/src/components/sections/blog/shares/icons/reddit.js b/src/components/sections/blog/shares/icons/reddit.js new file mode 100644 index 0000000..fa6daf3 --- /dev/null +++ b/src/components/sections/blog/shares/icons/reddit.js @@ -0,0 +1,18 @@ +const Reddit = (props) => ( + + + +) + +export default Reddit diff --git a/src/components/sections/blog/shares/icons/telegram.js b/src/components/sections/blog/shares/icons/telegram.js new file mode 100644 index 0000000..c3f36fc --- /dev/null +++ b/src/components/sections/blog/shares/icons/telegram.js @@ -0,0 +1,18 @@ +const Telegram = (props) => ( + + + +) + +export default Telegram diff --git a/src/components/sections/blog/shares/icons/twitter.js b/src/components/sections/blog/shares/icons/twitter.js new file mode 100644 index 0000000..b85f613 --- /dev/null +++ b/src/components/sections/blog/shares/icons/twitter.js @@ -0,0 +1,16 @@ +const Twitter = (props) => ( + + + +) + +export default Twitter diff --git a/src/components/sections/blog/shares/index.tsx b/src/components/sections/blog/shares/index.tsx new file mode 100644 index 0000000..981f135 --- /dev/null +++ b/src/components/sections/blog/shares/index.tsx @@ -0,0 +1,64 @@ +import Link from 'next/link' + +import { Container } from '~/components/layout/container' +import Section from '~/components/layout/section' + +import Discord from './icons/discord' +import Facebook from './icons/facebook' +import Instagram from './icons/instagram' +import Reddit from './icons/reddit' +import Telegram from './icons/telegram' +import Twitter from './icons/twitter' +import s from './shares.module.scss' + +interface SharesProps { + url: string +} + +const Shares = ({ url }: SharesProps) => { + const encodedUrl = encodeURIComponent(url) + + return ( +
+ +

SHARE THIS ARTICLE

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ ) +} + +export default Shares diff --git a/src/components/sections/blog/shares/shares.module.scss b/src/components/sections/blog/shares/shares.module.scss new file mode 100644 index 0000000..189d301 --- /dev/null +++ b/src/components/sections/blog/shares/shares.module.scss @@ -0,0 +1,47 @@ +@import '~/css/helpers'; + +.container { + border-top: 1px solid var(--color-white); + padding-top: tovw(96px, 'default', 48px); + padding-bottom: tovw(243px, 'default', 145px); + text-align: center; + max-width: tovw(856px, 'default', 856px); + + > p { + font-size: tovw(18px, 'default', 12px); + line-height: tovw(23px, 'default', 16px); + margin-bottom: tovw(56px, 'default', 32px); + + @media screen and (max-width: 800px) { + letter-spacing: -0.02em; + } + } + + > div { + display: flex; + gap: tovw(32px, 'default', 16px); + justify-content: center; + flex-wrap: wrap; + + a { + align-items: center; + background-color: var(--color-black); + border: 1px solid var(--color-white); + border-radius: 30px; + color: var(--color-white); + display: flex; + height: tovw(56px, 'default', 48px); + justify-content: center; + transition: filter 0.2s, filter 0.2s; + width: tovw(72px, 'default', 64px); + + svg { + height: 28px; + } + + &:hover { + filter: invert(1); + } + } + } +} diff --git a/src/lib/blog.ts b/src/lib/blog.ts index be64803..3192c0e 100644 --- a/src/lib/blog.ts +++ b/src/lib/blog.ts @@ -43,13 +43,6 @@ export const getBlogPosts = async ({ export const getBlogPostsCategories = async () => { const { allCategories } = await cms().GetBlogPostsCategories() - // Push dummy category - allCategories.push({ - id: '0', - slug: 'none', - title: 'No Category' - }) - return allCategories } diff --git a/src/lib/cms/fragments/blog-post.gql b/src/lib/cms/fragments/blog-post.gql index 427307d..1e41084 100644 --- a/src/lib/cms/fragments/blog-post.gql +++ b/src/lib/cms/fragments/blog-post.gql @@ -14,6 +14,23 @@ fragment BlogPost on BlogPostRecord { slug content { value + blocks { + __typename + ... on ImageRecord { + id + title + asset { + url + width + height + } + } + ... on CtaRecord { + id + label + url + } + } } image { ...Image diff --git a/src/lib/cms/generated.ts b/src/lib/cms/generated.ts index 056e452..c087724 100644 --- a/src/lib/cms/generated.ts +++ b/src/lib/cms/generated.ts @@ -99,9 +99,11 @@ export type AuthorRecord_SeoMetaTagsArgs = { locale?: InputMaybe; }; +export type BlogPostModelContentBlocksField = CtaRecord | ImageRecord; + export type BlogPostModelContentField = { __typename?: 'BlogPostModelContentField'; - blocks: Array; + blocks: Array; links: Array; value: Scalars['JsonField']; }; @@ -317,6 +319,33 @@ export type CreatedAtFilter = { neq?: InputMaybe; }; +/** Record of type Cta (cta) */ +export type CtaRecord = { + __typename?: 'CtaRecord'; + _createdAt: Scalars['DateTime']; + _firstPublishedAt?: Maybe; + _isValid: Scalars['BooleanType']; + _modelApiKey: Scalars['String']; + _publicationScheduledAt?: Maybe; + _publishedAt?: Maybe; + /** SEO meta tags */ + _seoMetaTags: Array; + _status: ItemStatus; + _unpublishingScheduledAt?: Maybe; + _updatedAt: Scalars['DateTime']; + createdAt: Scalars['DateTime']; + id: Scalars['ItemId']; + label?: Maybe; + updatedAt: Scalars['DateTime']; + url?: Maybe; +}; + + +/** Record of type Cta (cta) */ +export type CtaRecord_SeoMetaTagsArgs = { + locale?: InputMaybe; +}; + /** Specifies how to filter Date fields */ export type DateFilter = { /** Search for records with an exact match */ @@ -440,6 +469,33 @@ export type GlobalSeoField = { twitterAccount?: Maybe; }; +/** Record of type Image (image) */ +export type ImageRecord = { + __typename?: 'ImageRecord'; + _createdAt: Scalars['DateTime']; + _firstPublishedAt?: Maybe; + _isValid: Scalars['BooleanType']; + _modelApiKey: Scalars['String']; + _publicationScheduledAt?: Maybe; + _publishedAt?: Maybe; + /** SEO meta tags */ + _seoMetaTags: Array; + _status: ItemStatus; + _unpublishingScheduledAt?: Maybe; + _updatedAt: Scalars['DateTime']; + asset?: Maybe; + createdAt: Scalars['DateTime']; + id: Scalars['ItemId']; + title?: Maybe; + updatedAt: Scalars['DateTime']; +}; + + +/** Record of type Image (image) */ +export type ImageRecord_SeoMetaTagsArgs = { + locale?: InputMaybe; +}; + export type ImgixParams = { /** * Aspect Ratio @@ -2512,7 +2568,7 @@ export type FocalPoint = { export type AuthorFragment = { __typename?: 'AuthorRecord', name?: string | null }; -export type BlogPostFragment = { __typename?: 'BlogPostRecord', title?: string | null, date?: any | null, slug?: string | null, _seoMetaTags: Array<{ __typename?: 'Tag', content?: string | null, tag: string, attributes?: any | null }>, category: Array<{ __typename?: 'CategoryRecord', title?: string | null, slug?: string | null }>, author?: { __typename?: 'AuthorRecord', name?: string | null } | null, content?: { __typename?: 'BlogPostModelContentField', value: any } | null, image?: { __typename?: 'FileField', url: string, alt?: string | null, height?: any | null, width?: any | null, title?: string | null } | null }; +export type BlogPostFragment = { __typename?: 'BlogPostRecord', title?: string | null, date?: any | null, slug?: string | null, _seoMetaTags: Array<{ __typename?: 'Tag', content?: string | null, tag: string, attributes?: any | null }>, category: Array<{ __typename?: 'CategoryRecord', title?: string | null, slug?: string | null }>, author?: { __typename?: 'AuthorRecord', name?: string | null } | null, content?: { __typename?: 'BlogPostModelContentField', value: any, blocks: Array<{ __typename: 'CtaRecord', id: any, label?: string | null, url?: string | null } | { __typename: 'ImageRecord', id: any, title?: string | null, asset?: { __typename?: 'FileField', url: string, width?: any | null, height?: any | null } | null }> } | null, image?: { __typename?: 'FileField', url: string, alt?: string | null, height?: any | null, width?: any | null, title?: string | null } | null }; export type CategoryFragment = { __typename?: 'CategoryRecord', id: any, title?: string | null, slug?: string | null }; @@ -2535,7 +2591,7 @@ export type GetBlogPostsQueryVariables = Exact<{ }>; -export type GetBlogPostsQuery = { __typename?: 'Query', _allBlogPostsMeta: { __typename?: 'CollectionMetadata', count: any }, allBlogPosts: Array<{ __typename?: 'BlogPostRecord', title?: string | null, date?: any | null, slug?: string | null, _seoMetaTags: Array<{ __typename?: 'Tag', content?: string | null, tag: string, attributes?: any | null }>, category: Array<{ __typename?: 'CategoryRecord', title?: string | null, slug?: string | null }>, author?: { __typename?: 'AuthorRecord', name?: string | null } | null, content?: { __typename?: 'BlogPostModelContentField', value: any } | null, image?: { __typename?: 'FileField', url: string, alt?: string | null, height?: any | null, width?: any | null, title?: string | null } | null }> }; +export type GetBlogPostsQuery = { __typename?: 'Query', _allBlogPostsMeta: { __typename?: 'CollectionMetadata', count: any }, allBlogPosts: Array<{ __typename?: 'BlogPostRecord', title?: string | null, date?: any | null, slug?: string | null, _seoMetaTags: Array<{ __typename?: 'Tag', content?: string | null, tag: string, attributes?: any | null }>, category: Array<{ __typename?: 'CategoryRecord', title?: string | null, slug?: string | null }>, author?: { __typename?: 'AuthorRecord', name?: string | null } | null, content?: { __typename?: 'BlogPostModelContentField', value: any, blocks: Array<{ __typename: 'CtaRecord', id: any, label?: string | null, url?: string | null } | { __typename: 'ImageRecord', id: any, title?: string | null, asset?: { __typename?: 'FileField', url: string, width?: any | null, height?: any | null } | null }> } | null, image?: { __typename?: 'FileField', url: string, alt?: string | null, height?: any | null, width?: any | null, title?: string | null } | null }> }; export type BlogPostsSlugQueryVariables = Exact<{ skip: Scalars['IntType']; @@ -2550,7 +2606,7 @@ export type SingleBlogPostQueryVariables = Exact<{ }>; -export type SingleBlogPostQuery = { __typename?: 'Query', blogPost?: { __typename?: 'BlogPostRecord', title?: string | null, date?: any | null, slug?: string | null, _seoMetaTags: Array<{ __typename?: 'Tag', content?: string | null, tag: string, attributes?: any | null }>, category: Array<{ __typename?: 'CategoryRecord', title?: string | null, slug?: string | null }>, author?: { __typename?: 'AuthorRecord', name?: string | null } | null, content?: { __typename?: 'BlogPostModelContentField', value: any } | null, image?: { __typename?: 'FileField', url: string, alt?: string | null, height?: any | null, width?: any | null, title?: string | null } | null } | null }; +export type SingleBlogPostQuery = { __typename?: 'Query', blogPost?: { __typename?: 'BlogPostRecord', title?: string | null, date?: any | null, slug?: string | null, _seoMetaTags: Array<{ __typename?: 'Tag', content?: string | null, tag: string, attributes?: any | null }>, category: Array<{ __typename?: 'CategoryRecord', title?: string | null, slug?: string | null }>, author?: { __typename?: 'AuthorRecord', name?: string | null } | null, content?: { __typename?: 'BlogPostModelContentField', value: any, blocks: Array<{ __typename: 'CtaRecord', id: any, label?: string | null, url?: string | null } | { __typename: 'ImageRecord', id: any, title?: string | null, asset?: { __typename?: 'FileField', url: string, width?: any | null, height?: any | null } | null }> } | null, image?: { __typename?: 'FileField', url: string, alt?: string | null, height?: any | null, width?: any | null, title?: string | null } | null } | null }; export const SeoTagsFragmentDoc = gql` fragment SEOTags on Tag { @@ -2590,6 +2646,23 @@ export const BlogPostFragmentDoc = gql` slug content { value + blocks { + __typename + ... on ImageRecord { + id + title + asset { + url + width + height + } + } + ... on CtaRecord { + id + label + url + } + } } image { ...Image diff --git a/src/lib/cms/graphql.schema.json b/src/lib/cms/graphql.schema.json index 4182a05..6bd4919 100644 --- a/src/lib/cms/graphql.schema.json +++ b/src/lib/cms/graphql.schema.json @@ -568,6 +568,27 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "UNION", + "name": "BlogPostModelContentBlocksField", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "CtaRecord", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "ImageRecord", + "ofType": null + } + ] + }, { "kind": "OBJECT", "name": "BlogPostModelContentField", @@ -587,8 +608,8 @@ "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "String", + "kind": "UNION", + "name": "BlogPostModelContentBlocksField", "ofType": null } } @@ -2302,6 +2323,254 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "CtaRecord", + "description": "Record of type Cta (cta)", + "fields": [ + { + "name": "_createdAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_firstPublishedAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_isValid", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "BooleanType", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_modelApiKey", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_publicationScheduledAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_publishedAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_seoMetaTags", + "description": "SEO meta tags", + "args": [ + { + "name": "locale", + "description": "The locale to use to fetch the field's content", + "type": { + "kind": "ENUM", + "name": "SiteLocale", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Tag", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_status", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "ItemStatus", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_unpublishingScheduledAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_updatedAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "createdAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ItemId", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "label", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updatedAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "url", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "SCALAR", "name": "CustomData", @@ -3276,6 +3545,254 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "ImageRecord", + "description": "Record of type Image (image)", + "fields": [ + { + "name": "_createdAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_firstPublishedAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_isValid", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "BooleanType", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_modelApiKey", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_publicationScheduledAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_publishedAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_seoMetaTags", + "description": "SEO meta tags", + "args": [ + { + "name": "locale", + "description": "The locale to use to fetch the field's content", + "type": { + "kind": "ENUM", + "name": "SiteLocale", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Tag", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_status", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "ItemStatus", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_unpublishingScheduledAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "_updatedAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "asset", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "FileField", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "createdAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ItemId", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "title", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updatedAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "ImgixParams", diff --git a/src/lib/renderer/blog.module.scss b/src/lib/renderer/blog.module.scss index 71a0e91..446ab1f 100644 --- a/src/lib/renderer/blog.module.scss +++ b/src/lib/renderer/blog.module.scss @@ -1,8 +1,33 @@ @import '~/css/helpers'; .structured { + font-family: var(--font-tt-hoves); max-width: tovw(856px, 'default', 856px); + h1 { + margin-top: tovw(124px, 'default', 72px); + font-family: var(--font-arthemys); + font-size: tovw(58px, 'default', 30px); + line-height: 1; + + @media screen and (max-width: 800px) { + line-height: 124%; + } + } + + h2 { + font-family: var(--font-tt-hoves); + font-size: tovw(40px, 'default', 24px); + font-weight: 400; + line-height: 1; + margin-top: tovw(44px, 'default', 40px); + margin-bottom: tovw(44px, 'default', 24px); + + @media screen and (max-width: 800px) { + line-height: 160%; + } + } + p { font-family: var(--font-tt-hoves); font-size: tovw(22px, 'default', 15px); @@ -10,4 +35,60 @@ letter-spacing: -0.01em; margin: tovw(44px, 'default', 40px) 0; } + + blockquote { + margin: tovw(76px, 'default', 40px) 0; + border-left: 4px solid var(--color-accent); + padding-left: tovw(87px, 'default', 22px); + + p { + font-size: tovw(28px, 'default', 18px); + line-height: tovw(39px, 'default', 29px); + } + + footer { + font-size: tovw(20px, 'default', 15px); + line-height: tovw(32px, 'default', 24px); + } + } + + ul { + margin: tovw(76px, 'default', 40px) 0; + padding-left: tovw(72px, 'default', 12px); + list-style: none; + position: relative; + + li { + &::before { + content: ''; + position: absolute; + border-radius: 50%; + height: 8px; + width: 8px; + margin-top: 9px; + background: var(--color-accent); + left: 0; + } + } + } + + code { + border: tovw(1px, 'default', 1px) solid var(--color-grey-light); + border-radius: tovw(8px, 'default', 4px); + background: var(--color-black); + display: block; + font-family: var(--font-dm-mono), sans-serif; + padding: tovw(40px, 'default', 22px); + word-break: break-all; + white-space: break-spaces; + } +} + +.img { + margin: tovw(136px, 'default', 72px) auto; + + > img { + object-fit: cover; + width: 100%; + } } diff --git a/src/lib/renderer/index.js b/src/lib/renderer/index.js index 2e97697..fb65140 100644 --- a/src/lib/renderer/index.js +++ b/src/lib/renderer/index.js @@ -2,60 +2,31 @@ import { StructuredText } from 'react-datocms' import Marked from '~/components/common/marked' import { Container } from '~/components/layout/container' +import { ButtonLink } from '~/components/primitives/button' import s from './blog.module.scss' export const renderBlock = ({ record }) => { switch (record.__typename) { + case 'CalloutRecord': + return {record.content ?? ''} case 'ImageRecord': return ( -
+
{record.image?.alt
) - case 'CalloutRecord': - return {record.content ?? ''} - // case 'ShareLinkRecord': - // return ( - //
- // - // - // - // - //
- // ) - // case 'CtaRecord': - // return ( - // { - // return { - // href: link?.href ?? '', - // as: 'a', - // children: link?.label ?? '', - // variant: i === 0 ? 'primary' : 'tertiary', - // size: 'lg' - // } - // }) ?? [] - // } - // /> - // ) - // case 'TableRecord': - // return ( - // - // - //
- // {record.markdownTable ?? ''} - //
- //
- //
- // ) + case 'CtaRecord': + return ( + + {record.label} + + ) default: return null } diff --git a/src/pages/blog/[slug].tsx b/src/pages/blog/[slug].tsx index 24a3c35..92445b5 100644 --- a/src/pages/blog/[slug].tsx +++ b/src/pages/blog/[slug].tsx @@ -11,6 +11,7 @@ import { InferGetStaticPropsType } from 'next' import Error from 'next/error' +import { useRouter } from 'next/router' import { Key } from 'react' import { BlogCard } from '~/components/common/card' @@ -18,6 +19,7 @@ import { Meta } from '~/components/common/meta' import Content from '~/components/sections/blog/post-content' import Hero from '~/components/sections/blog/post-hero' import PostsGrid from '~/components/sections/blog/posts-grid' +import Shares from '~/components/sections/blog/shares' import { BlogPostFragment } from '~/lib/cms/generated' const BlogPost = ({ @@ -28,12 +30,17 @@ const BlogPost = ({ return } + const router = useRouter() + // `${location.origin}${router.asPath}` + const completeUrl = `https://laconic.com${router.asPath}` + return ( - + + {latestPosts.map((relatedPost: BlogPostFragment, index: Key) => { return (