diff --git a/.eslintrc.js b/.eslintrc.js index 5e238e6..267cb6e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,4 +3,8 @@ module.exports = { extends: ['plugin:@next/next/recommended', '@payloadcms'], ignorePatterns: ['**/payload-types.ts'], plugins: ['prettier'], + rules: { + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/array-type': 'off', + }, } diff --git a/src/app/(pages)/[slug]/page.tsx b/src/app/(pages)/[slug]/page.tsx index 2ebbfd8..f5c1304 100644 --- a/src/app/(pages)/[slug]/page.tsx +++ b/src/app/(pages)/[slug]/page.tsx @@ -1,19 +1,17 @@ -import { Metadata } from "next"; -import { draftMode } from "next/headers"; +import { Metadata } from 'next' +import { draftMode } from 'next/headers' +import { Page } from '../../../payload/payload-types' +import { fetchDoc } from '../../_api/fetchDoc' +import HubContentGrid from '../../_blocks/HubContentGrid/page' +import { Subscribe } from '../../_blocks/Subscribe' +import SearchBar from '../../_components/SearchBar' +import { ALL_CONTENT } from '../../_graphql/allContent' +import { fetcher } from '../../_utilities/fetcher' +import { generateMeta } from '../../_utilities/generateMeta' +import styles from './styles.module.css' -import { Page } from "../../../payload/payload-types"; -import { fetchDoc } from "../../_api/fetchDoc"; - -import { generateMeta } from "../../_utilities/generateMeta"; - -import styles from "./styles.module.css"; - - -import { Subscribe } from "@/app/_blocks/Subscribe"; -import { ALL_CONTENT } from "@/app/_graphql/allContent"; -import ContentCard from "@/app/_components/ContentCard"; -import { fetcher } from "@/app/_utilities/fetcher"; +import { AllIcon } from '@/app/_icons/icons' // Payload Cloud caches all files through Cloudflare, so we don't need Next.js to cache them as well // This means that we can turn off Next.js data caching and instead rely solely on the Cloudflare CDN @@ -21,25 +19,21 @@ import { fetcher } from "@/app/_utilities/fetcher"; // But we also need to force Next.js to dynamically render this page on each request for preview mode to work // See https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic // If you are not using Payload Cloud then this line can be removed, see `../../../README.md#cache` -export const dynamic = "force-dynamic"; - - -export default async function Page({ params: { slug = "home" } }) { - const content = await fetcher({ query: ALL_CONTENT }); - - const content_list = Object.keys(content).flatMap(key => - content[key].map(content => ({ - contentType: key, - content: content, - }))); +export const dynamic = 'force-dynamic' +export default async function Page({ params: { slug = 'home' } }) { + const articles = await fetcher({ query: ALL_CONTENT }) return ( <> {/* Head Block*/}
-
- +
+

+ Content +
+ Hub +

{/* Top Highlight */} @@ -47,10 +41,10 @@ export default async function Page({ params: { slug = "home" } }) {

HIGHLIGHTS

From nutritionist to product designer: Reinvinting my carrer at 30

- Inside subvisual Date and Readtime{" "} + Inside subvisual Date and Readtime{' '}

- + Rui Sousa
@@ -59,45 +53,40 @@ export default async function Page({ params: { slug = "home" } }) {

HIGHLIGHTS

- {" "} + {' '} From nutritionist to product designer:
Reinvinting my carrer at 30

- Inside subvisual Date and Readtime{" "} + Inside subvisual Date and Readtime{' '}

- + Rui Sousa
+ {/* Search Bar */} + {/* Content Grid */} -
-
- {content_list.map((article, i) => ( -
- -
- ))} -
-
+ + - ); + ) } -export async function generateMetadata({ params: { slug = "home" } }): Promise { - const { isEnabled: isDraftMode } = draftMode(); +export async function generateMetadata({ params: { slug = 'home' } }): Promise { + const { isEnabled: isDraftMode } = draftMode() - let page: Page | null = null; + let page: Page | null = null try { page = await fetchDoc({ - collection: "pages", + collection: 'pages', slug, draft: isDraftMode, - }); + }) } catch (error) { // don't throw an error if the fetch fails // this is so that we can render static fallback pages for the demo @@ -105,5 +94,5 @@ export async function generateMetadata({ params: { slug = "home" } }): Promise -

This is a Colfax Medium H1.

-

This is a Colfax Redular H1.

-

This is an Acta H1.

+

This is a Colfax Medium H1.

+

This is a Colfax Redular H1.

+

This is an Acta H1.

-

This is a Colfax Medium H2.

-

This is a Colfax Regular H2.

-

This is an Acta H2.

+

This is a Colfax Medium H2.

+

This is a Colfax Regular H2.

+

This is an Acta H2.

-

This is a Colfax Medium H3.

-

This is a Colfax Regular H3.

-

This is an Acta H3.

+

This is a Colfax Medium H3.

+

This is a Colfax Regular H3.

+

This is an Acta H3.

-

This is a Colfax Medium H4.

-

This is a Colfax Regular H4.

-

This is an Acta H4.

+

This is a Colfax Medium H4.

+

This is a Colfax Regular H4.

+

This is an Acta H4.

-
This is a Colfax Medium H5.
-
This is a Colfax Regular H5.
-
This is an Acta H5.
+
This is a Colfax Medium H5.
+
This is a Colfax Regular H5.
+
This is an Acta H5.
-
This is a Colfax Medium H6.
-
This is a Colfax Regular H6.
-
This is an Acta H6.
+
This is a Colfax Medium H6.
+
This is a Colfax Regular H6.
+
This is an Acta H6.
- ); + ) } diff --git a/src/app/_api/fetchGlobals.ts b/src/app/_api/fetchGlobals.ts index 281d3dc..7c8c5fe 100644 --- a/src/app/_api/fetchGlobals.ts +++ b/src/app/_api/fetchGlobals.ts @@ -1,108 +1,108 @@ -import type { Footer, Header, Settings, Social } from "../../payload/payload-types"; -import { FOOTER_QUERY, HEADER_QUERY, SETTINGS_QUERY, SOCIALS_QUERY } from "../_graphql/globals"; -import { GRAPHQL_API_URL } from "./shared"; +import type { Footer, Header, Settings, Social } from '../../payload/payload-types' +import { FOOTER_QUERY, HEADER_QUERY, SETTINGS_QUERY, SOCIALS_QUERY } from '../_graphql/globals' +import { GRAPHQL_API_URL } from './shared' export async function fetchSettings(): Promise { - if (!GRAPHQL_API_URL) throw new Error("NEXT_PUBLIC_SERVER_URL not found"); + if (!GRAPHQL_API_URL) throw new Error('NEXT_PUBLIC_SERVER_URL not found') try { return await fetch(`${GRAPHQL_API_URL}/api/graphql`, { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - cache: "no-store", + cache: 'no-store', body: JSON.stringify({ query: SETTINGS_QUERY, }), }) ?.then(res => { - if (!res.ok) throw new Error("Error fetching doc"); - return res.json(); + if (!res.ok) throw new Error('Error fetching doc') + return res.json() }) ?.then(res => { - if (res?.errors) throw new Error(res?.errors[0]?.message || "Error fetching settings"); - return res.data?.Settings; - }); + if (res?.errors) throw new Error(res?.errors[0]?.message || 'Error fetching settings') + return res.data?.Settings + }) } catch (err: unknown) { - throw new Error("Error fetching settings") + throw new Error('Error fetching settings') } } export async function fetchHeader(): Promise
{ - if (!GRAPHQL_API_URL) throw new Error("NEXT_PUBLIC_SERVER_URL not found"); + if (!GRAPHQL_API_URL) throw new Error('NEXT_PUBLIC_SERVER_URL not found') try { return await fetch(`${GRAPHQL_API_URL}/api/graphql`, { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - cache: "no-store", + cache: 'no-store', body: JSON.stringify({ query: HEADER_QUERY, }), }) ?.then(res => { - if (!res.ok) throw new Error("Error fetching doc"); - return res.json(); + if (!res.ok) throw new Error('Error fetching doc') + return res.json() }) ?.then(res => { - if (res?.errors) throw new Error(res?.errors[0]?.message || "Error fetching header"); - return res.data?.Header; - }); + if (res?.errors) throw new Error(res?.errors[0]?.message || 'Error fetching header') + return res.data?.Header + }) } catch (err: unknown) { - throw new Error("Error fetching header"); + throw new Error('Error fetching header') } } export async function fetchFooter(): Promise
{ - if (!GRAPHQL_API_URL) throw new Error("NEXT_PUBLIC_SERVER_URL not found"); + if (!GRAPHQL_API_URL) throw new Error('NEXT_PUBLIC_SERVER_URL not found') try { return await fetch(`${GRAPHQL_API_URL}/api/graphql`, { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, body: JSON.stringify({ query: FOOTER_QUERY, }), }) .then(res => { - if (!res.ok) throw new Error("Error fetching doc"); - return res.json(); + if (!res.ok) throw new Error('Error fetching doc') + return res.json() }) ?.then(res => { - if (res?.errors) throw new Error(res?.errors[0]?.message || "Error fetching footer"); - return res.data?.Footer; - }); + if (res?.errors) throw new Error(res?.errors[0]?.message || 'Error fetching footer') + return res.data?.Footer + }) } catch (err: unknown) { - throw new Error("Error fetching footer"); + throw new Error('Error fetching footer') } } export async function fetchSocials(): Promise { - if (!GRAPHQL_API_URL) throw new Error("NEXT_PUBLIC_SERVER_URL not found"); + if (!GRAPHQL_API_URL) throw new Error('NEXT_PUBLIC_SERVER_URL not found') try { return await fetch(`${GRAPHQL_API_URL}/api/graphql`, { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - cache: "no-store", + cache: 'no-store', body: JSON.stringify({ query: SOCIALS_QUERY, }), }) ?.then(res => res.json()) ?.then(res => { - if (res?.errors) throw new Error(res?.errors[0]?.message || "Error fetching socials"); - return res.data?.Social; - }); + if (res?.errors) throw new Error(res?.errors[0]?.message || 'Error fetching socials') + return res.data?.Social + }) } catch (err: unknown) { - throw new Error("Error fetching socials"); + throw new Error('Error fetching socials') } } @@ -115,19 +115,19 @@ export const fetchGlobals = async (): Promise<{ // initiate requests in parallel, then wait for them to resolve // this will eagerly start to the fetch requests at the same time // see https://nextjs.org/docs/app/building-your-application/data-fetching/fetching - const settingsData = fetchSettings(); - const headerData = fetchHeader(); - const footerData = fetchFooter(); - const socialsData = fetchSocials(); + const settingsData = fetchSettings() + const headerData = fetchHeader() + const footerData = fetchFooter() + const socialsData = fetchSocials() const [settings, header, footer, socials]: [Settings, Header, Footer, Social] = await Promise.all( [await settingsData, await headerData, await footerData, await socialsData], - ); + ) return { settings, header, footer, socials, - }; -}; + } +} diff --git a/src/app/_blocks/HubContentGrid/page.tsx b/src/app/_blocks/HubContentGrid/page.tsx new file mode 100644 index 0000000..96a31d2 --- /dev/null +++ b/src/app/_blocks/HubContentGrid/page.tsx @@ -0,0 +1,56 @@ +'use client' + +import { useState } from 'react' + +import ContentCard from '../../_components/ContentCard' +import ContentNavBar from '../../_components/ContentNavBar/ContentNavBar' +import { ContentTypeArrays } from '../../_interfaces/ContentTypeArrays' +import { filterArticles } from '../../_utilities/filterArticles' +import styles from './styles.module.css' + +const colorMap = { + All: 'var(--dark-rock-800)', + Blogposts: 'var(--sub-blue-600)', + PodcastEpisodes: 'var(--sub-purple-600)', + CaseStudies: 'var(--sub-orange-800)', + TalksAndRoundtables: 'var(--sub-purple-300)', +} + +interface HubContentGridProps { + articles: ContentTypeArrays +} + +export default function HubContentGrid({ articles }: HubContentGridProps) { + // todo: fix rendering when there is no content + + const [activeButton, setActiveButton] = useState('All') + + const handleActiveButtonChange = (buttonName: keyof ContentTypeArrays) => { + setActiveButton(buttonName) + } + + const filteredArticles = filterArticles({ + articles: articles, + filter: activeButton, + }) + + return ( + <> + {/* ToDo: add conditional management of border-top-right-radius based on dropdown toggle */} + +
+
+ {filteredArticles.map((article, i) => ( +
+ +
+ ))} +
+
+ + ) +} diff --git a/src/app/_blocks/HubContentGrid/styles.module.css b/src/app/_blocks/HubContentGrid/styles.module.css new file mode 100644 index 0000000..f9917e1 --- /dev/null +++ b/src/app/_blocks/HubContentGrid/styles.module.css @@ -0,0 +1,51 @@ +/* Content grid */ +.contentGridContainer { + position: relative; + background-color: #FFFFFF; + width: max(calc(100% - 32px), calc(100% - 160px)); + margin: 40px auto; + border-bottom-right-radius: 45px; + border-bottom-left-radius: 45px; + border-top-right-radius: 45px; + border: 1px solid var(--dark-rock-800) !important; + border-top: none; + overflow: hidden; + z-index: 3; +} + +.contentGridContainer::after { + content: ''; + position: absolute; + border-top: 2px solid var(--dark-rock-800); + width: 22.6%; + top: 0; + right: 0; +} + +.contentGrid { + padding: 0; + margin: 0 auto; + display: grid; + grid-template-columns: 1fr; + +} + +.contentCard { + padding: 50px 20px; + height: min-content; + border: 1px solid var(--dark-rock-800); + border-top: none; +} + +@media(min-width: 1024px) { + .contentGrid { + grid-template-columns: repeat(3, 1fr); + } + + .contentGridContainer { + border-top: 1px solid; + border-radius: 25px; + margin: 0 auto; + } + +} diff --git a/src/app/_blocks/Subscribe/index.tsx b/src/app/_blocks/Subscribe/index.tsx index 2c88193..24955ec 100644 --- a/src/app/_blocks/Subscribe/index.tsx +++ b/src/app/_blocks/Subscribe/index.tsx @@ -1,4 +1,4 @@ -import styles from "./styles.module.css"; +import styles from './styles.module.css' export function Subscribe() { return ( @@ -7,13 +7,15 @@ export function Subscribe() {
-
Subscribe to
Subvisual Inspo
+
+ Subscribe to
Subvisual Inspo +
- + {/* TODO: Implement the hook to subscribe */}
- ); + ) } diff --git a/src/app/_components/AuthorSummary/index.tsx b/src/app/_components/AuthorSummary/index.tsx index e8f7e4b..7b11c61 100644 --- a/src/app/_components/AuthorSummary/index.tsx +++ b/src/app/_components/AuthorSummary/index.tsx @@ -1,34 +1,27 @@ -import React from "react"; +import React from 'react' -import { Author } from "../../../payload/payload-types"; -import FeaturedImage from "../FeaturedImage"; -import SocialLinks from "../SocialLinks"; - -import styles from "./styles.module.css"; +import { Author } from '../../../payload/payload-types' +import FeaturedImage from '../FeaturedImage' +import SocialLinks from '../SocialLinks' +import styles from './styles.module.css' export default function AuthorSummary({ author }: { author: Author }) { - const { name, role, bio, linkedIn, gitHub, medium, x, featuredImage } = author; + const { name, role, bio, linkedIn, gitHub, medium, x, featuredImage } = author // TODO: Convert this to an array with names in collection config - const socials: string[] = [linkedIn, gitHub, medium, x].filter(Boolean); + const socials: string[] = [linkedIn, gitHub, medium, x].filter(Boolean) return (
-
{name}

{role}

{bio}
- - -
- - - ); + ) } diff --git a/src/app/_components/BackButton/index.tsx b/src/app/_components/BackButton/index.tsx index 25ee069..3116ccf 100644 --- a/src/app/_components/BackButton/index.tsx +++ b/src/app/_components/BackButton/index.tsx @@ -1,10 +1,10 @@ // TODO: update href to filter for the same category import Link from 'next/link' -import BackArrow from "../../_icons/BackArrow"; +import BackArrow from '../../_icons/BackArrow' import styles from './styles.module.css' -export default function BackButton({className}) { +export default function BackButton({ className }) { return ( <> diff --git a/src/app/_components/ContentNavBar/Buttons/index.tsx b/src/app/_components/ContentNavBar/Buttons/index.tsx new file mode 100644 index 0000000..de19d77 --- /dev/null +++ b/src/app/_components/ContentNavBar/Buttons/index.tsx @@ -0,0 +1,132 @@ +import styles from './styles.module.css' + +import { AllIcon, BlogpostIcon, CaseStudiesIcon, PodcastIcon, TalksIcon } from '@/app/_icons/icons' + +export function AllContent({ fill = 'var(--dark-rock-800)', textColor = 'var(--soft-white-100)' }) { + return ( + + {/* Change fill based on state? */} + + + + + All + + + ) +} + +export function Blogposts({ fill = 'var(--sub-blue-600)', textColor = 'var(--soft-white-100)' }) { + return ( + + + + + + Blogposts + + + ) +} + +export function Podcasts({ fill = 'var(--sub-purple-600)', textColor = 'var(--soft-white-100)' }) { + return ( + + + + + + Podcasts + + + ) +} + +export function CaseStudies({ + fill = 'var(--sub-orange-800)', + textColor = 'var(--soft-white-100)', +}) { + return ( + + + + + + Case Studies + + + ) +} + +export function Talks({ fill = 'var(--sub-purple-300)', textColor = 'var(--soft-white-100)' }) { + return ( + + + + + + Talks & Roundtables + + + ) +} diff --git a/src/app/_components/ContentNavBar/Buttons/styles.module.css b/src/app/_components/ContentNavBar/Buttons/styles.module.css new file mode 100644 index 0000000..a1f624c --- /dev/null +++ b/src/app/_components/ContentNavBar/Buttons/styles.module.css @@ -0,0 +1,6 @@ +.buttonText { + text-anchor: start; + dominant-baseline: middle; + font-size: 16px; + font-weight: bold; +} diff --git a/src/app/_components/ContentNavBar/ContentNavBar.tsx b/src/app/_components/ContentNavBar/ContentNavBar.tsx new file mode 100644 index 0000000..7d51a63 --- /dev/null +++ b/src/app/_components/ContentNavBar/ContentNavBar.tsx @@ -0,0 +1,99 @@ +'use client' + +import { AllContent, Blogposts, CaseStudies, Podcasts, Talks } from './Buttons' +import DropdownMenu from './DropdownMenu' +import styles from './styles.module.css' + +import { ContentTypeArrays } from '@/app/_interfaces/ContentTypeArrays' + +export default function ContentNavBar({ + activeButton, + onActiveButtonChange, +}: { + activeButton: string + onActiveButtonChange: (buttonName: keyof ContentTypeArrays | 'All') => void +}) { + const handleButtonClick = (buttonName: keyof ContentTypeArrays | 'All') => { + onActiveButtonChange(buttonName) + } + + return ( + // todo: make whole button clickable? + +
+ + + + + + + +
+ ) +} diff --git a/src/app/_components/ContentNavBar/DropdownMenu/index.tsx b/src/app/_components/ContentNavBar/DropdownMenu/index.tsx new file mode 100644 index 0000000..aaddc24 --- /dev/null +++ b/src/app/_components/ContentNavBar/DropdownMenu/index.tsx @@ -0,0 +1,91 @@ +import { useState } from 'react' + +import { + AllIcon, + BlogpostIcon, + CaseStudiesIcon, + ChevronDownIcon, + ChevronUpIcon, + PodcastIcon, + TalksIcon, +} from '../../../_icons/icons' +import { ContentTypeArrays } from '../../../_interfaces/ContentTypeArrays' +import styles from './styles.module.css' + +const iconMap = { + All: , + Blogposts: , + PodcastEpisodes: , + CaseStudies: , + TalksAndRoundtables: , +} + +const labelMap = { + All: 'All', + Blogposts: 'Blogposts', + PodcastEpisodes: 'Podcasts', + CaseStudies: 'Case Studies', + TalksAndRoundtables: 'Talks & Roundtables', +} + +export default function DropdownMenu({ + activeButton, + onActiveButtonChange, +}: { + activeButton: string + onActiveButtonChange: () => void +}) { + const [isDropdownActive, setIsDropdownActive] = useState(false) + const [buttonLabel, setButtonLabel] = useState('All') + + const handleButtonClick = (buttonName: keyof ContentTypeArrays | 'All') => { + onActiveButtonChange(buttonName) + setIsDropdownActive(false) + setButtonLabel(labelMap[buttonName]) + } + + return ( +
+ + + + + {isDropdownActive && ( +
+
+
    +
  • handleButtonClick('All')}>{iconMap['All']} All
  • +
  • handleButtonClick('Blogposts')}> + {iconMap['Blogposts']} Blogposts +
  • +
  • handleButtonClick('PodcastEpisodes')}> + {iconMap['PodcastEpisodes']} Podcasts +
  • +
  • handleButtonClick('CaseStudies')}> + {iconMap['CaseStudies']} Case Studies +
  • +
  • handleButtonClick('TalksAndRoundtables')}> + {iconMap['TalksAndRoundtables']} Talks & Roundtables +
  • +
