Skip to content

Commit

Permalink
v3 - consultants in project (#831)
Browse files Browse the repository at this point in the history
* fix(CustomerCase): overflowing content width

* feat(CustomerCase): project consultants section

* feat(CustomerCase): add usage note to consultants field in project info

* feat(CustomerCase): fetch employees by email instead of alias

since we don't know if aliases are unique across countries

* feat(CustomerCase): use email type for project consultant identifiers

* refactor(CustomerCase): extract consultants section to own component

* feat(CustomerCaseConsultants): add employee link to customer case consultant card

* feat(CustomerCase): remove nowrap for project info
  • Loading branch information
mathiazom authored Oct 30, 2024
1 parent 9ecb9f4 commit ba9658f
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 8 deletions.
18 changes: 15 additions & 3 deletions src/components/customerCases/customerCase/CustomerCase.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SanitySharedImage } from "src/components/image/SanityImage";
import Text from "src/components/text/Text";
import { fetchEmployeesByEmails } from "src/utils/employees";
import {
CustomerCase as CustomerCaseDocument,
CustomerCaseSection as CustomerCaseSectionObject,
Expand All @@ -8,6 +9,7 @@ import {

import styles from "./customerCase.module.css";
import FeaturedCases from "./featuredCases/FeaturedCases";
import CustomerCaseConsultants from "./sections/customerCaseConsultants/CustomerCaseConsultants";
import ImageSection from "./sections/image/ImageSection";
import RichTextSection from "./sections/richText/RichTextSection";

Expand Down Expand Up @@ -38,10 +40,14 @@ export interface CustomerCaseProps {
customerCasesPagePath: string[];
}

export default function CustomerCase({
export default async function CustomerCase({
customerCase,
customerCasesPagePath,
}: CustomerCaseProps) {
const consultantsResult = await fetchEmployeesByEmails(
customerCase.projectInfo.consultants,
);

return (
<div className={styles.wrapper}>
<div className={styles.content}>
Expand Down Expand Up @@ -116,14 +122,14 @@ export default function CustomerCase({
)}
</div>
)}
{customerCase.projectInfo.consultants && (
{consultantsResult.ok && (
<div className={styles.projectInfoItem}>
<Text type={"labelRegular"}>Konsulenter</Text>
<Text
type={"labelLight"}
className={styles.projectInfoItemValue}
>
{customerCase.projectInfo.consultants.join(", ")}
{consultantsResult.value.map((c) => c.name).join(", ")}
</Text>
</div>
)}
Expand All @@ -134,6 +140,12 @@ export default function CustomerCase({
<CustomerCaseSection key={section._key} section={section} />
))}
</div>
{consultantsResult.ok && (
<CustomerCaseConsultants
language={customerCase.language}
consultants={consultantsResult.value}
/>
)}
{customerCase.featuredCases &&
customerCase.featuredCases.length > 0 && (
<FeaturedCases
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
width: 100%;
display: flex;
flex-direction: column;
width: 100%;
max-width: 1400px;
padding: 0 2rem;

Expand Down Expand Up @@ -94,7 +95,6 @@

.projectInfoItemValue {
font-weight: 300 !important;
white-space: nowrap;
}

.projectInfoItemBadge {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import Image from "next/image";
import Link from "next/link";

import CustomLink from "src/components/link/CustomLink";
import Text from "src/components/text/Text";
import { ChewbaccaEmployee } from "src/types/employees";
import { aliasFromEmail } from "src/utils/employees";
import { LinkType } from "studio/lib/interfaces/navigation";
import { EMPLOYEE_PAGE_SLUG_QUERY } from "studio/lib/queries/siteSettings";
import { loadStudioQuery } from "studio/lib/store";

import styles from "./customerCaseConsulants.module.css";

export interface CustomerCaseConsultantsProps {
language: string;
consultants: ChewbaccaEmployee[];
}

export default async function CustomerCaseConsultants({
language,
consultants,
}: CustomerCaseConsultantsProps) {
const employeePageSlugRes = await loadStudioQuery<{ slug: string } | null>(
EMPLOYEE_PAGE_SLUG_QUERY,
{
language,
},
);
const employeePageSlug = employeePageSlugRes.data?.slug;

return (
<div className={styles.wrapper}>
<Text type={"h3"}>Varianter på prosjektet</Text>
<div className={styles.content}>
{consultants.map((consultant) => {
const title = (
<p className={styles.consultantName}>{consultant.name}</p>
);
return (
consultant.imageThumbUrl &&
consultant.name &&
consultant.email && (
<div key={consultant.email} className={styles.consultant}>
<div className={styles.consultantImage}>
<Image
src={consultant.imageUrl ?? consultant.imageThumbUrl}
alt={consultant.name}
objectFit="cover"
fill={true}
/>
</div>
<div className={styles.consultantInfo}>
{employeePageSlug !== undefined ? (
<Link
href={`/${language}/${employeePageSlug}/${aliasFromEmail(consultant.email)}`}
>
{title}
</Link>
) : (
title
)}
{consultant.officeName && (
<p className={styles.consultantRole}>
{consultant.officeName}
</p>
)}
{consultant.email && (
<p className={styles.consultantEmail}>{consultant.email}</p>
)}
{consultant.telephone && (
<p className={styles.consultantTelephone}>
{consultant.telephone}
</p>
)}
<CustomLink
size={"small"}
link={{
_key: "go-to-mini-cv",
_type: "link",
linkType: LinkType.Internal,
linkTitle: "Gå til Mini-CV",
internalLink: {
_ref: `${language}/${employeePageSlug}/${aliasFromEmail(consultant.email)}`,
},
}}
/>
</div>
</div>
)
);
})}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.wrapper {
display: flex;
flex-direction: column;
gap: 2rem;
margin: 4rem 0;
}

.content {
width: 100%;
text-wrap: wrap;
column-gap: 12px;
row-gap: 52px;
display: grid;
grid-template-columns: repeat(auto-fit, 350px);
justify-content: space-between;
}

.consultant {
display: flex;
width: 100%;
gap: 1rem;
}

.consultantImage {
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--primary-black);
border-radius: 12px;
height: 125px;
width: 50%;
padding: 1rem;
position: relative;
}

.consultantInfo {
display: flex;
flex-direction: column;
width: 50%;
gap: 0.5rem;
}

.consultantName {
color: var(--primary-black);
font-size: 16px;
font-weight: 600;
}

.consultantRole {
color: var(--primary-black);
font-size: 16px;
font-weight: 300;
}

.consultantEmail,
.consultantTelephone {
color: var(--primary-black);
font-size: 14px;
font-weight: 300;
}
14 changes: 12 additions & 2 deletions src/components/link/CustomLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ interface ICustomLink {
type?: ComponentLinkType;
link: ILink;
isSelected?: boolean;
size?: "normal" | "small";
}

const CustomLink = ({ type = "link", link, isSelected }: ICustomLink) => {
const CustomLink = ({
type = "link",
link,
isSelected,
size = "normal",
}: ICustomLink) => {
const href = getHref(link);
const newTab = link.newTab;
const target = newTab ? "_blank" : undefined;
Expand All @@ -27,7 +33,11 @@ const CustomLink = ({ type = "link", link, isSelected }: ICustomLink) => {
return (
link.linkTitle &&
(type === "link" ? (
<div className={styles.wrapper}>
<div
className={
styles.wrapper + (size === "small" ? ` ${styles.sizeSmall}` : "")
}
>
<Link
className={styles.link}
href={href}
Expand Down
21 changes: 21 additions & 0 deletions src/components/link/link.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,31 @@
}
}

.sizeSmall .link {
font-size: 0.95rem;
gap: 0.25rem;

@media (min-width: 1024px) {
font-size: 1rem;
}

&:hover {
gap: 0.5rem;
}

&::after {
transform: scale(0.75);
}
}

.span {
padding-bottom: 0.125rem;
}

.sizeSmall .span {
padding-bottom: 0;
}

.headerLink {
color: var(--primary-white);
cursor: pointer;
Expand Down
2 changes: 2 additions & 0 deletions src/types/employees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export interface ChewbaccaEmployeesResponse {
employees: ChewbaccaEmployee[];
}

export type ChewbaccaEmployeeResponse = ChewbaccaEmployee;

export function isChewbaccaEmployeesResponse(
value: unknown,
): value is ChewbaccaEmployeesResponse {
Expand Down
17 changes: 17 additions & 0 deletions src/utils/employees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,20 @@ export function aliasFromEmail(email: string): string {
export function domainFromEmail(email: string) {
return email.split("@")[1];
}

export async function fetchEmployeesByEmails(
emails: string[],
): Promise<Result<ChewbaccaEmployee[], string>> {
const allEmployeesRes = await fetchAllChewbaccaEmployees();
if (!allEmployeesRes.ok) {
return allEmployeesRes;
}
return ResultOk(
// mapping from input array (instead of filtering all employees) to preserve order
emails
.map((email) =>
allEmployeesRes.value.find((employee) => employee.email === email),
)
.filter((employee) => employee !== undefined),
);
}
5 changes: 3 additions & 2 deletions studioShared/schemas/fields/customerCaseProjectInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ export const customerCaseProjectInfo = defineField({
defineField({
// TODO: We should be able to select the consultants from a list
name: "consultants",
description: "The consultants for the project",
description:
"The consultants for the project. Use employee emails (e.g. '[email protected]').",
type: "array",
of: [{ type: "string" }],
of: [{ type: "email" }],
}),
],
});

0 comments on commit ba9658f

Please sign in to comment.