diff --git a/src/app/(main)/[slug]/page.tsx b/src/app/(main)/[slug]/page.tsx index 51f28eb21..fe3494a52 100644 --- a/src/app/(main)/[slug]/page.tsx +++ b/src/app/(main)/[slug]/page.tsx @@ -6,12 +6,15 @@ import CustomErrorMessage from "src/blog/components/customErrorMessage/CustomErr import { homeLink } from "src/blog/components/utils/linkTypes"; import Compensations from "src/compensations/Compensations"; import CompensationsPreview from "src/compensations/CompensationsPreview"; +import CustomerCases from "src/customerCases/CustomerCases"; +import CustomerCasesPreview from "src/customerCases/CustomerCasesPreview"; import { getDraftModeInfo } from "src/utils/draftmode"; import SectionRenderer from "src/utils/renderSection"; import { fetchSeoData, generateMetadataFromSeo } from "src/utils/seo"; import { CompanyLocation } from "studio/lib/interfaces/companyDetails"; import { CompensationsPage } from "studio/lib/interfaces/compensations"; import { BlogPage, PageBuilder, Post } from "studio/lib/interfaces/pages"; +import { CustomerCasePage } from "studio/lib/interfaces/specialPages"; import { COMPANY_LOCATIONS_QUERY } from "studio/lib/queries/companyDetails"; import { BLOG_PAGE_QUERY, @@ -19,8 +22,11 @@ import { SEO_SLUG_QUERY, SLUG_QUERY, } from "studio/lib/queries/page"; -import { COMPENSATIONS_PAGE_QUERY } from "studio/lib/queries/specialPages"; -import { loadQuery } from "studio/lib/store"; +import { + COMPENSATIONS_PAGE_QUERY, + CUSTOMER_CASES_PAGE_QUERY, +} from "studio/lib/queries/specialPages"; +import { loadStudioQuery } from "studio/lib/store"; export const dynamic = "force-dynamic"; @@ -53,15 +59,25 @@ async function Page({ params }: Props) { initialBlogPage, initialCompensationsPage, initialLocationsData, + initialCustomerCases, ] = await Promise.all([ - loadQuery(SLUG_QUERY, { slug }, { perspective }), - loadQuery(BLOG_PAGE_QUERY, { slug }, { perspective }), - loadQuery( + loadStudioQuery(SLUG_QUERY, { slug }, { perspective }), + loadStudioQuery(BLOG_PAGE_QUERY, { slug }, { perspective }), + loadStudioQuery( COMPENSATIONS_PAGE_QUERY, { slug }, { perspective }, ), - loadQuery(COMPANY_LOCATIONS_QUERY, {}, { perspective }), + loadStudioQuery( + COMPANY_LOCATIONS_QUERY, + {}, + { perspective }, + ), + loadStudioQuery( + CUSTOMER_CASES_PAGE_QUERY, + { slug }, + { perspective }, + ), ]); if (initialPage.data) { @@ -82,7 +98,7 @@ async function Page({ params }: Props) { } if (initialBlogPage.data) { - const initialPosts = await loadQuery( + const initialPosts = await loadStudioQuery( POSTS_QUERY, { slug }, { perspective }, @@ -121,6 +137,14 @@ async function Page({ params }: Props) { ); } + if (initialCustomerCases.data) { + return isDraftMode ? ( + + ) : ( + + ); + } + return Page404; } diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index e28cf5ca2..d80126d57 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -14,7 +14,7 @@ import { COMPANY_INFO_QUERY } from "studio/lib/queries/companyDetails"; import { LEGAL_DOCUMENTS_QUERY } from "studio/lib/queries/legalDocuments"; import { NAV_QUERY } from "studio/lib/queries/navigation"; import { SOMEPROFILES_QUERY } from "studio/lib/queries/socialMediaProfiles"; -import { loadQuery } from "studio/lib/store"; +import { loadStudioQuery } from "studio/lib/store"; import styles from "./layout.module.css"; @@ -34,11 +34,19 @@ export default async function Layout({ initialLegal, initialBrandAssets, ] = await Promise.all([ - loadQuery(NAV_QUERY, {}, { perspective }), - loadQuery(COMPANY_INFO_QUERY, {}, { perspective }), - loadQuery(SOMEPROFILES_QUERY, {}, { perspective }), - loadQuery(LEGAL_DOCUMENTS_QUERY, {}, { perspective }), - loadQuery(BRAND_ASSETS_QUERY, {}, { perspective }), + loadStudioQuery(NAV_QUERY, {}, { perspective }), + loadStudioQuery(COMPANY_INFO_QUERY, {}, { perspective }), + loadStudioQuery( + SOMEPROFILES_QUERY, + {}, + { perspective }, + ), + loadStudioQuery( + LEGAL_DOCUMENTS_QUERY, + {}, + { perspective }, + ), + loadStudioQuery(BRAND_ASSETS_QUERY, {}, { perspective }), ]); const hasNavData = hasValidData(initialNav.data); diff --git a/src/app/(main)/legal/[id]/page.tsx b/src/app/(main)/legal/[id]/page.tsx index e512ea7a0..9013bad4f 100644 --- a/src/app/(main)/legal/[id]/page.tsx +++ b/src/app/(main)/legal/[id]/page.tsx @@ -3,7 +3,7 @@ import LegalPreview from "src/blog/components/legal/LegalPreview"; import { getDraftModeInfo } from "src/utils/draftmode"; import { LegalDocument } from "studio/lib/interfaces/legalDocuments"; import { LEGAL_DOCUMENT_SLUG_QUERY } from "studio/lib/queries/legalDocuments"; -import { loadQuery } from "studio/lib/store"; +import { loadStudioQuery } from "studio/lib/store"; export const dynamic = "force-dynamic"; @@ -19,7 +19,7 @@ async function Page({ params }: Props) { const { id } = params; const { perspective, isDraftMode } = getDraftModeInfo(); - const initialDocument = await loadQuery( + const initialDocument = await loadStudioQuery( LEGAL_DOCUMENT_SLUG_QUERY, { slug: id }, { perspective }, diff --git a/src/app/(main)/page.tsx b/src/app/(main)/page.tsx index 238a16bb6..302bcb9fb 100644 --- a/src/app/(main)/page.tsx +++ b/src/app/(main)/page.tsx @@ -8,10 +8,10 @@ import { LinkType } from "studio/lib/interfaces/navigation"; import { PageBuilder } from "studio/lib/interfaces/pages"; import { LANDING_QUERY } from "studio/lib/queries/navigation"; import { PAGE_QUERY, SEO_PAGE_QUERY } from "studio/lib/queries/page"; -import { loadQuery } from "studio/lib/store"; +import { loadStudioQuery } from "studio/lib/store"; export async function generateMetadata(): Promise { - const { data: landingId } = await loadQuery(LANDING_QUERY); + const { data: landingId } = await loadStudioQuery(LANDING_QUERY); const seo = await fetchSeoData(SEO_PAGE_QUERY, { id: landingId }); return generateMetadataFromSeo(seo); } @@ -35,7 +35,7 @@ const pagesLink = { const Home = async () => { const { perspective, isDraftMode } = getDraftModeInfo(); - const { data: landingId } = await loadQuery( + const { data: landingId } = await loadStudioQuery( LANDING_QUERY, {}, { perspective }, @@ -53,7 +53,7 @@ const Home = async () => { ); } - const initialLandingPage = await loadQuery( + const initialLandingPage = await loadStudioQuery( PAGE_QUERY, { id: landingId }, { perspective }, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2073a7181..c9597e1fa 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,7 +7,7 @@ import { generateMetadataFromSeo } from "src/utils/seo"; import { DefaultLanguageObject } from "studio/lib/interfaces/languages"; import LiveVisualEditing from "studio/lib/loaders/AutomaticVisualEditing"; import { DEFAULT_LANGUAGE_QUERY } from "studio/lib/queries/languages"; -import { loadQuery } from "studio/lib/store"; +import { loadStudioQuery } from "studio/lib/store"; import "src/styles/global.css"; @@ -35,7 +35,7 @@ export default async function RootLayout({ let siteLang; try { - const { data } = await loadQuery( + const { data } = await loadStudioQuery( DEFAULT_LANGUAGE_QUERY, ); siteLang = data.defaultLanguage; diff --git a/src/blog/components/utils/linkTypes.ts b/src/blog/components/utils/linkTypes.ts index e5623d261..bc33320ac 100644 --- a/src/blog/components/utils/linkTypes.ts +++ b/src/blog/components/utils/linkTypes.ts @@ -15,5 +15,21 @@ export const studioLink = { _type: "link", linkTitle: "Go to studio", linkType: LinkType.Internal, - internalLink: { _ref: "/studio" }, + internalLink: { _ref: "studio" }, +}; + +export const sharedStudioLink = { + _key: "go-to-shared-studio", + _type: "link", + linkTitle: "Go to shared studio", + linkType: LinkType.Internal, + internalLink: { _ref: "shared" }, +}; + +export const sharedCustomerCasesLink = { + _key: "go-to-shared-studio-customer-cases", + _type: "link", + linkTitle: "Go to shared studio", + linkType: LinkType.Internal, + internalLink: { _ref: "shared/structure/customerCases" }, }; diff --git a/src/customerCases/CustomerCases.tsx b/src/customerCases/CustomerCases.tsx new file mode 100644 index 000000000..9ab8c5615 --- /dev/null +++ b/src/customerCases/CustomerCases.tsx @@ -0,0 +1,49 @@ +import { sharedCustomerCasesLink } from "src/blog/components/utils/linkTypes"; +import LinkButton from "src/components/linkButton/LinkButton"; +import { RichText } from "src/components/richText/RichText"; +import Text from "src/components/text/Text"; +import { getDraftModeInfo } from "src/utils/draftmode"; +import { CustomerCasePage } from "studio/lib/interfaces/specialPages"; +import { CustomerCase } from "studioShared/lib/interfaces/customerCases"; +import { CUSTOMER_CASES_QUERY } from "studioShared/lib/queries/customerCases"; +import { loadSharedQuery } from "studioShared/lib/store"; + +import styles from "./customerCases.module.css"; + +interface CustomerCasesProps { + customerCasesPage: CustomerCasePage; +} + +const CustomerCases = async ({ customerCasesPage }: CustomerCasesProps) => { + const { perspective } = getDraftModeInfo(); + + const [sharedCustomerCases] = await Promise.all([ + loadSharedQuery(CUSTOMER_CASES_QUERY, {}, { perspective }), + ]); + + return ( +
+ {customerCasesPage.basicTitle} + {sharedCustomerCases && sharedCustomerCases.data.length > 0 ? ( + sharedCustomerCases.data.map((customerCase: CustomerCase) => ( +
+ {customerCase.basicTitle} + {customerCase.richText && ( + + )} +
+ )) + ) : ( +
+ + It looks like you haven't created any customer cases yet. + Please visit the shared studio to add some. + + +
+ )} +
+ ); +}; + +export default CustomerCases; diff --git a/src/customerCases/CustomerCasesPreview.tsx b/src/customerCases/CustomerCasesPreview.tsx new file mode 100644 index 000000000..43b8aae85 --- /dev/null +++ b/src/customerCases/CustomerCasesPreview.tsx @@ -0,0 +1,31 @@ +import { QueryResponseInitial, useQuery } from "@sanity/react-loader"; +import { Suspense } from "react"; + +import { CustomerCasePage } from "studio/lib/interfaces/specialPages"; +import { CUSTOMER_CASES_PAGE_QUERY } from "studio/lib/queries/specialPages"; + +import CustomerCases from "./CustomerCases"; + +interface CustomerCasesPreviewProps { + initialCustomerCases: QueryResponseInitial; +} + +const CustomerCasesPreview = ({ + initialCustomerCases, +}: CustomerCasesPreviewProps) => { + const { data: customerCases } = useQuery( + CUSTOMER_CASES_PAGE_QUERY, + { slug: initialCustomerCases.data.slug.current }, + { initial: initialCustomerCases }, + ); + + return ( + customerCases && ( + + + + ) + ); +}; + +export default CustomerCasesPreview; diff --git a/src/customerCases/customerCases.module.css b/src/customerCases/customerCases.module.css new file mode 100644 index 000000000..119bf3d06 --- /dev/null +++ b/src/customerCases/customerCases.module.css @@ -0,0 +1,12 @@ +.wrapper { + display: flex; + flex-direction: column; + padding: 10rem 5rem; + gap: 5rem; +} + +.section { + display: flex; + flex-direction: column; + gap: 5rem; +} diff --git a/src/utils/seo.ts b/src/utils/seo.ts index 30ab3f9a5..b63a0c6f1 100644 --- a/src/utils/seo.ts +++ b/src/utils/seo.ts @@ -10,7 +10,7 @@ import { DefaultSeo } from "studio/lib/interfaces/defaultSeo"; import { BRAND_ASSETS_QUERY } from "studio/lib/queries/brandAssets"; import { COMPANY_INFO_QUERY } from "studio/lib/queries/companyDetails"; import { DEFAULT_SEO_QUERY } from "studio/lib/queries/seo"; -import { loadQuery } from "studio/lib/store"; +import { loadStudioQuery } from "studio/lib/store"; type SeoData = { title: string; @@ -36,7 +36,7 @@ export async function fetchSeoData( variables?: QueryParams | undefined, ): Promise { try { - const { data } = await loadQuery(query, variables); + const { data } = await loadStudioQuery(query, variables); return data; } catch (error) { console.error("Error loading SEO data:", error); @@ -49,7 +49,7 @@ export async function fetchPostSeoData( variables?: QueryParams | undefined, ): Promise { try { - const { data } = await loadQuery(query, variables); + const { data } = await loadStudioQuery(query, variables); if (data && data.description) { const plainTextDescription = toPlainText(data.description); @@ -71,13 +71,13 @@ export async function fetchPostSeoData( export async function generateMetadataFromSeo( seo: SeoData | null, ): Promise { - const { data: defaultSeo } = await loadQuery( + const { data: defaultSeo } = await loadStudioQuery( DEFAULT_SEO_QUERY, ); - const { data: companyInfo } = await loadQuery( + const { data: companyInfo } = await loadStudioQuery( COMPANY_INFO_QUERY, ); - const { data: brandAssets } = await loadQuery( + const { data: brandAssets } = await loadStudioQuery( BRAND_ASSETS_QUERY, ); diff --git a/studio/lib/interfaces/specialPages.ts b/studio/lib/interfaces/specialPages.ts new file mode 100644 index 000000000..f94e85a28 --- /dev/null +++ b/studio/lib/interfaces/specialPages.ts @@ -0,0 +1,12 @@ +import { Slug } from "./global"; + +export interface CustomerCasePage { + _createdAt: string; + _id: string; + _rev: string; + _type: string; + _updatedAt: string; + basicTitle: string; + page: string; + slug: Slug; +} diff --git a/studio/lib/queries/specialPages.ts b/studio/lib/queries/specialPages.ts index 1f2980783..75598fc45 100644 --- a/studio/lib/queries/specialPages.ts +++ b/studio/lib/queries/specialPages.ts @@ -1,5 +1,8 @@ import { groq } from "next-sanity"; +export const CUSTOMER_CASES_PAGE_QUERY = groq` + *[_type == "customerCases" && slug.current == $slug][0]`; + export const COMPENSATIONS_PAGE_QUERY = groq` *[_type == "compensations" && slug.current == $slug][0] `; diff --git a/studio/lib/store.ts b/studio/lib/store.ts index 60e779f06..22f465461 100644 --- a/studio/lib/store.ts +++ b/studio/lib/store.ts @@ -1,8 +1,13 @@ -import * as queryStore from "@sanity/react-loader"; +import { createQueryStore } from "@sanity/react-loader"; import { client } from "./client"; import { token } from "./token"; -queryStore.setServerClient(client.withConfig({ token })); +const { loadQuery: loadStudioQuery, setServerClient } = createQueryStore({ + client: false, + ssr: true, +}); -export const { loadQuery } = queryStore; +setServerClient(client.withConfig({ token })); + +export { loadStudioQuery }; diff --git a/studio/schema.ts b/studio/schema.ts index 07a0c2a3e..93876183c 100644 --- a/studio/schema.ts +++ b/studio/schema.ts @@ -13,6 +13,7 @@ import brokenLink from "./schemas/documents/siteSettings/brokenLink"; import navigationManager from "./schemas/documents/siteSettings/navigationManager"; import socialMediaLinks from "./schemas/documents/siteSettings/socialMediaProfiles"; import supportedLanguages from "./schemas/documents/siteSettings/supportedLanguages"; +import customerCasesPage from "./schemas/documents/specialPages/customerCasesPage"; import callToActionField from "./schemas/fields/callToActionFields"; import categories from "./schemas/fields/categories"; import benefitsByLocation from "./schemas/objects/compensations/benefitsByLocation"; @@ -35,6 +36,7 @@ export const schema: { types: SchemaTypeDefinition[] } = { categories, legalDocument, compensations, + customerCasesPage, brokenLink, benefitsByLocation, companyLocation, diff --git a/studio/schemas/deskStructure.ts b/studio/schemas/deskStructure.ts index bc758454e..fb998d164 100644 --- a/studio/schemas/deskStructure.ts +++ b/studio/schemas/deskStructure.ts @@ -26,6 +26,7 @@ import { brandAssetsID } from "./documents/siteSettings/brandAssets"; import { brokenLinkID } from "./documents/siteSettings/brokenLink"; import { soMeLinksID } from "./documents/siteSettings/socialMediaProfiles"; import { supportedLanguagesID } from "./documents/siteSettings/supportedLanguages"; +import { customerCasesPageID } from "./documents/specialPages/customerCasesPage"; // Admin Section const adminSection = (S: StructureBuilder) => @@ -152,6 +153,15 @@ const SpecialPagesSection = (S: StructureBuilder) => .documentId(compensationsId) .title("Compensations"), ), + S.listItem() + .title("Customer Cases") + .icon(ProjectsIcon) + .child( + S.document() + .schemaType(customerCasesPageID) + .documentId(customerCasesPageID) + .title("Customer Cases"), + ), ]), ); diff --git a/studio/schemas/documents/specialPages/customerCasesPage.ts b/studio/schemas/documents/specialPages/customerCasesPage.ts new file mode 100644 index 000000000..61f3a2b9b --- /dev/null +++ b/studio/schemas/documents/specialPages/customerCasesPage.ts @@ -0,0 +1,28 @@ +import { defineType } from "sanity"; + +import { title } from "studio/schemas/fields/text"; +import { titleSlug } from "studio/schemas/schemaTypes/slug"; + +export const customerCasesPageID = "customerCasesPage"; + +const customerCasesPage = defineType({ + name: customerCasesPageID, + type: "document", + title: "Customer Cases", + fields: [ + { + ...title, + title: "Customer Case Page Title", + description: + "Enter the primary title that will be displayed at the top of the customer cases page. This is what users will see when they visit the page.", + }, + titleSlug, + ], + preview: { + select: { + title: "basicTitle", + }, + }, +}); + +export default customerCasesPage; diff --git a/studio/schemas/objects/link.ts b/studio/schemas/objects/link.ts index efa9844aa..51e966a8e 100644 --- a/studio/schemas/objects/link.ts +++ b/studio/schemas/objects/link.ts @@ -25,6 +25,7 @@ interface Parent { const lazyPageBuilderID = () => "pageBuilder"; const lazyBlogID = () => "blog"; const lazyCompensationsID = () => "compensations"; +const lazyCustomerCasesPageID = () => "customerCasesPage"; export const link = defineField({ name: linkID, @@ -69,6 +70,7 @@ export const link = defineField({ { type: lazyPageBuilderID() }, { type: lazyBlogID() }, { type: lazyCompensationsID() }, + { type: lazyCustomerCasesPageID() }, ], validation: (rule) => rule.custom((value, context) => { diff --git a/studioShared/lib/interfaces/customerCases.ts b/studioShared/lib/interfaces/customerCases.ts new file mode 100644 index 000000000..46e7152d7 --- /dev/null +++ b/studioShared/lib/interfaces/customerCases.ts @@ -0,0 +1,13 @@ +import { PortableTextBlock, Slug } from "sanity"; + +export interface CustomerCase { + language: string; + _id: string; + basicTitle: string; + _updatedAt: string; + slug: Slug; + _createdAt: string; + _rev: string; + _type: string; + richText: PortableTextBlock[]; +} diff --git a/studioShared/lib/queries/customerCases.ts b/studioShared/lib/queries/customerCases.ts new file mode 100644 index 000000000..a8b2043c8 --- /dev/null +++ b/studioShared/lib/queries/customerCases.ts @@ -0,0 +1,4 @@ +import { groq } from "next-sanity"; + +export const CUSTOMER_CASES_QUERY = groq`*[_type == "customerCase"] +`; diff --git a/studioShared/lib/store.ts b/studioShared/lib/store.ts index edc4e100c..d092e427d 100644 --- a/studioShared/lib/store.ts +++ b/studioShared/lib/store.ts @@ -1,8 +1,13 @@ -import * as queryStore from "@sanity/react-loader"; +import { createQueryStore } from "@sanity/react-loader"; import { sharedClient } from "./client"; import { token } from "./token"; -queryStore.setServerClient(sharedClient.withConfig({ token })); +const { loadQuery: loadSharedQuery, setServerClient } = createQueryStore({ + client: false, + ssr: true, +}); -export const { loadQuery: loadSharedQuery } = queryStore; +setServerClient(sharedClient.withConfig({ token })); + +export { loadSharedQuery }; diff --git a/studioShared/schemas/documents/customerCase.ts b/studioShared/schemas/documents/customerCase.ts index e1a00f887..37033f261 100644 --- a/studioShared/schemas/documents/customerCase.ts +++ b/studioShared/schemas/documents/customerCase.ts @@ -3,7 +3,7 @@ import { defineField, defineType } from "sanity"; import { richText, title } from "studio/schemas/fields/text"; import { titleSlug } from "studio/schemas/schemaTypes/slug"; -export const customerCaseID = "CustomerCase"; +export const customerCaseID = "customerCase"; const customerCase = defineType({ name: customerCaseID,