diff --git a/_data/seo/newsletter.yml b/_data/seo/newsletter.yml new file mode 100644 index 0000000000..9c95505f4d --- /dev/null +++ b/_data/seo/newsletter.yml @@ -0,0 +1,5 @@ +title: Starknet Newsletter ✨🗞️ +subtitle: Stay up to date with the latest news and updates from the Starknet ecosystem. +description: Sign up to receive the latest news and updates from the Starknet ecosystem. + We'll send you emails with the latest news, updates, and resources + from the Starknet ecosystem. diff --git a/public/robots.txt b/public/robots.txt index e4fe782829..d80444785f 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -7,3 +7,5 @@ Disallow: /fr/* Disallow: /ja/* Disallow: /ko/* Disallow: /pt/* +Disallow: /*/subscribe-newsletter +Disallow: /subscribe-newsletter diff --git a/workspaces/cms-config/src/collections/seo.ts b/workspaces/cms-config/src/collections/seo.ts index c62eee9de7..91e160d823 100644 --- a/workspaces/cms-config/src/collections/seo.ts +++ b/workspaces/cms-config/src/collections/seo.ts @@ -149,6 +149,31 @@ export const SEOCollectionConfig = { crowdin: true, }, ], + }, { + label: "Starknet newsletter", + name: "newsletter", + file: `_data/seo/newsletter.yml`, + crowdin: true, + fields: [ + { + label: "Title", + name: "title", + widget: "string", + crowdin: true, + }, + { + label: "Sub Title", + name: "subtitle", + widget: "string", + crowdin: true, + }, + { + label: "Description", + name: "description", + widget: "string", + crowdin: true, + } + ], }, ], } satisfies CmsCollection; diff --git a/workspaces/cms-data/src/seo/index.ts b/workspaces/cms-data/src/seo/index.ts index 2b0f0f39bb..cdc0b0f809 100644 --- a/workspaces/cms-data/src/seo/index.ts +++ b/workspaces/cms-data/src/seo/index.ts @@ -21,6 +21,11 @@ export interface SEOTexts { subtitle: string; description: string; }; + newsletter: { + title: string; + subtitle: string; + description: string; + }; search: { search: string; cancel: string; diff --git a/workspaces/cms-scripts/src/index.ts b/workspaces/cms-scripts/src/index.ts index 8c83c41c20..6f07c48d3a 100644 --- a/workspaces/cms-scripts/src/index.ts +++ b/workspaces/cms-scripts/src/index.ts @@ -87,6 +87,7 @@ const createSharedData = async () => { "jobs", "search", "language", + "newsletter" ]; for (const locale of locales) { @@ -160,6 +161,7 @@ const simpleFiles = [ await getSimpleFiles("seo", "footer", true), await getSimpleFiles("seo", "home", true), await getSimpleFiles("seo", "language", true), + await getSimpleFiles("seo", "newsletter", true), await getSimpleFiles("seo", "search", true), ]; @@ -278,4 +280,4 @@ const redirects = await yaml("_data/settings/redirects.yml"); await write(`workspaces/website/redirects.json`, redirects); await createRoadmapDetails() await createAnnouncementDetails() -await createSharedData() \ No newline at end of file +await createSharedData() diff --git a/workspaces/website/src/pages/subscribe-newsletter/NewsletterPage.tsx b/workspaces/website/src/pages/subscribe-newsletter/NewsletterPage.tsx new file mode 100644 index 0000000000..9e15eca2d0 --- /dev/null +++ b/workspaces/website/src/pages/subscribe-newsletter/NewsletterPage.tsx @@ -0,0 +1,145 @@ + +/** + * Module dependencies. + */ + +import { + Box, + Button, + FormControl, + FormLabel, + Input, + useToast +} 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"; + +/** + * `Props` type. + */ + +export interface Props { + readonly env: { + readonly CLOUDFLARE_RECAPTCHA_KEY: string; + }; + readonly seo: SEOTexts['newsletter']; +} + +/** + * 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 + }); + } + } + + 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 new file mode 100644 index 0000000000..44c13a6602 --- /dev/null +++ b/workspaces/website/src/pages/subscribe-newsletter/index.page.server.ts @@ -0,0 +1,34 @@ +/** + * Module dependencies + */ + +import { PageContextServer } from "src/renderer/types"; +import { Props } from "src/pages/subscribe-newsletter/NewsletterPage"; +import { getDefaultPageContext } from "src/renderer/helpers"; + +/** + * Export `onBeforeRender` function. + */ + +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, + env: { + CLOUDFLARE_RECAPTCHA_KEY: import.meta.env.VITE_CLOUDFLARE_RECAPTCHA_KEY + }, + params: { + locale, + }, + }; + + return { + pageContext: { + ...defaultPageContext, + pageProps, + }, + }; +} diff --git a/workspaces/website/src/pages/subscribe-newsletter/index.page.tsx b/workspaces/website/src/pages/subscribe-newsletter/index.page.tsx new file mode 100644 index 0000000000..ecceb592dd --- /dev/null +++ b/workspaces/website/src/pages/subscribe-newsletter/index.page.tsx @@ -0,0 +1,20 @@ +/** + * Module dependencies. + */ + +import { DocumentProps } from "src/renderer/types"; + +/** + * Export `Page` component + */ + +export { NewsletterPage as Page } from "src/pages/subscribe-newsletter/NewsletterPage"; + +/** + * Export `documentProps`. + */ + +export const documentProps = { + title: "Starknet Newsletter", + description: "Subscribe to the Starknet newsletter to stay up to date with the latest news and developments from the Starknet team." +} satisfies DocumentProps