diff options
192 files changed, 9 insertions, 13156 deletions
diff --git a/__tests__/jest/components/Branding.test.tsx b/__tests__/jest/components/Branding.test.tsx deleted file mode 100644 index 14266be..0000000 --- a/__tests__/jest/components/Branding.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import Branding from '@components/Branding/Branding'; -import { render, screen } from '@test-utils'; - -describe('Branding', () => { - it('renders the title wrapped with an h1 element on homepage', () => { - render(<Branding isHome={true} />); - expect( - screen.getByRole('heading', { level: 1, name: 'Armand Philippot' }) - ).toBeInTheDocument(); - }); - - it('renders the title wrapped without an h1 element on other pages', () => { - render(<Branding isHome={false} />); - expect( - screen.queryByRole('heading', { level: 1, name: 'Armand Philippot' }) - ).not.toBeInTheDocument(); - }); - - it('renders the baseline', () => { - render(<Branding isHome={false} />); - // Currently, only French translation is returned. - expect(screen.getByText('Intégrateur web')).toBeInTheDocument(); - }); -}); diff --git a/__tests__/jest/components/Copyright.test.tsx b/__tests__/jest/components/Copyright.test.tsx deleted file mode 100644 index 08beb50..0000000 --- a/__tests__/jest/components/Copyright.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import Copyright from '@components/Copyright/Copyright'; -import { render, screen } from '@test-utils'; - -describe('Copyright', () => { - it('renders the Copyright component', () => { - render(<Copyright />); - }); - - it('displays author name', () => { - render(<Copyright />); - expect(screen.getByText('Armand Philippot')).toBeInTheDocument(); - }); -}); diff --git a/__tests__/jest/components/Header.test.tsx b/__tests__/jest/components/Header.test.tsx deleted file mode 100644 index 44c6871..0000000 --- a/__tests__/jest/components/Header.test.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import Header from '@components/Header/Header'; -import { render } from '@test-utils'; - -// Toolbar uses forwardRef. Without mocking an error occurred. -jest.mock('@components/Toolbar/Toolbar', () => 'div'); - -describe('Header', () => { - it('renders the Header component', () => { - const { container } = render(<Header isHome={false} />); - expect(container).toBeTruthy(); - }); -}); diff --git a/src/components/Branding/Branding.module.scss b/src/components/Branding/Branding.module.scss deleted file mode 100644 index 2cd3b15..0000000 --- a/src/components/Branding/Branding.module.scss +++ /dev/null @@ -1,169 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - --logo-size: clamp(#{fun.convert-px(68)}, 18vw, #{fun.convert-px(100)}); - - display: grid; - grid-template-columns: - var(--logo-size) - minmax(0, 1fr); - grid-template-rows: repeat(2, max-content); - align-items: center; - column-gap: var(--spacing-sm); - padding: var(--spacing-sm) 0; - text-shadow: fun.convert-px(2) fun.convert-px(2) 0 var(--color-fg-inverted); -} - -.logo { - --branding-logo-animation: none; - - grid-column: 1; - grid-row: 1 / -1; - justify-self: center; - display: flex; - place-content: center; - width: var(--logo-size); - height: var(--logo-size); - position: relative; - border-radius: 50%; - transition: all 0.6s linear 0s; - transform-style: preserve-3d; - animation: var(--branding-logo-animation); - - &__front, - &__back { - width: 100%; - height: 100%; - padding: fun.convert-px(2); - position: absolute; - top: 0; - left: 0; - backface-visibility: hidden; - background: var(--color-bg); - border: fun.convert-px(2) solid var(--color-primary-dark); - border-radius: 50%; - transition: all 0.6s linear 0s; - } - - &__front { - box-shadow: fun.convert-px(1) fun.convert-px(2) fun.convert-px(1) 0 - var(--color-shadow-light), - fun.convert-px(2) fun.convert-px(3) fun.convert-px(3) 0 - var(--color-shadow-light); - } - - &__back { - transform: rotateY(180deg); - } - - img, - svg { - border-radius: 50%; - } - - &:hover { - transform: rotateY(180deg); - } - - &:hover & { - &__front { - box-shadow: none; - } - - &__back { - box-shadow: fun.convert-px(1) fun.convert-px(2) fun.convert-px(1) 0 - var(--color-shadow-light), - fun.convert-px(2) fun.convert-px(3) fun.convert-px(3) 0 - var(--color-shadow-light); - } - } -} - -.name { - --branding-name-animation: none; - - grid-column: 2; - grid-row: 1; - margin: 0; - font-family: var(--font-family-secondary); - font-size: clamp(var(--font-size-xl), 6vw, var(--font-size-2xl)); - font-weight: 500; - letter-spacing: 0.01ex; - position: relative; - overflow: hidden; - - &::after { - content: "|"; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - background: var(--color-bg); - color: var(--color-primary-darker); - font-weight: 400; - visibility: hidden; - transform: translateX(100%); - transform-origin: right; - animation: var(--branding-name-animation); - } -} - -.job { - --branding-job-animation: none; - - grid-column: 2; - grid-row: 2; - width: max-content; - margin: 0; - color: var(--color-fg-light); - font-family: var(--font-family-secondary); - font-size: var(--font-size-lg); - font-weight: 500; - position: relative; - overflow: hidden; - - &::after { - content: "|"; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - background: var(--color-bg); - color: var(--color-primary-darker); - font-weight: 400; - visibility: hidden; - transform: translateX(100%); - transform-origin: right; - animation: var(--branding-job-animation); - } -} - -.link { - background: linear-gradient( - to top, - var(--color-primary-light) fun.convert-px(5), - transparent fun.convert-px(5) - ) - left / 0 100% no-repeat; - text-decoration: none; - transition: all 0.6s ease-out 0s; - - &:hover, - &:focus { - background-size: 100% 100%; - } - - &:focus { - color: var(--color-primary-light); - } - - &:active { - background-size: 0 100%; - color: var(--color-primary-dark); - } -} diff --git a/src/components/Branding/Branding.tsx b/src/components/Branding/Branding.tsx deleted file mode 100644 index b19116d..0000000 --- a/src/components/Branding/Branding.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import photo from '@assets/images/armand-philippot.jpg'; -import { settings } from '@utils/config'; -import Image from 'next/image'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { ReactElement, useEffect, useRef } from 'react'; -import { useIntl } from 'react-intl'; -import { Person, WithContext } from 'schema-dts'; -import styles from './Branding.module.scss'; -import Logo from './Logo/Logo'; - -type BrandingReturn = ({ isHome }: { isHome: boolean }) => ReactElement; - -const Branding: BrandingReturn = ({ isHome = false }) => { - const intl = useIntl(); - const { locale } = useRouter(); - const TitleTag = isHome ? 'h1' : 'p'; - const logoRef = useRef<HTMLDivElement>(null); - const titleRef = useRef<HTMLHeadingElement | HTMLParagraphElement>(null); - const jobRef = useRef<HTMLParagraphElement>(null); - - useEffect(() => { - if (logoRef.current) { - logoRef.current.style.setProperty( - '--branding-logo-animation', - 'flip-logo 9s ease-in 0s 1' - ); - } - }, []); - - useEffect(() => { - if (titleRef.current) { - titleRef.current.style.setProperty( - '--branding-name-animation', - 'blink 0.8s ease-in-out 0s 2, typing 4.3s linear 0s 1' - ); - } - }, []); - - useEffect(() => { - if (jobRef.current) { - jobRef.current.style.setProperty( - '--branding-job-animation', - 'hide-text 4.25s linear 0s 1, blink 0.8s ease-in-out 4.25s 2, typing 3.8s linear 4.25s 1' - ); - } - }, []); - - const schemaJsonLd: WithContext<Person> = { - '@context': 'https://schema.org', - '@type': 'Person', - '@id': `${settings.url}/#branding`, - name: settings.name, - url: settings.url, - jobTitle: locale?.startsWith('en') - ? settings.baseline.en - : settings.baseline.fr, - image: photo.src, - subjectOf: { '@id': `${settings.url}` }, - }; - - return ( - <> - <Script - id="schema-branding" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <div id="branding" className={styles.wrapper}> - <div className={styles.logo} ref={logoRef}> - <div className={styles.logo__front}> - <Image - src={photo} - alt={intl.formatMessage( - { - defaultMessage: '{brandingName} picture', - description: 'Branding: branding name picture.', - id: 'ILRLTq', - }, - { - brandingName: settings.name, - } - )} - layout="responsive" - /> - </div> - <div className={styles.logo__back}> - <Logo /> - </div> - </div> - <TitleTag ref={titleRef} className={styles.name}> - <Link href="/"> - <a className={styles.link}>{settings.name}</a> - </Link> - </TitleTag> - <p ref={jobRef} className={styles.job}> - {locale?.startsWith('en') - ? settings.baseline.en - : settings.baseline.fr} - </p> - </div> - </> - ); -}; - -export default Branding; diff --git a/src/components/Branding/Logo/Logo.module.scss b/src/components/Branding/Logo/Logo.module.scss deleted file mode 100644 index 3d62bf9..0000000 --- a/src/components/Branding/Logo/Logo.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -.wrapper { - position: relative; -} - -.bg-left { - fill: var(--color-primary-light); -} - -.bg-right { - fill: var(--color-primary-dark); -} - -.letter { - fill: var(--color-fg-inverted); - stroke: var(--color-primary-darker); - stroke-width: 5; -} - -.letter-shadow { - fill: var(--color-shadow-darker); - stroke: var(--color-shadow-darker); - stroke-width: 5; -} diff --git a/src/components/Branding/Logo/Logo.tsx b/src/components/Branding/Logo/Logo.tsx deleted file mode 100644 index 0623042..0000000 --- a/src/components/Branding/Logo/Logo.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import styles from './Logo.module.scss'; - -const Logo = () => { - return ( - <svg - viewBox="0 0 512 512" - xmlns="http://www.w3.org/2000/svg" - className={styles.wrapper} - > - <path className={styles['bg-left']} d="M 0,0 H 506 L 0,506 Z" /> - <path className={styles['bg-right']} d="M 512,512 H 6 L 512,6 Z" /> - <path - className={styles['letter-shadow']} - d="m 66.049088,353.26557 h 57.233082 l 15.4763,-40.00476 h 56.64908 l 15.76831,40.00476 h 57.2331 L 196.28357,165.21398 h -58.10911 z m 80.009522,-79.42552 21.02441,-55.18904 21.02439,55.18904 z" - /> - <path - className={styles['letter']} - d="m 59.569539,346.78602 h 57.233081 l 15.4763,-40.00476 H 188.928 l 15.76831,40.00476 h 57.2331 L 189.80402,158.73443 h -58.10911 z m 80.009521,-79.42552 21.02441,-55.18904 21.02439,55.18904 z" - /> - <path - className={styles['letter-shadow']} - d="m 288.84935,353.26557 h 54.89704 v -50.51696 h 40.88078 c 42.04881,0 68.91332,-28.61654 68.91332,-68.32931 0,-38.5447 -21.60841,-69.20532 -67.74528,-69.20532 h -96.94586 z m 54.89704,-92.56578 v -53.437 h 29.78458 c 16.35231,0 23.94446,10.51221 23.94446,27.15651 0,15.47629 -8.46817,26.28049 -25.40449,26.28049 z" - /> - <path - className={styles['letter']} - d="m 282.3698,346.78602 h 54.89704 v -50.51696 h 40.88078 c 42.04881,0 68.91332,-28.61654 68.91332,-68.3293 0,-38.54471 -21.60841,-69.20533 -67.74528,-69.20533 H 282.3698 Z m 54.89704,-92.56578 v -53.437 h 29.78458 c 16.35231,0 23.94446,10.51221 23.94446,27.15652 0,15.47628 -8.46817,26.28048 -25.40449,26.28048 z" - /> - </svg> - ); -}; - -export default Logo; diff --git a/src/components/Breadcrumb/Breadcrumb.module.scss b/src/components/Breadcrumb/Breadcrumb.module.scss deleted file mode 100644 index b8fadf8..0000000 --- a/src/components/Breadcrumb/Breadcrumb.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.wrapper { - composes: grid from "@styles/layout/_grid.scss"; - padding: var(--spacing-md) 0; -} - -.list { - @extend %reset-ordered-list; - - grid-column: 2; - display: flex; - flex-flow: row wrap; - align-items: center; - gap: var(--spacing-2xs); - margin: 0; - font-size: var(--font-size-sm); -} - -.item { - &:not(:last-of-type) { - &::after { - content: ">"; - margin-left: var(--spacing-2xs); - } - } -} diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx deleted file mode 100644 index a7b945a..0000000 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { settings } from '@utils/config'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { useIntl } from 'react-intl'; -import { BreadcrumbList, WithContext } from 'schema-dts'; -import styles from './Breadcrumb.module.scss'; - -const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => { - const intl = useIntl(); - const router = useRouter(); - - const isHome = router.pathname === '/'; - const isArticle = router.pathname.includes('/article/'); - const isProject = router.pathname.includes('/projet/'); - const isSubject = router.pathname.includes('/sujet/'); - const isThematic = router.pathname.includes('/thematique/'); - - const getItems = () => { - return ( - <> - <li className={styles.item}> - <Link href="/"> - <a> - {intl.formatMessage({ - defaultMessage: 'Home', - description: 'Breadcrumb: Home item', - id: 'Enij19', - })} - </a> - </Link> - </li> - {(isArticle || isThematic || isSubject) && ( - <> - <li className={styles.item}> - <Link href="/blog"> - <a> - {intl.formatMessage({ - defaultMessage: 'Blog', - description: 'Breadcrumb: Blog item', - id: 'z0ic9c', - })} - </a> - </Link> - </li> - </> - )} - {isProject && ( - <> - <li className={styles.item}> - <Link href="/projets"> - <a> - {intl.formatMessage({ - defaultMessage: 'Projects', - description: 'Breadcrumb: Projects item', - id: 'Igx3qp', - })} - </a> - </Link> - </li> - </> - )} - <li className="screen-reader-text">{pageTitle}</li> - </> - ); - }; - - const getElementsSchema = () => { - const items = []; - const homepage: BreadcrumbList['itemListElement'] = { - '@type': 'ListItem', - position: 1, - name: intl.formatMessage({ - defaultMessage: 'Home', - description: 'Breadcrumb: Home item', - id: 'Enij19', - }), - item: settings.url, - }; - - items.push(homepage); - - if (isArticle || isThematic || isSubject) { - const blog: BreadcrumbList['itemListElement'] = { - '@type': 'ListItem', - position: 2, - name: intl.formatMessage({ - defaultMessage: 'Blog', - description: 'Breadcrumb: Blog item', - id: 'z0ic9c', - }), - item: `${settings.url}/blog`, - }; - - items.push(blog); - } - - if (isProject) { - const blog: BreadcrumbList['itemListElement'] = { - '@type': 'ListItem', - position: 2, - name: intl.formatMessage({ - defaultMessage: 'Projects', - description: 'Breadcrumb: Projects item', - id: 'Igx3qp', - }), - item: `${settings.url}/projets`, - }; - - items.push(blog); - } - - const currentPage: BreadcrumbList['itemListElement'] = { - '@type': 'ListItem', - position: items.length + 1, - name: pageTitle, - item: `${settings.url}${router.asPath}`, - }; - - items.push(currentPage); - - return items; - }; - - const schemaJsonLd: WithContext<BreadcrumbList> = { - '@context': 'https://schema.org', - '@type': 'BreadcrumbList', - '@id': `${settings.url}/#breadcrumb`, - itemListElement: getElementsSchema(), - }; - - return ( - <> - <Script - id="schema-breadcrumb" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - {!isHome && ( - <nav id="breadcrumb" className={styles.wrapper}> - <span className="screen-reader-text"> - {intl.formatMessage({ - defaultMessage: 'You are here:', - description: 'Breadcrumb: You are here prefix', - id: '16zl9Z', - })} - </span> - <ol className={styles.list}>{getItems()}</ol> - </nav> - )} - </> - ); -}; - -export default Breadcrumb; diff --git a/src/components/Buttons/Button/Button.tsx b/src/components/Buttons/Button/Button.tsx deleted file mode 100644 index ada23fe..0000000 --- a/src/components/Buttons/Button/Button.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { ButtonKind, ButtonPosition } from '@ts/types/app'; -import { ReactNode } from 'react'; -import styles from '../Buttons.module.scss'; - -const Button = ({ - children, - clickHandler, - kind = 'secondary', - position = 'left', - spacing = false, - isDisabled = false, -}: { - children: ReactNode; - clickHandler: any; - kind?: ButtonKind; - position?: ButtonPosition; - spacing?: boolean; - isDisabled?: boolean; -}) => { - const spacingClass = spacing ? styles.spacing : ''; - const classes = `${styles.btn} ${styles[position]} ${styles[kind]} ${spacingClass}`; - - return ( - <button - className={classes} - type="button" - disabled={isDisabled} - onClick={clickHandler} - > - {children} - </button> - ); -}; - -export default Button; diff --git a/src/components/Buttons/ButtonHelp/ButtonHelp.module.scss b/src/components/Buttons/ButtonHelp/ButtonHelp.module.scss deleted file mode 100644 index b2a05d7..0000000 --- a/src/components/Buttons/ButtonHelp/ButtonHelp.module.scss +++ /dev/null @@ -1,52 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.icon { - color: var(--color-primary-dark); - font-weight: 600; -} - -.active { - .icon { - color: var(--color-fg-inverted); - } -} - -.wrapper { - width: fun.convert-px(44); - height: fun.convert-px(44); - background: var(--color-bg); - border: fun.convert-px(3) solid var(--color-primary-dark); - border-radius: 50%; - box-shadow: fun.convert-px(1) fun.convert-px(1) 0 0 var(--color-shadow); - transition: all 0.3s ease-in-out 0s; - - @include mix.pointer("fine") { - width: fun.convert-px(30); - height: fun.convert-px(30); - line-height: 1; - } - - &:hover, - &:focus { - border-color: var(--color-primary-light); - color: var(--color-primary-light); - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow-light), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) - var(--color-shadow-light), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) - var(--color-shadow-light), - fun.convert-px(7) fun.convert-px(10) fun.convert-px(12) fun.convert-px(-3) - var(--color-shadow-light); - transform: scale(1.1); - - .icon { - transform: scale(1.1); - } - } - - &.active { - background: var(--color-primary); - } -} diff --git a/src/components/Buttons/ButtonHelp/ButtonHelp.tsx b/src/components/Buttons/ButtonHelp/ButtonHelp.tsx deleted file mode 100644 index 2616a8f..0000000 --- a/src/components/Buttons/ButtonHelp/ButtonHelp.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { SetStateAction } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './ButtonHelp.module.scss'; - -const ButtonHelp = ({ - showHelp, - setShowHelp, - title, -}: { - showHelp: boolean; - setShowHelp: (value: SetStateAction<boolean>) => void; - title?: string; -}) => { - const intl = useIntl(); - - const handleClick = () => { - setShowHelp((prev) => !prev); - }; - - const activeModifier = showHelp ? styles.active : ''; - - return ( - <button - onClick={handleClick} - title={title} - className={`${styles.wrapper} ${activeModifier}`} - > - <span className={styles.icon} aria-hidden="true"> - ? - </span> - <span className="screen-reader-text"> - {intl.formatMessage({ - defaultMessage: 'Help', - description: 'ButtonHelp: screen reader text', - id: 'oPf+XA', - })} - </span> - </button> - ); -}; - -export default ButtonHelp; diff --git a/src/components/Buttons/ButtonLink/ButtonLink.tsx b/src/components/Buttons/ButtonLink/ButtonLink.tsx deleted file mode 100644 index 179c686..0000000 --- a/src/components/Buttons/ButtonLink/ButtonLink.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ButtonPosition } from '@ts/types/app'; -import Link from 'next/link'; -import { ReactNode } from 'react'; -import styles from '../Buttons.module.scss'; - -const ButtonLink = ({ - children, - target, - position = 'left', - isExternal = false, -}: { - children: ReactNode; - target: string; - position?: ButtonPosition; - isExternal?: boolean; -}) => { - const classes = `${styles.btn} ${styles[position]} ${styles.tertiary}`; - - return isExternal ? ( - <a className={classes} href={target}> - {children} - </a> - ) : ( - <Link href={target}> - <a className={classes}>{children}</a> - </Link> - ); -}; - -export default ButtonLink; diff --git a/src/components/Buttons/ButtonSubmit/ButtonSubmit.tsx b/src/components/Buttons/ButtonSubmit/ButtonSubmit.tsx deleted file mode 100644 index 4725cad..0000000 --- a/src/components/Buttons/ButtonSubmit/ButtonSubmit.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { ReactNode } from 'react'; -import styles from '../Buttons.module.scss'; - -type Modifier = 'search' | 'submit'; - -const ButtonSubmit = ({ - children, - modifier = 'submit', -}: { - children: ReactNode; - modifier?: Modifier; -}) => { - const withModifier = modifier === 'search' ? styles.search : styles.primary; - - return ( - <button type="submit" className={`${styles.btn} ${withModifier}`}> - {children} - </button> - ); -}; - -export default ButtonSubmit; diff --git a/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx b/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx deleted file mode 100644 index 7ceb70d..0000000 --- a/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { CloseIcon, CogIcon, SearchIcon } from '@components/Icons'; -import { ForwardedRef, forwardRef, SetStateAction } from 'react'; -import { useIntl } from 'react-intl'; -import styles from '../Buttons.module.scss'; - -type ButtonType = 'search' | 'settings'; - -const ButtonToolbar = ( - { - type, - isActivated, - setIsActivated, - }: { - type: ButtonType; - isActivated: boolean; - setIsActivated: (value: SetStateAction<boolean>) => void; - }, - ref: ForwardedRef<HTMLButtonElement> -) => { - const intl = useIntl(); - const ButtonIcon = () => (type === 'search' ? <SearchIcon /> : <CogIcon />); - const btnClasses = isActivated - ? `${styles.toolbar} ${styles['toolbar--activated']}` - : styles.toolbar; - - return ( - <button - ref={ref} - className={btnClasses} - type="button" - onClick={() => setIsActivated(!isActivated)} - > - <span className={styles.icon}> - <span className={styles.front}> - <ButtonIcon /> - </span> - <span className={styles.back}> - <CloseIcon /> - </span> - </span> - {isActivated ? ( - <span className="screen-reader-text"> - {intl.formatMessage( - { - defaultMessage: 'Close {type}', - description: 'ButtonToolbar: Close button', - id: 'SWq8a4', - }, - { - type, - } - )} - </span> - ) : ( - <span className="screen-reader-text"> - {intl.formatMessage( - { - defaultMessage: 'Open {type}', - description: 'ButtonToolbar: Open button', - id: 'Z1eSIz', - }, - { - type, - } - )} - </span> - )} - </button> - ); -}; - -export default forwardRef(ButtonToolbar); diff --git a/src/components/Buttons/Buttons.module.scss b/src/components/Buttons/Buttons.module.scss deleted file mode 100644 index 0ea9289..0000000 --- a/src/components/Buttons/Buttons.module.scss +++ /dev/null @@ -1,289 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.btn { - display: block; - border: none; - font-size: var(--font-size-md); -} - -.left { - margin-right: auto; -} - -.right { - margin-left: auto; -} - -.center { - margin-left: auto; - margin-right: auto; -} - -.primary { - margin: auto; - padding: var(--spacing-2xs) var(--spacing-md); - background: var(--color-primary); - border: fun.convert-px(2) solid var(--color-bg); - border-radius: fun.convert-px(5); - box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary), - 0 0 0 fun.convert-px(3) var(--color-primary-darker), - fun.convert-px(2) fun.convert-px(2) 0 fun.convert-px(3) - var(--color-primary-dark); - color: var(--color-fg-inverted); - font-weight: 600; - text-shadow: fun.convert-px(2) fun.convert-px(2) 0 var(--color-shadow); - transition: all 0.25s ease-in-out 0s; - - &:hover, - &:focus { - background: var(--color-primary-light); - box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary-light), - 0 0 0 fun.convert-px(3) var(--color-primary-darker), - fun.convert-px(7) fun.convert-px(7) 0 fun.convert-px(2) - var(--color-primary-dark); - transform: translateX(#{fun.convert-px(-4)}) - translateY(#{fun.convert-px(-4)}); - } - - &:focus { - text-decoration: underline solid var(--color-fg-inverted) fun.convert-px(2); - } - - &:active { - background: var(--color-primary-dark); - box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary), - 0 0 0 fun.convert-px(3) var(--color-primary-darker), - 0 0 0 0 var(--color-primary-dark); - text-decoration: none; - transform: translateX(#{fun.convert-px(4)}) translateY(#{fun.convert-px(4)}); - } -} - -.secondary { - padding: var(--spacing-2xs) var(--spacing-md); - background: var(--color-bg); - border: fun.convert-px(3) solid var(--color-primary); - border-radius: fun.convert-px(5); - box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 var(--color-bg), - fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-primary-dark), - fun.convert-px(5) fun.convert-px(5) 0 0 var(--color-bg), - fun.convert-px(6) fun.convert-px(6) 0 0 var(--color-primary-dark); - color: var(--color-primary); - font-weight: 600; - transition: all 0.35s ease-out 0s; - - &:disabled { - color: var(--color-fg-light); - border-color: var(--color-border-dark); - box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 var(--color-bg), - fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-primary-darker), - fun.convert-px(5) fun.convert-px(5) 0 0 var(--color-bg), - fun.convert-px(6) fun.convert-px(6) 0 0 var(--color-primary-darker); - cursor: wait; - } - - &:not(:disabled) { - &:hover, - &:focus { - transform: translateX(#{fun.convert-px(-3)}) - translateY(#{fun.convert-px(-5)}); - border-color: var(--color-primary-light); - box-shadow: fun.convert-px(2) fun.convert-px(3) 0 0 var(--color-bg), - fun.convert-px(4) fun.convert-px(5) 0 0 var(--color-primary), - fun.convert-px(6) fun.convert-px(8) 0 0 var(--color-bg), - fun.convert-px(8) fun.convert-px(10) 0 0 var(--color-primary), - fun.convert-px(10) fun.convert-px(12) fun.convert-px(1) 0 - var(--color-shadow-light), - fun.convert-px(10) fun.convert-px(12) fun.convert-px(5) - fun.convert-px(1) var(--color-shadow-light); - color: var(--color-primary-light); - } - - &:focus { - text-decoration: underline var(--color-primary) fun.convert-px(2); - } - - &:active { - text-decoration: none; - transform: translateX(#{fun.convert-px(5)}) - translateY(#{fun.convert-px(6)}); - box-shadow: 0 0 0 0 var(--color-shadow); - } - } -} - -.tertiary { - display: flex; - flex-flow: row wrap; - place-items: center; - gap: var(--spacing-2xs); - width: max-content; - padding: var(--spacing-2xs) var(--spacing-sm); - position: relative; - background: var(--color-bg); - border: fun.convert-px(3) solid var(--color-primary); - border-radius: fun.convert-px(5); - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) - var(--color-shadow), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) - var(--color-shadow); - color: var(--color-primary); - font-weight: 600; - text-decoration: underline transparent 0; - transition: all 0.3s ease-in-out 0s, text-decoration 0.35s ease-in-out 0s; - - &:hover, - &:focus { - border-color: var(--color-primary-light); - color: var(--color-primary-light); - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow-light), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) - var(--color-shadow-light), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) - var(--color-shadow-light), - fun.convert-px(7) fun.convert-px(10) fun.convert-px(12) fun.convert-px(-3) - var(--color-shadow-light); - transform: scale(1.1); - } - - &:focus { - text-decoration: underline var(--color-primary-light) fun.convert-px(3); - } - - &:active { - border-color: var(--color-primary-dark); - color: var(--color-primary-dark); - box-shadow: 0 0 0 0 var(--color-shadow); - text-decoration: underline transparent 0; - transform: scale(0.94); - } -} - -:global { - [data-theme="dark"] { - :local { - .tertiary { - img[src*="png"] { - background: none; - } - } - } - } -} - -.toolbar { - --draw-border-thickness: #{fun.convert-px(4)}; - --icon-size: 100%; - - display: flex; - flex-flow: row nowrap; - place-items: center; - width: var(--btn-size, 100%); - height: var(--btn-size, 100%); - padding: var(--spacing-2xs); - background: none; - border: none; - font-size: var(--font-size-md); - - &:hover, - &:focus { - --draw-border-color1: var(--color-primary-light); - --draw-border-color2: var(--color-primary-lighter); - - @extend %draw-borders; - } - - @include mix.media("screen") { - @include mix.dimensions("md") { - border-radius: 8%; - } - } -} - -.icon { - display: block; - width: 100%; - height: 100%; - position: relative; - transform-style: preserve-3d; - transform: translate3d(0, 0, 0); - transition: all 0.5s ease-in-out 0s; -} - -.front, -.back { - display: flex; - place-content: center; - width: var(--icon-size); - height: var(--icon-size); - position: absolute; - top: 0; - right: 0; - transition: all 0.6s ease-in 0s; - backface-visibility: hidden; -} - -.front { - transform: scale(1); - z-index: 20; -} - -.back { - transform: scale(0.2) rotateY(180deg); - z-index: 10; -} - -.toolbar--activated { - .icon { - transform: rotateY(180deg); - } - - .front { - transform: scale(0.2); - } - - .back { - transform: scale(1) rotateY(180deg); - } -} - -.search { - background: transparent; - margin-left: calc(var(--btn-size) * -1); - z-index: 5; - transition: all 0.3s ease-in-out 0s; - - svg { - transform: scale(0.85); - transition: all 0.3s ease-in-out 0s; - } - - &:hover, - &:focus { - svg { - transform: scale(0.85) rotate(20deg) translateY(#{fun.convert-px(3)}); - } - } - - &:focus { - outline: var(--color-primary-light) solid fun.convert-px(3); - } - - &:active { - outline: none; - - svg { - transform: scale(0.7); - } - } -} - -.spacing { - margin-top: var(--spacing-md); - margin-bottom: var(--spacing-md); -} diff --git a/src/components/Buttons/index.tsx b/src/components/Buttons/index.tsx deleted file mode 100644 index 9b4b756..0000000 --- a/src/components/Buttons/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import Button from './Button/Button'; -import ButtonLink from './ButtonLink/ButtonLink'; -import ButtonToolbar from './ButtonToolbar/ButtonToolbar'; -import ButtonSubmit from './ButtonSubmit/ButtonSubmit'; -import ButtonHelp from './ButtonHelp/ButtonHelp'; - -export { Button, ButtonHelp, ButtonLink, ButtonToolbar, ButtonSubmit }; diff --git a/src/components/Comment/Comment.module.scss b/src/components/Comment/Comment.module.scss deleted file mode 100644 index dd52db2..0000000 --- a/src/components/Comment/Comment.module.scss +++ /dev/null @@ -1,99 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.item { - margin: var(--spacing-sm) 0; -} - -.wrapper { - background: var(--color-bg); - border: fun.convert-px(1) solid var(--color-border); - box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow-light), - fun.convert-px(4) fun.convert-px(4) fun.convert-px(3) fun.convert-px(-2) - var(--color-shadow); - padding: var(--spacing-md); - position: relative; - - @include mix.media("screen") { - @include mix.dimensions("sm") { - display: grid; - grid-template-columns: minmax(#{fun.convert-px(150)}, 1fr) minmax(0, 85ch); - column-gap: var(--spacing-lg); - } - } -} - -.header { - display: flex; - flex-flow: column wrap; - align-items: center; - row-gap: var(--spacing-sm); - - @include mix.media("screen") { - @include mix.dimensions("sm") { - grid-row: 1 / 4; - } - } -} - -.avatar { - width: fun.convert-px(85); - height: fun.convert-px(85); - margin: 0 auto; - border-radius: fun.convert-px(3); - box-shadow: 0 0 0 fun.convert-px(1) var(--color-shadow-light), - fun.convert-px(2) fun.convert-px(2) 0 fun.convert-px(1) var(--color-shadow); - position: relative; - - img { - border-radius: fun.convert-px(3); - } -} - -.author { - color: var(--color-primary-darker); - font-weight: 600; - text-align: center; -} - -.date { - color: var(--color-fg-secondary); - font-size: var(--font-size-sm); - display: flex; - flex-flow: row wrap; - column-gap: var(--spacing-2xs); - justify-content: center; - margin: var(--spacing-md) 0; - - @include mix.media("screen") { - @include mix.dimensions("sm") { - justify-content: left; - margin: 0 0 var(--spacing-md); - } - } -} - -.body { - overflow-wrap: break-word; -} - -.footer { - display: flex; - justify-content: flex-end; - align-items: center; - padding: var(--spacing-md) 0 0; - - @include mix.media("screen") { - @include mix.dimensions("sm") { - padding: var(--spacing-sm) 0 0; - } - } - - button { - margin: 0; - } -} - -.list { - padding-left: var(--spacing-md); -} diff --git a/src/components/Comment/Comment.tsx b/src/components/Comment/Comment.tsx deleted file mode 100644 index 355363b..0000000 --- a/src/components/Comment/Comment.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { Button } from '@components/Buttons'; -import Spinner from '@components/Spinner/Spinner'; -import { Comment as CommentData } from '@ts/types/comments'; -import { settings } from '@utils/config'; -import { getFormattedDate } from '@utils/helpers/format'; -import dynamic from 'next/dynamic'; -import Image from 'next/image'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { useState } from 'react'; -import { useIntl } from 'react-intl'; -import { Comment as CommentSchema, WithContext } from 'schema-dts'; -import styles from './Comment.module.scss'; - -const DynamicCommentForm = dynamic( - () => import('@components/CommentForm/CommentForm'), - { - loading: () => <Spinner />, - } -); - -const Comment = ({ - articleId, - comment, - isNested = false, -}: { - articleId: number; - comment: CommentData; - isNested?: boolean; -}) => { - const intl = useIntl(); - const router = useRouter(); - const locale = router.locale ? router.locale : settings.locales.defaultLocale; - const [shouldOpenForm, setShouldOpenForm] = useState<boolean>(false); - - const getCommentAuthor = () => { - return comment.author.url ? ( - <Link href={comment.author.url}> - <a className={styles.author}>{comment.author.name}</a> - </Link> - ) : ( - <span className={styles.author}>{comment.author.name}</span> - ); - }; - - const getLocaleDate = () => { - const date = getFormattedDate(comment.date, locale); - const time = new Date(comment.date) - .toLocaleTimeString(locale, { - hour: 'numeric', - minute: 'numeric', - }) - .replace(':', 'h'); - return intl.formatMessage( - { - defaultMessage: '{date} at {time}', - description: 'Comment: publication date', - id: 'CT3ydM', - }, - { - date, - time, - } - ); - }; - - const getApprovedComment = () => { - return ( - <> - <article - className={styles.wrapper} - id={`comment-${comment.databaseId}`} - > - <header className={styles.header}> - {comment.author.gravatarUrl && ( - <div className={styles.avatar}> - <Image - src={comment.author.gravatarUrl} - alt={comment.author.name} - layout="fill" - /> - </div> - )} - {getCommentAuthor()} - </header> - <dl className={styles.date}> - <dt> - {intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Comment: publication date label', - id: 'soj7do', - })} - </dt> - <dd> - <time dateTime={comment.date}> - <Link href={`#comment-${comment.databaseId}`}> - <a>{getLocaleDate()}</a> - </Link> - </time> - </dd> - </dl> - <div - className={styles.body} - dangerouslySetInnerHTML={{ __html: comment.content }} - ></div> - {!isNested && ( - <footer className={styles.footer}> - <Button clickHandler={() => setShouldOpenForm((prev) => !prev)}> - {shouldOpenForm - ? intl.formatMessage({ - defaultMessage: 'Cancel reply', - description: 'Comment: reply button', - id: 'e1Forh', - }) - : intl.formatMessage({ - defaultMessage: 'Reply', - description: 'Comment: reply button', - id: 'hzHuCc', - })} - </Button> - </footer> - )} - </article> - {shouldOpenForm && ( - <DynamicCommentForm - articleId={articleId} - parentId={comment.databaseId} - /> - )} - {comment.replies.length > 0 && ( - <ol className={styles.list}> - {comment.replies.map((reply) => { - return ( - <Comment - articleId={articleId} - key={reply.databaseId} - comment={reply} - isNested={true} - /> - ); - })} - </ol> - )} - </> - ); - }; - - const getCommentStatus = () => { - return ( - <p> - {intl.formatMessage({ - defaultMessage: 'This comment is awaiting moderation.', - description: 'Comment: awaiting moderation message', - id: 'rXeTkM', - })} - </p> - ); - }; - - const schemaJsonLd: WithContext<CommentSchema> = { - '@context': 'https://schema.org', - '@id': `${settings.url}/#comment-${comment.databaseId}`, - '@type': 'Comment', - parentItem: isNested - ? { '@id': `${settings.url}/#comment-${comment.parentDatabaseId}` } - : undefined, - about: { '@type': 'Article', '@id': `${settings.url}/#article` }, - author: { - '@type': 'Person', - name: comment.author.name, - image: comment.author.gravatarUrl, - url: comment.author.url, - }, - creator: { - '@type': 'Person', - name: comment.author.name, - image: comment.author.gravatarUrl, - url: comment.author.url, - }, - dateCreated: comment.date, - datePublished: comment.date, - text: comment.content, - }; - - return ( - <> - <Script - id="schema-comments" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <li className={styles.item}> - {comment.approved ? getApprovedComment() : getCommentStatus()} - </li> - </> - ); -}; - -export default Comment; diff --git a/src/components/CommentForm/CommentForm.module.scss b/src/components/CommentForm/CommentForm.module.scss deleted file mode 100644 index d19a1ef..0000000 --- a/src/components/CommentForm/CommentForm.module.scss +++ /dev/null @@ -1,25 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.wrapper { - width: min(calc(100vw - (var(--spacing-md) * 2)), fun.convert-px(500)); - margin: auto; - - &--reply { - width: 100%; - margin-top: var(--spacing-sm); - padding: var(--spacing-md); - position: relative; - background: var(--color-bg); - border: fun.convert-px(1) solid var(--color-border); - box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 - var(--color-shadow-light), - fun.convert-px(4) fun.convert-px(4) fun.convert-px(3) fun.convert-px(-2) - var(--color-shadow); - } -} - -.title { - width: max-content; - margin-left: auto; - margin-right: auto; -} diff --git a/src/components/CommentForm/CommentForm.tsx b/src/components/CommentForm/CommentForm.tsx deleted file mode 100644 index 128dc58..0000000 --- a/src/components/CommentForm/CommentForm.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { ButtonSubmit } from '@components/Buttons'; -import { Field, Form, FormItem, Label } from '@components/FormElements'; -import Notice from '@components/Notice/Notice'; -import Spinner from '@components/Spinner/Spinner'; -import { createComment } from '@services/graphql/mutations'; -import { NoticeType } from '@ts/types/app'; -import { useEffect, useRef, useState } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './CommentForm.module.scss'; - -const CommentForm = ({ - articleId, - parentId = 0, -}: { - articleId: number; - parentId?: number; -}) => { - const intl = useIntl(); - const [name, setName] = useState<string>(''); - const [email, setEmail] = useState<string>(''); - const [website, setWebsite] = useState<string>(''); - const [comment, setComment] = useState<string>(''); - const [isSubmitting, setIsSubmitting] = useState<boolean>(false); - const [notice, setNotice] = useState<string>(); - const [noticeType, setNoticeType] = useState<NoticeType>('success'); - const nameFieldRef = useRef<HTMLInputElement>(null); - - useEffect(() => { - if (parentId === 0) return; - nameFieldRef.current && nameFieldRef.current.focus(); - }); - - const resetForm = () => { - setName(''); - setEmail(''); - setWebsite(''); - setComment(''); - setIsSubmitting(false); - }; - - const isEmptyString = (value: string): boolean => value.trim() === ''; - const areRequiredFieldsSet = (): boolean => - !isEmptyString(name) && !isEmptyString(email) && !isEmptyString(comment); - - const sendComment = async () => { - const data = { - author: name, - authorEmail: email, - authorUrl: website, - content: comment, - parent: parentId, - commentOn: articleId, - mutationId: 'createComment', - }; - - const createdComment = await createComment(data); - - if (createdComment.success) { - resetForm(); - setNoticeType('success'); - if (createdComment.comment?.approved) { - setNotice( - intl.formatMessage({ - defaultMessage: 'Thanks for your comment!', - description: 'CommentForm: success notice', - id: 'AVUUgG', - }) - ); - } else { - setNotice( - intl.formatMessage({ - defaultMessage: - 'Thanks for your comment! It is now awaiting moderation.', - description: 'CommentForm: success notice but awaiting moderation', - id: 'Ul2NIl', - }) - ); - } - - setTimeout(() => { - setNotice(undefined); - }, 10000); - } else { - setNoticeType('error'); - setNotice( - intl.formatMessage({ - defaultMessage: - 'An unexpected error happened. Comment cannot be submitted.', - description: 'CommentForm: error notice', - id: 'cjK9Ad', - }) - ); - } - }; - - const submitHandler = async (e: SubmitEvent) => { - e.preventDefault(); - setNotice(undefined); - setIsSubmitting(true); - - if (areRequiredFieldsSet()) { - sendComment(); - } else { - setIsSubmitting(false); - setNoticeType('warning'); - setNotice( - intl.formatMessage({ - defaultMessage: - 'Some required fields are empty. Comment cannot be submitted.', - description: 'CommentForm: missing required fields', - id: 'Rle+UK', - }) - ); - } - }; - - const isReply = parentId !== 0; - const wrapperClasses = `${styles.wrapper} ${ - isReply ? styles['wrapper--reply'] : '' - }`; - - const getLabel = ( - body: string, - htmlFor: string, - required: boolean = false - ) => { - return <Label body={body} htmlFor={htmlFor} required={required} />; - }; - - const nameLabelBody = intl.formatMessage({ - defaultMessage: 'Name', - description: 'CommentForm: Name field label', - id: 'F7QxJH', - }); - - const emailLabelBody = intl.formatMessage({ - defaultMessage: 'Email', - description: 'CommentForm: Email field label', - id: 'Oim3rQ', - }); - - const websiteLabelBody = intl.formatMessage({ - defaultMessage: 'Website', - description: 'CommentForm: Website field label', - id: 'jN+dY5', - }); - - const commentLabelBody = intl.formatMessage({ - defaultMessage: 'Comment', - description: 'CommentForm: Comment field label', - id: 'J4nhm4', - }); - - return ( - <div className={wrapperClasses}> - <h2 className={styles.title}> - {intl.formatMessage({ - defaultMessage: 'Leave a comment', - description: 'CommentForm: Form title', - id: '+aHn7j', - })} - </h2> - <Form - submitHandler={submitHandler} - kind={isReply ? 'centered' : undefined} - > - <FormItem> - <Field - id="commenter-name" - name="commenter-name" - label={getLabel(nameLabelBody, 'commenter-name', true)} - value={name} - setValue={setName} - required={true} - ref={nameFieldRef} - /> - </FormItem> - <FormItem> - <Field - id="commenter-email" - name="commenter-email" - kind="email" - label={getLabel(emailLabelBody, 'commenter-email', true)} - value={email} - setValue={setEmail} - required={true} - /> - </FormItem> - <FormItem> - <Field - id="commenter-website" - name="commenter-website" - label={getLabel(websiteLabelBody, 'commenter-website')} - value={website} - setValue={setWebsite} - /> - </FormItem> - <FormItem> - <Field - id="commenter-comment" - name="commenter-comment" - kind="textarea" - label={getLabel(commentLabelBody, 'commenter-comment', true)} - value={comment} - setValue={setComment} - required={true} - /> - </FormItem> - <FormItem> - <noscript> - {intl.formatMessage({ - defaultMessage: 'Javascript is required to post a comment.', - description: 'CommentForm: noscript tag', - id: 'g1cFCa', - })} - </noscript> - <ButtonSubmit> - {intl.formatMessage({ - defaultMessage: 'Send', - description: 'CommentForm: Send button', - id: 'WGFOmA', - })} - </ButtonSubmit> - </FormItem> - </Form> - {isSubmitting && ( - <Spinner - message={intl.formatMessage({ - defaultMessage: 'Submitting...', - description: 'CommentForm: submitting message', - id: 'HEJ3Gv', - })} - /> - )} - {notice && <Notice type={noticeType}>{notice}</Notice>} - </div> - ); -}; - -export default CommentForm; diff --git a/src/components/CommentsList/CommentsList.module.scss b/src/components/CommentsList/CommentsList.module.scss deleted file mode 100644 index 4971b15..0000000 --- a/src/components/CommentsList/CommentsList.module.scss +++ /dev/null @@ -1,14 +0,0 @@ -@use "@styles/abstracts/placeholders"; - -.title, -.no-comments { - width: max-content; - margin-left: auto; - margin-right: auto; -} - -.list { - @extend %reset-ordered-list; - - margin-bottom: var(--spacing-lg); -} diff --git a/src/components/CommentsList/CommentsList.tsx b/src/components/CommentsList/CommentsList.tsx deleted file mode 100644 index 0eaac17..0000000 --- a/src/components/CommentsList/CommentsList.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import Comment from '@components/Comment/Comment'; -import Spinner from '@components/Spinner/Spinner'; -import { getCommentsByPostId } from '@services/graphql/queries'; -import { Comment as CommentData } from '@ts/types/comments'; -import { useIntl } from 'react-intl'; -import useSWR from 'swr'; -import styles from './CommentsList.module.scss'; - -const CommentsList = ({ - articleId, - comments, -}: { - articleId: number; - comments: CommentData[]; -}) => { - const intl = useIntl(); - const { data, error } = useSWR<CommentData[]>( - '/api/comments', - () => getCommentsByPostId(articleId), - { fallbackData: comments } - ); - - const getCommentsList = () => { - if (error) { - return intl.formatMessage({ - defaultMessage: 'Failed to load.', - description: 'CommentsList: failed to load', - id: 'Zlkww3', - }); - } - - if (!data) return <Spinner />; - - return data.map((comment) => { - return ( - <Comment - key={comment.databaseId} - articleId={articleId} - comment={comment} - /> - ); - }); - }; - - return ( - <> - <h2 className={styles.title}> - {intl.formatMessage({ - defaultMessage: 'Comments', - description: 'CommentsList: Comments section title', - id: 'Ns8CFb', - })} - </h2> - {data && data.length > 0 ? ( - <ol className={styles.list}>{getCommentsList()}</ol> - ) : ( - <p className={styles['no-comments']}> - {intl.formatMessage({ - defaultMessage: 'No comments yet.', - description: 'CommentsList: No comment message', - id: 'e9L59q', - })} - </p> - )} - </> - ); -}; - -export default CommentsList; diff --git a/src/components/ContactForm/ContactForm.module.scss b/src/components/ContactForm/ContactForm.module.scss deleted file mode 100644 index 3f0e861..0000000 --- a/src/components/ContactForm/ContactForm.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.status { - max-width: max-content; - margin: var(--spacing-md) 0; - padding: var(--spacing-sm); - border: fun.convert-px(3) solid var(--color-border-light); - border-radius: fun.convert-px(5); - - &--error { - border-color: var(--color-token-red); - } - - &--success { - border-color: var(--color-token-green); - } - - &--warning { - border-color: var(--color-token-orange); - } -} diff --git a/src/components/ContactForm/ContactForm.tsx b/src/components/ContactForm/ContactForm.tsx deleted file mode 100644 index 5af6982..0000000 --- a/src/components/ContactForm/ContactForm.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { ButtonSubmit } from '@components/Buttons'; -import { Field, Form, FormItem, Label } from '@components/FormElements'; -import { sendMail } from '@services/graphql/mutations'; -import { settings } from '@utils/config'; -import { FormEvent, useState } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './ContactForm.module.scss'; - -type Status = 'success' | 'error' | 'warning'; - -const ContactForm = () => { - const intl = useIntl(); - const [name, setName] = useState(''); - const [email, setEmail] = useState(''); - const [subject, setSubject] = useState(''); - const [message, setMessage] = useState(''); - const [status, setStatus] = useState<Status>(); - const [statusMessage, setStatusMessage] = useState<string>(''); - - const resetForm = () => { - setName(''); - setEmail(''); - setSubject(''); - setMessage(''); - }; - - const submitHandler = async (e: FormEvent) => { - e.preventDefault(); - - if (!name || !email || !message) { - setStatus('warning'); - setStatusMessage( - intl.formatMessage({ - defaultMessage: - 'Warning: mail not sent. Some required fields are empty.', - description: 'ContactForm: missing fields message.', - id: 'WpycgB', - }) - ); - return; - } - - const messageHTML = message.replace(/\r?\n/g, '<br />'); - const body = `Message received from ${name} <${email}> on ${settings.url}.<br /><br />${messageHTML}`; - const replyTo = `${name} <${email}>`; - const data = { - body, - mutationId: 'contact', - replyTo, - subject, - }; - const mail = await sendMail(data); - - if (mail.sent) { - setStatus('success'); - setStatusMessage( - intl.formatMessage({ - defaultMessage: - 'Thanks. Your message was successfully sent. I will answer it as soon as possible.', - description: 'ContactForm: success message', - id: 'gQKeF+', - }) - ); - resetForm(); - } else { - const errorPrefix = intl.formatMessage({ - defaultMessage: 'An error occurred:', - description: 'ContactForm: error message', - id: 'pTxT7N', - }); - const error = `${errorPrefix} ${mail.message}`; - setStatus('error'); - setStatusMessage(error); - } - }; - - const getStatus = () => { - if (!status) return <></>; - - const statusModifier = `status--${status}`; - - return ( - <p className={`${styles.status} ${styles[statusModifier]}`}> - {statusMessage} - </p> - ); - }; - - const getLabel = ( - body: string, - htmlFor: string, - required: boolean = false - ) => { - return <Label body={body} htmlFor={htmlFor} required={required} />; - }; - - const nameLabelBody = intl.formatMessage({ - defaultMessage: 'Name', - description: 'ContactForm: name field label', - id: '6ibqFS', - }); - - const emailLabelBody = intl.formatMessage({ - defaultMessage: 'Email', - description: 'ContactForm: email field label', - id: 'Vuryko', - }); - - const subjectLabelBody = intl.formatMessage({ - defaultMessage: 'Subject', - description: 'ContactForm: subject field label', - id: 'uMURuJ', - }); - - const messageLabelBody = intl.formatMessage({ - defaultMessage: 'Message', - description: 'ContactForm: message field label', - id: '0zBQpa', - }); - - return ( - <> - <Form submitHandler={submitHandler}> - <FormItem> - <Field - id="contact-name" - name="name" - value={name} - setValue={setName} - required={true} - label={getLabel(nameLabelBody, 'contact-name', true)} - /> - </FormItem> - <FormItem> - <Field - id="contact-email" - kind="email" - name="email" - value={email} - setValue={setEmail} - required={true} - label={getLabel(emailLabelBody, 'contact-email', true)} - /> - </FormItem> - <FormItem> - <Field - id="contact-subject" - name="subject" - value={subject} - setValue={setSubject} - label={getLabel(subjectLabelBody, 'contact-subject')} - /> - </FormItem> - <FormItem> - <Field - id="contact-message" - kind="textarea" - name="message" - value={message} - setValue={setMessage} - required={true} - label={getLabel(messageLabelBody, 'contact-message', true)} - /> - </FormItem> - <FormItem> - <ButtonSubmit> - {intl.formatMessage({ - defaultMessage: 'Send', - description: 'ContactForm: send button text', - id: 'X7n7N2', - })} - </ButtonSubmit> - </FormItem> - </Form> - {getStatus()} - </> - ); -}; - -export default ContactForm; diff --git a/src/components/Copyright/Copyright.module.scss b/src/components/Copyright/Copyright.module.scss deleted file mode 100644 index 35445b2..0000000 --- a/src/components/Copyright/Copyright.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - --icon-size: #{fun.convert-px(70)}; - - display: flex; - flex-flow: row wrap; - align-items: center; - place-content: center; - gap: var(--spacing-2xs); - margin: 0; - font-family: var(--font-family-secondary); - font-size: var(--font-size-md); - text-align: center; - - @include mix.media("screen") { - @include mix.dimensions("sm") { - place-content: start; - text-align: left; - } - } -} - -.name { - flex: 1 0 100%; - - @include mix.media("screen") { - @include mix.dimensions("sm") { - flex: auto; - } - } -} diff --git a/src/components/Copyright/Copyright.tsx b/src/components/Copyright/Copyright.tsx deleted file mode 100644 index d2de2e9..0000000 --- a/src/components/Copyright/Copyright.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { CopyrightIcon } from '@components/Icons'; -import { settings } from '@utils/config'; -import styles from './Copyright.module.scss'; - -const Copyright = () => { - return ( - <p className={styles.wrapper}> - <span className={styles.name}>{settings.name}</span> - <CopyrightIcon /> - <span> - {settings.copyright.startYear} - {settings.copyright.endYear} - </span> - </p> - ); -}; - -export default Copyright; diff --git a/src/components/Footer/Footer.module.scss b/src/components/Footer/Footer.module.scss deleted file mode 100644 index 1d156f8..0000000 --- a/src/components/Footer/Footer.module.scss +++ /dev/null @@ -1,90 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - display: flex; - flex-flow: column wrap; - gap: var(--spacing-xs); - place-items: center; - place-content: center; - padding: var(--spacing-md) 0 calc(var(--toolbar-size) + var(--spacing-md)); - border-top: fun.convert-px(3) solid var(--color-border-light); - - @include mix.media("screen") { - @include mix.dimensions("sm") { - flex-flow: row wrap; - font-size: var(--font-size-sm); - } - } -} - -.back-to-top { - --button-size: #{fun.convert-px(55)}; - --icon-size: #{fun.convert-px(32)}; - - position: fixed; - bottom: calc(var(--toolbar-size) + var(--spacing-md)); - right: var(--spacing-md); - transition: all 0.4s ease-in 0s; - - &--hidden { - opacity: 0; - transform: translateY(calc(var(--button-size) + var(--spacing-md))); - } - - &--visible { - opacity: 1; - transform: translateY(0); - } - - a { - display: flex; - place-content: center; - padding: 0; - width: var(--button-size); - height: var(--button-size); - - svg { - height: 85%; - } - - :global { - .arrow-head { - transform: translateY(30%); - transition: all 0.45s ease-in-out 0s; - } - - .arrow-bar { - opacity: 0; - transform: translateY(30%) translateX(25%) scale(0.5); - transition: transform 0.45s ease-in-out 0s, opacity 0.3s ease-in-out 0s; - } - } - - &:hover, - &:focus { - :global { - .arrow-head { - transform: translateY(0); - } - - .arrow-bar { - opacity: 1; - transform: translateY(0) translateX(0) scale(1); - } - } - - svg { - :global { - animation: pulse 1.2s ease-in-out 0.6s infinite; - } - } - } - - &:active { - svg { - animation-play-state: paused; - } - } - } -} diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx deleted file mode 100644 index 381b4a8..0000000 --- a/src/components/Footer/Footer.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { ButtonLink } from '@components/Buttons'; -import Copyright from '@components/Copyright/Copyright'; -import FooterNav from '@components/FooterNav/FooterNav'; -import { ArrowIcon } from '@components/Icons'; -import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './Footer.module.scss'; - -const Footer = () => { - const intl = useIntl(); - const [backToTopClasses, setBackToTopClasses] = useState( - `${styles['back-to-top']} ${styles['back-to-top--hidden']}` - ); - - const handleScroll = () => { - const currentScrollY = window.scrollY; - - if (currentScrollY > 300) { - setBackToTopClasses( - `${styles['back-to-top']} ${styles['back-to-top--visible']}` - ); - } else { - setBackToTopClasses( - `${styles['back-to-top']} ${styles['back-to-top--hidden']}` - ); - } - }; - - useEffect(() => { - window.addEventListener('scroll', handleScroll); - return () => window.removeEventListener('scroll', handleScroll); - }, []); - - return ( - <footer className={styles.wrapper}> - <Copyright /> - <FooterNav /> - <div className={backToTopClasses}> - <ButtonLink target="#top" position="center"> - <span className="screen-reader-text"> - {intl.formatMessage({ - defaultMessage: 'Back to top', - description: 'Footer: Back to top button', - id: 'dqrd6I', - })} - </span> - <ArrowIcon direction="top" /> - </ButtonLink> - </div> - </footer> - ); -}; - -export default Footer; diff --git a/src/components/FooterNav/FooterNav.module.scss b/src/components/FooterNav/FooterNav.module.scss deleted file mode 100644 index 73ea568..0000000 --- a/src/components/FooterNav/FooterNav.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.list { - @extend %flex-list; - - gap: var(--spacing-xs); - place-content: center; -} - -.item { - @include mix.media("screen") { - @include mix.dimensions("sm") { - &::before { - content: "\2022"; - margin-right: var(--spacing-xs); - } - } - } -} diff --git a/src/components/FooterNav/FooterNav.tsx b/src/components/FooterNav/FooterNav.tsx deleted file mode 100644 index 763e951..0000000 --- a/src/components/FooterNav/FooterNav.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import Link from 'next/link'; -import styles from './FooterNav.module.scss'; -import { NavItem } from '@ts/types/nav'; -import { useIntl } from 'react-intl'; - -const FooterNav = () => { - const intl = useIntl(); - - const footerNavConfig: NavItem[] = [ - { - id: 'legal-notice', - name: intl.formatMessage({ - defaultMessage: 'Legal notice', - description: 'FooterNav: legal notice link', - id: 'yWjXRx', - }), - slug: '/mentions-legales', - }, - ]; - - const navItems = footerNavConfig.map((item) => { - return ( - <li key={item.id} className={styles.item}> - <Link href={item.slug}> - <a className={styles.link}>{item.name}</a> - </Link> - </li> - ); - }); - - return ( - <nav - className={styles.nav} - aria-label={intl.formatMessage({ - defaultMessage: 'Footer', - description: 'FooterNav: aria-label', - id: 'HTdaZj', - })} - > - <ul className={styles.list}>{navItems}</ul> - </nav> - ); -}; - -export default FooterNav; diff --git a/src/components/FormElements/Field/Field.module.scss b/src/components/FormElements/Field/Field.module.scss deleted file mode 100644 index 9100495..0000000 --- a/src/components/FormElements/Field/Field.module.scss +++ /dev/null @@ -1,53 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.field { - background: var(--color-bg-tertiary); - border: fun.convert-px(2) solid var(--color-border); - box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow); - transition: all 0.25s linear 0s; - - &:not(.select) { - width: 100%; - padding: var(--spacing-2xs) var(--spacing-xs); - } - - &:hover { - box-shadow: fun.convert-px(5) fun.convert-px(5) 0 fun.convert-px(1) - var(--color-shadow); - transform: translate(#{fun.convert-px(-3)}, #{fun.convert-px(-3)}); - } - - &:focus { - background: var(--color-bg); - border-color: var(--color-primary); - box-shadow: 0 0 0 0 var(--color-shadow); - transform: translate(#{fun.convert-px(3)}, #{fun.convert-px(3)}); - outline: none; - transition: all 0.2s ease-in-out 0s, transform 0.3s ease-out 0s; - } -} - -.select { - padding: var(--spacing-2xs) var(--spacing-xs); - cursor: pointer; - - @include mix.pointer("fine") { - padding: fun.convert-px(3) var(--spacing-xs); - } - - &:hover { - box-shadow: fun.convert-px(4) fun.convert-px(4) 0 fun.convert-px(1) - var(--color-shadow); - transform: translate(#{fun.convert-px(-2)}, #{fun.convert-px(-2)}); - } - - &:focus { - box-shadow: 0 0 0 0 var(--color-shadow); - transform: translate(#{fun.convert-px(3)}, #{fun.convert-px(3)}); - } -} - -.textarea { - min-height: fun.convert-px(200); -} diff --git a/src/components/FormElements/Field/Field.tsx b/src/components/FormElements/Field/Field.tsx deleted file mode 100644 index c8df0f9..0000000 --- a/src/components/FormElements/Field/Field.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { - ChangeEvent, - ForwardedRef, - forwardRef, - ReactElement, - SetStateAction, -} from 'react'; -import styles from './Field.module.scss'; - -type FieldType = 'email' | 'number' | 'search' | 'select' | 'text' | 'textarea'; -type SelectOptions = { - id: string; - name: string; - value: string; -}; - -const Field = ( - { - id, - name, - value, - setValue, - required = false, - kind = 'text', - label, - options, - }: { - id: string; - name: string; - value: string; - setValue: (value: SetStateAction<string>) => void; - required?: boolean; - kind?: FieldType; - label?: ReactElement; - options?: SelectOptions[]; - }, - ref: ForwardedRef<HTMLInputElement | HTMLTextAreaElement> -) => { - const updateValue = ( - e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> - ) => { - setValue(e.target.value); - }; - - const getOptions = () => { - return options - ? options.map((option) => ( - <option key={option.id} value={option.value}> - {option.name} - </option> - )) - : ''; - }; - - const getField = () => { - switch (kind) { - case 'select': - return ( - <select - name={name} - id={id} - value={value} - onChange={updateValue} - required={required} - className={`${styles.field} ${styles.select}`} - > - {getOptions()} - </select> - ); - case 'textarea': - return ( - <textarea - ref={ref as ForwardedRef<HTMLTextAreaElement>} - id={id} - name={name} - value={value} - required={required} - onChange={updateValue} - className={`${styles.field} ${styles.textarea}`} - /> - ); - default: - return ( - <input - ref={ref as ForwardedRef<HTMLInputElement>} - type={kind} - id={id} - name={name} - value={value} - required={required} - onChange={updateValue} - className={styles.field} - /> - ); - } - }; - - return ( - <> - {label} - {getField()} - </> - ); -}; - -export default forwardRef(Field); diff --git a/src/components/FormElements/Form/Form.module.scss b/src/components/FormElements/Form/Form.module.scss deleted file mode 100644 index 0f7c437..0000000 --- a/src/components/FormElements/Form/Form.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.wrapper { - width: 100%; -} - -.centered { - max-width: 45ch; - margin-left: auto; - margin-right: auto; -} - -.search { - display: flex; - flex-flow: row nowrap; - align-items: center; - - > input { - padding-right: calc(var(--btn-size) + var(--spacing-2xs)); - - &:hover ~ button { - transform: translate(fun.convert-px(-3), fun.convert-px(-3)); - } - - &:focus ~ button { - transform: translate(fun.convert-px(3), fun.convert-px(3)); - } - } -} - -.settings { - display: flex; - flex-flow: row nowrap; - align-items: center; - margin: var(--spacing-sm) 0; - position: relative; -} diff --git a/src/components/FormElements/Form/Form.tsx b/src/components/FormElements/Form/Form.tsx deleted file mode 100644 index 10fdcdf..0000000 --- a/src/components/FormElements/Form/Form.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ReactNode } from 'react'; -import styles from './Form.module.scss'; - -type FormKind = 'centered' | 'search' | 'settings'; - -const Form = ({ - children, - submitHandler, - kind, - id, -}: { - children: ReactNode; - submitHandler: any; - kind?: FormKind; - id?: string; -}) => { - const kindStyles = kind ? styles[kind] : ''; - const classes = `${styles.wrapper} ${kindStyles}`; - - return ( - <form onSubmit={submitHandler} className={classes} id={id}> - {children} - </form> - ); -}; - -export default Form; diff --git a/src/components/FormElements/FormItem/FormItem.module.scss b/src/components/FormElements/FormItem/FormItem.module.scss deleted file mode 100644 index 07ef56f..0000000 --- a/src/components/FormElements/FormItem/FormItem.module.scss +++ /dev/null @@ -1,4 +0,0 @@ -.wrapper { - margin: var(--spacing-xs) 0; - max-width: 45ch; -} diff --git a/src/components/FormElements/FormItem/FormItem.tsx b/src/components/FormElements/FormItem/FormItem.tsx deleted file mode 100644 index 8d674f1..0000000 --- a/src/components/FormElements/FormItem/FormItem.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import styles from './FormItem.module.scss'; - -const FormItem: React.FunctionComponent = ({ children }) => { - return <div className={styles.wrapper}>{children}</div>; -}; - -export default FormItem; diff --git a/src/components/FormElements/Label/Label.module.scss b/src/components/FormElements/Label/Label.module.scss deleted file mode 100644 index c527b16..0000000 --- a/src/components/FormElements/Label/Label.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.regular { - display: block; - color: var(--color-primary-darker); - font-size: var(--font-size-sm); - font-variant: small-caps; - font-weight: 600; -} - -.settings { - --icon-size: #{fun.convert-px(25)}; - --toggle-width: #{fun.convert-px(45)}; - --toggle-height: calc(var(--toggle-width) / 2); - - display: inline-flex; - align-items: center; -} - -.required { - color: var(--color-secondary); -} diff --git a/src/components/FormElements/Label/Label.tsx b/src/components/FormElements/Label/Label.tsx deleted file mode 100644 index baedff0..0000000 --- a/src/components/FormElements/Label/Label.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import styles from './Label.module.scss'; - -type LabelKind = 'regular' | 'settings'; - -const Label = ({ - body, - htmlFor, - required = false, - kind = 'regular', -}: { - body: string; - htmlFor: string; - required?: boolean; - kind?: LabelKind; -}) => { - return ( - <label htmlFor={htmlFor} className={styles[kind]}> - {body} - {required && <span className={styles.required}> *</span>} - </label> - ); -}; - -export default Label; diff --git a/src/components/FormElements/Toggle/Toggle.module.scss b/src/components/FormElements/Toggle/Toggle.module.scss deleted file mode 100644 index 48c88f6..0000000 --- a/src/components/FormElements/Toggle/Toggle.module.scss +++ /dev/null @@ -1,75 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.label { - --icon-size: #{fun.convert-px(25)}; - --toggle-width: #{fun.convert-px(45)}; - --toggle-height: calc(var(--toggle-width) / 2); - - display: inline-flex; - align-items: center; -} - -.title { - margin-right: var(--spacing-xs); -} - -.toggle { - display: inline-flex; - align-items: center; - width: var(--toggle-width); - height: var(--toggle-height); - background: var(--color-shadow-light); - border: fun.convert-px(1) solid var(--color-primary); - border-radius: fun.convert-px(32); - box-shadow: inset 0 0 fun.convert-px(3) 0 var(--color-shadow-dark); - margin: 0 var(--spacing-2xs); - position: relative; - - &::after { - content: ""; - display: block; - width: calc(var(--toggle-width) / 2); - height: calc(var(--toggle-width) / 2); - background: var(--color-primary-light); - border: fun.convert-px(1) solid var(--color-primary); - border-radius: 50%; - box-shadow: inset 0 0 fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - 0 0 fun.convert-px(2) fun.convert-px(1) var(--color-shadow-light); - position: absolute; - left: fun.convert-px(-2); - transition: all 0.3s ease-in-out 0s; - } -} - -.checkbox { - position: absolute; - opacity: 0; - cursor: pointer; - - &:checked ~ .label { - .toggle::after { - position: absolute; - left: calc(100% - (var(--toggle-width) / 2) + #{fun.convert-px(2)}); - } - } - - &:hover, - &:focus { - ~ .label { - .toggle::after { - background: var(--color-primary-lighter); - } - } - } - - &:focus ~ .label { - .title { - text-decoration: underline solid var(--color-primary) fun.convert-px(2); - } - - .toggle { - outline: var(--color-border) solid fun.convert-px(5); - } - } -} diff --git a/src/components/FormElements/Toggle/Toggle.tsx b/src/components/FormElements/Toggle/Toggle.tsx deleted file mode 100644 index 4db7d43..0000000 --- a/src/components/FormElements/Toggle/Toggle.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { FormEvent, ReactElement } from 'react'; -import { Form } from '..'; -import styles from './Toggle.module.scss'; - -const Toggle = ({ - id, - label, - value, - changeHandler, - leftChoice, - rightChoice, - name, -}: { - id: string; - label: string; - value: boolean; - changeHandler: (value: boolean) => void; - leftChoice: ReactElement | string; - rightChoice: ReactElement | string; - name?: string; -}) => { - const onSubmit = (e: FormEvent) => { - e.preventDefault(); - }; - - return ( - <Form kind="settings" submitHandler={onSubmit}> - <input - className={styles.checkbox} - type="checkbox" - id={id} - name={name ? name : id} - checked={value} - onChange={() => changeHandler(!value)} - /> - <label htmlFor={id} className={styles.label}> - <span className={styles.title}>{label}</span> - {leftChoice} - <span className={styles.toggle}></span> - {rightChoice} - </label> - </Form> - ); -}; - -export default Toggle; diff --git a/src/components/FormElements/index.tsx b/src/components/FormElements/index.tsx deleted file mode 100644 index 8ca69b4..0000000 --- a/src/components/FormElements/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import Field from './Field/Field'; -import Form from './Form/Form'; -import FormItem from './FormItem/FormItem'; -import Label from './Label/Label'; -import Toggle from './Toggle/Toggle'; - -export { Field, Form, FormItem, Label, Toggle }; diff --git a/src/components/Header/Header.module.scss b/src/components/Header/Header.module.scss deleted file mode 100644 index aa0d8cf..0000000 --- a/src/components/Header/Header.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.wrapper { - display: grid; - grid-template-columns: - minmax(0, 1fr) min(calc(100vw - calc(var(--spacing-md) * 2)), 100ch) - minmax(0, 1fr); - align-items: center; - padding: var(--spacing-sm) 0 var(--spacing-md); - position: relative; - background: var(--color-bg); - border-bottom: fun.convert-px(3) solid var(--color-border-light); -} - -.body { - grid-column: 2; - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: space-between; - gap: var(--spacing-md); -} diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx deleted file mode 100644 index 0b773e9..0000000 --- a/src/components/Header/Header.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import Branding from '@components/Branding/Branding'; -import Toolbar from '@components/Toolbar/Toolbar'; -import styles from './Header.module.scss'; - -const Header = ({ isHome }: { isHome: boolean }) => { - return ( - <header id="top" className={styles.wrapper}> - <div className={styles.body}> - <Branding isHome={isHome} /> - <Toolbar /> - </div> - </header> - ); -}; - -export default Header; diff --git a/src/components/Icons/Arrow/Arrow.module.scss b/src/components/Icons/Arrow/Arrow.module.scss deleted file mode 100644 index 49e9b02..0000000 --- a/src/components/Icons/Arrow/Arrow.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - fill: var(--color-primary); - width: var(--icon-size, #{fun.convert-px(30)}); - transition: all 0.25s ease-in-out 0s; -} diff --git a/src/components/Icons/Arrow/Arrow.tsx b/src/components/Icons/Arrow/Arrow.tsx deleted file mode 100644 index e9131d1..0000000 --- a/src/components/Icons/Arrow/Arrow.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import styles from './Arrow.module.scss'; - -type ArrowDirection = 'top' | 'right' | 'bottom' | 'left'; - -const ArrowIcon = ({ direction = 'right' }: { direction?: ArrowDirection }) => { - if (direction === 'top') { - return ( - <svg - className={styles.icon} - viewBox="0 0 23.476 64.644995" - xmlns="http://www.w3.org/2000/svg" - > - <path - className="arrow-head" - d="M 23.476001,24.637 11.715001,0 0,24.800001 Z" - /> - <path - className="arrow-bar" - d="m 15.441001,64.644997 -0.018,-40.007999 H 8.035 l 0.142,40.007999 z" - /> - </svg> - ); - } - - if (direction === 'bottom') { - return ( - <svg - className={styles.icon} - viewBox="0 0 23.476 64.644995" - xmlns="http://www.w3.org/2000/svg" - > - <path - className="arrow-head" - d="m 23.476001,40.007997 -11.761,24.637 L 0,39.844996 Z" - /> - <path - className="arrow-bar" - d="m 15.441001,0 -0.018,40.007999 H 8.035 L 8.177,0 Z" - /> - </svg> - ); - } - - if (direction === 'left') { - return ( - <svg - className={styles.icon} - viewBox="0 0 64.644997 23.476001" - xmlns="http://www.w3.org/2000/svg" - > - <path - className="arrow-head" - d="M 24.637,23.476 0,11.715 24.8,-8.3923343e-8 Z" - /> - <path - className="arrow-bar" - d="m 64.644997,15.441 -40.008,-0.018 V 8.0349999 l 40.008,0.142 z" - /> - </svg> - ); - } - - return ( - <svg - className={styles.icon} - viewBox="0 0 64.644997 23.476001" - xmlns="http://www.w3.org/2000/svg" - > - <path - className="arrow-head" - d="M 40.007997,23.476 64.644997,11.715 39.844997,-8.3923343e-8 Z" - /> - <path - className="arrow-bar" - d="M 0,15.441 40.008,15.423 V 8.0349999 L 0,8.1769999 Z" - /> - </svg> - ); -}; - -export default ArrowIcon; diff --git a/src/components/Icons/Blog/Blog.module.scss b/src/components/Icons/Blog/Blog.module.scss deleted file mode 100644 index 5376c61..0000000 --- a/src/components/Icons/Blog/Blog.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - margin: auto; - width: var(--icon-size, #{fun.convert-px(40)}); -} - -.lines { - fill: var(--color-fg); - stroke-width: 4; -} - -.picture { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); -} - -.background { - fill: var(--color-bg); - stroke: var(--color-primary-darker); - stroke-width: 4; -} diff --git a/src/components/Icons/Blog/Blog.tsx b/src/components/Icons/Blog/Blog.tsx deleted file mode 100644 index bd32111..0000000 --- a/src/components/Icons/Blog/Blog.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import styles from './Blog.module.scss'; - -const BlogIcon = () => { - return ( - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - className={styles.icon} - > - <path - className={styles.background} - d="M 28.992096,1.4822128 H 90.770752 V 82.312253 H 28.992096 Z" - /> - <path - className={styles.background} - d="m 19.110672,8.992094 h 61.778656 v 80.83004 H 19.110672 Z" - /> - <path - className={styles.background} - d="m 9.229248,17.687748 h 61.778656 v 80.83004 H 9.229248 Z" - /> - <path - className={styles.picture} - d="M 18.149242,74.65544 H 33.375246 V 90.194215 H 18.149242 Z" - /> - <path - className={styles.picture} - d="M 18.142653,24.858688 H 62.094499 V 35.908926 H 18.142653 Z" - /> - <path - className={styles.lines} - d="m 17.618576,41.908926 h 45 v 2 h -45 z" - /> - <path - className={styles.lines} - d="m 17.618576,49.908926 h 45 v 2 h -45 z" - /> - <path - className={styles.lines} - d="m 17.618576,57.908926 h 45 v 2 h -45 z" - /> - <path - className={styles.lines} - d="m 17.618576,65.908926 h 45 v 2 h -45 z" - /> - <path - className={styles.lines} - d="m 41.833105,73.424828 h 20.785471 v 2 H 41.833105 Z" - /> - <path - className={styles.lines} - d="m 41.833105,81.424828 h 20.785471 v 2 H 41.833105 Z" - /> - <path - className={styles.lines} - d="m 41.833105,89.424828 h 20.785471 v 2 H 41.833105 Z" - /> - </svg> - ); -}; - -export default BlogIcon; diff --git a/src/components/Icons/CV/CV.module.scss b/src/components/Icons/CV/CV.module.scss deleted file mode 100644 index aaa8a1a..0000000 --- a/src/components/Icons/CV/CV.module.scss +++ /dev/null @@ -1,54 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - margin: auto; - width: var(--icon-size, #{fun.convert-px(40)}); -} - -.lock { - fill: var(--color-bg); - stroke: var(--color-primary-darker); - stroke-width: 3; -} - -.lines { - fill: var(--color-fg); - stroke-width: 4; -} - -.seal-top { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 2; -} - -.seal-bottom { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 2; -} - -.diploma { - fill: var(--color-bg); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.top { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.handle { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 3; -} - -.bottom { - fill: var(--color-primary); - stroke: var(--color-primary-darker); - stroke-width: 4; -} diff --git a/src/components/Icons/CV/CV.tsx b/src/components/Icons/CV/CV.tsx deleted file mode 100644 index 876d1cb..0000000 --- a/src/components/Icons/CV/CV.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import styles from './CV.module.scss'; - -const CVIcon = () => { - return ( - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - className={styles.icon} - > - <path - className={styles.bottom} - d="M 0.72670447,19.813041 H 77.467597 v 54.36591 H 0.72670447 Z" - /> - <path - className={styles.handle} - d="m 22.263958,10.17849 c 12.6493,-1.81512 21.613185,-1.732794 33.666442,0 l 1.683339,10.99517 h -5.891624 v -5.474639 c -7.949741,-2.722434 -16.311959,-2.706359 -25.249837,0 v 5.474639 h -5.891625 z" - /> - <path - className={styles.top} - d="M 0.72670447,19.813041 H 77.467597 V 51.17622 H 0.72670447 Z" - /> - <path - className={styles.diploma} - d="M 44.217117,47.159906 H 98.921356 V 82.664122 H 44.217117 Z" - /> - <path - className={styles['seal-bottom']} - d="m 84.933665,80.775336 h 6.957554 V 90.992144 L 88.412426,87.2244 84.933665,90.992144 Z" - /> - <path - className={styles['seal-top']} - d="m 93.326919,76.83334 a 4.914472,4.9188584 0 0 1 -4.914493,4.918858 4.914472,4.9188584 0 0 1 -4.914461,-4.918858 4.914472,4.9188584 0 0 1 4.914461,-4.918858 4.914472,4.9188584 0 0 1 4.914493,4.918858 z" - /> - <path - className={styles.lines} - d="m 54.53557,60.491974 h 34.067282 v 1.515453 H 54.53557 Z" - /> - <path - className={styles.lines} - d="m 54.53557,67.437763 h 34.067282 v 1.515453 H 54.53557 Z" - /> - <path - className={styles.lines} - d="m 54.53557,74.383628 h 17.563315 v 1.515454 H 54.53557 Z" - /> - <path - className={styles.lines} - d="m 63.495911,53.546123 h 16.146628 v 1.515452 H 63.495911 Z" - /> - <path - className={styles.lock} - d="M 34.048314,42.893007 H 44.145988 V 57.849688 H 34.048314 Z" - /> - </svg> - ); -}; - -export default CVIcon; diff --git a/src/components/Icons/Close/Close.module.scss b/src/components/Icons/Close/Close.module.scss deleted file mode 100644 index 5a1f638..0000000 --- a/src/components/Icons/Close/Close.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - margin: auto; - width: var(--icon-size, #{fun.convert-px(40)}); -} - -.line { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 3; -} diff --git a/src/components/Icons/Close/Close.tsx b/src/components/Icons/Close/Close.tsx deleted file mode 100644 index 12214de..0000000 --- a/src/components/Icons/Close/Close.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import styles from './Close.module.scss'; - -const CloseIcon = () => { - return ( - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - className={styles.icon} - > - <path - className={styles.line} - d="m 3.6465461,3.6465455 c 2.8785908,-2.87859092 7.5134339,-2.87859092 10.3920249,0 L 96.353457,85.96143 c 2.878587,2.878591 2.878587,7.513434 0,10.392025 -2.878597,2.878591 -7.513432,2.878591 -10.392029,0 L 3.6465451,14.038571 C 0.76795421,11.15998 0.76795421,6.5251364 3.6465461,3.6465455 Z" - /> - <path - className={styles.line} - d="m 96.353453,3.646546 c 2.878592,2.8785909 2.878592,7.513435 0,10.392026 L 14.03857,96.353457 c -2.878589,2.878587 -7.5134337,2.878587 -10.3920246,0 -2.87859084,-2.878597 -2.87858985,-7.513442 -1e-6,-10.392029 L 85.961428,3.646546 c 2.878591,-2.87859097 7.513434,-2.87859097 10.392025,0 z" - /> - </svg> - ); -}; - -export default CloseIcon; diff --git a/src/components/Icons/Cog/Cog.module.scss b/src/components/Icons/Cog/Cog.module.scss deleted file mode 100644 index a861f0c..0000000 --- a/src/components/Icons/Cog/Cog.module.scss +++ /dev/null @@ -1,10 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - width: var(--icon-size, #{fun.convert-px(40)}); - margin: auto; - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 4; -} diff --git a/src/components/Icons/Cog/Cog.tsx b/src/components/Icons/Cog/Cog.tsx deleted file mode 100644 index 7a04d76..0000000 --- a/src/components/Icons/Cog/Cog.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import styles from './Cog.module.scss'; - -const CogIcon = () => { - return ( - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - className={styles.icon} - > - <path d="m 71.782287,3.1230469 c -1.164356,0 -2.3107,0.076326 -3.435131,0.2227895 L 66.33766,9.1021499 C 64.651951,9.5517047 63.049493,10.204637 61.558109,11.033725 L 56.112383,8.2889128 c -1.970928,1.4609237 -3.730521,3.1910632 -5.22513,5.1351362 l 2.648234,5.494014 c -0.855644,1.477262 -1.537042,3.067161 -2.016082,4.743334 l -5.791433,1.911821 c -0.188001,1.269731 -0.286444,2.568579 -0.286444,3.890587 0,1.164355 0.07633,2.310701 0.222789,3.435131 l 5.756315,2.009497 c 0.449555,1.685708 1.102486,3.288168 1.931575,4.779551 l -2.744813,5.445725 c 1.460924,1.970927 3.191063,3.730521 5.135137,5.22513 l 5.494014,-2.648233 c 1.477261,0.85564 3.067161,1.537039 4.743334,2.016081 L 67.8917,55.51812 c 1.26973,0.188002 2.568578,0.286444 3.890587,0.286444 1.16565,0 2.313889,-0.07601 3.43952,-0.222789 l 2.008399,-5.756314 c 1.684332,-0.449523 3.285984,-1.103103 4.776259,-1.931575 l 5.445725,2.744812 c 1.970928,-1.460924 3.730521,-3.191061 5.22513,-5.135136 l -2.648233,-5.494015 c 0.85564,-1.477262 1.537039,-3.067161 2.016082,-4.743334 l 5.79253,-1.91182 c 0.187995,-1.269731 0.285346,-2.56858 0.285346,-3.890588 0,-1.16565 -0.07601,-2.313889 -0.222789,-3.439521 L 92.143942,24.015886 C 91.694419,22.331554 91.04084,20.729903 90.212367,19.239628 l 2.744812,-5.445726 C 91.496255,11.822973 89.766118,10.063381 87.822043,8.5687715 L 82.328028,11.217006 C 80.850766,10.361361 79.260867,9.6799641 77.584694,9.2009234 L 75.672874,3.4094907 C 74.403143,3.2214898 73.104295,3.1230469 71.782287,3.1230469 Z m 0,15.0520191 a 11.288679,11.288679 0 0 1 11.288739,11.288739 11.288679,11.288679 0 0 1 -11.288739,11.28874 11.288679,11.288679 0 0 1 -11.28874,-11.28874 11.288679,11.288679 0 0 1 11.28874,-11.288739 z" /> - <path d="m 38.326115,25.84777 c -1.583642,0 -3.142788,0.103807 -4.672127,0.303016 l -2.73312,7.829173 c -2.292736,0.611441 -4.472242,1.499494 -6.500676,2.627139 L 17.01345,32.873874 c -2.680664,1.987004 -5.073889,4.340169 -7.1067095,6.984309 l 3.6018685,7.472418 c -1.163764,2.009226 -2.090533,4.171652 -2.742078,6.451418 l -7.8769382,2.60027 C 2.6338924,58.109252 2.5,59.875819 2.5,61.673885 c 0,1.583642 0.1038125,3.142788 0.3030165,4.672128 l 7.8291725,2.73312 c 0.611441,2.292734 1.499494,4.472243 2.627139,6.500673 L 9.5261037,82.98655 c 1.9870063,2.680661 4.3401703,5.07389 6.9843093,7.106709 l 7.472419,-3.601867 c 2.009226,1.16376 4.171651,2.090533 6.451418,2.742079 l 2.60027,7.876932 C 34.761483,97.366114 36.528049,97.5 38.326115,97.5 c 1.585404,0 3.147126,-0.103373 4.678099,-0.303015 l 2.731628,-7.829178 c 2.290862,-0.611397 4.469272,-1.500329 6.496197,-2.627132 l 7.406741,3.733224 c 2.680664,-1.987007 5.07389,-4.340171 7.10671,-6.984313 l -3.601866,-7.472415 c 1.163756,-2.00923 2.090529,-4.171655 2.742076,-6.45142 l 7.878431,-2.60027 c 0.255691,-1.726964 0.3881,-3.49353 0.3881,-5.291596 0,-1.585404 -0.103373,-3.147127 -0.303016,-4.678099 L 66.020041,54.264159 C 65.408645,51.973296 64.51971,49.794888 63.392903,47.767962 l 3.733224,-7.406742 c -1.987006,-2.680664 -4.340168,-5.073889 -6.984309,-7.10671 l -7.472419,3.601867 c -2.009228,-1.163762 -4.171651,-2.090533 -6.451418,-2.742076 l -2.60027,-7.876939 C 41.890748,25.981661 40.124181,25.84777 38.326115,25.84777 Z m 0,20.472278 A 15.353754,15.353754 0 0 1 53.679952,61.673885 15.353754,15.353754 0 0 1 38.326115,77.027724 15.353754,15.353754 0 0 1 22.972279,61.673885 15.353754,15.353754 0 0 1 38.326115,46.320048 Z" /> - </svg> - ); -}; - -export default CogIcon; diff --git a/src/components/Icons/Contact/Contact.module.scss b/src/components/Icons/Contact/Contact.module.scss deleted file mode 100644 index 963c1dc..0000000 --- a/src/components/Icons/Contact/Contact.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - margin: auto; - width: var(--icon-size, #{fun.convert-px(40)}); -} - -.envelop { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.lines { - fill: var(--color-fg); -} - -.background { - fill: var(--color-shadow-dark); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.paper { - fill: var(--color-bg); - stroke: var(--color-primary-darker); - stroke-width: 4; -} diff --git a/src/components/Icons/Contact/Contact.tsx b/src/components/Icons/Contact/Contact.tsx deleted file mode 100644 index 19295d0..0000000 --- a/src/components/Icons/Contact/Contact.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import styles from './Contact.module.scss'; - -const ContactIcon = () => { - return ( - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - className={styles.icon} - > - <path - className={styles.background} - d="M 1.5262527,42.535416 H 98.473747 V 98.371662 H 1.5262527 Z" - /> - <path - className={styles.envelop} - d="m 49.999985,1.6283075 c 2.855148,0 48.473753,40.8563885 48.473753,40.8563885 H 1.5262359 c 0,0 45.6186001,-40.8563885 48.4737491,-40.8563885 z" - /> - <path - className={styles.paper} - d="M 8.3434839,28.463842 H 91.656465 V 97.348661 H 8.3434839 Z" - /> - <path - className={styles.envelop} - d="M 49.999985,63.571925 98.473738,98.371692 H 1.5262359 Z" - /> - <path - className={styles.lines} - d="m 24.562439,37.640923 h 50.875053 v 1.5 H 24.562439 Z" - /> - <path - className={styles.lines} - d="m 24.562439,45.140923 h 50.875053 v 1.5 H 24.562439 Z" - /> - <path - className={styles.lines} - d="m 24.562443,52.640923 h 50.875053 v 1.5 H 24.562443 Z" - /> - <path - className={styles.lines} - d="M 24.562447,60.140923 H 75.4375 v 1.5 H 24.562447 Z" - /> - <path - className={styles.envelop} - d="M 39.93749,70.965004 1.5262559,43.55838 v 54.813242 z" - /> - <path - className={styles.envelop} - d="M 60.0625,70.965004 98.473738,43.55838 v 54.813242 z" - /> - </svg> - ); -}; - -export default ContactIcon; diff --git a/src/components/Icons/Copyright/Copyright.module.scss b/src/components/Icons/Copyright/Copyright.module.scss deleted file mode 100644 index 8ea801e..0000000 --- a/src/components/Icons/Copyright/Copyright.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - width: var(--icon-size, #{fun.convert-px(40)}); - fill: var(--color-fg); -} diff --git a/src/components/Icons/Copyright/Copyright.tsx b/src/components/Icons/Copyright/Copyright.tsx deleted file mode 100644 index d27c042..0000000 --- a/src/components/Icons/Copyright/Copyright.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import styles from './Copyright.module.scss'; - -const CopyrightIcon = () => { - return ( - <svg - className={styles.icon} - viewBox="0 0 211.99811 63.999996" - xmlns="http://www.w3.org/2000/svg" - > - <title>CC BY SA</title> - <path d="m 175.53911,15.829498 c 0,-3.008 1.485,-4.514 4.458,-4.514 2.973,0 4.457,1.504 4.457,4.514 0,2.971 -1.486,4.457 -4.457,4.457 -2.971,0 -4.458,-1.486 -4.458,-4.457 z" /> - <path d="m 188.62611,24.057498 v 13.085 h -3.656 v 15.542 h -9.944 v -15.541 h -3.656 v -13.086 c 0,-0.572 0.2,-1.057 0.599,-1.457 0.401,-0.399 0.887,-0.6 1.457,-0.6 h 13.144 c 0.533,0 1.01,0.2 1.428,0.6 0.417,0.4 0.628,0.886 0.628,1.457 z" /> - <path d="m 179.94147,-1.9073486e-6 c -8.839,0 -16.34167,3.0848125073486 -22.51367,9.2578125073486 -6.285,6.4000004 -9.42969,13.9811874 -9.42969,22.7421874 0,8.762 3.14469,16.284312 9.42969,22.570312 6.361,6.286 13.86467,9.429688 22.51367,9.429688 8.799,0 16.43611,-3.181922 22.91211,-9.544922 6.096,-5.98 9.14453,-13.464078 9.14453,-22.455078 0,-8.952 -3.10646,-16.532188 -9.31446,-22.7421874 -6.172,-6.172 -13.75418,-9.2578125073486 -22.74218,-9.2578125073486 z M 180.05475,5.7714825 c 7.238,0 13.40967,2.55225 18.51367,7.6562495 5.103,5.106 7.65625,11.294313 7.65625,18.570313 0,7.391 -2.51397,13.50575 -7.54297,18.34375 -5.295,5.221 -11.50591,7.828125 -18.6289,7.828125 -7.162,0 -13.33268,-2.589484 -18.51368,-7.771484 -5.18,-5.178001 -7.76953,-11.310485 -7.76953,-18.396485 0,-7.047 2.60813,-13.238266 7.82813,-18.572265 5.029,-5.1040004 11.18103,-7.6582035 18.45703,-7.6582035 z" /> - <path d="m 91.998554,27.114498 c 0.609,-3.924 2.189,-6.962 4.742,-9.114 2.552,-2.152 5.655996,-3.228 9.313996,-3.228 5.027,0 9.029,1.62 12,4.856 2.971,3.238 4.457,7.391 4.457,12.457 0,4.915 -1.543,9 -4.627,12.256 -3.088,3.256 -7.086,4.886 -12.002,4.886 -3.619,0 -6.742996,-1.085 -9.370996,-3.257 -2.629,-2.172 -4.209,-5.257 -4.743,-9.257 h 8.059 c 0.189996,3.886 2.532996,5.829 7.028996,5.829 2.246,0 4.057,-0.972 5.428,-2.914 1.373,-1.942 2.059,-4.534 2.059,-7.771 0,-3.391 -0.629,-5.971 -1.885,-7.743 -1.258,-1.771 -3.066,-2.657 -5.43,-2.657 -4.268,0 -6.667,1.885 -7.199996,5.656 h 2.342996 l -6.341996,6.343 -6.343,-6.343 z" /> - <path d="m 105.94241,-1.8610229e-6 c -8.799996,0 -16.304676,3.1054062610229 -22.513666,9.3164061610229 -6.285,6.3999997 -9.42969,13.9625467 -9.42969,22.6855467 0,8.763 3.14469,16.28336 9.42969,22.568359 6.361,6.286001 13.86467,9.429688 22.513666,9.429688 8.836,0 16.47511,-3.162328 22.91211,-9.486328 6.096,-6.057 9.14453,-13.559672 9.14453,-22.513672 0,-8.952 -3.10646,-16.513547 -9.31446,-22.6855468 -6.211,-6.21 -13.79118,-9.3144530610229 -22.74218,-9.3144530610229 z M 106.05569,5.7714825 c 7.275,0 13.44667,2.5698437 18.51367,7.7148435 5.103,5.028 7.65625,11.200672 7.65625,18.513672 0,7.353 -2.51397,13.46775 -7.54297,18.34375 -5.295,5.219 -11.50591,7.828125 -18.6289,7.828125 -7.161996,0 -13.332676,-2.589484 -18.513676,-7.771484 -5.18,-5.143 -7.76953,-11.275391 -7.76953,-18.400391 0,-7.046 2.60813,-13.217672 7.82813,-18.513672 5.029,-5.1429998 11.18103,-7.7148435 18.457026,-7.7148435 z" /> - <path d="M 31.942383,5.9265138e-7 C 23.066111,5.9265138e-7 15.579851,3.1065496 9.484666,9.3147376 6.399571,12.400832 4.046856,15.896269 2.427808,19.801386 0.80876,23.706506 0,27.771846 0,32.000976 c 0,4.26713 0.800415,8.32413 2.400463,12.17225 1.600051,3.84811 3.933123,7.30532 7.000216,10.37141 3.067093,3.06609 6.534587,5.40951 10.400708,7.02756 3.867116,1.62105 7.914819,2.4278 12.142946,2.4278 4.22813,0 8.32441,-0.8171 12.28553,-2.45515 3.96313,-1.63805 7.50614,-4.00301 10.62923,-7.0881 3.0081,-2.93309 5.28529,-6.31477 6.82834,-10.14289 1.54104,-3.82712 2.31257,-7.93174 2.31257,-12.31288 0,-4.34313 -0.78277,-8.44771 -2.34382,-12.31483 C 60.094133,15.82003 57.808593,12.380471 54.800503,9.3713796 48.515313,3.1241896 40.893653,5.9265136e-7 31.942383,5.9265138e-7 Z M 32.057623,5.7716626 c 7.23822,0 13.42863,2.571923 18.57478,7.7150794 2.47408,2.478074 4.35948,5.297144 5.65252,8.459244 1.29504,3.16209 1.94342,6.51384 1.94342,10.05694 0,7.35423 -2.49445,13.46816 -7.4846,18.34432 -2.59208,2.51407 -5.49406,4.43661 -8.71316,5.77166 -3.2231,1.33404 -6.54486,1.9981 -9.97296,1.9981 -3.467107,0 -6.782568,-0.65672 -9.943661,-1.97076 -3.164098,-1.31604 -5.999858,-3.21894 -8.513933,-5.71502 -2.515077,-2.49507 -4.447918,-5.33279 -5.800959,-8.51588 -1.354042,-3.1791 -2.029358,-6.48331 -2.029358,-9.91242 0,-3.4671 0.675316,-6.79186 2.029358,-9.97295 1.352043,-3.1811 3.285882,-6.046798 5.800959,-8.599875 4.991151,-5.1041594 11.14337,-7.6584384 18.457594,-7.6584384 z" /> - <path d="m 50.114533,26.687816 -4.22913,2.22907 c -0.45702,-0.95103 -1.02003,-1.61905 -1.68605,-2.00006 -0.66802,-0.38001 -1.30704,-0.57102 -1.91406,-0.57102 -2.85709,0 -4.28713,1.88506 -4.28713,5.65717 0,1.71406 0.363,3.0841 1.08603,4.11313 0.72302,1.02903 1.78906,1.54405 3.2011,1.54405 1.86506,0 3.1801,-0.91503 3.94112,-2.74309 l 4.00012,2.00007 c -0.87502,1.56304 -2.05706,2.79108 -3.54111,3.68611 -1.48604,0.89602 -3.10509,1.34304 -4.85715,1.34304 -2.89608,0 -5.20915,-0.87503 -6.94121,-2.62908 -1.73605,-1.75205 -2.60207,-4.19013 -2.60207,-7.31323 0,-3.04809 0.88502,-5.46616 2.65808,-7.25722 1.77005,-1.79005 4.00812,-2.68608 6.7132,-2.68608 3.96212,-0.002 6.78321,1.54105 8.45826,4.62714 z" /> - <path d="m 31.656963,26.687816 -4.287128,2.22907 c -0.458013,-0.95103 -1.019029,-1.61905 -1.685048,-2.00006 -0.667024,-0.38001 -1.286042,-0.57102 -1.858057,-0.57102 -2.856087,0 -4.28613,1.88506 -4.28613,5.65717 0,1.71406 0.362014,3.0841 1.085029,4.11313 0.724025,1.02903 1.791056,1.54405 3.201101,1.54405 1.867057,0 3.181095,-0.91503 3.944118,-2.74309 l 3.942125,2.00007 c -0.83803,1.56304 -2.000065,2.79108 -3.486111,3.68611 -1.484043,0.89602 -3.123093,1.34304 -4.914149,1.34304 -2.857088,0 -5.163158,-0.87503 -6.915212,-2.62908 -1.752053,-1.75205 -2.62808,-4.19013 -2.62808,-7.31323 0,-3.04809 0.886028,-5.46616 2.657081,-7.25722 1.771054,-1.79005 4.009125,-2.68608 6.715205,-2.68608 3.963122,-0.002 6.800209,1.54105 8.515256,4.62714 z" /> - </svg> - ); -}; - -export default CopyrightIcon; diff --git a/src/components/Icons/Hamburger/Hamburger.module.scss b/src/components/Icons/Hamburger/Hamburger.module.scss deleted file mode 100644 index 9965c5e..0000000 --- a/src/components/Icons/Hamburger/Hamburger.module.scss +++ /dev/null @@ -1,56 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - position: relative; - width: 100%; - - &, - &::before, - &::after { - background: var(--color-primary-lighter); - background-image: linear-gradient( - to right, - var(--color-primary-light) 0%, - var(--color-primary-lighter) 100% - ); - border: fun.convert-px(1) solid var(--color-primary-darker); - border-radius: fun.convert-px(3); - display: block; - height: fun.convert-px(7); - margin: auto; - transition: all 0.25s ease-in-out 0s, transform 0.4s ease-in 0s; - } - - &::before, - &::after { - content: ""; - position: absolute; - left: fun.convert-px(-1); - right: fun.convert-px(-1); - } - - &::before { - bottom: fun.convert-px(15); - } - - &::after { - top: fun.convert-px(15); - } - - &--active { - background: transparent; - border: transparent; - - &::before { - transform-origin: 50% 50%; - transform: rotate(45deg); - bottom: 0; - } - - &::after { - transform-origin: 50% 50%; - transform: rotate(-45deg); - top: 0; - } - } -} diff --git a/src/components/Icons/Hamburger/Hamburger.tsx b/src/components/Icons/Hamburger/Hamburger.tsx deleted file mode 100644 index 9b39272..0000000 --- a/src/components/Icons/Hamburger/Hamburger.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import styles from './Hamburger.module.scss'; - -const HamburgerIcon = ({ isActive }: { isActive: boolean }) => { - const withModifier = isActive ? ` ${styles['icon--active']}` : ''; - const iconClasses = `${styles.icon} ${withModifier}`; - - return <span className={iconClasses}></span>; -}; - -export default HamburgerIcon; diff --git a/src/components/Icons/Home/Home.module.scss b/src/components/Icons/Home/Home.module.scss deleted file mode 100644 index f2e7f9e..0000000 --- a/src/components/Icons/Home/Home.module.scss +++ /dev/null @@ -1,42 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - margin: auto; - width: var(--icon-size, #{fun.convert-px(40)}); -} - -.wall { - fill: var(--color-bg); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.indoor { - fill: var(--color-shadow-dark); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.door { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.roof { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.chimney { - fill: var(--color-bg); - stroke: var(--color-primary-darker); - stroke-width: 4; -} - -.lines { - fill: var(--color-primary-darker); - stroke-width: 4; -} diff --git a/src/components/Icons/Home/Home.tsx b/src/components/Icons/Home/Home.tsx deleted file mode 100644 index 11c0c8c..0000000 --- a/src/components/Icons/Home/Home.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import styles from './Home.module.scss'; - -const HomeIcon = () => { - return ( - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - className={styles.icon} - > - <path - className={styles.wall} - d="M 9.2669392,15.413749 H 90.709833 V 97.751815 H 9.2669392 Z" - /> - <path - className={styles.indoor} - d="m 39.190941,65.836418 h 21.594871 v 31.91539 H 39.190941 Z" - /> - <path - className={styles.door} - d="m 39.190941,65.836418 h 21.594871 v 31.91539 H 39.190941 Z" - /> - <path - className={styles.roof} - d="M 4.8219096,11.719266 H 94.720716 l 3.47304,33.365604 H 1.7830046 Z" - /> - <path - className={styles.chimney} - d="M 70.41848,2.2481852 H 82.957212 V 22.636212 H 70.41848 Z" - /> - <path - className={styles.lines} - d="M 3.9536645,19.342648 H 61.003053 v 3.293563 H 3.9536645 Z" - /> - <path - className={styles.lines} - d="m 38.973709,32.057171 h 57.049389 v 3.293563 H 38.973709 Z" - /> - </svg> - ); -}; - -export default HomeIcon; diff --git a/src/components/Icons/Moon/Moon.module.scss b/src/components/Icons/Moon/Moon.module.scss deleted file mode 100644 index 799a282..0000000 --- a/src/components/Icons/Moon/Moon.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.moon { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 4; - width: var(--icon-size, #{fun.convert-px(25)}); -} diff --git a/src/components/Icons/Moon/Moon.tsx b/src/components/Icons/Moon/Moon.tsx deleted file mode 100644 index 26f56a1..0000000 --- a/src/components/Icons/Moon/Moon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useIntl } from 'react-intl'; -import styles from './Moon.module.scss'; - -const MoonIcon = () => { - const intl = useIntl(); - - return ( - <svg - className={styles.moon} - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - > - <title> - {intl.formatMessage({ - defaultMessage: 'Dark theme', - description: 'Icons: Moon icon (dark theme)', - id: 'ode0YK', - })} - </title> - <path d="M 51.077315,1.9893942 A 43.319985,43.319985 0 0 1 72.840039,39.563145 43.319985,43.319985 0 0 1 29.520053,82.88313 43.319985,43.319985 0 0 1 5.4309911,75.569042 48.132997,48.132997 0 0 0 46.126047,98 48.132997,48.132997 0 0 0 94.260004,49.867002 48.132997,48.132997 0 0 0 51.077315,1.9893942 Z" /> - </svg> - ); -}; - -export default MoonIcon; diff --git a/src/components/Icons/Projects/Projects.module.scss b/src/components/Icons/Projects/Projects.module.scss deleted file mode 100644 index 3cf939a..0000000 --- a/src/components/Icons/Projects/Projects.module.scss +++ /dev/null @@ -1,40 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - margin: auto; - width: var(--icon-size, #{fun.convert-px(40)}); -} - -.root, -.separator, -.cursor, -.line, -.text { - fill: var(--color-fg); -} - -.stand { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-dark); - - &--top { - stroke-width: 3; - } - - &--bottom { - stroke-width: 2; - } -} - -.screen { - fill: var(--color-bg); - stroke: var(--color-primary-dark); - stroke-width: 3; -} - -.contour { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-dark); - stroke-width: 3; -} diff --git a/src/components/Icons/Projects/Projects.tsx b/src/components/Icons/Projects/Projects.tsx deleted file mode 100644 index d4af247..0000000 --- a/src/components/Icons/Projects/Projects.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import styles from './Projects.module.scss'; - -const ProjectsIcon = () => { - return ( - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - className={styles.icon} - > - <path - d="M 1.0206528,11.991149 H 98.979347 V 78.466748 H 1.0206528 Z" - className={styles.contour} - /> - <path - d="M 6.2503581,18.032451 H 93.563283 V 71.12731 H 6.2503581 Z" - className={styles.screen} - /> - <path - d="m 40.038268,78.939276 c 4.614714,2.7794 4.333151,10.099225 0,17.60572 H 50 59.961731 c -4.333151,-7.506495 -4.614715,-14.82632 0,-17.60572 H 50 Z" - className={`${styles.stand} ${styles['stand--top']}`} - /> - <path - d="m 31.084262,96.254656 h 37.831475 c 1.394769,0 2.517635,0.404907 2.517635,0.907864 v 1.179616 c 0,0.502956 -1.122866,0.907864 -2.517635,0.907864 H 31.084262 c -1.394769,0 -2.517635,-0.404908 -2.517635,-0.907864 V 97.16252 c 0,-0.502957 1.122866,-0.907864 2.517635,-0.907864 z" - className={`${styles.stand} ${styles['stand--bottom']}`} - /> - <path - d="m 13.259277,26.737199 h 29.132596 v 2.567314 H 13.259277 Z" - className={styles.line} - /> - <path - d="M 13.259277,36.439141 H 36.46805 v 2.567315 H 13.259277 Z" - className={styles.line} - /> - <path - d="m 13.259277,46.141084 h 26.586812 v 2.567314 H 13.259277 Z" - className={styles.line} - /> - <path - d="m 18.443194,65.930804 h 4.417548 v 1 h -4.417548 z" - className={styles.cursor} - /> - <path - d="m 77.586096,42.217577 v -1.680914 l 6.160884,-2.39919 -6.160884,-2.406595 v -1.68832 l 7.604842,2.89532 v 2.38438 z" - className={styles.text} - /> - <path - d="m 68.396606,43.291289 6.07943,-11.136982 h 1.688318 l -6.049809,11.136982 z" - className={styles.text} - /> - <path - d="m 59.384832,39.322258 v -2.38438 l 7.604841,-2.89532 v 1.68832 l -6.168289,2.406595 6.168289,2.399191 v 1.680915 z" - className={styles.text} - /> - <path - d="M 7.1079167,57.876372 H 92.892083 v 0.813634 H 7.1079167 Z" - className={styles.separator} - /> - <path - d="m 17.042456,64.960616 q 0,0.632276 -0.426175,0.9816 -0.422681,0.345831 -1.254074,0.37727 v 0.611318 h -0.380763 v -0.600838 q -0.751047,-0.02795 -1.170236,-0.352818 -0.419189,-0.328364 -0.551931,-1.002559 l 0.89427,-0.164183 q 0.06637,0.394736 0.261992,0.579878 0.199115,0.181648 0.565905,0.216581 v -1.365857 q -0.01048,-0.007 -0.0524,-0.01398 -0.04192,-0.01048 -0.05589,-0.01048 -0.562412,-0.129244 -0.848857,-0.303907 -0.286445,-0.178155 -0.443642,-0.447135 -0.153701,-0.272472 -0.153701,-0.663715 0,-0.579878 0.394736,-0.894269 0.394736,-0.317886 1.159755,-0.349325 v -0.468093 h 0.380763 v 0.468095 q 0.681183,0.02445 1.047973,0.303911 0.36679,0.275967 0.527479,0.918723 l -0.92222,0.136236 q -0.104797,-0.600837 -0.653236,-0.674195 v 1.22962 l 0.03843,0.007 q 0.101305,0 0.614811,0.167676 0.517,0.167676 0.772007,0.496041 0.255006,0.324871 0.255006,0.817418 z m -2.061012,-2.731715 q -0.639264,0.04891 -0.639264,0.558918 0,0.157196 0.0524,0.2585 0.0524,0.09781 0.157197,0.167676 0.104797,0.06986 0.429668,0.174662 z m 1.152769,2.745688 q 0,-0.174662 -0.06288,-0.282954 -0.06288,-0.111783 -0.185141,-0.181648 -0.118771,-0.06986 -0.523987,-0.185142 v 1.28202 q 0.772006,-0.0524 0.772006,-0.632276 z" - className={styles.root} - /> - </svg> - ); -}; - -export default ProjectsIcon; diff --git a/src/components/Icons/Search/Search.module.scss b/src/components/Icons/Search/Search.module.scss deleted file mode 100644 index 4c42028..0000000 --- a/src/components/Icons/Search/Search.module.scss +++ /dev/null @@ -1,31 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.icon { - display: block; - margin: auto; - width: var(--icon-size, #{fun.convert-px(40)}); -} - -.big-handle { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 3; -} - -.glass { - fill: var(--color-bg-opacity); - stroke: var(--color-primary-darker); - stroke-width: 2; -} - -.upright { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 3; -} - -.small-handle { - fill: var(--color-primary); - stroke: var(--color-primary-darker); - stroke-width: 2; -} diff --git a/src/components/Icons/Search/Search.tsx b/src/components/Icons/Search/Search.tsx deleted file mode 100644 index abb7b53..0000000 --- a/src/components/Icons/Search/Search.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import styles from './Search.module.scss'; - -const SearchIcon = () => { - return ( - <svg - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - className={styles.icon} - > - <path - className={styles['small-handle']} - d="m 45.39268,48.064692 5.611922,4.307881 -10.292886,13.414321 -5.611923,-4.307882 z" - /> - <path - className={styles.upright} - d="M 90.904041,28.730105 A 27.725691,27.730085 0 0 1 63.17835,56.46019 27.725691,27.730085 0 0 1 35.45266,28.730105 27.725691,27.730085 0 0 1 63.17835,1.00002 27.725691,27.730085 0 0 1 90.904041,28.730105 Z" - /> - <path - className={styles.glass} - d="M 82.438984,28.730105 A 19.260633,19.263685 0 0 1 63.17835,47.99379 19.260633,19.263685 0 0 1 43.917716,28.730105 19.260633,19.263685 0 0 1 63.17835,9.4664203 19.260633,19.263685 0 0 1 82.438984,28.730105 Z" - /> - <path - className={styles['big-handle']} - d="m 35.826055,60.434903 5.75193,4.415356 c 0.998045,0.766128 1.184879,2.186554 0.418913,3.184809 L 18.914717,98.117182 c -0.765969,0.998256 -2.186094,1.185131 -3.18414,0.418997 L 9.9786472,94.120827 C 8.9806032,93.354698 8.7937692,91.934273 9.5597392,90.936014 L 32.641919,60.853903 c 0.765967,-0.998254 2.186091,-1.185129 3.184136,-0.419 z" - /> - </svg> - ); -}; - -export default SearchIcon; diff --git a/src/components/Icons/Sun/Sun.module.scss b/src/components/Icons/Sun/Sun.module.scss deleted file mode 100644 index 5682aa3..0000000 --- a/src/components/Icons/Sun/Sun.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.sun { - fill: var(--color-primary-lighter); - stroke: var(--color-primary-darker); - stroke-width: 4; - width: var(--icon-size, #{fun.convert-px(25)}); -} diff --git a/src/components/Icons/Sun/Sun.tsx b/src/components/Icons/Sun/Sun.tsx deleted file mode 100644 index 12f47d3..0000000 --- a/src/components/Icons/Sun/Sun.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useIntl } from 'react-intl'; -import styles from './Sun.module.scss'; - -const SunIcon = () => { - const intl = useIntl(); - - return ( - <svg - className={styles.sun} - viewBox="0 0 100 100" - xmlns="http://www.w3.org/2000/svg" - > - <title> - {intl.formatMessage({ - defaultMessage: 'Light theme', - description: 'Icons: Sun icon (light theme)', - id: 'KeRtm/', - })} - </title> - <path d="M 69.398043,50.000437 A 19.399259,19.399204 0 0 1 49.998784,69.399641 19.399259,19.399204 0 0 1 30.599525,50.000437 19.399259,19.399204 0 0 1 49.998784,30.601234 19.399259,19.399204 0 0 1 69.398043,50.000437 Z m 27.699233,1.125154 c 2.657696,0.0679 1.156196,12.061455 -1.435545,11.463959 L 80.113224,59.000697 c -2.589801,-0.597494 -1.625657,-8.345536 1.032041,-8.278609 z m -18.06653,37.251321 c 1.644087,2.091234 -9.030355,8.610337 -10.126414,6.188346 L 62.331863,80.024585 c -1.096058,-2.423931 5.197062,-6.285342 6.839209,-4.194107 z M 38.611418,97.594444 C 38.02653,100.18909 26.24148,95.916413 27.436475,93.54001 l 7.168026,-14.256474 c 1.194024,-2.376403 8.102101,0.151313 7.517214,2.744986 z M 6.1661563,71.834242 C 3.7916868,73.028262 -0.25499873,61.16274 2.3386824,60.577853 L 17.905618,57.067567 c 2.593681,-0.584886 4.894434,6.403678 2.518995,7.598668 z M 6.146757,30.055146 c -2.3764094,-1.194991 4.46571,-11.714209 6.479353,-9.97798 l 12.090589,10.414462 c 2.014613,1.736229 -1.937017,7.926514 -4.314396,6.731524 z M 38.56777,4.2639045 C 37.982883,1.6682911 50.480855,0.41801247 50.415868,3.0766733 L 50.020123,19.028638 c -0.06596,2.657691 -7.357169,3.394862 -7.943027,0.800218 z m 40.403808,9.1622435 c 1.635357,-2.098023 10.437771,6.872168 8.339742,8.506552 l -12.58818,9.805327 c -2.099,1.634383 -7.192276,-3.626682 -5.557888,-5.724706 z M 97.096306,50.69105 c 2.657696,-0.06596 1.164926,12.462047 -1.425846,11.863582 L 80.122924,58.96578 c -2.590771,-0.597496 -1.636327,-7.814 1.021371,-7.879957 z" /> - </svg> - ); -}; - -export default SunIcon; diff --git a/src/components/Icons/index.tsx b/src/components/Icons/index.tsx deleted file mode 100644 index 5fe2c19..0000000 --- a/src/components/Icons/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import ArrowIcon from './Arrow/Arrow'; -import BlogIcon from './Blog/Blog'; -import CloseIcon from './Close/Close'; -import CogIcon from './Cog/Cog'; -import ContactIcon from './Contact/Contact'; -import CopyrightIcon from './Copyright/Copyright'; -import CVIcon from './CV/CV'; -import HamburgerIcon from './Hamburger/Hamburger'; -import HomeIcon from './Home/Home'; -import MoonIcon from './Moon/Moon'; -import ProjectsIcon from './Projects/Projects'; -import SearchIcon from './Search/Search'; -import SunIcon from './Sun/Sun'; - -export { - ArrowIcon, - BlogIcon, - CloseIcon, - CogIcon, - ContactIcon, - CopyrightIcon, - CVIcon, - HamburgerIcon, - HomeIcon, - MoonIcon, - ProjectsIcon, - SearchIcon, - SunIcon, -}; diff --git a/src/components/Layouts/Layout.module.scss b/src/components/Layouts/Layout.module.scss deleted file mode 100644 index 33339d4..0000000 --- a/src/components/Layouts/Layout.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.noscript { - width: 100%; - padding: var(--spacing-xs) var(--spacing-sm); - position: fixed; - top: 0; - z-index: 10; - background: var(--color-bg); - border-bottom: fun.convert-px(3) solid var(--color-border); - color: var(--color-primary-darker); - font-size: var(--font-size-sm); - font-weight: 600; - text-align: center; -} - -.noscript-spacing { - width: 100%; - height: fun.convert-px(80); -} diff --git a/src/components/Layouts/Layout.tsx b/src/components/Layouts/Layout.tsx deleted file mode 100644 index ada32b3..0000000 --- a/src/components/Layouts/Layout.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import Footer from '@components/Footer/Footer'; -import Header from '@components/Header/Header'; -import Main from '@components/Main/Main'; -import Breadcrumb from '@components/Breadcrumb/Breadcrumb'; -import { settings } from '@utils/config'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import { ReactElement, ReactNode, useEffect, useRef } from 'react'; -import { useIntl } from 'react-intl'; -import { SearchAction, WebSite, WithContext } from 'schema-dts'; -import styles from './Layout.module.scss'; -import Script from 'next/script'; - -const Layout = ({ - children, - isHome = false, -}: { - children: ReactNode; - isHome?: boolean; -}) => { - const intl = useIntl(); - const { asPath, locale } = useRouter(); - const ref = useRef<HTMLSpanElement>(null); - - useEffect(() => { - ref.current?.focus(); - }, [asPath]); - - type QueryAction = SearchAction & { - 'query-input': string; - }; - - const searchActionSchema: QueryAction = { - '@type': 'SearchAction', - target: { - '@type': 'EntryPoint', - urlTemplate: `${settings.url}/recherche?s={search_term_string}`, - }, - query: 'required', - 'query-input': 'required name=search_term_string', - }; - - const schemaJsonLd: WithContext<WebSite> = { - '@context': 'https://schema.org', - '@id': `${settings.url}`, - '@type': 'WebSite', - name: settings.name, - description: locale?.startsWith('en') - ? settings.baseline.en - : settings.baseline.fr, - url: settings.url, - author: { '@id': `${settings.url}/#branding` }, - copyrightYear: Number(settings.copyright.startYear), - creator: { '@id': `${settings.url}/#branding` }, - editor: { '@id': `${settings.url}/#branding` }, - inLanguage: settings.locales.defaultLocale, - potentialAction: searchActionSchema, - }; - - return ( - <> - <Head> - <meta property="og:site_name" content={settings.name} /> - <meta - property="og:locale" - content={`${settings.locales.defaultLocale}_${settings.locales.defaultCountry}`} - /> - <meta property="twitter:card" content="summary" /> - <meta property="twitter:site" content={settings.twitterId} /> - <meta property="twitter:creator" content={settings.twitterId} /> - <meta - name="theme-color" - content="#14578a" - media="(prefers-color-scheme: light)" - /> - <meta - name="theme-color" - content="#85bbd6" - media="(prefers-color-scheme: dark)" - /> - <link - rel="alternate" - href="/feed.xml" - type="application/rss+xml" - title={`${settings.name}'s RSS feed`} - /> - <link - rel="alternate" - href="/atom.xml" - type="application/atom+xml" - title={`${settings.name}'s Atom feed`} - /> - <link - rel="alternate" - href="/feed.json" - type="application/feed+json" - title={`${settings.name}'s Json feed`} - /> - <link rel="icon" href="/favicon.ico" sizes="any" /> - <link rel="icon" href="/icon.svg" type="image/svg+xml" /> - <link rel="apple-touch-icon" href="/apple-touch-icon.png" /> - <link rel="manifest" href="/manifest.webmanifest" /> - </Head> - <Script - id="schema-layout" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - ></Script> - <Script - strategy="afterInteractive" - async - src={`${settings.ackee.url}/${settings.ackee.filename}`} - data-ackee-server={settings.ackee.url} - data-ackee-domain-id={settings.ackee.siteId} - /> - <noscript> - <div className={styles['noscript-spacing']}></div> - </noscript> - <span ref={ref} tabIndex={-1} /> - <a href="#main" className="screen-reader-text"> - {intl.formatMessage({ - defaultMessage: 'Skip to content', - description: 'Layout: Skip to content button', - id: 'iqAbyn', - })} - </a> - <Header isHome={isHome} /> - <Main>{children}</Main> - <Footer /> - <noscript> - <div className={styles.noscript}> - {intl.formatMessage({ - defaultMessage: - 'Without Javascript, some features may not work like loading more posts or use search. If you want to benefit from these features, please activate Javascript.', - description: 'Layout: noscript banner', - id: 'LR70nt', - })} - </div> - </noscript> - </> - ); -}; - -export const getLayout = (page: ReactElement) => { - const pageTitle: string = page.props.breadcrumbTitle; - - return ( - <Layout> - <Breadcrumb pageTitle={pageTitle} /> - {page} - </Layout> - ); -}; - -export default Layout; diff --git a/src/components/MDX/CodeBlock/CodeBlock.tsx b/src/components/MDX/CodeBlock/CodeBlock.tsx deleted file mode 100644 index c330063..0000000 --- a/src/components/MDX/CodeBlock/CodeBlock.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { - PrismDefaultPlugins, - PrismLanguages, - PrismPlugins, -} from '@ts/types/prism'; -import { usePrismTheme } from '@utils/providers/prism-theme'; -import { useRouter } from 'next/router'; -import Prism from 'prismjs'; -import { useCallback, useEffect, useMemo } from 'react'; -import { useIntl } from 'react-intl'; - -const CodeBlock = ({ - code, - language, - plugins, -}: { - code: string; - language: PrismLanguages; - plugins: PrismPlugins[]; -}) => { - const intl = useIntl(); - const router = useRouter(); - const { setCodeBlocks } = usePrismTheme(); - - useEffect(() => { - const allPre: NodeListOf<HTMLPreElement> = document.querySelectorAll( - 'pre[data-prismjs-color-scheme-current]' - ); - setCodeBlocks(allPre); - }, [setCodeBlocks, router.asPath]); - - const defaultPlugins: PrismDefaultPlugins[] = useMemo( - () => [ - 'autoloader', - 'toolbar', - 'show-language', - 'copy-to-clipboard', - 'color-scheme', - 'match-braces', - 'normalize-whitespace', - ], - [] - ); - - const loadPrismPlugins = useCallback( - async (prismPlugins: (PrismDefaultPlugins | PrismPlugins)[]) => { - for (const plugin of prismPlugins) { - try { - if (plugin === 'color-scheme') { - await import(`@utils/plugins/prism-${plugin}`); - } else { - await import(`prismjs/plugins/${plugin}/prism-${plugin}.min.js`); - - if (plugin === 'autoloader') - Prism.plugins.autoloader.languages_path = '/prism/'; - } - } catch (error) { - console.error('CodeBlock: an error occurred with Prism.'); - console.error(error); - } - } - }, - [] - ); - - useEffect(() => { - loadPrismPlugins([...defaultPlugins, ...plugins]).then(() => { - Prism.highlightAll(); - }); - }, [loadPrismPlugins, defaultPlugins, plugins]); - - const copyText = intl.formatMessage({ - defaultMessage: 'Copy', - description: 'Prism: copy button text (no clicked)', - id: '/ly3AC', - }); - const copiedText = intl.formatMessage({ - defaultMessage: 'Copied!', - description: 'Prism: copy button text (clicked)', - id: 'OV9r1K', - }); - const errorText = intl.formatMessage({ - defaultMessage: 'Use Ctrl+c to copy', - description: 'Prism: error text', - id: 'z9qkcQ', - }); - const darkTheme = intl.formatMessage({ - defaultMessage: 'Dark Theme 🌙', - description: 'Prism: toggle dark theme button text', - id: 'nFMdWI', - }); - const lightTheme = intl.formatMessage({ - defaultMessage: 'Light Theme 🌞', - description: 'Prism: toggle light theme button text', - id: 'Ua2g2p', - }); - - const defaultPluginsClasses = 'match-braces'; - const pluginsClasses = plugins.join(' '); - - return ( - <pre - className={`language-${language} ${defaultPluginsClasses} ${pluginsClasses}`} - data-prismjs-copy={copyText} - data-prismjs-copy-success={copiedText} - data-prismjs-copy-error={errorText} - data-prismjs-color-scheme-dark={darkTheme} - data-prismjs-color-scheme-light={lightTheme} - > - <code className={`language-${language}`}>{code}</code> - </pre> - ); -}; - -export default CodeBlock; diff --git a/src/components/MDX/Gallery/Gallery.module.scss b/src/components/MDX/Gallery/Gallery.module.scss deleted file mode 100644 index 2654b59..0000000 --- a/src/components/MDX/Gallery/Gallery.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.wrapper { - @extend %reset-list; - - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: var(--spacing-sm); - max-width: 100%; - margin: var(--spacing-sm) 0; - - @for $i from 0 to 6 { - &--#{$i}-columns { - @include mix.media("screen") { - @include mix.dimensions("xs") { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - @include mix.dimensions("sm") { - grid-template-columns: repeat(#{$i}, minmax(0, 1fr)); - } - } - } - } -} - -.item { - > figure { - margin: 0; - } -} diff --git a/src/components/MDX/Gallery/Gallery.tsx b/src/components/MDX/Gallery/Gallery.tsx deleted file mode 100644 index 561ec53..0000000 --- a/src/components/MDX/Gallery/Gallery.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Children, ReactElement } from 'react'; -import styles from './Gallery.module.scss'; - -const Gallery = ({ - children, - columns = 2, -}: { - children: ReactElement; - columns: number; -}) => { - const columnClass = styles[`wrapper--${columns}-columns`]; - - return ( - <ul className={`${styles.wrapper} ${columnClass}`}> - {Children.map(children, (child) => { - return <li className={styles.item}>{child}</li>; - })} - </ul> - ); -}; - -export default Gallery; diff --git a/src/components/MDX/Link/Link.tsx b/src/components/MDX/Link/Link.tsx deleted file mode 100644 index 40e773b..0000000 --- a/src/components/MDX/Link/Link.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { ReactChildren } from 'react'; - -const Link = ({ - children, - target, - isExternal = false, - lang, -}: { - children: ReactChildren; - target: string; - isExternal: boolean; - lang?: string; -}) => { - const className = isExternal ? 'external' : ''; - - return ( - <a href={target} className={className} hrefLang={lang}> - {children} - </a> - ); -}; - -export default Link; diff --git a/src/components/MDX/ResponsiveImage/ResponsiveImage.module.scss b/src/components/MDX/ResponsiveImage/ResponsiveImage.module.scss deleted file mode 100644 index cf2b77f..0000000 --- a/src/components/MDX/ResponsiveImage/ResponsiveImage.module.scss +++ /dev/null @@ -1,50 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.wrapper { - width: 100%; - max-width: 100%; - margin: var(--spacing-sm) auto; - position: relative; - text-align: center; -} - -.caption { - 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); - font-weight: 500; -} - -.link { - display: flex; - flex-flow: column; - background: none; - text-decoration: none; - - .caption { - color: var(--color-primary-darker); - } - - &:hover, - &:focus { - transform: scale(1.1); - } - - &:focus { - .caption { - text-decoration: underline solid var(--color-primary-darker) - fun.convert-px(3); - } - } - - &:active { - transform: scale(0.9); - - .caption { - text-decoration: none; - } - } -} diff --git a/src/components/MDX/ResponsiveImage/ResponsiveImage.tsx b/src/components/MDX/ResponsiveImage/ResponsiveImage.tsx deleted file mode 100644 index 6c39e7f..0000000 --- a/src/components/MDX/ResponsiveImage/ResponsiveImage.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { ResponsiveImageProps } from '@ts/types/app'; -import Image from 'next/image'; -import Link from 'next/link'; -import styles from './ResponsiveImage.module.scss'; - -const ResponsiveImage = (props: ResponsiveImageProps) => { - const { caption, linkTarget, ...attributes } = props; - - return ( - <figure className={styles.wrapper}> - {linkTarget ? ( - <Link href={linkTarget}> - <a className={styles.link}> - <Image - alt={attributes.alt} - layout={attributes.layout || 'intrinsic'} - {...attributes} - /> - {caption && ( - <figcaption className={styles.caption}>{caption}</figcaption> - )} - </a> - </Link> - ) : ( - <> - <Image - alt={attributes.alt} - layout={attributes.layout || 'intrinsic'} - {...attributes} - /> - {caption && ( - <figcaption className={styles.caption}>{caption}</figcaption> - )} - </> - )} - </figure> - ); -}; - -export default ResponsiveImage; diff --git a/src/components/MDX/index.tsx b/src/components/MDX/index.tsx deleted file mode 100644 index bc7aa35..0000000 --- a/src/components/MDX/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import CodeBlock from './CodeBlock/CodeBlock'; -import Gallery from './Gallery/Gallery'; -import Link from './Link/Link'; -import ResponsiveImage from './ResponsiveImage/ResponsiveImage'; - -export { CodeBlock, Gallery, Link, ResponsiveImage }; diff --git a/src/components/Main/Main.module.scss b/src/components/Main/Main.module.scss deleted file mode 100644 index 819474c..0000000 --- a/src/components/Main/Main.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.wrapper { - flex: 1; - - :global { - animation: fade-in 1.5s ease-in-out 0s 1; - } -} diff --git a/src/components/Main/Main.tsx b/src/components/Main/Main.tsx deleted file mode 100644 index b21ab1c..0000000 --- a/src/components/Main/Main.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FunctionComponent } from 'react'; -import styles from './Main.module.scss'; - -const Main: FunctionComponent = ({ children }) => { - return ( - <main id="main" className={styles.wrapper}> - {children} - </main> - ); -}; - -export default Main; diff --git a/src/components/MainNav/MainNav.module.scss b/src/components/MainNav/MainNav.module.scss deleted file mode 100644 index f3e6c10..0000000 --- a/src/components/MainNav/MainNav.module.scss +++ /dev/null @@ -1,242 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.wrapper { - --icon-size: #{fun.convert-px(30)}; - - display: flex; - flex-flow: column nowrap; - align-items: center; - height: var(--btn-size); - width: calc(var(--btn-size) * 1.2); - background: var(--color-bg); - position: relative; - - @include mix.media("screen") { - @include mix.dimensions("sm") { - background: inherit; - } - - @include mix.dimensions("md") { - width: unset; - height: unset; - } - } -} - -.label { - --draw-border-thickness: #{fun.convert-px(5)}; - --draw-border-color1: var(--color-primary-light); - --draw-border-color2: var(--color-primary-lighter); - - flex: 1; - display: flex; - flex-flow: column nowrap; - width: 100%; - padding: var(--spacing-2xs); - - &:hover { - @extend %draw-borders; - } - - @include mix.media("screen") { - @include mix.dimensions("md") { - display: none; - } - } -} - -.checkbox { - position: absolute; - - // centered checkbox = btn-size - approximated checkbox size / 2 - top: calc((var(--btn-size) - #{fun.convert-px(14)}) / 2); - left: calc(((var(--btn-size) * 1.2) - #{fun.convert-px(14)}) / 2); - opacity: 0; - cursor: pointer; - - &:hover { - ~ .label { - @extend %draw-borders; - } - } - - &:focus { - ~ .label { - @extend %draw-borders; - } - } - - @include mix.media("screen") { - @include mix.dimensions("md") { - display: none; - } - } -} - -.nav { - display: flex; - flex-flow: column wrap; - place-content: center; - padding-bottom: var(--toolbar-size); - position: fixed; - bottom: 0; - z-index: -1; - background: var(--color-bg-opacity); - box-shadow: 0 0 fun.convert-px(3) 0 var(--color-shadow-dark); - text-align: center; - opacity: 1; - visibility: visible; - transform: translateY(0); - transition: all 0.8s ease-in-out 0s; - - @include mix.media("screen") { - @include mix.dimensions("sm") { - padding-bottom: 0; - position: absolute; - bottom: auto; - left: auto; - right: auto; - top: calc(var(--btn-size) + var(--spacing-sm)); - z-index: unset; - border-bottom-width: fun.convert-px(5); - transform-origin: 50% -100%; - } - - @include mix.dimensions("md") { - background: transparent; - border: none; - box-shadow: none; - position: relative; - top: 0; - } - } -} - -.list { - @extend %reset-list; - - @include mix.media("screen") { - @include mix.dimensions(null, "2xs", "height") { - display: grid; - grid-template-columns: min-content min-content; - max-height: calc(100vh - var(--toolbar-size)); - } - - @include mix.dimensions("md") { - display: flex; - flex-flow: row wrap; - align-items: center; - gap: var(--spacing-2xs); - } - } -} - -.link { - --draw-border-thickness: #{fun.convert-px(4)}; - --draw-border-color1: var(--color-primary-light); - --draw-border-color2: var(--color-primary-lighter); - - display: block; - min-width: fun.convert-px(85); - padding: var(--spacing-xs) var(--spacing-xs) var(--spacing-2xs); - background: var(--color-bg); - background-repeat: no-repeat; - font-size: var(--font-size-sm); - font-variant: small-caps; - font-weight: 600; - text-decoration: none; - - @include mix.media("screen") { - @include mix.dimensions("md") { - margin: 0; - background-color: inherit; - border-radius: 8%; - } - } - - &:hover, - &:focus { - @extend %draw-borders; - } - - &:focus { - color: var(--color-primary-light); - } - - &:active { - --draw-border-color1: var(--color-primary-dark); - --draw-border-color2: var(--color-primary-light); - - @extend %draw-borders; - } - - &.current { - background-image: linear-gradient(to right, transparent, transparent), - linear-gradient(to bottom, transparent, transparent), - linear-gradient( - to left, - var(--color-primary-lighter), - var(--color-primary-light) - ), - linear-gradient(to top, transparent, transparent); - background-position: top left, top right, bottom center, bottom left; - background-size: 0% var(--draw-border-thickness), - var(--draw-border-thickness) 0%, 60% var(--draw-border-thickness), - var(--draw-border-thickness) 0%; - - &:hover, - &:focus { - --draw-border-color1: var(--color-primary-light); - --draw-border-color2: var(--color-primary-lighter); - - @extend %draw-borders; - } - - &:active { - --draw-border-color1: var(--color-primary-dark); - --draw-border-color2: var(--color-primary-light); - - @extend %draw-borders; - } - } -} - -.checkbox:not(:checked) { - ~ .nav { - opacity: 0; - visibility: hidden; - transform: translateY(100vw); - - @include mix.media("screen") { - @include mix.dimensions("sm") { - transform: perspective(20rem) translate3d(0, 100%, -20rem); - } - - @include mix.dimensions("md") { - opacity: 1; - visibility: visible; - transform: none; - } - } - } -} - -.checkbox:checked { - ~ .label:hover { - span { - background: none; - box-shadow: none; - } - } - - &:hover { - ~ .label { - span { - background: none; - box-shadow: none; - } - } - } -} diff --git a/src/components/MainNav/MainNav.tsx b/src/components/MainNav/MainNav.tsx deleted file mode 100644 index 9cb6b4c..0000000 --- a/src/components/MainNav/MainNav.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { - BlogIcon, - ContactIcon, - CVIcon, - HamburgerIcon, - HomeIcon, - ProjectsIcon, -} from '@components/Icons'; -import { NavItem } from '@ts/types/nav'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { ForwardedRef, forwardRef, SetStateAction } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './MainNav.module.scss'; - -const MainNav = ( - { - isOpened, - setIsOpened, - }: { - isOpened: boolean; - setIsOpened: (value: SetStateAction<boolean>) => void; - }, - ref: ForwardedRef<HTMLDivElement> -) => { - const intl = useIntl(); - const router = useRouter(); - - const mainNavConfig: NavItem[] = [ - { - id: 'home', - name: intl.formatMessage({ - defaultMessage: 'Home', - description: 'MainNav: home link', - id: 'ZJMNRW', - }), - slug: '/', - }, - { - id: 'blog', - name: intl.formatMessage({ - defaultMessage: 'Blog', - description: 'MainNav: blog link', - id: 'zPJifH', - }), - slug: '/blog', - }, - { - id: 'projects', - name: intl.formatMessage({ - defaultMessage: 'Projects', - description: 'MainNav: projects link', - id: 'akSutM', - }), - slug: '/projets', - }, - { - id: 'cv', - name: intl.formatMessage({ - defaultMessage: 'Resume', - description: 'MainNav: resume link', - id: 'jpv+Nz', - }), - slug: '/cv', - }, - { - id: 'contact', - name: intl.formatMessage({ - defaultMessage: 'Contact', - description: 'MainNav: contact link', - id: 'c2NtPj', - }), - slug: '/contact', - }, - ]; - - const getIcon = (id: string) => { - switch (id) { - case 'home': - return <HomeIcon />; - case 'blog': - return <BlogIcon />; - case 'contact': - return <ContactIcon />; - case 'cv': - return <CVIcon />; - case 'projects': - return <ProjectsIcon />; - default: - break; - } - }; - - const navItems = mainNavConfig.map((item) => { - const currentClass = router.asPath === item.slug ? styles.current : ''; - - return ( - <li key={item.id}> - <Link href={item.slug}> - <a className={`${styles.link} ${currentClass}`}> - {getIcon(item.id)} - <span>{item.name}</span> - </a> - </Link> - </li> - ); - }); - - return ( - <div id="main-nav" ref={ref} className={styles.wrapper}> - <input - type="checkbox" - name="main-nav__checkbox" - id="main-nav__checkbox" - aria-labelledby="main-nav-toggle" - className={styles.checkbox} - checked={isOpened} - onChange={() => setIsOpened(!isOpened)} - autoComplete="off" - /> - <label - htmlFor="main-nav__checkbox" - id="main-nav-toggle" - className={styles.label} - > - <HamburgerIcon isActive={isOpened} /> - <span className="screen-reader-text"> - {isOpened - ? intl.formatMessage({ - defaultMessage: 'Close menu', - description: 'MainNav: close button', - id: 'dE8xxV', - }) - : intl.formatMessage({ - defaultMessage: 'Open menu', - description: 'MainNav: open button', - id: 'azc1GT', - })} - </span> - </label> - <nav - className={styles.nav} - aria-label={intl.formatMessage({ - defaultMessage: 'Primary', - description: 'MainNav: aria-label', - id: 'H7C5Bk', - })} - > - <ul className={styles.list}>{navItems}</ul> - </nav> - </div> - ); -}; - -export default forwardRef(MainNav); diff --git a/src/components/MetaItems/Author/Author.tsx b/src/components/MetaItems/Author/Author.tsx deleted file mode 100644 index 4ff0086..0000000 --- a/src/components/MetaItems/Author/Author.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { useIntl } from 'react-intl'; -import { MetaItem } from '..'; - -const Author = ({ name, kind }: { name: string; kind: MetaKind }) => { - const intl = useIntl(); - - return ( - <MetaItem - title={intl.formatMessage({ - defaultMessage: 'Written by:', - description: 'Author: article author meta label', - id: 'jCyqZS', - })} - value={name} - kind={kind} - /> - ); -}; - -export default Author; diff --git a/src/components/MetaItems/CommentsCount/CommentsCount.tsx b/src/components/MetaItems/CommentsCount/CommentsCount.tsx deleted file mode 100644 index 04cffa6..0000000 --- a/src/components/MetaItems/CommentsCount/CommentsCount.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; -import { MetaItem } from '..'; - -const CommentsCount = ({ total, kind }: { total: number; kind: MetaKind }) => { - const intl = useIntl(); - const { asPath } = useRouter(); - - const isArticle = () => asPath.includes('/article/'); - - const getCommentsCount = () => { - return intl.formatMessage( - { - defaultMessage: - '{total, plural, =0 {No comments} one {# comment} other {# comments}}', - description: 'CommentsCount: comment count value', - id: 'lKGNKx', - }, - { total } - ); - }; - - return ( - <MetaItem - title={intl.formatMessage({ - defaultMessage: 'Comments:', - description: 'CommentsCount: comment count meta label', - id: '6BRtAu', - })} - value={ - isArticle() ? ( - <a href="#comments">{getCommentsCount()}</a> - ) : ( - getCommentsCount() - ) - } - kind={kind} - /> - ); -}; - -export default CommentsCount; diff --git a/src/components/MetaItems/Dates/Dates.tsx b/src/components/MetaItems/Dates/Dates.tsx deleted file mode 100644 index 4314ed9..0000000 --- a/src/components/MetaItems/Dates/Dates.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { settings } from '@utils/config'; -import { getFormattedDate } from '@utils/helpers/format'; -import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; -import { MetaItem } from '..'; - -const Dates = ({ - publication, - update, - kind, -}: { - publication: string; - update: string; - kind: MetaKind; -}) => { - const intl = useIntl(); - const { locale } = useRouter(); - const validLocale = locale ? locale : settings.locales.defaultLocale; - - const publicationDate = getFormattedDate(publication, validLocale); - const updateDate = getFormattedDate(update, validLocale); - - return ( - <> - <MetaItem - title={intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Dates: publication date meta label', - id: '52Fev1', - })} - values={[ - <time key={publication} dateTime={publication}> - {publicationDate} - </time>, - ]} - kind={kind} - /> - {publicationDate !== updateDate && ( - <MetaItem - title={intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Dates: update date meta label', - id: 'C+r/LF', - })} - values={[ - <time key={update} dateTime={update}> - {updateDate} - </time>, - ]} - kind={kind} - /> - )} - </> - ); -}; - -export default Dates; diff --git a/src/components/MetaItems/MetaItem/MetaItem.module.scss b/src/components/MetaItems/MetaItem/MetaItem.module.scss deleted file mode 100644 index 0b159ca..0000000 --- a/src/components/MetaItems/MetaItem/MetaItem.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.wrapper--article { - display: flex; - flex-flow: row wrap; -} - -.title--article { - margin-right: var(--spacing-2xs); - color: var(--color-fg-light); -} - -.body--article { - &:not(:first-of-type) { - &::before { - content: "/"; - margin: 0 var(--spacing-2xs); - } - } -} diff --git a/src/components/MetaItems/MetaItem/MetaItem.tsx b/src/components/MetaItems/MetaItem/MetaItem.tsx deleted file mode 100644 index 5c51283..0000000 --- a/src/components/MetaItems/MetaItem/MetaItem.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { ReactElement } from 'react'; -import styles from './MetaItem.module.scss'; - -const MetaItem = ({ - title, - value, - values, - info, - kind = 'list', -}: { - title: string; - value?: ReactElement | string; - values?: ReactElement[] | string[]; - info?: string; - kind: MetaKind; -}) => { - return ( - <div className={styles[`wrapper--${kind}`]}> - <dt className={styles[`title--${kind}`]}>{title}</dt> - {value && ( - <dd className={styles[`body--${kind}`]} title={info}> - {value} - </dd> - )} - {values && - values.map((currentValue, index) => ( - <dd key={index} className={styles[`body--${kind}`]} title={info}> - {currentValue} - </dd> - ))} - </div> - ); -}; - -export default MetaItem; diff --git a/src/components/MetaItems/PostsCount/PostsCount.tsx b/src/components/MetaItems/PostsCount/PostsCount.tsx deleted file mode 100644 index 679abcd..0000000 --- a/src/components/MetaItems/PostsCount/PostsCount.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { useIntl } from 'react-intl'; -import { MetaItem } from '..'; - -const PostsCount = ({ total, kind }: { total: number; kind: MetaKind }) => { - const intl = useIntl(); - - return ( - <MetaItem - title={intl.formatMessage({ - defaultMessage: 'Total:', - description: 'PostCount: total found articles meta label', - id: 'p1zZ/Z', - })} - value={intl.formatMessage( - { - defaultMessage: - '{total, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'PostCount: total found articles', - id: '4EMSLO', - }, - { total } - )} - kind={kind} - /> - ); -}; - -export default PostsCount; diff --git a/src/components/MetaItems/ReadingTime/ReadingTime.tsx b/src/components/MetaItems/ReadingTime/ReadingTime.tsx deleted file mode 100644 index 79d6f3c..0000000 --- a/src/components/MetaItems/ReadingTime/ReadingTime.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; -import { MetaItem } from '..'; - -const ReadingTime = ({ - time, - words, - kind, -}: { - time: number; - words: number; - kind: MetaKind; -}) => { - const intl = useIntl(); - const { locale } = useRouter(); - - const getEstimation = () => { - if (time < 0) { - return intl.formatMessage({ - defaultMessage: 'less than 1 minute', - description: 'ReadingTime: Reading time value', - id: 'ySsWZl', - }); - } - - return intl.formatMessage( - { - defaultMessage: - '{time, plural, =0 {# minutes} one {# minute} other {# minutes}}', - description: 'ReadingTime: reading time value', - id: 'wdqOpf', - }, - { time } - ); - }; - - return ( - <MetaItem - title={intl.formatMessage({ - defaultMessage: 'Reading time:', - description: 'ReadingTime: reading time meta label', - id: 'n0Gbod', - })} - value={getEstimation()} - info={intl.formatMessage( - { - defaultMessage: 'Approximately {number} words', - description: 'ReadingTime: number of words', - id: 'k7/SkN', - }, - { number: words.toLocaleString(locale) } - )} - kind={kind} - /> - ); -}; - -export default ReadingTime; diff --git a/src/components/MetaItems/Thematics/Thematics.tsx b/src/components/MetaItems/Thematics/Thematics.tsx deleted file mode 100644 index e655c5d..0000000 --- a/src/components/MetaItems/Thematics/Thematics.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { ThematicPreview } from '@ts/types/taxonomies'; -import Link from 'next/link'; -import { useIntl } from 'react-intl'; -import { MetaItem } from '..'; - -const Thematics = ({ - list, - kind, -}: { - list: ThematicPreview[]; - kind: MetaKind; -}) => { - const intl = useIntl(); - - const getThematics = () => { - return list.map((thematic) => { - return ( - <Link key={thematic.databaseId} href={`/thematique/${thematic.slug}`}> - <a>{thematic.title}</a> - </Link> - ); - }); - }; - - return ( - <MetaItem - title={intl.formatMessage( - { - defaultMessage: - '{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}', - description: 'Thematics: thematics list meta label', - id: '1r4ujR', - }, - { thematicsCount: list.length } - )} - values={getThematics()} - kind={kind} - /> - ); -}; - -export default Thematics; diff --git a/src/components/MetaItems/Topics/Topics.tsx b/src/components/MetaItems/Topics/Topics.tsx deleted file mode 100644 index d5d90f0..0000000 --- a/src/components/MetaItems/Topics/Topics.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { TopicPreview } from '@ts/types/taxonomies'; -import Link from 'next/link'; -import { useIntl } from 'react-intl'; -import { MetaItem } from '..'; - -const Topics = ({ list, kind }: { list: TopicPreview[]; kind: MetaKind }) => { - const intl = useIntl(); - - const getTopics = () => { - return list.map((topic) => { - return ( - <Link key={topic.databaseId} href={`/sujet/${topic.slug}`}> - <a>{topic.title}</a> - </Link> - ); - }); - }; - - return ( - <MetaItem - title={intl.formatMessage( - { - defaultMessage: - '{topicsCount, plural, =0 {Topics:} one {Topic:} other {Topics:}}', - description: 'Topics: topics list meta label', - id: '0pp/IQ', - }, - { topicsCount: list.length } - )} - values={getTopics()} - kind={kind} - /> - ); -}; - -export default Topics; diff --git a/src/components/MetaItems/Website/Website.tsx b/src/components/MetaItems/Website/Website.tsx deleted file mode 100644 index 7d2dc06..0000000 --- a/src/components/MetaItems/Website/Website.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { MetaKind } from '@ts/types/app'; -import { useIntl } from 'react-intl'; -import { MetaItem } from '..'; - -const Website = ({ url, kind }: { url: string; kind: MetaKind }) => { - const intl = useIntl(); - - return ( - <MetaItem - title={intl.formatMessage({ - defaultMessage: 'Website:', - description: 'Website: website meta label', - id: 'JsOoAW', - })} - value={<a href={url}>{url}</a>} - kind={kind} - /> - ); -}; - -export default Website; diff --git a/src/components/MetaItems/index.tsx b/src/components/MetaItems/index.tsx deleted file mode 100644 index e90d5a6..0000000 --- a/src/components/MetaItems/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Author from './Author/Author'; -import CommentsCount from './CommentsCount/CommentsCount'; -import Dates from './Dates/Dates'; -import MetaItem from './MetaItem/MetaItem'; -import PostsCount from './PostsCount/PostsCount'; -import ReadingTime from './ReadingTime/ReadingTime'; -import Thematics from './Thematics/Thematics'; -import Topics from './Topics/Topics'; -import Website from './Website/Website'; - -export { - Author, - CommentsCount, - Dates, - MetaItem, - PostsCount, - ReadingTime, - Thematics, - Topics, - Website, -}; diff --git a/src/components/Notice/Notice.module.scss b/src/components/Notice/Notice.module.scss deleted file mode 100644 index aa7175c..0000000 --- a/src/components/Notice/Notice.module.scss +++ /dev/null @@ -1,28 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.message { - border: fun.convert-px(2) solid; - font-weight: bold; - margin: var(--spacing-sm) auto; - padding: var(--spacing-2xs) var(--spacing-xs); - - &--error { - border-color: var(--color-token-red); - color: var(--color-token-red); - } - - &--info { - border-color: var(--color-token-blue); - color: var(--color-token-blue); - } - - &--success { - border-color: var(--color-token-green); - color: var(--color-token-green); - } - - &--warning { - border-color: var(--color-token-orange); - color: var(--color-token-orange); - } -} diff --git a/src/components/Notice/Notice.tsx b/src/components/Notice/Notice.tsx deleted file mode 100644 index 02b1f12..0000000 --- a/src/components/Notice/Notice.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { NoticeType } from '@ts/types/app'; -import { ReactNode } from 'react'; -import styles from './Notice.module.scss'; - -const Notice = ({ - children, - type, -}: { - children: ReactNode; - type: NoticeType; -}) => { - const withModifier = `message--${type}`; - - return ( - <div className={`${styles.message} ${styles[withModifier]}`}> - {children} - </div> - ); -}; - -export default Notice; diff --git a/src/components/Pagination/Pagination.module.scss b/src/components/Pagination/Pagination.module.scss deleted file mode 100644 index 4d74d1b..0000000 --- a/src/components/Pagination/Pagination.module.scss +++ /dev/null @@ -1,92 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.list { - @extend %flex-list; - justify-content: center; - - row-gap: var(--spacing-sm); -} - -.link { - display: block; - padding: var(--spacing-xs) var(--spacing-sm); - background: var(--color-bg); - border: fun.convert-px(2) solid var(--color-primary); - box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 - var(--color-primary-darker); - font-weight: 600; - text-decoration: none; - - @include mix.pointer("fine") { - padding: var(--spacing-2xs) var(--spacing-xs); - } - - &--current { - padding: calc(var(--spacing-xs) / 1.5) var(--spacing-sm); - border-color: var(--color-primary-darker); - box-shadow: none; - color: var(--color-primary-darker); - transform: translateY(#{fun.convert-px(10)}); - - @include mix.pointer("fine") { - padding: calc(var(--spacing-2xs) / 1.5) var(--spacing-xs); - transform: translateY(#{fun.convert-px(7)}); - } - } - - &:not(.link--current) { - &:hover, - &:focus { - border-color: var(--color-primary-light); - box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 - var(--color-primary-darker), - 0 fun.convert-px(2) fun.convert-px(2) fun.convert-px(1) - var(--color-shadow-dark), - 0 fun.convert-px(7) fun.convert-px(7) fun.convert-px(2) - var(--color-shadow-light); - color: var(--color-primary-light); - transform: translateY(#{fun.convert-px(-5)}); - } - - &:active { - padding: calc(var(--spacing-xs) / 1.5) var(--spacing-sm); - border-color: var(--color-primary-dark); - box-shadow: none; - color: var(--color-primary-dark); - transform: translateY(#{fun.convert-px(10)}); - - @include mix.pointer("fine") { - padding: calc(var(--spacing-2xs) / 1.5) var(--spacing-xs); - transform: translateY(#{fun.convert-px(7)}); - } - } - } -} - -.item { - position: relative; - - &:first-child { - .link { - border-top-left-radius: fun.convert-px(4); - border-bottom-left-radius: fun.convert-px(4); - } - } - - &:last-child { - .link { - border-top-right-radius: fun.convert-px(4); - border-bottom-right-radius: fun.convert-px(4); - } - } - - &:not(:first-child) { - margin-left: fun.convert-px(-1); - } - - &:not(:last-child) { - margin-right: fun.convert-px(-1); - } -} diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx deleted file mode 100644 index 55c366a..0000000 --- a/src/components/Pagination/Pagination.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { settings } from '@utils/config'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; -import styles from './Pagination.module.scss'; - -const Pagination = ({ baseUrl, total }: { baseUrl: string; total: number }) => { - const intl = useIntl(); - const { asPath } = useRouter(); - const totalPages = Math.floor(total / settings.postsPerPage); - const currentPage = asPath.includes('/page/') - ? Number(asPath.split(`${baseUrl}/page/`)[1]) - : 1; - const hasPreviousPage = currentPage !== 1; - const hasNextPage = currentPage !== totalPages; - - const getPreviousPageItem = () => { - return ( - <li className={styles.item}> - <Link href={`${baseUrl}/page/${currentPage - 1}`}> - <a className={styles.link}> - {intl.formatMessage( - { - defaultMessage: '{icon} Previous page', - description: 'Pagination: previous page link', - id: 'aMFqPH', - }, - { icon: '←' } - )} - </a> - </Link> - </li> - ); - }; - - const getNextPageItem = () => { - return ( - <li className={styles.item}> - <Link href={`${baseUrl}/page/${currentPage + 1}`}> - <a className={styles.link}> - {intl.formatMessage( - { - defaultMessage: 'Next page {icon}', - description: 'Pagination: Next page link', - id: 'R4yaW6', - }, - { icon: '→' } - )} - </a> - </Link> - </li> - ); - }; - - const getPages = () => { - const pages = []; - for (let i = 1; i <= totalPages; i++) { - if (i === currentPage) { - pages.push({ - id: `page-${i}`, - link: ( - <span className={`${styles.link} ${styles['link--current']}`}> - {intl.formatMessage( - { - defaultMessage: '<a11y>Page </a11y>{number}', - description: 'Pagination: page number', - id: 'TSXPzr', - }, - { - number: i, - a11y: (chunks: string) => ( - <span className="screen-reader-text">{chunks}</span> - ), - } - )} - </span> - ), - }); - } else { - pages.push({ - id: `page-${i}`, - link: ( - <Link href={`${baseUrl}/page/${i}`}> - <a className={styles.link}> - {intl.formatMessage( - { - defaultMessage: '<a11y>Page </a11y>{number}', - description: 'Pagination: page number', - id: 'TSXPzr', - }, - { - number: i, - a11y: (chunks: string) => ( - <span className="screen-reader-text">{chunks}</span> - ), - } - )} - </a> - </Link> - ), - }); - } - } - - return pages; - }; - - const getItems = () => { - const pages = getPages(); - - return pages.map((page) => ( - <li key={page.id} className={styles.item}> - {page.link} - </li> - )); - }; - - return ( - <nav className={styles.wrapper} aria-labelledby="pagination-title"> - <h2 id="pagination-title" className="screen-reader-text"> - {intl.formatMessage({ - defaultMessage: 'Pagination', - description: 'Pagination: pagination title', - id: 'BAkq7J', - })} - </h2> - <ul className={styles.list}> - {hasPreviousPage && getPreviousPageItem()} - {getItems()} - {hasNextPage && getNextPageItem()} - </ul> - </nav> - ); -}; - -export default Pagination; diff --git a/src/components/PaginationCursor/PaginationCursor.module.scss b/src/components/PaginationCursor/PaginationCursor.module.scss deleted file mode 100644 index 542584c..0000000 --- a/src/components/PaginationCursor/PaginationCursor.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.wrapper { - width: max-content; - margin: var(--spacing-sm) auto var(--spacing-md); - text-align: center; - - .bar[value] { - display: block; - width: clamp(25ch, 20vw, 30ch); - max-width: 100%; - height: fun.convert-px(13); - appearance: none; - background: var(--color-bg-tertiary); - border: fun.convert-px(1) solid var(--color-primary-darker); - border-radius: 1em; - box-shadow: inset 0 0 fun.convert-px(4) fun.convert-px(1) - var(--color-shadow-light); - - &::-webkit-progress-value { - background-color: var(--color-primary-dark); - border-radius: 1em; - } - - &::-moz-progress-bar { - background-color: var(--color-primary-dark); - border-radius: 1em; - } - - &::-webkit-progress-bar { - background: var(--color-bg-tertiary); - border: fun.convert-px(1) solid var(--color-primary-darker); - border-radius: 1em; - box-shadow: inset 0 0 fun.convert-px(4) fun.convert-px(1) - var(--color-shadow-light); - } - } -} - -.info { - margin-bottom: var(--spacing-2xs); - font-size: var(--font-size-sm); -} diff --git a/src/components/PaginationCursor/PaginationCursor.tsx b/src/components/PaginationCursor/PaginationCursor.tsx deleted file mode 100644 index d64f961..0000000 --- a/src/components/PaginationCursor/PaginationCursor.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useIntl } from 'react-intl'; -import styles from './PaginationCursor.module.scss'; - -const PaginationCursor = ({ - current, - total, -}: { - current: number; - total: number; -}) => { - const intl = useIntl(); - - return ( - <div className={styles.wrapper}> - <div className={styles.info}> - {intl.formatMessage( - { - defaultMessage: - '{articlesCount, plural, =0 {# loaded articles} one {# loaded article} other {# loaded articles}} out of a total of {total}', - description: 'PaginationCursor: loaded articles count message', - id: 'du4MLN', - }, - { articlesCount: current, total } - )} - </div> - <progress - className={styles.bar} - max={total} - value={current} - aria-valuemin={0} - aria-valuemax={total} - aria-label={intl.formatMessage({ - defaultMessage: - 'Number of articles loaded out of the total available.', - description: 'PaginationCursor: loaded articles count aria-label', - id: 'mC21ht', - })} - ></progress> - </div> - ); -}; - -export default PaginationCursor; diff --git a/src/components/PostFooter/PostFooter.module.scss b/src/components/PostFooter/PostFooter.module.scss deleted file mode 100644 index 7c1f1ce..0000000 --- a/src/components/PostFooter/PostFooter.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/placeholders"; - -.meta { - flex-flow: column wrap; -} - -.list { - @extend %flex-list; - - gap: var(--spacing-xs); -} - -.item { - > a { - padding: calc(var(--spacing-2xs) / 2) var(--spacing-xs); - } -} diff --git a/src/components/PostFooter/PostFooter.tsx b/src/components/PostFooter/PostFooter.tsx deleted file mode 100644 index 9bc4053..0000000 --- a/src/components/PostFooter/PostFooter.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { ButtonLink } from '@components/Buttons'; -import { TopicPreview } from '@ts/types/taxonomies'; -import Image from 'next/image'; -import { useIntl } from 'react-intl'; -import styles from './PostFooter.module.scss'; - -const PostFooter = ({ topics }: { topics: TopicPreview[] }) => { - const intl = useIntl(); - - const getTopics = () => { - return topics.map((topic) => { - return ( - <li className={styles.item} key={topic.id}> - <ButtonLink target={`/sujet/${topic.slug}`}> - {topic.featuredImage && ( - <Image - src={topic.featuredImage.sourceUrl} - alt={topic.featuredImage.altText} - layout="intrinsic" - width="20" - height="20" - /> - )} - {topic.title} - </ButtonLink> - </li> - ); - }); - }; - - return ( - <footer> - {topics.length > 0 && ( - <> - <dl className={styles.meta}> - <dt> - {intl.formatMessage({ - defaultMessage: 'Read more articles about:', - description: 'PostFooter: read more posts about given subjects', - id: 'YEudoh', - })} - </dt> - <dd> - <ul className={styles.list}>{getTopics()}</ul> - </dd> - </dl> - </> - )} - </footer> - ); -}; - -export default PostFooter; diff --git a/src/components/PostHeader/PostHeader.module.scss b/src/components/PostHeader/PostHeader.module.scss deleted file mode 100644 index f813060..0000000 --- a/src/components/PostHeader/PostHeader.module.scss +++ /dev/null @@ -1,83 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - composes: grid from "@styles/layout/_grid.scss"; - max-width: 100%; - position: relative; - - @include mix.media("screen") { - @include mix.dimensions("md") { - margin-bottom: var(--spacing-md); - } - - @include mix.dimensions("lg") { - --grid-gap: var(--spacing-lg); - } - } - - &::before, - &::after { - content: ""; - width: 100%; - height: 100%; - background: var(--color-bg-secondary); - border-top: fun.convert-px(3) solid var(--color-border-light); - border-bottom: fun.convert-px(3) solid var(--color-border-light); - } - - &::before { - grid-column: 1; - justify-self: start; - border-right: fun.convert-px(3) solid var(--color-border-light); - } - - &::after { - grid-column: 3; - justify-self: end; - border-left: fun.convert-px(3) solid var(--color-border-light); - } -} - -.body { - grid-column: 2; - background: var(--color-bg); -} - -.title { - flex: 0 0 100%; - display: flex; - flex-flow: row wrap; - align-items: center; - margin: 0; - position: relative; - text-shadow: fun.convert-px(1) fun.convert-px(1) 0 var(--color-shadow-light); - - &::before, - &::after { - content: ""; - width: 100%; - height: fun.convert-px(4); - background: radial-gradient( - ellipse at center, - var(--color-primary-light), - var(--color-primary-dark) - ); - } -} - -.cover { - display: block; - width: fun.convert-px(50); - height: fun.convert-px(50); - position: relative; - margin-right: var(--spacing-sm); -} - -.intro { - margin: var(--spacing-sm) 0 0; - - > *:last-child { - margin-bottom: 0; - } -} diff --git a/src/components/PostHeader/PostHeader.tsx b/src/components/PostHeader/PostHeader.tsx deleted file mode 100644 index c0a6b68..0000000 --- a/src/components/PostHeader/PostHeader.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import PostMeta from '@components/PostMeta/PostMeta'; -import { ArticleMeta } from '@ts/types/articles'; -import { Cover } from '@ts/types/cover'; -import Image from 'next/image'; -import React, { ReactElement } from 'react'; -import styles from './PostHeader.module.scss'; - -const PostHeader = ({ - cover, - intro, - title, - meta, -}: { - cover?: Cover; - intro?: string | ReactElement; - meta?: ArticleMeta; - title: string; -}) => { - const getIntro = () => { - if (React.isValidElement(intro)) { - const Intro = () => intro; - return ( - <div className={styles.intro}> - <Intro /> - </div> - ); - } - - return ( - intro && ( - <div - className={styles.intro} - dangerouslySetInnerHTML={{ __html: intro }} - ></div> - ) - ); - }; - - return ( - <header className={styles.wrapper}> - <div className={styles.body}> - <h1 className={styles.title}> - {cover && ( - <span className={styles.cover}> - <Image src={cover.sourceUrl} alt={cover.altText} layout="fill" /> - </span> - )} - {title} - </h1> - {meta && <PostMeta kind="article" meta={meta} />} - {getIntro()} - </div> - </header> - ); -}; - -export default PostHeader; diff --git a/src/components/PostMeta/PostMeta.module.scss b/src/components/PostMeta/PostMeta.module.scss deleted file mode 100644 index d438635..0000000 --- a/src/components/PostMeta/PostMeta.module.scss +++ /dev/null @@ -1,31 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - &--list { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - margin-top: var(--spacing-md); - font-size: var(--font-size-sm); - - @include mix.media("screen") { - @include mix.dimensions("sm") { - display: flex; - flex-flow: column nowrap; - margin: 0; - composes: meta from "@components/PostPreview/PostPreview.module.scss"; - } - } - } - - &--article { - flex-flow: column wrap; - margin: var(--spacing-sm) 0 0; - - @include mix.media("screen") { - @include mix.dimensions("xs") { - font-size: var(--font-size-sm); - } - } - } -} diff --git a/src/components/PostMeta/PostMeta.tsx b/src/components/PostMeta/PostMeta.tsx deleted file mode 100644 index 7fba0be..0000000 --- a/src/components/PostMeta/PostMeta.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { - Author, - CommentsCount, - Dates, - PostsCount, - ReadingTime, - Thematics, - Topics, - Website, -} from '@components/MetaItems'; -import { MetaKind } from '@ts/types/app'; -import { ArticleMeta } from '@ts/types/articles'; -import { useRouter } from 'next/router'; -import styles from './PostMeta.module.scss'; - -const PostMeta = ({ - meta, - kind = 'list', -}: { - meta: ArticleMeta; - kind?: MetaKind; -}) => { - const { - author, - commentCount, - dates, - readingTime, - results, - thematics, - topics, - website, - wordsCount, - } = meta; - const { asPath } = useRouter(); - const isThematic = () => asPath.includes('/thematique/'); - - const wrapperClass = styles[`wrapper--${kind}`]; - - return ( - <dl className={wrapperClass}> - {author && <Author name={author.name} kind={kind} />} - {dates && ( - <Dates - publication={dates.publication} - update={dates.update} - kind={kind} - /> - )} - {readingTime !== undefined && wordsCount !== undefined && ( - <ReadingTime time={readingTime} words={wordsCount} kind={kind} /> - )} - {results !== undefined && <PostsCount total={results} kind={kind} />} - {!isThematic() && thematics && thematics.length > 0 && ( - <Thematics list={thematics} kind={kind} /> - )} - {isThematic() && topics && topics.length > 0 && ( - <Topics list={topics} kind={kind} /> - )} - {website && <Website url={website} kind={kind} />} - {commentCount !== undefined && ( - <CommentsCount total={commentCount} kind={kind} /> - )} - </dl> - ); -}; - -export default PostMeta; diff --git a/src/components/PostPreview/PostPreview.module.scss b/src/components/PostPreview/PostPreview.module.scss deleted file mode 100644 index c30ab75..0000000 --- a/src/components/PostPreview/PostPreview.module.scss +++ /dev/null @@ -1,105 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - --icon-size: #{fun.convert-px(20)}; - - padding: var(--spacing-2xs) 0 var(--spacing-lg); - transition: all 0.3s ease-in-out 0s, border 0s; - - &:hover { - --icon-size: #{fun.convert-px(25)}; - - a { - > svg { - :global { - animation: pulse 1.5s ease-in-out 0.5s infinite; - } - } - - &:hover { - > svg { - animation: none; - } - } - } - } - - &:active { - --icon-size: 0; - } -} - -.cover { - width: auto; - height: fun.convert-px(100); - margin: 0 auto var(--spacing-sm); - position: relative; - border: fun.convert-px(1) solid var(--color-border); -} - -h2.title { - background: none; - text-shadow: none; -} - -@include mix.media("screen") { - @include mix.dimensions("xs") { - .wrapper { - margin: 0; - padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-md); - border: fun.convert-px(1) solid var(--color-primary-dark); - border-radius: fun.convert-px(3); - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) 0 - var(--color-shadow), - fun.convert-px(3) fun.convert-px(3) fun.convert-px(3) fun.convert-px(-1) - var(--color-shadow-light), - fun.convert-px(5) fun.convert-px(5) fun.convert-px(7) fun.convert-px(-1) - var(--color-shadow-light); - } - - .read-more { - font-size: var(--font-size-sm); - } - } - - @include mix.dimensions("sm") { - .wrapper { - display: grid; - grid-template-columns: minmax(0, 3fr) minmax(0, 1fr); - grid-template-rows: repeat(3, max-content); - column-gap: var(--spacing-md); - } - - .cover { - grid-column: 2; - grid-row: 1; - margin: 0 0 var(--spacing-sm); - } - - .header { - grid-column: 1; - grid-row: 1; - align-self: center; - } - - .meta { - grid-column: 2; - grid-row: 2 / 4; - } - - .body { - grid-column: 1; - grid-row: 2; - } - - .footer { - grid-column: 1; - grid-row: 3; - } - - .read-more { - margin: 0; - } - } -} diff --git a/src/components/PostPreview/PostPreview.tsx b/src/components/PostPreview/PostPreview.tsx deleted file mode 100644 index 0b9e332..0000000 --- a/src/components/PostPreview/PostPreview.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { ButtonLink } from '@components/Buttons'; -import { ArrowIcon } from '@components/Icons'; -import PostMeta from '@components/PostMeta/PostMeta'; -import { TitleLevel } from '@ts/types/app'; -import { ArticleMeta, ArticlePreview } from '@ts/types/articles'; -import { settings } from '@utils/config'; -import Image from 'next/image'; -import Link from 'next/link'; -import { FormattedMessage } from 'react-intl'; -import { BlogPosting, WithContext } from 'schema-dts'; -import styles from './PostPreview.module.scss'; -import Script from 'next/script'; - -const PostPreview = ({ - post, - titleLevel, -}: { - post: ArticlePreview; - titleLevel: TitleLevel; -}) => { - const TitleTag = `h${titleLevel}` as keyof JSX.IntrinsicElements; - const { - commentCount, - dates, - featuredImage, - info, - intro, - slug, - thematics, - title, - topics, - } = post; - - const meta: ArticleMeta = { - commentCount: commentCount ? commentCount : 0, - dates: dates, - readingTime: info.readingTime, - thematics: thematics, - topics: topics, - wordsCount: info.wordsCount, - }; - - const publicationDate = new Date(dates.publication); - const updateDate = new Date(dates.update); - - const schemaJsonLd: WithContext<BlogPosting> = { - '@context': 'https://schema.org', - '@type': 'BlogPosting', - name: title, - description: intro, - articleBody: intro, - author: { '@id': `${settings.url}/#branding` }, - commentCount: commentCount ? commentCount : 0, - copyrightYear: publicationDate.getFullYear(), - creator: { '@id': `${settings.url}/#branding` }, - dateCreated: publicationDate.toISOString(), - dateModified: updateDate.toISOString(), - datePublished: publicationDate.toISOString(), - editor: { '@id': `${settings.url}/#branding` }, - headline: title, - image: featuredImage?.sourceUrl, - inLanguage: settings.locales.defaultLocale, - isBasedOn: `${settings.url}/article/${slug}`, - isPartOf: { '@id': `${settings.url}/blog` }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - thumbnailUrl: featuredImage?.sourceUrl, - }; - - return ( - <> - <Script - id="schema-post-preview" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article className={styles.wrapper}> - {featuredImage && Object.keys(featuredImage).length > 0 && ( - <div className={styles.cover}> - <Image - src={featuredImage.sourceUrl} - alt={featuredImage.altText} - layout="fill" - objectFit="contain" - /> - </div> - )} - <header className={styles.header}> - <TitleTag className={styles.title}> - <Link href={`/article/${slug}`}> - <a>{title}</a> - </Link> - </TitleTag> - </header> - <div - className={styles.body} - dangerouslySetInnerHTML={{ __html: intro }} - ></div> - <footer className={styles.footer}> - <ButtonLink target={`/article/${slug}`} position="left"> - <FormattedMessage - defaultMessage="Read more<a11y> about {title}</a11y>" - description="PostPreview: read more link" - id="bkbrN7" - values={{ - title, - a11y: (chunks: string) => ( - <span className="screen-reader-text">{chunks}</span> - ), - }} - /> - <ArrowIcon /> - </ButtonLink> - </footer> - <PostMeta meta={meta} /> - </article> - </> - ); -}; - -export default PostPreview; diff --git a/src/components/PostsList/PostsList.module.scss b/src/components/PostsList/PostsList.module.scss deleted file mode 100644 index b4ffbd9..0000000 --- a/src/components/PostsList/PostsList.module.scss +++ /dev/null @@ -1,51 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.section { - --column-3: 0; - --grid-gap: 0; - - composes: grid from "@styles/layout/_grid.scss"; - align-items: first baseline; -} - -.year { - grid-column: 2; - margin: var(--spacing-md) 0 0; - - @include mix.media("screen") { - @include mix.dimensions("md") { - grid-column: 1; - justify-self: end; - position: sticky; - top: var(--spacing-xs); - margin-right: var(--spacing-lg); - } - } -} - -.list { - @extend %reset-ordered-list; - - grid-column: 2; - margin: 0 auto var(--spacing-md); -} - -li.item { - border-bottom: fun.convert-px(1) solid var(--color-border); - - &:not(:last-of-type) { - margin: 0 0 var(--spacing-md) 0; - } - - &:first-of-type { - margin-top: var(--spacing-sm); - - @include mix.media("screen") { - @include mix.dimensions("md") { - margin-top: 0; - } - } - } -} diff --git a/src/components/PostsList/PostsList.tsx b/src/components/PostsList/PostsList.tsx deleted file mode 100644 index f998846..0000000 --- a/src/components/PostsList/PostsList.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import PostPreview from '@components/PostPreview/PostPreview'; -import { PostsList as PostsListData } from '@ts/types/blog'; -import { sortPostsByYear } from '@utils/helpers/sort'; -import { ForwardedRef, forwardRef, Fragment } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './PostsList.module.scss'; - -const PostsList = ( - { - data, - showYears, - }: { - data: PostsListData[]; - showYears: boolean; - }, - ref: ForwardedRef<HTMLSpanElement> -) => { - const intl = useIntl(); - const titleLevel = showYears ? 3 : 2; - - const getPostsListByYear = () => { - const posts = sortPostsByYear(data); - const years = Object.keys(posts).reverse(); - - const getLastPostId = () => { - const oldestYear = Object.keys(posts)[0]; - const lastPost = posts[oldestYear][posts[oldestYear].length - 1]; - return lastPost.id; - }; - - return years.map((year) => { - return ( - <section key={year} className={styles.section}> - {showYears && ( - <h2 className={styles.year}> - <span className="screen-reader-text"> - {intl.formatMessage({ - defaultMessage: 'Published on', - description: 'PostsList: published on year label', - id: 'EvODgw', - })}{' '} - </span> - {year} - </h2> - )} - <ol className={styles.list}> - {posts[year].map((post) => { - const isLastPost = post.id === getLastPostId(); - return ( - <Fragment key={post.id}> - <li className={styles.item}> - <PostPreview post={post} titleLevel={titleLevel} /> - </li> - {isLastPost && ( - <li className={styles.item}> - <span ref={ref} tabIndex={-1} /> - </li> - )} - </Fragment> - ); - })} - </ol> - </section> - ); - }); - }; - - const getPostsList = () => { - return data.map((page) => { - const getLastPostId = () => { - const lastPost = page.posts[page.posts.length - 1]; - return lastPost.id; - }; - - if (page.posts.length === 0) { - return ( - <p key="no-result"> - {intl.formatMessage({ - defaultMessage: 'No results found.', - description: 'PostsList: no results', - id: 'vK7Sxv', - })} - </p> - ); - } else { - return ( - <Fragment key={page.pageInfo.endCursor}> - <ol className={styles.list}> - {page.posts.map((post) => { - const isLastPost = post.id === getLastPostId(); - return ( - <Fragment key={post.id}> - <li key={post.id} className={styles.item}> - <PostPreview post={post} titleLevel={titleLevel} /> - </li> - {isLastPost && <span ref={ref} tabIndex={-1} />} - </Fragment> - ); - })} - </ol> - </Fragment> - ); - } - }); - }; - - return <div>{showYears ? getPostsListByYear() : getPostsList()}</div>; -}; - -export default forwardRef(PostsList); diff --git a/src/components/ProjectPreview/ProjectPreview.module.scss b/src/components/ProjectPreview/ProjectPreview.module.scss deleted file mode 100644 index 3bf56ec..0000000 --- a/src/components/ProjectPreview/ProjectPreview.module.scss +++ /dev/null @@ -1,98 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.article { - display: flex; - flex-flow: column nowrap; - height: 100%; - padding: var(--spacing-md); - text-align: center; -} - -.cover { - height: fun.convert-px(150); - position: relative; -} - -.title { - flex: 1; - margin: var(--spacing-xs) 0; - background: none; - text-decoration: underline solid transparent 0; - text-shadow: none; - transition: all 0.3s linear 0s; -} - -.body { - margin: 0 0 var(--spacing-xs); -} - -.footer { - margin-top: auto; -} - -.meta { - display: block; - - &__item { - display: flex; - flex-flow: row wrap; - place-content: center; - gap: var(--spacing-2xs); - } -} - -.link { - display: block; - height: 100%; - background: var(--color-bg); - color: inherit; - text-decoration: none; - border: fun.convert-px(3) solid var(--color-primary); - border-radius: fun.convert-px(5); - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) - var(--color-shadow), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) - var(--color-shadow); - transition: all 0.3s ease-in-out 0s; - - &:hover, - &:focus, - &:active { - color: inherit; - } - - &:hover, - &:focus { - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow-light), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) - var(--color-shadow-light), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) - var(--color-shadow-light), - fun.convert-px(7) fun.convert-px(10) fun.convert-px(12) fun.convert-px(-3) - var(--color-shadow-light); - transform: scale(1.05); - } - - &:focus { - .title { - text-decoration: underline solid var(--color-primary) 0.3ex; - } - } - - &:active { - box-shadow: 0 0 0 0 var(--color-shadow); - transform: scale(0.95); - - .title { - text-decoration: none; - } - } -} - -.techno { - padding: 0 var(--spacing-2xs); - border: fun.convert-px(1) solid var(--color-primary-darker); -} diff --git a/src/components/ProjectPreview/ProjectPreview.tsx b/src/components/ProjectPreview/ProjectPreview.tsx deleted file mode 100644 index 1e1ced2..0000000 --- a/src/components/ProjectPreview/ProjectPreview.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Project } from '@ts/types/app'; -import { slugify } from '@utils/helpers/slugify'; -import Image from 'next/image'; -import Link from 'next/link'; -import { useIntl } from 'react-intl'; -import styles from './ProjectPreview.module.scss'; - -const ProjectPreview = ({ project }: { project: Project }) => { - const { id, meta, tagline, title } = project; - const intl = useIntl(); - - return ( - <Link href={`/projet/${project.slug}`}> - <a className={styles.link}> - <article className={styles.article}> - <header> - {meta.hasCover && ( - <div className={styles.cover}> - <Image - src={`/projects/${id}.jpg`} - layout="fill" - objectFit="contain" - objectPosition="center" - alt={intl.formatMessage( - { - defaultMessage: '{title} picture', - description: 'ProjectPreview: cover alt text', - id: '2pykor', - }, - { title } - )} - /> - </div> - )} - <h2 className={styles.title}>{title}</h2> - </header> - {tagline && ( - <div - className={styles.body} - dangerouslySetInnerHTML={{ __html: tagline }} - ></div> - )} - <footer className={styles.footer}> - <dl className={styles.meta}> - {meta.technologies && ( - <div className={styles.meta__item}> - <dt className="screen-reader-text"> - {intl.formatMessage( - { - defaultMessage: - '{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}', - description: 'ProjectPreview: technologies list label', - id: 'okFrAO', - }, - { count: meta.technologies.length } - )} - </dt> - {meta.technologies.map((techno) => ( - <dd key={slugify(techno)} className={styles.techno}> - {techno} - </dd> - ))} - </div> - )} - </dl> - </footer> - </article> - </a> - </Link> - ); -}; - -export default ProjectPreview; diff --git a/src/components/ProjectSummary/ProjectSummary.module.scss b/src/components/ProjectSummary/ProjectSummary.module.scss deleted file mode 100644 index cf1e77f..0000000 --- a/src/components/ProjectSummary/ProjectSummary.module.scss +++ /dev/null @@ -1,73 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.wrapper { - margin-bottom: var(--spacing-md); - padding: var(--spacing-sm) var(--spacing-md) var(--spacing-md); - border: fun.convert-px(1) solid var(--color-border); -} - -.cover { - height: fun.convert-px(150); - position: relative; -} - -.info { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(20ch, 1fr)); - align-items: start; - justify-content: left; - column-gap: var(--spacing-md); - margin: var(--spacing-md) 0 0; -} - -.inline-data { - display: inline-block; - margin-top: fun.convert-px(3); - - &:not(:last-of-type) { - margin-right: var(--spacing-xs); - } -} - -.techno { - padding: 0 var(--spacing-2xs); - border: fun.convert-px(1) solid var(--color-primary-darker); -} - -.repo { - display: block; - width: 3em; - height: 3em; - background: none; - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-1) - var(--color-shadow), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(4) fun.convert-px(-3) - var(--color-shadow), - 0 0 0 0 var(--color-shadow); - transition: all 0.3s linear 0s; - - &:hover, - &:focus { - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - fun.convert-px(1) fun.convert-px(1) fun.convert-px(2) fun.convert-px(-1) - var(--color-shadow-light), - fun.convert-px(3) fun.convert-px(3) fun.convert-px(4) fun.convert-px(-4) - var(--color-shadow-light), - fun.convert-px(6) fun.convert-px(6) fun.convert-px(10) fun.convert-px(-3) - var(--color-shadow); - transform: scale(1.15); - } - - &:focus { - outline: var(--color-primary) dashed fun.convert-px(2); - } - - &:active { - box-shadow: 0 0 0 0 var(--color-shadow); - outline: none; - transform: scale(0.9); - } -} diff --git a/src/components/ProjectSummary/ProjectSummary.tsx b/src/components/ProjectSummary/ProjectSummary.tsx deleted file mode 100644 index 79e783e..0000000 --- a/src/components/ProjectSummary/ProjectSummary.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import GithubIcon from '@assets/images/social-media/github.svg'; -import GitlabIcon from '@assets/images/social-media/gitlab.svg'; -import { ProjectMeta } from '@ts/types/app'; -import { settings } from '@utils/config'; -import { getFormattedDate } from '@utils/helpers/format'; -import { slugify } from '@utils/helpers/slugify'; -import useGithubApi from '@utils/hooks/useGithubApi'; -import Image from 'next/image'; -import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; -import styles from './ProjectSummary.module.scss'; - -const ProjectSummary = ({ - id, - meta, -}: { - id: string; - title: string; - meta: ProjectMeta; -}) => { - const { hasCover, license, repos, technologies } = meta; - const intl = useIntl(); - const router = useRouter(); - const locale = router.locale ? router.locale : settings.locales.defaultLocale; - const { data } = useGithubApi(repos?.github ? repos.github : ''); - - return ( - <div className={styles.wrapper}> - {hasCover && ( - <div className={styles.cover}> - <Image - src={`/projects/${id}.jpg`} - alt={intl.formatMessage({ - defaultMessage: '{title} preview', - description: 'ProjectSummary: cover alt text', - id: 'mh7tGg', - })} - layout="fill" - objectFit="contain" - /> - </div> - )} - <dl className={styles.info}> - {data && ( - <div className={styles.info__item}> - <dt> - {intl.formatMessage({ - defaultMessage: 'Created on:', - description: 'ProjectSummary: creation date label', - id: 'CWi0go', - })} - </dt> - <dd> - <time dateTime={data.created_at}> - {getFormattedDate(data.created_at, locale)} - </time> - </dd> - </div> - )} - {data && ( - <div className={styles.info__item}> - <dt> - {intl.formatMessage({ - defaultMessage: 'Last updated on:', - description: 'ProjectSummary: update date label', - id: 'vJ+QDV', - })} - </dt> - <dd> - <time dateTime={data.updated_at}> - {getFormattedDate(data.updated_at, locale)} - </time> - </dd> - </div> - )} - <div className={styles.info__item}> - <dt> - {intl.formatMessage({ - defaultMessage: 'License:', - description: 'ProjectSummary: license label', - id: 'hKagVG', - })} - </dt> - <dd>{license}</dd> - </div> - {technologies && ( - <div className={styles.info__item}> - <dt> - {intl.formatMessage( - { - defaultMessage: - '{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}', - description: 'ProjectSummary: technologies list label', - id: 'enwhNm', - }, - { count: technologies.length } - )} - </dt> - {technologies.map((techno) => ( - <dd - key={slugify(techno)} - className={`${styles.techno} ${styles['inline-data']}`} - > - {techno} - </dd> - ))} - </div> - )} - {repos && ( - <div className={styles.info__item}> - <dt> - {intl.formatMessage( - { - defaultMessage: - '{count, plural, =0 {Repositories:} one {Repository:} other {Repositories:}}', - description: 'ProjectSummary: repositories list label', - id: 'OTTv+m', - }, - { count: Object.keys(repos).length } - )} - </dt> - {repos.github && ( - <dd className={styles['inline-data']}> - <a - href={`https://github.com/${repos.github}`} - className={styles.repo} - > - <GithubIcon /> - <span className="screen-reader-text">Github</span> - </a> - </dd> - )} - {repos.gitlab && ( - <dd className={styles['inline-data']}> - <a - href={`https://gitlab.com/${repos.gitlab}`} - className={styles.repo} - > - <GitlabIcon /> - <span className="screen-reader-text">Gitlab</span> - </a> - </dd> - )} - </div> - )} - {data && repos && ( - <div> - <dt> - {intl.formatMessage({ - defaultMessage: 'Popularity:', - description: 'ProjectSummary: popularity label', - id: 'vgMk0q', - })} - </dt> - {repos.github && ( - <dd> - ⭐ - <a href={`https://github.com/${repos.github}/stargazers`}> - {intl.formatMessage( - { - defaultMessage: - '{starsCount, plural, =0 {0 stars on Github} one {# star on Github} other {# stars on Github}}', - description: 'ProjectSummary: technologies list label', - id: 'aA3hOT', - }, - { starsCount: data.stargazers_count } - )} - </a> - </dd> - )} - </div> - )} - </dl> - </div> - ); -}; - -export default ProjectSummary; diff --git a/src/components/ProjectsList/ProjectsList.module.scss b/src/components/ProjectsList/ProjectsList.module.scss deleted file mode 100644 index fbed08d..0000000 --- a/src/components/ProjectsList/ProjectsList.module.scss +++ /dev/null @@ -1,25 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/placeholders"; - -.list { - --items: 4; - --items-size: 31ch; - - @extend %reset-list; - - display: grid; - grid-template-columns: repeat( - auto-fit, - min(calc(100vw - (var(--spacing-md) * 2)), var(--items-size)) - ); - gap: var(--spacing-sm); - place-content: center; - width: min( - calc(100vw - (var(--spacing-md) * 2)), - calc( - (var(--items-size) * var(--items)) + - (var(--spacing-sm) * (var(--items) - 1)) - ) - ); - margin: var(--spacing-sm) auto 0; -} diff --git a/src/components/ProjectsList/ProjectsList.tsx b/src/components/ProjectsList/ProjectsList.tsx deleted file mode 100644 index 07e6a71..0000000 --- a/src/components/ProjectsList/ProjectsList.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import ProjectPreview from '@components/ProjectPreview/ProjectPreview'; -import { Project } from '@ts/types/app'; -import styles from './ProjectsList.module.scss'; - -const ProjectsList = ({ projects }: { projects: Project[] }) => { - const getProjectItems = () => { - return projects.map((project) => { - return project.title ? ( - <li className={styles.item} key={project.id}> - <ProjectPreview project={project} /> - </li> - ) : ( - '' - ); - }); - }; - - return <ul className={styles.list}>{getProjectItems()}</ul>; -}; - -export default ProjectsList; diff --git a/src/components/SearchForm/SearchForm.module.scss b/src/components/SearchForm/SearchForm.module.scss deleted file mode 100644 index 4debfbb..0000000 --- a/src/components/SearchForm/SearchForm.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.title { - margin-bottom: var(--spacing-sm); - color: var(--color-primary-dark); - font-size: var(--font-size-lg); - font-weight: 600; -} diff --git a/src/components/SearchForm/SearchForm.tsx b/src/components/SearchForm/SearchForm.tsx deleted file mode 100644 index f4735af..0000000 --- a/src/components/SearchForm/SearchForm.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { ButtonSubmit } from '@components/Buttons'; -import { Field, Form } from '@components/FormElements'; -import { SearchIcon } from '@components/Icons'; -import { useRouter } from 'next/router'; -import { FormEvent, useEffect, useRef, useState } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './SearchForm.module.scss'; - -const SearchForm = ({ isOpened }: { isOpened: boolean }) => { - const intl = useIntl(); - const [query, setQuery] = useState(''); - const inputRef = useRef<HTMLInputElement>(null); - const router = useRouter(); - - useEffect(() => { - setTimeout(() => { - if (isOpened && inputRef.current) { - inputRef.current.focus(); - } - }, 400); - }, [isOpened]); - - const launchSearch = (e: FormEvent) => { - e.preventDefault(); - router.push({ pathname: '/recherche', query: { s: query } }); - setQuery(''); - }; - - return ( - <> - <div className={styles.title}> - {intl.formatMessage({ - defaultMessage: 'Search', - description: 'SearchForm : form title', - id: 'eFMu2E', - })} - </div> - <Form submitHandler={launchSearch} kind="search" id="search"> - <label htmlFor="search-query" className="screen-reader-text"> - {intl.formatMessage({ - defaultMessage: 'Keywords:', - description: 'SearchForm: search field label', - id: 'YvMPuD', - })} - </label> - <Field - ref={inputRef} - id="search-query" - name="search-query" - kind="search" - value={query} - setValue={setQuery} - required={true} - /> - <ButtonSubmit modifier="search"> - <SearchIcon /> - <span className="screen-reader-text"> - {intl.formatMessage({ - defaultMessage: 'Search', - description: 'SearchForm: search button text', - id: 'AnaPbu', - })} - </span> - </ButtonSubmit> - </Form> - </> - ); -}; - -export default SearchForm; diff --git a/src/components/Settings/AckeeSelect/AckeeSelect.module.scss b/src/components/Settings/AckeeSelect/AckeeSelect.module.scss deleted file mode 100644 index b145761..0000000 --- a/src/components/Settings/AckeeSelect/AckeeSelect.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.wrapper { - display: flex; - flex-flow: row wrap; - align-items: center; - gap: var(--spacing-xs); -} diff --git a/src/components/Settings/AckeeSelect/AckeeSelect.tsx b/src/components/Settings/AckeeSelect/AckeeSelect.tsx deleted file mode 100644 index f711fe2..0000000 --- a/src/components/Settings/AckeeSelect/AckeeSelect.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { Field, Label } from '@components/FormElements'; -import Tooltip from '@components/Tooltip/Tooltip'; -import { LocalStorage } from '@services/local-storage'; -import { useAckeeTracker } from '@utils/providers/ackee'; -import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './AckeeSelect.module.scss'; - -const AckeeSelect = () => { - const intl = useIntl(); - const options = [ - { - id: 'partial', - name: intl.formatMessage({ - defaultMessage: 'Partial', - description: 'AckeeSelect: partial option name', - id: 'e/8Kyj', - }), - value: 'partial', - }, - { - id: 'full', - name: intl.formatMessage({ - defaultMessage: 'Full', - description: 'AckeeSelect: full option name', - id: 'PzRpPw', - }), - value: 'full', - }, - ]; - const [value, setValue] = useState<string>('full'); - const { setDetailed } = useAckeeTracker(); - - useEffect(() => { - setDetailed(value === 'full'); - }, [setDetailed, value]); - - useEffect(() => { - const initialState = LocalStorage.get('ackee-tracking'); - if (initialState) setValue(initialState); - }, []); - - useEffect(() => { - LocalStorage.set('ackee-tracking', `${value}`); - }, [value]); - - const label = ( - <Label - body={intl.formatMessage({ - defaultMessage: 'Tracking:', - description: 'AckeeSelect: select label', - id: '2pmylc', - })} - htmlFor="ackee-settings" - kind="settings" - /> - ); - - const message = [ - intl.formatMessage({ - defaultMessage: 'Partial includes only page url, views and duration.', - description: 'AckeeSelect: tooltip message', - id: 'skb4W5', - }), - intl.formatMessage({ - defaultMessage: - 'Full includes all information from partial as well as information about referrer, operating system, device, browser, screen size and language.', - description: 'AckeeSelect: tooltip message', - id: 'Ogccx6', - }), - ]; - - return ( - <div className={styles.wrapper}> - <Field - id="ackee-settings" - name="ackee-settings" - kind="select" - label={label} - options={options} - value={value} - setValue={setValue} - /> - <Tooltip - message={message} - title={intl.formatMessage({ - defaultMessage: 'Ackee tracking (analytics)', - description: 'AckeeSelect: tooltip title', - id: 'F1EQX3', - })} - /> - </div> - ); -}; - -export default AckeeSelect; diff --git a/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx b/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx deleted file mode 100644 index 20ad267..0000000 --- a/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Toggle } from '@components/FormElements'; -import { MoonIcon, SunIcon } from '@components/Icons'; -import Spinner from '@components/Spinner/Spinner'; -import { usePrismTheme } from '@utils/providers/prism-theme'; -import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; - -const PrismThemeToggle = () => { - const intl = useIntl(); - const [isMounted, setIsMounted] = useState<boolean>(false); - - useEffect(() => { - setIsMounted(true); - }, []); - - const { theme, setTheme, resolvedTheme } = usePrismTheme(); - const [isDarkTheme, setIsDarkTheme] = useState<boolean>(theme === 'dark'); - - useEffect(() => { - if (theme === 'system') { - setIsDarkTheme(resolvedTheme === 'dark'); - } else { - setIsDarkTheme(theme === 'dark'); - } - }, [theme, resolvedTheme]); - - const updateTheme = () => { - isDarkTheme ? setTheme('light') : setTheme('dark'); - setIsDarkTheme(!isDarkTheme); - }; - - if (!isMounted) return <Spinner />; - - return ( - <Toggle - id="prism-theme" - label={intl.formatMessage({ - defaultMessage: 'Code blocks:', - description: 'PrismThemeToggle: toggle label', - id: 'w0UfY0', - })} - leftChoice={<SunIcon />} - rightChoice={<MoonIcon />} - value={isDarkTheme} - changeHandler={updateTheme} - /> - ); -}; - -export default PrismThemeToggle; diff --git a/src/components/Settings/ReduceMotion/ReduceMotion.tsx b/src/components/Settings/ReduceMotion/ReduceMotion.tsx deleted file mode 100644 index 00562cd..0000000 --- a/src/components/Settings/ReduceMotion/ReduceMotion.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Toggle } from '@components/FormElements'; -import { LocalStorage } from '@services/local-storage'; -import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; - -const ReduceMotion = () => { - const intl = useIntl(); - const [isDeactivated, setIsDeactivated] = useState<boolean>(false); - - useEffect(() => { - const initialState = LocalStorage.get('reduced-motion'); - if (initialState) setIsDeactivated(initialState === 'true' ? true : false); - }, []); - - useEffect(() => { - document.documentElement.dataset.reducedMotion = `${isDeactivated}`; - LocalStorage.set('reduced-motion', `${isDeactivated}`); - }, [isDeactivated]); - - const updateState = () => { - setIsDeactivated(!isDeactivated); - }; - - return ( - <Toggle - id="reduced-motion" - label={intl.formatMessage({ - defaultMessage: 'Animations:', - description: 'ReduceMotion: toggle label', - id: 'X3PDXO', - })} - leftChoice={intl.formatMessage({ - defaultMessage: 'On', - description: 'ReduceMotion: toggle on label', - id: 'qPU/Qn', - })} - rightChoice={intl.formatMessage({ - defaultMessage: 'Off', - description: 'ReduceMotion: toggle off label', - id: 'w1nIrj', - })} - value={isDeactivated} - changeHandler={updateState} - /> - ); -}; - -export default ReduceMotion; diff --git a/src/components/Settings/Settings.module.scss b/src/components/Settings/Settings.module.scss deleted file mode 100644 index fe6b17b..0000000 --- a/src/components/Settings/Settings.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.title { - --icon-size: #{fun.convert-px(30)}; - - display: flex; - flex-flow: row nowrap; - gap: var(--spacing-2xs); - margin-bottom: var(--spacing-md); - color: var(--color-primary-dark); - font-size: var(--font-size-lg); - font-weight: 600; - - svg { - margin: 0; - } -} diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx deleted file mode 100644 index fec4c45..0000000 --- a/src/components/Settings/Settings.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { CogIcon } from '@components/Icons'; -import ThemeToggle from '@components/Settings/ThemeToggle/ThemeToggle'; -import { useIntl } from 'react-intl'; -import AckeeSelect from './AckeeSelect/AckeeSelect'; -import PrismThemeToggle from './PrismThemeToggle/PrismThemeToggle'; -import ReduceMotion from './ReduceMotion/ReduceMotion'; -import styles from './Settings.module.scss'; - -const Settings = () => { - const intl = useIntl(); - - return ( - <> - <div className={styles.title}> - <CogIcon />{' '} - {intl.formatMessage({ - defaultMessage: 'Settings', - description: 'Settings: modal title', - id: 'bHEmkY', - })} - </div> - <ThemeToggle /> - <ReduceMotion /> - <PrismThemeToggle /> - <AckeeSelect /> - </> - ); -}; - -export default Settings; diff --git a/src/components/Settings/ThemeToggle/ThemeToggle.tsx b/src/components/Settings/ThemeToggle/ThemeToggle.tsx deleted file mode 100644 index ec2cee1..0000000 --- a/src/components/Settings/ThemeToggle/ThemeToggle.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Toggle } from '@components/FormElements'; -import { MoonIcon, SunIcon } from '@components/Icons'; -import Spinner from '@components/Spinner/Spinner'; -import { useTheme } from 'next-themes'; -import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; - -const ThemeToggle = () => { - const intl = useIntl(); - const [isMounted, setIsMounted] = useState<boolean>(false); - const { resolvedTheme, setTheme } = useTheme(); - - useEffect(() => { - setIsMounted(true); - }, []); - - if (!isMounted) return <Spinner />; - - const isDarkTheme = resolvedTheme === 'dark'; - - const updateTheme = () => { - setTheme(isDarkTheme ? 'light' : 'dark'); - }; - - return ( - <Toggle - id="dark-theme" - label={intl.formatMessage({ - defaultMessage: 'Theme:', - description: 'ThemeToggle: toggle label', - id: 'O9XLDc', - })} - leftChoice={<SunIcon />} - rightChoice={<MoonIcon />} - value={isDarkTheme} - changeHandler={updateTheme} - /> - ); -}; - -export default ThemeToggle; diff --git a/src/components/Sidebar/Sidebar.module.scss b/src/components/Sidebar/Sidebar.module.scss deleted file mode 100644 index fb6230d..0000000 --- a/src/components/Sidebar/Sidebar.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - grid-column: 2; - - &--left { - margin: var(--spacing-md) 0; - } - - &--right { - margin: var(--spacing-md) 0 0; - } - - @include mix.media("screen") { - @include mix.dimensions("md") { - align-self: stretch; - margin: 0 var(--spacing-xs) var(--spacing-md); - - &--right { - grid-row: 2 / 4; - grid-column: 3; - } - } - - @include mix.dimensions("lg") { - &--left { - grid-row: 2 / 4; - grid-column: 1; - } - } - } -} - -.body { - @include mix.media("screen") { - @include mix.dimensions("md") { - align-self: flex-start; - width: 100%; - position: sticky; - top: var(--spacing-xs); - } - } -} diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx deleted file mode 100644 index 9e2079d..0000000 --- a/src/components/Sidebar/Sidebar.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Children, cloneElement, isValidElement, ReactNode } from 'react'; -import styles from './Sidebar.module.scss'; - -type SidebarPosition = 'left' | 'right'; - -const Sidebar = ({ - children, - position, - ariaLabel, - title, -}: { - children: ReactNode; - position: SidebarPosition; - ariaLabel?: string; - title?: string; -}) => { - const childrenWithProps = Children.map(children, (child) => { - if (isValidElement(child)) { - return cloneElement(child, { titleLevel: title ? 3 : 2 }); - } - return child; - }); - - const positionClass = `wrapper--${position}`; - - return ( - <aside - className={`${styles.wrapper} ${styles[positionClass]}`} - aria-label={ariaLabel} - aria-labelledby={title ? `${position}-sidebar-title` : undefined} - > - <div className={styles.body}> - {title && <h2 id={`${position}-sidebar-title`}>{title}</h2>} - {childrenWithProps} - </div> - </aside> - ); -}; - -export default Sidebar; diff --git a/src/components/Spinner/Spinner.module.scss b/src/components/Spinner/Spinner.module.scss deleted file mode 100644 index 8d818a2..0000000 --- a/src/components/Spinner/Spinner.module.scss +++ /dev/null @@ -1,48 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.wrapper { - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: center; - gap: var(--spacing-2xs); - margin: var(--spacing-md) 0; -} - -.ball { - width: fun.convert-px(8); - height: fun.convert-px(8); - background: linear-gradient( - to right, - var(--color-primary-light) 0%, - var(--color-primary-lighter) 100% - ); - border-radius: 50%; - animation: spinner 1.4s infinite ease-in-out both; - - &:first-child { - animation-delay: -0.32s; - } - - &:nth-child(2) { - animation-delay: -0.16s; - } -} - -.text { - margin-left: var(--spacing-xs); - color: var(--color-primary-darker); - text-align: center; -} - -@keyframes spinner { - 0%, - 80%, - 100% { - transform: scale(0); - } - - 40% { - transform: scale(1); - } -} diff --git a/src/components/Spinner/Spinner.tsx b/src/components/Spinner/Spinner.tsx deleted file mode 100644 index 9117d90..0000000 --- a/src/components/Spinner/Spinner.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useIntl } from 'react-intl'; -import styles from './Spinner.module.scss'; - -const Spinner = ({ message }: { message?: string }) => { - const intl = useIntl(); - - return ( - <div className={styles.wrapper}> - <div className={styles.ball}></div> - <div className={styles.ball}></div> - <div className={styles.ball}></div> - <div className={styles.text}> - {message || - intl.formatMessage({ - defaultMessage: 'Loading...', - description: 'Spinner: loading text', - id: 'q9cJQe', - })} - </div> - </div> - ); -}; - -export default Spinner; diff --git a/src/components/Toolbar/Toolbar.module.scss b/src/components/Toolbar/Toolbar.module.scss deleted file mode 100644 index debb3b7..0000000 --- a/src/components/Toolbar/Toolbar.module.scss +++ /dev/null @@ -1,114 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - --btn-size: #{fun.convert-px(60)}; - - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: space-around; - width: 100%; - height: var(--toolbar-size); - position: fixed; - bottom: 0; - left: 0; - z-index: 5; - background: var(--color-bg); - border-top: fun.convert-px(4) solid; - border-image: radial-gradient( - ellipse at top, - var(--color-primary-lighter) 20%, - var(--color-primary) 100% - ) - 1; - box-shadow: 0 fun.convert-px(-2) fun.convert-px(3) fun.convert-px(-1) - var(--color-shadow-dark); - - :global { - animation: slide-in-from-bottom 0.8s ease-in-out 0s 1; - } - - @include mix.media("screen") { - @include mix.dimensions("sm") { - --toolbar-size: auto; - - justify-content: flex-end; - gap: var(--spacing-sm); - width: auto; - background: inherit; - border: none; - box-shadow: none; - position: relative; - left: unset; - margin-right: unset; - transform: unset; - - :global { - animation: slide-in-from-top 1s ease-in-out 0s 1; - } - } - } -} - -.menu { - padding: var(--spacing-md); - position: absolute; - bottom: 100%; - left: 0; - right: 0; - background: var(--color-bg-secondary); - border-top: fun.convert-px(4) solid; - border-bottom: fun.convert-px(4) solid; - border-image: radial-gradient( - ellipse at top, - var(--color-primary-lighter) 20%, - var(--color-primary) 100% - ) - 1; - box-shadow: fun.convert-px(2) fun.convert-px(-2) fun.convert-px(3) - fun.convert-px(-1) var(--color-shadow-dark); - transition: all 0.7s ease-in-out 0s; - - &--closed { - transform: translateX(-100%); - visibility: hidden; - } - - &--opened { - transform: translateX(0); - visibility: visible; - } - - @include mix.media("screen") { - @include mix.dimensions("sm") { - width: fun.convert-px(500); - left: unset; - right: unset; - top: 120%; - bottom: unset; - border: fun.convert-px(4) solid; - border-image: radial-gradient( - ellipse at top, - var(--color-primary-lighter) 20%, - var(--color-primary) 100% - ) - 1; - box-shadow: fun.convert-px(2) fun.convert-px(2) fun.convert-px(3) - fun.convert-px(1) var(--color-shadow-dark); - transform-origin: 50% -200%; - transition: all 0.8s ease-in-out 0s; - - &--closed { - opacity: 0; - transform: perspective(20rem) translate3d(0, 100%, -20rem); - visibility: hidden; - } - - &--opened { - opacity: 1; - transform: none; - } - } - } -} diff --git a/src/components/Toolbar/Toolbar.tsx b/src/components/Toolbar/Toolbar.tsx deleted file mode 100644 index 17f9ef9..0000000 --- a/src/components/Toolbar/Toolbar.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { ButtonToolbar } from '@components/Buttons'; -import MainNav from '@components/MainNav/MainNav'; -import Spinner from '@components/Spinner/Spinner'; -import dynamic from 'next/dynamic'; -import { RefObject, useCallback, useEffect, useRef, useState } from 'react'; -import styles from './Toolbar.module.scss'; - -const DynamicSearchForm = dynamic( - () => import('@components/SearchForm/SearchForm'), - { - loading: () => <Spinner />, - } -); - -const DynamicSettings = dynamic(() => import('@components/Settings/Settings'), { - loading: () => <Spinner />, -}); - -const Toolbar = () => { - const [isNavOpened, setIsNavOpened] = useState<boolean>(false); - const [isSearchOpened, setIsSearchOpened] = useState<boolean>(false); - const [isSettingsOpened, setIsSettingsOpened] = useState<boolean>(false); - const mainNavRef = useRef<HTMLDivElement>(null); - const searchBtnRef = useRef<HTMLButtonElement>(null); - const searchModalRef = useRef<HTMLDivElement>(null); - const settingsBtnRef = useRef<HTMLButtonElement>(null); - const settingsModalRef = useRef<HTMLDivElement>(null); - - useEffect(() => { - if (isNavOpened) { - setIsSearchOpened(false); - setIsSettingsOpened(false); - } - }, [isNavOpened]); - - useEffect(() => { - if (isSearchOpened) { - setIsNavOpened(false); - setIsSettingsOpened(false); - } - }, [isSearchOpened]); - - useEffect(() => { - if (isSettingsOpened) { - setIsNavOpened(false); - setIsSearchOpened(false); - } - }, [isSettingsOpened]); - - const isClickOutside = ( - ref: RefObject<HTMLDivElement>, - target: EventTarget - ) => { - return ref.current && !ref.current.contains(target as Node); - }; - - const isToggleBtn = (ref: RefObject<HTMLDivElement>, target: EventTarget) => { - return ( - ref.current && - ref.current.previousElementSibling && - ref.current.previousElementSibling.contains(target as Node) - ); - }; - - const isSearchBtn = useCallback((target: HTMLElement) => { - return ( - target === searchBtnRef.current || searchBtnRef.current?.contains(target) - ); - }, []); - - const isSettingsBtn = useCallback((target: HTMLElement) => { - return ( - target === settingsBtnRef.current || - settingsBtnRef.current?.contains(target) - ); - }, []); - - const handleVisibility = useCallback( - (e: MouseEvent | FocusEvent) => { - let ref: RefObject<HTMLDivElement> | null = null; - if (isNavOpened) ref = mainNavRef; - if (isSearchOpened) ref = searchModalRef; - if (isSettingsOpened) ref = settingsModalRef; - - if (!ref || !ref.current || !ref.current.id) return; - if (!isClickOutside(ref, e.target as Node)) return; - if (isToggleBtn(ref, e.target as Node)) return; - - if ( - ref.current.id === 'main-nav' && - !isSettingsBtn(e.target as HTMLElement) && - !isSearchBtn(e.target as HTMLElement) - ) { - setIsNavOpened(false); - } - - if ( - ref.current.id === 'search-modal' && - !isSettingsBtn(e.target as HTMLElement) - ) - setIsSearchOpened(false); - if ( - ref.current.id === 'settings-modal' && - !isSearchBtn(e.target as HTMLElement) - ) - setIsSettingsOpened(false); - }, - [isNavOpened, isSearchOpened, isSettingsOpened, isSearchBtn, isSettingsBtn] - ); - - useEffect(() => { - document.addEventListener('mousedown', handleVisibility); - document.addEventListener('focusin', handleVisibility); - - return () => { - document.removeEventListener('mousedown', handleVisibility); - document.removeEventListener('focusin', handleVisibility); - }; - }, [handleVisibility]); - - const searchClasses = `${styles.menu} ${ - isSearchOpened ? styles['menu--opened'] : styles['menu--closed'] - }`; - - const settingsClasses = `${styles.menu} ${ - isSettingsOpened ? styles['menu--opened'] : styles['menu--closed'] - }`; - - return ( - <div className={styles.wrapper}> - <MainNav - ref={mainNavRef} - isOpened={isNavOpened} - setIsOpened={setIsNavOpened} - /> - <ButtonToolbar - ref={searchBtnRef} - type="search" - isActivated={isSearchOpened} - setIsActivated={setIsSearchOpened} - /> - <div id="search-modal" className={searchClasses} ref={searchModalRef}> - <DynamicSearchForm isOpened={isSearchOpened} /> - </div> - <ButtonToolbar - ref={settingsBtnRef} - type="settings" - isActivated={isSettingsOpened} - setIsActivated={setIsSettingsOpened} - /> - <div - id="settings-modal" - className={settingsClasses} - ref={settingsModalRef} - > - <DynamicSettings /> - </div> - </div> - ); -}; - -export default Toolbar; diff --git a/src/components/Tooltip/Tooltip.module.scss b/src/components/Tooltip/Tooltip.module.scss deleted file mode 100644 index 34fa23d..0000000 --- a/src/components/Tooltip/Tooltip.module.scss +++ /dev/null @@ -1,120 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.title { - padding: var(--spacing-2xs) var(--spacing-xs); - position: absolute; - top: calc(var(--spacing-sm) * -1); - left: var(--spacing-lg); - background: var(--color-bg); - border: fun.convert-px(1) solid var(--color-primary-dark); - box-shadow: fun.convert-px(1) fun.convert-px(1) 0 0 var(--color-shadow); - color: var(--color-primary-darker); - font-size: var(--font-size-sm); - font-variant: small-caps; - font-weight: 500; - - @include mix.media("screen") { - @include mix.dimensions(null, "2xs", "height") { - top: 0; - } - - @include mix.dimensions("md") { - left: var(--spacing-md); - } - } - - &::before { - content: "?"; - padding: var(--spacing-2xs); - position: absolute; - top: fun.convert-px(-1); - bottom: fun.convert-px(-1); - right: 100%; - background: var(--color-primary-dark); - border: fun.convert-px(1) solid var(--color-primary-dark); - box-shadow: fun.convert-px(1) fun.convert-px(1) 0 0 var(--color-shadow); - color: var(--color-fg-inverted); - font-weight: 600; - } -} - -.message { - transition: all 0.5s ease-in-out 0; -} - -.wrapper { - padding: 9% 6% var(--spacing-sm) 6%; - position: absolute; - bottom: 30%; - left: fun.convert-px(15); - right: fun.convert-px(15); - background: var(--color-bg); - border: fun.convert-px(2) solid var(--color-primary-dark); - border-radius: fun.convert-px(3); - box-shadow: fun.convert-px(1) fun.convert-px(1) 0 0 var(--color-shadow), - fun.convert-px(2) fun.convert-px(2) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow-light); - transform-origin: bottom; - - @include mix.media("screen") { - @include mix.dimensions(null, "2xs", "height") { - overflow-y: auto; - top: 18%; - } - - @include mix.dimensions("sm") { - bottom: unset; - left: fun.convert-px(15); - right: fun.convert-px(15); - top: 100%; - transform-origin: top; - } - } - - ul, - p { - margin: 0; - padding: 0; - } -} - -.hidden { - visibility: hidden; - opacity: 0; - transition: all 0.5s ease-in-out 0s, opacity 0.3s ease-in-out 0.2s; - transform: scaleY(0); - - .message, - .title { - opacity: 0; - } - - .message { - transition: all 0.3s ease-in-out 0s; - } - - .title { - transition: all 0.2s ease-in-out 0.2s; - } -} - -.visible { - visibility: visible; - opacity: 1; - transform: scaleY(1); - transition: all 0.8s ease-in-out 0s, opacity 0.7s ease-in-out 0.2s; - - .message, - .title { - opacity: 1; - } - - .message { - transition: all 0.5s ease-in-out 0.2s; - } - - .title { - transition: all 0.4s ease-in-out 0s; - } -} diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx deleted file mode 100644 index 56a87ab..0000000 --- a/src/components/Tooltip/Tooltip.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { ButtonHelp } from '@components/Buttons'; -import { useState } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './Tooltip.module.scss'; - -const Tooltip = ({ - message, - title, -}: { - message: string | string[]; - title: string; -}) => { - const intl = useIntl(); - const [isOpen, setIsOpen] = useState<boolean>(false); - - const getMessageFromArray = (strings: string[]) => { - let keyIndex = 0; - return ( - <ul> - {strings.map((string) => { - keyIndex = keyIndex + 1; - return <li key={`message-${keyIndex}`}>{string}</li>; - })} - </ul> - ); - }; - - const buttonTitle = isOpen - ? intl.formatMessage({ - defaultMessage: 'Close help', - description: 'Tooltip: button title', - id: '9kx83j', - }) - : intl.formatMessage({ - defaultMessage: 'Show help', - description: 'Tooltip: button title', - id: 'A5n+C9', - }); - - const wrapperModifier = isOpen ? styles.visible : styles.hidden; - - return ( - <div> - <ButtonHelp - showHelp={isOpen} - setShowHelp={setIsOpen} - title={buttonTitle} - /> - <div className={`${styles.wrapper} ${wrapperModifier}`}> - <div className={styles.title}>{title}</div> - <div className={styles.message}> - {Array.isArray(message) ? getMessageFromArray(message) : message} - </div> - </div> - </div> - ); -}; - -export default Tooltip; diff --git a/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.module.scss b/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.module.scss deleted file mode 100644 index 6a7757d..0000000 --- a/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.module.scss +++ /dev/null @@ -1,146 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; - -.title { - margin: 0; - padding: 0; - background: none; - font-size: var(--font-size-xl); - text-align: left; -} - -.icon { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: center; - width: fun.convert-px(30); - height: fun.convert-px(30); - background: var(--color-bg); - border: fun.convert-px(1) solid var(--color-primary); - border-radius: fun.convert-px(3); - color: var(--color-primary); - font-weight: 800; - transition: all 0.25s ease-in-out 0s; - - &::before, - &::after { - content: ""; - background: var(--color-primary); - transition: all 0.4s ease-out 0s; - } - - &::before { - width: 10%; - height: 60%; - position: relative; - left: 30%; - } - - &::after { - width: 60%; - height: 10%; - position: relative; - left: -5%; - } -} - -.body { - width: 100%; - max-height: 0; - margin: 0 0 fun.convert-px(-6); // collapse borders - overflow: hidden; - visibility: hidden; - transition: all 0.6s cubic-bezier(0, 1, 0, 1) 0s, margin 0.2s ease-in-out 0s, - border 0.1s ease-in-out 0.3s, visibility 0.1s linear 0.6s; - - &--borders { - border: 0 solid transparent; - } - - > *:last-child { - margin-bottom: 0; - } - - @include mix.media("screen") { - @include mix.dimensions("md") { - font-size: var(--font-size-sm); - font-weight: 500; - } - } -} - -.wrapper { - --header-height: #{fun.convert-px(65)}; - - display: flex; - flex-flow: column; - - &--expanded { - .icon::before { - height: 0; - } - - .body { - max-height: 10000px; // needs a fixed value for transition. - margin: var(--spacing-sm) 0; - overflow: visible; - visibility: visible; - transition: visibility 0.1s linear 0s, max-height 0.6s linear 0s, - margin 0.2s ease-in-out 0s; - - &--borders { - border: fun.convert-px(2) solid var(--color-primary-dark); - } - } - } -} - -.wrapper--expanded.wrapper--toc { - @include mix.media("screen") { - @include mix.dimensions("lg") { - max-height: 100vh; - - .body { - overflow-y: auto; - } - } - } -} - -.header { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: space-between; - gap: var(--spacing-md); - width: 100%; - min-height: var(--header-height); - padding: 0; - position: sticky; - top: 0; - z-index: 3; - background: var(--color-bg); - border: none; - border-top: fun.convert-px(2) solid var(--color-primary-dark); - border-bottom: fun.convert-px(2) solid var(--color-primary-dark); - cursor: pointer; - - &:hover, - &:focus { - .icon { - background: var(--color-primary-light); - color: var(--color-fg-inverted); - transform: scale(1.2); - - &::before, - &::after { - background: var(--color-bg); - } - } - } - - > button { - padding: 0 var(--spacing-xs); - } -} diff --git a/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx b/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx deleted file mode 100644 index 38e57ad..0000000 --- a/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { TitleLevel } from '@ts/types/app'; -import { ReactNode, useState } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './ExpandableWidget.module.scss'; - -const ExpandableWidget = ({ - children, - title, - titleLevel = 2, - expand = false, - withBorders = false, - kind = 'regular', -}: { - children: ReactNode; - title: string; - titleLevel?: TitleLevel; - expand?: boolean; - withBorders?: boolean; - kind?: 'regular' | 'toc'; -}) => { - const intl = useIntl(); - const [isExpanded, setIsExpanded] = useState<boolean>(expand); - - const handleExpanse = () => setIsExpanded((prev) => !prev); - - const TitleTag = `h${titleLevel}` as keyof JSX.IntrinsicElements; - - const wrapperKindClass = styles[`wrapper--${kind}`]; - const wrapperClasses = `${styles.wrapper} ${ - isExpanded ? styles['wrapper--expanded'] : '' - } ${wrapperKindClass}`; - - const bodyClasses = `${styles.body} ${ - withBorders ? styles['body--borders'] : '' - }`; - - return ( - <div className={wrapperClasses}> - <button type="button" className={styles.header} onClick={handleExpanse}> - <span className="screen-reader-text"> - {isExpanded - ? intl.formatMessage({ - defaultMessage: 'Collapse', - description: 'ExpandableWidget: collapse text', - id: 'WRkY1/', - }) - : intl.formatMessage({ - defaultMessage: 'Expand', - description: 'ExpandableWidget: expand text', - id: 'hV0qHp', - })} - </span> - <TitleTag className={styles.title}>{title}</TitleTag> - <span className={styles.icon} aria-hidden={true}></span> - </button> - <div className={bodyClasses}>{children}</div> - </div> - ); -}; - -export default ExpandableWidget; diff --git a/src/components/WidgetParts/List/List.module.scss b/src/components/WidgetParts/List/List.module.scss deleted file mode 100644 index 958f792..0000000 --- a/src/components/WidgetParts/List/List.module.scss +++ /dev/null @@ -1,49 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.list { - margin: 0; - padding: 0; - list-style-type: none; - - .list { - border: none; - border-top: fun.convert-px(1) solid var(--color-primary-dark); - } - - li { - margin: 0; - - &:not(:last-of-type) { - border-bottom: fun.convert-px(1) solid var(--color-primary-dark); - } - } - - a { - display: flex; - flex-flow: row nowrap; - width: 100%; - padding: var(--spacing-2xs) var(--spacing-xs); - background: none; - text-decoration: underline solid transparent 0; - transition: all 0.2s ease-in-out 0s, font-weight 0s, - text-decoration-color 0s; - - &:hover, - &:focus { - background: var(--color-bg-secondary); - font-weight: 600; - } - - &:focus { - color: var(--color-primary); - text-decoration-color: var(--color-primary-light); - text-decoration-thickness: 0.25ex; - } - - &:active { - background: var(--color-bg-tertiary); - text-decoration-color: transparent; - text-decoration-thickness: 0; - } - } -} diff --git a/src/components/WidgetParts/List/List.tsx b/src/components/WidgetParts/List/List.tsx deleted file mode 100644 index 317c4d1..0000000 --- a/src/components/WidgetParts/List/List.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import styles from './List.module.scss'; - -const List = ({ items }: { items: Array<any> }) => { - return <ul className={styles.list}>{items}</ul>; -}; - -export default List; diff --git a/src/components/WidgetParts/OrderedList/OrderedList.module.scss b/src/components/WidgetParts/OrderedList/OrderedList.module.scss deleted file mode 100644 index a286932..0000000 --- a/src/components/WidgetParts/OrderedList/OrderedList.module.scss +++ /dev/null @@ -1,66 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/placeholders"; - -.list { - @extend %reset-ordered-list; - counter-reset: link; - - .list { - border-top: fun.convert-px(1) solid var(--color-primary-dark); - } - - a { - display: flex; - flex-flow: row nowrap; - width: 100%; - padding: var(--spacing-2xs) var(--spacing-xs); - background: none; - text-decoration: underline solid transparent 0; - transition: all 0.16s ease-in-out 0s, text-decoration-color 0s; - counter-increment: link; - - &:hover, - &:focus { - background: var(--color-bg-secondary); - } - - &:focus { - color: var(--color-primary); - text-decoration-color: var(--color-primary-light); - text-decoration-thickness: 0.25ex; - } - - &:active { - background: var(--color-bg-tertiary); - text-decoration-color: transparent; - text-decoration-thickness: 0; - } - - &::before { - content: counters(link, ".") ". "; - color: var(--color-secondary); - padding-right: var(--spacing-2xs); - } - } - - li { - width: 100%; - margin: 0; - - &:not(:last-of-type) { - border-bottom: fun.convert-px(1) solid var(--color-primary-dark); - } - - &::before { - display: none; - } - } - - li li a::before { - padding-left: var(--spacing-sm); - } - - li li li a::before { - padding-left: var(--spacing-lg); - } -} diff --git a/src/components/WidgetParts/OrderedList/OrderedList.tsx b/src/components/WidgetParts/OrderedList/OrderedList.tsx deleted file mode 100644 index a12ec06..0000000 --- a/src/components/WidgetParts/OrderedList/OrderedList.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import styles from './OrderedList.module.scss'; - -const OrderedList = ({ items }: { items: Array<any> }) => { - return <ol className={styles.list}>{items}</ol>; -}; - -export default OrderedList; diff --git a/src/components/WidgetParts/index.tsx b/src/components/WidgetParts/index.tsx deleted file mode 100644 index 59df3bd..0000000 --- a/src/components/WidgetParts/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import ExpandableWidget from './ExpandableWidget/ExpandableWidget'; -import List from './List/List'; -import OrderedList from './OrderedList/OrderedList'; - -export { ExpandableWidget, List, OrderedList }; diff --git a/src/components/Widgets/CVPreview/CVPreview.module.scss b/src/components/Widgets/CVPreview/CVPreview.module.scss deleted file mode 100644 index 6ddd696..0000000 --- a/src/components/Widgets/CVPreview/CVPreview.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.preview { - position: relative; - width: 100%; - height: 20rem; - margin-bottom: var(--spacing-sm); -} diff --git a/src/components/Widgets/CVPreview/CVPreview.tsx b/src/components/Widgets/CVPreview/CVPreview.tsx deleted file mode 100644 index cf6a8fa..0000000 --- a/src/components/Widgets/CVPreview/CVPreview.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { ExpandableWidget } from '@components/WidgetParts'; -import Image from 'next/image'; -import Link from 'next/link'; -import { FormattedMessage } from 'react-intl'; -import styles from './CVPreview.module.scss'; - -const CVPreview = ({ - title, - imgSrc, - pdf, -}: { - title: string; - imgSrc: string; - pdf: string; -}) => { - return ( - <ExpandableWidget title={title} expand={true}> - <div className={styles.preview}> - <Image - src={imgSrc} - layout="fill" - objectFit="contain" - objectPosition="left" - alt="CV Armand Philippot" - /> - </div> - <p> - <FormattedMessage - defaultMessage="Download <link>CV in PDF</link>" - description="CVPreview: download as PDF link" - id="xC3Khf" - values={{ - link: (chunks: string) => ( - <Link href={pdf}> - <a>{chunks}</a> - </Link> - ), - }} - /> - </p> - </ExpandableWidget> - ); -}; - -export default CVPreview; diff --git a/src/components/Widgets/RecentPosts/RecentPosts.module.scss b/src/components/Widgets/RecentPosts/RecentPosts.module.scss deleted file mode 100644 index 1b85265..0000000 --- a/src/components/Widgets/RecentPosts/RecentPosts.module.scss +++ /dev/null @@ -1,109 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/placeholders"; - -.list { - --items: 3; - --items-size: 25ch; - - @extend %reset-list; - - display: grid; - grid-template-columns: repeat( - auto-fit, - min(calc(100vw - (var(--spacing-md) * 2)), var(--items-size)) - ); - justify-content: center; - gap: var(--spacing-sm); - width: min( - calc(100vw - (var(--spacing-md) * 2)), - calc( - (var(--items-size) * var(--items)) + - (var(--spacing-sm) * (var(--items) - 1)) - ) - ); - margin-bottom: var(--spacing-md); -} - -.item { - text-align: center; -} - -.article { - display: flex; - flex-flow: column nowrap; - height: 100%; - padding: 0 0 var(--spacing-md); -} - -.title { - flex: 1; - margin: var(--spacing-sm) 0; - padding: 0 var(--spacing-md); - text-decoration: underline solid transparent 0; - transition: all 0.3s linear 0s; -} - -.link { - display: block; - height: 100%; - background: var(--color-bg); - color: inherit; - text-decoration: none; - border: fun.convert-px(3) solid var(--color-primary); - border-radius: fun.convert-px(5); - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) - var(--color-shadow), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) - var(--color-shadow); - transition: all 0.3s ease-in-out 0s; - - &:hover, - &:focus, - &:active { - color: inherit; - } - - &:hover, - &:focus { - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow-light), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) - var(--color-shadow-light), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) - var(--color-shadow-light), - fun.convert-px(7) fun.convert-px(10) fun.convert-px(12) fun.convert-px(-3) - var(--color-shadow-light); - transform: scale(1.05); - } - - &:focus { - .title { - text-decoration: underline solid var(--color-primary) 0.3ex; - } - } - - &:active { - box-shadow: 0 0 0 0 var(--color-shadow); - transform: scale(0.95); - - .title { - text-decoration: none; - } - } -} - -.cover { - width: 100%; - height: clamp(fun.convert-px(100), 20vw, fun.convert-px(150)); - position: relative; - border: fun.convert-px(1) solid var(--color-border); -} - -.meta { - display: block; - margin: 0; - padding: 0 var(--spacing-md); - font-size: var(--font-size-sm); -} diff --git a/src/components/Widgets/RecentPosts/RecentPosts.tsx b/src/components/Widgets/RecentPosts/RecentPosts.tsx deleted file mode 100644 index 11d8558..0000000 --- a/src/components/Widgets/RecentPosts/RecentPosts.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import Spinner from '@components/Spinner/Spinner'; -import { getPublishedPosts } from '@services/graphql/queries'; -import { ArticlePreview } from '@ts/types/articles'; -import { PostsList } from '@ts/types/blog'; -import { settings } from '@utils/config'; -import { getFormattedDate } from '@utils/helpers/format'; -import Image from 'next/image'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; -import useSWR from 'swr'; -import styles from './RecentPosts.module.scss'; - -const RecentPosts = ({ posts }: { posts: PostsList }) => { - const intl = useIntl(); - const { data, error } = useSWR<PostsList>( - '/recent-posts', - () => getPublishedPosts({ first: 3 }), - { fallbackData: posts } - ); - const router = useRouter(); - const locale = router.locale ? router.locale : settings.locales.defaultLocale; - - const getPost = (post: ArticlePreview) => { - return ( - <li key={post.id} className={styles.item}> - <Link href={`/article/${post.slug}`}> - <a className={styles.link}> - <article className={styles.article}> - {post.featuredImage && - Object.keys(post.featuredImage).length > 0 && ( - <div className={styles.cover}> - <Image - src={post.featuredImage.sourceUrl} - alt={post.featuredImage.altText} - layout="fill" - objectFit="contain" - /> - </div> - )} - <h3 className={styles.title}>{post.title}</h3> - <dl className={styles.meta}> - <dt> - {intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'RecentPosts: publication date label', - id: '1h+N2z', - })} - </dt> - <dd> - <time dateTime={post.dates.publication}> - {getFormattedDate(post.dates.publication, locale)} - </time> - </dd> - </dl> - </article> - </a> - </Link> - </li> - ); - }; - - const getPostsItems = () => { - if (error) - return intl.formatMessage({ - defaultMessage: 'Failed to load.', - description: 'RecentPosts: failed to load text', - id: 'iyEh0R', - }); - if (!data) return <Spinner />; - - return data.posts.map((post) => getPost(post)); - }; - - return <ul className={styles.list}>{getPostsItems()}</ul>; -}; - -export default RecentPosts; diff --git a/src/components/Widgets/RelatedThematics/RelatedThematics.tsx b/src/components/Widgets/RelatedThematics/RelatedThematics.tsx deleted file mode 100644 index a66de82..0000000 --- a/src/components/Widgets/RelatedThematics/RelatedThematics.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { ExpandableWidget, List } from '@components/WidgetParts'; -import { ThematicPreview } from '@ts/types/taxonomies'; -import Link from 'next/link'; -import { useIntl } from 'react-intl'; - -const RelatedThematics = ({ thematics }: { thematics: ThematicPreview[] }) => { - const intl = useIntl(); - const sortedThematics = [...thematics].sort((a, b) => - a.title.localeCompare(b.title) - ); - - const thematicsList = sortedThematics.map((thematic) => { - return ( - <li key={thematic.databaseId}> - <Link href={`/thematique/${thematic.slug}`}> - <a>{thematic.title}</a> - </Link> - </li> - ); - }); - - return ( - <ExpandableWidget - expand={true} - title={intl.formatMessage( - { - defaultMessage: - '{thematicsCount, plural, =0 {Related thematics} one {Related thematic} other {Related thematics}}', - description: 'RelatedThematics: widget title', - id: 'qXQETZ', - }, - { thematicsCount: thematics.length } - )} - withBorders={true} - > - <List items={thematicsList} /> - </ExpandableWidget> - ); -}; - -export default RelatedThematics; diff --git a/src/components/Widgets/RelatedTopics/RelatedTopics.tsx b/src/components/Widgets/RelatedTopics/RelatedTopics.tsx deleted file mode 100644 index 992173d..0000000 --- a/src/components/Widgets/RelatedTopics/RelatedTopics.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { ExpandableWidget, List } from '@components/WidgetParts'; -import { TopicPreview } from '@ts/types/taxonomies'; -import Link from 'next/link'; -import { useIntl } from 'react-intl'; - -const RelatedTopics = ({ topics }: { topics: TopicPreview[] }) => { - const intl = useIntl(); - const sortedTopics = [...topics].sort((a, b) => - a.title.localeCompare(b.title) - ); - - const topicsList = sortedTopics.map((topic) => { - return ( - <li key={topic.databaseId}> - <Link href={`/sujet/${topic.slug}`}> - <a>{topic.title}</a> - </Link> - </li> - ); - }); - - return ( - <ExpandableWidget - expand={true} - title={intl.formatMessage( - { - defaultMessage: - '{topicsCount, plural, =0 {Related topics} one {Related topic} other {Related topics}}', - description: 'RelatedTopics: widget title', - id: 'w/lPUh', - }, - { topicsCount: topicsList.length } - )} - withBorders={true} - > - <List items={topicsList} /> - </ExpandableWidget> - ); -}; - -export default RelatedTopics; diff --git a/src/components/Widgets/Sharing/Sharing.module.scss b/src/components/Widgets/Sharing/Sharing.module.scss deleted file mode 100644 index ada3e2f..0000000 --- a/src/components/Widgets/Sharing/Sharing.module.scss +++ /dev/null @@ -1,193 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.list { - @extend %flex-list; - - gap: var(--spacing-sm); - padding: var(--spacing-2xs) 0 0 var(--spacing-2xs); - - @include mix.media("screen") { - @include mix.dimensions("md") { - gap: var(--spacing-xs); - width: min-content; - } - } -} - -.link { - display: flex; - flex-flow: row nowrap; - align-items: center; - padding: var(--spacing-2xs) var(--spacing-xs); - border-radius: fun.convert-px(3); - font-weight: 600; - text-decoration: none; - transition: all 0.3s ease-in-out 0s; - - &:hover, - &:focus { - color: hsl(0, 0%, 100%); - transform: translateX(#{fun.convert-px(-3)}) - translateY(#{fun.convert-px(-3)}); - } - - &:active { - color: hsl(0, 0%, 100%); - transform: translateX(#{fun.convert-px(2)}) translateY(#{fun.convert-px(2)}); - - @include mix.motion("reduce") { - transform: none; - } - } - - &::before { - display: block; - background-repeat: no-repeat; - content: ""; - filter: drop-shadow( - #{fun.convert-px(1)} #{fun.convert-px(1)} #{fun.convert-px(1)} hsl(0, 0%, 0%) - ); - width: fun.convert-px(30); - height: fun.convert-px(30); - } - - &--diaspora { - background: hsl(0, 0%, 13%); - box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(0, 0%, 3%); - - &:hover, - &:focus { - box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 hsl(0, 0%, 3%); - } - - &:active { - box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 hsl(0, 0%, 3%); - } - - &::before { - background-image: url(fun.encode-svg( - '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path style="fill:#ffffff;" d="M15.257 21.928l-2.33-3.255c-.622-.87-1.128-1.549-1.155-1.55-.027 0-1.007 1.317-2.317 3.115-1.248 1.713-2.28 3.115-2.292 3.115-.035 0-4.5-3.145-4.51-3.178-.006-.016 1.003-1.497 2.242-3.292 1.239-1.794 2.252-3.29 2.252-3.325 0-.056-.401-.197-3.55-1.247a1604.93 1604.93 0 01-3.593-1.2c-.033-.013.153-.635.79-2.648.46-1.446.845-2.642.857-2.656.013-.015 1.71.528 3.772 1.207 2.062.678 3.766 1.233 3.787 1.233.021 0 .045-.032.053-.07.008-.039.026-1.794.04-3.902.013-2.107.036-3.848.05-3.87.02-.03.599-.038 2.725-.038 1.485 0 2.716.01 2.735.023.023.016.064 1.175.132 3.776.112 4.273.115 4.33.183 4.33.026 0 1.66-.547 3.631-1.216 1.97-.668 3.593-1.204 3.605-1.191.04.045 1.656 5.307 1.636 5.327-.011.01-1.656.574-3.655 1.252-2.75.932-3.638 1.244-3.645 1.284-.006.029.94 1.442 2.143 3.202 1.184 1.733 2.148 3.164 2.143 3.18-.012.036-4.442 3.299-4.48 3.299-.015 0-.577-.767-1.249-1.705z"/></svg>' - )); - } - } - - &--email { - background: hsl(0, 0%, 44%); - box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(0, 0%, 34%); - - &:hover, - &:focus { - box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 hsl(0, 0%, 34%); - } - - &:active { - box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 hsl(0, 0%, 34%); - } - - &::before { - background-image: url(fun.encode-svg( - '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill:#ffffff;" d="M15.909 12.123L24 17.238V6.792zM0 6.792v10.446l8.091-5.115zM22.5 3.75h-21c-.748 0-1.343.558-1.455 1.276L12 12.904l11.955-7.877c-.112-.718-.706-1.276-1.455-1.276zm-7.965 9.279l-2.123 1.398a.75.75 0 01-.825 0l-2.122-1.4-9.417 5.957c.116.712.707 1.266 1.452 1.266h21c.746 0 1.337-.553 1.452-1.266z"/></svg>' - )); - } - } - - &--facebook { - background: hsl(214, 89%, 52%); - box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(214, 89%, 42%); - - &:hover, - &:focus { - box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 - hsl(214, 89%, 42%); - } - - &:active { - box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 - hsl(214, 89%, 42%); - } - - &::before { - background-image: url(fun.encode-svg( - '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill:#ffffff;" d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>' - )); - } - } - - &--journal-du-hacker { - background: hsl(210, 24%, 51%); - box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(210, 24%, 41%); - - &:hover, - &:focus { - box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 - hsl(210, 24%, 41%); - } - - &:active { - box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 - hsl(210, 24%, 41%); - } - - &::before { - background-image: url(fun.encode-svg( - '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill:#ffffff;" d="M17.822 23.297a6.644 6.644 0 00-.654.032c-1.104.1-2.451-.378-3.244-1.15a3.223 3.223 0 01-.52-.739c-.209-.425-.22-.489-.211-1.178a8.174 8.174 0 01.19-1.585c.243-1.151.155-1.449-.514-1.737-.4-.172-.632-.135-1 .16-.268.215-.28.463-.07 1.532.298 1.526.286 2.238-.05 2.907-.28.56-.443.703-1.287 1.133-1.005.513-1.461.638-2.332.638-.73 0-1.014-.082-1.276-.366-.134-.145-.148-.2-.085-.32.099-.184.329-.3.959-.488.277-.082.604-.236.727-.341.123-.105.329-.265.457-.354.32-.222.562-.761.563-1.254 0-.331-.188-1.034-.45-1.676-.138-.338-.38.085-.38.666 0 .434-.673 1.569-.93 1.569-.048 0-.288.101-.532.225-.43.219-.47.225-1.31.225-.815 0-.889-.011-1.235-.194-.42-.22-.902-.694-1.094-1.073a2.752 2.752 0 00-.227-.377c-.083-.102-.08-.143.018-.293.206-.314.473-.317 1.186-.011.583.25 1.22.215 1.582-.086.168-.139.325-.697.342-1.217.02-.598-.049-.66-.596-.528-.86.206-1.762-.084-2.76-.887-.916-.739-1.362-.845-2.241-.538-.262.092-.51.153-.552.137-.042-.016-.134-.136-.204-.268-.118-.218-.12-.252-.02-.403.156-.24.714-.573 1.185-.708.297-.086.588-.11 1.076-.09.655.026.687.035 1.567.458.54.259.99.43 1.127.43.27 0 1.014-.37 1.159-.577.167-.238.124-.34-.322-.776-1.19-1.16-1.943-2.608-2.24-4.31-.124-.702-.14-1.888-.035-2.483.116-.656.677-2.273.915-2.64.385-.59 1.823-1.965 2.585-2.469C9.187.905 11.43.395 13.715.785c2.457.42 4.507 1.61 5.849 3.394 1.062 1.414 1.554 2.859 1.553 4.57 0 1.778-.497 3.238-1.599 4.693a6.207 6.207 0 00-.34.476c0 .013.205.12.456.238.737.345 1.169.844 1.726 1.994.256.527.531 1.031.613 1.12.225.247.614.42 1.099.49.588.085.804.178.9.388.109.24-.111.55-.402.563-.11.005-.394.033-.63.062-.887.107-1.851-.251-2.416-.898-.17-.193-.503-.616-.74-.939-.455-.616-.818-.922-1.054-.888-.117.017-.14.066-.127.28.008.142.068.34.133.438.09.137.127.412.161 1.196.05 1.153.147 1.458.55 1.726.306.204.552.198 1.11-.025.581-.233.923-.238 1.159-.018.243.227.2.637-.11 1.026-.33.419-1.338.899-2.001.954-1.194.1-2.371-.602-2.828-1.686-.062-.147-.197-.61-.301-1.03-.12-.486-.221-.762-.28-.762-.109 0-.263.401-.27.705-.003.12-.056.417-.118.657-.328 1.282.307 2.309 1.66 2.684.657.182.808.299.808.623 0 .319-.165.494-.454.481z"/></svg>' - )); - } - } - - &--linkedin { - background: hsl(210, 90%, 40%); - box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(210, 90%, 30%); - - &:hover, - &:focus { - box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 - hsl(210, 90%, 30%); - } - - &:active { - box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 - hsl(210, 90%, 30%); - } - - &::before { - background-image: url(fun.encode-svg( - '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path style="fill:#ffffff;" d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>' - )); - } - } - - &--twitter { - background: hsl(203, 89%, 53%); - box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(203, 89%, 43%); - - &:hover, - &:focus { - box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 - hsl(203, 89%, 43%); - } - - &:active { - box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 - hsl(203, 89%, 43%); - } - - &::before { - background-image: url(fun.encode-svg( - '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill:#ffffff;" d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>' - )); - } - } -} - -:global { - [data-theme="dark"] { - :local { - .link { - filter: brightness(0.85) contrast(1.1); - } - } - } -} diff --git a/src/components/Widgets/Sharing/Sharing.tsx b/src/components/Widgets/Sharing/Sharing.tsx deleted file mode 100644 index 45fe3ce..0000000 --- a/src/components/Widgets/Sharing/Sharing.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { ExpandableWidget } from '@components/WidgetParts'; -import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; -import styles from './Sharing.module.scss'; - -type Parameters = { - content: string; - image: string; - title: string; - url: string; -}; - -type Website = { - id: string; - name: string; - parameters: Parameters; - url: string; -}; - -const Sharing = ({ excerpt, title }: { excerpt: string; title: string }) => { - const intl = useIntl(); - const [pageExcerpt, setPageExcerpt] = useState(''); - const [pageUrl, setPageUrl] = useState(''); - const [domainName, setDomainName] = useState(''); - const router = useRouter(); - - useEffect(() => { - const divEl = document.createElement('div'); - divEl.innerHTML = excerpt; - const cleanExcerpt = divEl.textContent!; - setPageExcerpt(cleanExcerpt); - }, [excerpt]); - - useEffect(() => { - const { protocol, hostname, port } = window.location; - const currentPort = port ? `:${port}` : ''; - const fullUrl = `${protocol}//${hostname}${currentPort}${router.asPath}`; - - setDomainName(hostname); - setPageUrl(fullUrl); - }, [router.asPath]); - - const getSharingUrl = (website: Website): string => { - const { id, parameters, url } = website; - let sharingUrl = `${url}?`; - let count = 0; - - for (const [key, value] of Object.entries(parameters)) { - if (!value) continue; - - sharingUrl += count > 0 ? `&${value}=` : `${value}=`; - - switch (key) { - case 'content': - if (id === 'email') { - const intro = intl.formatMessage({ - defaultMessage: 'Introduction:', - description: 'Sharing: email content prefix', - id: 'yfgMcl', - }); - const readMore = intl.formatMessage({ - defaultMessage: 'Read more here:', - description: 'Sharing: content link prefix', - id: 'UsQske', - }); - const body = `${intro}\n\n"${pageExcerpt}"\n\n${readMore} ${pageUrl}`; - sharingUrl += encodeURI(body); - } else { - sharingUrl += encodeURI(pageExcerpt); - } - break; - case 'title': - const prefix = - id === 'email' - ? intl.formatMessage( - { - defaultMessage: 'Seen on {domainName}:', - description: 'Sharing: seen on text', - id: 'eUXMG4', - }, - { domainName } - ) - : ''; - sharingUrl += encodeURI(`${prefix} ${title}`); - break; - case 'url': - sharingUrl += encodeURI(pageUrl); - break; - default: - break; - } - - count++; - } - - return sharingUrl; - }; - - const websites = [ - { - id: 'diaspora', - name: intl.formatMessage({ - defaultMessage: 'Diaspora', - description: 'Sharing: Diaspora', - id: 'Dhow1m', - }), - parameters: { - content: '', - image: '', - title: 'title', - url: 'url', - }, - url: 'https://share.diasporafoundation.org/', - }, - { - id: 'facebook', - name: intl.formatMessage({ - defaultMessage: 'Facebook', - description: 'Sharing: Facebook', - id: '7iiaRx', - }), - parameters: { - content: '', - image: '', - title: '', - url: 'u', - }, - url: 'https://www.facebook.com/sharer/sharer.php', - }, - { - id: 'linkedin', - name: intl.formatMessage({ - defaultMessage: 'LinkedIn', - description: 'Sharing: LinkedIn', - id: 'csCQQk', - }), - parameters: { - content: '', - image: '', - title: '', - url: 'url', - }, - url: 'https://www.linkedin.com/sharing/share-offsite/', - }, - { - id: 'twitter', - name: intl.formatMessage({ - defaultMessage: 'Twitter', - description: 'Sharing: Twitter', - id: 'WjVBnY', - }), - parameters: { - content: '', - image: '', - title: 'text', - url: 'url', - }, - url: 'https://twitter.com/intent/tweet', - }, - { - id: 'journal-du-hacker', - name: intl.formatMessage({ - defaultMessage: 'Journal du hacker', - description: 'Sharing: Journal du hacker', - id: 'P0I+Xm', - }), - parameters: { - content: '', - image: '', - title: 'title', - url: 'url', - }, - url: 'https://www.journalduhacker.net/stories/new', - }, - { - id: 'email', - name: intl.formatMessage({ - defaultMessage: 'Email', - description: 'Sharing: Email', - id: 'lKZm9t', - }), - parameters: { - content: 'body', - image: '', - title: 'subject', - url: '', - }, - url: 'mailto:', - }, - ]; - - const getItems = () => { - return websites.map((website) => { - const { id, name } = website; - const sharingUrl = getSharingUrl(website); - const linkModifier = `link--${id}`; - - return ( - <li key={id}> - <a - href={sharingUrl} - title={name} - className={`${styles.link} ${styles[linkModifier]}`} - > - <span className="screen-reader-text"> - {intl.formatMessage( - { - defaultMessage: 'Share on {name}', - description: 'Sharing: share on social network text', - id: 'ureXFw', - }, - { name } - )} - </span> - </a> - </li> - ); - }); - }; - - return ( - <ExpandableWidget - title={intl.formatMessage({ - defaultMessage: 'Share', - description: 'Sharing: widget title', - id: 'q3U6uI', - })} - expand={true} - > - <ul className={`${styles.list} ${styles['list--sharing']}`}> - {getItems()} - </ul> - </ExpandableWidget> - ); -}; - -export default Sharing; diff --git a/src/components/Widgets/SocialMedia/SocialMedia.module.scss b/src/components/Widgets/SocialMedia/SocialMedia.module.scss deleted file mode 100644 index 2ef34bf..0000000 --- a/src/components/Widgets/SocialMedia/SocialMedia.module.scss +++ /dev/null @@ -1,59 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/placeholders"; - -.list { - @extend %flex-list; - - flex: 0 0 100%; - gap: var(--spacing-xs); - align-items: center; - padding: var(--spacing-2xs) 0 0 var(--spacing-2xs); -} - -.link { - display: block; - width: 3em; - height: 3em; - background: none; - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-1) - var(--color-shadow), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(4) fun.convert-px(-3) - var(--color-shadow), - 0 0 0 0 var(--color-shadow); - transition: all 0.3s linear 0s; - - &:hover, - &:focus { - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - fun.convert-px(1) fun.convert-px(1) fun.convert-px(2) fun.convert-px(-1) - var(--color-shadow-light), - fun.convert-px(3) fun.convert-px(3) fun.convert-px(4) fun.convert-px(-4) - var(--color-shadow-light), - fun.convert-px(6) fun.convert-px(6) fun.convert-px(10) fun.convert-px(-3) - var(--color-shadow); - transform: scale(1.15); - } - - &:focus { - outline: var(--color-primary) dashed fun.convert-px(2); - } - - &:active { - box-shadow: 0 0 0 0 var(--color-shadow); - outline: none; - transform: scale(0.9); - } -} - -:global { - [data-theme="dark"] { - :local { - .icon { - filter: brightness(0.85) contrast(1.1); - } - } - } -} diff --git a/src/components/Widgets/SocialMedia/SocialMedia.tsx b/src/components/Widgets/SocialMedia/SocialMedia.tsx deleted file mode 100644 index decf657..0000000 --- a/src/components/Widgets/SocialMedia/SocialMedia.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import GithubIcon from '@assets/images/social-media/github.svg'; -import GitlabIcon from '@assets/images/social-media/gitlab.svg'; -import LinkedInIcon from '@assets/images/social-media/linkedin.svg'; -import TwitterIcon from '@assets/images/social-media/twitter.svg'; -import styles from './SocialMedia.module.scss'; -import { ExpandableWidget } from '@components/WidgetParts'; -import { useIntl } from 'react-intl'; - -const SocialMedia = ({ - title, - github = false, - gitlab = false, - linkedin = false, - twitter = false, -}: { - title: string; - github?: boolean; - gitlab?: boolean; - linkedin?: boolean; - twitter?: boolean; -}) => { - const intl = useIntl(); - - const websites = [ - { - id: 'github', - name: intl.formatMessage({ - defaultMessage: 'Github', - description: 'SocialMedia: Github', - id: 'SWjj4l', - }), - url: 'https://github.com/ArmandPhilippot', - }, - { - id: 'gitlab', - name: intl.formatMessage({ - defaultMessage: 'Gitlab', - description: 'SocialMedia: Gitlab', - id: 'obmlFh', - }), - url: 'https://gitlab.com/ArmandPhilippot', - }, - { - id: 'linkedin', - name: intl.formatMessage({ - defaultMessage: 'LinkedIn', - description: 'SocialMedia: LinkedIn', - id: 'VbcHZ4', - }), - url: 'https://www.linkedin.com/in/armandphilippot', - }, - { - id: 'twitter', - name: intl.formatMessage({ - defaultMessage: 'Twitter', - description: 'SocialMedia: Twitter', - id: 'IPs/Ck', - }), - url: 'https://twitter.com/ArmandPhilippot', - }, - ]; - - const getIcon = (id: string) => { - switch (id) { - case 'github': - return <GithubIcon className={styles.icon} />; - case 'gitlab': - return <GitlabIcon className={styles.icon} />; - case 'linkedin': - return <LinkedInIcon className={styles.icon} />; - case 'twitter': - return <TwitterIcon className={styles.icon} />; - default: - break; - } - }; - - const shouldDisplayLink = (id: string) => { - switch (id) { - case 'github': - return github; - case 'gitlab': - return gitlab; - case 'linkedin': - return linkedin; - case 'twitter': - return twitter; - default: - break; - } - }; - - const items = websites.map((website) => { - return shouldDisplayLink(website.id) ? ( - <li key={website.id}> - <a href={website.url} className={styles.link}> - {getIcon(website.id)} - <span className="screen-reader-text">{website.name}</span> - </a> - </li> - ) : ( - '' - ); - }); - - return ( - <ExpandableWidget title={title} expand={true}> - <ul className={styles.list}>{items}</ul> - </ExpandableWidget> - ); -}; - -export default SocialMedia; diff --git a/src/components/Widgets/ThematicsList/ThematicsList.tsx b/src/components/Widgets/ThematicsList/ThematicsList.tsx deleted file mode 100644 index 51254ee..0000000 --- a/src/components/Widgets/ThematicsList/ThematicsList.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import Spinner from '@components/Spinner/Spinner'; -import { ExpandableWidget, List } from '@components/WidgetParts'; -import { getAllThematics } from '@services/graphql/queries'; -import { TitleLevel } from '@ts/types/app'; -import { ThematicPreview } from '@ts/types/taxonomies'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; -import useSWR from 'swr'; - -const ThematicsList = ({ - title, - titleLevel, - initialData, -}: { - title: string; - titleLevel?: TitleLevel; - initialData?: ThematicPreview[]; -}) => { - const intl = useIntl(); - const router = useRouter(); - const isThematic = () => router.asPath.includes('/thematique/'); - const currentThematicSlug = isThematic() - ? router.asPath.replace('/thematique/', '') - : ''; - - const { data, error } = useSWR('/api/thematics', getAllThematics, { - fallbackData: initialData, - }); - - const getList = () => { - if (error) - return ( - <ul> - {intl.formatMessage({ - defaultMessage: 'Failed to load.', - description: 'ThematicsList: failed to load text', - id: 'PxMDzL', - })} - </ul> - ); - if (!data) - return ( - <ul> - <Spinner /> - </ul> - ); - - const thematics = data.map((thematic) => { - return currentThematicSlug !== thematic.slug ? ( - <li key={thematic.databaseId}> - <Link href={`/thematique/${thematic.slug}`}> - <a>{thematic.title}</a> - </Link> - </li> - ) : ( - '' - ); - }); - - return <List items={thematics} />; - }; - - return ( - <ExpandableWidget - title={title} - titleLevel={titleLevel} - withBorders={true} - expand={true} - > - {getList()} - </ExpandableWidget> - ); -}; - -export default ThematicsList; diff --git a/src/components/Widgets/ToC/ToC.tsx b/src/components/Widgets/ToC/ToC.tsx deleted file mode 100644 index 3f759db..0000000 --- a/src/components/Widgets/ToC/ToC.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { ExpandableWidget, OrderedList } from '@components/WidgetParts'; -import { Heading } from '@ts/types/app'; -import useHeadingsTree from '@utils/hooks/useHeadingsTree'; -import { FormattedMessage, useIntl } from 'react-intl'; - -const ToC = () => { - const intl = useIntl(); - const headingsTree = useHeadingsTree('article'); - const title = intl.formatMessage({ - defaultMessage: 'Table of contents', - description: 'ToC: widget title', - id: 'Zg4L7U', - }); - - const getItems = (headings: Heading[]) => { - return headings.map((heading) => { - return ( - <li key={heading.id}> - <a href={`#${heading.id}`}> - <FormattedMessage - defaultMessage="<a11y>Jump to </a11y>{title}" - description="ToC: link" - id="GgIWnN" - values={{ - title: heading.title, - a11y: (chunks: string) => ( - <span className="screen-reader-text">{chunks}</span> - ), - }} - /> - </a> - {heading.children.length > 0 && ( - <OrderedList items={getItems(heading.children)} /> - )} - </li> - ); - }); - }; - - return ( - <ExpandableWidget title={title} kind="toc" expand={true} withBorders={true}> - <noscript> - {intl.formatMessage({ - defaultMessage: - 'Javascript is required to use the table of contents.', - description: 'ToC: noscript tag', - id: 'RZzx/4', - })} - </noscript> - <OrderedList items={getItems(headingsTree)} /> - </ExpandableWidget> - ); -}; - -export default ToC; diff --git a/src/components/Widgets/TopicsList/TopicsList.tsx b/src/components/Widgets/TopicsList/TopicsList.tsx deleted file mode 100644 index 7bc7d70..0000000 --- a/src/components/Widgets/TopicsList/TopicsList.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import Spinner from '@components/Spinner/Spinner'; -import { ExpandableWidget, List } from '@components/WidgetParts'; -import { getAllTopics } from '@services/graphql/queries'; -import { TitleLevel } from '@ts/types/app'; -import { TopicPreview } from '@ts/types/taxonomies'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; -import useSWR from 'swr'; - -const TopicsList = ({ - title, - titleLevel, - initialData, -}: { - title: string; - titleLevel?: TitleLevel; - initialData?: TopicPreview[]; -}) => { - const intl = useIntl(); - const router = useRouter(); - const isTopic = () => router.asPath.includes('/sujet/'); - const currentTopicSlug = isTopic() - ? router.asPath.replace('/sujet/', '') - : ''; - - const { data, error } = useSWR('/api/topics', getAllTopics, { - fallbackData: initialData, - }); - - const getList = () => { - if (error) - return ( - <ul> - {intl.formatMessage({ - defaultMessage: 'Failed to load.', - description: 'TopicsList: failed to load text', - id: '00Pf5p', - })} - </ul> - ); - if (!data) - return ( - <ul> - <Spinner /> - </ul> - ); - - const topics = data.map((topic) => { - return currentTopicSlug !== topic.slug ? ( - <li key={topic.databaseId}> - <Link href={`/sujet/${topic.slug}`}> - <a>{topic.title}</a> - </Link> - </li> - ) : ( - '' - ); - }); - - return <List items={topics} />; - }; - - return ( - <ExpandableWidget - title={title} - titleLevel={titleLevel} - withBorders={true} - expand={true} - > - {getList()} - </ExpandableWidget> - ); -}; - -export default TopicsList; diff --git a/src/components/Widgets/index.tsx b/src/components/Widgets/index.tsx deleted file mode 100644 index 8354449..0000000 --- a/src/components/Widgets/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import CVPreview from './CVPreview/CVPreview'; -import RecentPosts from './RecentPosts/RecentPosts'; -import RelatedThematics from './RelatedThematics/RelatedThematics'; -import RelatedTopics from './RelatedTopics/RelatedTopics'; -import Sharing from './Sharing/Sharing'; -import SocialMedia from './SocialMedia/SocialMedia'; -import ThematicsList from './ThematicsList/ThematicsList'; -import ToC from './ToC/ToC'; -import TopicsList from './TopicsList/TopicsList'; - -export { - CVPreview, - RecentPosts, - RelatedThematics, - RelatedTopics, - Sharing, - SocialMedia, - ThematicsList, - ToC, - TopicsList, -}; diff --git a/src/pages/404.tsx b/src/pages/404.tsx deleted file mode 100644 index 24c6951..0000000 --- a/src/pages/404.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { getLayout } from '@components/Layouts/Layout'; -import PostHeader from '@components/PostHeader/PostHeader'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { settings } from '@utils/config'; -import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n'; -import { GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import Link from 'next/link'; -import { FormattedMessage, useIntl } from 'react-intl'; - -const Error404: NextPageWithLayout = () => { - const intl = useIntl(); - - const pageTitle = intl.formatMessage( - { - defaultMessage: 'Error 404: Page not found - {websiteName}', - description: '404Page: SEO - Page title', - id: '310o3F', - }, - { websiteName: settings.name } - ); - const pageDescription = intl.formatMessage({ - defaultMessage: 'Page not found.', - description: '404Page: SEO - Meta description', - id: '48Ww//', - }); - - return ( - <> - <Head> - <title>{pageTitle}</title> - <meta name="description" content={pageDescription} /> - </Head> - <div className={`${styles.article} ${styles['article--no-comments']}`}> - <PostHeader - title={intl.formatMessage({ - defaultMessage: 'Page not found', - description: '404Page: page title', - id: 'OccTWi', - })} - /> - <div className={styles.body}> - <FormattedMessage - defaultMessage="Sorry, it seems that the page your are looking for does not exist. If you think this path should work, feel free to <link>contact me</link> with the necessary information so that I can fix the problem." - description="404Page: page body" - id="ZWh78Y" - values={{ - link: (chunks: string) => ( - <Link href="/contact/"> - <a>{chunks}</a> - </Link> - ), - }} - /> - </div> - </div> - </> - ); -}; - -Error404.getLayout = getLayout; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const intl = await getIntlInstance(); - const breadcrumbTitle = intl.formatMessage({ - defaultMessage: 'Error 404', - description: '404Page: breadcrumb item', - id: 'ywkCsK', - }); - const { locale } = context; - const translation = await loadTranslation(locale); - - return { - props: { - breadcrumbTitle, - locale, - translation, - }, - }; -}; - -export default Error404; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 84c2469..939b337 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,17 +1,16 @@ -import { AppPropsWithLayout } from '@ts/types/app'; import { settings } from '@utils/config'; import { AckeeProvider } from '@utils/providers/ackee'; import { PrismThemeProvider } from '@utils/providers/prism-theme'; import { ThemeProvider } from 'next-themes'; +import { AppProps } from 'next/app'; import { useRouter } from 'next/router'; import { IntlProvider } from 'react-intl'; import '../styles/globals.scss'; -const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => { +const App = ({ Component, pageProps }: AppProps) => { const { locale, defaultLocale } = useRouter(); const appLocale: string = locale || settings.locales.defaultLocale; - const getLayout = Component.getLayout ?? ((page) => page); return ( <AckeeProvider domain={settings.ackee.url} siteId={settings.ackee.siteId}> <IntlProvider @@ -25,7 +24,7 @@ const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => { enableSystem={true} > <PrismThemeProvider> - {getLayout(<Component {...pageProps} />)} + <Component {...pageProps} /> </PrismThemeProvider> </ThemeProvider> </IntlProvider> @@ -33,4 +32,4 @@ const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => { ); }; -export default MyApp; +export default App; diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx deleted file mode 100644 index 27a6f7b..0000000 --- a/src/pages/article/[slug].tsx +++ /dev/null @@ -1,291 +0,0 @@ -import CommentForm from '@components/CommentForm/CommentForm'; -import CommentsList from '@components/CommentsList/CommentsList'; -import { getLayout } from '@components/Layouts/Layout'; -import PostFooter from '@components/PostFooter/PostFooter'; -import PostHeader from '@components/PostHeader/PostHeader'; -import Sidebar from '@components/Sidebar/Sidebar'; -import Spinner from '@components/Spinner/Spinner'; -import { Sharing, ToC } from '@components/Widgets'; -import { - getAllPostsSlug, - getCommentsByPostId, - getPostBySlug, -} from '@services/graphql/queries'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { ArticleMeta, ArticleProps } from '@ts/types/articles'; -import { PrismDefaultPlugins, PrismPlugins } from '@ts/types/prism'; -import { settings } from '@utils/config'; -import { getFormattedPaths } from '@utils/helpers/format'; -import { loadTranslation } from '@utils/helpers/i18n'; -import { addPrismClasses } from '@utils/helpers/prism'; -import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import Prism from 'prismjs'; -import { ParsedUrlQuery } from 'querystring'; -import { useCallback, useEffect, useMemo } from 'react'; -import { useIntl } from 'react-intl'; -import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts'; - -const SingleArticle: NextPageWithLayout<ArticleProps> = ({ - comments, - post, -}) => { - const intl = useIntl(); - const router = useRouter(); - - const loadPrismPlugins = useCallback( - async (prismPlugins: (PrismDefaultPlugins | PrismPlugins)[]) => { - for (const plugin of prismPlugins) { - try { - if (plugin === 'color-scheme') { - await import(`@utils/plugins/prism-${plugin}`); - } else { - await import(`prismjs/plugins/${plugin}/prism-${plugin}.min.js`); - - if (plugin === 'autoloader') - Prism.plugins.autoloader.languages_path = '/prism/'; - } - } catch (error) { - console.error('Article: an error occurred with Prism.'); - console.error(error); - } - } - }, - [] - ); - - const plugins: (PrismDefaultPlugins | PrismPlugins)[] = useMemo( - () => [ - 'autoloader', - 'toolbar', - 'show-language', - 'copy-to-clipboard', - 'color-scheme', - 'command-line', - 'line-numbers', - 'match-braces', - 'normalize-whitespace', - ], - [] - ); - - useEffect(() => { - loadPrismPlugins(plugins).then(() => { - addPrismClasses(); - Prism.highlightAll(); - }); - }, [plugins, loadPrismPlugins]); - - if (router.isFallback) return <Spinner />; - - const { - author, - commentCount, - content, - databaseId, - dates, - featuredImage, - info, - intro, - seo, - topics, - thematics, - title, - } = post; - - const meta: ArticleMeta = { - author, - commentCount: commentCount || undefined, - dates, - readingTime: info.readingTime, - thematics, - wordsCount: info.wordsCount, - }; - - const articleUrl = `${settings.url}${router.asPath}`; - - const webpageSchema: WebPage = { - '@id': `${articleUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - lastReviewed: dates.update, - name: seo.title, - description: seo.metaDesc, - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${articleUrl}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const blogSchema: Blog = { - '@id': `${settings.url}/#blog`, - '@type': 'Blog', - blogPost: { '@id': `${settings.url}/#article` }, - isPartOf: { - '@id': `${articleUrl}`, - }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - }; - - const publicationDate = new Date(dates.publication); - const updateDate = new Date(dates.update); - - const blogPostSchema: BlogPosting = { - '@id': `${settings.url}/#article`, - '@type': 'BlogPosting', - name: title, - description: intro, - articleBody: content, - author: { '@id': `${settings.url}/#branding` }, - commentCount: commentCount || undefined, - copyrightYear: publicationDate.getFullYear(), - creator: { '@id': `${settings.url}/#branding` }, - dateCreated: publicationDate.toISOString(), - dateModified: updateDate.toISOString(), - datePublished: publicationDate.toISOString(), - discussionUrl: `${articleUrl}/#comments`, - editor: { '@id': `${settings.url}/#branding` }, - headline: title, - image: featuredImage?.sourceUrl, - inLanguage: settings.locales.defaultLocale, - isPartOf: { - '@id': `${settings.url}/blog`, - }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${articleUrl}` }, - thumbnailUrl: featuredImage?.sourceUrl, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, blogSchema, blogPostSchema], - }; - - const copyText = intl.formatMessage({ - defaultMessage: 'Copy', - description: 'Prism: copy button text (no clicked)', - id: '/ly3AC', - }); - const copiedText = intl.formatMessage({ - defaultMessage: 'Copied!', - description: 'Prism: copy button text (clicked)', - id: 'OV9r1K', - }); - const errorText = intl.formatMessage({ - defaultMessage: 'Use Ctrl+c to copy', - description: 'Prism: error text', - id: 'z9qkcQ', - }); - const darkTheme = intl.formatMessage({ - defaultMessage: 'Dark Theme 🌙', - description: 'Prism: toggle dark theme button text', - id: 'nFMdWI', - }); - const lightTheme = intl.formatMessage({ - defaultMessage: 'Light Theme 🌞', - description: 'Prism: toggle light theme button text', - id: 'Ua2g2p', - }); - - return ( - <> - <Head> - <title>{seo.title}</title> - <meta name="description" content={seo.metaDesc} /> - <meta property="og:url" content={`${articleUrl}`} /> - <meta property="og:type" content="article" /> - <meta property="og:title" content={title} /> - <meta property="og:description" content={intro} /> - <meta property="og:image" content={featuredImage?.sourceUrl} /> - <meta property="og:image:alt" content={featuredImage?.altText} /> - </Head> - <Script - id="schema-article" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="article" - className={styles.article} - data-prismjs-copy={copyText} - data-prismjs-copy-success={copiedText} - data-prismjs-copy-error={errorText} - data-prismjs-color-scheme-dark={darkTheme} - data-prismjs-color-scheme-light={lightTheme} - > - <PostHeader intro={intro} meta={meta} title={title} /> - <Sidebar - position="left" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Table of Contents', - description: 'ArticlePage: ToC sidebar aria-label', - id: '9nhYRA', - })} - > - <ToC /> - </Sidebar> - <div - className={styles.body} - dangerouslySetInnerHTML={{ __html: content }} - ></div> - <PostFooter topics={topics} /> - <Sidebar - position="right" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Sidebar', - description: 'ArticlePage: right sidebar aria-label', - id: 'JeYOeA', - })} - > - <Sharing title={title} excerpt={intro} /> - </Sidebar> - <section id="comments" className={styles.comments}> - <CommentsList articleId={databaseId} comments={comments} /> - <CommentForm articleId={databaseId} /> - </section> - </article> - </> - ); -}; - -SingleArticle.getLayout = getLayout; - -interface PostParams extends ParsedUrlQuery { - slug: string; -} - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const { locale } = context; - const translation = await loadTranslation(locale); - const { slug } = context.params as PostParams; - const post = await getPostBySlug(slug); - const comments = await getCommentsByPostId(post.databaseId); - const breadcrumbTitle = post.title; - - return { - props: { - breadcrumbTitle, - comments, - post, - translation, - }, - }; -}; - -export const getStaticPaths: GetStaticPaths = async () => { - const allSlugs = await getAllPostsSlug(); - const paths = getFormattedPaths(allSlugs); - - return { - paths, - fallback: true, - }; -}; - -export default SingleArticle; diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx deleted file mode 100644 index b5ced07..0000000 --- a/src/pages/blog/index.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import { Button } from '@components/Buttons'; -import { getLayout } from '@components/Layouts/Layout'; -import Pagination from '@components/Pagination/Pagination'; -import PaginationCursor from '@components/PaginationCursor/PaginationCursor'; -import PostHeader from '@components/PostHeader/PostHeader'; -import PostsList from '@components/PostsList/PostsList'; -import Sidebar from '@components/Sidebar/Sidebar'; -import Spinner from '@components/Spinner/Spinner'; -import { ThematicsList, TopicsList } from '@components/Widgets'; -import { - getAllThematics, - getAllTopics, - getPostsTotal, - getPublishedPosts, -} from '@services/graphql/queries'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { BlogPageProps, PostsList as PostsListData } from '@ts/types/blog'; -import { settings } from '@utils/config'; -import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n'; -import { GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { useEffect, useRef, useState } from 'react'; -import { useIntl } from 'react-intl'; -import { Blog as BlogSchema, Graph, WebPage } from 'schema-dts'; -import useSWRInfinite from 'swr/infinite'; - -const Blog: NextPageWithLayout<BlogPageProps> = ({ - allThematics, - allTopics, - posts, - totalPosts, -}) => { - const intl = useIntl(); - const lastPostRef = useRef<HTMLSpanElement>(null); - const router = useRouter(); - const [isMounted, setIsMounted] = useState<boolean>(false); - - useEffect(() => { - if (typeof window !== undefined) setIsMounted(true); - }, []); - - const getKey = (pageIndex: number, previousData: PostsListData) => { - if (previousData && !previousData.posts) return null; - - return pageIndex === 0 - ? { first: settings.postsPerPage } - : { - first: settings.postsPerPage, - after: previousData.pageInfo.endCursor, - }; - }; - - const { data, error, size, setSize } = useSWRInfinite( - getKey, - getPublishedPosts, - { fallbackData: [posts] } - ); - const [totalPostsCount, setTotalPostsCount] = useState<number>(totalPosts); - - useEffect(() => { - if (data) setTotalPostsCount(data[0].pageInfo.total); - }, [data]); - - const [loadedPostsCount, setLoadedPostsCount] = useState<number>( - settings.postsPerPage - ); - - useEffect(() => { - if (data && data.length > 0) { - const newCount = - settings.postsPerPage + - data[0].pageInfo.total - - data[data.length - 1].pageInfo.total; - setLoadedPostsCount(newCount); - } - }, [data]); - - const isLoadingInitialData = !data && !error; - const isLoadingMore: boolean = - isLoadingInitialData || - (size > 0 && data !== undefined && typeof data[size - 1] === 'undefined'); - - const hasNextPage = data && data[data.length - 1].pageInfo.hasNextPage; - - const loadMorePosts = () => { - if (lastPostRef.current) { - lastPostRef.current.focus(); - } - setSize(size + 1); - }; - - const getPostsList = () => { - if (error) - return intl.formatMessage({ - defaultMessage: 'Failed to load.', - description: 'BlogPage: failed to load text', - id: 'C/XGkH', - }); - if (!data) return <Spinner />; - - return <PostsList ref={lastPostRef} data={data} showYears={true} />; - }; - - const pageTitle = intl.formatMessage( - { - defaultMessage: 'Blog: development, open source - {websiteName}', - description: 'BlogPage: SEO - Page title', - id: '+Y+tLK', - }, - { websiteName: settings.name } - ); - const pageDescription = intl.formatMessage( - { - defaultMessage: - "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.", - description: 'BlogPage: SEO - Meta description', - id: '18h/t0', - }, - { websiteName: settings.name } - ); - const pageUrl = `${settings.url}${router.asPath}`; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: pageTitle, - description: pageDescription, - inLanguage: settings.locales.defaultLocale, - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${settings.url}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const blogSchema: BlogSchema = { - '@id': `${settings.url}/#blog`, - '@type': 'Blog', - author: { '@id': `${settings.url}/#branding` }, - creator: { '@id': `${settings.url}/#branding` }, - editor: { '@id': `${settings.url}/#branding` }, - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, blogSchema], - }; - - const title = intl.formatMessage({ - defaultMessage: 'Blog', - description: 'BlogPage: page title', - id: '7TbbIk', - }); - - return ( - <> - <Head> - <title>{pageTitle}</title> - <meta name="description" content={pageDescription} /> - <meta property="og:url" content={`${pageUrl}`} /> - <meta property="og:type" content="website" /> - <meta property="og:title" content={title} /> - <meta property="og:description" content={pageDescription} /> - </Head> - <Script - id="schema-blog" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="blog" - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader title={title} meta={{ results: totalPostsCount }} /> - <div className={styles.body}> - {getPostsList()} - {hasNextPage && - (isMounted ? ( - <> - <PaginationCursor - current={loadedPostsCount} - total={totalPostsCount} - /> - <Button - isDisabled={isLoadingMore} - clickHandler={loadMorePosts} - position="center" - spacing={true} - > - {intl.formatMessage({ - defaultMessage: 'Load more?', - description: 'BlogPage: load more text', - id: 'Kqq2cm', - })} - </Button> - </> - ) : ( - <Pagination baseUrl="/blog" total={totalPostsCount} /> - ))} - </div> - <Sidebar - position="right" - title={intl.formatMessage({ - defaultMessage: 'Filter by:', - description: 'BlogPage: sidebar title', - id: 'KERk7L', - })} - > - <ThematicsList - initialData={allThematics} - title={intl.formatMessage({ - defaultMessage: 'Thematics', - description: 'BlogPage: thematics list widget title', - id: 'HriY57', - })} - /> - <TopicsList - initialData={allTopics} - title={intl.formatMessage({ - defaultMessage: 'Topics', - description: 'BlogPage: topics list widget title', - id: '2D9tB5', - })} - /> - </Sidebar> - </article> - </> - ); -}; - -Blog.getLayout = getLayout; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const intl = await getIntlInstance(); - const breadcrumbTitle = intl.formatMessage({ - defaultMessage: 'Blog', - description: 'BlogPage: breadcrumb item', - id: 'R0eDmw', - }); - const firstPosts = await getPublishedPosts({ first: settings.postsPerPage }); - const totalPosts = await getPostsTotal(); - const allThematics = await getAllThematics(); - const allTopics = await getAllTopics(); - const { locale } = context; - const translation = await loadTranslation(locale); - - return { - props: { - allThematics, - allTopics, - breadcrumbTitle, - locale, - posts: firstPosts, - totalPosts, - translation, - }, - }; -}; - -export default Blog; diff --git a/src/pages/blog/page/[id].tsx b/src/pages/blog/page/[id].tsx deleted file mode 100644 index 6c4d2f8..0000000 --- a/src/pages/blog/page/[id].tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { getLayout } from '@components/Layouts/Layout'; -import Pagination from '@components/Pagination/Pagination'; -import PostHeader from '@components/PostHeader/PostHeader'; -import PostsList from '@components/PostsList/PostsList'; -import Sidebar from '@components/Sidebar/Sidebar'; -import { ThematicsList, TopicsList } from '@components/Widgets'; -import { - getAllThematics, - getAllTopics, - getEndCursor, - getPostsTotal, - getPublishedPosts, -} from '@services/graphql/queries'; -import { NextPageWithLayout } from '@ts/types/app'; -import { BlogPageProps } from '@ts/types/blog'; -import { settings } from '@utils/config'; -import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n'; -import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { useIntl } from 'react-intl'; -import { Blog, Graph, WebPage } from 'schema-dts'; -import styles from '@styles/pages/Page.module.scss'; -import { getFormattedPageNumbers } from '@utils/helpers/format'; -import { useEffect } from 'react'; -import Spinner from '@components/Spinner/Spinner'; - -const BlogPage: NextPageWithLayout<BlogPageProps> = ({ - allThematics, - allTopics, - posts, - totalPosts, -}) => { - const intl = useIntl(); - const router = useRouter(); - const pageNumber = Number(router.query.id); - - useEffect(() => { - if (router.query.id === '1') router.push('/blog'); - }, [router]); - - if (router.isFallback) return <Spinner />; - - const pageTitle = intl.formatMessage( - { - defaultMessage: 'Blog - Page {number} - {websiteName}', - description: 'BlogPage: SEO - Page title', - id: '8w+jnD', - }, - { number: pageNumber, websiteName: settings.name } - ); - const pageDescription = intl.formatMessage( - { - defaultMessage: - "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.", - description: 'BlogPage: SEO - Meta description', - id: '18h/t0', - }, - { websiteName: settings.name } - ); - const pageUrl = `${settings.url}${router.asPath}`; - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: pageTitle, - description: pageDescription, - inLanguage: settings.locales.defaultLocale, - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${settings.url}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const blogSchema: Blog = { - '@id': `${settings.url}/#blog`, - '@type': 'Blog', - author: { '@id': `${settings.url}/#branding` }, - creator: { '@id': `${settings.url}/#branding` }, - editor: { '@id': `${settings.url}/#branding` }, - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, blogSchema], - }; - - const title = intl.formatMessage({ - defaultMessage: 'Blog', - description: 'BlogPage: page title', - id: '7TbbIk', - }); - - return ( - <> - <Head> - <title>{pageTitle}</title> - <meta name="description" content={pageDescription} /> - <meta property="og:url" content={`${pageUrl}`} /> - <meta property="og:type" content="website" /> - <meta property="og:title" content={title} /> - <meta property="og:description" content={pageDescription} /> - </Head> - <Script - id="schema-blog" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="blog" - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader title={title} meta={{ results: totalPosts }} /> - <div className={styles.body}> - <PostsList data={[posts]} showYears={true} /> - <Pagination baseUrl="/blog" total={totalPosts} /> - </div> - <Sidebar - position="right" - title={intl.formatMessage({ - defaultMessage: 'Filter by:', - description: 'BlogPage: sidebar title', - id: 'KERk7L', - })} - > - <ThematicsList - initialData={allThematics} - title={intl.formatMessage({ - defaultMessage: 'Thematics', - description: 'BlogPage: thematics list widget title', - id: 'HriY57', - })} - /> - <TopicsList - initialData={allTopics} - title={intl.formatMessage({ - defaultMessage: 'Topics', - description: 'BlogPage: topics list widget title', - id: '2D9tB5', - })} - /> - </Sidebar> - </article> - </> - ); -}; - -BlogPage.getLayout = getLayout; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const intl = await getIntlInstance(); - const breadcrumbTitle = intl.formatMessage({ - defaultMessage: 'Blog', - description: 'BlogPage: breadcrumb item', - id: 'R0eDmw', - }); - const { locale, params } = context; - const queriedPageNumber = params ? Number(params.id) : 1; - const queriedPostsNumber = settings.postsPerPage * queriedPageNumber; - const endCursor = - queriedPostsNumber === 1 - ? undefined - : await getEndCursor({ first: queriedPostsNumber }); - const posts = await getPublishedPosts({ - first: settings.postsPerPage, - after: endCursor, - }); - const totalPosts = await getPostsTotal(); - const allThematics = await getAllThematics(); - const allTopics = await getAllTopics(); - const translation = await loadTranslation(locale); - - return { - props: { - allThematics, - allTopics, - breadcrumbTitle, - locale, - posts, - totalPosts, - translation, - }, - }; -}; - -export default BlogPage; - -export const getStaticPaths: GetStaticPaths = async () => { - const totalPosts = await getPostsTotal(); - const totalPages = Math.floor(totalPosts / settings.postsPerPage); - const paths = getFormattedPageNumbers(totalPages); - - return { - paths, - fallback: true, - }; -}; diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx deleted file mode 100644 index 5934dd9..0000000 --- a/src/pages/contact.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import ContactForm from '@components/ContactForm/ContactForm'; -import { getLayout } from '@components/Layouts/Layout'; -import PostHeader from '@components/PostHeader/PostHeader'; -import Sidebar from '@components/Sidebar/Sidebar'; -import { SocialMedia } from '@components/Widgets'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { settings } from '@utils/config'; -import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n'; -import { GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { useIntl } from 'react-intl'; -import { ContactPage as ContactPageSchema, Graph, WebPage } from 'schema-dts'; - -const ContactPage: NextPageWithLayout = () => { - const intl = useIntl(); - const router = useRouter(); - - const pageTitle = intl.formatMessage( - { - defaultMessage: 'Contact form - {websiteName}', - description: 'ContactPage: SEO - Page title', - id: 'Y3qRib', - }, - { websiteName: settings.name } - ); - const pageDescription = intl.formatMessage( - { - defaultMessage: - "Contact {websiteName} through its website. All you need to do it's to fill the contact form.", - description: 'ContactPage: SEO - Meta description', - id: 'OIffB4', - }, - { websiteName: settings.name } - ); - const pageUrl = `${settings.url}${router.asPath}`; - const title = intl.formatMessage({ - defaultMessage: 'Contact', - description: 'ContactPage: page title', - id: 'AN9iy7', - }); - const intro = intl.formatMessage({ - defaultMessage: 'Please fill the form to contact me.', - description: 'ContactPage: page introduction', - id: '8Ls2mD', - }); - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: pageTitle, - description: pageDescription, - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const contactSchema: ContactPageSchema = { - '@id': `${settings.url}/#contact`, - '@type': 'ContactPage', - name: title, - description: intro, - author: { '@id': `${settings.url}/#branding` }, - creator: { '@id': `${settings.url}/#branding` }, - editor: { '@id': `${settings.url}/#branding` }, - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, contactSchema], - }; - - return ( - <> - <Head> - <title>{pageTitle}</title> - <meta name="description" content={pageDescription} /> - <meta property="og:url" content={`${pageUrl}`} /> - <meta property="og:type" content="article" /> - <meta property="og:title" content={title} /> - <meta property="og:description" content={intro} /> - </Head> - <Script - id="schema-contact" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="contact" - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader title={title} intro={intro} /> - <div className={styles.body}> - <p> - {intl.formatMessage({ - defaultMessage: 'All fields marked with * are required.', - description: 'ContactPage: required fields text', - id: 'txusHd', - })} - </p> - <ContactForm /> - </div> - <Sidebar position="right"> - <SocialMedia - title={intl.formatMessage({ - defaultMessage: 'Find me elsewhere', - description: 'ContactPage: social media widget title', - id: 'Qh2CwH', - })} - github={true} - gitlab={true} - linkedin={true} - /> - </Sidebar> - </article> - </> - ); -}; - -ContactPage.getLayout = getLayout; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const intl = await getIntlInstance(); - const breadcrumbTitle = intl.formatMessage({ - defaultMessage: 'Contact', - description: 'ContactPage: breadcrumb item', - id: 'CzTbM4', - }); - const { locale } = context; - const translation = await loadTranslation(locale); - - return { - props: { - breadcrumbTitle, - locale, - translation, - }, - }; -}; - -export default ContactPage; diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx deleted file mode 100644 index 71eb449..0000000 --- a/src/pages/cv.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { getLayout } from '@components/Layouts/Layout'; -import PostHeader from '@components/PostHeader/PostHeader'; -import Sidebar from '@components/Sidebar/Sidebar'; -import { CVPreview, SocialMedia, ToC } from '@components/Widgets'; -import CVContent, { intro, meta, pdf, image } from '@content/pages/cv.mdx'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { ArticleMeta } from '@ts/types/articles'; -import { settings } from '@utils/config'; -import { loadTranslation } from '@utils/helpers/i18n'; -import { GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { useIntl } from 'react-intl'; -import { AboutPage, Graph, WebPage } from 'schema-dts'; - -const CV: NextPageWithLayout = () => { - const intl = useIntl(); - const router = useRouter(); - const dates = { - publication: meta.publishedOn, - update: meta.updatedOn, - }; - - const pageMeta: ArticleMeta = { - dates, - }; - const pageUrl = `${settings.url}${router.asPath}`; - const pageTitle = intl.formatMessage( - { - defaultMessage: 'CV Front-end developer - {websiteName}', - description: 'CVPage: SEO - Page title', - id: 'Y1ZdJ6', - }, - { websiteName: settings.name } - ); - const pageDescription = intl.formatMessage( - { - defaultMessage: - 'Discover the curriculum of {websiteName}, front-end developer located in France: skills, experiences and training.', - description: 'CVPage: SEO - Meta description', - id: 'bBdMGm', - }, - { websiteName: settings.name } - ); - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: pageTitle, - description: pageDescription, - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const publicationDate = new Date(dates.publication); - const updateDate = new Date(dates.update); - - const cvSchema: AboutPage = { - '@id': `${settings.url}/#cv`, - '@type': 'AboutPage', - name: pageTitle, - description: intro, - author: { '@id': `${settings.url}/#branding` }, - creator: { '@id': `${settings.url}/#branding` }, - dateCreated: publicationDate.toISOString(), - dateModified: updateDate.toISOString(), - datePublished: publicationDate.toISOString(), - editor: { '@id': `${settings.url}/#branding` }, - image, - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - thumbnailUrl: image, - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, cvSchema], - }; - - const title = intl.formatMessage( - { - defaultMessage: "{name}'s CV", - description: 'CVPage: page title', - id: 'Mj2BQf', - }, - { name: settings.name } - ); - - return ( - <> - <Head> - <title>{pageTitle}</title> - <meta name="description" content={pageDescription} /> - <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} /> - <meta property="og:image:alt" content={title} /> - </Head> - <Script - id="schema-cv" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="cv" - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader intro={intro} meta={pageMeta} title={meta.title} /> - <Sidebar - position="left" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Table of Contents', - description: 'CVPage: ToC sidebar aria-label', - id: 'g4DckL', - })} - > - <ToC /> - </Sidebar> - <div className={styles.body}> - <CVContent /> - </div> - <Sidebar - position="right" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Sidebar', - description: 'CVPage: right sidebar aria-label', - id: 'QHOm5t', - })} - > - <CVPreview - title={intl.formatMessage({ - defaultMessage: 'Others formats', - description: 'CVPage: cv preview widget title', - id: 'B9OCyV', - })} - imgSrc={image} - pdf={pdf} - /> - <SocialMedia - title={intl.formatMessage({ - defaultMessage: 'Open-source projects', - description: 'CVPage: social media widget title', - id: '+Dre5J', - })} - github={true} - gitlab={true} - /> - </Sidebar> - </article> - </> - ); -}; - -CV.getLayout = getLayout; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const breadcrumbTitle = meta.title; - const { locale } = context; - const translation = await loadTranslation(locale); - - return { - props: { - breadcrumbTitle, - locale, - translation, - }, - }; -}; - -export default CV; diff --git a/src/pages/index.tsx b/src/pages/index.tsx deleted file mode 100644 index ca0a809..0000000 --- a/src/pages/index.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import FeedIcon from '@assets/images/icon-feed.svg'; -import { ButtonLink } from '@components/Buttons'; -import { ContactIcon } from '@components/Icons'; -import Layout from '@components/Layouts/Layout'; -import { ResponsiveImage } from '@components/MDX'; -import { RecentPosts } from '@components/Widgets'; -import HomePageContent from '@content/pages/homepage.mdx'; -import { getPublishedPosts } from '@services/graphql/queries'; -import styles from '@styles/pages/Home.module.scss'; -import { NextPageWithLayout, ResponsiveImageProps } from '@ts/types/app'; -import { PostsList } from '@ts/types/blog'; -import { settings } from '@utils/config'; -import { loadTranslation } from '@utils/helpers/i18n'; -import { NestedMDXComponents } from 'mdx/types'; -import { GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import Script from 'next/script'; -import type { ReactElement } from 'react'; -import { useIntl } from 'react-intl'; -import { Graph, WebPage } from 'schema-dts'; - -type HomePageProps = { - recentPosts: PostsList; -}; - -const Home: NextPageWithLayout<HomePageProps> = ({ - recentPosts, -}: { - recentPosts: PostsList; -}) => { - const intl = useIntl(); - - const CodingLinks = () => { - return ( - <ul className={styles['links-list']}> - <li> - <ButtonLink target="/thematique/developpement-web"> - {intl.formatMessage({ - defaultMessage: 'Web development', - description: 'HomePage: link to web development thematic', - id: 'vkF/RP', - })} - </ButtonLink> - </li> - <li> - <ButtonLink target="/projets"> - {intl.formatMessage({ - defaultMessage: 'Projects', - description: 'HomePage: link to projects', - id: 'N44SOc', - })} - </ButtonLink> - </li> - </ul> - ); - }; - - const ColdarkRepos = () => { - return ( - <ul className={styles['links-list']}> - <li> - <ButtonLink - target="https://github.com/ArmandPhilippot/coldark" - isExternal={true} - > - Github - </ButtonLink> - </li> - <li> - <ButtonLink - target="https://gitlab.com/ArmandPhilippot/coldark" - isExternal={true} - > - Gitlab - </ButtonLink> - </li> - </ul> - ); - }; - - const LibreLinks = () => { - return ( - <ul className={styles['links-list']}> - <li> - <ButtonLink target="/thematique/libre"> - {intl.formatMessage({ - defaultMessage: 'Free', - description: 'HomePage: link to free thematic', - id: 'w8GrOf', - })} - </ButtonLink> - </li> - <li> - <ButtonLink target="/thematique/linux"> - {intl.formatMessage({ - defaultMessage: 'Linux', - description: 'HomePage: link to Linux thematic', - id: 'jASD7k', - })} - </ButtonLink> - </li> - </ul> - ); - }; - - const ShaarliLink = () => { - return ( - <ul className={styles['links-list']}> - <li> - <ButtonLink target="https://shaarli.armandphilippot.com/"> - {intl.formatMessage({ - defaultMessage: 'Shaarli', - description: 'HomePage: link to Shaarli', - id: 'i5L19t', - })} - </ButtonLink> - </li> - </ul> - ); - }; - - const MoreLinks = () => { - return ( - <ul className={styles['links-list']}> - <li> - <ButtonLink target="/contact"> - <ContactIcon /> - {intl.formatMessage({ - defaultMessage: 'Contact me', - description: 'HomePage: contact button text', - id: 'sO/Iwj', - })} - </ButtonLink> - </li> - <li> - <ButtonLink target="/feed"> - <FeedIcon className={styles['icon--feed']} /> - {intl.formatMessage({ - defaultMessage: 'Subscribe', - description: 'HomePage: RSS feed subscription text', - id: 'T4YA64', - })} - </ButtonLink> - </li> - </ul> - ); - }; - - const getRecentPosts = () => { - return <RecentPosts posts={recentPosts} />; - }; - - const components: NestedMDXComponents = { - CodingLinks: CodingLinks, - ColdarkRepos: ColdarkRepos, - Image: (props: ResponsiveImageProps) => ResponsiveImage({ ...props }), - LibreLinks: LibreLinks, - MoreLinks: MoreLinks, - RecentPosts: getRecentPosts, - ShaarliLink: ShaarliLink, - }; - - const pageTitle = intl.formatMessage( - { - defaultMessage: '{websiteName} | Front-end developer: WordPress/React', - description: 'HomePage: SEO - Page title', - id: 'PXp2hv', - }, - { websiteName: settings.name } - ); - const pageDescription = intl.formatMessage( - { - defaultMessage: - '{websiteName} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.', - description: 'HomePage: SEO - Meta description', - id: 'tMuNTy', - }, - { websiteName: settings.name } - ); - - const webpageSchema: WebPage = { - '@id': `${settings.url}/#home`, - '@type': 'WebPage', - name: pageTitle, - description: pageDescription, - author: { '@id': `${settings.url}/#branding` }, - creator: { '@id': `${settings.url}/#branding` }, - editor: { '@id': `${settings.url}/#branding` }, - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${settings.url}`, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema], - }; - - return ( - <> - <Head> - <title>{pageTitle}</title> - <meta name="description" content={pageDescription} /> - <meta property="og:type" content="website" /> - <meta property="og:url" content={`${settings.url}`} /> - <meta property="og:title" content={pageTitle} /> - <meta property="og:description" content={pageDescription} /> - </Head> - <Script - id="schema-homepage" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <div id="home"> - <HomePageContent components={components} /> - </div> - </> - ); -}; - -Home.getLayout = function getLayout(page: ReactElement) { - return <Layout isHome={true}>{page}</Layout>; -}; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const { locale } = context; - const translation = await loadTranslation(locale); - const recentPosts = await getPublishedPosts({ first: 3 }); - - return { - props: { - recentPosts, - translation, - }, - }; -}; - -export default Home; diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx deleted file mode 100644 index b103b5e..0000000 --- a/src/pages/mentions-legales.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { getLayout } from '@components/Layouts/Layout'; -import { Link } from '@components/MDX'; -import PostHeader from '@components/PostHeader/PostHeader'; -import Sidebar from '@components/Sidebar/Sidebar'; -import { ToC } from '@components/Widgets'; -import LegalNoticeContent, { - intro, - meta, -} from '@content/pages/legal-notice.mdx'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { ArticleMeta } from '@ts/types/articles'; -import { settings } from '@utils/config'; -import { loadTranslation } from '@utils/helpers/i18n'; -import { NestedMDXComponents } from 'mdx/types'; -import { GetStaticProps, GetStaticPropsContext } 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'; - -const LegalNotice: NextPageWithLayout = () => { - const intl = useIntl(); - const router = useRouter(); - const dates = { - publication: meta.publishedOn, - update: meta.updatedOn, - }; - - const pageMeta: ArticleMeta = { - dates, - }; - const pageTitle = intl.formatMessage( - { - defaultMessage: 'Legal notice - {websiteName}', - description: 'LegalNoticePage: SEO - Page title', - id: '4zAUSu', - }, - { websiteName: settings.name } - ); - const pageDescription = intl.formatMessage( - { - defaultMessage: "Discover the legal notice of {websiteName}'s website.", - description: 'LegalNoticePage: SEO - Meta description', - id: 'uvB+32', - }, - { websiteName: settings.name } - ); - const pageUrl = `${settings.url}${router.asPath}`; - const title = intl.formatMessage({ - defaultMessage: 'Legal notice', - description: 'LegalNoticePage: page title', - id: '/IirIt', - }); - const publicationDate = new Date(dates.publication); - const updateDate = new Date(dates.update); - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: pageTitle, - description: pageDescription, - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const articleSchema: Article = { - '@id': `${settings.url}/#legal-notice`, - '@type': 'Article', - name: title, - description: intro, - author: { '@id': `${settings.url}/#branding` }, - copyrightYear: publicationDate.getFullYear(), - creator: { '@id': `${settings.url}/#branding` }, - dateCreated: publicationDate.toISOString(), - dateModified: updateDate.toISOString(), - datePublished: publicationDate.toISOString(), - editor: { '@id': `${settings.url}/#branding` }, - headline: title, - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${pageUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; - - const components: NestedMDXComponents = { - Link: (props) => Link(props), - }; - - return ( - <> - <Head> - <title>{pageTitle}</title> - <meta name="description" content={pageDescription} /> - <meta property="og:url" content={`${pageUrl}`} /> - <meta property="og:type" content="article" /> - <meta property="og:title" content={pageTitle} /> - <meta property="og:description" content={intro} /> - </Head> - <Script - id="schema-legal-notice" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="legal-notice" - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader intro={intro} meta={pageMeta} title={meta.title} /> - <Sidebar position="left"> - <ToC /> - </Sidebar> - <div className={styles.body}> - <LegalNoticeContent components={components} /> - </div> - </article> - </> - ); -}; - -LegalNotice.getLayout = getLayout; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const breadcrumbTitle = meta.title; - const { locale } = context; - const translation = await loadTranslation(locale); - - return { - props: { - breadcrumbTitle, - locale, - translation, - }, - }; -}; - -export default LegalNotice; diff --git a/src/pages/projet/[slug].tsx b/src/pages/projet/[slug].tsx deleted file mode 100644 index 1f09fed..0000000 --- a/src/pages/projet/[slug].tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { getLayout } from '@components/Layouts/Layout'; -import { CodeBlock, Gallery, Link, ResponsiveImage } from '@components/MDX'; -import PostHeader from '@components/PostHeader/PostHeader'; -import ProjectSummary from '@components/ProjectSummary/ProjectSummary'; -import Sidebar from '@components/Sidebar/Sidebar'; -import { Sharing, ToC } from '@components/Widgets'; -import styles from '@styles/pages/Page.module.scss'; -import { - NextPageWithLayout, - Project as ProjectData, - ProjectProps, -} from '@ts/types/app'; -import { settings } from '@utils/config'; -import { loadTranslation } from '@utils/helpers/i18n'; -import { - getAllProjectsFilename, - getProjectData, -} from '@utils/helpers/projects'; -import { MDXComponents, NestedMDXComponents } from 'mdx/types'; -import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { ParsedUrlQuery } from 'querystring'; -import { ComponentType } from 'react'; -import { useIntl } from 'react-intl'; -import { Article, Graph, WebPage } from 'schema-dts'; - -const Project: NextPageWithLayout<ProjectProps> = ({ - project, -}: { - project: ProjectData; -}) => { - const intl = useIntl(); - const router = useRouter(); - const projectUrl = `${settings.url}${router.asPath}`; - const { id, intro, meta, title, seo } = project; - const dates = { - publication: meta.publishedOn, - update: meta.updatedOn, - }; - - const components: NestedMDXComponents = { - CodeBlock: (props) => CodeBlock(props), - Gallery: (props) => Gallery(props), - Image: (props) => ResponsiveImage({ caption: props.caption, ...props }), - Link: (props) => Link(props), - pre: ({ children }) => CodeBlock(children.props), - }; - - const ProjectContent: ComponentType<MDXComponents> = - require(`../../content/projects/${id}.mdx`).default; - - const webpageSchema: WebPage = { - '@id': `${projectUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: seo.title, - description: seo.description, - inLanguage: settings.locales.defaultLocale, - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${settings.url}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const publicationDate = new Date(dates.publication); - const updateDate = new Date(dates.update); - - const articleSchema: Article = { - '@id': `${settings.url}/project`, - '@type': 'Article', - name: title, - description: intro, - author: { '@id': `${settings.url}/#branding` }, - copyrightYear: publicationDate.getFullYear(), - creator: { '@id': `${settings.url}/#branding` }, - dateCreated: publicationDate.toISOString(), - dateModified: updateDate.toISOString(), - datePublished: publicationDate.toISOString(), - editor: { '@id': `${settings.url}/#branding` }, - headline: title, - thumbnailUrl: meta.hasCover ? `/projects/${id}.jpg` : '', - image: meta.hasCover ? `/projects/${id}.jpg` : '', - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${projectUrl}` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; - - return ( - <> - <Head> - <title>{seo.title}</title> - <meta name="description" content={seo.description} /> - <meta property="og:url" content={`${projectUrl}`} /> - <meta property="og:type" content="article" /> - <meta property="og:title" content={title} /> - <meta property="og:description" content={intro} /> - </Head> - <Script - id="schema-project" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="project" - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader title={title} intro={intro} meta={{ dates }} /> - <Sidebar - position="left" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Table of Contents', - description: 'ProjectPage: ToC sidebar aria-label', - id: '6dXfvr', - })} - > - <ToC /> - </Sidebar> - <div className={styles.body}> - <ProjectSummary id={id} title={title} meta={meta} /> - <ProjectContent components={components} /> - </div> - <Sidebar - position="right" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Sidebar', - description: 'ProjectPage: right sidebar aria-label', - id: 'hHrNd0', - })} - > - <Sharing title={title} excerpt={intro} /> - </Sidebar> - </article> - </> - ); -}; - -Project.getLayout = getLayout; - -interface ProjectParams extends ParsedUrlQuery { - slug: string; -} - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const { locale } = context; - const translation = await loadTranslation(locale); - const { slug } = context.params as ProjectParams; - const project = await getProjectData(slug); - const breadcrumbTitle = project.title; - - return { - props: { - breadcrumbTitle, - locale, - project, - translation, - }, - }; -}; - -export const getStaticPaths: GetStaticPaths = async () => { - const filenames = getAllProjectsFilename(); - const paths = filenames.map((filename) => { - return { - params: { - slug: filename, - }, - }; - }); - - return { - paths, - fallback: false, - }; -}; - -export default Project; diff --git a/src/pages/projets.tsx b/src/pages/projets.tsx deleted file mode 100644 index 8a81f39..0000000 --- a/src/pages/projets.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { getLayout } from '@components/Layouts/Layout'; -import PostHeader from '@components/PostHeader/PostHeader'; -import ProjectsList from '@components/ProjectsList/ProjectsList'; -import PageContent, { meta } from '@content/pages/projects.mdx'; -import styles from '@styles/pages/Projects.module.scss'; -import { Project } from '@ts/types/app'; -import { settings } from '@utils/config'; -import { loadTranslation } from '@utils/helpers/i18n'; -import { getSortedProjects } from '@utils/helpers/projects'; -import { GetStaticProps, GetStaticPropsContext } 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'; - -const Projects = ({ projects }: { projects: Project[] }) => { - const intl = useIntl(); - const dates = { - publication: meta.publishedOn, - update: meta.updatedOn, - }; - const publicationDate = new Date(dates.publication); - const updateDate = new Date(dates.update); - const router = useRouter(); - const pageUrl = `${settings.url}${router.asPath}`; - const pageTitle = intl.formatMessage( - { - defaultMessage: 'Projects: open-source makings - {websiteName}', - description: 'ProjectsPage: SEO - Page title', - id: 'SX1z3t', - }, - { websiteName: settings.name } - ); - const pageDescription = intl.formatMessage( - { - defaultMessage: - 'Discover {websiteName} projects. Mostly related to web development and open source.', - description: 'ProjectsPage: SEO - Meta description', - id: 's6U1Xt', - }, - { websiteName: settings.name } - ); - - const webpageSchema: WebPage = { - '@id': `${pageUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: pageTitle, - description: pageDescription, - inLanguage: settings.locales.defaultLocale, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${pageUrl}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const articleSchema: Article = { - '@id': `${settings.url}/#projects`, - '@type': 'Article', - name: meta.title, - description: pageDescription, - author: { '@id': `${settings.url}/#branding` }, - copyrightYear: publicationDate.getFullYear(), - creator: { '@id': `${settings.url}/#branding` }, - dateCreated: publicationDate.toISOString(), - dateModified: updateDate.toISOString(), - datePublished: publicationDate.toISOString(), - editor: { '@id': `${settings.url}/#branding` }, - headline: meta.title, - inLanguage: settings.locales.defaultLocale, - 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 ( - <> - <Head> - <title>{pageTitle}</title> - <meta name="description" content={pageDescription} /> - <meta property="og:url" content={`${pageUrl}`} /> - <meta property="og:type" content="article" /> - <meta property="og:title" content={meta.title} /> - <meta property="og:description" content={pageDescription} /> - </Head> - <Script - id="schema-projects" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article id="projects" className={styles.article}> - <PostHeader title={meta.title} intro={<PageContent />} /> - <div className={styles.body}> - {projects.length > 0 && <ProjectsList projects={projects} />} - </div> - </article> - </> - ); -}; - -Projects.getLayout = getLayout; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const breadcrumbTitle = meta.title; - const { locale } = context; - const projects: Project[] = await getSortedProjects(); - const translation = await loadTranslation(locale); - - return { - props: { - breadcrumbTitle, - locale, - projects, - translation, - }, - }; -}; - -export default Projects; diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx deleted file mode 100644 index b843f8d..0000000 --- a/src/pages/recherche/index.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import { Button } from '@components/Buttons'; -import { getLayout } from '@components/Layouts/Layout'; -import PaginationCursor from '@components/PaginationCursor/PaginationCursor'; -import PostHeader from '@components/PostHeader/PostHeader'; -import PostsList from '@components/PostsList/PostsList'; -import Sidebar from '@components/Sidebar/Sidebar'; -import Spinner from '@components/Spinner/Spinner'; -import { ThematicsList, TopicsList } from '@components/Widgets'; -import { getPublishedPosts } from '@services/graphql/queries'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { PostsList as PostsListData } from '@ts/types/blog'; -import { settings } from '@utils/config'; -import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n'; -import { GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import { useEffect, useRef, useState } from 'react'; -import { useIntl } from 'react-intl'; -import useSWRInfinite from 'swr/infinite'; - -const Search: NextPageWithLayout = () => { - const intl = useIntl(); - const [query, setQuery] = useState(''); - const router = useRouter(); - const lastPostRef = useRef<HTMLSpanElement>(null); - - useEffect(() => { - if (!router.isReady) return; - - if (router.query?.s && typeof router.query.s === 'string') { - setQuery(router.query.s); - } - }, [router.isReady, router.query.s]); - - const getKey = (pageIndex: number, previousData: PostsListData) => { - if (previousData && !previousData.posts) return null; - - return pageIndex === 0 - ? { first: settings.postsPerPage, searchQuery: query } - : { - first: settings.postsPerPage, - after: previousData.pageInfo.endCursor, - searchQuery: query, - }; - }; - - const { data, error, size, setSize } = useSWRInfinite( - getKey, - getPublishedPosts - ); - const [totalPostsCount, setTotalPostsCount] = useState<number>(0); - - useEffect(() => { - if (data) setTotalPostsCount(data[0].pageInfo.total); - }, [data]); - - const [loadedPostsCount, setLoadedPostsCount] = useState<number>( - settings.postsPerPage - ); - - useEffect(() => { - if (data && data.length > 0) { - const newCount = - settings.postsPerPage + - data[0].pageInfo.total - - data[data.length - 1].pageInfo.total; - setLoadedPostsCount(newCount); - } - }, [data]); - - const isLoadingInitialData = !data && !error; - const isLoadingMore: boolean = - isLoadingInitialData || - (size > 0 && data !== undefined && typeof data[size - 1] === 'undefined'); - - const hasNextPage = data && data[data.length - 1].pageInfo.hasNextPage; - - const title = query - ? intl.formatMessage( - { - defaultMessage: 'Search results for {query}', - description: 'SearchPage: search results text', - id: 'VSGuGE', - }, - { query } - ) - : intl.formatMessage({ - defaultMessage: 'Search', - description: 'SearchPage: page title', - id: 'U+35YD', - }); - - const description = query - ? intl.formatMessage( - { - defaultMessage: 'Discover search results for {query}', - description: 'SearchPage: meta description with query', - id: 'A4LTGq', - }, - { query } - ) - : intl.formatMessage( - { - defaultMessage: 'Search for a post on {websiteName}', - description: 'SearchPage: meta description without query', - id: 'PrIz5o', - }, - { websiteName: settings.name } - ); - - const head = { - title: `${title} | ${settings.name}`, - description, - }; - - const loadMorePosts = () => { - if (lastPostRef.current) { - lastPostRef.current.focus(); - } - setSize(size + 1); - }; - - const getPostsList = () => { - if (error) - return intl.formatMessage({ - defaultMessage: 'Failed to load.', - description: 'SearchPage: failed to load text', - id: 'fOe8rH', - }); - if (!data) return <Spinner />; - - return <PostsList ref={lastPostRef} data={data} showYears={false} />; - }; - - return ( - <> - <Head> - <title>{head.title}</title> - <meta name="description" content={head.description} /> - </Head> - <article - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader title={title} meta={{ results: totalPostsCount }} /> - <div className={styles.body}> - {getPostsList()} - {hasNextPage && ( - <> - <PaginationCursor - current={loadedPostsCount} - total={totalPostsCount} - /> - <Button - isDisabled={isLoadingMore} - clickHandler={loadMorePosts} - position="center" - spacing={true} - > - {intl.formatMessage({ - defaultMessage: 'Load more?', - description: 'SearchPage: load more text', - id: 'pEtJik', - })} - </Button> - </> - )} - </div> - <Sidebar position="right"> - <ThematicsList - title={intl.formatMessage({ - defaultMessage: 'Thematics', - description: 'SearchPage: thematics list widget title', - id: 'Dq6+WH', - })} - /> - <TopicsList - title={intl.formatMessage({ - defaultMessage: 'Topics', - description: 'SearchPage: topics list widget title', - id: 'N804XO', - })} - /> - </Sidebar> - </article> - </> - ); -}; - -Search.getLayout = getLayout; - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const intl = await getIntlInstance(); - const breadcrumbTitle = intl.formatMessage({ - defaultMessage: 'Search', - description: 'SearchPage: breadcrumb item', - id: 'TfU6Qm', - }); - const { locale } = context; - const translation = await loadTranslation(locale); - - return { - props: { - breadcrumbTitle, - locale, - translation, - }, - }; -}; - -export default Search; diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx deleted file mode 100644 index 30dd36c..0000000 --- a/src/pages/sujet/[slug].tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { getLayout } from '@components/Layouts/Layout'; -import PostHeader from '@components/PostHeader/PostHeader'; -import PostPreview from '@components/PostPreview/PostPreview'; -import Sidebar from '@components/Sidebar/Sidebar'; -import Spinner from '@components/Spinner/Spinner'; -import { RelatedThematics, ToC, TopicsList } from '@components/Widgets'; -import { - getAllTopics, - getAllTopicsSlug, - getTopicBySlug, -} from '@services/graphql/queries'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { ArticleMeta } from '@ts/types/articles'; -import { TopicProps, ThematicPreview } from '@ts/types/taxonomies'; -import { settings } from '@utils/config'; -import { getFormattedPaths } from '@utils/helpers/format'; -import { loadTranslation } from '@utils/helpers/i18n'; -import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { ParsedUrlQuery } from 'querystring'; -import { useRef } from 'react'; -import { useIntl } from 'react-intl'; -import { Article as Article, Graph, WebPage } from 'schema-dts'; - -const Topic: NextPageWithLayout<TopicProps> = ({ topic, allTopics }) => { - const intl = useIntl(); - const relatedThematics = useRef<ThematicPreview[]>([]); - const router = useRouter(); - - if (router.isFallback) return <Spinner />; - - const updateRelatedThematics = (newThematics: ThematicPreview[]) => { - newThematics.forEach((thematic) => { - const thematicIndex = relatedThematics.current.findIndex( - (relatedThematic) => relatedThematic.id === thematic.id - ); - const hasThematic = thematicIndex === -1 ? false : true; - - if (!hasThematic) relatedThematics.current.push(thematic); - }); - }; - - const getPostsList = () => { - return [...topic.posts].reverse().map((post) => { - updateRelatedThematics(post.thematics); - - return ( - <li key={post.id} className={styles.item}> - <PostPreview post={post} titleLevel={3} /> - </li> - ); - }); - }; - - const meta: ArticleMeta = { - dates: topic.dates, - results: topic.posts.length, - website: topic.officialWebsite, - }; - const topicUrl = `${settings.url}${router.asPath}`; - - const webpageSchema: WebPage = { - '@id': `${topicUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: topic.seo.title, - description: topic.seo.metaDesc, - inLanguage: settings.locales.defaultLocale, - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${settings.url}`, - isPartOf: { - '@id': `${settings.url}`, - }, - }; - - const publicationDate = new Date(topic.dates.publication); - const updateDate = new Date(topic.dates.update); - - const articleSchema: Article = { - '@id': `${settings.url}/#topic`, - '@type': 'Article', - name: topic.title, - description: topic.intro, - author: { '@id': `${settings.url}/#branding` }, - copyrightYear: publicationDate.getFullYear(), - creator: { '@id': `${settings.url}/#branding` }, - dateCreated: publicationDate.toISOString(), - dateModified: updateDate.toISOString(), - datePublished: publicationDate.toISOString(), - editor: { '@id': `${settings.url}/#branding` }, - headline: topic.title, - thumbnailUrl: topic.featuredImage?.sourceUrl, - image: topic.featuredImage?.sourceUrl, - inLanguage: settings.locales.defaultLocale, - isPartOf: { '@id': `${settings.url}/blog` }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${topicUrl}` }, - subjectOf: { '@id': `${settings.url}/blog` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; - - return ( - <> - <Head> - <title>{topic.seo.title}</title> - <meta name="description" content={topic.seo.metaDesc} /> - <meta property="og:url" content={`${topicUrl}`} /> - <meta property="og:type" content="article" /> - <meta property="og:title" content={topic.title} /> - <meta property="og:description" content={topic.intro} /> - <meta property="og:image" content={topic.featuredImage?.sourceUrl} /> - <meta property="og:image:alt" content={topic.featuredImage?.altText} /> - </Head> - <Script - id="schema-subject" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="topic" - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader - cover={topic.featuredImage} - intro={topic.intro} - meta={meta} - title={topic.title} - /> - <Sidebar - position="left" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Table of Contents', - description: 'TopicPage: ToC sidebar aria-label', - id: 'lsDB5G', - })} - > - <ToC /> - </Sidebar> - <div className={styles.body}> - <div dangerouslySetInnerHTML={{ __html: topic.content }}></div> - {topic.posts.length > 0 && ( - <section className={styles.section}> - <h2> - {intl.formatMessage( - { - defaultMessage: 'All posts in {name}', - description: 'TopicPage: posts list title', - id: 'FLkF2R', - }, - { name: topic.title } - )} - </h2> - <ol className={styles.list}>{getPostsList()}</ol> - </section> - )} - </div> - <Sidebar - position="right" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Sidebar', - description: 'TopicPage: right sidebar aria-label', - id: 'eu3beS', - })} - > - <RelatedThematics thematics={relatedThematics.current} /> - <TopicsList - initialData={allTopics} - title={intl.formatMessage({ - defaultMessage: 'Others topics', - description: 'TopicPage: topics list widget title', - id: '+4tiVb', - })} - /> - </Sidebar> - </article> - </> - ); -}; - -Topic.getLayout = getLayout; - -interface PostParams extends ParsedUrlQuery { - slug: string; -} - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const { locale } = context; - const translation = await loadTranslation(locale); - const { slug } = context.params as PostParams; - const topic = await getTopicBySlug(slug); - const allTopics = await getAllTopics(); - const breadcrumbTitle = topic.title; - - return { - props: { - allTopics, - breadcrumbTitle, - locale, - topic, - translation, - }, - }; -}; - -export const getStaticPaths: GetStaticPaths = async () => { - const allTopics = await getAllTopicsSlug(); - const paths = getFormattedPaths(allTopics); - - return { - paths, - fallback: true, - }; -}; - -export default Topic; diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx deleted file mode 100644 index db22214..0000000 --- a/src/pages/thematique/[slug].tsx +++ /dev/null @@ -1,214 +0,0 @@ -import { getLayout } from '@components/Layouts/Layout'; -import PostHeader from '@components/PostHeader/PostHeader'; -import PostPreview from '@components/PostPreview/PostPreview'; -import Sidebar from '@components/Sidebar/Sidebar'; -import Spinner from '@components/Spinner/Spinner'; -import { RelatedTopics, ThematicsList, ToC } from '@components/Widgets'; -import { - getAllThematics, - getAllThematicsSlug, - getThematicBySlug, -} from '@services/graphql/queries'; -import styles from '@styles/pages/Page.module.scss'; -import { NextPageWithLayout } from '@ts/types/app'; -import { ArticleMeta } from '@ts/types/articles'; -import { TopicPreview, ThematicProps } from '@ts/types/taxonomies'; -import { settings } from '@utils/config'; -import { getFormattedPaths } from '@utils/helpers/format'; -import { loadTranslation } from '@utils/helpers/i18n'; -import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next'; -import Head from 'next/head'; -import { useRouter } from 'next/router'; -import Script from 'next/script'; -import { ParsedUrlQuery } from 'querystring'; -import { useRef } from 'react'; -import { useIntl } from 'react-intl'; -import { Article, Graph, WebPage } from 'schema-dts'; - -const Thematic: NextPageWithLayout<ThematicProps> = ({ - thematic, - allThematics, -}) => { - const intl = useIntl(); - const relatedTopics = useRef<TopicPreview[]>([]); - const router = useRouter(); - - if (router.isFallback) return <Spinner />; - - const updateRelatedTopics = (newTopics: TopicPreview[]) => { - newTopics.forEach((topic) => { - const topicIndex = relatedTopics.current.findIndex( - (relatedTopic) => relatedTopic.id === topic.id - ); - const hasTopic = topicIndex === -1 ? false : true; - - if (!hasTopic) relatedTopics.current.push(topic); - }); - }; - - const getPostsList = () => { - return [...thematic.posts].reverse().map((post) => { - updateRelatedTopics(post.topics); - - return ( - <li key={post.id} className={styles.item}> - <PostPreview post={post} titleLevel={3} /> - </li> - ); - }); - }; - - const meta: ArticleMeta = { - dates: thematic.dates, - results: thematic.posts.length, - }; - const thematicUrl = `${settings.url}${router.asPath}`; - - const webpageSchema: WebPage = { - '@id': `${thematicUrl}`, - '@type': 'WebPage', - breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, - name: thematic.seo.title, - description: thematic.seo.metaDesc, - inLanguage: settings.locales.defaultLocale, - reviewedBy: { '@id': `${settings.url}/#branding` }, - url: `${settings.url}`, - }; - - const publicationDate = new Date(thematic.dates.publication); - const updateDate = new Date(thematic.dates.update); - - const articleSchema: Article = { - '@id': `${settings.url}/#thematic`, - '@type': 'Article', - name: thematic.title, - description: thematic.intro, - author: { '@id': `${settings.url}/#branding` }, - copyrightYear: publicationDate.getFullYear(), - creator: { '@id': `${settings.url}/#branding` }, - dateCreated: publicationDate.toISOString(), - dateModified: updateDate.toISOString(), - datePublished: publicationDate.toISOString(), - editor: { '@id': `${settings.url}/#branding` }, - headline: thematic.title, - inLanguage: settings.locales.defaultLocale, - isPartOf: { '@id': `${settings.url}/blog` }, - license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', - mainEntityOfPage: { '@id': `${thematicUrl}` }, - subjectOf: { '@id': `${settings.url}/blog` }, - }; - - const schemaJsonLd: Graph = { - '@context': 'https://schema.org', - '@graph': [webpageSchema, articleSchema], - }; - - return ( - <> - <Head> - <title>{thematic.seo.title}</title> - <meta name="description" content={thematic.seo.metaDesc} /> - <meta property="og:url" content={`${thematic}`} /> - <meta property="og:type" content="article" /> - <meta property="og:title" content={thematic.title} /> - <meta property="og:description" content={thematic.intro} /> - </Head> - <Script - id="schema-thematic" - type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} - /> - <article - id="thematic" - className={`${styles.article} ${styles['article--no-comments']}`} - > - <PostHeader intro={thematic.intro} meta={meta} title={thematic.title} /> - <Sidebar - position="left" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Table of Contents', - description: 'ThematicPage: ToC sidebar aria-label', - id: 'YwvYfw', - })} - > - <ToC /> - </Sidebar> - <div className={styles.body}> - <div dangerouslySetInnerHTML={{ __html: thematic.content }}></div> - {thematic.posts.length > 0 && ( - <section className={styles.section}> - <h2> - {intl.formatMessage( - { - defaultMessage: 'All posts in {name}', - description: 'ThematicPage: posts list title', - id: 'P7fxX2', - }, - { name: thematic.title } - )} - </h2> - <ol className={styles.list}>{getPostsList()}</ol> - </section> - )} - </div> - <Sidebar - position="right" - ariaLabel={intl.formatMessage({ - defaultMessage: 'Sidebar', - description: 'ThematicPage: right sidebar aria-label', - id: 'syLgY9', - })} - > - <RelatedTopics topics={relatedTopics.current} /> - <ThematicsList - initialData={allThematics} - title={intl.formatMessage({ - defaultMessage: 'Others thematics', - description: 'ThematicPage: thematics list widget title', - id: 'norrGp', - })} - /> - </Sidebar> - </article> - </> - ); -}; - -Thematic.getLayout = getLayout; - -interface PostParams extends ParsedUrlQuery { - slug: string; -} - -export const getStaticProps: GetStaticProps = async ( - context: GetStaticPropsContext -) => { - const { locale } = context; - const translation = await loadTranslation(locale); - const { slug } = context.params as PostParams; - const thematic = await getThematicBySlug(slug); - const allThematics = await getAllThematics(); - const breadcrumbTitle = thematic.title; - - return { - props: { - allThematics, - breadcrumbTitle, - locale, - thematic, - translation, - }, - }; -}; - -export const getStaticPaths: GetStaticPaths = async () => { - const allSlugs = await getAllThematicsSlug(); - const paths = getFormattedPaths(allSlugs); - - return { - paths, - fallback: true, - }; -}; - -export default Thematic; diff --git a/src/services/graphql/api.ts b/src/services/graphql/api.ts deleted file mode 100644 index a5be026..0000000 --- a/src/services/graphql/api.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { RequestType, VariablesType } from '@ts/types/app'; -import { settings } from '@utils/config'; -import { GraphQLClient } from 'graphql-request'; - -export const getGraphQLClient = (): GraphQLClient => { - const apiUrl = settings.api.url; - - if (!apiUrl) throw new Error('API URL not defined.'); - - return new GraphQLClient(apiUrl); -}; - -export const fetchApi = async <T extends RequestType>( - query: string, - variables: VariablesType<T> -): Promise<T> => { - const client = getGraphQLClient(); - - try { - return await client.request(query, variables); - } catch (error) { - console.error(error, undefined, 2); - process.exit(1); - } -}; diff --git a/src/services/graphql/mutations.ts b/src/services/graphql/mutations.ts deleted file mode 100644 index c697835..0000000 --- a/src/services/graphql/mutations.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { CommentData, CreateComment, CreatedComment } from '@ts/types/comments'; -import { ContactData, SendEmail } from '@ts/types/contact'; -import { gql } from 'graphql-request'; -import { fetchApi } from './api'; - -//============================================================================== -// Comment mutation -//============================================================================== - -export const createComment = async ( - data: CommentData -): Promise<CreatedComment> => { - const mutation = gql` - mutation CreateComment( - $author: String! - $authorEmail: String! - $authorUrl: String! - $content: String! - $parent: ID! - $commentOn: Int! - $mutationId: String! - ) { - createComment( - input: { - author: $author - authorEmail: $authorEmail - authorUrl: $authorUrl - content: $content - parent: $parent - commentOn: $commentOn - clientMutationId: $mutationId - } - ) { - clientMutationId - success - comment { - approved - } - } - } - `; - - const variables = { ...data }; - const response = await fetchApi<CreateComment>(mutation, variables); - - return response.createComment; -}; - -//============================================================================== -// Contact mutation -//============================================================================== - -export const sendMail = async (data: ContactData) => { - const mutation = gql` - mutation SendEmail( - $subject: String! - $body: String! - $replyTo: String! - $mutationId: String! - ) { - sendEmail( - input: { - clientMutationId: $mutationId - body: $body - replyTo: $replyTo - subject: $subject - } - ) { - clientMutationId - message - sent - origin - replyTo - to - } - } - `; - - const variables = { ...data }; - const response = await fetchApi<SendEmail>(mutation, variables); - return response.sendEmail; -}; diff --git a/src/services/graphql/queries.ts b/src/services/graphql/queries.ts deleted file mode 100644 index 9caf62b..0000000 --- a/src/services/graphql/queries.ts +++ /dev/null @@ -1,535 +0,0 @@ -import { Slug } from '@ts/types/app'; -import { Article, PostBy, TotalArticles } from '@ts/types/articles'; -import { - AllPostsSlug, - LastPostCursor, - PostsList, - RawPostsList, -} from '@ts/types/blog'; -import { Comment, CommentsByPostId } from '@ts/types/comments'; -import { - AllTopics, - AllTopicsSlug, - AllThematics, - AllThematicsSlug, - Topic, - TopicBy, - TopicPreview, - Thematic, - ThematicBy, - ThematicPreview, -} from '@ts/types/taxonomies'; -import { - getFormattedPost, - getFormattedPostPreview, - getFormattedTopic, - getFormattedThematic, - getFormattedComments, - buildCommentsTree, -} from '@utils/helpers/format'; -import { gql } from 'graphql-request'; -import { fetchApi } from './api'; - -//============================================================================== -// Posts list queries -//============================================================================== - -export const getPostsTotal = async (): Promise<number> => { - const query = gql` - query PostsTotal { - posts { - pageInfo { - total - } - } - } - `; - - const response = await fetchApi<TotalArticles>(query, null); - return response.posts.pageInfo.total; -}; - -export const getPublishedPosts = async ({ - first = 10, - after = '', - searchQuery = '', -}: { - first: number; - after?: string; - searchQuery?: string; -}): Promise<PostsList> => { - const query = gql` - query AllPublishedPosts($first: Int, $after: String, $searchQuery: String) { - posts( - after: $after - first: $first - where: { - status: PUBLISH - orderby: { field: DATE, order: DESC } - search: $searchQuery - } - ) { - edges { - cursor - node { - acfPosts { - postsInTopic { - ... on Topic { - databaseId - featuredImage { - node { - altText - sourceUrl - title - } - } - id - slug - title - } - } - postsInThematic { - ... on Thematic { - databaseId - id - slug - title - } - } - } - commentCount - contentParts { - beforeMore - } - date - featuredImage { - node { - altText - sourceUrl - title - } - } - id - info { - readingTime - wordsCount - } - databaseId - modified - slug - title - } - } - pageInfo { - endCursor - hasNextPage - total - } - } - } - `; - - const variables = { first, after, searchQuery }; - const response = await fetchApi<RawPostsList>(query, variables); - const formattedPosts = response.posts.edges.map((post) => { - return getFormattedPostPreview(post.node); - }); - - return { - posts: formattedPosts, - pageInfo: response.posts.pageInfo, - }; -}; - -export const getAllPostsSlug = async (): Promise<Slug[]> => { - // 10 000 is an arbitrary number that I use for small websites. - const query = gql` - query AllPostsSlug { - posts(first: 10000) { - nodes { - slug - } - } - } - `; - - const response = await fetchApi<AllPostsSlug>(query, null); - return response.posts.nodes; -}; - -//============================================================================== -// Single Post query -//============================================================================== - -export const getPostBySlug = async (slug: string): Promise<Article> => { - const query = gql` - query PostBySlug($slug: ID!) { - post(id: $slug, idType: SLUG) { - acfPosts { - postsInTopic { - ... on Topic { - id - featuredImage { - node { - altText - sourceUrl - title - } - } - slug - title - } - } - postsInThematic { - ... on Thematic { - id - slug - title - } - } - } - author { - node { - firstName - lastName - name - } - } - commentCount - contentParts { - afterMore - beforeMore - } - databaseId - date - featuredImage { - node { - altText - sourceUrl - title - } - } - id - info { - readingTime - wordsCount - } - modified - seo { - metaDesc - title - } - title - } - } - `; - const variables = { slug }; - const response = await fetchApi<PostBy>(query, variables); - - return getFormattedPost(response.post); -}; - -//============================================================================== -// Comments query -//============================================================================== - -export const getCommentsByPostId = async (id: number): Promise<Comment[]> => { - const query = gql` - query PostComments($id: ID!) { - comments(where: { contentId: $id, order: ASC, orderby: COMMENT_DATE }) { - nodes { - approved - author { - node { - databaseId - gravatarUrl - name - url - } - } - content - databaseId - date - parentDatabaseId - } - } - } - `; - - const variables = { id }; - const response = await fetchApi<CommentsByPostId>(query, variables); - const formattedComments = getFormattedComments(response.comments.nodes); - - return buildCommentsTree(formattedComments); -}; - -//============================================================================== -// Topic query -//============================================================================== - -export const getTopicBySlug = async (slug: string): Promise<Topic> => { - const query = gql` - query TopicBySlug($slug: ID!) { - topic(id: $slug, idType: SLUG) { - acfTopics { - officialWebsite - postsInTopic { - ... on Post { - acfPosts { - postsInTopic { - ... on Topic { - databaseId - featuredImage { - node { - altText - sourceUrl - title - } - } - id - slug - title - } - } - postsInThematic { - ... on Thematic { - databaseId - id - slug - title - } - } - } - id - info { - readingTime - wordsCount - } - commentCount - contentParts { - beforeMore - } - databaseId - date - featuredImage { - node { - altText - sourceUrl - title - } - } - modified - slug - title - } - } - } - contentParts { - afterMore - beforeMore - } - databaseId - date - featuredImage { - node { - altText - sourceUrl - title - } - } - id - info { - readingTime - wordsCount - } - modified - seo { - metaDesc - title - } - title - } - } - `; - const variables = { slug }; - const response = await fetchApi<TopicBy>(query, variables); - - return getFormattedTopic(response.topic); -}; - -export const getAllTopicsSlug = async (): Promise<Slug[]> => { - // 10 000 is an arbitrary number that I use for small websites. - const query = gql` - query AllTopicsSlug { - topics(first: 10000) { - nodes { - slug - } - } - } - `; - const response = await fetchApi<AllTopicsSlug>(query, null); - return response.topics.nodes; -}; - -export const getAllTopics = async (): Promise<TopicPreview[]> => { - // 10 000 is an arbitrary number that I use for small websites. - const query = gql` - query AllTopics { - topics(first: 10000, where: { orderby: { field: TITLE, order: ASC } }) { - nodes { - databaseId - slug - title - } - } - } - `; - - const response = await fetchApi<AllTopics>(query, null); - return response.topics.nodes; -}; - -//============================================================================== -// Thematic query -//============================================================================== - -export const getThematicBySlug = async (slug: string): Promise<Thematic> => { - const query = gql` - query ThematicBySlug($slug: ID!) { - thematic(id: $slug, idType: SLUG) { - acfThematics { - postsInThematic { - ... on Post { - acfPosts { - postsInTopic { - ... on Topic { - databaseId - featuredImage { - node { - altText - sourceUrl - title - } - } - id - slug - title - } - } - postsInThematic { - ... on Thematic { - databaseId - id - slug - title - } - } - } - id - info { - readingTime - wordsCount - } - commentCount - contentParts { - beforeMore - } - databaseId - date - featuredImage { - node { - altText - sourceUrl - title - } - } - modified - slug - title - } - } - } - contentParts { - afterMore - beforeMore - } - databaseId - date - id - info { - readingTime - wordsCount - } - modified - seo { - metaDesc - title - } - title - } - } - `; - const variables = { slug }; - const response = await fetchApi<ThematicBy>(query, variables); - - return getFormattedThematic(response.thematic); -}; - -export const getAllThematicsSlug = async (): Promise<Slug[]> => { - // 10 000 is an arbitrary number that I use for small websites. - const query = gql` - query AllThematicsSlug { - thematics(first: 10000) { - nodes { - slug - } - } - } - `; - const response = await fetchApi<AllThematicsSlug>(query, null); - return response.thematics.nodes; -}; - -export const getAllThematics = async (): Promise<ThematicPreview[]> => { - // 10 000 is an arbitrary number that I use for small websites. - const query = gql` - query AllThematics { - thematics( - first: 10000 - where: { orderby: { field: TITLE, order: ASC } } - ) { - nodes { - databaseId - slug - title - } - } - } - `; - - const response = await fetchApi<AllThematics>(query, null); - return response.thematics.nodes; -}; - -export const getEndCursor = async ({ - first = 10, -}: { - first: number; -}): Promise<string> => { - const query = gql` - query EndCursorAfter($first: Int) { - posts(first: $first) { - pageInfo { - hasNextPage - endCursor - } - } - } - `; - - const variables = { first }; - const response = await fetchApi<LastPostCursor>(query, variables); - - return response.posts.pageInfo.endCursor; -}; 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<P = {}> = NextPage<P> & { - getLayout?: (page: ReactElement) => ReactNode; -}; - -export type AppPropsWithLayout = AppProps & { - Component: NextPageWithLayout; -}; - -//============================================================================== -// API -//============================================================================== - -export type VariablesType<T> = 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<Meta, 'title'> & { - 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<ContentParts, 'beforeMore'>; - 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<Comment, 'author' | 'replies'> & { - 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; -}; diff --git a/src/utils/helpers/format.ts b/src/utils/helpers/format.ts deleted file mode 100644 index 47a7b57..0000000 --- a/src/utils/helpers/format.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { ParamsIds, ParamsSlug, Slug } from '@ts/types/app'; -import { - Article, - ArticlePreview, - RawArticle, - RawArticlePreview, -} from '@ts/types/articles'; -import { Comment, RawComment } from '@ts/types/comments'; -import { - RawTopic, - RawTopicPreview, - RawThematic, - Topic, - TopicPreview, - Thematic, -} from '@ts/types/taxonomies'; -import { settings } from '@utils/config'; - -/** - * Format a post preview from RawArticlePreview to ArticlePreview type. - * @param rawPost - A post preview coming from WP GraphQL. - * @returns A formatted post preview. - */ -export const getFormattedPostPreview = (rawPost: RawArticlePreview) => { - const { - acfPosts, - commentCount, - contentParts, - date, - featuredImage, - id, - info, - modified, - slug, - title, - } = rawPost; - - const dates = { - publication: date, - update: modified, - }; - - const topics = acfPosts.postsInTopic ? acfPosts.postsInTopic : []; - const thematics = acfPosts.postsInThematic ? acfPosts.postsInThematic : []; - - const formattedPost: ArticlePreview = { - commentCount, - dates, - featuredImage: featuredImage ? featuredImage.node : null, - id, - info, - intro: contentParts.beforeMore, - slug, - topics, - thematics, - title, - }; - - return formattedPost; -}; - -/** - * Format an array of posts list from RawArticlePreview to ArticlePreview type. - * @param rawPosts - A posts list coming from WP GraphQL. - * @returns A formatted posts list. - */ -export const getFormattedPostsList = ( - rawPosts: RawArticlePreview[] -): ArticlePreview[] => { - return rawPosts - .filter((post) => Object.getOwnPropertyNames(post).length > 0) - .map((post) => { - return getFormattedPostPreview(post); - }); -}; - -/** - * Format a topic from RawTopic to Topic type. - * @param rawTopic - A topic coming from WP GraphQL. - * @returns A formatted topic. - */ -export const getFormattedTopic = (rawTopic: RawTopic): Topic => { - const { - acfTopics, - contentParts, - databaseId, - date, - featuredImage, - id, - info, - modified, - seo, - title, - } = rawTopic; - - const dates = { - publication: date, - update: modified, - }; - - const posts = getFormattedPostsList(acfTopics.postsInTopic); - - const formattedTopic: Topic = { - content: contentParts.afterMore, - databaseId, - dates, - featuredImage: featuredImage ? featuredImage.node : null, - id, - info, - intro: contentParts.beforeMore, - officialWebsite: acfTopics.officialWebsite, - posts, - seo, - title, - }; - - return formattedTopic; -}; - -/** - * Format a thematic from RawThematic to Thematic type. - * @param rawThematic - A thematic coming from wP GraphQL. - * @returns A formatted thematic. - */ -export const getFormattedThematic = (rawThematic: RawThematic): Thematic => { - const { - acfThematics, - contentParts, - databaseId, - date, - id, - info, - modified, - seo, - title, - } = rawThematic; - - const dates = { - publication: date, - update: modified, - }; - - const posts = getFormattedPostsList(acfThematics.postsInThematic); - - const formattedThematic: Thematic = { - content: contentParts.afterMore, - databaseId, - dates, - id, - info, - intro: contentParts.beforeMore, - posts, - seo, - title, - }; - - return formattedThematic; -}; - -/** - * Format a comments list from RawComment to Comment type. - * @param rawComments - A comments list coming from WP GraphQL. - * @returns A formatted comments list. - */ -export const getFormattedComments = (rawComments: RawComment[]): Comment[] => { - const formattedComments: Comment[] = rawComments.map((comment) => { - const formattedComment: Comment = { - ...comment, - author: comment.author.node, - replies: [], - }; - - return formattedComment; - }); - - return formattedComments; -}; - -/** - * Create a comments tree with replies. - * @param comments - A flatten comments list. - * @returns An array of comments with replies. - */ -export const buildCommentsTree = (comments: Comment[]) => { - type CommentsHashTable = { - [key: string]: Comment; - }; - - const hashTable: CommentsHashTable = Object.create(null); - const commentsTree: Comment[] = []; - - comments.forEach( - (comment) => (hashTable[comment.databaseId] = { ...comment, replies: [] }) - ); - - comments.forEach((comment) => { - if (!comment.parentDatabaseId) { - commentsTree.push(hashTable[comment.databaseId]); - } else { - hashTable[comment.parentDatabaseId].replies.push( - hashTable[comment.databaseId] - ); - } - }); - - return commentsTree; -}; - -export const getFormattedTopicsPreview = ( - topics: RawTopicPreview[] -): TopicPreview[] => { - const formattedTopics: TopicPreview[] = topics.map((topic) => { - return { - ...topic, - featuredImage: topic.featuredImage ? topic.featuredImage.node : null, - }; - }); - - return formattedTopics; -}; - -/** - * Format an article from RawArticle to Article type. - * @param rawPost - An article coming from WP GraphQL. - * @returns A formatted article. - */ -export const getFormattedPost = (rawPost: RawArticle): Article => { - const { - acfPosts, - author, - commentCount, - contentParts, - databaseId, - date, - featuredImage, - id, - info, - modified, - seo, - title, - } = rawPost; - - const dates = { - publication: date, - update: modified, - }; - - const topics = acfPosts.postsInTopic - ? getFormattedTopicsPreview(acfPosts.postsInTopic) - : []; - - const formattedPost: Article = { - author: author.node, - commentCount, - content: contentParts.afterMore, - databaseId, - dates, - featuredImage: featuredImage ? featuredImage.node : null, - id, - info, - intro: contentParts.beforeMore, - seo, - topics, - thematics: acfPosts.postsInThematic ? acfPosts.postsInThematic : [], - title, - }; - - return formattedPost; -}; - -/** - * Converts a date to a string by using the specified locale. - * @param {string} date - The date. - * @param {string} [locale] - A locale. - * @returns {string} The formatted date to 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); -}; - -/** - * Converts a date to a time string by using the specified locale. - * @param {string} date - The date. - * @param {string} [locale] - A locale. - * @returns {string} The formatted time to locale date string. - */ -export const getFormattedTime = ( - date: string, - locale: string = settings.locales.defaultLocale -): string => { - const time = new Date(date).toLocaleTimeString(locale, { - hour: 'numeric', - minute: 'numeric', - }); - - return locale === 'fr' ? time.replace(':', 'h') : time; -}; - -/** - * Convert an array of slugs to an array of params with slug. - * @param {Slug} array - An array of object with slug. - * @returns {ParamsSlug} An array of params with slug. - */ -export const getFormattedPaths = (array: Slug[]): ParamsSlug[] => { - return array.map((object) => { - return { params: { slug: object.slug } }; - }); -}; - -/** - * Convert a number of pages to an array of params with ids. - * @param {number} totalPages - The total pages. - * @returns {ParamsIds} An array of params with ids. - */ -export const getFormattedPageNumbers = (totalPages: number): ParamsIds[] => { - const paths = []; - - for (let i = 1; i <= totalPages; i++) { - paths.push({ params: { id: `${i}` } }); - } - - return paths; -}; diff --git a/src/utils/helpers/i18n.ts b/src/utils/helpers/i18n.ts index c4734ad..5d19c8c 100644 --- a/src/utils/helpers/i18n.ts +++ b/src/utils/helpers/i18n.ts @@ -3,7 +3,7 @@ import { settings } from '@utils/config'; import { readFile } from 'fs/promises'; import path from 'path'; -type Messages = { [key: string]: string }; +export type Messages = { [key: string]: string }; export const defaultLocale = settings.locales.defaultLocale; diff --git a/src/utils/helpers/prism.ts b/src/utils/helpers/prism.ts deleted file mode 100644 index a5f5787..0000000 --- a/src/utils/helpers/prism.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Check if the current block has a defined language. - * @param classList - A list of class. - * @returns {boolean} - True if a class starts with "language-". - */ -const isLanguageBlock = (classList: DOMTokenList) => { - const classes = Array.from(classList); - return classes.some((className) => /language-.*/.test(className)); -}; - -/** - * Add automatically some classes and attributes for PrismJs. - * - * These classes and attributes are needed by Prism or to customize comments. - */ -export const addPrismClasses = () => { - const preTags = document.getElementsByTagName('pre'); - - Array.from(preTags).forEach((preTag) => { - if (!isLanguageBlock(preTag.classList)) return; - - preTag.classList.add('match-braces'); - - if (preTag.classList.contains('filter-output')) { - preTag.setAttribute('data-filter-output', '#output#'); - } - - if (preTag.classList.contains('language-bash')) { - preTag.classList.add('command-line'); - } else if (!preTag.classList.contains('language-diff')) { - preTag.classList.add('line-numbers'); - } - }); -}; diff --git a/src/utils/helpers/projects.ts b/src/utils/helpers/projects.ts deleted file mode 100644 index 1612dae..0000000 --- a/src/utils/helpers/projects.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Project, ProjectMeta } from '@ts/types/app'; -import { readdirSync } from 'fs'; -import path from 'path'; - -/** - * Retrieve project's data by id. - * @param {string} id - The filename without extension. - * @returns {Promise<Project>} - The project data. - */ -export const getProjectData = async (id: string): Promise<Project> => { - try { - const { - intro, - meta, - seo, - tagline, - }: { - intro: string; - meta: ProjectMeta & { title: string }; - seo: { title: string; description: string }; - tagline?: string; - } = await import(`../../content/projects/${id}.mdx`); - - const { title, ...onlyMeta } = meta; - - return { - id, - intro: intro || '', - meta: onlyMeta || {}, - slug: id, - title, - seo: seo || {}, - tagline: tagline || '', - }; - } catch (err) { - console.error(err); - throw err; - } -}; - -/** - * Retrieve the projects data from filenames. - * @param {string[]} filenames - An array of filenames. - * @returns {Promise<Project[]>} An array of projects with meta. - */ -const getProjectsWithMeta = async (filenames: string[]): Promise<Project[]> => { - return Promise.all( - filenames.map(async (filename) => { - return getProjectData(filename); - }) - ); -}; - -/** - * Method to sort an array of projects by publication date. - * @param {Project} a - A single project. - * @param {Project} b - A single project. - * @returns The result used by Array.sort() method: 1 || -1 || 0. - */ -const sortProjectByPublicationDate = (a: Project, b: Project) => { - if (a.meta.publishedOn < b.meta.publishedOn) return 1; - if (a.meta.publishedOn > b.meta.publishedOn) return -1; - return 0; -}; - -/** - * Retrieve all the projects filename. - * @returns {string[]} An array of filenames. - */ -export const getAllProjectsFilename = (): string[] => { - const projectsDirectory = path.join(process.cwd(), 'src/content/projects'); - const filenames = readdirSync(projectsDirectory); - - return filenames.map((filename) => filename.replace(/\.mdx$/, '')); -}; - -/** - * Retrieve all projects in content folder sorted by publication date. - * @returns {Promise<Project[]>} An array of projects. - */ -export const getSortedProjects = async (): Promise<Project[]> => { - const filenames = getAllProjectsFilename(); - const projects = await getProjectsWithMeta(filenames); - - return [...projects].sort(sortProjectByPublicationDate); -}; diff --git a/src/utils/helpers/sort.ts b/src/utils/helpers/sort.ts deleted file mode 100644 index c1ee35d..0000000 --- a/src/utils/helpers/sort.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ArticlePreview } from '@ts/types/articles'; -import { PostsList } from '@ts/types/blog'; - -type YearCollection = { - [key: string]: ArticlePreview[]; -}; - -export const sortPostsByYear = (data: PostsList[]) => { - const yearCollection: YearCollection = {}; - - data.forEach((page) => { - page.posts.forEach((post) => { - const postYear = new Date(post.dates.publication) - .getFullYear() - .toString(); - yearCollection[postYear] = [...(yearCollection[postYear] || []), post]; - }); - }); - - return yearCollection; -}; diff --git a/src/utils/hooks/useGithubApi.tsx b/src/utils/hooks/useGithubApi.tsx deleted file mode 100644 index 4b0b3b2..0000000 --- a/src/utils/hooks/useGithubApi.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { RepoData } from '@ts/types/repos'; -import useSWR, { Fetcher } from 'swr'; - -const fetcher: Fetcher<RepoData, string> = (...args) => - fetch(...args).then((res) => res.json()); - -/** - * Retrieve data from Github API. - * @param repo The repo name. Format: "User/project-slug". - * @returns {object} The data and two booleans to determine if is loading/error. - */ -const useGithubApi = (repo: string) => { - const apiUrl = repo ? `https://api.github.com/repos/${repo}` : null; - const { data, error } = useSWR<RepoData>(apiUrl, fetcher); - - return { - data, - isLoading: !error && !data, - isError: error, - }; -}; - -export default useGithubApi; diff --git a/src/utils/hooks/useHeadingsTree.tsx b/src/utils/hooks/useHeadingsTree.tsx deleted file mode 100644 index f2be406..0000000 --- a/src/utils/hooks/useHeadingsTree.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { Heading } from '@ts/types/app'; -import { slugify } from '@utils/helpers/slugify'; -import { useRouter } from 'next/router'; -import { useCallback, useEffect, useMemo, useState } from 'react'; - -const useHeadingsTree = (wrapper: string) => { - const router = useRouter(); - const depths = useMemo(() => ['h2', 'h3', 'h4', 'h5', 'h6'], []); - const [allHeadings, setAllHeadings] = - useState<NodeListOf<HTMLHeadingElement>>(); - - useEffect(() => { - const query = depths - .map((depth) => `${wrapper} > *:not(aside, #comments) ${depth}`) - .join(', '); - const result: NodeListOf<HTMLHeadingElement> = - document.querySelectorAll(query); - setAllHeadings(result); - }, [depths, wrapper, router.asPath]); - - const [headingsTree, setHeadingsTree] = useState<Heading[]>([]); - - const getElementDepth = useCallback( - (el: HTMLHeadingElement) => { - return depths.findIndex((depth) => depth === el.localName); - }, - [depths] - ); - - const formatHeadings = useCallback( - (headings: NodeListOf<HTMLHeadingElement>): Heading[] => { - const formattedHeadings: Heading[] = []; - - Array.from(headings).forEach((heading) => { - const title: string = heading.textContent!; - const id = slugify(title); - const depth = getElementDepth(heading); - const children: Heading[] = []; - - heading.id = id; - - formattedHeadings.push({ - depth, - id, - children, - title, - }); - }); - - return formattedHeadings; - }, - [getElementDepth] - ); - - const buildSubTree = useCallback( - (parent: Heading, currentHeading: Heading): void => { - if (parent.depth === currentHeading.depth - 1) { - parent.children.push(currentHeading); - } else { - const lastItem = parent.children[parent.children.length - 1]; - buildSubTree(lastItem, currentHeading); - } - }, - [] - ); - - const buildTree = useCallback( - (headings: Heading[]): Heading[] => { - const tree: Heading[] = []; - - headings.forEach((heading) => { - if (heading.depth === 0) { - tree.push(heading); - } else { - const lastItem = tree[tree.length - 1]; - buildSubTree(lastItem, heading); - } - }); - - return tree; - }, - [buildSubTree] - ); - - const getHeadingsList = useCallback( - (headings: NodeListOf<HTMLHeadingElement>): Heading[] => { - const formattedHeadings = formatHeadings(headings); - - return buildTree(formattedHeadings); - }, - [formatHeadings, buildTree] - ); - - useEffect(() => { - if (allHeadings) { - const headingsList = getHeadingsList(allHeadings); - setHeadingsTree(headingsList); - } - }, [allHeadings, getHeadingsList]); - - return headingsTree; -}; - -export default useHeadingsTree; diff --git a/src/utils/providers/ackee.tsx b/src/utils/providers/ackee.tsx index c103668..0cb0166 100644 --- a/src/utils/providers/ackee.tsx +++ b/src/utils/providers/ackee.tsx @@ -1,5 +1,5 @@ import { useRouter } from 'next/router'; -import { createContext, FC, useContext, useState } from 'react'; +import { createContext, FC, ReactNode, useContext, useState } from 'react'; import useAckee from 'use-ackee'; export type AckeeProps = { @@ -10,6 +10,7 @@ export type AckeeProps = { }; export type AckeeProviderProps = { + children: ReactNode; domain: string; siteId: string; ignoreLocalhost?: boolean; diff --git a/src/utils/providers/prism-theme.tsx b/src/utils/providers/prism-theme.tsx index 2ed8454..62bcf56 100644 --- a/src/utils/providers/prism-theme.tsx +++ b/src/utils/providers/prism-theme.tsx @@ -2,6 +2,7 @@ import { LocalStorage } from '@services/local-storage'; import { createContext, FC, + ReactNode, useCallback, useContext, useEffect, @@ -22,6 +23,7 @@ export type UsePrismThemeProps = { export type PrismThemeProviderProps = { attribute?: string; + children: ReactNode; storageKey?: string; themes?: PrismTheme[]; }; |
