Skip to content

Commit

Permalink
feature(website): newsletter popup (#778)
Browse files Browse the repository at this point in the history
  • Loading branch information
brennerthomas authored Mar 24, 2024
1 parent b03114d commit 26b968c
Show file tree
Hide file tree
Showing 29 changed files with 250 additions and 76 deletions.
7 changes: 7 additions & 0 deletions shared/locales/de/website-newsletter.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,12 @@
"submit-button": "Beim Newsletter anmelden",
"newsletter-updated-toast": "Du wurdest zum Newsletter hinzugefügt",
"newsletter-error-toast": "Du konntest leider nicht zum Newsletter hinzugefügt werden"
},
"popup": {
"information-label": "Melde dich für unseren Newsletter an und werde Teil der Social Income Community!",
"toast-success": "Danke für das Abonnieren des Social Income Newsletters!",
"toast-failure": "Oops! Etwas ist schief gelaufen. Bitte versuche es nochmals, oder kontaktiere uns auf [email protected]",
"email-placeholder": "Deine E-Mail Adresse",
"button-subscribe": "Abonnieren"
}
}
7 changes: 7 additions & 0 deletions shared/locales/en/website-newsletter.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,12 @@
"submit-button": "Subscribe to newsletter",
"newsletter-updated-toast": "You have been added to the newsletter",
"newsletter-error-toast": "We weren't able to add you to our newsletter"
},
"popup": {
"information-label": "Join our newsletter and become part of the Social Income Community!",
"toast-success": "Thank you for subscribing to our newsletter!",
"toast-failure": "Something went wrong. Try again or contact us at [email protected]",
"email-placeholder": "Your email",
"button-subscribe": "Subscribe"
}
}
6 changes: 3 additions & 3 deletions shared/src/mailchimp/MailchimpAPI.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import mailchimp, { Status } from '@mailchimp/mailchimp_marketing';
import { CountryCode } from '../types/country';

