From 5dd53ddc1008729f271668e1122a4f0db2d39578 Mon Sep 17 00:00:00 2001 From: anemne <63043552+anemne@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:28:44 +0100 Subject: [PATCH] V3 customercase entry module (#920) * skeleton for customercase entry * added botton for customerCases we have * Merge branch 'v3' into v3-customercase-entry * temp * linting fixes * imagerendering and datafetching * media * add tag component * remove tag ad we're replacing it with other tag component * add link to customercase * format deliverystrings * image sizing * Templatus gridificous! * tried fixing scaling on card * fix: responsiveness of costumer case (#957) * border on image * focus and opacity on images * small changes in opacity * remove unused Text component in header to fix lint check * swedish translation --------- Co-authored-by: Truls Henrik <39614013+trulshj@users.noreply.github.com> Co-authored-by: Jacob Berglund Co-authored-by: Mikael Brevik --- .vscode/settings.json | 3 +- messages/en.json | 16 ++- messages/no.json | 16 ++- messages/se.json | 16 ++- .../customerCases/customerCases.module.css | 2 + .../employeeCard/employeeCard.module.css | 3 - src/components/image/SanityImage.tsx | 2 - src/components/navigation/header/Header.tsx | 1 - .../customerCasesEntry/CustomerCasesEntry.tsx | 55 ++++++++ .../customerCasesEntry/CustomerCasesList.tsx | 132 ++++++++++++++++++ .../customerCasesEntry.module.css | 128 +++++++++++++++++ .../utils/formatCapitalizedFirstLetter.ts | 3 + src/utils/renderSection.tsx | 25 ++++ studio/lib/interfaces/pages.ts | 8 ++ studio/lib/queries/specialPages.ts | 8 +- studio/schemas/documents/pageBuilder.ts | 2 + .../objects/sections/customerCasesEntry.ts | 16 +++ studioShared/lib/interfaces/customerCases.ts | 4 + studioShared/lib/queries/customerCases.ts | 20 +++ 19 files changed, 439 insertions(+), 21 deletions(-) create mode 100644 src/components/sections/customerCasesEntry/CustomerCasesEntry.tsx create mode 100644 src/components/sections/customerCasesEntry/CustomerCasesList.tsx create mode 100644 src/components/sections/customerCasesEntry/customerCasesEntry.module.css create mode 100644 src/components/utils/formatCapitalizedFirstLetter.ts create mode 100644 studio/schemas/objects/sections/customerCasesEntry.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 4f789f228..a3b881bde 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "i18n-ally.localesPaths": ["i18n", "messages", "src/i18n"] + "i18n-ally.localesPaths": ["i18n", "messages", "src/i18n"], + "i18n-ally.keystyle": "nested" } diff --git a/messages/en.json b/messages/en.json index 2e56ddb0d..dea808977 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,4 +1,7 @@ { + "linking_words": { + "for": "for" + }, "navigation": { "home": "Home" }, @@ -14,7 +17,12 @@ "development": "Development", "project_management": "Project management", "url": "Url", - "collaborators": "Collaborators" + "collaborators": "Collaborators", + "customer_case_entry": { + "image": "image", + "case": "Case", + "field": "Field" + } }, "contact_information": { "help": "Curious about Variant and want to explore what we offer?", @@ -46,9 +54,9 @@ "location": "Location", "all": "All", "Project Management": "Project Management", - "Design": "Design", - "Utvikling": "Development", - "Administasjon": "Administration", + "design": "Design", + "development": "Development", + "administration": "Administration", "field": "Field", "showMore": "Show all" }, diff --git a/messages/no.json b/messages/no.json index 1a142a983..b962c7935 100644 --- a/messages/no.json +++ b/messages/no.json @@ -1,4 +1,7 @@ { + "linking_words": { + "for": "for" + }, "navigation": { "home": "Hjem" }, @@ -14,7 +17,12 @@ "development": "Utvikling", "project_management": "Prosjektledelse", "url": "Link", - "collaborators": "Samarbeidspartnere" + "collaborators": "Samarbeidspartnere", + "customer_case_entry": { + "image": "bilde", + "case": "Case", + "field": "Fag" + } }, "contact_information": { "help": "Trenger du hjelp med lignende eller noe helt annet?", @@ -45,9 +53,9 @@ "location": "Lokasjon", "all": "Alle", "Project Management": "Prosjektledelse", - "Design": "Design", - "Utvikling": "Utvikling", - "Administasjon": "Administrasjon", + "design": "Design", + "development": "Utvikling", + "administration": "Administrasjon", "field": "Fag", "showMore": "Vis alle" }, diff --git a/messages/se.json b/messages/se.json index e88f3a4b3..dd88fcde5 100644 --- a/messages/se.json +++ b/messages/se.json @@ -1,4 +1,7 @@ { + "linking_words": { + "for": "för" + }, "navigation": { "home": "Hem" }, @@ -14,7 +17,12 @@ "development": "Utveckling", "project_management": "Projektledning", "url": "Link", - "collaborators": "Samarbeidspartnere" + "collaborators": "Samarbeidspartnere", + "customer_case_entry": { + "image": "bild", + "case": "Case", + "field": "Fält" + } }, "contact_information": { "help": "Nyfiken på Variant och vill utforska vad vi erbjuder?", @@ -45,9 +53,9 @@ "location": "Kontor", "all": "Alla", "Project Management": "Projektledning", - "Design": "Design", - "Utvikling": "Utveckling", - "Administasjon": "Administration", + "design": "Design", + "development": "Utveckling", + "administration": "Administration", "field": "Fält", "showMore": "Vis alla" }, diff --git a/src/components/customerCases/customerCases.module.css b/src/components/customerCases/customerCases.module.css index 1ad7a4eeb..3fe42d831 100644 --- a/src/components/customerCases/customerCases.module.css +++ b/src/components/customerCases/customerCases.module.css @@ -26,6 +26,8 @@ .caseImageWrapper { min-width: 20rem; max-width: 20rem; + width: 20rem; + height: 14rem; } .caseImageWrapper img { diff --git a/src/components/employeeCard/employeeCard.module.css b/src/components/employeeCard/employeeCard.module.css index 920269654..a068f41e1 100644 --- a/src/components/employeeCard/employeeCard.module.css +++ b/src/components/employeeCard/employeeCard.module.css @@ -74,9 +74,6 @@ color: currentColor; } -.employeeRole { -} - .employeeRoleDot::after { content: "·"; margin: 0 0.25rem; diff --git a/src/components/image/SanityImage.tsx b/src/components/image/SanityImage.tsx index 676c8279d..fb2aa6ed7 100644 --- a/src/components/image/SanityImage.tsx +++ b/src/components/image/SanityImage.tsx @@ -61,8 +61,6 @@ const SanityAssetImage = ({ objectPosition, maxWidth: "100%", maxHeight: "100%", - height: "auto", - width: "auto", }} /> ); diff --git a/src/components/navigation/header/Header.tsx b/src/components/navigation/header/Header.tsx index a8561a441..ff40317e2 100644 --- a/src/components/navigation/header/Header.tsx +++ b/src/components/navigation/header/Header.tsx @@ -10,7 +10,6 @@ import { defaultLanguage } from "i18n/supportedLanguages"; import LanguageSwitcher from "src/components/languageSwitcher/LanguageSwitcher"; import CustomLink from "src/components/link/CustomLink"; import LinkButton from "src/components/linkButton/LinkButton"; -import Text from "src/components/text/Text"; import useScrollDirection from "src/utils/hooks/useScrollDirection"; import { getHref } from "src/utils/link"; import { Announcement } from "studio/lib/interfaces/announcement"; diff --git a/src/components/sections/customerCasesEntry/CustomerCasesEntry.tsx b/src/components/sections/customerCasesEntry/CustomerCasesEntry.tsx new file mode 100644 index 000000000..c1fec4869 --- /dev/null +++ b/src/components/sections/customerCasesEntry/CustomerCasesEntry.tsx @@ -0,0 +1,55 @@ +import { headers } from "next/headers"; + +import { Locale } from "src/i18n/routing"; +import { getDraftModeInfo } from "src/utils/draftmode"; +import { domainFromHostname } from "src/utils/url"; +import { CUSTOMER_CASES_PAGE_SITEMAP_QUERY } from "studio/lib/queries/specialPages"; +import { loadStudioQuery } from "studio/lib/store"; +import { CustomerCaseEntry } from "studioShared/lib/interfaces/customerCases"; +import { CUSTOMER_CASE_ENTRY_QUERY } from "studioShared/lib/queries/customerCases"; +import { loadSharedQuery } from "studioShared/lib/store"; + +import CustomerCasesList from "./CustomerCasesList"; + +interface CustomerCasesProps { + language: Locale; +} + +async function CustomerCasesEntry({ language }: CustomerCasesProps) { + const { perspective } = getDraftModeInfo(); + const domain = domainFromHostname(headers().get("host")); + + const customerCaseResult = await loadSharedQuery( + CUSTOMER_CASE_ENTRY_QUERY, + { + domain, + language, + }, + { + perspective, + }, + ); + + const customerCasePageSlug = ( + await loadStudioQuery<{ slug: string } | null>( + CUSTOMER_CASES_PAGE_SITEMAP_QUERY, + { + language, + }, + ) + ).data?.slug; + + return ( + customerCaseResult && ( +
+ +
+ ) + ); +} + +export default CustomerCasesEntry; diff --git a/src/components/sections/customerCasesEntry/CustomerCasesList.tsx b/src/components/sections/customerCasesEntry/CustomerCasesList.tsx new file mode 100644 index 000000000..44e15b8de --- /dev/null +++ b/src/components/sections/customerCasesEntry/CustomerCasesList.tsx @@ -0,0 +1,132 @@ +"use client"; + +import Link from "next/link"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; + +import { SanitySharedImage } from "src/components/image/SanityImage"; +import { Tag } from "src/components/tag"; +import Text from "src/components/text/Text"; +import { capitalizeFirstLetter } from "src/components/utils/formatCapitalizedFirstLetter"; +import { CustomerCaseEntry } from "studioShared/lib/interfaces/customerCases"; + +import styles from "./customerCasesEntry.module.css"; + +interface CustomerCasesProps { + customerCases: CustomerCaseEntry[]; + language: string; + customerCasePageSlug?: string; +} + +const CustomerCaseList = ({ + customerCases, + language, + customerCasePageSlug, +}: CustomerCasesProps) => { + const [selectedCustomerCase, setSelectedCustomerCase] = + useState(customerCases[0] || null); + + const deliveryNames = [ + selectedCustomerCase.projectInfo.deliveries.projectManagement && + "Project Management", + selectedCustomerCase.projectInfo.deliveries.design && "Design", + selectedCustomerCase.projectInfo.deliveries.development && "Development", + ].filter(Boolean); + + return ( +
+
+
+
+ + + + +
+
+ + {selectedCustomerCase.image && ( + + )} + +
+
+ ); +}; +export default CustomerCaseList; + +function CardInfo({ + selectedCustomerCase, + deliveryNames, +}: { + selectedCustomerCase: CustomerCaseEntry; + deliveryNames: string[]; +}) { + const t = useTranslations("customer_case"); + + return ( +
+ + {selectedCustomerCase.basicTitle} + +
+ {t("customer_case_entry.field")} +
+ {deliveryNames.map((deliveryName, index) => ( + + {deliveryName} + + ))} +
+
+
+ ); +} + +function TagRow({ + customerCases, + selectedCustomerCase, + setSelectedCustomerCase, +}: { + customerCases: CustomerCaseEntry[]; + selectedCustomerCase: CustomerCaseEntry; + setSelectedCustomerCase: (customerCase: CustomerCaseEntry) => void; +}) { + const t = useTranslations("customer_case"); + + return ( +
+ + {t("customer_case_entry.case")} + + {customerCases.map( + (customerCase: CustomerCaseEntry) => + customerCase && ( +
+ setSelectedCustomerCase(customerCase)} + text={capitalizeFirstLetter(customerCase.projectInfo.customer)} + /> +
+ ), + )} +
+ ); +} diff --git a/src/components/sections/customerCasesEntry/customerCasesEntry.module.css b/src/components/sections/customerCasesEntry/customerCasesEntry.module.css new file mode 100644 index 000000000..b8f35e48b --- /dev/null +++ b/src/components/sections/customerCasesEntry/customerCasesEntry.module.css @@ -0,0 +1,128 @@ +.container { + container-type: inline-size; + container-name: customer-case-container; +} + +.wrapper { + --container-padding: 0.375rem; + background-color: var(--background-bg-dark); + padding: var(--container-padding); + border-radius: 0.375rem; + display: flex; + gap: 1rem; +} + +.imageWrapper { + aspect-ratio: 16 / 9; + max-height: 30rem; + flex-shrink: 1; +} +.imageWrapper img { + display: block; + width: 100%; + height: 100%; + border-radius: 1.5rem; + transition: all 0.3s ease; + opacity: 0.8; + &:hover { + opacity: 1; + border-radius: 0.375rem; + } +} + +.info:hover ~ .imageWrapper img { + border-radius: 0.375rem; + opacity: 1; +} + +.info { + flex: 1; + box-sizing: border-box; + min-width: 28rem; + padding: 2rem 0 2rem 2rem; + + @media (max-width: 500px) { + padding: 1rem 0 1rem 1rem; + min-width: 20rem; + } + @media (max-width: 400px) { + padding: 0.5rem 0 0.5rem 0.5rem; + } +} + +/* Used to take all width in flex but limit content width */ +.infoInnerMaxWidth { + max-width: 30.5rem; + display: flex; + flex-direction: column; + gap: 1.5rem; +} +@container customer-case-container (width > 0) { + .heading { + font-size: max(2em, 2rem + 1cqi); + } +} + +@container customer-case-container (width < 900px) { + .wrapper { + flex-direction: column; + } + .imageWrapper { + order: 1; + } + + .info { + order: 2; + } +} + +.link:focus-visible .heading { + text-decoration: underline; +} + +.heading { + text-wrap: balance; + overflow: hidden; + + @media (max-width: 1130px) { + font-size: 2.125rem; + } +} + +.font { + color: var(--text-primary-light); +} + +.TagRow { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; + grid-area: tags; +} + +.cardInfo { + grid-area: content; + color: var(--text-primary-light); + display: flex; + gap: 2rem; + flex-direction: column; +} + +.deliveries { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.deliveriesList { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.dotSeparator:not(:last-child)::after { + content: "·"; + margin: 0.5rem; +} diff --git a/src/components/utils/formatCapitalizedFirstLetter.ts b/src/components/utils/formatCapitalizedFirstLetter.ts new file mode 100644 index 000000000..c83285017 --- /dev/null +++ b/src/components/utils/formatCapitalizedFirstLetter.ts @@ -0,0 +1,3 @@ +export function capitalizeFirstLetter(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); +} diff --git a/src/utils/renderSection.tsx b/src/utils/renderSection.tsx index 55023a94d..3795d5c0d 100644 --- a/src/utils/renderSection.tsx +++ b/src/utils/renderSection.tsx @@ -8,6 +8,7 @@ import CallToAction from "src/components/sections/callToAction/CallToAction"; import CallToActionPreview from "src/components/sections/callToAction/CallToActionPreview"; import CompensationCalculator from "src/components/sections/compensation-calculator/CompensationCalculator"; import ContactBox from "src/components/sections/contact-box/ContactBox"; +import CustomerCasesEntry from "src/components/sections/customerCasesEntry/CustomerCasesEntry"; import EmployeeHighlight from "src/components/sections/employeeHighlight/EmployeeHighlight"; import Employees from "src/components/sections/employees/Employees"; import Grid from "src/components/sections/grid/Grid"; @@ -23,10 +24,12 @@ import { LogoSalad } from "src/components/sections/logoSalad/LogoSalad"; import LogoSaladPreview from "src/components/sections/logoSalad/LogoSaladPreview"; import { Testimonials } from "src/components/sections/testimonials/Testimonials"; import TestimonialsPreview from "src/components/sections/testimonials/TestimonialsPreview"; +import { Locale } from "src/i18n/routing"; import { ArticleSection, CallToActionSection, CalloutSection, + CustomerCasesEntrySection, GridSection, HeroSection, ImageSection, @@ -184,6 +187,20 @@ const renderGridSection = ( ); }; +const renderCustomerCasesEntrySection = ( + section: CustomerCasesEntrySection, + sectionIndex: number, + isDraftMode: boolean, + initialData: QueryResponseInitial, + language: Locale, +) => { + return isDraftMode ? ( + + ) : ( + + ); +}; + const SectionRenderer = ({ language, section, @@ -256,6 +273,14 @@ const SectionRenderer = ({ return ; case "grid": return renderGridSection(section, sectionIndex, isDraftMode, initialData); + case "customerCasesEntry": + return renderCustomerCasesEntrySection( + section, + sectionIndex, + isDraftMode, + initialData, + language as Locale, + ); case "contactBox": return ; case "employees": diff --git a/studio/lib/interfaces/pages.ts b/studio/lib/interfaces/pages.ts index d6bf42d49..f42b81e66 100644 --- a/studio/lib/interfaces/pages.ts +++ b/studio/lib/interfaces/pages.ts @@ -116,6 +116,12 @@ export interface EmployeesSection { basicTitle: string; } +export interface CustomerCasesEntrySection { + _type: "customerCasesEntry"; + _key: string; + basicTitle: string; +} + export interface JobsSection { _type: "jobs"; _key: string; @@ -161,6 +167,8 @@ export type Section = | ImageSection | ImageSplitSection | GridSection + | EmployeesSection + | CustomerCasesEntrySection | ContactBoxSection | EmployeesSection | EmployeeHighlightSection diff --git a/studio/lib/queries/specialPages.ts b/studio/lib/queries/specialPages.ts index 2c040affa..86d2431ba 100644 --- a/studio/lib/queries/specialPages.ts +++ b/studio/lib/queries/specialPages.ts @@ -1,6 +1,10 @@ import { groq } from "next-sanity"; -import { LANGUAGE_FIELD_FRAGMENT, TRANSLATED_LINK_FRAGMENT } from "./i18n"; +import { + LANGUAGE_FIELD_FRAGMENT, + TRANSLATED_LINK_FRAGMENT, + TRANSLATED_SLUG_VALUE_FRAGMENT, +} from "./i18n"; import { translatedFieldFragment } from "./utils/i18n"; //Compensations @@ -73,6 +77,6 @@ export const CUSTOMER_CASES_PAGE_QUERY = groq` export const CUSTOMER_CASES_PAGE_SITEMAP_QUERY = groq` *[_type == "customerCasesPage"][0] { _updatedAt, - slug + "slug": ${TRANSLATED_SLUG_VALUE_FRAGMENT} } `; diff --git a/studio/schemas/documents/pageBuilder.ts b/studio/schemas/documents/pageBuilder.ts index 3b5a220ce..f3a712b8d 100644 --- a/studio/schemas/documents/pageBuilder.ts +++ b/studio/schemas/documents/pageBuilder.ts @@ -7,6 +7,7 @@ import callout from "studio/schemas/objects/sections/callout"; import callToAction from "studio/schemas/objects/sections/callToAction"; import { compensationCalculator } from "studio/schemas/objects/sections/compensation-calculator"; import contactBox from "studio/schemas/objects/sections/contact-box"; +import { customerCasesEntry } from "studio/schemas/objects/sections/customerCasesEntry"; import { employeeHighlightSection } from "studio/schemas/objects/sections/employeeHighlight"; import { employees } from "studio/schemas/objects/sections/employees"; import grid from "studio/schemas/objects/sections/grid"; @@ -55,6 +56,7 @@ const pageBuilder = defineType({ imageSection, grid, employees, + customerCasesEntry, contactBox, jobs, employeeHighlightSection, diff --git a/studio/schemas/objects/sections/customerCasesEntry.ts b/studio/schemas/objects/sections/customerCasesEntry.ts new file mode 100644 index 000000000..8334f2eb8 --- /dev/null +++ b/studio/schemas/objects/sections/customerCasesEntry.ts @@ -0,0 +1,16 @@ +import { defineField } from "sanity"; + +export const customerCasesEntry = defineField({ + name: "customerCasesEntry", + title: "Customer Cases Entry", + type: "object", + fields: [ + { + name: "basicTitle", + title: "Basic Title", + type: "string", + description: + "This will be the title of the customer cases entry section. Make it engaging to capture the attention of your audience.", + }, + ], +}); diff --git a/studioShared/lib/interfaces/customerCases.ts b/studioShared/lib/interfaces/customerCases.ts index 20d165266..9c82968ad 100644 --- a/studioShared/lib/interfaces/customerCases.ts +++ b/studioShared/lib/interfaces/customerCases.ts @@ -76,3 +76,7 @@ export interface CustomerCase extends CustomerCaseBase { sections: CustomerCaseSection[]; featuredCases?: CustomerCaseBase[] | null; } + +export interface CustomerCaseEntry extends CustomerCaseBase { + projectInfo: CustomerCaseProjectInfo; +} diff --git a/studioShared/lib/queries/customerCases.ts b/studioShared/lib/queries/customerCases.ts index 024976b4e..f50ffc566 100644 --- a/studioShared/lib/queries/customerCases.ts +++ b/studioShared/lib/queries/customerCases.ts @@ -118,6 +118,26 @@ export const CUSTOMER_CASE_QUERY = groq` } `; +export const CUSTOMER_CASE_ENTRY_QUERY = groq` + *[_type == "customerCase" && ($domain == null || $domain in domains)] { + ${CUSTOMER_CASE_BASE_FRAGMENT}, + "projectInfo": projectInfo { + customer, + "deliveries": { + "design": deliveries.design[] { + "designDelivery": ${translatedFieldFragment("designDelivery")} + }, + "development": deliveries.development[] { + "developmentDelivery": ${translatedFieldFragment("developmentDelivery")} + }, + "projectManagement": deliveries.projectManagement[] { + "projectManagementDelivery": ${translatedFieldFragment("projectManagementDelivery")} + } + }, + }, + } +`; + export const CUSTOMER_CASES_SITEMAP_QUERY = groq` *[_type == "customerCase"] { _updatedAt,