diff --git a/_data/pages/home.yml b/_data/pages/home.yml index a3b5385ad5..1114e870b2 100644 --- a/_data/pages/home.yml +++ b/_data/pages/home.yml @@ -5,6 +5,12 @@ template: landing breadcrumbs: false page_last_updated: false blocks: + - type: nav_sticky_banner + text: Starknet is the secure scaling technology bringing Ethereum’s benefits to + the world. + buttonText: See more + buttonLink: https://starkware.co + isActive: false - type: group blocks: - type: home_hero diff --git a/package.json b/package.json index 1660e7b6c2..6da9f7b162 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "highlight.js": "^11.8.0", "highlightjs-cairo": "^0.2.0", "itty-router": "^4.0.11", + "js-sha256": "^0.11.0", "marked": "^5.1.0", "moment": "^2.29.4", "moment-timezone": "^0.5.43", diff --git a/workspaces/cms-config/src/blocks.ts b/workspaces/cms-config/src/blocks.ts index 993bd65ec8..9d06d6d221 100644 --- a/workspaces/cms-config/src/blocks.ts +++ b/workspaces/cms-config/src/blocks.ts @@ -56,7 +56,7 @@ export const ambassadorTags = [ label: "Tag", name: "tag", widget: "select", - options: ["Content generator", "Event organizer", "Event speaker"] + options: ["Content generator", "Event organizer", "Event speaker"], }, ] satisfies CmsField[]; @@ -89,19 +89,19 @@ export const ambassador = [ label: "Website url", name: "website", widget: "string", - crowdin: false + crowdin: false, }, { label: "Twitter handle", name: "twitter", widget: "string", - crowdin: false + crowdin: false, }, { label: "Discord", name: "discord", widget: "string", - crowdin: false + crowdin: false, }, { label: "Tags", @@ -111,7 +111,7 @@ export const ambassador = [ crowdin: true, required: false, index_file: "", - meta: true + meta: true, }, ] satisfies CmsField[]; @@ -141,7 +141,7 @@ export const cardListItem = [ required: false, widget: "string", index_file: "", - meta: false + meta: false, }, { label: "Twitter", @@ -149,7 +149,7 @@ export const cardListItem = [ required: false, widget: "string", index_file: "", - meta: false + meta: false, }, { label: "Start date time", @@ -157,7 +157,7 @@ export const cardListItem = [ required: false, widget: "string", index_file: "", - meta: false + meta: false, }, { label: "Location", @@ -165,7 +165,7 @@ export const cardListItem = [ required: false, widget: "string", index_file: "", - meta: false + meta: false, }, { label: "City", @@ -173,7 +173,7 @@ export const cardListItem = [ required: false, widget: "string", index_file: "", - meta: false + meta: false, }, { label: "Country", @@ -181,7 +181,7 @@ export const cardListItem = [ required: false, widget: "string", index_file: "", - meta: false + meta: false, }, { label: "Discord", @@ -189,7 +189,7 @@ export const cardListItem = [ required: false, widget: "string", index_file: "", - meta: false + meta: false, }, { label: "Type list", @@ -208,7 +208,7 @@ export const cardListItem = [ name: "url", widget: "string", crowdin: false, - } + }, ], }, { @@ -217,7 +217,7 @@ export const cardListItem = [ required: false, widget: "string", index_file: "", - meta: false + meta: false, }, { label: "Recap", @@ -242,35 +242,35 @@ export const cardListItem = [ name: "isExternal", widget: "boolean", crowdin: false, - } + }, ], index_file: "", - meta: false + meta: false, }, ] satisfies CmsField[]; const videoChapterFields = [ { crowdin: true, - label: 'Title', - name: 'title', + label: "Title", + name: "title", required: true, - widget: 'string' + widget: "string", }, { crowdin: true, - label: 'Description', - name: 'description', + label: "Description", + name: "description", required: true, - widget: 'string' + widget: "string", }, { crowdin: true, - label: 'Content', - name: 'content', + label: "Content", + name: "content", required: true, - widget: 'markdown' - } + widget: "markdown", + }, ] satisfies CmsField[]; export const blocks = [ @@ -298,7 +298,7 @@ export const blocks = [ { name: "description", widget: "string", - crowdin: true + crowdin: true, }, ], }, @@ -332,7 +332,7 @@ export const blocks = [ { name: "title", widget: "string", - crowdin: true + crowdin: true, }, { name: "link", @@ -364,7 +364,7 @@ export const blocks = [ { label: "Icon link card", value: "icon_link_card" }, { label: "Dapp", value: "dapp" }, { label: "Large card", value: "large_card" }, - { label: "Community card", value: "community_card" } + { label: "Community card", value: "community_card" }, ], }, { @@ -375,7 +375,7 @@ export const blocks = [ required: false, options: [ { label: "Large", value: "large" }, - { label: "Small", value: "small" } + { label: "Small", value: "small" }, ], }, { @@ -388,12 +388,12 @@ export const blocks = [ { name: "title", widget: "string", - crowdin: true + crowdin: true, }, { name: "description", widget: "string", - crowdin: true + crowdin: true, }, { name: "link", @@ -402,7 +402,7 @@ export const blocks = [ }, { name: "icon", - widget: "image" + widget: "image", }, { name: "color", @@ -416,13 +416,14 @@ export const blocks = [ "cyan", "orange", "pink", - "grey" + "grey", ], default: "orange", }, { name: "columns", - label: "Columns (number of cards per row, works only for icon_link_card)", + label: + "Columns (number of cards per row, works only for icon_link_card)", widget: "select", default: "4", required: false, @@ -435,7 +436,7 @@ export const blocks = [ default: "left", required: false, options: ["left", "right", "vertical"], - } + }, ], }, { @@ -446,9 +447,9 @@ export const blocks = [ { name: "videoId", widget: "string", - crowdin: true - } - ] + crowdin: true, + }, + ], }, { name: "ambassadors_list", @@ -460,7 +461,7 @@ export const blocks = [ name: "title", required: false, widget: "string", - crowdin: true + crowdin: true, }, { label: "Ambassador", @@ -470,9 +471,9 @@ export const blocks = [ crowdin: true, required: true, index_file: "", - meta: true + meta: true, }, - ] + ], }, { name: "card_list", @@ -484,14 +485,14 @@ export const blocks = [ name: "title", required: false, widget: "string", - crowdin: true + crowdin: true, }, { label: "Description", name: "description", required: false, widget: "string", - crowdin: true + crowdin: true, }, { name: "randomize", @@ -508,9 +509,9 @@ export const blocks = [ crowdin: true, required: true, index_file: "", - meta: true + meta: true, }, - ] + ], }, { name: "hero", @@ -520,12 +521,12 @@ export const blocks = [ { name: "title", widget: "string", - crowdin: true + crowdin: true, }, { name: "description", widget: "string", - crowdin: true + crowdin: true, }, { name: "variant", @@ -549,21 +550,21 @@ export const blocks = [ label: "Button text", required: false, widget: "string", - crowdin: true + crowdin: true, }, { name: "buttonUrl", label: "Button url", required: false, widget: "string", - crowdin: false + crowdin: false, }, { name: "leftBoxMaxWidth", label: "Left box max width", required: false, widget: "number", - crowdin: false + crowdin: false, }, ], }, @@ -577,7 +578,7 @@ export const blocks = [ label: "Heading", required: false, widget: "string", - crowdin: true + crowdin: true, }, { name: "listSize", @@ -629,13 +630,13 @@ export const blocks = [ name: "label", label: "Label", widget: "string", - crowdin: true + crowdin: true, }, { name: "boldLabel", label: "Bold Label", widget: "string", - crowdin: true + crowdin: true, }, ], }, @@ -648,13 +649,13 @@ export const blocks = [ name: "url", label: "URL", widget: "string", - crowdin: false + crowdin: false, }, { name: "title", label: "Title", widget: "string", - crowdin: true + crowdin: true, }, { name: "displayTitle", @@ -678,7 +679,7 @@ export const blocks = [ label: "Heading", required: false, widget: "string", - crowdin: true + crowdin: true, }, { name: "blocks", @@ -689,7 +690,7 @@ export const blocks = [ name: "label", label: "Label", widget: "string", - crowdin: true + crowdin: true, }, { name: "body", @@ -699,7 +700,8 @@ export const blocks = [ ], }, ], - }, { + }, + { name: "video_section", label: "Education video section", widget: "object", @@ -720,52 +722,54 @@ export const blocks = [ }, { crowdin: true, - label: 'Chapter 1', - name: 'scaling-eth', - widget: 'object', - fields: videoChapterFields + label: "Chapter 1", + name: "scaling-eth", + widget: "object", + fields: videoChapterFields, }, { crowdin: true, - label: 'Chapter 2', - name: 'sequencer', - widget: 'object', - fields: videoChapterFields + label: "Chapter 2", + name: "sequencer", + widget: "object", + fields: videoChapterFields, }, { crowdin: true, - label: 'Chapter 3', - name: 'prover', - widget: 'object', - fields: videoChapterFields + label: "Chapter 3", + name: "prover", + widget: "object", + fields: videoChapterFields, }, { crowdin: true, - label: 'Chapter 4', - name: 'eth-settlement', - widget: 'object', - fields: videoChapterFields - } - ] - }, { + label: "Chapter 4", + name: "eth-settlement", + widget: "object", + fields: videoChapterFields, + }, + ], + }, + { name: "newsletter_popup", label: "Newsletter Popup", widget: "object", fields: [ { crowdin: true, - label: 'Title', - name: 'title', - widget: 'string' + label: "Title", + name: "title", + widget: "string", }, { crowdin: true, - label: 'Description', - name: 'description', - widget: 'string' + label: "Description", + name: "description", + widget: "string", }, - ] - }, { + ], + }, + { name: "ordered_block", label: "Ordered Block", widget: "object", @@ -778,7 +782,7 @@ export const blocks = [ { name: "title", widget: "string", - crowdin: true + crowdin: true, }, { name: "body", @@ -789,6 +793,37 @@ export const blocks = [ }, ], }, + { + name: "nav_sticky_banner", + label: "Sticky Banner Under Nav", + widget: "object", + fields: [ + { + name: "text", + label: "Text", + widget: "string", + crowdin: true, + }, + { + name: "buttonText", + label: "ButtonText", + widget: "string", + crowdin: true, + }, + { + name: "buttonLink", + label: "buttonLink", + widget: "string", + crowdin: true, + }, + { + name: "isActive", + label: "IsActive", + widget: "boolean", + default: true, + }, + ], + }, ] satisfies CmsFieldList["types"]; const flexLayout = { @@ -820,7 +855,7 @@ const flexLayout = { name: "heading", required: false, widget: "string", - crowdin: true + crowdin: true, }, { name: "heading_variant", @@ -882,15 +917,15 @@ export const topLevelBlocks = [ label: "Heading", name: "heading", widget: "string", - crowdin: true + crowdin: true, }, { label: "Heading variant", name: "heading_variant", widget: "select", options: ["h2", "h3", "h4", "h5", "h6"], - default: 'h2', - crowdin: false + default: "h2", + crowdin: false, }, { name: "blocks", diff --git a/workspaces/cms-data/src/pages.ts b/workspaces/cms-data/src/pages.ts index e156a25d72..f29c8c230c 100644 --- a/workspaces/cms-data/src/pages.ts +++ b/workspaces/cms-data/src/pages.ts @@ -1,6 +1,10 @@ import { LinkData } from "./settings/main-menu"; import { defaultLocale } from "./i18n/config"; -import { getFirst, getJSON, getShuffledArray } from "@starknet-io/cms-utils/src/index"; +import { + getFirst, + getJSON, + getShuffledArray, +} from "@starknet-io/cms-utils/src/index"; import type { Meta } from "@starknet-io/cms-utils/src/index"; export interface MarkdownBlock { @@ -133,6 +137,15 @@ export interface HomeHeroBlock { readonly heroText: string; }; } + +export interface NavStickyBannerBlock { + readonly type: "nav_sticky_banner"; + readonly text: string; + readonly buttonText: string; + readonly buttonLink: string; + readonly isActive: boolean; +} + export interface LinkListBlock { readonly type: "link_list"; readonly heading?: string; @@ -152,17 +165,17 @@ export interface OrderedBlock { } export interface ChapterInfo { - content: MarkdownBlock['body']; + content: MarkdownBlock["body"]; subtitle: string; title: string; -}; +} export interface VideoSectionBlock { readonly type: "video_section"; - readonly 'scaling-eth': ChapterInfo; + readonly "scaling-eth": ChapterInfo; readonly sequencer: ChapterInfo; readonly prover: ChapterInfo; - readonly 'eth-settlement': ChapterInfo; + readonly "eth-settlement": ChapterInfo; readonly chapterDescriptionFullWidth: boolean; readonly playlistOnBottom: boolean; } @@ -183,6 +196,7 @@ export type Block = | YoutubeBlock | HeroBlock | HomeHeroBlock + | NavStickyBannerBlock | LinkListBlock | PageHeaderBlock | AccordionBlock @@ -218,7 +232,12 @@ export interface HeadingContainerBlock { readonly blocks: readonly Block[]; } -export type TopLevelBlock = Block | FlexLayoutBlock | GroupBlock | Container | HeadingContainerBlock; +export type TopLevelBlock = + | Block + | FlexLayoutBlock + | GroupBlock + | Container + | HeadingContainerBlock; export interface Page extends Meta { readonly id: string; @@ -238,20 +257,19 @@ export interface Page extends Meta { } const getPageWithRandomizedData = (data: Page): Page => { - const randomizedData = {...data} + const randomizedData = { ...data }; randomizedData.blocks?.forEach((block: TopLevelBlock) => { - - if (block.type === 'link_list' && block.randomize) { + if (block.type === "link_list" && block.randomize) { //@ts-expect-error block.blocks = getShuffledArray(block.blocks || []); - } else if (block.type === 'card_list' && block.randomize) { + } else if (block.type === "card_list" && block.randomize) { //@ts-expect-error block.card_list_items = getShuffledArray(block.card_list_items || []); } - }) + }); - return randomizedData -} + return randomizedData; +}; export async function getPageBySlug( slug: string, locale: string, @@ -265,7 +283,7 @@ export async function getPageBySlug( ) ); - return getPageWithRandomizedData(data) + return getPageWithRandomizedData(data); } catch (cause) { throw new Error(`Page not found! ${slug}`, { cause, @@ -281,7 +299,8 @@ export async function getPageById( try { return await getFirst( ...[locale, defaultLocale].map( - (value) => async () => getJSON("data/pages/" + value + "/" + id, context) + (value) => async () => + getJSON("data/pages/" + value + "/" + id, context) ) ); } catch (cause) { diff --git a/workspaces/website/src/blocks/Block.tsx b/workspaces/website/src/blocks/Block.tsx index e15714634a..431aaebca3 100644 --- a/workspaces/website/src/blocks/Block.tsx +++ b/workspaces/website/src/blocks/Block.tsx @@ -21,162 +21,193 @@ import { HeadingContainer } from "./HeadingContainer"; import VideoSectionBlock from "./VideoSectionBlock"; import { NewsletterCard } from "@ui/Card/NewsletterCard"; import { YoutubePlayer } from "@ui/YoutubePlayer/YoutubePlayer"; +import NavbarStickyBanner from "../pages/(components)/NavbarStickyBanner/NavbarStickyBanner"; + +export enum BlockPlacements { + DEFAULT = "DEFAULT", + NAVBAR = "NAVBAR", +} interface Props { + placement?: BlockPlacements; disallowH1?: boolean; readonly block: TopLevelBlock; env: { CLOUDFLARE_RECAPTCHA_KEY: string; - } + }; readonly locale: string; } -export function Block({ disallowH1, block, env, locale }: Props): JSX.Element | null { - if (block.type === "basic_card") { - return ; - } else if (block.type === "container") { - return ( - - {block.blocks.map((block, i) => ( - - ))} - - ); - } else if (block.type === "image_icon_link_card") { - return ; - } else if (block.type === "youtube") { - return - } else if (block.type === "newsletter_popup") { - return ; - } else if (block.type === "markdown") { - return ; - } else if (block.type === "ambassadors_list") { - return ; - } else if (block.type === "community_events") { - return ( - - ); - } else if (block.type === "flex_layout") { - return ( - - {block.blocks.map((block, i) => ( - - ))} - - ); - } else if (block.type === "link_list") { - return ; - } else if (block.type === "accordion") { - return ( - - {block.blocks?.map((block, i) => ( - - - - ))} - - ); - } else if (block.type === "ordered_block") { - let blocks = Array.from(block.blocks || []).sort((a, b) => { - return a.title.localeCompare(b.title); - }); - - return ( - - {blocks.map((block: any, i: number) => { +export function Block({ + placement = BlockPlacements.DEFAULT, + disallowH1, + block, + env, + locale, +}: Props): JSX.Element | null { + switch (placement) { + case BlockPlacements.DEFAULT: + switch (block.type) { + case "basic_card": + return ; + + case "container": + return ( + + {block.blocks.map((block, i) => ( + + ))} + + ); + + case "image_icon_link_card": + return ; + + case "youtube": + return ; + + case "newsletter_popup": + return ; + + case "markdown": + return ; + + case "ambassadors_list": + return ; + + case "community_events": + return ( + + ); + + case "flex_layout": + return ( + + {block.blocks.map((block, i) => ( + + ))} + + ); + + case "link_list": + return ; + + case "accordion": + return ( + + {block.blocks?.map((block, i) => ( + + + + ))} + + ); + + case "ordered_block": + let blocks = Array.from(block.blocks || []).sort((a, b) => { + return a.title.localeCompare(b.title); + }); + return ( + + {blocks.map((block: any, i: number) => { + return ( + + + + ); + })} + + ); + + case "page_header": + return ( + + ); + + case "group": + return ( + + {block.blocks.map((block, i) => ( + + ))} + + ); + + case "heading_container": + return ( + + {block.blocks.map((block, i) => ( + + ))} + + ); + + case "hero": + return ( + + ); + + case "home_hero": + const pageContext = usePageContext(); + const homeSEO = useAsync(["getBlockExplorers", locale], () => + getHomeSEO(locale, pageContext.context) + ); + + return ; + + case "card_list": + return ( + + ); + case "video_section": + return ; + } + break; + + //======================================== + + case BlockPlacements.NAVBAR: + switch (block.type) { + case "nav_sticky_banner": + if (!block.isActive) return null; return ( - - - + ); - })} - - ); - } else if (block.type === "page_header") { - return ( - - ); - } else if (block.type === "group") { - return ( - - {block.blocks.map((block, i) => ( - - ))} - - ); - }else if (block.type === "heading_container") { - return ( - - {block.blocks.map((block, i) => ( - - ))} - - ); - } else if (block.type === "hero") { - return ( - - ); - } else if (block.type === "home_hero") { - const pageContext = usePageContext(); - const homeSEO = useAsync(["getBlockExplorers", locale], () => - getHomeSEO(locale, pageContext.context) - ); - - return ; - } else if (block.type === "card_list") { - return ( - - ); - } else if (block.type === "video_section") { - return ( - - ) - } else { - // this will report type error if there is unhandled block.type - block satisfies never; + } + break; } return null; diff --git a/workspaces/website/src/components/ProvisionsPopup/CloseIcon/CloseIcon.tsx b/workspaces/website/src/components/Icons/CloseIcon/CloseIcon.tsx similarity index 59% rename from workspaces/website/src/components/ProvisionsPopup/CloseIcon/CloseIcon.tsx rename to workspaces/website/src/components/Icons/CloseIcon/CloseIcon.tsx index 63f4279afb..121413eafb 100644 --- a/workspaces/website/src/components/ProvisionsPopup/CloseIcon/CloseIcon.tsx +++ b/workspaces/website/src/components/Icons/CloseIcon/CloseIcon.tsx @@ -7,18 +7,18 @@ const CloseIcon = () => { fill="none" xmlns="http://www.w3.org/2000/svg" > - + - - + + diff --git a/workspaces/website/src/components/Layout/Navbar/NavLayout.tsx b/workspaces/website/src/components/Layout/Navbar/NavLayout.tsx index a6fe83aaf8..5c131fd6e6 100644 --- a/workspaces/website/src/components/Layout/Navbar/NavLayout.tsx +++ b/workspaces/website/src/components/Layout/Navbar/NavLayout.tsx @@ -35,13 +35,17 @@ export const NavLayout = (props: NavLayoutProps) => { toggleColorMode(); if (typeof window !== "undefined" && window.gtag) { window.gtag("event", "theme_change", { - 'event_category': "engagement", - 'value': colorMode + event_category: "engagement", + value: colorMode, }); } - } + }; return ( - + @@ -55,46 +59,58 @@ export const NavLayout = (props: NavLayoutProps) => { {props.searchArea} - - - ) : ( - - ) - } - aria-label="Toggle color mode" - onClick={toogleTheme} - marginInlineStart="0 !important" - /> - - - {!!props.languageSwitcher && ( - <> - - - {props.languageSwitcher} - - - )} + + + ) : ( + + ) + } + aria-label="Toggle color mode" + onClick={toogleTheme} + marginInlineStart="0 !important" + /> + - - } - aria-label="Open Menu" - onClick={onClickMenu} - marginInlineStart={{ base: "0px !important", md: "12px !important" }} + {!!props.languageSwitcher && ( + <> + - + + {props.languageSwitcher} + + + )} + + + } + aria-label="Open Menu" + onClick={onClickMenu} + marginInlineStart={{ + base: "0px !important", + md: "12px !important", + }} + /> + diff --git a/workspaces/website/src/components/Layout/Navbar/Navbar.tsx b/workspaces/website/src/components/Layout/Navbar/Navbar.tsx index 0a2363a85e..d0eb3017ff 100644 --- a/workspaces/website/src/components/Layout/Navbar/Navbar.tsx +++ b/workspaces/website/src/components/Layout/Navbar/Navbar.tsx @@ -23,7 +23,7 @@ type Props = { mobileNavItems?: React.ReactNode; languageSwitcher?: React.ReactNode; search?: React.ReactNode; - seo: SEOTexts['language']; + seo: SEOTexts["language"]; }; declare global { @@ -37,7 +37,7 @@ export const NavBar = ({ mobileNavItems, languageSwitcher, search, - seo + seo, }: Props) => { const { isOpen, onOpen, onClose } = useDisclosure(); const menuButtonRef = React.useRef(null); @@ -52,11 +52,11 @@ export const NavBar = ({ toggleColorMode(); if (typeof window !== "undefined" && window.gtag) { window.gtag("event", "theme_change", { - 'event_category': "engagement", - 'value': colorMode + event_category: "engagement", + value: colorMode, }); } - } + }; return ( diff --git a/workspaces/website/src/components/ProvisionsPopup/ProvisionsPopup.tsx b/workspaces/website/src/components/ProvisionsPopup/ProvisionsPopup.tsx index d27cc88672..f73bd6546e 100644 --- a/workspaces/website/src/components/ProvisionsPopup/ProvisionsPopup.tsx +++ b/workspaces/website/src/components/ProvisionsPopup/ProvisionsPopup.tsx @@ -1,7 +1,7 @@ import { Box, Image, Icon, Fade, IconButton } from "@chakra-ui/react"; import Background from "./popup-background.png"; import Logo from "./popup-text.svg"; -import CloseIcon from "./CloseIcon/CloseIcon"; +import CloseIcon from "@ui/Icons/CloseIcon/CloseIcon"; import ArrowRight from "./ArrowRight/ArrowRight"; import { Button } from "@ui/Button"; import { useLocalStorage } from "usehooks-ts"; @@ -29,7 +29,7 @@ const ProvisionsPopup = () => { setIsOpenStorage(false); }; - if (!isOpenStorage) return; + if (!isOpenStorage) return null; return ( { top="10px" width="28px" height="28px" - style={{ backgroundColor: "transparent" }} + bgColor="transparent" + _dark={{ bgColor: "transparent" }} onClick={onClose} > diff --git a/workspaces/website/src/pages/(components)/CMSPage.tsx b/workspaces/website/src/pages/(components)/CMSPage.tsx index 100e6dfaa6..90dee1cae8 100644 --- a/workspaces/website/src/pages/(components)/CMSPage.tsx +++ b/workspaces/website/src/pages/(components)/CMSPage.tsx @@ -10,24 +10,19 @@ import { BreadcrumbItem, BreadcrumbLink, Flex, - Divider + Divider, } from "@chakra-ui/react"; -import '@ui/CodeHighlight/code-highlight-init' +import "@ui/CodeHighlight/code-highlight-init"; import { blocksToTOC } from "./TableOfContents/blocksToTOC"; import NotFound from "@ui/NotFound/NotFound"; - type CMSPageProps = { data: PageType; env: { CLOUDFLARE_RECAPTCHA_KEY: string; - } + }; locale: string; }; -export default function CMSPage({ - data, - env, - locale, -}: CMSPageProps) { +export default function CMSPage({ data, env, locale }: CMSPageProps) { const date = data?.gitlog?.date; if (data?.hidden_page) { @@ -60,7 +55,6 @@ export default function CMSPage({ {data.breadcrumbs_data[0].title} - {data.title} @@ -77,38 +71,46 @@ export default function CMSPage({ - {data.show_title ? <> - - - {data.title} - - - - : null} + {data.show_title ? ( + <> + + + {data.title} + + + + + ) : null} {data.blocks?.map((block, i) => { - return ( - - ); + return ; })} } rightAside={ data.template === "content" && !data.hideToc ? ( - + ) : null } /> diff --git a/workspaces/website/src/pages/(components)/ClientOnly.tsx b/workspaces/website/src/pages/(components)/ClientOnly.tsx new file mode 100644 index 0000000000..ab08d7e218 --- /dev/null +++ b/workspaces/website/src/pages/(components)/ClientOnly.tsx @@ -0,0 +1,14 @@ +// @see https://vite-plugin-ssr.com/client-only-components#react +import React, { useEffect, useState } from "react"; + +const ClientOnly = ({ children }: { children: React.ReactNode }) => { + const [isMounted, setIsMounted] = useState(false); + + useEffect(() => { + setIsMounted(true); + }, []); + + return isMounted ? <>{children} : null; +}; + +export default ClientOnly; diff --git a/workspaces/website/src/pages/(components)/Navbar.tsx b/workspaces/website/src/pages/(components)/Navbar.tsx index bf7d63f10a..5bccfafbd5 100644 --- a/workspaces/website/src/pages/(components)/Navbar.tsx +++ b/workspaces/website/src/pages/(components)/Navbar.tsx @@ -1,4 +1,3 @@ - import * as NavAccordian from "@ui/Layout/Navbar/NavAccordion"; import type { MainMenu } from "@starknet-io/cms-data/src/settings/main-menu"; import { NavBar } from "@ui/Layout/Navbar/Navbar"; @@ -6,15 +5,16 @@ import { MenuItemWithDropdown } from "@ui/Layout/Navbar/MenuItemWithDropdown"; import { NavbarContainer } from "@ui/Layout/Navbar/NavbarContainer"; import { NavBarLink } from "@ui/Layout/Navbar/NavBarLink"; import { NavbarHeading } from "@ui/Layout/Navbar/NavbarHeading"; -import { Flex } from "@chakra-ui/react"; +import { Box, ButtonGroup, Flex } from "@chakra-ui/react"; import { getComputedLinkData } from "src/utils/utils"; import { MainSearch } from "./MainSearch"; import React, { Fragment } from "react"; -import { Box, ButtonGroup } from "@chakra-ui/react"; import { IconButton } from "@ui/IconButton"; import { SiDiscord, SiGithub, SiTwitter, SiYoutube } from "react-icons/si"; import { SEOTexts } from "@starknet-io/cms-data/src/seo"; import { usePageContext } from "src/renderer/PageContextProvider"; +import type { TopLevelBlock } from "@starknet-io/cms-data/src/pages"; +import { Block, BlockPlacements } from "../../blocks/Block"; export interface Props { readonly mainMenu: MainMenu; @@ -22,9 +22,11 @@ export interface Props { readonly ALGOLIA_INDEX: string; readonly ALGOLIA_APP_ID: string; readonly ALGOLIA_SEARCH_API_KEY: string; + readonly CLOUDFLARE_RECAPTCHA_KEY: string; }; - readonly searchSEO: SEOTexts['search']; - readonly languageCenterSeo: SEOTexts['language']; + readonly searchSEO: SEOTexts["search"]; + readonly languageCenterSeo: SEOTexts["language"]; + readonly pageBlocks?: TopLevelBlock[]; } export default function Navbar({ @@ -32,6 +34,7 @@ export default function Navbar({ env, searchSEO, languageCenterSeo, + pageBlocks, }: Props) { const { locale, urlPathname: pathname } = usePageContext(); @@ -88,7 +91,9 @@ export default function Navbar({ ) : item.custom_icon === "SiTwitter" ? ( ) : item.custom_icon === "SiYoutube" ? ( - + + + ) : ( ) @@ -187,6 +192,17 @@ export default function Navbar({ } /> + {pageBlocks?.map((block, i) => { + return ( + + ); + })} ); } diff --git a/workspaces/website/src/pages/(components)/NavbarStickyBanner/NavbarStickyBanner.tsx b/workspaces/website/src/pages/(components)/NavbarStickyBanner/NavbarStickyBanner.tsx new file mode 100644 index 0000000000..c39d807510 --- /dev/null +++ b/workspaces/website/src/pages/(components)/NavbarStickyBanner/NavbarStickyBanner.tsx @@ -0,0 +1,104 @@ +import { Center, Text, IconButton } from "@chakra-ui/react"; +import { Button } from "@ui/Button"; +import CloseIcon from "@ui/Icons/CloseIcon/CloseIcon"; +import { useMemo } from "react"; +import { useLocalStorage } from "usehooks-ts"; +import { sha256 } from "js-sha256"; +import ClientOnly from "../ClientOnly"; + +interface NavbarStickyBannerProps { + text: string; + buttonText: string; + buttonLink: string; +} + +const NavbarStickyBanner = ({ + text, + buttonText, + buttonLink, +}: NavbarStickyBannerProps) => { + const hash = useMemo( + () => sha256(text + buttonLink + buttonText).slice(-8), + [text, buttonLink, buttonText] + ); + + const [isOpen, setIsOpenStorage] = useLocalStorage( + `isNavbarStickyBannerOpen_${hash}`, + true + ); + + const gtmEvent = (target: string) => + window.gtag?.("event", target, { event_category: "engagement" }); + + const onClose = () => { + gtmEvent("Navbar_banner_close"); + setIsOpenStorage(false); + }; + + const onReadMore = () => gtmEvent("Navbar_banner_read_more"); + + return ( + +
+
+ + {text} + + +
+ + + +
+
+ ); +}; + +export default NavbarStickyBanner; diff --git a/workspaces/website/src/renderer/PageShell.tsx b/workspaces/website/src/renderer/PageShell.tsx index 2490e9b09b..ad0c5392e3 100644 --- a/workspaces/website/src/renderer/PageShell.tsx +++ b/workspaces/website/src/renderer/PageShell.tsx @@ -12,6 +12,7 @@ import { ThemeProvider } from "./ThemeProvider"; import { PageContainer } from "src/pages/(components)/PageContainer"; import Navbar from "src/pages/(components)/Navbar"; import { Footer } from "src/pages/(components)/Footer"; +import { TopLevelBlock } from "@starknet-io/cms-data/src/pages"; interface Props { readonly pageContext: PageContext; @@ -58,9 +59,12 @@ function PageLayout(props: Props) { ALGOLIA_APP_ID: import.meta.env.VITE_ALGOLIA_APP_ID!, ALGOLIA_SEARCH_API_KEY: import.meta.env .VITE_ALGOLIA_SEARCH_API_KEY!, + CLOUDFLARE_RECAPTCHA_KEY: import.meta.env + .VITE_CLOUDFLARE_RECAPTCHA_KEY!, }} searchSEO={pageContext.seo?.search} languageCenterSeo={pageContext.seo?.language} + pageBlocks={pageContext.pageProps?.data?.blocks as TopLevelBlock[]} /> {children} diff --git a/workspaces/website/src/renderer/types.ts b/workspaces/website/src/renderer/types.ts index acb711b577..3b3806b316 100644 --- a/workspaces/website/src/renderer/types.ts +++ b/workspaces/website/src/renderer/types.ts @@ -11,10 +11,19 @@ import type { PageContextBuiltInClientWithServerRouting as PageContextBuiltInClient //*/ } from "vite-plugin-ssr/types"; +import { TopLevelBlock } from "@starknet-io/cms-data/src/pages"; type Page = (pageProps: PageProps) => React.ReactElement; -export type PageProps = Record; +interface DeepNestedObject { + [key: string]: T | DeepNestedObject; +} + +export type PageProps = DeepNestedObject & { + data?: DeepNestedObject & { + blocks: TopLevelBlock[]; + }; +}; export interface DocumentProps { title?: string; @@ -28,7 +37,7 @@ export type SeoType = { seoCanonicalUrl?: string; seoDescription?: string; seoFocusKeywords?: string[]; -} +}; export type PageContextCustom = { Page: Page; diff --git a/workspaces/website/src/style/theme.ts b/workspaces/website/src/style/theme.ts index 542d0d4a07..748bc1859c 100644 --- a/workspaces/website/src/style/theme.ts +++ b/workspaces/website/src/style/theme.ts @@ -31,16 +31,16 @@ const theme = extendTheme(proTheme, { 100: "#80DCDA", 200: "#6DDAF5", 300: "#4B9EDA", - 400: "#261F63" + 400: "#261F63", }, orange: { sunfade: "#FF7E6D", - fanta: "#FF6450" + fanta: "#FF6450", }, yellowSunfade: "#FFCD9A", bg: { main: "#F6F6F6", - 200: "#FBFBFB" + 200: "#FBFBFB", }, grey: { morning: "#CCCCCC", @@ -49,41 +49,42 @@ const theme = extendTheme(proTheme, { coolerText: "#6B7280", coolText: "#6B7280", darkText: "#363636", - lineOnDark: "#313131" + lineOnDark: "#313131", }, darkMode: { card: "#1B1B1B", bg2: "#0B0B0B", - navGrey: "#121212" + navGrey: "#121212", }, snNavy: "#0C0C4F", selected: { main: "#5C94FF", 100: "#AFCAFF", 200: "#9EBFFF", - 300: "#B2CDFF" + 300: "#B2CDFF", }, notify: { dark: "#172726", green1: "#EFFBFA", - green2: "#C4E2E0" + green2: "#C4E2E0", }, note: { dark: "#171C27", main: "#4B9EDA", 100: "#EBF2FF", - 200: "#DEE3ED" + 200: "#DEE3ED", }, important: { dark: "#231F1A", 100: "#FFF5EB", - 200: "#E9E1DA" + 200: "#E9E1DA", }, warning: { dark: "#231B1A", 100: "#FFEDEB", - 200: "#E7D5D4" - } + 200: "#E7D5D4", + }, + surfaceAccent: "#A4A4EA", }, components: { Accordion: accordionTheme, @@ -110,34 +111,35 @@ const theme = extendTheme(proTheme, { Link: linkTheme, Heading: { baseStyle: { - fontWeight: "300" + fontWeight: "300", }, variants: { - ...headingTheme + ...headingTheme, }, - sizes: null}, + sizes: null, + }, Text: textTheme, - Alert: alertTheme + Alert: alertTheme, }, fonts: { heading: "Inter, sans-serif", }, fontSizes: { - "h1": "5rem", - "h2": "3rem", - "h3": "1.5rem", - "h4": "1.125rem", - "h5": "1rem", - "h6": "0.875rem", + h1: "5rem", + h2: "3rem", + h3: "1.5rem", + h4: "1.125rem", + h5: "1rem", + h6: "0.875rem", }, lineHeights: { - "h1": "100%", - "h2": "3.625rem", - "h3": "100%", - "h4": "1.375rem", - "h5": "1.188rem", - "h6": "1.063rem", - "heading6": "1.063rem", + h1: "100%", + h2: "3.625rem", + h3: "100%", + h4: "1.375rem", + h5: "1.188rem", + h6: "1.063rem", + heading6: "1.063rem", }, breakpoints: { xs: "24em", @@ -145,8 +147,7 @@ const theme = extendTheme(proTheme, { md: "48em", lg: "62em", xl: "80em", - "2xl": "96em" + "2xl": "96em", }, - }); export default theme; diff --git a/yarn.lock b/yarn.lock index aa134bfe2f..05bdd13ca6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4804,6 +4804,7 @@ __metadata: highlight.js: ^11.8.0 highlightjs-cairo: ^0.2.0 itty-router: ^4.0.11 + js-sha256: ^0.11.0 marked: ^5.1.0 moment: ^2.29.4 moment-timezone: ^0.5.43 @@ -12149,6 +12150,13 @@ __metadata: languageName: node linkType: hard +"js-sha256@npm:^0.11.0": + version: 0.11.0 + resolution: "js-sha256@npm:0.11.0" + checksum: 742d34a0c6eb15247309f1c74889b5a51df01a96e4307375b420fbe973f2f25585012b4d3c8fa52a1b18546153d12587e6463bb725dc1bc58686da03e892c334 + languageName: node + linkType: hard + "js-sha256@npm:^0.9.0": version: 0.9.0 resolution: "js-sha256@npm:0.9.0"