B2B-88: add starter kit structure and elements

This commit is contained in:
devmc-ee
2025-06-08 16:18:30 +03:00
parent 657a36a298
commit e7b25600cb
1280 changed files with 77893 additions and 5688 deletions

View File

@@ -0,0 +1,161 @@
'use client';
import { useState, useTransition } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Button } from '@kit/ui/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { Textarea } from '@kit/ui/textarea';
import { Trans } from '@kit/ui/trans';
import { ContactEmailSchema } from '~/(marketing)/contact/_lib/contact-email.schema';
import { sendContactEmail } from '~/(marketing)/contact/_lib/server/server-actions';
export function ContactForm() {
const [pending, startTransition] = useTransition();
const [state, setState] = useState({
success: false,
error: false,
});
const form = useForm({
resolver: zodResolver(ContactEmailSchema),
defaultValues: {
name: '',
email: '',
message: '',
},
});
if (state.success) {
return <SuccessAlert />;
}
if (state.error) {
return <ErrorAlert />;
}
return (
<Form {...form}>
<form
className={'flex flex-col space-y-4'}
onSubmit={form.handleSubmit((data) => {
startTransition(async () => {
try {
await sendContactEmail(data);
setState({ success: true, error: false });
} catch {
setState({ error: true, success: false });
}
});
})}
>
<FormField
name={'name'}
render={({ field }) => {
return (
<FormItem>
<FormLabel>
<Trans i18nKey={'marketing:contactName'} />
</FormLabel>
<FormControl>
<Input maxLength={200} {...field} />
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
name={'email'}
render={({ field }) => {
return (
<FormItem>
<FormLabel>
<Trans i18nKey={'marketing:contactEmail'} />
</FormLabel>
<FormControl>
<Input type={'email'} {...field} />
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
name={'message'}
render={({ field }) => {
return (
<FormItem>
<FormLabel>
<Trans i18nKey={'marketing:contactMessage'} />
</FormLabel>
<FormControl>
<Textarea
className={'min-h-36'}
maxLength={5000}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<Button disabled={pending} type={'submit'}>
<Trans i18nKey={'marketing:sendMessage'} />
</Button>
</form>
</Form>
);
}
function SuccessAlert() {
return (
<Alert variant={'success'}>
<AlertTitle>
<Trans i18nKey={'marketing:contactSuccess'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'marketing:contactSuccessDescription'} />
</AlertDescription>
</Alert>
);
}
function ErrorAlert() {
return (
<Alert variant={'destructive'}>
<AlertTitle>
<Trans i18nKey={'marketing:contactError'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'marketing:contactErrorDescription'} />
</AlertDescription>
</Alert>
);
}

View File

@@ -0,0 +1,7 @@
import { z } from 'zod';
export const ContactEmailSchema = z.object({
name: z.string().min(1).max(200),
email: z.string().email(),
message: z.string().min(1).max(5000),
});

View File

@@ -0,0 +1,51 @@
'use server';
import { z } from 'zod';
import { getMailer } from '@kit/mailers';
import { enhanceAction } from '@kit/next/actions';
import { ContactEmailSchema } from '../contact-email.schema';
const contactEmail = z
.string({
description: `The email where you want to receive the contact form submissions.`,
required_error:
'Contact email is required. Please use the environment variable CONTACT_EMAIL.',
})
.parse(process.env.CONTACT_EMAIL);
const emailFrom = z
.string({
description: `The email sending address.`,
required_error:
'Sender email is required. Please use the environment variable EMAIL_SENDER.',
})
.parse(process.env.EMAIL_SENDER);
export const sendContactEmail = enhanceAction(
async (data) => {
const mailer = await getMailer();
await mailer.sendEmail({
to: contactEmail,
from: emailFrom,
subject: 'Contact Form Submission',
html: `
<p>
You have received a new contact form submission.
</p>
<p>Name: ${data.name}</p>
<p>Email: ${data.email}</p>
<p>Message: ${data.message}</p>
`,
});
return {};
},
{
schema: ContactEmailSchema,
auth: false,
},
);

View File

@@ -0,0 +1,54 @@
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
import { ContactForm } from '~/(marketing)/contact/_components/contact-form';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
export async function generateMetadata() {
const { t } = await createI18nServerInstance();
return {
title: t('marketing:contact'),
};
}
async function ContactPage() {
const { t } = await createI18nServerInstance();
return (
<div>
<SitePageHeader
title={t(`marketing:contact`)}
subtitle={t(`marketing:contactDescription`)}
/>
<div className={'container mx-auto'}>
<div
className={'flex flex-1 flex-col items-center justify-center py-12'}
>
<div
className={
'flex w-full max-w-lg flex-col space-y-4 rounded-lg border p-8'
}
>
<div>
<Heading level={3}>
<Trans i18nKey={'marketing:contactHeading'} />
</Heading>
<p className={'text-muted-foreground'}>
<Trans i18nKey={'marketing:contactSubheading'} />
</p>
</div>
<ContactForm />
</div>
</div>
</div>
</div>
);
}
export default withI18n(ContactPage);