From 93a7331ddf7cfabf38dd9fbf3358a96634610258 Mon Sep 17 00:00:00 2001
From: brennerthomas
Date: Tue, 3 Dec 2024 21:48:55 +0100
Subject: [PATCH] Web: Solving newsletter subscription issues (#939)
---
functions/src/webhooks/stripe/index.ts | 12 ++----
shared/emails/transactional/welcome-2-fr.html | 10 ++---
shared/emails/transactional/welcome-3-fr.html | 6 +--
shared/emails/transactional/welcome-4-fr.html | 5 ++-
.../sendgrid/SendgridSubscriptionClient.ts | 5 +--
website/.env.development | 7 +++-
.../newsletter/subscription-info-form.tsx | 34 +++++++++++------
.../newsletter/subscription/public/route.ts | 38 +++++++++++++++----
.../app/api/newsletter/subscription/route.ts | 14 +++----
9 files changed, 80 insertions(+), 51 deletions(-)
diff --git a/functions/src/webhooks/stripe/index.ts b/functions/src/webhooks/stripe/index.ts
index 0ee7e1ce4..3ec3f21c3 100644
--- a/functions/src/webhooks/stripe/index.ts
+++ b/functions/src/webhooks/stripe/index.ts
@@ -3,11 +3,7 @@ import { logger } from 'firebase-functions';
import { onRequest } from 'firebase-functions/v2/https';
import Stripe from 'stripe';
import { FirestoreAdmin } from '../../../../shared/src/firebase/admin/FirestoreAdmin';
-import {
- NEWSLETTER_LIST_ID,
- NEWSLETTER_SUPPRESSION_LIST_ID,
- SendgridSubscriptionClient,
-} from '../../../../shared/src/sendgrid/SendgridSubscriptionClient';
+import { SendgridSubscriptionClient } from '../../../../shared/src/sendgrid/SendgridSubscriptionClient';
import { StripeEventHandler } from '../../../../shared/src/stripe/StripeEventHandler';
import { Contribution } from '../../../../shared/src/types/contribution';
import { CountryCode } from '../../../../shared/src/types/country';
@@ -17,12 +13,12 @@ import { STRIPE_API_READ_KEY, STRIPE_WEBHOOK_SECRET } from '../../config';
const addContributorToNewsletter = async (contributionRef: DocumentReference) => {
const newsletterClient = new SendgridSubscriptionClient({
apiKey: process.env.SENDGRID_API_KEY!,
- listId: NEWSLETTER_LIST_ID,
- suppressionListId: NEWSLETTER_SUPPRESSION_LIST_ID,
+ listId: process.env.SENDGRID_LIST_ID!,
+ suppressionListId: parseInt(process.env.SENDGRID_SUPPRESSION_LIST_ID!),
});
const user = (await contributionRef.parent.parent?.get()) as DocumentSnapshot;
logger.info(
- `Adding contributor ${user.id} (${user.get('email')}) to Sendgrid newsletter list (${NEWSLETTER_LIST_ID}).`,
+ `Adding contributor ${user.id} (${user.get('email')}) to Sendgrid newsletter list (${process.env.SENDGRID_LIST_ID}).`,
);
await newsletterClient.upsertSubscription({
firstname: user.get('personal.name'),
diff --git a/shared/emails/transactional/welcome-2-fr.html b/shared/emails/transactional/welcome-2-fr.html
index 45b036c66..14d652002 100644
--- a/shared/emails/transactional/welcome-2-fr.html
+++ b/shared/emails/transactional/welcome-2-fr.html
@@ -242,7 +242,7 @@
class="feature"
style="margin: 0 0 12px; font-size: 50px; font-weight: 500; line-height: 60px; color: #191a19"
>
- Agir contre la pauvreté. Qui le fera, sinon nous?
+ Agir contre la pauvreté. À nous de jouer!
@@ -286,7 +286,7 @@
"
>
Faut-il aider les personnes qui vivent dans la pauvreté?
- Voici quelques raisons personnelles de le faire:
+ Personnellement j'ai quelques bonnes raisons de le faire:
@@ -320,7 +320,7 @@
class="feature"
style="color: #191a19; font-family: 'Unica77', Helvetica, Arial, sans-serif; font-weight: 400"
>
- Avoir conscience de la pauvreté globale n’aide personne à y échapper.
@@ -369,8 +369,8 @@
Ne laissons pas les gens sous la pluie
+ >Ne laissons personne sous la pluie
+
si nous pouvons faire autrement.
diff --git a/shared/emails/transactional/welcome-3-fr.html b/shared/emails/transactional/welcome-3-fr.html
index 967243b61..6ce040476 100644
--- a/shared/emails/transactional/welcome-3-fr.html
+++ b/shared/emails/transactional/welcome-3-fr.html
@@ -420,13 +420,13 @@
bénévoles internationaux
- entier qui communiquent, codent et lèvent des fonds d’un peu
+ qui communiquent, codent et lèvent des fonds.
partout dans le monde.Nous sommes tous convaincus
- Nous sommes tous convaincus qu’aider des personnes dans le besoin est une responsabilité sociale.
+ qu’aider des personnes dans le besoin est une responsabilité sociale.
diff --git a/shared/emails/transactional/welcome-4-fr.html b/shared/emails/transactional/welcome-4-fr.html
index c5951f109..3322b4270 100644
--- a/shared/emails/transactional/welcome-4-fr.html
+++ b/shared/emails/transactional/welcome-4-fr.html
@@ -242,7 +242,8 @@
class="feature"
style="margin: 0 0 12px; font-size: 50px; font-weight: 500; line-height: 60px; color: #191a19"
>
- Si ce n’est pas maintenant, alors quand?
+ Le moment d'agir,
+ c'est maintenant!
@@ -349,7 +350,7 @@
color: #191a19;
"
>
- Nous garantissons que votre don parviendra
+ Nous garantissons que ton don parviendra
aux personnes qui en ont le plus besoin. Afin de pouvoir nous
consacrer entièrement à notre mission en minimisant les coûts liés à la collecte de fonds, nous
t’incitons à donner 1% de ton salaire de manière récurrente. Tu peux interrompre tes versements à tout
diff --git a/shared/src/sendgrid/SendgridSubscriptionClient.ts b/shared/src/sendgrid/SendgridSubscriptionClient.ts
index 565d7f4a8..7d09cde39 100644
--- a/shared/src/sendgrid/SendgridSubscriptionClient.ts
+++ b/shared/src/sendgrid/SendgridSubscriptionClient.ts
@@ -3,14 +3,11 @@ import { SendgridContactType } from '@socialincome/shared/src/sendgrid/types';
import { CountryCode } from '../types/country';
import { Suppression } from './types';
-export const NEWSLETTER_LIST_ID = '2896ee4d-d1e0-4a4a-8565-7e592c377e36';
-export const NEWSLETTER_SUPPRESSION_LIST_ID = 45634;
-
export type NewsletterSubscriptionData = {
firstname?: string;
lastname?: string;
email: string;
- language: 'de' | 'en';
+ language: 'de' | 'en' | 'fr' | 'it';
country?: CountryCode;
status?: 'subscribed' | 'unsubscribed';
isContributor?: boolean;
diff --git a/website/.env.development b/website/.env.development
index b26b9b13d..6833321ca 100644
--- a/website/.env.development
+++ b/website/.env.development
@@ -13,4 +13,9 @@ 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"
\ No newline at end of file
+BASE_URL="http://localhost:3001"
+
+SENDGRID_LIST_ID="2896ee4d-d1e0-4a4a-8565-7e592c377e36"
+SENDGRID_SUPPRESSION_LIST_ID=45634
+SENDGRID_API_KEY="SG.Q****"
+
diff --git a/website/src/app/[lang]/[region]/(website)/newsletter/subscription-info-form.tsx b/website/src/app/[lang]/[region]/(website)/newsletter/subscription-info-form.tsx
index f8c8ff37a..f611521da 100644
--- a/website/src/app/[lang]/[region]/(website)/newsletter/subscription-info-form.tsx
+++ b/website/src/app/[lang]/[region]/(website)/newsletter/subscription-info-form.tsx
@@ -1,10 +1,12 @@
'use client';
import { DefaultParams } from '@/app/[lang]/[region]';
+import { SpinnerIcon } from '@/components/logos/spinner-icon';
import { useApi } from '@/hooks/useApi';
import { zodResolver } from '@hookform/resolvers/zod';
import { NewsletterSubscriptionData } from '@socialincome/shared/src/sendgrid/SendgridSubscriptionClient';
import { Button, Form, FormControl, FormField, FormItem, FormLabel, FormMessage, Input } from '@socialincome/ui';
+import { useState } from 'react';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import * as z from 'zod';
@@ -21,6 +23,7 @@ type PersonalInfoFormProps = {
export function SubscriptionInfoForm({ lang, translations }: PersonalInfoFormProps) {
const api = useApi();
+ const [isSubmitting, setIsSubmitting] = useState(false);
const formSchema = z.object({
firstname: z.string(),
@@ -37,20 +40,27 @@ export function SubscriptionInfoForm({ lang, translations }: PersonalInfoFormPro
});
const onSubmit = async (values: FormSchema) => {
+ setIsSubmitting(true);
const data: NewsletterSubscriptionData = {
firstname: values.firstname,
email: values.email,
- language: lang === 'de' ? 'de' : 'en',
+ language: lang === 'de' ? 'de' : lang === 'fr' ? 'fr' : lang === 'it' ? 'it' : 'en',
status: 'subscribed',
};
- api.post('/api/newsletter/subscription/public', data).then((response) => {
+ try {
+ const response = await api.post('/api/newsletter/subscription/public', data);
if (response.status === 200) {
toast.success(translations.toastMessage);
+ form.reset();
} else {
toast.error(translations.toastErrorMessage + '(' + response.statusText + ')');
}
- });
+ } catch (error) {
+ toast.error(translations.toastErrorMessage);
+ } finally {
+ setIsSubmitting(false);
+ }
};
return (
@@ -82,15 +92,15 @@ export function SubscriptionInfoForm({ lang, translations }: PersonalInfoFormPro
)}
/>
-
+ {isSubmitting ? (
+
+
+
+ ) : (
+
+ )}
);
diff --git a/website/src/app/api/newsletter/subscription/public/route.ts b/website/src/app/api/newsletter/subscription/public/route.ts
index 752239e44..c136752b4 100644
--- a/website/src/app/api/newsletter/subscription/public/route.ts
+++ b/website/src/app/api/newsletter/subscription/public/route.ts
@@ -1,20 +1,44 @@
import {
- NEWSLETTER_LIST_ID,
- NEWSLETTER_SUPPRESSION_LIST_ID,
NewsletterSubscriptionData,
SendgridSubscriptionClient,
} from '@socialincome/shared/src/sendgrid/SendgridSubscriptionClient';
export type CreateNewsletterSubscription = Omit;
-type CreateNewsletterSubscriptionReqeust = { json(): Promise } & Request;
+type CreateNewsletterSubscriptionRequest = { json(): Promise } & Request;
-export async function POST(request: CreateNewsletterSubscriptionReqeust) {
+type SendgridClientProps = {
+ SENDGRID_API_KEY: string;
+ SENDGRID_LIST_ID: string;
+ SENDGRID_SUPPRESSION_LIST_ID: number;
+};
+
+const validateSendgridClientProps = (): SendgridClientProps => {
+ const suppressionListId = parseInt(process.env.SENDGRID_SUPPRESSION_LIST_ID!);
+ if (isNaN(suppressionListId)) {
+ throw new Error('SENDGRID_SUPPRESSION_LIST_ID must be a valid number');
+ }
+
+ const sendgridClientProps: SendgridClientProps = {
+ SENDGRID_API_KEY: process.env.SENDGRID_API_KEY!,
+ SENDGRID_LIST_ID: process.env.SENDGRID_LIST_ID!,
+ SENDGRID_SUPPRESSION_LIST_ID: suppressionListId,
+ };
+
+ Object.entries(sendgridClientProps).forEach(([key, value]) => {
+ if (!value) throw new Error(`Missing required environment variable: ${key}`);
+ });
+ return sendgridClientProps;
+};
+
+export async function POST(request: CreateNewsletterSubscriptionRequest) {
const data = await request.json();
+ const sendgridClientProps: SendgridClientProps = validateSendgridClientProps();
const sendgrid = new SendgridSubscriptionClient({
- apiKey: process.env.SENDGRID_API_KEY!,
- listId: NEWSLETTER_LIST_ID,
- suppressionListId: NEWSLETTER_SUPPRESSION_LIST_ID,
+ apiKey: sendgridClientProps.SENDGRID_API_KEY!,
+ listId: sendgridClientProps.SENDGRID_LIST_ID!,
+ suppressionListId: sendgridClientProps.SENDGRID_SUPPRESSION_LIST_ID,
});
+
try {
await sendgrid.upsertSubscription({ ...data, status: 'subscribed' });
return new Response(null, { status: 200 });
diff --git a/website/src/app/api/newsletter/subscription/route.ts b/website/src/app/api/newsletter/subscription/route.ts
index 5bcc709c1..59a70ba96 100644
--- a/website/src/app/api/newsletter/subscription/route.ts
+++ b/website/src/app/api/newsletter/subscription/route.ts
@@ -1,9 +1,5 @@
import { authorizeRequest, handleApiError } from '@/app/api/auth';
-import {
- NEWSLETTER_LIST_ID,
- NEWSLETTER_SUPPRESSION_LIST_ID,
- SendgridSubscriptionClient,
-} from '@socialincome/shared/src/sendgrid/SendgridSubscriptionClient';
+import { SendgridSubscriptionClient } from '@socialincome/shared/src/sendgrid/SendgridSubscriptionClient';
import { NextResponse } from 'next/server';
/**
@@ -14,8 +10,8 @@ export async function GET(request: Request) {
const userDoc = await authorizeRequest(request);
const sendgrid = new SendgridSubscriptionClient({
apiKey: process.env.SENDGRID_API_KEY!,
- listId: NEWSLETTER_LIST_ID,
- suppressionListId: NEWSLETTER_SUPPRESSION_LIST_ID,
+ listId: process.env.SENDGRID_LIST_ID!,
+ suppressionListId: parseInt(process.env.SENDGRID_SUPPRESSION_LIST_ID!),
});
const subscriber = await sendgrid.getContact(userDoc.get('email'));
return NextResponse.json(subscriber);
@@ -37,8 +33,8 @@ export async function POST(request: NewsletterSubscriptionUpdateRequest) {
const data = await request.json();
const sendgrid = new SendgridSubscriptionClient({
apiKey: process.env.SENDGRID_API_KEY!,
- listId: NEWSLETTER_LIST_ID,
- suppressionListId: NEWSLETTER_SUPPRESSION_LIST_ID,
+ listId: process.env.SENDGRID_LIST_ID!,
+ suppressionListId: parseInt(process.env.SENDGRID_SUPPRESSION_LIST_ID!),
});
await sendgrid.upsertSubscription({
firstname: userDoc.get('personal.name'),