feat: add contact message email template and localization support

- Introduced a new email template for contact messages, including localization for English and Chinese.
- Updated the send-message action to utilize the new contact message template and added locale support.
- Enhanced the contact form validation schema and improved code formatting for consistency.
This commit is contained in:
javayhu 2025-04-06 17:28:31 +08:00
parent 345528e4b3
commit a2849a9758
8 changed files with 67 additions and 41 deletions

View File

@ -532,6 +532,12 @@
"subscribeNewsletter": {
"body": "Thank you for subscribing to the newsletter. We will keep you updated with the latest news and updates.",
"subject": "Thanks for subscribing"
},
"contactMessage": {
"name": "Name: {name}",
"email": "Email: {email}",
"message": "Message: {message}",
"subject": "Contact Message from Website"
}
}
}

View File

@ -531,6 +531,12 @@
"subscribeNewsletter": {
"body": "感谢您订阅我们的邮件列表,我们将为您提供最新的新闻和更新。",
"subject": "感谢您的订阅"
},
"contactMessage": {
"name": "姓名: {name}",
"email": "邮箱: {email}",
"message": "消息: {message}",
"subject": "来自网站的联系消息"
}
}
}

View File

@ -2,6 +2,7 @@
import { websiteConfig } from '@/config';
import { send } from '@/mail';
import { getLocale } from 'next-intl/server';
import { createSafeActionClient } from 'next-safe-action';
import { z } from 'zod';
@ -9,20 +10,17 @@ import { z } from 'zod';
const actionClient = createSafeActionClient();
/**
* TODO: When using Zod for validation, how can I localize error messages?
* DOC: When using Zod for validation, how can I localize error messages?
* https://next-intl.dev/docs/environments/actions-metadata-route-handlers#server-actions
*/
// Contact form schema for validation
const contactFormSchema = z.object({
name: z
.string()
name: z.string()
.min(3, { message: 'Name must be at least 3 characters' })
.max(30, { message: 'Name must not exceed 30 characters' }),
email: z
.string()
email: z.string()
.email({ message: 'Please enter a valid email address' }),
message: z
.string()
message: z.string()
.min(10, { message: 'Message must be at least 10 characters' })
.max(500, { message: 'Message must not exceed 500 characters' }),
});
@ -40,20 +38,18 @@ export const sendMessageAction = actionClient
throw new Error('The mail receiver is not set');
}
// Send email using the mail service
// Customize the email template for your needs
// TODO: add locale to the email or customize it by yourself?
const locale = await getLocale();
// Send message as an email to admin
const result = await send({
to: websiteConfig.mail.to,
subject: `Contact Form: Message from ${name}`,
text: '',
html: `
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> ${email}</p>
<br />
<p><strong>Message:</strong></p>
<p>${message.replace(/\n/g, '<br />')}</p>
`,
template: 'contactMessage',
context: {
name,
email,
message,
},
locale,
});
if (!result) {

View File

@ -0,0 +1,34 @@
import { defaultMessages } from '@/i18n/messages';
import { routing } from '@/i18n/routing';
import EmailLayout from '@/mail/components/email-layout';
import type { BaseEmailProps } from '@/mail/types';
import { Text } from '@react-email/components';
import { createTranslator } from 'use-intl/core';
interface ContactMessageProps extends BaseEmailProps {
name: string;
email: string;
message: string;
}
export function ContactMessage({ name, email, message, locale, messages }: ContactMessageProps) {
const t = createTranslator({ locale, messages, });
return (
<EmailLayout locale={locale} messages={messages}>
<Text>{t('Mail.contactMessage.name', { name })}</Text>
<Text>{t('Mail.contactMessage.email', { email })}</Text>
<Text>{t('Mail.contactMessage.message', { message })}</Text>
</EmailLayout>
);
}
ContactMessage.PreviewProps = {
locale: routing.defaultLocale,
messages: defaultMessages,
name: 'username',
email: 'username@example.com',
message: 'This is a test message',
};
export default ContactMessage;

View File

@ -11,23 +11,13 @@ interface ForgotPasswordProps extends BaseEmailProps {
name: string;
}
export function ForgotPassword({
url,
name,
locale,
messages,
}: ForgotPasswordProps) {
const t = createTranslator({
locale,
messages,
});
export function ForgotPassword({ url, name, locale, messages, }: ForgotPasswordProps) {
const t = createTranslator({ locale, messages, });
return (
<EmailLayout locale={locale} messages={messages}>
<Text>{t('Mail.forgotPassword.title', { name })}</Text>
<Text>{t('Mail.forgotPassword.body')}</Text>
<EmailButton href={url}>
{t('Mail.forgotPassword.resetPassword')}
</EmailButton>

View File

@ -1,6 +1,7 @@
import { VerifyEmail } from './verify-email';
import { ForgotPassword } from './forgot-password';
import { SubscribeNewsletter } from './subscribe-newsletter';
import { ContactMessage } from './contact-message';
/**
* list all the email templates here
@ -9,4 +10,5 @@ export const EmailTemplates = {
forgotPassword: ForgotPassword,
verifyEmail: VerifyEmail,
subscribeNewsletter: SubscribeNewsletter,
contactMessage: ContactMessage,
} as const;

View File

@ -9,10 +9,7 @@ interface SubscribeNewsletterProps extends BaseEmailProps {
}
export function SubscribeNewsletter({ locale, messages }: SubscribeNewsletterProps) {
const t = createTranslator({
locale,
messages,
});
const t = createTranslator({ locale, messages, });
return (
<EmailLayout locale={locale} messages={messages}>

View File

@ -12,17 +12,12 @@ interface VerifyEmailProps extends BaseEmailProps {
}
export function VerifyEmail({ url, name, locale, messages }: VerifyEmailProps) {
const t = createTranslator({
locale,
messages,
});
const t = createTranslator({ locale, messages, });
return (
<EmailLayout locale={locale} messages={messages}>
<Text>{t('Mail.verifyEmail.title', { name })}</Text>
<Text>{t('Mail.verifyEmail.body')}</Text>
<EmailButton href={url}>{t('Mail.verifyEmail.confirmEmail')}</EmailButton>
</EmailLayout>
);