Added smart language switch for posts ad hoc page and content

This commit is contained in:
DrMint 2022-04-12 12:25:40 +02:00
parent 049a2e2044
commit a84560d86e
16 changed files with 612 additions and 629 deletions

View File

@ -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",

View File

@ -11,42 +11,61 @@ interface Props {
target?: "_blank";
onClick?: MouseEventHandler<HTMLDivElement>;
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 = (
<div
draggable={props.draggable}
id={props.id}
onClick={props.onClick}
className={`grid place-content-center place-items-center border-[1px] border-dark text-dark rounded-full px-4 pt-[0.4rem] pb-[0.5rem] transition-all select-none ${
props.className
} ${
props.active
draggable={draggable}
id={id}
onClick={onClick}
className={`grid place-content-center place-items-center border-[1px]
border-dark text-dark rounded-full px-4 pt-[0.4rem] pb-[0.5rem]
transition-all select-none hover:[--opacityBadge:0] --opacityBadge:100 ${className} ${
active
? "text-light bg-black drop-shadow-black-lg !border-black cursor-not-allowed"
: "cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-shade-lg active:bg-black active:text-light active:drop-shadow-black-lg active:border-black"
}`}
>
{props.children}
{badgeNumber && (
<div className="opacity-[var(--opacityBadge)] transition-opacity grid place-items-center absolute -top-3 -right-2 bg-dark w-8 h-8 text-light font-bold rounded-full">
{badgeNumber}
</div>
)}
{children}
</div>
);
if (props.target) {
if (target) {
return (
<a href={props.href} target={props.target} rel="noreferrer">
<div>{button}</div>
<a href={href} target={target} rel="noreferrer">
<div className="relative">{button}</div>
</a>
);
}
return (
<div
className="relative"
onClick={() => {
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,
});
}}
>

View File

@ -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 {
</div>
</div>
<div className="grid grid-flow-col gap-8">
<div className="flex place-content-center flex-row flew-wrap gap-8">
{type?.data?.attributes && (
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.type}</h3>
@ -92,6 +94,7 @@ export default function ThumbnailHeader(props: Props): JSX.Element {
</div>
</div>
)}
{languageSwitcher}
</div>
{description && <InsetBox className="mt-8">{description}</InsetBox>}
</>

View File

@ -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<string, number>;
localesIndex: number | undefined;
setLocalesIndex: Dispatch<SetStateAction<number | undefined>>;
}
export default function LanguageSwitcher(props: Props): JSX.Element {
const { locales, langui, href } = props;
const router = useRouter();
const { locales, localesIndex, setLocalesIndex } = props;
return (
<div className="w-full grid place-content-center">
<div className="flex flex-col place-items-center text-center gap-4 my-12 border-2 border-mid rounded-xl p-8 max-w-lg">
<p>{langui.language_switch_message}</p>
<div className="flex flex-wrap flex-row gap-2">
{locales.map((locale, index) => (
<ToolTip
content={
<div className="flex flex-col gap-2">
{[...locales].map(([locale, value], index) => (
<>
{locale && (
<Button
key={index}
active={locale === router.locale}
href={href}
locale={locale}
active={value === localesIndex}
onClick={() => setLocalesIndex(value)}
>
{prettyLanguage(locale, props.languages)}
</Button>
@ -35,7 +33,11 @@ export default function LanguageSwitcher(props: Props): JSX.Element {
</>
))}
</div>
</div>
</div>
}
>
<Button badgeNumber={locales.size}>
<span className="material-icons">translate</span>
</Button>
</ToolTip>
);
}

230
src/components/Post.tsx Normal file
View File

@ -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<Props["post"], null | undefined>["translations"],
null | undefined
>[number]
>();
const translationLocales: Map<string, number> = 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 ? (
<SubPanel>
{returnHref && returnTitle && (
<ReturnButton
href={returnHref}
title={returnTitle}
langui={langui}
displayOn={ReturnButtonType.desktop}
horizontalLine
/>
)}
{displayCredits && (
<>
{selectedTranslation && (
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
<p className="font-headers">{langui.status}:</p>
<ToolTip
content={getStatusDescription(
selectedTranslation.status,
langui
)}
maxWidth={"20rem"}
>
<Chip>{selectedTranslation.status}</Chip>
</ToolTip>
</div>
)}
{post?.authors && post.authors.data.length > 0 && (
<div>
<p className="font-headers">{"Authors"}:</p>
<div className="grid place-items-center place-content-center gap-2">
{post.authors.data.map((author) => (
<>
{author.attributes && (
<RecorderChip
key={author.id}
langui={langui}
recorder={author.attributes}
/>
)}
</>
))}
</div>
</div>
)}
<HorizontalLine />
</>
)}
{displayToc && <TOC text={body} title={title} />}
</SubPanel>
) : undefined;
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/news"
title={langui.news}
langui={langui}
displayOn={ReturnButtonType.mobile}
className="mb-10"
/>
{displayThumbnailHeader ? (
<>
<ThumbnailHeader
thumbnail={thumbnail}
title={title}
description={except}
langui={langui}
categories={post?.categories}
languageSwitcher={
<LanguageSwitcher
languages={languages}
locales={translationLocales}
localesIndex={selectedTranslationIndex}
setLocalesIndex={setSelectedTranslationIndex}
/>
}
/>
<HorizontalLine />
</>
) : (
<>
{displayLanguageSwitcher && (
<div className="grid place-content-end place-items-start">
<LanguageSwitcher
languages={languages}
locales={translationLocales}
localesIndex={selectedTranslationIndex}
setLocalesIndex={setSelectedTranslationIndex}
/>
</div>
)}
{displayTitle && (
<h1 className="text-center flex gap-3 justify-center text-4xl my-16">
{title}
</h1>
)}
</>
)}
{prependBody}
<Markdawn text={body} />
{appendBody}
</ContentPanel>
);
return (
<AppLayout
navTitle={title}
contentPanel={contentPanel}
subPanel={subPanel}
thumbnail={thumbnail ?? undefined}
{...props}
/>
);
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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 = (
<SubPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.desktop}
langui={langui}
title={langui.about_us}
horizontalLine
/>
<TOC text={body} title={title} />
</SubPanel>
);
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.mobile}
langui={langui}
title={langui.about_us}
className="mb-10"
/>
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={body} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
</ContentPanel>
);
const { post, langui, languages, currencies } = props;
return (
<AppLayout
navTitle={title}
subPanel={subPanel}
contentPanel={contentPanel}
{...props}
<Post
currencies={currencies}
languages={languages}
langui={langui}
post={post}
returnHref="/about-us/"
returnTitle={langui.about_us}
displayToc
displayLanguageSwitcher
/>
);
}

View File

@ -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 = (
<div className="flex flex-col gap-8 text-center">
<form
className={`gap-8 grid ${
formState !== "stale" &&
"opacity-60 cursor-not-allowed touch-none pointer-events-none"
}`}
onSubmit={(event) => {
event.preventDefault();
const subPanel = (
<SubPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.desktop}
langui={langui}
title={langui.about_us}
horizontalLine
/>
<TOC text={body} title={title} />
</SubPanel>
);
const fields = event.target as unknown as {
verif: HTMLInputElement;
name: HTMLInputElement;
email: HTMLInputElement;
message: HTMLInputElement;
};
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.mobile}
langui={langui}
title={langui.about_us}
className="mb-10"
/>
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={body} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
setFormState("ongoing");
<div className="flex flex-col gap-8 text-center">
<form
className={`gap-8 grid ${
formState !== "stale" &&
"opacity-60 cursor-not-allowed touch-none pointer-events-none"
}`}
onSubmit={(event) => {
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 = "";
}}
>
<div className="flex flex-col place-items-center gap-1">
<label htmlFor="name">{langui.name}:</label>
<input
type="text"
className="mobile:w-full"
name="name"
id="name"
required
disabled={formState !== "stale"}
/>
</div>
<div className="flex flex-col place-items-center gap-1">
<label htmlFor="email">{langui.email}:</label>
<input
type="email"
className="mobile:w-full"
name="email"
id="email"
required
disabled={formState !== "stale"}
/>
<p className="text-sm text-dark italic opacity-70">
{langui.email_gdpr_notice}
</p>
</div>
<div className="flex flex-col place-items-center gap-1 w-full">
<label htmlFor="message">{langui.message}:</label>
<textarea
name="message"
id="message"
className="w-full"
rows={8}
required
disabled={formState !== "stale"}
/>
</div>
<div className="grid grid-cols-2 place-items-center">
<div className="flex flex-row place-items-center gap-2">
<label
className="flex-shrink-0"
htmlFor="verif"
>{`${randomNumber1} + ${randomNumber2} =`}</label>
<input
className="w-24"
type="number"
name="verif"
id="verif"
required
disabled={formState !== "stale"}
/>
</div>
<input
type="submit"
value={langui.send ?? "Send"}
className="w-min !px-6"
disabled={formState !== "stale"}
/>
</div>
</form>
<div id="send-response">
{formResponse && (
<InsetBox>
<p>{formResponse}</p>
</InsetBox>
)}
router.replace("#send-response");
fields.verif.value = "";
}}
>
<div className="flex flex-col place-items-center gap-1">
<label htmlFor="name">{langui.name}:</label>
<input
type="text"
className="mobile:w-full"
name="name"
id="name"
required
disabled={formState !== "stale"}
/>
</div>
<div className="flex flex-col place-items-center gap-1">
<label htmlFor="email">{langui.email}:</label>
<input
type="email"
className="mobile:w-full"
name="email"
id="email"
required
disabled={formState !== "stale"}
/>
<p className="text-sm text-dark italic opacity-70">
{langui.email_gdpr_notice}
</p>
</div>
<div className="flex flex-col place-items-center gap-1 w-full">
<label htmlFor="message">{langui.message}:</label>
<textarea
name="message"
id="message"
className="w-full"
rows={8}
required
disabled={formState !== "stale"}
/>
</div>
<div className="grid grid-cols-2 place-items-center">
<div className="flex flex-row place-items-center gap-2">
<label
className="flex-shrink-0"
htmlFor="verif"
>{`${randomNumber1} + ${randomNumber2} =`}</label>
<input
className="w-24"
type="number"
name="verif"
id="verif"
required
disabled={formState !== "stale"}
/>
</div>
<input
type="submit"
value={langui.send ?? "Send"}
className="w-min !px-6"
disabled={formState !== "stale"}
/>
</div>
</form>
<div id="send-response">
{formResponse && (
<InsetBox>
<p>{formResponse}</p>
</InsetBox>
)}
</div>
</ContentPanel>
</div>
);
return (
<AppLayout
navTitle={"Contact"}
subPanel={subPanel}
contentPanel={contentPanel}
{...props}
<Post
currencies={currencies}
languages={languages}
langui={langui}
post={post}
returnHref="/about-us/"
returnTitle={langui.about_us}
displayToc
appendBody={contactForm}
displayLanguageSwitcher
/>
);
}

View File

@ -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 SiteInformation(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 = (
<SubPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.desktop}
langui={langui}
title={langui.about_us}
horizontalLine
/>
<TOC text={body} title={title} />
</SubPanel>
);
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.mobile}
langui={langui}
title={langui.about_us}
className="mb-10"
/>
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={body} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
</ContentPanel>
);
const { post, langui, languages, currencies } = props;
return (
<AppLayout
navTitle={title}
subPanel={subPanel}
contentPanel={contentPanel}
{...props}
<Post
currencies={currencies}
languages={languages}
langui={langui}
post={post}
returnHref="/about-us/"
returnTitle={langui.about_us}
displayToc
displayLanguageSwitcher
/>
);
}

View File

@ -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<
@ -21,53 +11,17 @@ interface Props extends AppStaticProps {
>["data"][number]["attributes"];
}
export default function SharingPolicy(props: Props): JSX.Element {
const { langui, post } = props;
const locales = getLocalesFromLanguages(post?.translations_languages);
const router = useRouter();
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const subPanel = (
<SubPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.desktop}
langui={langui}
title={langui.about_us}
horizontalLine
/>
<TOC text={body} title={title} />
</SubPanel>
);
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.mobile}
langui={langui}
title={langui.about_us}
className="mb-10"
/>
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={body} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
</ContentPanel>
);
const { post, langui, languages, currencies } = props;
return (
<AppLayout
navTitle={title}
subPanel={subPanel}
contentPanel={contentPanel}
{...props}
<Post
currencies={currencies}
languages={languages}
langui={langui}
post={post}
returnHref="/about-us/"
returnTitle={langui.about_us}
displayToc
displayLanguageSwitcher
/>
);
}

View File

@ -13,6 +13,7 @@ import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel";
import RecorderChip from "components/RecorderChip";
import ToolTip from "components/ToolTip";
import { useAppLayout } from "contexts/AppLayoutContext";
import { GetContentTextQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import {
@ -23,7 +24,7 @@ import {
import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import {
getLocalesFromLanguages,
getPreferredLanguage,
getStatusDescription,
prettyinlineTitle,
prettyLanguage,
@ -31,6 +32,7 @@ import {
prettyTestError,
prettyTestWarning,
} from "queries/helpers";
import { useEffect, useMemo, useState } from "react";
interface Props extends AppStaticProps {
content: Exclude<
@ -47,7 +49,49 @@ export default function Content(props: Props): JSX.Element {
useTesting(props);
const { langui, content, languages } = props;
const router = useRouter();
const locales = getLocalesFromLanguages(content?.text_set_languages);
const appLayout = useAppLayout();
const [selectedTextSet, setSelectedTextSet] = useState<
| Exclude<
Exclude<Props["content"], null | undefined>["text_set"],
null | undefined
>[number]
>();
const [selectedTitle, setSelectedTitle] = useState<
| Exclude<
Exclude<Props["content"], null | undefined>["titles"],
null | undefined
>[number]
>();
const textSetLocales: Map<string, number> = new Map();
const [selectedTextSetIndex, setSelectedTextSetIndex] = useState<
number | undefined
>();
if (content?.text_set) {
content.text_set.map((textSet, index) => {
if (textSet?.language?.data?.attributes?.code && textSet.text) {
textSetLocales.set(textSet.language.data.attributes.code, index);
}
});
}
useMemo(() => {
setSelectedTextSetIndex(
getPreferredLanguage(
appLayout.preferredLanguages ?? [router.locale],
textSetLocales
)
);
}, [appLayout.preferredLanguages]);
useEffect(() => {
if (selectedTextSetIndex !== undefined)
setSelectedTextSet(content?.text_set?.[selectedTextSetIndex]);
if (selectedTextSetIndex !== undefined)
setSelectedTitle(content?.titles?.[selectedTextSetIndex]);
}, [selectedTextSetIndex]);
const subPanel = (
<SubPanel>
@ -59,27 +103,25 @@ export default function Content(props: Props): JSX.Element {
horizontalLine
/>
{content?.text_set?.[0]?.source_language?.data?.attributes && (
{selectedTextSet?.source_language?.data?.attributes && (
<div className="grid gap-5">
<h2 className="text-xl">
{content.text_set[0].source_language.data.attributes.code ===
router.locale
{selectedTextSet.source_language.data.attributes.code ===
selectedTextSet.language?.data?.attributes?.code
? langui.transcript_notice
: langui.translation_notice}
</h2>
{content.text_set[0].source_language.data.attributes.code !==
router.locale && (
{selectedTextSet.source_language.data.attributes.code !==
selectedTextSet.language?.data?.attributes?.code && (
<div className="grid place-items-center gap-2">
<p className="font-headers">{langui.source_language}:</p>
<Button
href={router.asPath}
locale={
content.text_set[0].source_language.data.attributes.code
}
locale={selectedTextSet.source_language.data.attributes.code}
>
{prettyLanguage(
content.text_set[0].source_language.data.attributes.code,
selectedTextSet.source_language.data.attributes.code,
languages
)}
</Button>
@ -90,19 +132,19 @@ export default function Content(props: Props): JSX.Element {
<p className="font-headers">{langui.status}:</p>
<ToolTip
content={getStatusDescription(content.text_set[0].status, langui)}
content={getStatusDescription(selectedTextSet.status, langui)}
maxWidth={"20rem"}
>
<Chip>{content.text_set[0].status}</Chip>
<Chip>{selectedTextSet.status}</Chip>
</ToolTip>
</div>
{content.text_set[0].transcribers &&
content.text_set[0].transcribers.data.length > 0 && (
{selectedTextSet.transcribers &&
selectedTextSet.transcribers.data.length > 0 && (
<div>
<p className="font-headers">{langui.transcribers}:</p>
<div className="grid place-items-center place-content-center gap-2">
{content.text_set[0].transcribers.data.map((recorder) => (
{selectedTextSet.transcribers.data.map((recorder) => (
<>
{recorder.attributes && (
<RecorderChip
@ -117,12 +159,12 @@ export default function Content(props: Props): JSX.Element {
</div>
)}
{content.text_set[0].translators &&
content.text_set[0].translators.data.length > 0 && (
{selectedTextSet.translators &&
selectedTextSet.translators.data.length > 0 && (
<div>
<p className="font-headers">{langui.translators}:</p>
<div className="grid place-items-center place-content-center gap-2">
{content.text_set[0].translators.data.map((recorder) => (
{selectedTextSet.translators.data.map((recorder) => (
<>
{recorder.attributes && (
<RecorderChip
@ -137,12 +179,12 @@ export default function Content(props: Props): JSX.Element {
</div>
)}
{content.text_set[0].proofreaders &&
content.text_set[0].proofreaders.data.length > 0 && (
{selectedTextSet.proofreaders &&
selectedTextSet.proofreaders.data.length > 0 && (
<div>
<p className="font-headers">{langui.proofreaders}:</p>
<div className="grid place-items-center place-content-center gap-2">
{content.text_set[0].proofreaders.data.map((recorder) => (
{selectedTextSet.proofreaders.data.map((recorder) => (
<>
{recorder.attributes && (
<RecorderChip
@ -159,25 +201,23 @@ export default function Content(props: Props): JSX.Element {
</div>
)}
{content?.text_set &&
content.text_set.length > 0 &&
content.text_set[0]?.text && (
<>
<HorizontalLine />
<TOC
text={content.text_set[0].text}
title={
content.titles && content.titles.length > 0 && content.titles[0]
? prettyinlineTitle(
content.titles[0].pre_title,
content.titles[0].title,
content.titles[0].subtitle
)
: prettySlug(content.slug)
}
/>
</>
)}
{selectedTextSet && content?.text_set && selectedTextSet.text && (
<>
<HorizontalLine />
<TOC
text={selectedTextSet.text}
title={
content.titles && content.titles.length > 0 && selectedTitle
? prettyinlineTitle(
selectedTitle.pre_title,
selectedTitle.title,
selectedTitle.subtitle
)
: prettySlug(content.slug)
}
/>
</>
)}
</SubPanel>
);
const contentPanel = (
@ -189,46 +229,37 @@ export default function Content(props: Props): JSX.Element {
displayOn={ReturnButtonType.mobile}
className="mb-10"
/>
{content && (
<div className="grid place-items-center">
<ThumbnailHeader
thumbnail={content.thumbnail?.data?.attributes}
pre_title={
content.titles && content.titles.length > 0
? content.titles[0]?.pre_title
: undefined
}
title={
content.titles && content.titles.length > 0
? content.titles[0]?.title
: prettySlug(content.slug)
}
subtitle={
content.titles && content.titles.length > 0
? content.titles[0]?.subtitle
: undefined
selectedTitle?.pre_title ?? content.titles?.[0]?.pre_title
}
title={selectedTitle?.title ?? content.titles?.[0]?.title}
subtitle={selectedTitle?.subtitle ?? content.titles?.[0]?.subtitle}
description={
content.titles && content.titles.length > 0
? content.titles[0]?.description
: undefined
selectedTitle?.description ?? content.titles?.[0]?.description
}
type={content.type}
categories={content.categories}
langui={langui}
languageSwitcher={
selectedTextSet ? (
<LanguageSwitcher
locales={textSetLocales}
languages={props.languages}
localesIndex={selectedTextSetIndex}
setLocalesIndex={setSelectedTextSetIndex}
/>
) : undefined
}
/>
<HorizontalLine />
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={content.text_set?.[0]?.text ?? ""} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
<Markdawn text={selectedTextSet?.text ?? ""} />
</div>
)}
</ContentPanel>
@ -276,7 +307,7 @@ export async function getStaticProps(
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = context.params?.slug?.toString() ?? "";
const slug = context.params?.slug.toString() ?? "";
const content = await sdk.getContentText({
slug: slug,
language_code: context.locale ?? "en",

View File

@ -1,13 +1,8 @@
import AppLayout from "components/AppLayout";
import LanguageSwitcher from "components/LanguageSwitcher";
import Markdawn from "components/Markdown/Markdawn";
import ContentPanel from "components/Panels/ContentPanel";
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<
@ -17,35 +12,26 @@ interface Props extends AppStaticProps {
}
export default function Home(props: Props): JSX.Element {
const { post } = props;
const locales = getLocalesFromLanguages(post?.translations_languages);
const router = useRouter();
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const contentPanel = (
<ContentPanel>
<div className="grid place-items-center place-content-center w-full gap-5 text-center">
<div className="[mask:url('/icons/accords.svg')] [mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] w-32 aspect-square mobile:w-[50vw] bg-black" />
<h1 className="text-5xl mb-0">Accord&rsquo;s Library</h1>
<h2 className="text-xl -mt-5">
Discover Analyze Translate Archive
</h2>
</div>
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={body} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
</ContentPanel>
const { post, langui, languages, currencies } = props;
return (
<Post
currencies={currencies}
languages={languages}
langui={langui}
post={post}
prependBody={
<div className="grid place-items-center place-content-center w-full gap-5 text-center">
<div className="[mask:url('/icons/accords.svg')] [mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] w-32 aspect-square mobile:w-[50vw] bg-black" />
<h1 className="text-5xl mb-0">Accord&rsquo;s Library</h1>
<h2 className="text-xl -mt-5">
Discover Analyze Translate Archive
</h2>
</div>
}
displayTitle={false}
displayLanguageSwitcher
/>
);
return <AppLayout navTitle={title} contentPanel={contentPanel} {...props} />;
}
export async function getStaticProps(

View File

@ -1,17 +1,4 @@
import AppLayout from "components/AppLayout";
import Chip from "components/Chip";
import ThumbnailHeader from "components/Content/ThumbnailHeader";
import HorizontalLine from "components/HorizontalLine";
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 RecorderChip from "components/RecorderChip";
import ToolTip from "components/ToolTip";
import Post from "components/Post";
import { GetPostQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import {
@ -19,13 +6,7 @@ import {
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import {
getLocalesFromLanguages,
getStatusDescription,
prettySlug,
} from "queries/helpers";
interface Props extends AppStaticProps {
post: Exclude<
@ -39,106 +20,18 @@ interface Props extends AppStaticProps {
}
export default function LibrarySlug(props: Props): JSX.Element {
const { post, langui } = props;
const locales = getLocalesFromLanguages(post?.translations_languages);
const router = useRouter();
const thumbnail = post?.translations?.[0]?.thumbnail?.data
? post.translations[0].thumbnail.data.attributes
: post?.thumbnail?.data
? post.thumbnail.data.attributes
: undefined;
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const except = post?.translations?.[0]?.excerpt ?? "";
const subPanel = (
<SubPanel>
<ReturnButton
href="/news"
title={langui.news}
langui={langui}
displayOn={ReturnButtonType.desktop}
horizontalLine
/>
{post?.translations?.[0] && (
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
<p className="font-headers">{langui.status}:</p>
<ToolTip
content={getStatusDescription(post.translations[0].status, langui)}
maxWidth={"20rem"}
>
<Chip>{post.translations[0].status}</Chip>
</ToolTip>
</div>
)}
{post?.authors && post.authors.data.length > 0 && (
<div>
<p className="font-headers">{"Authors"}:</p>
<div className="grid place-items-center place-content-center gap-2">
{post.authors.data.map((author) => (
<>
{author.attributes && (
<RecorderChip
key={author.id}
langui={langui}
recorder={author.attributes}
/>
)}
</>
))}
</div>
</div>
)}
<HorizontalLine />
<TOC text={body} title={title} />
</SubPanel>
);
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/news"
title={langui.news}
langui={langui}
displayOn={ReturnButtonType.mobile}
className="mb-10"
/>
<ThumbnailHeader
thumbnail={thumbnail}
title={title}
description={except}
langui={langui}
categories={post?.categories}
/>
<HorizontalLine />
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={body} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
</ContentPanel>
);
const { post, langui, languages, currencies } = props;
return (
<AppLayout
navTitle={title}
contentPanel={contentPanel}
subPanel={subPanel}
thumbnail={thumbnail ?? undefined}
{...props}
<Post
currencies={currencies}
languages={languages}
langui={langui}
post={post}
returnHref="/news"
returnTitle={langui.news}
displayCredits
displayThumbnailHeader
displayToc
/>
);
}

View File

@ -480,3 +480,15 @@ export function arrayMove<T>(arr: T[], old_index: number, new_index: number) {
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr;
}
export function getPreferredLanguage(
preferredLanguages: (string | undefined)[],
availableLanguages: Map<string, number>
): number | undefined {
for (const locale of preferredLanguages) {
if (locale && availableLanguages.has(locale)) {
return availableLanguages.get(locale);
}
}
return undefined;
}