diff --git a/_data/pages/home.yml b/_data/pages/home.yml index 0a120d6df7..16c053a349 100644 --- a/_data/pages/home.yml +++ b/_data/pages/home.yml @@ -52,6 +52,9 @@ blocks: operates as a Validity-rollup (or ZK-rollup): it "bundles" many transactions and "rolls" them to Ethereum as a single transaction.' columns: "4" + - title: "Starknet newsletter. " + type: newsletter_popup + description: "Sign up to receive the latest news and updates from the Starknet ecosystem. " - type: flex_layout heading_variant: h3 base: 1 diff --git a/workspaces/cms-config/src/blocks.ts b/workspaces/cms-config/src/blocks.ts index aac44961db..1a98203e15 100644 --- a/workspaces/cms-config/src/blocks.ts +++ b/workspaces/cms-config/src/blocks.ts @@ -721,8 +721,25 @@ export const blocks = [ fields: videoChapterFields } ] - }, - { + }, { + name: "newsletter_popup", + label: "Newsletter Popup", + widget: "object", + fields: [ + { + crowdin: true, + label: 'Title', + name: 'title', + widget: 'string' + }, + { + crowdin: true, + label: 'Description', + name: 'description', + widget: 'string' + }, + ] + }, { name: "ordered_block", label: "Ordered Block", widget: "object", diff --git a/workspaces/cms-data/src/pages.ts b/workspaces/cms-data/src/pages.ts index e075ded407..2dd60c14a1 100644 --- a/workspaces/cms-data/src/pages.ts +++ b/workspaces/cms-data/src/pages.ts @@ -160,6 +160,12 @@ export interface VideoSectionBlock { readonly 'eth-settlement': ChapterInfo; } +export interface NewsletterBlock { + readonly type: "newsletter_popup"; + readonly title: string; + readonly description: string; +} + export type HeadingVariant = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; export type Block = @@ -175,7 +181,8 @@ export type Block = | OrderedBlock | ListCardItemsBlock | AmbassadorsListBlock - | VideoSectionBlock; + | VideoSectionBlock + | NewsletterBlock; export interface Container { readonly type: "container"; diff --git a/workspaces/website/src/blocks/Block.tsx b/workspaces/website/src/blocks/Block.tsx index e2c915d567..636e3da7d3 100644 --- a/workspaces/website/src/blocks/Block.tsx +++ b/workspaces/website/src/blocks/Block.tsx @@ -19,13 +19,17 @@ import { useAsync } from "react-streaming"; import { usePageContext } from "src/renderer/PageContextProvider"; import { HeadingContainer } from "./HeadingContainer"; import VideoSectionBlock from "./VideoSectionBlock"; +import { NewsletterCard } from "@ui/Card/NewsletterCard"; interface Props { readonly block: TopLevelBlock; + env: { + CLOUDFLARE_RECAPTCHA_KEY: string; + } readonly locale: string; } -export function Block({ block, locale }: Props): JSX.Element | null { +export function Block({ block, env, locale }: Props): JSX.Element | null { if (block.type === "basic_card") { return ; } else if (block.type === "container") { @@ -33,6 +37,7 @@ export function Block({ block, locale }: Props): JSX.Element | null { {block.blocks.map((block, i) => ( ; + } else if (block.type === "newsletter_popup") { + return ; } else if (block.type === "markdown") { return ; } else if (block.type === "ambassadors_list") { @@ -66,6 +73,7 @@ export function Block({ block, locale }: Props): JSX.Element | null { > {block.blocks.map((block, i) => ( {block.blocks.map((block, i) => ( {block.blocks.map((block, i) => ( void; }; type titleVariantType = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; @@ -54,11 +55,12 @@ export const ImageIconCard = ({ withIllustration = false, variant = "image_icon_link_card", columns = 4, - orientation = "left" + orientation = "left", + onClick }: Props) => { - const { href = 'test', label } = getComputedLinkData(locale, link ?? { - custom_title: 'dsa', - custom_internal_link: 'asd' + const { href , label } = getComputedLinkData(locale, link ?? { + custom_title: '', + custom_internal_link: '' }); let titleVariant; let descriptionVariant; @@ -101,9 +103,11 @@ export const ImageIconCard = ({ descriptionVariant = size === "large" ? "body" : "cardBody"; linkVariant = size === "large" ? "cardLink" : "smallCardLink"; } + return ( {description}{" "} - {variant === "community_card" && ( + {variant === "community_card" && href !== '#' && ( { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> + + + setIsOpen(true)} + orientation={'left'} + size={'large'} + title={title} + variant={'community_card'} + locale={locale} + withIllustration={false} + /> + + ); +}; diff --git a/workspaces/website/src/pages/(components)/CMSPage.tsx b/workspaces/website/src/pages/(components)/CMSPage.tsx index 2c46f905d9..48635175f4 100644 --- a/workspaces/website/src/pages/(components)/CMSPage.tsx +++ b/workspaces/website/src/pages/(components)/CMSPage.tsx @@ -17,17 +17,21 @@ import { blocksToTOC } from "./TableOfContents/blocksToTOC"; type CMSPageProps = { data: PageType; + env: { + CLOUDFLARE_RECAPTCHA_KEY: string; + } locale: string; }; export default function CMSPage({ data, + env, locale, }: CMSPageProps) { const date = data?.gitlog?.date; return ( {data.breadcrumbs && @@ -87,6 +91,7 @@ export default function CMSPage({ {data.blocks?.map((block, i) => { return ( (null); + const captchaRef = useRef(null); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setFormState('submitting'); + + try { + const token = captchaRef.current?.getResponse(); + + await axios.post(`/api/newsletter-subscribe?${qs.stringify({ + email: (event.target as any)[0].value, + token, + })}`) + + captchaRef.current?.reset(); + setFormState('success'); + } catch (error: any) { + setFormState(null); + captchaRef.current?.reset(); + + const toastErrorConfig = { + duration: 1500, + isClosable: true, + status: 'error' as 'error', + }; + + if(error.response.data?.title === 'Invalid Captcha') { + toast({ + description: 'We\'re having trouble verifying you\'re a human. Please try again.', + ...toastErrorConfig + }); + + return; + } + + if(error.response.data?.title === 'Member Exists') { + toast({ + description: 'You are already subscribed to the newsletter.', + ...toastErrorConfig + }); + + return; + } + + toast({ + description: 'There was an issue subscribing you to the newsletter.', + ...toastErrorConfig + }); + } + } + + return ( + <> + {!hideHeader && ( + <> + + Starknet Dev News ✨🗞️ + + + + Receive notifications on Starknet version updates and network status. + + + )} + + {formState !== 'success' ? ( +
+ + + Email + + + + + + + + + + ) : ( +
+ + Thank you and may the STARK be with you ✨🗞️ + +
+ )} + + ) +}; diff --git a/workspaces/website/src/pages/(components)/roadmap/RoadmapSubscribeForm.tsx b/workspaces/website/src/pages/(components)/roadmap/RoadmapSubscribeForm.tsx index 4be498e7c3..f4c69ad63a 100644 --- a/workspaces/website/src/pages/(components)/roadmap/RoadmapSubscribeForm.tsx +++ b/workspaces/website/src/pages/(components)/roadmap/RoadmapSubscribeForm.tsx @@ -2,25 +2,15 @@ * Module dependencies. */ -import { Button } from '@ui/Button'; import { - FormControl, - FormLabel, - Input, Modal, ModalOverlay, ModalContent, ModalCloseButton, Container, - useToast, } from '@chakra-ui/react'; -import { Heading } from '@ui/Typography/Heading'; -import { Text } from '@ui/Typography/Text'; -import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile' -import { useRef, useState } from 'react'; -import axios from 'axios'; -import qs from 'qs'; +import { NewsletterForm } from './NewsletterForm'; /** * `RoadmapSubscribeForm` props. @@ -43,63 +33,9 @@ function RoadmapSubscribeForm({ isOpen, setIsOpen, }: RoadmapSubscribeFormProps) { - const toast = useToast(); - const [formState, setFormState] = useState<'submitting' | 'success' | null>(null); - const captchaRef = useRef(null); - const handleClose = () => { setIsOpen(false); - setFormState(null); }; - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - setFormState('submitting'); - - try { - const token = captchaRef.current?.getResponse(); - - await axios.post(`/api/newsletter-subscribe?${qs.stringify({ - email: (event.target as any)[0].value, - token, - })}`) - - captchaRef.current?.reset(); - setFormState('success'); - } catch (error: any) { - setFormState(null); - captchaRef.current?.reset(); - - const toastErrorConfig = { - duration: 1500, - isClosable: true, - status: 'error' as 'error', - }; - - if(error.response.data?.title === 'Invalid Captcha') { - toast({ - description: 'We\'re having trouble verifying you\'re a human. Please try again.', - ...toastErrorConfig - }); - - return; - } - - if(error.response.data?.title === 'Member Exists') { - toast({ - description: 'You are already subscribed to the newsletter.', - ...toastErrorConfig - }); - - return; - } - - toast({ - description: 'There was an issue subscribing you to the newsletter.', - ...toastErrorConfig - }); - } - } return ( @@ -114,67 +50,7 @@ function RoadmapSubscribeForm({ - {formState !== 'success' ? ( - <> - - Starknet Dev News ✨🗞️ - - - - Receive notifications on Starknet version updates and network status. - - -
- - - Email - - - - - - - - - - - ) : ( -
- - Thank you and may the STARK be with you ✨🗞️ - -
- )} +
diff --git a/workspaces/website/src/pages/PagePage.tsx b/workspaces/website/src/pages/PagePage.tsx index 6004b995c4..14009d5d62 100644 --- a/workspaces/website/src/pages/PagePage.tsx +++ b/workspaces/website/src/pages/PagePage.tsx @@ -4,16 +4,19 @@ import CMSPage from "./(components)/CMSPage"; export interface Props { readonly data: PageType; + env: { + CLOUDFLARE_RECAPTCHA_KEY: string; + } } -export default function Page({ - data, -}: Props): JSX.Element { +export default function Page(props: Props): JSX.Element { const { locale } = usePageContext(); + const { data, env } = props; return ( ); diff --git a/workspaces/website/src/pages/pages.page.server.tsx b/workspaces/website/src/pages/pages.page.server.tsx index cac605417a..5c0cc6fc79 100644 --- a/workspaces/website/src/pages/pages.page.server.tsx +++ b/workspaces/website/src/pages/pages.page.server.tsx @@ -8,10 +8,16 @@ export async function onBeforeRender(pageContext: PageContextServer) { const locale = pageContext.locale ?? defaultLocale; const slug = pageContext.routeParams["*"] || "home"; const data = await getPageBySlug(slug, locale, pageContext.context); + const env = { + CLOUDFLARE_RECAPTCHA_KEY: import.meta.env.VITE_CLOUDFLARE_RECAPTCHA_KEY, + }; return { pageContext: { - pageProps: { data } satisfies Props, + pageProps: { + data, + env + } satisfies Props, documentProps: { title: slug == "home" ? undefined : data.title, } satisfies DocumentProps, diff --git a/workspaces/website/src/pages/pages.page.tsx b/workspaces/website/src/pages/pages.page.tsx index c214a470d0..27fa859cb5 100644 --- a/workspaces/website/src/pages/pages.page.tsx +++ b/workspaces/website/src/pages/pages.page.tsx @@ -1,10 +1,10 @@ import PagePage, { Props } from "src/pages/PagePage"; import NotFound from "@ui/NotFound/NotFound"; -export function Page({ data }: Props): JSX.Element { +export function Page({ data, env }: Props): JSX.Element { if (data == null) { return ; } - return ; + return ; } diff --git a/workspaces/website/src/pages/subscribe-newsletter/NewsletterPage.tsx b/workspaces/website/src/pages/subscribe-newsletter/NewsletterPage.tsx index 9e15eca2d0..161536e5ad 100644 --- a/workspaces/website/src/pages/subscribe-newsletter/NewsletterPage.tsx +++ b/workspaces/website/src/pages/subscribe-newsletter/NewsletterPage.tsx @@ -3,22 +3,10 @@ * Module dependencies. */ -import { - Box, - Button, - FormControl, - FormLabel, - Input, - useToast -} from "@chakra-ui/react"; - +import { Box } from "@chakra-ui/react"; import { PageLayout } from "@ui/Layout/PageLayout"; import { SEOTexts } from "@starknet-io/cms-data/src/seo"; -import { Text } from '@ui/Typography/Text'; -import { Turnstile, TurnstileInstance } from "@marsidev/react-turnstile"; -import { useRef, useState } from "react"; -import axios from "axios"; -import qs from "qs"; +import { NewsletterForm } from "../(components)/roadmap/NewsletterForm"; /** * `Props` type. @@ -35,109 +23,17 @@ export interface Props { * Export `NewsletterPage` component. */ -export function NewsletterPage({ env, seo }: Props): JSX.Element | null { - const toast = useToast(); - const [formState, setFormState] = useState<'submitting' | 'success' | null>(null); - const captchaRef = useRef(null); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - setFormState('submitting'); - - try { - const token = captchaRef.current?.getResponse(); - - await axios.post(`/api/newsletter-subscribe?${qs.stringify({ - email: (event.target as any)[0].value, - token, - })}`) - - captchaRef.current?.reset(); - setFormState('success'); - } catch (error: any) { - setFormState(null); - captchaRef.current?.reset(); - - const toastErrorConfig = { - duration: 1500, - isClosable: true, - status: 'error' as 'error', - }; - - if(error.response.data?.title === 'Invalid Captcha') { - toast({ - description: 'We\'re having trouble verifying you\'re a human. Please try again.', - ...toastErrorConfig - }); - - return; - } - - if(error.response.data?.title === 'Member Exists') { - toast({ - description: 'You are already subscribed to the newsletter.', - ...toastErrorConfig - }); - - return; - } - - toast({ - description: 'There was an issue subscribing you to the newsletter.', - ...toastErrorConfig - }); - } - } - +export function NewsletterPage({ env, seo }: Props) { return ( - {formState !== 'success' ? ( -
- - - Email - - - - - - - - - - ) : ( -
- - Thank you and may the STARK be with you ✨🗞️ - -
- )} +
} /> diff --git a/workspaces/website/src/pages/subscribe-newsletter/index.page.server.ts b/workspaces/website/src/pages/subscribe-newsletter/index.page.server.ts index 44c13a6602..f3e9647526 100644 --- a/workspaces/website/src/pages/subscribe-newsletter/index.page.server.ts +++ b/workspaces/website/src/pages/subscribe-newsletter/index.page.server.ts @@ -13,7 +13,6 @@ import { getDefaultPageContext } from "src/renderer/helpers"; export async function onBeforeRender(pageContext: PageContextServer) { const defaultPageContext = await getDefaultPageContext(pageContext); const { locale } = defaultPageContext; - console.log(defaultPageContext.seo) const pageProps: Props = { seo: defaultPageContext.seo.newsletter,