feat: implement newsletter subscription and unsubscription actions
- Added `subscribeAction` and `unsubscribeAction` in `newsletter.ts` to handle newsletter subscriptions and unsubscriptions with validation using Zod. - Introduced new localization messages for email validation in English and Chinese localization files. - Updated `WaitlistFormCard` to utilize the new subscription action, improving user feedback with toast notifications. - Replaced the alert icon in `FormError` component for better visual representation of errors.
This commit is contained in:
parent
9b221f4583
commit
9ddf1a3c20
@ -72,6 +72,7 @@
|
||||
"formTitle": "Join Our Waitlist",
|
||||
"formDescription": "We will send you only one email every week, and no spam",
|
||||
"email": "Email",
|
||||
"emailValidation": "Please enter a valid email address",
|
||||
"subscribe": "Subscribe",
|
||||
"subscribing": "Subscribing...",
|
||||
"success": "Subscribed successfully",
|
||||
|
@ -66,6 +66,7 @@
|
||||
"formTitle": "加入我们的邮件列表",
|
||||
"formDescription": "我们每周只发送一封邮件,并且不会发送垃圾邮件",
|
||||
"email": "邮箱",
|
||||
"emailValidation": "请输入有效的邮箱地址",
|
||||
"subscribe": "订阅",
|
||||
"subscribing": "订阅中...",
|
||||
"success": "订阅成功",
|
||||
|
68
src/actions/newsletter.ts
Normal file
68
src/actions/newsletter.ts
Normal file
@ -0,0 +1,68 @@
|
||||
'use server';
|
||||
|
||||
import {
|
||||
subscribe as subscribeToNewsletter,
|
||||
unsubscribe as unsubscribeFromNewsletter
|
||||
} from '@/newsletter';
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Create a safe action client
|
||||
const actionClient = createSafeActionClient();
|
||||
|
||||
// Newsletter schema for validation
|
||||
const newsletterSchema = z.object({
|
||||
email: z.string().email({ message: 'Please enter a valid email address' }),
|
||||
});
|
||||
|
||||
// Create a safe action for newsletter subscription
|
||||
export const subscribeAction = actionClient
|
||||
.schema(newsletterSchema)
|
||||
.action(async ({ parsedInput: { email } }) => {
|
||||
try {
|
||||
const subscribed = await subscribeToNewsletter(email);
|
||||
|
||||
if (!subscribed) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to subscribe to the newsletter',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Newsletter subscription error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'An unexpected error occurred',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Create a safe action for newsletter unsubscription
|
||||
export const unsubscribeAction = actionClient
|
||||
.schema(newsletterSchema)
|
||||
.action(async ({ parsedInput: { email } }) => {
|
||||
try {
|
||||
const unsubscribed = await unsubscribeFromNewsletter(email);
|
||||
|
||||
if (!unsubscribed) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to unsubscribe from the newsletter',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Newsletter unsubscription error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'An unexpected error occurred',
|
||||
};
|
||||
}
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import { TriangleAlertIcon } from 'lucide-react';
|
||||
import { BugIcon } from 'lucide-react';
|
||||
|
||||
interface FormErrorProps {
|
||||
message?: string;
|
||||
@ -9,7 +9,7 @@ export const FormError = ({ message }: FormErrorProps) => {
|
||||
|
||||
return (
|
||||
<div className="bg-destructive/15 p-3 rounded-md flex items-center gap-x-2 text-sm text-destructive">
|
||||
<TriangleAlertIcon className="h-4 w-4" />
|
||||
<BugIcon className="h-4 w-4" />
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { subscribeAction } from '@/actions/newsletter';
|
||||
import { FormError } from '@/components/shared/form-error';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@ -39,7 +40,7 @@ export function WaitlistFormCard() {
|
||||
const formSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.email({ message: 'Please enter a valid email address' }),
|
||||
.email({ message: t('emailValidation') }),
|
||||
});
|
||||
|
||||
// Initialize the form
|
||||
@ -52,21 +53,21 @@ export function WaitlistFormCard() {
|
||||
|
||||
// Handle form submission
|
||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
setIsSubmitting(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
// Here you would typically send the form data to your API
|
||||
console.log('Form submitted:', values);
|
||||
setError('');
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Simulate API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const result = await subscribeAction({
|
||||
email: values.email,
|
||||
});
|
||||
|
||||
// Show success message
|
||||
toast.success(t('success'));
|
||||
|
||||
// Reset form
|
||||
form.reset();
|
||||
if (result?.data?.success) {
|
||||
toast.success(t('success'));
|
||||
form.reset();
|
||||
} else {
|
||||
setError(t('fail'));
|
||||
toast.error(t('fail'));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Form submission error:', err);
|
||||
setError(t('fail'));
|
||||
|
3
src/newsletter/index.ts
Normal file
3
src/newsletter/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// export the subscribe and unsubscribe functions
|
||||
export { subscribe } from './newsletter';
|
||||
export { unsubscribe } from './newsletter';
|
11
src/newsletter/newsletter.ts
Normal file
11
src/newsletter/newsletter.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { subscribeNewsletter, unsubscribeNewsletter } from './provider/resend';
|
||||
|
||||
export const subscribe = async (email: string) => {
|
||||
const subscribed = await subscribeNewsletter({ email });
|
||||
return subscribed;
|
||||
};
|
||||
|
||||
export const unsubscribe = async (email: string) => {
|
||||
const unsubscribed = await unsubscribeNewsletter({ email });
|
||||
return unsubscribed;
|
||||
};
|
51
src/newsletter/provider/resend.ts
Normal file
51
src/newsletter/provider/resend.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { SubscribeNewsletterHandler, UnsubscribeNewsletterHandler } from '@/newsletter/types';
|
||||
import { Resend } from 'resend';
|
||||
|
||||
const apiKey = process.env.RESEND_API_KEY || 'test_api_key';
|
||||
const audienceId = process.env.RESEND_AUDIENCE_ID || 'test_audience_id';
|
||||
|
||||
const resend = new Resend(apiKey);
|
||||
|
||||
export const subscribeNewsletter: SubscribeNewsletterHandler = async ({ email }) => {
|
||||
if (!process.env.RESEND_API_KEY || !process.env.RESEND_AUDIENCE_ID) {
|
||||
console.warn('RESEND_API_KEY or RESEND_AUDIENCE_ID not set, skipping subscribe newsletter');
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await resend.contacts.create({
|
||||
email,
|
||||
audienceId,
|
||||
unsubscribed: false,
|
||||
});
|
||||
const subscribed = !result.error;
|
||||
|
||||
if (!subscribed) {
|
||||
console.error('Error subscribing newsletter', result.error);
|
||||
return false;
|
||||
} else {
|
||||
console.log('Subscribed newsletter', email);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export const unsubscribeNewsletter: UnsubscribeNewsletterHandler = async ({ email }) => {
|
||||
if (!process.env.RESEND_API_KEY || !process.env.RESEND_AUDIENCE_ID) {
|
||||
console.warn('RESEND_API_KEY or RESEND_AUDIENCE_ID not set, skipping unsubscribe newsletter');
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await resend.contacts.update({
|
||||
email,
|
||||
audienceId,
|
||||
unsubscribed: true,
|
||||
});
|
||||
const unsubscribed = !result.error;
|
||||
|
||||
if (!unsubscribed) {
|
||||
console.error('Error unsubscribing newsletter', result.error);
|
||||
return false;
|
||||
} else {
|
||||
console.log('Unsubscribed newsletter', email);
|
||||
return true;
|
||||
}
|
||||
};
|
19
src/newsletter/types.ts
Normal file
19
src/newsletter/types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export interface SubscribeNewsletterProps {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface UnsubscribeNewsletterProps {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export type SubscribeNewsletterHandler = (params: SubscribeNewsletterProps) => Promise<boolean>;
|
||||
|
||||
export type UnsubscribeNewsletterHandler = (params: UnsubscribeNewsletterProps) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Newsletter provider, currently only Resend is supported
|
||||
*/
|
||||
export interface NewsletterProvider {
|
||||
subscribe: SubscribeNewsletterHandler;
|
||||
unsubscribe: UnsubscribeNewsletterHandler;
|
||||
}
|
Loading…
Reference in New Issue
Block a user