Skip to content

Commit

Permalink
Web: Solving newsletter subscription issues (#939)
Browse files Browse the repository at this point in the history
  • Loading branch information
brennerthomas authored Dec 3, 2024
1 parent 892b5fc commit 93a7331
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 51 deletions.
12 changes: 4 additions & 8 deletions functions/src/webhooks/stripe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,12 +13,12 @@ import { STRIPE_API_READ_KEY, STRIPE_WEBHOOK_SECRET } from '../../config';
const addContributorToNewsletter = async (contributionRef: DocumentReference<Contribution>) => {
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<User>;
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'),
Expand Down
10 changes: 5 additions & 5 deletions shared/emails/transactional/welcome-2-fr.html
Original file line number Diff line number Diff line change
Expand Up @@ -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é. <span style="color: #01579b">Qui le fera, sinon nous?</span>
Agir contre la pauvreté. <span style="color: #01579b">À nous de jouer!</span>
</h1>
</td>
</tr>
Expand Down Expand Up @@ -286,7 +286,7 @@
"
>
Faut-il aider les personnes qui vivent dans la pauvreté?
<span style="color: #01579b">Voici quelques raisons personnelles de le faire:</span>
<span style="color: #01579b">Personnellement j'ai quelques bonnes raisons de le faire:</span>
</p>
</td>
</tr>
Expand Down Expand Up @@ -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 naide personne à y échapper.</span
Le seul fait d'être conscient de la pauvreté globale n'aide personne à y échapper.</span
>
</li>
<li>
Expand Down Expand Up @@ -369,8 +369,8 @@
<span
class="feature"
style="color: #01579b; font-family: 'Unica77', Helvetica, Arial, sans-serif; font-weight: 500"
>Ne laissons pas les gens sous la pluie</span
>
>Ne laissons personne sous la pluie
</span>
si nous pouvons faire autrement.
</p>
</td>
Expand Down
6 changes: 3 additions & 3 deletions shared/emails/transactional/welcome-3-fr.html
Original file line number Diff line number Diff line change
Expand Up @@ -420,13 +420,13 @@
<a href="https://socialincome.org/about-us#team" target="_blank" rel="noopener noreferrer"
>bénévoles internationaux</a
>
entier qui communiquent, codent et lèvent des fonds d’un peu
qui communiquent, codent et lèvent des fonds.
<span
class="feature"
style="color: #01579b; font-family: 'Unica77', Helvetica, Arial, sans-serif; font-weight: 400"
>partout dans le monde.</span
>Nous sommes tous convaincus</span
>
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.
</p>
</td>
</tr>
Expand Down
5 changes: 3 additions & 2 deletions shared/emails/transactional/welcome-4-fr.html
Original file line number Diff line number Diff line change
Expand Up @@ -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, <span style="color: #01579b">alors quand?</span>
Le moment d'agir,
<span style="color: #01579b">c'est maintenant!</span>
</h1>
</td>
</tr>
Expand Down Expand Up @@ -349,7 +350,7 @@
color: #191a19;
"
>
Nous garantissons que votre don parviendra
Nous garantissons que ton don parviendra
<span style="color: #01579b">aux personnes qui en ont le plus besoin.</span> 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
Expand Down
5 changes: 1 addition & 4 deletions shared/src/sendgrid/SendgridSubscriptionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion website/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -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"
BASE_URL="http://localhost:3001"

SENDGRID_LIST_ID="2896ee4d-d1e0-4a4a-8565-7e592c377e36"
SENDGRID_SUPPRESSION_LIST_ID=45634
SENDGRID_API_KEY="SG.Q****"

Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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(),
Expand All @@ -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 (
Expand Down Expand Up @@ -82,15 +92,15 @@ export function SubscriptionInfoForm({ lang, translations }: PersonalInfoFormPro
</FormItem>
)}
/>
<Button
type="submit"
size="lg"
showLoadingSpinner={form.formState.isSubmitting}
color="accent"
className="mt-4 rounded-full"
>
{translations.submitButton}
</Button>
{isSubmitting ? (
<div key="spinner" className="mt-4 flex justify-center">
<SpinnerIcon />
</div>
) : (
<Button key="button" type="submit" size="lg" color="accent" className="mt-4 rounded-full">
{translations.submitButton}
</Button>
)}
</form>
</Form>
);
Expand Down
38 changes: 31 additions & 7 deletions website/src/app/api/newsletter/subscription/public/route.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
import {
NEWSLETTER_LIST_ID,
NEWSLETTER_SUPPRESSION_LIST_ID,
NewsletterSubscriptionData,
SendgridSubscriptionClient,
} from '@socialincome/shared/src/sendgrid/SendgridSubscriptionClient';

export type CreateNewsletterSubscription = Omit<NewsletterSubscriptionData, 'status'>;
type CreateNewsletterSubscriptionReqeust = { json(): Promise<CreateNewsletterSubscription> } & Request;
type CreateNewsletterSubscriptionRequest = { json(): Promise<CreateNewsletterSubscription> } & 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 });
Expand Down
14 changes: 5 additions & 9 deletions website/src/app/api/newsletter/subscription/route.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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);
Expand All @@ -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'),
Expand Down

0 comments on commit 93a7331

Please sign in to comment.