diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-05-03 16:53:55 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-05-03 16:53:55 +0200 |
| commit | ee04742d1f0645908baa30e47845126c28848f50 (patch) | |
| tree | 1d80dca17437be6a351e932885e95c940833e571 /src | |
| parent | 83a029084f1bbfd78b7099d9bea3371d4533c6d9 (diff) | |
chore: add a CV page
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/atoms/links/link.module.scss | 87 | ||||
| -rw-r--r-- | src/components/atoms/links/link.stories.tsx | 20 | ||||
| -rw-r--r-- | src/components/atoms/links/link.tsx | 18 | ||||
| -rw-r--r-- | src/components/molecules/images/responsive-image.module.scss | 11 | ||||
| -rw-r--r-- | src/components/molecules/images/responsive-image.stories.tsx | 1 | ||||
| -rw-r--r-- | src/components/molecules/images/responsive-image.tsx | 6 | ||||
| -rw-r--r-- | src/components/organisms/widgets/image-widget.module.scss | 13 | ||||
| -rw-r--r-- | src/components/organisms/widgets/image-widget.stories.tsx | 25 | ||||
| -rw-r--r-- | src/components/organisms/widgets/image-widget.test.tsx | 11 | ||||
| -rw-r--r-- | src/components/organisms/widgets/image-widget.tsx | 18 | ||||
| -rw-r--r-- | src/pages/cv.tsx | 198 | ||||
| -rw-r--r-- | src/styles/pages/cv.module.scss | 3 |
12 files changed, 375 insertions, 36 deletions
diff --git a/src/components/atoms/links/link.module.scss b/src/components/atoms/links/link.module.scss index d23667a..1b89727 100644 --- a/src/components/atoms/links/link.module.scss +++ b/src/components/atoms/links/link.module.scss @@ -5,23 +5,64 @@ &[hreflang] { &::after { display: inline-block; + /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ content: "\0000a0[" attr(hreflang) "]"; font-size: var(--font-size-sm); } } + &--download { + &::after { + display: inline-block; + + /* Prettier is removing spacing between content parts. */ + + /* prettier-ignore */ + content: "\0000a0" url( +fun.encode-svg( + '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>' +)); + } + + &:focus:not(:active)::after { + /* Prettier is removing spacing between content parts. */ + + /* prettier-ignore */ + content: "\0000a0" url( +fun.encode-svg( + '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_white}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>' +)); + } + + &[hreflang] { + &::after { + /* Prettier is removing spacing between content parts. */ + + /* prettier-ignore */ + content: "\0000a0[" attr(hreflang) "]\0000a0" url( +fun.encode-svg( + '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>' +)); + } + } + } + &--external { &::after { display: inline-block; + /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); } &:focus:not(:active)::after { /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); } @@ -29,18 +70,56 @@ &[hreflang] { &::after { /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg( + content: "\0000a0[" attr(hreflang) "]\0000a0" url( +fun.encode-svg( '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>' - )); +)); } &:focus:not(:active)::after { /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg( + content: "\0000a0[" attr(hreflang) "]\0000a0" url( +fun.encode-svg( '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>' - )); +)); + } + } + } + + &--external#{&}--download { + &::after { + /* Prettier is removing spacing between content parts. */ + + /* prettier-ignore */ + content: "\0000a0" url( +fun.encode-svg( + '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>' +)) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); + } + + &[hreflang] { + &::after { + /* Prettier is removing spacing between content parts. */ + + /* prettier-ignore */ + content: "\0000a0[" attr(hreflang) "]\0000a0" url( +fun.encode-svg( + '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>' +)) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); + } + + &:focus:not(:active)::after { + /* Prettier is removing spacing between content parts. */ + + /* prettier-ignore */ + content: "\0000a0[" attr(hreflang) "]\0000a0" url( +fun.encode-svg( + '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>' +)) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); } } } diff --git a/src/components/atoms/links/link.stories.tsx b/src/components/atoms/links/link.stories.tsx index c3b3465..420eafe 100644 --- a/src/components/atoms/links/link.stories.tsx +++ b/src/components/atoms/links/link.stories.tsx @@ -31,14 +31,29 @@ export default { required: false, }, }, - external: { + download: { control: { type: 'boolean', }, + description: 'Determine if the link purpose is to download a file.', table: { category: 'Options', + defaultValue: { summary: false }, + }, + type: { + name: 'boolean', + required: false, + }, + }, + external: { + control: { + type: 'boolean', }, description: 'Determine if the link is external of the current website.', + table: { + category: 'Options', + defaultValue: { summary: false }, + }, type: { name: 'boolean', required: false, @@ -79,6 +94,7 @@ export const Default = Template.bind({}); Default.args = { children: 'A link', href: '#', + download: false, external: false, }; @@ -89,6 +105,7 @@ export const External = Template.bind({}); External.args = { children: 'A link', href: '#', + download: false, external: true, }; @@ -99,6 +116,7 @@ export const ExternalWithLang = Template.bind({}); ExternalWithLang.args = { children: 'A link', href: '#', + download: false, external: true, lang: 'en', }; diff --git a/src/components/atoms/links/link.tsx b/src/components/atoms/links/link.tsx index 674c07b..c8ba273 100644 --- a/src/components/atoms/links/link.tsx +++ b/src/components/atoms/links/link.tsx @@ -12,6 +12,10 @@ export type LinkProps = { */ className?: string; /** + * True if it is a download link. Default: false. + */ + download?: boolean; + /** * True if it is an external link. Default: false. */ external?: boolean; @@ -33,21 +37,29 @@ export type LinkProps = { const Link: FC<LinkProps> = ({ children, className = '', + download = false, + external = false, href, lang, - external = false, }) => { + const downloadClass = download ? styles['link--download'] : ''; + return external ? ( <a href={href} hrefLang={lang} - className={`${styles.link} ${styles['link--external']} ${className}`} + className={`${styles.link} ${styles['link--external']} ${downloadClass} ${className}`} > {children} </a> ) : ( <NextLink href={href}> - <a className={`${styles.link} ${className}`}>{children}</a> + <a + hrefLang={lang} + className={`${styles.link} ${downloadClass} ${className}`} + > + {children} + </a> </NextLink> ); }; diff --git a/src/components/molecules/images/responsive-image.module.scss b/src/components/molecules/images/responsive-image.module.scss index 3566421..3a4f283 100644 --- a/src/components/molecules/images/responsive-image.module.scss +++ b/src/components/molecules/images/responsive-image.module.scss @@ -3,8 +3,7 @@ .wrapper { display: flex; flex-flow: column; - width: 100%; - max-width: max-content; + width: fit-content; margin: 0; position: relative; text-align: center; @@ -14,9 +13,7 @@ margin: 0; padding: fun.convert-px(4) var(--spacing-2xs); background: var(--color-bg-secondary); - border: fun.convert-px(1) solid var(--color-border); - box-shadow: 0 fun.convert-px(-1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow-light); + border: fun.convert-px(1) solid var(--color-border-light); font-weight: 500; } @@ -32,7 +29,7 @@ &:hover, &:focus { - transform: scale(1.1); + transform: scale(var(--scale-up, 1.05)); } &:focus { @@ -43,7 +40,7 @@ } &:active { - transform: scale(0.9); + transform: scale(var(--scale-down, 0.95)); .caption { text-decoration: none; diff --git a/src/components/molecules/images/responsive-image.stories.tsx b/src/components/molecules/images/responsive-image.stories.tsx index a1f5295..35d9116 100644 --- a/src/components/molecules/images/responsive-image.stories.tsx +++ b/src/components/molecules/images/responsive-image.stories.tsx @@ -64,6 +64,7 @@ export default { required: false, }, }, + unoptimized: { table: { disable: true } }, width: { control: { type: 'number', diff --git a/src/components/molecules/images/responsive-image.tsx b/src/components/molecules/images/responsive-image.tsx index 31cbcd1..d07dd6c 100644 --- a/src/components/molecules/images/responsive-image.tsx +++ b/src/components/molecules/images/responsive-image.tsx @@ -1,6 +1,6 @@ import Link, { type LinkProps } from '@components/atoms/links/link'; import Image, { type ImageProps } from 'next/image'; -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './responsive-image.module.scss'; export type ResponsiveImageProps = Omit< @@ -14,7 +14,7 @@ export type ResponsiveImageProps = Omit< /** * A figure caption. */ - caption?: string; + caption?: ReactNode; /** * Set additional classnames to the figure wrapper. */ @@ -55,6 +55,7 @@ const ResponsiveImage: FC<ResponsiveImageProps> = ({ alt={alt} layout={layout || 'intrinsic'} objectFit={objectFit || 'contain'} + className={styles.img} {...props} /> {caption && ( @@ -67,6 +68,7 @@ const ResponsiveImage: FC<ResponsiveImageProps> = ({ alt={alt} layout={layout || 'intrinsic'} objectFit={objectFit || 'contain'} + className={styles.img} {...props} /> {caption && ( diff --git a/src/components/organisms/widgets/image-widget.module.scss b/src/components/organisms/widgets/image-widget.module.scss index 8e4d0aa..0d69441 100644 --- a/src/components/organisms/widgets/image-widget.module.scss +++ b/src/components/organisms/widgets/image-widget.module.scss @@ -1,7 +1,12 @@ @use "@styles/abstracts/functions" as fun; -.img { +.figure { + --scale-up: 1.02; + --scale-down: 0.98; + margin: 0; + padding: fun.convert-px(5); + border: fun.convert-px(1) solid var(--color-border); } .txt { @@ -10,7 +15,7 @@ .widget { &--left { - .img { + .figure { margin-right: auto; } @@ -20,7 +25,7 @@ } &--center { - .img { + .figure { margin-left: auto; margin-right: auto; } @@ -31,7 +36,7 @@ } &--right { - .img { + .figure { margin-left: auto; } diff --git a/src/components/organisms/widgets/image-widget.stories.tsx b/src/components/organisms/widgets/image-widget.stories.tsx index 27871ae..3014b36 100644 --- a/src/components/organisms/widgets/image-widget.stories.tsx +++ b/src/components/organisms/widgets/image-widget.stories.tsx @@ -50,7 +50,7 @@ export default { required: true, }, }, - img: { + image: { description: 'An image object.', type: { name: 'object', @@ -58,6 +58,19 @@ export default { value: {}, }, }, + imageClassName: { + control: { + type: 'text', + }, + description: 'Set additional classnames to the image wrapper.', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, level: { control: { type: 'number', @@ -107,7 +120,7 @@ const Template: ComponentStory<typeof ImageWidget> = (args) => ( <ImageWidget {...args} /> ); -const img = { +const image = { alt: 'Et perferendis quaerat', height: 480, src: 'http://placeimg.com/640/480/nature', @@ -122,7 +135,7 @@ export const AlignLeft = Template.bind({}); AlignLeft.args = { alignment: 'left', expanded: true, - img, + image, level: 2, title: 'Quo et totam', }; @@ -134,7 +147,7 @@ export const AlignCenter = Template.bind({}); AlignCenter.args = { alignment: 'center', expanded: true, - img, + image, level: 2, title: 'Quo et totam', }; @@ -146,7 +159,7 @@ export const AlignRight = Template.bind({}); AlignRight.args = { alignment: 'right', expanded: true, - img, + image, level: 2, title: 'Quo et totam', }; @@ -158,7 +171,7 @@ export const WithDescription = Template.bind({}); WithDescription.args = { description: 'Sint enim harum', expanded: true, - img, + image, level: 2, title: 'Quo et totam', }; diff --git a/src/components/organisms/widgets/image-widget.test.tsx b/src/components/organisms/widgets/image-widget.test.tsx index 8c24bd9..c6b1a3a 100644 --- a/src/components/organisms/widgets/image-widget.test.tsx +++ b/src/components/organisms/widgets/image-widget.test.tsx @@ -18,7 +18,12 @@ const url = '/another-page'; describe('ImageWidget', () => { it('renders an image', () => { render( - <ImageWidget expanded={true} img={img} title={title} level={titleLevel} /> + <ImageWidget + expanded={true} + image={img} + title={title} + level={titleLevel} + /> ); expect(screen.getByRole('img', { name: img.alt })).toBeInTheDocument(); }); @@ -27,7 +32,7 @@ describe('ImageWidget', () => { render( <ImageWidget expanded={true} - img={img} + image={img} title={title} level={titleLevel} url={url} @@ -43,7 +48,7 @@ describe('ImageWidget', () => { render( <ImageWidget expanded={true} - img={img} + image={img} description={description} title={title} level={titleLevel} diff --git a/src/components/organisms/widgets/image-widget.tsx b/src/components/organisms/widgets/image-widget.tsx index ba04c6a..873337b 100644 --- a/src/components/organisms/widgets/image-widget.tsx +++ b/src/components/organisms/widgets/image-widget.tsx @@ -14,7 +14,7 @@ export type Image = Pick< export type ImageWidgetProps = Pick< WidgetProps, - 'expanded' | 'level' | 'title' + 'className' | 'expanded' | 'level' | 'title' > & { /** * The content alignment. @@ -27,7 +27,11 @@ export type ImageWidgetProps = Pick< /** * An object describing the image. */ - img: Image; + image: Image; + /** + * Set additional classnames to the image wrapper. + */ + imageClassName?: string; /** * Add a link to the image. */ @@ -41,20 +45,22 @@ export type ImageWidgetProps = Pick< */ const ImageWidget: FC<ImageWidgetProps> = ({ alignment = 'left', + className = '', description, - img, + image, + imageClassName = '', url, ...props }) => { const alignmentClass = `widget--${alignment}`; return ( - <Widget className={styles[alignmentClass]} {...props}> + <Widget className={`${styles[alignmentClass]} ${className}`} {...props}> <ResponsiveImage target={url} caption={description} - className={styles.img} - {...img} + className={`${styles.figure} ${imageClassName}`} + {...image} /> </Widget> ); diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx new file mode 100644 index 0000000..d47edc6 --- /dev/null +++ b/src/pages/cv.tsx @@ -0,0 +1,198 @@ +import Link from '@components/atoms/links/link'; +import { type BreadcrumbItem } from '@components/molecules/nav/breadcrumb'; +import ImageWidget from '@components/organisms/widgets/image-widget'; +import SocialMedia from '@components/organisms/widgets/social-media'; +import PageLayout, { + type PageLayoutProps, +} from '@components/templates/page/page-layout'; +import CVContent, { data, meta } from '@content/pages/cv.mdx'; +import styles from '@styles/pages/cv.module.scss'; +import { getFormattedDate } from '@utils/helpers/dates'; +import { loadTranslation } from '@utils/helpers/i18n'; +import useSettings from '@utils/hooks/use-settings'; +import { GetStaticProps, NextPage } from 'next'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import Script from 'next/script'; +import { ReactNode } from 'react'; +import { useIntl } from 'react-intl'; +import { AboutPage, Graph, WebPage } from 'schema-dts'; + +/** + * CV page. + */ +const CVPage: NextPage = () => { + const intl = useIntl(); + const { file, image } = data; + 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: 'cv', name: title, url: '/cv' }, + ]; + + const imageWidgetTitle = intl.formatMessage({ + defaultMessage: 'Others formats', + description: 'CVPage: cv preview widget title', + id: 'B9OCyV', + }); + const socialMediaTitle = intl.formatMessage({ + defaultMessage: 'Open-source projects', + description: 'CVPage: social media widget title', + id: '+Dre5J', + }); + + 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 { website } = useSettings(); + const cvAlt = intl.formatMessage( + { + defaultMessage: '{name} CV', + description: 'CVPage: CV image alternative text', + id: 'KUowUk', + }, + { name: website.name } + ); + const cvCaption = intl.formatMessage( + { + defaultMessage: '<link>Download the CV in PDF</link>', + id: 'fN04AJ', + description: 'CVPage: download CV in PDF text', + }, + { + link: (chunks: ReactNode) => ( + <Link download={true} href={file}> + {chunks} + </Link> + ), + } + ); + + const widgets = [ + <ImageWidget + key="image-widget" + expanded={true} + title={imageWidgetTitle} + level={2} + image={{ alt: cvAlt, ...image }} + description={cvCaption} + imageClassName={styles.image} + />, + <SocialMedia + key="social-media" + title={socialMediaTitle} + level={2} + media={[ + { name: 'Github', url: 'https://github.com/ArmandPhilippot' }, + { name: 'Gitlab', url: 'https://gitlab.com/ArmandPhilippot' }, + { + name: 'LinkedIn', + url: 'https://www.linkedin.com/in/armandphilippot', + }, + ]} + />, + ]; + + 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, + reviewedBy: { '@id': `${website.url}/#branding` }, + url: `${pageUrl}`, + isPartOf: { + '@id': `${website.url}`, + }, + }; + + const cvSchema: AboutPage = { + '@id': `${website.url}/#cv`, + '@type': 'AboutPage', + name: seo.title, + description: intro, + author: { '@id': `${website.url}/#branding` }, + creator: { '@id': `${website.url}/#branding` }, + dateCreated: pagePublicationDate.toISOString(), + dateModified: pageUpdateDate.toISOString(), + datePublished: pagePublicationDate.toISOString(), + editor: { '@id': `${website.url}/#branding` }, + image: image.src, + inLanguage: website.locales.default, + license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', + thumbnailUrl: image.src, + mainEntityOfPage: { '@id': `${pageUrl}` }, + }; + + const schemaJsonLd: Graph = { + '@context': 'https://schema.org', + '@graph': [webpageSchema, cvSchema], + }; + + return ( + <PageLayout + title={title} + intro={intro} + headerMeta={headerMeta} + breadcrumb={breadcrumb} + withToC={true} + widgets={widgets} + > + <Head> + <title>{`${seo.title} - ${website.name}`}</title> + <meta name="description" content={seo.description} /> + <meta property="og:url" content={`${pageUrl}`} /> + <meta property="og:type" content="article" /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={intro} /> + <meta property="og:image" content={image.src} /> + <meta property="og:image:alt" content={title} /> + </Head> + <Script + id="schema-cv" + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} + /> + <CVContent /> + </PageLayout> + ); +}; + +export const getStaticProps: GetStaticProps = async ({ locale }) => { + const translation = await loadTranslation(locale); + + return { + props: { + translation, + }, + }; +}; + +export default CVPage; diff --git a/src/styles/pages/cv.module.scss b/src/styles/pages/cv.module.scss new file mode 100644 index 0000000..615c50d --- /dev/null +++ b/src/styles/pages/cv.module.scss @@ -0,0 +1,3 @@ +.image { + max-width: min(100%, 25ch); +} |
