diff --git a/_data/settings/latest-announcements.yml b/_data/settings/latest-announcements.yml new file mode 100644 index 0000000000..4c50ecbccc --- /dev/null +++ b/_data/settings/latest-announcements.yml @@ -0,0 +1,10 @@ + - isActive: true + image: https://www.starknet.io/assets/final-.jpeg + text: What are onramps and cross rollup bridges? + buttonText: See more + buttonLink: / + - isActive: true + image: https://www.starknet.io/assets/guide-image-3-.jpg + text: What are onramps and cross rollup bridges? + buttonText: See more + buttonLink: / \ No newline at end of file diff --git a/workspaces/cms-config/src/blocks.ts b/workspaces/cms-config/src/blocks.ts index 9d06d6d221..bb077a138b 100644 --- a/workspaces/cms-config/src/blocks.ts +++ b/workspaces/cms-config/src/blocks.ts @@ -936,6 +936,25 @@ export const topLevelBlocks = [ }, ], }, + { + name: "side_sticky_banner", + label: "Side Sticky Banner", + widget: "object", + fields: [ + { + name: "blocks", + label: "Blocks", + widget: "list", + types: [...blocks, flexLayout], + default: [], + }, + { + name: "isActive", + widget: "boolean", + default: false, + }, + ], + }, flexLayout, ...blocks, ] satisfies CmsFieldList["types"]; diff --git a/workspaces/cms-config/src/collections/index.ts b/workspaces/cms-config/src/collections/index.ts index 1d088e599f..743f9ef951 100644 --- a/workspaces/cms-config/src/collections/index.ts +++ b/workspaces/cms-config/src/collections/index.ts @@ -24,5 +24,5 @@ export const collections: CmsConfig["collections"] = [ SEOCollectionConfig, roadmapPostsCollectionConfig, roadmapVersionsCollectionConfig, - announcementsCollectionConfig + announcementsCollectionConfig, ]; diff --git a/workspaces/cms-config/src/collections/settings.ts b/workspaces/cms-config/src/collections/settings.ts index 2b776261f4..a5853f44ce 100644 --- a/workspaces/cms-config/src/collections/settings.ts +++ b/workspaces/cms-config/src/collections/settings.ts @@ -1,7 +1,6 @@ import { linkFields } from "../blocks"; import { CmsCollection } from "../types"; - const permissionOptions = [ { label: "All", @@ -43,7 +42,7 @@ const permissionOptions = [ label: "Roadmap and Announcements", value: "roadmap", }, -] +]; export const settingsCollectionConfig = { crowdin: true, @@ -65,7 +64,7 @@ export const settingsCollectionConfig = { label: "Title", name: "title", widget: "string", - crowdin: true + crowdin: true, }, { label: "Columns", @@ -83,7 +82,7 @@ export const settingsCollectionConfig = { name: "title", required: false, widget: "string", - crowdin: true + crowdin: true, }, { label: "Menu Items", @@ -120,12 +119,12 @@ export const settingsCollectionConfig = { { name: "source", widget: "string", - crowdin: false + crowdin: false, }, { name: "destination", widget: "string", - crowdin: false + crowdin: false, }, ], }, @@ -143,14 +142,14 @@ export const settingsCollectionConfig = { widget: "list", fields: [ { - name: 'category', - label: 'Category', - widget: 'relation', - collection: 'categories', - search_fields: ['name'], - value_field: 'id', - display_fields: ['name'], - options_length: 300 + name: "category", + label: "Category", + widget: "relation", + collection: "categories", + search_fields: ["name"], + value_field: "id", + display_fields: ["name"], + options_length: 300, }, ], }, @@ -171,7 +170,7 @@ export const settingsCollectionConfig = { label: "Name", name: "name", widget: "string", - crowdin: true + crowdin: true, }, { label: "Type", @@ -202,19 +201,19 @@ export const settingsCollectionConfig = { label: "Twitter handle", name: "twitter", widget: "string", - crowdin: false + crowdin: false, }, { label: "Website url", name: "website_url", widget: "string", - crowdin: false + crowdin: false, }, { label: "Description", name: "body", widget: "string", - crowdin: true + crowdin: true, }, ], }, @@ -255,7 +254,7 @@ export const settingsCollectionConfig = { name: "title", label: "Title", widget: "string", - crowdin: true + crowdin: true, }, { name: "body", @@ -268,7 +267,7 @@ export const settingsCollectionConfig = { hint: "If page url is not specified (e.g. 'learn/glossary'), it will be used globally", required: false, widget: "string", - crowdin: false + crowdin: false, }, ], }, @@ -294,7 +293,7 @@ export const settingsCollectionConfig = { search_fields: ["title"], value_field: "id", display_fields: ["title"], - options_length: 300 + options_length: 300, }, ], }, @@ -315,36 +314,36 @@ export const settingsCollectionConfig = { label: "Hero banner title", widget: "string", required: true, - crowdin: true + crowdin: true, }, { name: "hero_description", label: "Hero description", widget: "string", required: true, - crowdin: true + crowdin: true, }, { name: "show_hero_cta", label: "Show hero banner CTA", widget: "boolean", default: true, - crowdin: true + crowdin: true, }, { name: "hero_cta_copy", label: "Hero banner CTA copy", widget: "string", required: false, - crowdin: true + crowdin: true, }, { name: "roadmap_post_ps", label: "Roadmap post p.s. copy", widget: "markdown", required: false, - crowdin: true - } + crowdin: true, + }, ], }, { @@ -369,14 +368,55 @@ export const settingsCollectionConfig = { label: "Name", widget: "string", crowdin: false, - required: false + required: false, }, { name: "access", label: "Access to collections", multiple: true, widget: "select", - options: permissionOptions + options: permissionOptions, + }, + ], + }, + ], + }, + { + label: "Latest Announcements", + name: "LatestAnnouncements", + file: `_data/settings/latest-announcements.yml`, + crowdin: false, + fields: [ + { + label: "announcements", + name: "announcements", + widget: "list", + fields: [ + { + name: "image", + widget: "image", + crowdin: false, + }, + { + name: "text", + widget: "string", + crowdin: false, + }, + { + name: "buttonText", + widget: "string", + crowdin: false, + }, + { + name: "buttonLink", + widget: "string", + crowdin: false, + }, + { + name: "isActive", + widget: "boolean", + default: true, + crowdin: false, }, ], }, diff --git a/workspaces/cms-data/src/settings/latest-announcements.ts b/workspaces/cms-data/src/settings/latest-announcements.ts new file mode 100644 index 0000000000..93c5879cfb --- /dev/null +++ b/workspaces/cms-data/src/settings/latest-announcements.ts @@ -0,0 +1,26 @@ +import { getJSON } from "@starknet-io/cms-utils/src/index"; + +export type LatestAnnouncements = { + readonly image: string; + readonly text: string; + readonly buttonText: string; + readonly buttonLink: string; + readonly isActive: boolean; +}; + +export async function getLatestAnnouncements( + context: EventContext<{}, any, Record> +) { + try { + const latestAnnouncements = await getJSON( + "data/latest-announcements/latest-announcements", + context + ); + + return latestAnnouncements; + } catch (cause) { + throw new Error("getLatestAnnouncements failed!", { + cause, + }); + } +} diff --git a/workspaces/cms-scripts/src/index.ts b/workspaces/cms-scripts/src/index.ts index b6b7261235..841676b87e 100644 --- a/workspaces/cms-scripts/src/index.ts +++ b/workspaces/cms-scripts/src/index.ts @@ -24,58 +24,66 @@ import { import { translateFile } from "./crowdin"; const createRoadmapDetails = async () => { - await fs.mkdir(`public/data/roadmap-details`, {recursive: true}); + await fs.mkdir(`public/data/roadmap-details`, { recursive: true }); for (const locale of locales) { const roadmapPosts: RoadmapDetails[] = []; - const filesPath = path.join("public/data/roadmap-posts", locale) + const filesPath = path.join("public/data/roadmap-posts", locale); const filesInDir = await fs.readdir(filesPath); const jsonFilesInDir = filesInDir.filter((file) => file.endsWith(".json")); for (const fileName of jsonFilesInDir) { const fileData = await fs.readFile( - path.join( - process.cwd(), - "public/data/roadmap-posts", - locale, - fileName - ), + path.join(process.cwd(), "public/data/roadmap-posts", locale, fileName), "utf8" ); - const { blocks, gitlog, sourceFilepath, objectID, ...roadmapDetails }: RoadmapPost = JSON.parse(fileData.toString()); + const { + blocks, + gitlog, + sourceFilepath, + objectID, + ...roadmapDetails + }: RoadmapPost = JSON.parse(fileData.toString()); roadmapPosts.push(roadmapDetails); } - await write(path.join("public/data/roadmap-details", `${locale}.json`), roadmapPosts); + await write( + path.join("public/data/roadmap-details", `${locale}.json`), + roadmapPosts + ); } -} +}; const createAnnouncementDetails = async () => { - await fs.mkdir(`public/data/announcements-details`, {recursive: true}); + await fs.mkdir(`public/data/announcements-details`, { recursive: true }); for (const locale of locales) { const roadmapPosts: AnnouncementDetails[] = []; - const filesPath = path.join("public/data/announcements", locale) + const filesPath = path.join("public/data/announcements", locale); const filesInDir = await fs.readdir(filesPath); const jsonFilesInDir = filesInDir.filter((file) => file.endsWith(".json")); for (const fileName of jsonFilesInDir) { const fileData = await fs.readFile( - path.join( - process.cwd(), - "public/data/announcements", - locale, - fileName - ), + path.join(process.cwd(), "public/data/announcements", locale, fileName), "utf8" ); - const { blocks, gitlog, sourceFilepath, objectID, ...roadmapDetails }: AnnouncementsPost = JSON.parse(fileData.toString()); + const { + blocks, + gitlog, + sourceFilepath, + objectID, + ...roadmapDetails + }: AnnouncementsPost = JSON.parse(fileData.toString()); roadmapPosts.push(roadmapDetails); } - await write(path.join("public/data/announcements-details", `${locale}.json`), roadmapPosts); + await write( + path.join("public/data/announcements-details", `${locale}.json`), + roadmapPosts + ); } -} +}; const createSharedData = async () => { await fs.mkdir(`public/data/shared-data`, { recursive: true }); @@ -87,20 +95,20 @@ const createSharedData = async () => { "jobs", "search", "language", - "newsletter" + "newsletter", ]; for (const locale of locales) { - const seo: Record = {} + const seo: Record = {}; for (const fileName of seoFiles) { const fileData = await fs.readFile( path.join(process.cwd(), `public/data/seo/${fileName}/${locale}.json`), "utf8" ); - + const fileDataParsed = JSON.parse(fileData.toString()); - seo[fileName] = fileDataParsed + seo[fileName] = fileDataParsed; } const mainMenuData = await fs.readFile( @@ -117,7 +125,7 @@ const createSharedData = async () => { seo, mainMenu: JSON.parse(mainMenuData.toString()), alerts: JSON.parse(alertsData.toString()), - } + }; await write( path.join("public/data/shared-data", `${locale}.json`), @@ -226,9 +234,9 @@ for (const data of roadmapPosts.filenameMap.values()) { for (const data of announcements.filenameMap.values()) { await write( - `public/data/announcements/${data.locale}/${data.slug}.json`, - data -); + `public/data/announcements/${data.locale}/${data.slug}.json`, + data + ); } for (const locale of locales) { @@ -278,10 +286,22 @@ for (const locale of locales) { const redirects = await yaml("_data/settings/redirects.yml"); await write(`workspaces/website/redirects.json`, redirects); +await fs.mkdir("public/data/latest-announcements", { recursive: true }); +const latestAnnouncements = await yaml( + "_data/settings/latest-announcements.yml" +); +await write( + `public/data/latest-announcements/latest-announcements.json`, + latestAnnouncements +); + await fs.mkdir("public/data/featured-sections", { recursive: true }); const featuredSections = await yaml("_data/settings/featured-sections.yml"); -await write(`public/data/featured-sections/featured-sections.json`, featuredSections); +await write( + `public/data/featured-sections/featured-sections.json`, + featuredSections +); -await createRoadmapDetails() -await createAnnouncementDetails() -await createSharedData() +await createRoadmapDetails(); +await createAnnouncementDetails(); +await createSharedData(); diff --git a/workspaces/cms-utils/src/index.ts b/workspaces/cms-utils/src/index.ts index ff87adee9f..f419561dcf 100644 --- a/workspaces/cms-utils/src/index.ts +++ b/workspaces/cms-utils/src/index.ts @@ -10,7 +10,7 @@ export function youtubeVideoIdFromURL(url: string): string | undefined | void { if (obj.pathname.startsWith("/live/")) { return obj.pathname.replace("/live/", ""); } - if(obj.pathname.startsWith("/embed/")) { + if (obj.pathname.startsWith("/embed/")) { return obj.pathname.replace("/embed/", ""); } } else if (obj.hostname === "youtu.be") { @@ -64,9 +64,11 @@ export async function getJSON( ): Promise { if (import.meta.env.SSR || context) { if (context) { - const res = await context.env.ASSETS.fetch(new URL("/" + src + ".json", "http://localhost:3000")) + const res = await context.env.ASSETS.fetch( + new URL("/" + src + ".json", "http://localhost:3000") + ); - return res.json() + return res.json(); } else if (import.meta.env.DEV) { const fs = await import("node:fs/promises"); const path = await import("node:path"); diff --git a/workspaces/website/functions/[[route]].ts b/workspaces/website/functions/[[route]].ts index 92e6eb19cc..449631fed0 100644 --- a/workspaces/website/functions/[[route]].ts +++ b/workspaces/website/functions/[[route]].ts @@ -10,11 +10,14 @@ router.all("/api/*", (req, context) => apiRouter.handle(req, context.env)); redirects.items.forEach(({ source, destination }) => { router.get( source, - (req: IRequest, context: EventContext<{}, any, Record>) => { - let src = destination + ( + req: IRequest, + context: EventContext<{}, any, Record> + ) => { + let src = destination; for (const [key, value] of Object.entries(req.params)) { - src = src.replace(new RegExp(`:${key}\\+?`), value) + src = src.replace(new RegExp(`:${key}\\+?`), value); } return Response.redirect(new URL(src, req.url), 301); @@ -27,30 +30,35 @@ redirects.items.forEach(({ source, destination }) => { */ router.get( - '/:locale?/posts/:category', + "/:locale?/posts/:category", (req: IRequest, _context: EventContext<{}, any, Record>) => { return Response.redirect( new URL( - `${req.params.locale ? `/${req.params.locale}` : ``}/content/category/${req.params.category}`, + `${req.params.locale ? `/${req.params.locale}` : ``}/content/category/${ + req.params.category + }`, req.url - ), - 301); + ), + 301 + ); } ); router.get( - '/:locale?/posts/:cat/:slug', + "/:locale?/posts/:cat/:slug", (req: IRequest, _context: EventContext<{}, any, Record>) => { return Response.redirect( new URL( - `${req.params.locale ? `/${req.params.locale}` : ``}/content/${req.params.slug}`, + `${req.params.locale ? `/${req.params.locale}` : ``}/content/${ + req.params.slug + }`, req.url ), - 301); + 301 + ); } ); - async function ittyAssetshandler( req: IRequest, context: EventContext<{}, any, Record> @@ -66,41 +74,43 @@ router.get("/assets/*", ittyAssetshandler); router.get("/sitemap.xml", ittyAssetshandler); router.all("/data/*", preflight); -router.get("/data/*", async (req, context: EventContext<{}, any, Record>) => { - return corsify(await context.env.ASSETS.fetch(req)); -}); - -router.all("*", async (req, context: EventContext<{}, any, Record>) => { - const userAgent = req.headers.get("User-Agent")!; - - const pageContextInit = { - context, - urlOriginal: req.url, - fetch, - userAgent, - }; - - const pageContext: any = await renderPage(pageContextInit); - - if (pageContext.redirectTo) { - return Response.redirect( - new URL(pageContext.redirectTo, req.url), - 301 - ); - } else if (pageContext.httpResponse != null) { - return new Response(pageContext.httpResponse.getReadableWebStream(), { - headers: { "content-type": pageContext.httpResponse.contentType }, - status: pageContext.httpResponse.statusCode, - }); +router.get( + "/data/*", + async (req, context: EventContext<{}, any, Record>) => { + return corsify(await context.env.ASSETS.fetch(req)); } +); - return context.env.ASSETS.fetch(req); -}); +router.all( + "*", + async (req, context: EventContext<{}, any, Record>) => { + const userAgent = req.headers.get("User-Agent")!; + + const pageContextInit = { + context, + urlOriginal: req.url, + fetch, + userAgent, + }; + + const pageContext: any = await renderPage(pageContextInit); + + if (pageContext.redirectTo) { + return Response.redirect(new URL(pageContext.redirectTo, req.url), 301); + } else if (pageContext.httpResponse != null) { + return new Response(pageContext.httpResponse.getReadableWebStream(), { + headers: { "content-type": pageContext.httpResponse.contentType }, + status: pageContext.httpResponse.statusCode, + }); + } + return context.env.ASSETS.fetch(req); + } +); export const onRequest: PagesFunction<{}> = async (context) => { return router.handle(context.request, context).catch((err) => { console.error(err); return new Response("Internal Error", { status: 500 }); - }) -} + }); +}; diff --git a/workspaces/website/src/components/LatestAnnouncement/LatestAnnouncement.tsx b/workspaces/website/src/components/LatestAnnouncement/LatestAnnouncement.tsx new file mode 100644 index 0000000000..88da1074f2 --- /dev/null +++ b/workspaces/website/src/components/LatestAnnouncement/LatestAnnouncement.tsx @@ -0,0 +1,97 @@ +import { Box, Text, Heading, FlexProps } from "@chakra-ui/react"; +import { Link } from "@chakra-ui/react"; +import { LatestAnnouncements } from "@starknet-io/cms-data/src/settings/latest-announcements"; +import { Image } from "@ui/ArticleCard/ArticleCard"; +import { useMemo } from "react"; +import { gtmEvent } from "src/utils/utils"; + +interface LatestAnnouncementProps extends FlexProps { + readonly list: readonly LatestAnnouncements[]; +} + +const LatestAnnouncement = ({ list, ...rest }: LatestAnnouncementProps) => { + const LatestAnnouncementList = useMemo( + () => list.filter(({ isActive }) => isActive), + [] + ); + + const onReadMore = () => gtmEvent("Latest_announcement_read_more"); + + if (!LatestAnnouncementList.length) return null; + + return ( + + + Latest announcements + + + {LatestAnnouncementList.map((item) => ( + + + + {item.text} + + + {item.buttonText} -{">"} + + + ))} + + + ); +}; + +export default LatestAnnouncement; diff --git a/workspaces/website/src/components/Layout/PageLayout.tsx b/workspaces/website/src/components/Layout/PageLayout.tsx index 9130a626ec..5e7369585c 100644 --- a/workspaces/website/src/components/Layout/PageLayout.tsx +++ b/workspaces/website/src/components/Layout/PageLayout.tsx @@ -1,6 +1,4 @@ import { Box, Container, Flex, Spacer, Stack } from "@chakra-ui/react"; -import { useState, useEffect } from "react"; -import { useLocalStorage } from "react-use"; import { SectionHeader } from "@ui/SectionHeader/SectionHeader"; import { Text } from "@ui/Typography/Text"; // import { SummitPromo } from "./SummitPromo"; @@ -23,7 +21,12 @@ type Props = { export const PageLayout = (props: Props) => { return ( - + {props.breadcrumbs} @@ -57,13 +60,7 @@ export const PageLayout = (props: Props) => { )} - + {props.sectionHeaderTitle && ( { const isSsr = typeof window === "undefined"; @@ -13,10 +14,6 @@ const ProvisionsPopup = () => { "isProvisionsPopupOpen", !isSsr ); - - const gtmEvent = (target: string) => - window.gtag?.("event", target, { event_category: "engagement" }); - const onClose = (event: React.MouseEvent) => { event.stopPropagation(); gtmEvent("Provisions_popup_close"); diff --git a/workspaces/website/src/pages/(components)/CMSPage.tsx b/workspaces/website/src/pages/(components)/CMSPage.tsx index 90dee1cae8..db94fb1642 100644 --- a/workspaces/website/src/pages/(components)/CMSPage.tsx +++ b/workspaces/website/src/pages/(components)/CMSPage.tsx @@ -17,7 +17,7 @@ import { blocksToTOC } from "./TableOfContents/blocksToTOC"; import NotFound from "@ui/NotFound/NotFound"; type CMSPageProps = { data: PageType; - env: { + env?: { CLOUDFLARE_RECAPTCHA_KEY: string; }; locale: string; diff --git a/workspaces/website/src/pages/(components)/NavbarStickyBanner/NavbarStickyBanner.tsx b/workspaces/website/src/pages/(components)/NavbarStickyBanner/NavbarStickyBanner.tsx index c39d807510..af38794071 100644 --- a/workspaces/website/src/pages/(components)/NavbarStickyBanner/NavbarStickyBanner.tsx +++ b/workspaces/website/src/pages/(components)/NavbarStickyBanner/NavbarStickyBanner.tsx @@ -5,6 +5,7 @@ import { useMemo } from "react"; import { useLocalStorage } from "usehooks-ts"; import { sha256 } from "js-sha256"; import ClientOnly from "../ClientOnly"; +import { gtmEvent } from "src/utils/utils"; interface NavbarStickyBannerProps { text: string; @@ -27,9 +28,6 @@ const NavbarStickyBanner = ({ true ); - const gtmEvent = (target: string) => - window.gtag?.("event", target, { event_category: "engagement" }); - const onClose = () => { gtmEvent("Navbar_banner_close"); setIsOpenStorage(false); diff --git a/workspaces/website/src/pages/content/@slug/PostPage.tsx b/workspaces/website/src/pages/content/@slug/PostPage.tsx index 027f4346c7..cb42d41b63 100644 --- a/workspaces/website/src/pages/content/@slug/PostPage.tsx +++ b/workspaces/website/src/pages/content/@slug/PostPage.tsx @@ -20,7 +20,6 @@ import { InstantSearch, useHits, } from "react-instantsearch-hooks-web"; - import { Post } from "@starknet-io/cms-data/src/posts"; import { TableOfContents } from "src/pages/(components)/TableOfContents/TableOfContents"; import { Text } from "@ui/Typography/Text"; @@ -36,6 +35,8 @@ import { BlogCard } from "@ui/Blog/BlogCard"; import { BlogHit } from "../PostsPage"; import { BlogBreadcrumbs } from "@ui/Blog/BlogBreadcrumbs"; import SocialShare from "./SocialShare/SocialShare"; +import { LatestAnnouncements } from "@starknet-io/cms-data/src/settings/latest-announcements"; +import LatestAnnouncement from "@ui/LatestAnnouncement/LatestAnnouncement"; export interface Props { readonly params: LocaleParams & { @@ -43,6 +44,7 @@ export interface Props { }; readonly categories: readonly Category[]; readonly topics: readonly Topic[]; + readonly latestAnnouncements: readonly LatestAnnouncements[]; readonly post: Post; readonly env?: { readonly ALGOLIA_INDEX: string; @@ -64,18 +66,24 @@ export interface MarkdownBlock { /** * Export `PostPage` component. */ - -export function PostPage(props: Props): JSX.Element { - const { - params: { slug, locale }, - categories, - topics, - post, - env, - } = props; +enum GridAreas { + BREADCRUMBS = "breadcrumbs", + POST = "post", + LATEST_ANNOUNCEMENT = "latestAnnouncement", + TIMELINE = "timeline", +} +export function PostPage({ + params: { slug, locale }, + categories, + topics, + latestAnnouncements, + post, + env, +}: Props): JSX.Element { const postCategories = categories.filter((c) => post.category.includes(c.id)); const videoId = post.post_type !== "article" ? post.video?.id : undefined; const isMobile = useBreakpointValue({ base: true, lg: false }); + const isTablet = useBreakpointValue({ base: true, xl: false }); const searchClient = useMemo(() => { return algoliasearch( env?.ALGOLIA_APP_ID ?? "", @@ -84,20 +92,21 @@ export function PostPage(props: Props): JSX.Element { }, [env?.ALGOLIA_APP_ID, env?.ALGOLIA_SEARCH_API_KEY]); return ( - + ) : null} - + {post.post_type === "article" ? ( @@ -188,9 +197,11 @@ export function PostPage(props: Props): JSX.Element { {post.post_desc} )} - - - + {isTablet && ( + + + + )} {post.post_type !== "article" && ( @@ -246,10 +257,25 @@ export function PostPage(props: Props): JSX.Element { + {!isTablet && ( + + + + + )} + {isTablet && ( + + )} - - May also interest you @@ -265,7 +291,6 @@ export function PostPage(props: Props): JSX.Element { locale: [locale], }} /> - diff --git a/workspaces/website/src/pages/content/@slug/SocialShare/SocialShare.tsx b/workspaces/website/src/pages/content/@slug/SocialShare/SocialShare.tsx index 30e46fe3b7..ef920f04f6 100644 --- a/workspaces/website/src/pages/content/@slug/SocialShare/SocialShare.tsx +++ b/workspaces/website/src/pages/content/@slug/SocialShare/SocialShare.tsx @@ -16,23 +16,21 @@ interface Props { const SocialShare = ({ params: { slug, locale } }: Props) => { const shareUrl = `${import.meta.env.VITE_SITE_URL}/${locale}/content/${slug}`; - const encodedUrl = encodeURIComponent(shareUrl); - const linkedinShareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`; - const handleLinkedinShare = () => window.open(linkedinShareUrl, "_blank"); return ( Share this post: @@ -45,8 +43,8 @@ const SocialShare = ({ params: { slug, locale } }: Props) => { { { return algoliasearch(env.ALGOLIA_APP_ID, env.ALGOLIA_SEARCH_API_KEY); }, [env.ALGOLIA_APP_ID, env.ALGOLIA_SEARCH_API_KEY]); - + const featuredCategories = useMemo(() => { - return categories.filter(category => featuredSections.includes(category.id)) + return categories.filter((category) => + featuredSections.includes(category.id) + ); }, [categories, featuredSections]); - const isMobile = useBreakpointValue({ base: true, lg: false }) + const isMobile = useBreakpointValue({ base: true, lg: false }); return ( - {!isMobile && ( <> - {'Explore'} + {"Explore"} + display={"flex"} + columnGap={"12px"} + ml={"auto"} + buttonHref={(postType) => `/${params.locale}/content/category/all?${qs.stringify({ - postType + postType, })}` } /> @@ -121,32 +119,25 @@ export function PostsPage({ )} - + {!isMobile && ( - )} - + - - {featuredCategories.map(category => ( + {featuredCategories.map((category) => ( @@ -172,11 +163,7 @@ export function PostsPage({ {isMobile && ( - + )} ); @@ -189,14 +176,14 @@ type VideoData = { snippet: object; contentDetails: { duration: string; - } -} + }; +}; type Video = { data: VideoData; url: string; id: string; -} +}; export type BlogHit = { readonly id: string; diff --git a/workspaces/website/src/pages/content/index.page.server.ts b/workspaces/website/src/pages/content/index.page.server.ts index 403232c654..461c875a80 100644 --- a/workspaces/website/src/pages/content/index.page.server.ts +++ b/workspaces/website/src/pages/content/index.page.server.ts @@ -8,7 +8,9 @@ import qs from "qs"; export async function onBeforeRender(pageContext: PageContextServer) { const defaultPageContext = await getDefaultPageContext(pageContext); - const query = qs.parse((pageContext as any)._urlPristine?.split("?")[1] ?? ''); + const query = qs.parse( + (pageContext as any)._urlPristine?.split("?")[1] ?? "" + ); const { locale } = defaultPageContext; const featuredSections = await getFeaturedSections(pageContext.context); @@ -23,12 +25,14 @@ export async function onBeforeRender(pageContext: PageContextServer) { }, params: { locale, - ...query.postType && { + ...(query.postType && { postType: query.postType as string, - }, - ...!!query.topicFilters && { - topicFilters: (Array.isArray(query.topicFilters) ? query.topicFilters : [query.topicFilters]) as string[], - } + }), + ...(!!query.topicFilters && { + topicFilters: (Array.isArray(query.topicFilters) + ? query.topicFilters + : [query.topicFilters]) as string[], + }), }, }; diff --git a/workspaces/website/src/pages/live-preview/(components)/LivePreviewPage.tsx b/workspaces/website/src/pages/live-preview/(components)/LivePreviewPage.tsx new file mode 100644 index 0000000000..220631ddd3 --- /dev/null +++ b/workspaces/website/src/pages/live-preview/(components)/LivePreviewPage.tsx @@ -0,0 +1,84 @@ +import { Box } from "@chakra-ui/react"; +import { Category } from "@starknet-io/cms-data/src/categories"; +import { Topic } from "@starknet-io/cms-data/src/topics"; +import usePreviewData, { CustomPreviewType } from "./usePreviewData"; +import EventCard from "src/pages/events/EventCard"; +import { isUpcomingEvent } from "src/pages/events/utils"; +import JobsCard from "src/pages/jobs/JobsCard"; +import TutorialsCard from "src/pages/tutorials/TutorialsCard"; +import CMSPage from "src/pages/(components)/CMSPage"; +import { ThemeProvider } from "src/renderer/ThemeProvider"; +import RoadmapPost from "src/pages/roadmap/@slug/(components)/RoadmapPost"; +import { RoadmapVersion } from "@starknet-io/cms-data/src/roadmap"; +import RoadmapPostVersion from "src/pages/roadmap/(components)/RoadmapPostVersion"; +import { PostPage } from "src/pages/content/@slug/PostPage"; +import { LatestAnnouncements } from "@starknet-io/cms-data/src/settings/latest-announcements"; + +export interface Props { + readonly categories: readonly Category[]; + readonly latestAnnouncements: LatestAnnouncements[]; + readonly topics: readonly Topic[]; + readonly roadmapVersions: readonly RoadmapVersion[]; +} + +export default function LivePreviewPage({ + topics, + categories, + latestAnnouncements, + roadmapVersions, +}: Props) { + const data = usePreviewData(); + + return ( + +
+ {data.type === CustomPreviewType.EVENTS && ( + + )} + {data.type === CustomPreviewType.JOBS && ( + + )} + {data.type === CustomPreviewType.TUTORIALS && ( + + + + )} + {data.type === CustomPreviewType.POST && ( + + )} + {data.type === CustomPreviewType.PAGE && ( + + )} + {data.type === CustomPreviewType.ROADMAP_POST && ( + v.version === data.payload.version)! + } + locale="en" + /> + )} + {data.type === CustomPreviewType.ROADMAP_VERSION && ( + + )} +
+
+ ); +} diff --git a/workspaces/website/src/pages/live-preview/(componnets)/live-preview.css b/workspaces/website/src/pages/live-preview/(components)/live-preview.css similarity index 100% rename from workspaces/website/src/pages/live-preview/(componnets)/live-preview.css rename to workspaces/website/src/pages/live-preview/(components)/live-preview.css diff --git a/workspaces/website/src/pages/live-preview/(componnets)/usePreviewData.ts b/workspaces/website/src/pages/live-preview/(components)/usePreviewData.ts similarity index 100% rename from workspaces/website/src/pages/live-preview/(componnets)/usePreviewData.ts rename to workspaces/website/src/pages/live-preview/(components)/usePreviewData.ts diff --git a/workspaces/website/src/pages/live-preview/(componnets)/LivePreivewPage.tsx b/workspaces/website/src/pages/live-preview/(componnets)/LivePreivewPage.tsx deleted file mode 100644 index 3f024babc6..0000000000 --- a/workspaces/website/src/pages/live-preview/(componnets)/LivePreivewPage.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Box } from "@chakra-ui/react"; -import { Category } from "@starknet-io/cms-data/src/categories"; -import { Topic } from "@starknet-io/cms-data/src/topics"; -import usePreviewData, { CustomPreviewType } from "./usePreviewData"; -import EventCard from "src/pages/events/EventCard"; -import { isUpcomingEvent } from "src/pages/events/utils"; -import JobsCard from "src/pages/jobs/JobsCard"; -import TutorialsCard from "src/pages/tutorials/TutorialsCard"; -import CMSPage from "src/pages/(components)/CMSPage"; -import { ThemeProvider } from "src/renderer/ThemeProvider"; -import RoadmapPost from "src/pages/roadmap/@slug/(components)/RoadmapPost"; -import { RoadmapVersion } from "@starknet-io/cms-data/src/roadmap"; -import RoadmapPostVersion from "src/pages/roadmap/(components)/RoadmapPostVersion"; -import { PostPage } from "src/pages/content/@slug/PostPage"; - -export interface Props { - readonly categories: readonly Category[]; - readonly topics: readonly Topic[]; - readonly roadmapVersions: readonly RoadmapVersion[] -} - -export default function LivePreivewPage({ - topics, - categories, - roadmapVersions -}: Props) { - const data = usePreviewData(); - - return ( - -
- {data.type === CustomPreviewType.EVENTS && ( - - )} - {data.type === CustomPreviewType.JOBS && } - {data.type === CustomPreviewType.TUTORIALS && ( - - - - )} - {data.type === CustomPreviewType.POST && ( - - )} - {data.type === CustomPreviewType.PAGE && ( - - )} - {data.type === CustomPreviewType.ROADMAP_POST && ( - v.version === data.payload.version)! - } - locale="en" - /> - )} - {data.type === CustomPreviewType.ROADMAP_VERSION && ( - - )} -
-
- ); -} diff --git a/workspaces/website/src/pages/live-preview/index.page.server.ts b/workspaces/website/src/pages/live-preview/index.page.server.ts index 7703466f8a..fa5fb37ed7 100644 --- a/workspaces/website/src/pages/live-preview/index.page.server.ts +++ b/workspaces/website/src/pages/live-preview/index.page.server.ts @@ -2,8 +2,9 @@ import { getCategories } from "@starknet-io/cms-data/src/categories"; import { getTopics } from "@starknet-io/cms-data/src/topics"; import { PageContextServer } from "src/renderer/types"; import { getDefaultPageContext } from "src/renderer/helpers"; -import { Props } from "./(componnets)/LivePreivewPage"; +import { Props } from "./(components)/LivePreviewPage"; import { getRoadmapVersions } from "@starknet-io/cms-data/src/roadmap"; +import { getLatestAnnouncements } from "@starknet-io/cms-data/src/settings/latest-announcements"; export async function onBeforeRender(pageContext: PageContextServer) { const defaultPageContext = await getDefaultPageContext(pageContext); @@ -11,8 +12,9 @@ export async function onBeforeRender(pageContext: PageContextServer) { const pageProps: Props = { categories: await getCategories(locale, pageContext.context), + latestAnnouncements: await getLatestAnnouncements(pageContext.context), topics: await getTopics(locale, pageContext.context), - roadmapVersions: await getRoadmapVersions(locale, pageContext.context) + roadmapVersions: await getRoadmapVersions(locale, pageContext.context), }; return { diff --git a/workspaces/website/src/pages/live-preview/index.page.tsx b/workspaces/website/src/pages/live-preview/index.page.tsx index 733dca0e49..77763ffeb7 100644 --- a/workspaces/website/src/pages/live-preview/index.page.tsx +++ b/workspaces/website/src/pages/live-preview/index.page.tsx @@ -1 +1 @@ -export { default as Page } from "./(componnets)/LivePreivewPage"; \ No newline at end of file +export { default as Page } from "./(components)/LivePreviewPage"; diff --git a/workspaces/website/src/pages/roadmap/@slug/(components)/RoadmapPost.tsx b/workspaces/website/src/pages/roadmap/@slug/(components)/RoadmapPost.tsx index 76bc6ce4d5..1130c527ea 100644 --- a/workspaces/website/src/pages/roadmap/@slug/(components)/RoadmapPost.tsx +++ b/workspaces/website/src/pages/roadmap/@slug/(components)/RoadmapPost.tsx @@ -23,48 +23,51 @@ import { RoadmapVersion, } from "@starknet-io/cms-data/src/roadmap"; import RoadmapPostVersion from "../../(components)/RoadmapPostVersion"; -import '@ui/CodeHighlight/code-highlight-init' +import "@ui/CodeHighlight/code-highlight-init"; interface KeyValuePairs { [key: string]: string; } const stages: KeyValuePairs = { - "completed": "Completed", + completed: "Completed", "building-now": "Building now", "building-next": "Building next", - "backlog": "Details are WIP", + backlog: "Details are WIP", }; export type RoadmapPostProps = { - env: { + env?: { CLOUDFLARE_RECAPTCHA_KEY: string; }; roadmapPost: RoadmapPostType; roadmapVersion?: RoadmapVersion; locale: string; psCopy?: string; -} +}; export default function RoadmapPost({ env, roadmapPost, locale, roadmapVersion, - psCopy + psCopy, }: RoadmapPostProps) { const [isOpen, setIsOpen] = useBoolean(false); useEffect(() => { const handleClick = (event: MouseEvent) => { const target = event.target as HTMLElement; - if (target.tagName === 'A' && (target as HTMLAnchorElement).getAttribute('href') === '#sendgrid') { + if ( + target.tagName === "A" && + (target as HTMLAnchorElement).getAttribute("href") === "#sendgrid" + ) { event.preventDefault(); - setIsOpen(true) + setIsOpen(true); } }; - document.addEventListener('click', handleClick); + document.addEventListener("click", handleClick); return () => { - document.removeEventListener('click', handleClick); + document.removeEventListener("click", handleClick); }; }, [setIsOpen]); @@ -73,11 +76,7 @@ export default function RoadmapPost({ breadcrumbs={ - + Home @@ -118,34 +117,93 @@ export default function RoadmapPost({ - + {roadmapPost.title} - + - STAGE: {stages[roadmapPost?.stage]} - - {roadmapPost.availability} + + STAGE: {stages[roadmapPost?.stage]} + + + {roadmapPost.availability} - {roadmapPost?.state ? {roadmapPost?.specific_info ? {roadmapPost?.specific_info} : null} : null} + {roadmapPost?.state ? ( + + {roadmapPost?.specific_info ? ( + + {roadmapPost?.specific_info} + + ) : null} + + ) : null} -
{roadmapPost?.state ? {roadmapPost?.state} : null}
+
+ {roadmapPost?.state ? ( + + {roadmapPost?.state} + + + ) : null} +
{roadmapPost.blocks?.map((block, i) => ( - + ))} - +
} diff --git a/workspaces/website/src/style/theme.ts b/workspaces/website/src/style/theme.ts index 748bc1859c..efe573bb61 100644 --- a/workspaces/website/src/style/theme.ts +++ b/workspaces/website/src/style/theme.ts @@ -84,6 +84,8 @@ const theme = extendTheme(proTheme, { 100: "#FFEDEB", 200: "#E7D5D4", }, + surface: "#EDF2F7", + Link: "#1061FF", surfaceAccent: "#A4A4EA", }, components: { diff --git a/workspaces/website/src/utils/utils.ts b/workspaces/website/src/utils/utils.ts index 2a2001ec0f..dcd1355421 100644 --- a/workspaces/website/src/utils/utils.ts +++ b/workspaces/website/src/utils/utils.ts @@ -10,8 +10,8 @@ export function getComputedLinkData( locale: string, link?: LinkData ): { href?: string; label?: string } { - if(!link){ - return {href: '', label: ''} + if (!link) { + return { href: "", label: "" }; } let href; @@ -28,8 +28,8 @@ export function getComputedLinkData( href = `/${locale}/content/${link.post_data.slug}`; } - if(!href){ - href = '#' + if (!href) { + href = "#"; } return { href, label }; @@ -49,3 +49,5 @@ export function loadScript(url: string) { head.appendChild(script); }); } +export const gtmEvent = (target: string) => + window.gtag?.("event", target, { event_category: "engagement" });