+
+
+ )} +
+ ) +} diff --git a/src/app/_components/ContentNavBar/DropdownMenu/styles.module.css b/src/app/_components/ContentNavBar/DropdownMenu/styles.module.css new file mode 100644 index 0000000..4cd5528 --- /dev/null +++ b/src/app/_components/ContentNavBar/DropdownMenu/styles.module.css @@ -0,0 +1,82 @@ +.dropdownContainer { + width: 100%; + padding: 0; + z-index: 5; +} + +.dropdownButton { + display: flex; + position: relative; + align-items: center; + width: 208px; + margin-left: 20px; + margin-top: -40px; + padding: 8px 10px; + border-radius: 100px; + border: none; + background-color: var(--soft-white-100); + color: var(--dark-rock-800); + margin-bottom: 6px; + font-weight: bold; + gap: 8px; + z-index: 5; +} + +.dropdownMenuContainer { + position: relative; + border-left: 2px solid var(--dark-rock-800); + background-color: white; + margin-top: -8px; + margin-bottom: -40px; + padding-top: 6px; +} + +.dropdownMenuContainer::after { + content: ''; + position: absolute; + border-top: 2px solid var(--dark-rock-800); + border-right: 2px solid var(--dark-rock-800); + border-top-right-radius: 45px; + width: 24.5%; + height: 100%; + top: 0; + right: 0; +} + + +.dropdownMenu { + width: 208px; + margin-left: 20px; + background-color: var(--soft-white-100); + font-weight: bold; + border-radius: 15px; +} + +.dropdownMenu ul { + list-style: none; + padding: 0; + margin: 0; + gap: 20px; + +} + +.dropdownMenu li { + display: flex; + align-items: center; + padding: 8px 16px; + gap: 8px; + margin-bottom: 10px; + transition: 0.3s ease; +} + +.dropdownMenu li:hover { + background-color: #E4E4E4; + transition: 0.3s ease; + +} + +@media (min-width: 1024px) { + .dropdownContainer { + display: none; + } +} diff --git a/src/app/_components/ContentNavBar/styles.module.css b/src/app/_components/ContentNavBar/styles.module.css new file mode 100644 index 0000000..0f2a380 --- /dev/null +++ b/src/app/_components/ContentNavBar/styles.module.css @@ -0,0 +1,52 @@ +/* Content nav */ +.contentNav { + display: flex; + position: relative; + padding: 0; + width: var(--content-width); + margin: auto auto -48px; + flex-wrap: wrap; + z-index: 5; + font-size: 12px; +} + +.button { + background-color: transparent; + border: none; + padding: 0; + width: fit-content; + position: relative; + display: none; +} + +.activeButton { + z-index: 1; +} + + +.blogpostsButton { + margin-left: -120px; +} + +.podcastsButton { + margin-left: -120px; +} + +.caseStudiesButton { + margin-left: -120px; +} + +.talksButton { + margin-left: -80px; +} + +@media (min-width: 1024px) { + .contentNav { + margin: auto auto -18px; + z-index: 0; + } + + .button { + display: flex; + } +} diff --git a/src/app/_components/FeaturedImage/index.tsx b/src/app/_components/FeaturedImage/index.tsx index 2763e72..f3770bf 100644 --- a/src/app/_components/FeaturedImage/index.tsx +++ b/src/app/_components/FeaturedImage/index.tsx @@ -1,10 +1,7 @@ +import { className } from 'postcss-selector-parser' + import { getImage } from '../../_utilities/getImage' -import { className } from "postcss-selector-parser"; export default function FeaturedImage({ src, className }) { - return ( - - - - ) + return } diff --git a/src/app/_components/Header/DropDownIcon/index.tsx b/src/app/_components/Header/DropDownIcon/index.tsx index c3f3cdb..023407a 100644 --- a/src/app/_components/Header/DropDownIcon/index.tsx +++ b/src/app/_components/Header/DropDownIcon/index.tsx @@ -1,10 +1,14 @@ - - -export default function DropDownIcon({className}) { +export default function DropDownIcon({ className }) { return ( - - - + + + ) } diff --git a/src/app/_components/Header/Logo/index.tsx b/src/app/_components/Header/Logo/index.tsx index 27f382e..dc81560 100644 --- a/src/app/_components/Header/Logo/index.tsx +++ b/src/app/_components/Header/Logo/index.tsx @@ -1,9 +1,16 @@ export default function Logo() { return ( - + + fill="#403F4C" + /> - ); + ) } diff --git a/src/app/_components/Header/index.tsx b/src/app/_components/Header/index.tsx index 7fe9c22..ca40c41 100644 --- a/src/app/_components/Header/index.tsx +++ b/src/app/_components/Header/index.tsx @@ -1,17 +1,17 @@ -import Link from "next/link"; +import Link from 'next/link' -import { Header } from "../../../payload/payload-types"; -import { fetchHeader } from "../../_api/fetchGlobals"; -import styles from "./styles.module.css"; +import { Header } from '../../../payload/payload-types' +import { fetchHeader } from '../../_api/fetchGlobals' +import DropDownIcon from './DropDownIcon' +import styles from './styles.module.css' -import DropDownIcon from "./DropDownIcon"; -import Logo from "@/app/_components/Header/Logo"; +import Logo from '@/app/_components/Header/Logo' export async function Header() { - let header: Header | null = null; + let header: Header | null = null try { - header = await fetchHeader(); + header = await fetchHeader() } catch (error) { // When deploying this template on Payload Cloud, this page needs to build before the APIs are live // So swallow the error here and simply render the header without nav items if one occurs @@ -19,13 +19,13 @@ export async function Header() { // console.error(error) } - const navItems = header?.navItems || []; + const navItems = header?.navItems || [] return ( <>
- +
{/* TODO: Conditionally format Content Hub to reflect active link?*/} @@ -35,14 +35,14 @@ export async function Header() { {link.label} - ); + ) })} - + CONTACT US
- ); + ) } diff --git a/src/app/_components/SearchBar/index.tsx b/src/app/_components/SearchBar/index.tsx new file mode 100644 index 0000000..a252bb0 --- /dev/null +++ b/src/app/_components/SearchBar/index.tsx @@ -0,0 +1,11 @@ +import styles from './styles.module.css' + +import { MagnifyingGlass } from '@/app/_icons/icons' +export default function SearchBar() { + return ( +
+ + +
+ ) +} diff --git a/src/app/_components/SearchBar/styles.module.css b/src/app/_components/SearchBar/styles.module.css new file mode 100644 index 0000000..ee9905c --- /dev/null +++ b/src/app/_components/SearchBar/styles.module.css @@ -0,0 +1,32 @@ +/* Search bar */ + +.searchBar { + display: flex; + margin: 40px auto; + border-radius: 100px; + border: 1px solid var(--dark-rock-800); + width: 328px; + overflow: hidden; + padding: 10px 10px 10px 20px; + gap: 10px; + align-items: center; +} + +.searchInput { + display: flex; + border: none; + width: 100%; +} + +.searchInput:focus, +.searchInput:active { + border: none; + outline: none; +} + +@media (min-width: 1024px) { + .searchBar { + width: 836px; + margin: 80px auto; + } +} diff --git a/src/app/_components/SocialLinks/index.tsx b/src/app/_components/SocialLinks/index.tsx index bc0f8c4..6cbc7a9 100644 --- a/src/app/_components/SocialLinks/index.tsx +++ b/src/app/_components/SocialLinks/index.tsx @@ -1,5 +1,6 @@ import Link from 'next/link' -import LinkedInIcon from "@/app/_icons/LinkedInIcon"; + +import LinkedInIcon from '@/app/_icons/LinkedInIcon' export default function SocialLinks({ socials }: { socials: string[] }) { return ( diff --git a/src/app/_css/globals.css b/src/app/_css/globals.css index d539a89..12c0981 100644 --- a/src/app/_css/globals.css +++ b/src/app/_css/globals.css @@ -24,6 +24,8 @@ --sub-purple-100: #D4BEFE; --sub-purple-50: #EFE5FF; + --sub-orange-800: #F36312; + --dark-rock-800: #403F4C; --soft-white-100: #F6F6F6; @@ -33,6 +35,10 @@ --font-weight-500: 500; --font-weight-800: 800; + /* Content Sizing */ + --content-width: max(calc(100% - 32px), calc(100% - 160px)); + + /* Font Sizes */ --size-160: 160px; --size-113: 113px; @@ -50,6 +56,7 @@ --size-14: 14px; --size-12: 12px; --size-13: 13px; + --size-10: 10px; } /* end of :root */ *, *::before, *::after { @@ -61,11 +68,13 @@ } body { + max-width: 1728px; + margin: 0 auto; font-size: var(--size-14); text-rendering: optimizeLegibility; line-height: 1.5; -webkit-font-smoothing: antialiased; - font-family: var(--colfax-regular); + font-family: var(--colfax); } img, picture, video, canvas, svg { @@ -145,4 +154,4 @@ h6 { font-size: var(--size-28); } -} \ No newline at end of file +} diff --git a/src/app/_graphql/allContent.ts b/src/app/_graphql/allContent.ts index 32eb165..cbd22ca 100644 --- a/src/app/_graphql/allContent.ts +++ b/src/app/_graphql/allContent.ts @@ -1,9 +1,8 @@ -import { MEDIA_FIELDS } from "@/app/_graphql/media"; - export const ALL_CONTENT = ` query Content { - PodcastEpisodes { + PodcastEpisodes(limit: 50) { docs { + id slug title summary @@ -22,8 +21,9 @@ query Content { } } } - Blogposts { + Blogposts(limit: 50) { docs { + id slug title summary @@ -42,13 +42,15 @@ query Content { } } } - CaseStudies { + CaseStudies(limit: 50) { docs { + id slug } } - TalksAndRoundtables { + TalksAndRoundtables(limit: 50) { docs { + id slug } } diff --git a/src/app/_icons/BackArrow.tsx b/src/app/_icons/BackArrow.tsx index 9420f47..5c26fd0 100644 --- a/src/app/_icons/BackArrow.tsx +++ b/src/app/_icons/BackArrow.tsx @@ -1,12 +1,22 @@ -import { className } from "postcss-selector-parser"; +import { className } from 'postcss-selector-parser' -export default function BackArrow({className}) { +export default function BackArrow({ className }) { return ( - + - + - ) } diff --git a/src/app/_icons/LinkedInIcon.tsx b/src/app/_icons/LinkedInIcon.tsx index 5c24d52..13c90b5 100644 --- a/src/app/_icons/LinkedInIcon.tsx +++ b/src/app/_icons/LinkedInIcon.tsx @@ -2,7 +2,10 @@ export default function LinkedInIcon() { return ( - + ) } diff --git a/src/app/_icons/icons.tsx b/src/app/_icons/icons.tsx new file mode 100644 index 0000000..68d3d1f --- /dev/null +++ b/src/app/_icons/icons.tsx @@ -0,0 +1,452 @@ +export function MagnifyingGlass({ x, y, width = '24', height = '24', stroke = 'black' }) { + return ( + + + + ) +} + +export function AllIcon({ x, y, width = '24', height = '24', stroke = 'black' }) { + return ( + + + + + + + ) +} + +export function BlogpostIcon({ x, y, width = '24', height = '24', stroke = 'black' }) { + return ( + + + + ) +} + +export function PodcastIcon({ x, y, width = '24', height = '24', stroke = 'black' }) { + return ( + + + + ) +} + +export function CaseStudiesIcon({ x, y, width = '24', height = '24', stroke = 'black' }) { + return ( + + + + ) +} + +export function TalksIcon({ x, y, width = '24', height = '24', fill = 'black' }) { + return ( + + + + ) +} + +export function SpectaclesIcon({ x, y }) { + return ( + + + + ) +} + +export function ShareIcon({ x, y }) { + return ( + + + + ) +} + +export function XIcon({ x, y }) { + return ( + + + + ) +} + +export function ChevronUpIcon({ x, y }) { + return ( + + + + ) +} + +export function ChevronDownIcon({ x, y }) { + return ( + + + + ) +} + +export function RSSIcon({ x, y }) { + return ( + + + + ) +} + +export function MuteIcon({ x, y }) { + return ( + + + + ) +} + +export function LowerVolume({ x, y }) { + return ( + + + + ) +} + +export function RaiseVolumeIcon({ x, y }) { + return ( + + + + ) +} + +export function PlayIcon({ x, y }) { + return ( + + + + ) +} + +export function MoveFifteenIcon({ x, y }) { + return ( + + + + + ) +} + +export function BackFifteenIcon({ x, y }) { + return ( + + + + + ) +} + +export function HeadphonesIcon({ x, y }) { + return ( + + + + ) +} + +export function LinkIcon({ x, y }) { + return ( + + + + ) +} diff --git a/src/app/_interfaces/ContentTypeArrays.ts b/src/app/_interfaces/ContentTypeArrays.ts new file mode 100644 index 0000000..28cae6c --- /dev/null +++ b/src/app/_interfaces/ContentTypeArrays.ts @@ -0,0 +1,13 @@ +import type { + Blogpost, + CaseStudy, + PodcastEpisode, + TalksAndRoundtable, +} from '@/payload/payload-types' + +export interface ContentTypeArrays { + Blogposts: Blogpost[] + PodcastEpisodes: PodcastEpisode[] + CaseStudies: CaseStudy[] + TalksAndRoundtables: TalksAndRoundtable[] +} diff --git a/src/app/_utilities/fetcher.ts b/src/app/_utilities/fetcher.ts index 511bbd1..5a753e9 100644 --- a/src/app/_utilities/fetcher.ts +++ b/src/app/_utilities/fetcher.ts @@ -1,7 +1,11 @@ -import { GRAPHQL_API_URL } from "@/app/_api/shared"; +import { GRAPHQL_API_URL } from '../_api/shared' -export async function fetcher(args: { query: string; variables?: Record }) { +import type { ContentTypeArrays } from '@/app/_interfaces/ContentTypeArrays' +export async function fetcher(args: { + query: string + variables?: Record +}): Promise { const { query, variables } = args try { @@ -24,6 +28,6 @@ export async function fetcher(args: { query: string; variables?: Record { + if (filter === 'All') { + const keys = Object.keys(articles) as Array + + return keys.flatMap(articleType => + articles[articleType].map(article => ({ + contentType: articleType, + content: article, + })),) + } + + return articles[filter]?.map(article => ({ + contentType: filter, + content: article, + })) +} diff --git a/src/payload/globals/Socials.ts b/src/payload/globals/Socials.ts index f97bee3..b998c22 100644 --- a/src/payload/globals/Socials.ts +++ b/src/payload/globals/Socials.ts @@ -1,16 +1,16 @@ -import type { GlobalConfig } from "payload/types"; +import type { GlobalConfig } from 'payload/types' -import link from "../fields/link"; +import link from '../fields/link' export const Socials: GlobalConfig = { - slug: "socials", + slug: 'socials', access: { read: () => true, }, fields: [ { - name: "socials", - type: "array", + name: 'socials', + type: 'array', fields: [ { name: 'name', @@ -25,5 +25,4 @@ export const Socials: GlobalConfig = { ], }, ], - -}; +} diff --git a/src/payload/payload.config.ts b/src/payload/payload.config.ts index f74a8b4..d4bf114 100644 --- a/src/payload/payload.config.ts +++ b/src/payload/payload.config.ts @@ -1,7 +1,6 @@ import { webpackBundler } from '@payloadcms/bundler-webpack' import { mongooseAdapter } from '@payloadcms/db-mongodb' import { lexicalEditor } from '@payloadcms/richtext-lexical' - import dotenv from 'dotenv' import path from 'path' import { buildConfig } from 'payload/config' diff --git a/tsconfig.json b/tsconfig.json index 69aaaa8..1784325 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ ], "allowJs": true, "skipLibCheck": true, - "strict": false, + "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "incremental": true,