diff --git a/frontend/graphql/generated.ts b/frontend/graphql/generated.ts index 85a4651..0fd525a 100644 --- a/frontend/graphql/generated.ts +++ b/frontend/graphql/generated.ts @@ -2148,6 +2148,7 @@ export type GetGlobalQueryVariables = Exact<{ [key: string]: never; }>; export type GetGlobalQuery = { __typename?: 'Query', global?: Maybe<{ __typename?: 'Global', id: string, topbar?: Maybe<{ __typename?: 'ComponentGlobalTopbar', id: string, menu?: Maybe<{ __typename?: 'Menu', id: string, title: string, links?: Maybe, url?: Maybe }>>> }> }>, companyData?: Maybe<{ __typename?: 'ComponentGlobalCompanyData', id: string, primaryEmail?: Maybe, companyName?: Maybe, copyright?: Maybe, vatId?: Maybe, capital?: Maybe, additionalLegalInfo?: Maybe, locations?: Maybe, provinceInitials?: Maybe, type?: Maybe, street?: Maybe, city?: Maybe, cap?: Maybe }>>> }>, footer?: Maybe<{ __typename?: 'ComponentGlobalFooter', id: string, description?: Maybe }> }> }; export type GetProjectsQueryVariables = Exact<{ + where?: Maybe; locale?: Maybe; }>; @@ -2170,6 +2171,29 @@ export type UpdateMenuMutationVariables = Exact<{ export type UpdateMenuMutation = { __typename?: 'Mutation', updateMenu?: Maybe<{ __typename?: 'updateMenuPayload', menu?: Maybe<{ __typename?: 'Menu', id: string }> }> }; +export type UpdateProjectMutationVariables = Exact<{ + projectInput?: Maybe; + menuInput?: Maybe; +}>; + + +export type UpdateProjectMutation = ( + { __typename?: 'Mutation' } + & { updateProject?: Maybe<( + { __typename?: 'updateProjectPayload' } + & { project?: Maybe<( + { __typename?: 'Project' } + & Pick + )> } + )>, updateMenu?: Maybe<( + { __typename?: 'updateMenuPayload' } + & { menu?: Maybe<( + { __typename?: 'Menu' } + & Pick + )> } + )> } +); + export const GetPages = ` query GetPages($where: JSON, $locale: String) { @@ -2319,8 +2343,8 @@ export const GetGlobal = ` } `; export const GetProjects = ` - query getProjects($locale: String) { - projects(locale: $locale) { + query getProjects($where: JSON, $locale: String) { + projects(where: $where, locale: $locale) { id linkPath linkLabel @@ -2405,5 +2429,19 @@ export const UpdateMenu = ` id } } +} + `; +export const UpdateProject = ` + mutation updateProject($projectInput: updateProjectInput, $menuInput: updateMenuInput) { + updateProject(input: $projectInput) { + project { + id + } + } + updateMenu(input: $menuInput) { + menu { + id + } + } } `; \ No newline at end of file diff --git a/frontend/graphql/getProjects.graphql b/frontend/graphql/getProjects.graphql index f223327..f36193d 100644 --- a/frontend/graphql/getProjects.graphql +++ b/frontend/graphql/getProjects.graphql @@ -1,5 +1,5 @@ -query getProjects($locale: String) { - projects(locale: $locale) { +query getProjects($where: JSON, $locale: String) { + projects(where: $where, locale: $locale) { id linkPath linkLabel diff --git a/frontend/graphql/updateProject.graphql b/frontend/graphql/updateProject.graphql new file mode 100644 index 0000000..d93f966 --- /dev/null +++ b/frontend/graphql/updateProject.graphql @@ -0,0 +1,16 @@ +mutation updateProject( + $projectInput: updateProjectInput + $menuInput: updateMenuInput +) { + updateProject(input: $projectInput) { + project { + id + } + } + + updateMenu(input: $menuInput) { + menu { + id + } + } +} diff --git a/frontend/pages/[[...slug]].tsx b/frontend/pages/[[...slug]].tsx index 3284bfa..5802cbc 100644 --- a/frontend/pages/[[...slug]].tsx +++ b/frontend/pages/[[...slug]].tsx @@ -73,7 +73,6 @@ const StyledInlineBlocks = chakra(InlineBlocks); export default function DynamicPage({ data, preview }: DynamicPageProps) { const { colorMode } = useColorMode(); - const itemProps = React.useMemo(() => { return { isPreview: preview, @@ -214,7 +213,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => { return { paths, fallback: false }; }; -function wrap(value: T | T[]): T[] { +export function wrap(value: T | T[]): T[] { if (Array.isArray(value)) { return value; } @@ -504,7 +503,7 @@ function getPageData( path: page.path ? page.path : undefined, localizations: page.localizations ? filterListNullableItems( - page.localizations?.map((localization) => { + page.localizations.map((localization) => { return { id: localization?.id, locale: localization?.locale, diff --git a/frontend/pages/projects/[...handle].tsx b/frontend/pages/projects/[...handle].tsx new file mode 100644 index 0000000..8947a7f --- /dev/null +++ b/frontend/pages/projects/[...handle].tsx @@ -0,0 +1,443 @@ +import { Box, Img, Text } from "@chakra-ui/react"; +import { + LocaleMenu, + LocaleMenuButton, + LocaleMenuList, + LocaleMenuLink, +} from "@components/LocaleMenu"; +import { MobileNavDrawer } from "@components/MobileNavDrawer"; +import { WordmarkLogo } from "@components/WordmarkLogo"; +import { + NavBar, + NavMenuMobile, + NavMenuDesktop, +} from "@features/mainNavigation"; +import { + GetGlobal, + GetGlobalQuery, + GetGlobalQueryVariables, + GetProjects, + GetProjectsQuery, + GetProjectsQueryVariables, + UpdateMenuInput, + UpdateProject, + UpdateProjectInput, +} from "@graphql/generated"; +import { fetchGraphQL } from "@graphql/utils"; +import { SiteLayout } from "@layouts/siteLayout"; +import { + GlobalData, + LocalizationsData, + MenuData, +} from "@plugins/usePagePlugin"; +import { filterListNullableItems } from "@utils"; +import { GetStaticPaths, GetStaticProps } from "next"; +import { useRouter } from "next/router"; +import { wrap } from "pages/[[...slug]]"; +import React from "react"; +import { InlineForm, InlineText } from "react-tinacms-inline"; +import { useCMS, useForm, usePlugin } from "tinacms"; +import { FormOptions } from "tinacms"; +import { Form } from "tinacms"; +import { getGlobalData } from "."; +import NextLink from "next/link"; +import { STRAPI_URL } from "@config/env"; + +interface ProjectData { + id: string; + path: string; + linkPath: Nullable; + linkLabel: Nullable; + description: Nullable; + locale: Nullable; + companyName: Nullable; + projectType: Nullable; + image: Nullable; + localizations?: LocalizationsData[]; + blocks: string; +} + +interface ProjectImage { + id: string; + url: string; + alternativeText: Nullable; +} + +interface DynamicPageProps { + locale: string; + preview: boolean; + data: { + global: GlobalData; + project: ProjectData; + }; +} + +export default function DynamicPage({ data: data }: DynamicPageProps) { + const [_, form] = useProjectPlugin(data); + const router = useRouter(); + + return ( + + + + + + + + + + + + + + + + {router.locale!.toUpperCase()} + + + + {router.locale?.toUpperCase()} + + {data ? ( + data.project.localizations?.map((pageLocale) => { + return ( + + {pageLocale.locale?.toUpperCase()} + + ); + }) + ) : ( + + )} + + + + + + + + + + + + + + + + + + + + + + + Blocks + + + + + + ); +} + +export const getStaticPaths: GetStaticPaths = async (context) => { + if (context.locales == null) { + throw new Error("No locale has been defined!"); + } + const allProjectsRequests = context.locales.map(async (locale) => { + const localeProjects = await fetchGraphQL< + GetProjectsQuery, + GetProjectsQueryVariables + >(GetProjects, { + locale, + }); + + if (localeProjects.projects) { + return filterListNullableItems(localeProjects.projects); + } + return []; + }); + + const allProjects = await Promise.all(allProjectsRequests); + const projects = allProjects.flat(); + + const paths = projects.map((page) => { + const pagePath = page.path?.replace(/^\/+/, "") || ""; + + const slugArray: any = + pagePath.length > 0 ? pagePath.split("/") : undefined; + + return { + params: { handle: slugArray }, + locale: page.locale!, + }; + }); + + return { paths, fallback: "blocking" }; +}; + +export const getStaticProps: GetStaticProps< + DynamicPageProps | { notFound: boolean } +> = async (context) => { + const pathParts = wrap(context.params?.handle || []); + const path = `/projects/${pathParts.join("/")}`; + const locale = context.locale; + if (locale == null) { + throw new Error(`Path "${pathParts.join("/")}" has no locale!`); + } + const preview = context.preview === true; + + const availableProjects = await fetchGraphQL< + GetProjectsQuery, + GetProjectsQueryVariables + >(GetProjects, { + locale, + where: { + path, + }, + }); + + if (availableProjects.projects == null) { + return { + notFound: true, + }; + } + + const availableGlobal = await fetchGraphQL< + GetGlobalQuery, + GetGlobalQueryVariables + >(GetGlobal); + + if (availableGlobal.global == null) { + return { + notFound: true, + }; + } + + const projectData = getProjectData(availableProjects.projects, locale); + + const globalData = getGlobalData(availableGlobal.global); + + if (projectData == null) { + return { + notFound: true, + }; + } + + if (globalData == null) { + return { + notFound: true, + }; + } + + if (preview) { + return { + props: { + path: pathParts, + locale, + preview, + previewData: context.previewData, + data: { + global: globalData, + project: projectData, + }, + }, + }; + } + + return { + props: { + path: pathParts, + locale, + preview, + data: { + global: globalData, + project: projectData, + }, + }, + }; +}; + +function getProjectData( + projects: GetProjectsQuery["projects"], + locale: string +): ProjectData | undefined { + const project = projects?.find((project) => project?.locale === locale); + if (project == null) { + return undefined; + } + return { + id: project.id, + companyName: project.companyName || null, + description: project.description || null, + projectType: project.projectType || null, + linkLabel: project.linkLabel || null, + linkPath: project.linkPath || null, + path: project.path, + locale: project.locale || null, + blocks: "aa", + image: project.image + ? { + id: project.image.id, + url: project.image.url, + alternativeText: project.image.alternativeText || null, + } + : null, + localizations: project.localizations + ? filterListNullableItems(project.localizations).map( + (localization) => { + return { + id: localization.id, + locale: localization.locale || null, + path: localization.path || null, + }; + } + ) + : [], + }; +} + +function getProjectInput(data: ProjectData): UpdateProjectInput { + return { + where: { id: data.id }, + data: { + companyName: data.companyName, + description: data.description, + projectType: data.projectType?.toUpperCase(), + linkLabel: data.linkLabel, + linkPath: data.linkPath, + path: data.path, + locale: data.locale, + blocks: undefined, + image: data.image ? data.image.id : null, + }, + }; +} + +interface Data { + project: ProjectData; + global: GlobalData; +} + +function useProjectPlugin(data: Data): [Data, Form] { + const cms = useCMS(); + const formConfig: FormOptions = { + id: data.project.id, + label: "Project settings", + initialValues: data, + onSubmit: async (data) => { + const projectInput = getProjectInput(data.project); + const menuInput = getMenuInput(data.global.topbar.menu); + try { + const response = await cms.api.strapi.fetchGraphql(UpdateProject, { + projectInput: projectInput, + menuInput: menuInput, + }); + + if (response.errors != null) { + cms.alerts.error("Error while saving data", 10000); + } else { + if (response.data) { + cms.alerts.success("Changes saved!"); + } else { + cms.alerts.error("Error while saving changes"); + } + } + } catch (error) { + console.log(error); + cms.alerts.error("Error while saving changes"); + } + }, + fields: [], + }; + + const [formData, form] = useForm(formConfig, { values: data }); + usePlugin(form); + + return [formData, form]; +} + +function getMenuInput(data: MenuData): UpdateMenuInput { + return { + where: { id: data.id }, + data: { + title: data.title, + links: data.links.map((link) => { + return { + id: link.id, + label: link.label || null, + url: link.url || null, + }; + }), + }, + }; +} diff --git a/frontend/pages/projects/index.tsx b/frontend/pages/projects/index.tsx index 36d6311..fa595bd 100644 --- a/frontend/pages/projects/index.tsx +++ b/frontend/pages/projects/index.tsx @@ -498,7 +498,7 @@ function getMenuInput(data: GlobalData): UpdateMenuInput { }; } -function getGlobalData( +export function getGlobalData( global: GetGlobalQuery["global"] ): GlobalData | undefined { if (global == null) { diff --git a/frontend/theme/index.ts b/frontend/theme/index.ts index 4923ea0..d5e329d 100644 --- a/frontend/theme/index.ts +++ b/frontend/theme/index.ts @@ -57,6 +57,14 @@ const overrides: ThemeOverride = { }, }, }, + styles: { + global: { + body: { + fontSize: "sm", + letterSpacing: "0.02em", + }, + }, + }, }; const customTheme = extendTheme(overrides);