export type MailchimpSubscriptionData = {
export type NewsletterSubscriptionData = {
email: string;
status: Status;
language?: 'de' | 'en';
Expand All @@ -26,7 +26,7 @@ export class MailchimpAPI {
}
};

upsertSubscription = async (data: MailchimpSubscriptionData, listId: string) => {
upsertSubscription = async (data: NewsletterSubscriptionData, listId: string) => {
const subscriber = await this.getSubscriber(data.email, listId);
if (subscriber === null) {
return this.createSubscription(data, listId);
Expand All @@ -44,7 +44,7 @@ export class MailchimpAPI {
}
};

createSubscription = async (data: MailchimpSubscriptionData, listId: string) => {
createSubscription = async (data: NewsletterSubscriptionData, listId: string) => {
await mailchimp.lists.addListMember(listId, {
email_address: data.email,
status: data.status,
Expand Down
21 changes: 21 additions & 0 deletions website/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# These environment variables are required so that the Firebase Admin SDK (server side) connects to the local emulator.
GCLOUD_PROJECT="social-income-staging"
FIRESTORE_EMULATOR_HOST=127.0.0.1:8080
FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099

# These environment variables are required so that the Firebase JS SDK (client side) connects to the local emulator.
NEXT_PUBLIC_FIREBASE_API_KEY="demo-key"
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN="social-income-staging"
NEXT_PUBLIC_FIREBASE_PROJECT_ID="social-income-staging"
NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_URL="http://localhost:9099"
NEXT_PUBLIC_FIREBASE_FIRESTORE_EMULATOR_HOST="localhost"
NEXT_PUBLIC_FIREBASE_FIRESTORE_EMULATOR_PORT="8080"
NEXT_PUBLIC_FIREBASE_FUNCTIONS_EMULATOR_HOST="localhost"
NEXT_PUBLIC_FIREBASE_FUNCTIONS_EMULATOR_PORT="5001"

BASE_URL="http://localhost:3001"

MAILCHIMP_LIST_ID="11223344"
MAILCHIMP_API_KEY="ABC*************"
MAILCHIMP_SERVER="us11"

2 changes: 2 additions & 0 deletions website/src/app/[lang]/[region]/(website)/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Sdg } from '@/app/[lang]/[region]/(website)/(home)/(sections)/sdg';
import { SwissSection } from '@/app/[lang]/[region]/(website)/(home)/(sections)/swiss-section';
import { ThreeApproaches } from '@/app/[lang]/[region]/(website)/(home)/(sections)/three-approaches';
import { WhatWouldChange } from '@/app/[lang]/[region]/(website)/(home)/(sections)/what-would-change';
import NewsletterPopup from '@/components/newsletter-popup/newsletter-popup';
import { Translator } from '@socialincome/shared/src/utils/i18n';
import { Video } from './(sections)/video';

Expand All @@ -30,6 +31,7 @@ export default async function Page({ params: { lang, region } }: DefaultPageProp
<ThreeApproaches lang={lang} />
<Sdg lang={lang} />
<Recognized lang={lang} />
<NewsletterPopup lang={lang} region={region} delay={5000} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DefaultPageProps } from '@/app/[lang]/[region]';
import NewsletterPopup from '@/components/newsletter-popup/newsletter-popup';
import { getMetadata } from '@/metadata';
import { Translator } from '@socialincome/shared/src/utils/i18n';
import { BaseContainer, Button, Typography } from '@socialincome/ui';
Expand Down Expand Up @@ -45,11 +46,12 @@ export default async function Page({ params: { lang, region } }: DefaultPageProp
<Typography size="xl" lineHeight="normal" className="border-t pt-8">
{translator.t('difference')}
</Typography>
<Link href={`${lang}/${region}/donate/individual`}>
<Link href={`/${lang}/${region}/donate/individual`}>
<Button size="lg" className="w-full">
{translator.t('take-action')}
</Button>
</Link>
<NewsletterPopup lang={lang} region={region} delay={5000} />
</BaseContainer>
);
}
12 changes: 6 additions & 6 deletions website/src/app/[lang]/[region]/(website)/me/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,31 +84,31 @@ export const useDonationCertificates = () => {
};

// Mailchimp
export const useMailchimpSubscription = () => {
export const useNewsletterSubscription = () => {
const api = useApi();
const {
data: status,
isLoading,
isRefetching,
error,
} = useQuery<string | null>({
queryKey: ['me', 'mailchimp'],
queryKey: ['me', 'newsletter'],
queryFn: async () => {
const response = await api.get('/api/mailchimp/subscription');
const response = await api.get('/api/newsletter/subscription');
return (await response.json()).status;
},
staleTime: 3600000, // 1 hour
});
return { status, loading: isLoading || isRefetching, error };
};

export const useUpsertMailchimpSubscription = () => {
export const useUpsertNewsletterSubscription = () => {
const api = useApi();
const queryClient = useQueryClient();

return async (status: Status) => {
const response = await api.post('/api/mailchimp/subscription', { status });
await queryClient.invalidateQueries({ queryKey: ['me', 'mailchimp'] });
const response = await api.post('/api/newsletter/subscription', { status });
await queryClient.invalidateQueries({ queryKey: ['me', 'newsletter'] });
return response;
};
};
Expand Down
37 changes: 17 additions & 20 deletions website/src/app/[lang]/[region]/(website)/me/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { DefaultLayoutProps } from '@/app/[lang]/[region]';
import { LayoutClient } from '@/app/[lang]/[region]/(website)/me/layout-client';
import { ApiProvider } from '@/components/providers/api-provider';
import { UserContextProvider } from '@/components/providers/user-context-provider';
import { getMetadata } from '@/metadata';
import { Translator } from '@socialincome/shared/src/utils/i18n';
Expand All @@ -17,25 +16,23 @@ export default async function Layout({ children, params }: PropsWithChildren<Def
return (
<BaseContainer className="pb-8">
<UserContextProvider>
<ApiProvider>
<LayoutClient
params={params}
translations={{
accountTitle: translator.t('sections.account.title'),
personalInfo: translator.t('sections.account.personal-info'),
security: translator.t('sections.account.security'),
contributionsTitle: translator.t('sections.contributions.title'),
payments: translator.t('sections.contributions.payments'),
subscriptions: translator.t('sections.contributions.subscriptions'),
donationCertificatesShort: translator.t('sections.contributions.donation-certificates-short'),
donationCertificatesLong: translator.t('sections.contributions.donation-certificates-long'),
employerTitle: translator.t('sections.employer.title'),
work: translator.t('sections.employer.work'),
}}
>
{children}
</LayoutClient>
</ApiProvider>
<LayoutClient
params={params}
translations={{
accountTitle: translator.t('sections.account.title'),
personalInfo: translator.t('sections.account.personal-info'),
security: translator.t('sections.account.security'),
contributionsTitle: translator.t('sections.contributions.title'),
payments: translator.t('sections.contributions.payments'),
subscriptions: translator.t('sections.contributions.subscriptions'),
donationCertificatesShort: translator.t('sections.contributions.donation-certificates-short'),
donationCertificatesLong: translator.t('sections.contributions.donation-certificates-long'),
employerTitle: translator.t('sections.employer.title'),
work: translator.t('sections.employer.work'),
}}
>
{children}
</LayoutClient>
</UserContextProvider>
</BaseContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import { DefaultParams } from '@/app/[lang]/[region]';
import {
useMailchimpSubscription,
useUpsertMailchimpSubscription,
useNewsletterSubscription,
useUpsertNewsletterSubscription,
useUser,
} from '@/app/[lang]/[region]/(website)/me/hooks';
import { useTranslator } from '@/hooks/useTranslator';
Expand Down Expand Up @@ -60,8 +60,8 @@ export function PersonalInfoForm({ lang, translations }: PersonalInfoFormProps)
const queryClient = useQueryClient();
const commonTranslator = useTranslator(lang, 'common');
const countryTranslator = useTranslator(lang, 'countries');
const { status, loading } = useMailchimpSubscription();
const upsertMailchimpSubscription = useUpsertMailchimpSubscription();
const { status, loading } = useNewsletterSubscription();
const upsertMailchimpSubscription = useUpsertNewsletterSubscription();

const formSchema = z.object({
firstname: z.string(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DefaultPageProps } from '@/app/[lang]/[region]';
import { SubscriptionInfoForm } from '@/app/[lang]/[region]/(website)/updates/subscription-info-form';
import { SubscriptionInfoForm } from '@/app/[lang]/[region]/(website)/newsletter/subscription-info-form';

import { Translator } from '@socialincome/shared/src/utils/i18n';
import { Alert, AlertDescription, AlertTitle } from '@socialincome/ui';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use client';

import { DefaultParams } from '@/app/[lang]/[region]';
import { useApi } from '@/hooks/useApi';
import { useTranslator } from '@/hooks/useTranslator';
import { zodResolver } from '@hookform/resolvers/zod';
import { MailchimpSubscriptionData } from '@socialincome/shared/src/mailchimp/MailchimpAPI';
import { NewsletterSubscriptionData } from '@socialincome/shared/src/mailchimp/MailchimpAPI';
import { COUNTRY_CODES } from '@socialincome/shared/src/types/country';
import {
Button,
Expand Down Expand Up @@ -41,6 +42,7 @@ type PersonalInfoFormProps = {
export function SubscriptionInfoForm({ lang, translations }: PersonalInfoFormProps) {
const commonTranslator = useTranslator(lang, 'common');
const countryTranslator = useTranslator(lang, 'countries');
const api = useApi();

const formSchema = z.object({
firstname: z.string(),
Expand All @@ -63,16 +65,16 @@ export function SubscriptionInfoForm({ lang, translations }: PersonalInfoFormPro
});

const onSubmit = async (values: FormSchema) => {
const data: MailchimpSubscriptionData = {
const data: NewsletterSubscriptionData = {
firstname: values.firstname,
lastname: values.lastname,
email: values.email,
country: values.country as any,
language: values.language,
status: 'subscribed',
};
// Call the API to change Mailchimp subscription
fetch('/api/mailchimp/subscription/public', { method: 'POST', body: JSON.stringify(data) }).then((response) => {

api.post('/api/newsletter/subscription/public', data).then((response) => {
if (response.status === 200) {
toast.success(translations.toastMessage);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useI18n } from '@/app/context-providers';
import { useI18n } from '@/components/providers/context-providers';
import { WebsiteCurrency } from '@/i18n';
import { redirect } from 'next/navigation';
import { useEffect } from 'react';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useI18n } from '@/app/context-providers';
import { useI18n } from '@/components/providers/context-providers';
import { redirect } from 'next/navigation';
import { useEffect } from 'react';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default async function Page({ params: { lang, region } }: DefaultPageProp
<Typography size="xl" lineHeight="normal" className="border-t pt-8">
{translator.t('difference')}
</Typography>
<Link href={`${lang}/${region}/donate/individual`}>
<Link href={`/${lang}/${region}/donate/individual`}>
<Button size="lg" className="w-full">
{translator.t('take-action')}
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DefaultPageProps } from '@/app/[lang]/[region]';
import NewsletterPopup from '@/components/newsletter-popup/newsletter-popup';
import { getMetadata } from '@/metadata';
import { Translator } from '@socialincome/shared/src/utils/i18n';
import { BaseContainer, Button, Typography } from '@socialincome/ui';
Expand All @@ -7,6 +8,7 @@ import Link from 'next/link';
export async function generateMetadata({ params }: DefaultPageProps) {
return getMetadata(params.lang, 'website-poverty-statistics-2024');
}

export default async function Page({ params: { lang, region } }: DefaultPageProps) {
const translator = await Translator.getInstance({
language: lang,
Expand Down Expand Up @@ -45,11 +47,12 @@ export default async function Page({ params: { lang, region } }: DefaultPageProp
<Typography size="xl" lineHeight="normal" className="border-t pt-8">
{translator.t('difference')}
</Typography>
<Link href={`${lang}/${region}/donate/individual`}>
<Link href={`/${lang}/${region}/donate/individual`}>
<Button size="lg" className="w-full">
{translator.t('take-action')}
</Button>
</Link>
<NewsletterPopup lang={lang} region={region} delay={5000} />
</BaseContainer>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { DefaultParams } from '@/app/[lang]/[region]';
import { CreateCheckoutSessionData } from '@/app/api/stripe/checkout-session/create/route';
import { useI18n } from '@/app/context-providers';
import { useI18n } from '@/components/providers/context-providers';
import { CurrencySelector } from '@/components/ui/currency-selector';
import { useTranslator } from '@/hooks/useTranslator';
import { websiteCurrencies, WebsiteLanguage } from '@/i18n';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { DefaultParams } from '@/app/[lang]/[region]';
import { CreateCheckoutSessionData } from '@/app/api/stripe/checkout-session/create/route';
import { useI18n } from '@/app/context-providers';
import { useI18n } from '@/components/providers/context-providers';
import { CurrencySelector } from '@/components/ui/currency-selector';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button, Form, FormControl, FormField, FormItem, Input } from '@socialincome/ui';
Expand Down
15 changes: 0 additions & 15 deletions website/src/app/api/mailchimp/subscription/public/route.ts

This file was deleted.

16 changes: 16 additions & 0 deletions website/src/app/api/newsletter/subscription/public/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MailchimpAPI, NewsletterSubscriptionData } from '@socialincome/shared/src/mailchimp/MailchimpAPI';

export type CreateNewsletterSubscription = Omit<NewsletterSubscriptionData, 'status'>;
type CreateNewsletterSubscriptionReqeust = { json(): Promise<CreateNewsletterSubscription> } & Request;

export async function POST(request: CreateNewsletterSubscriptionReqeust) {
const data = await request.json();
const mailchimpAPI = new MailchimpAPI(process.env.MAILCHIMP_API_KEY!, process.env.MAILCHIMP_SERVER!);
try {
await mailchimpAPI.createSubscription({ ...data, status: 'subscribed' }, process.env.MAILCHIMP_LIST_ID!);
return new Response(null, { status: 200 });
} catch (e: any) {
console.error(e);
return new Response(null, { status: 405, statusText: e });
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { authorizeRequest, handleApiError } from '@/app/api/auth';
import { Body } from '@mailchimp/mailchimp_marketing';
import { MailchimpAPI } from '@socialincome/shared/src/mailchimp/MailchimpAPI';
import { NextResponse } from 'next/server';

Expand All @@ -20,9 +19,8 @@ export async function GET(request: Request) {
/**
* Upsert Mailchimp subscription
*/
export type MailchimpSubscriptionUpdate = { status: 'subscribed' | 'unsubscribed' } & Body;
type MailchimpSubscriptionUpdateRequest = { json(): Promise<MailchimpSubscriptionUpdate> } & Request;
export async function POST(request: MailchimpSubscriptionUpdateRequest) {
type NewsletterSubscriptionUpdateRequest = { json(): Promise<{ status: 'subscribed' | 'unsubscribed' }> } & Request;
export async function POST(request: NewsletterSubscriptionUpdateRequest) {
try {
const userDoc = await authorizeRequest(request);
const data = await request.json();
Expand Down
2 changes: 1 addition & 1 deletion website/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ContextProviders } from '@/app/context-providers';
import { ContextProviders } from '@/components/providers/context-providers';
import { getMetadata } from '@/metadata';
import { PropsWithChildren } from 'react';
import './globals.css';
Expand Down
Loading

0 comments on commit 26b968c

Please sign in to comment.