From a84560d86ea5e84fd9a39b62e7d328684c41ab04 Mon Sep 17 00:00:00 2001 From: DrMint Date: Tue, 12 Apr 2022 12:25:40 +0200 Subject: [PATCH] Added smart language switch for posts ad hoc page and content --- .eslintrc.js | 2 +- src/components/Button.tsx | 47 ++- src/components/Content/ThumbnailHeader.tsx | 5 +- src/components/LanguageSwitcher.tsx | 34 +- src/components/Post.tsx | 230 ++++++++++++ src/graphql/operations/getContentText.graphql | 10 +- .../operations/getLibraryItemScans.graphql | 13 +- src/graphql/operations/getPost.graphql | 4 +- src/pages/about-us/accords-handbook.tsx | 68 +--- src/pages/about-us/contact.tsx | 330 ++++++++---------- src/pages/about-us/legality.tsx | 68 +--- src/pages/about-us/sharing-policy.tsx | 68 +--- src/pages/contents/[slug]/index.tsx | 165 +++++---- src/pages/index.tsx | 54 ++- src/pages/news/[slug].tsx | 131 +------ src/queries/helpers.ts | 12 + 16 files changed, 612 insertions(+), 629 deletions(-) create mode 100644 src/components/Post.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 3285bef..dd49b89 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -171,7 +171,7 @@ module.exports = { "@typescript-eslint/no-require-imports": "error", // "@typescript-eslint/no-type-alias": "warn", "@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn", - // "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/no-unnecessary-condition": "warn", "@typescript-eslint/no-unnecessary-qualifier": "warn", "@typescript-eslint/no-unnecessary-type-arguments": "warn", "@typescript-eslint/prefer-enum-initializers": "error", diff --git a/src/components/Button.tsx b/src/components/Button.tsx index c5aa7d6..0c2b0eb 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -11,42 +11,61 @@ interface Props { target?: "_blank"; onClick?: MouseEventHandler; draggable?: boolean; + badgeNumber?: number; } export default function Button(props: Props): JSX.Element { + const { + draggable, + id, + onClick, + active, + className, + children, + target, + href, + locale, + badgeNumber, + } = props; const router = useRouter(); const button = (
- {props.children} + {badgeNumber && ( +
+ {badgeNumber} +
+ )} + {children}
); - if (props.target) { + if (target) { return ( - -
{button}
+
+
{button}
); } return (
{ - if (props.href || props.locale) - router.push(props.href ?? router.asPath, props.href, { - locale: props.locale, + if (href || locale) + router.push(href ?? router.asPath, href, { + locale: locale, }); }} > diff --git a/src/components/Content/ThumbnailHeader.tsx b/src/components/Content/ThumbnailHeader.tsx index 0d41c49..b4608d5 100644 --- a/src/components/Content/ThumbnailHeader.tsx +++ b/src/components/Content/ThumbnailHeader.tsx @@ -26,6 +26,7 @@ interface Props { >["categories"]; thumbnail?: UploadImageFragment | null | undefined; langui: AppStaticProps["langui"]; + languageSwitcher?: JSX.Element; } export default function ThumbnailHeader(props: Props): JSX.Element { @@ -38,6 +39,7 @@ export default function ThumbnailHeader(props: Props): JSX.Element { type, categories, description, + languageSwitcher, } = props; return ( @@ -67,7 +69,7 @@ export default function ThumbnailHeader(props: Props): JSX.Element {
-
+
{type?.data?.attributes && (

{langui.type}

@@ -92,6 +94,7 @@ export default function ThumbnailHeader(props: Props): JSX.Element {
)} + {languageSwitcher}
{description && {description}} diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx index 79cb1f9..42ad095 100644 --- a/src/components/LanguageSwitcher.tsx +++ b/src/components/LanguageSwitcher.tsx @@ -1,33 +1,31 @@ -import { useRouter } from "next/router"; import { AppStaticProps } from "queries/getAppStaticProps"; import { prettyLanguage } from "queries/helpers"; +import { Dispatch, SetStateAction } from "react"; import Button from "./Button"; +import ToolTip from "./ToolTip"; interface Props { className?: string; - locales: (string | undefined)[]; languages: AppStaticProps["languages"]; - langui: AppStaticProps["langui"]; - href?: string; + locales: Map; + localesIndex: number | undefined; + setLocalesIndex: Dispatch>; } export default function LanguageSwitcher(props: Props): JSX.Element { - const { locales, langui, href } = props; - const router = useRouter(); + const { locales, localesIndex, setLocalesIndex } = props; return ( -
-
-

{langui.language_switch_message}

-
- {locales.map((locale, index) => ( + + {[...locales].map(([locale, value], index) => ( <> {locale && ( @@ -35,7 +33,11 @@ export default function LanguageSwitcher(props: Props): JSX.Element { ))}
-
-
+ } + > + + ); } diff --git a/src/components/Post.tsx b/src/components/Post.tsx new file mode 100644 index 0000000..475bd59 --- /dev/null +++ b/src/components/Post.tsx @@ -0,0 +1,230 @@ +import { useAppLayout } from "contexts/AppLayoutContext"; +import { GetPostQuery } from "graphql/generated"; +import { useRouter } from "next/router"; +import { AppStaticProps } from "queries/getAppStaticProps"; +import { + getPreferredLanguage, + getStatusDescription, + prettySlug, +} from "queries/helpers"; +import { useEffect, useMemo, useState } from "react"; +import AppLayout from "./AppLayout"; +import Chip from "./Chip"; +import ThumbnailHeader from "./Content/ThumbnailHeader"; +import HorizontalLine from "./HorizontalLine"; +import LanguageSwitcher from "./LanguageSwitcher"; +import Markdawn from "./Markdown/Markdawn"; +import TOC from "./Markdown/TOC"; +import ReturnButton, { ReturnButtonType } from "./PanelComponents/ReturnButton"; +import ContentPanel from "./Panels/ContentPanel"; +import SubPanel from "./Panels/SubPanel"; +import RecorderChip from "./RecorderChip"; +import ToolTip from "./ToolTip"; + +interface Props { + post: Exclude< + GetPostQuery["posts"], + null | undefined + >["data"][number]["attributes"]; + langui: AppStaticProps["langui"]; + languages: AppStaticProps["languages"]; + currencies: AppStaticProps["currencies"]; + returnHref?: string; + returnTitle?: string | null | undefined; + displayCredits?: boolean; + displayToc?: boolean; + displayThumbnailHeader?: boolean; + displayTitle?: boolean; + displayLanguageSwitcher?: boolean; + prependBody?: JSX.Element; + appendBody?: JSX.Element; +} + +export default function Post(props: Props): JSX.Element { + const { + post, + langui, + languages, + returnHref, + returnTitle, + displayCredits, + displayToc, + displayThumbnailHeader, + displayLanguageSwitcher, + appendBody, + prependBody, + } = props; + const displayTitle = props.displayTitle ?? true; + + const appLayout = useAppLayout(); + const router = useRouter(); + + const [selectedTranslation, setSelectedTranslation] = useState< + | Exclude< + Exclude["translations"], + null | undefined + >[number] + >(); + const translationLocales: Map = new Map(); + + const [selectedTranslationIndex, setSelectedTranslationIndex] = useState< + number | undefined + >(); + + if (post?.translations) { + post.translations.map((translation, index) => { + if (translation?.language?.data?.attributes?.code) { + translationLocales.set( + translation.language.data.attributes.code, + index + ); + } + }); + } + + useMemo(() => { + setSelectedTranslationIndex( + getPreferredLanguage( + appLayout.preferredLanguages ?? [router.locale], + translationLocales + ) + ); + }, [appLayout.preferredLanguages]); + + useEffect(() => { + if (selectedTranslationIndex !== undefined) + setSelectedTranslation(post?.translations?.[selectedTranslationIndex]); + }, [selectedTranslationIndex]); + + const thumbnail = + selectedTranslation?.thumbnail?.data?.attributes ?? + post?.thumbnail?.data?.attributes; + + const body = selectedTranslation?.body ?? ""; + const title = selectedTranslation?.title ?? prettySlug(post?.slug); + const except = selectedTranslation?.excerpt ?? ""; + + const subPanel = + returnHref || returnTitle || displayCredits || displayToc ? ( + + {returnHref && returnTitle && ( + + )} + + {displayCredits && ( + <> + {selectedTranslation && ( +
+

{langui.status}:

+ + + {selectedTranslation.status} + +
+ )} + + {post?.authors && post.authors.data.length > 0 && ( +
+

{"Authors"}:

+
+ {post.authors.data.map((author) => ( + <> + {author.attributes && ( + + )} + + ))} +
+
+ )} + + + + )} + + {displayToc && } +
+ ) : undefined; + + const contentPanel = ( + + + + {displayThumbnailHeader ? ( + <> + + } + /> + + + + ) : ( + <> + {displayLanguageSwitcher && ( +
+ +
+ )} + {displayTitle && ( +

+ {title} +

+ )} + + )} + + {prependBody} + + {appendBody} +
+ ); + + return ( + + ); +} diff --git a/src/graphql/operations/getContentText.graphql b/src/graphql/operations/getContentText.graphql index ad719c7..7f9c102 100644 --- a/src/graphql/operations/getContentText.graphql +++ b/src/graphql/operations/getContentText.graphql @@ -4,7 +4,7 @@ query getContentText($slug: String, $language_code: String) { id attributes { slug - titles(filters: { language: { code: { eq: $language_code } } }) { + titles { pre_title title subtitle @@ -56,7 +56,9 @@ query getContentText($slug: String, $language_code: String) { } } } - text_set_languages: text_set { + text_set { + status + text language { data { attributes { @@ -64,10 +66,6 @@ query getContentText($slug: String, $language_code: String) { } } } - } - text_set(filters: { language: { code: { eq: $language_code } } }) { - status - text source_language { data { attributes { diff --git a/src/graphql/operations/getLibraryItemScans.graphql b/src/graphql/operations/getLibraryItemScans.graphql index 3889ece..3f8f4fc 100644 --- a/src/graphql/operations/getLibraryItemScans.graphql +++ b/src/graphql/operations/getLibraryItemScans.graphql @@ -29,7 +29,8 @@ query getLibraryItemScans($slug: String, $language_code: String) { ending_time } } - scan_set_languages: scan_set { + scan_set { + status language { data { attributes { @@ -37,16 +38,6 @@ query getLibraryItemScans($slug: String, $language_code: String) { } } } - } - scan_set( - filters: { - or: [ - { language: { code: { eq: "xx" } } } - { language: { code: { eq: $language_code } } } - ] - } - ) { - status source_language { data { attributes { diff --git a/src/graphql/operations/getPost.graphql b/src/graphql/operations/getPost.graphql index c407296..89b5066 100644 --- a/src/graphql/operations/getPost.graphql +++ b/src/graphql/operations/getPost.graphql @@ -33,7 +33,7 @@ query getPost($slug: String, $language_code: String) { } } } - translations_languages: translations { + translations { language { data { attributes { @@ -41,8 +41,6 @@ query getPost($slug: String, $language_code: String) { } } } - } - translations(filters: { language: { code: { eq: $language_code } } }) { status title excerpt diff --git a/src/pages/about-us/accords-handbook.tsx b/src/pages/about-us/accords-handbook.tsx index 64608dc..2faba56 100644 --- a/src/pages/about-us/accords-handbook.tsx +++ b/src/pages/about-us/accords-handbook.tsx @@ -1,18 +1,8 @@ -import AppLayout from "components/AppLayout"; -import LanguageSwitcher from "components/LanguageSwitcher"; -import Markdawn from "components/Markdown/Markdawn"; -import TOC from "components/Markdown/TOC"; -import ReturnButton, { - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; -import ContentPanel from "components/Panels/ContentPanel"; -import SubPanel from "components/Panels/SubPanel"; +import Post from "components/Post"; import { GetPostQuery } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; import { GetStaticPropsContext } from "next"; -import { useRouter } from "next/router"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; -import { getLocalesFromLanguages, prettySlug } from "queries/helpers"; interface Props extends AppStaticProps { post: Exclude< @@ -22,53 +12,17 @@ interface Props extends AppStaticProps { } export default function AccordsHandbook(props: Props): JSX.Element { - const { langui, post } = props; - const router = useRouter(); - const locales = getLocalesFromLanguages(post?.translations_languages); - - const body = post?.translations?.[0]?.body ?? ""; - const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug); - - const subPanel = ( - - - - - ); - - const contentPanel = ( - - - {locales.includes(router.locale ?? "en") ? ( - - ) : ( - - )} - - ); - + const { post, langui, languages, currencies } = props; return ( - ); } diff --git a/src/pages/about-us/contact.tsx b/src/pages/about-us/contact.tsx index 6aaa93e..ea1477e 100644 --- a/src/pages/about-us/contact.tsx +++ b/src/pages/about-us/contact.tsx @@ -1,24 +1,12 @@ -import AppLayout from "components/AppLayout"; import InsetBox from "components/InsetBox"; -import LanguageSwitcher from "components/LanguageSwitcher"; -import Markdawn from "components/Markdown/Markdawn"; -import TOC from "components/Markdown/TOC"; -import ReturnButton, { - ReturnButtonType, -} from "components/PanelComponents/ReturnButton"; -import ContentPanel from "components/Panels/ContentPanel"; -import SubPanel from "components/Panels/SubPanel"; +import Post from "components/Post"; import { GetPostQuery } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; import { GetStaticPropsContext } from "next"; import { useRouter } from "next/router"; import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; -import { - getLocalesFromLanguages, - prettySlug, - randomInt, -} from "queries/helpers"; +import { randomInt } from "queries/helpers"; import { useState } from "react"; interface Props extends AppStaticProps { @@ -29,200 +17,170 @@ interface Props extends AppStaticProps { } export default function AboutUs(props: Props): JSX.Element { - const { langui, post } = props; + const { post, langui, languages, currencies } = props; + const router = useRouter(); const [formResponse, setFormResponse] = useState(""); const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">( "stale" ); - const locales = getLocalesFromLanguages(post?.translations_languages); const [randomNumber1, setRandomNumber1] = useState(randomInt(0, 10)); const [randomNumber2, setRandomNumber2] = useState(randomInt(0, 10)); - const body = post?.translations?.[0]?.body ?? ""; - const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug); + const contactForm = ( +
+
{ + event.preventDefault(); - const subPanel = ( - - - - - ); + const fields = event.target as unknown as { + verif: HTMLInputElement; + name: HTMLInputElement; + email: HTMLInputElement; + message: HTMLInputElement; + }; - const contentPanel = ( - - - {locales.includes(router.locale ?? "en") ? ( - - ) : ( - - )} + setFormState("ongoing"); -
- { - event.preventDefault(); - - const fields = event.target as unknown as { - verif: HTMLInputElement; - name: HTMLInputElement; - email: HTMLInputElement; - message: HTMLInputElement; + if ( + parseInt(fields.verif.value, 10) === + randomNumber1 + randomNumber2 && + formState !== "completed" + ) { + const content: RequestMailProps = { + name: fields.name.value, + email: fields.email.value, + message: fields.message.value, + formName: "Contact Form", }; + fetch("/api/mail", { + method: "POST", + body: JSON.stringify(content), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, + }) + .then(async (responseJson) => responseJson.json()) + .then((response: ResponseMailProps) => { + switch (response.code) { + case "OKAY": + setFormResponse(langui.response_email_success ?? ""); + setFormState("completed"); - setFormState("ongoing"); + break; - if ( - parseInt(fields.verif.value, 10) === - randomNumber1 + randomNumber2 && - formState !== "completed" - ) { - const content: RequestMailProps = { - name: fields.name.value, - email: fields.email.value, - message: fields.message.value, - formName: "Contact Form", - }; - fetch("/api/mail", { - method: "POST", - body: JSON.stringify(content), - headers: { - "Content-type": "application/json; charset=UTF-8", - }, - }) - .then(async (responseJson) => responseJson.json()) - .then((response: ResponseMailProps) => { - switch (response.code) { - case "OKAY": - setFormResponse(langui.response_email_success ?? ""); - setFormState("completed"); + case "EENVELOPE": + setFormResponse(langui.response_invalid_email ?? ""); + setFormState("stale"); + break; - break; + default: + setFormResponse(response.message ?? ""); + setFormState("stale"); + break; + } + }); + } else { + setFormResponse(langui.response_invalid_code ?? ""); + setFormState("stale"); + setRandomNumber1(randomInt(0, 10)); + setRandomNumber2(randomInt(0, 10)); + } - case "EENVELOPE": - setFormResponse(langui.response_invalid_email ?? ""); - setFormState("stale"); - break; - - default: - setFormResponse(response.message ?? ""); - setFormState("stale"); - break; - } - }); - } else { - setFormResponse(langui.response_invalid_code ?? ""); - setFormState("stale"); - setRandomNumber1(randomInt(0, 10)); - setRandomNumber2(randomInt(0, 10)); - } - - router.replace("#send-response"); - fields.verif.value = ""; - }} - > -
- - -
- -
- - -

- {langui.email_gdpr_notice} -

-
- -
- -