summaryrefslogtreecommitdiff
path: root/src/components/newsletter-form.tsx
diff options
context:
space:
mode:
authorBertrand Yuan <bert.yuan@outlook.com>2025-12-15 23:48:10 +0800
committerBertrand Yuan <bert.yuan@outlook.com>2025-12-15 23:48:10 +0800
commit5b7ccf0b671e2999b62befc729a3e517a0433728 (patch)
tree8bf476dc7c75914c221042546840dc76267366df /src/components/newsletter-form.tsx
initial commit -- the front-end prototype
The initial code is base on Anirudh's work. More to see at: https://github.com/techwithanirudh/shadcn-blog Therefore, the code in this commit is under MIT license.
Diffstat (limited to 'src/components/newsletter-form.tsx')
-rw-r--r--src/components/newsletter-form.tsx96
1 files changed, 96 insertions, 0 deletions
diff --git a/src/components/newsletter-form.tsx b/src/components/newsletter-form.tsx
new file mode 100644
index 0000000..7dd0cc0
--- /dev/null
+++ b/src/components/newsletter-form.tsx
@@ -0,0 +1,96 @@
+'use client';
+
+import { useAction } from 'next-safe-action/hooks';
+
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import type { Newsletter } from '@/lib/validators';
+import { NewsletterSchema } from '@/lib/validators';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+
+import { Alert, AlertTitle } from '@/components/ui/alert';
+
+import { subscribeUser } from '@/app/(home)/actions';
+import { Icons } from '@/components/icons/icons';
+
+export const NewsletterForm = () => {
+ const form = useForm({
+ resolver: zodResolver(NewsletterSchema),
+ defaultValues: {
+ email: '',
+ },
+ });
+
+ const { execute, result, status } = useAction(subscribeUser);
+
+ const onSubmit = (values: Newsletter) => {
+ execute(values);
+ };
+
+ return (
+ <Form {...form}>
+ <form onSubmit={form.handleSubmit(onSubmit)} className='flex-1 space-y-4'>
+ <div className='flex h-full min-h-10 overflow-hidden rounded-md border bg-muted p-0'>
+ <div className='flex-1'>
+ <FormField
+ control={form.control}
+ name='email'
+ render={({ field }) => (
+ <FormItem className='group h-full'>
+ <FormControl className='h-full group-has-[p]:pt-3'>
+ <Input
+ {...field}
+ disabled={status === 'executing'}
+ placeholder='Email address'
+ type='email'
+ className='h-full rounded-md rounded-r-none border-none px-4 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0'
+ />
+ </FormControl>
+ <FormMessage className='ml-4 pb-2 text-xs' />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ <Button
+ disabled={status === 'executing'}
+ type='submit'
+ size='icon'
+ className='group size-auto w-15 rounded-md rounded-l-none px-3'
+ >
+ {status === 'executing' ? (
+ <Icons.spinner className='size-4 animate-spin' />
+ ) : (
+ <Icons.send className='group-hover:-rotate-45 size-4 transition-transform' />
+ )}
+ </Button>
+ </div>
+
+ {status === 'hasSucceeded' && (
+ <Alert className='border-emerald-500/15 bg-emerald-500/15 p-3 px-3 py-2 text-emerald-500 has-[>svg]:gap-x-1.5'>
+ <Icons.success size={16} />
+ <AlertTitle className='mb-0 leading-normal'>
+ {result.data?.message ?? "Hmm... Our server didn't respond."}
+ </AlertTitle>
+ </Alert>
+ )}
+ {result.serverError && (
+ <Alert className='border-destructive/15 bg-destructive/15 p-3 px-3 py-2 text-destructive has-[>svg]:gap-x-1.5 dark:border-destructive dark:bg-destructive dark:text-destructive-foreground'>
+ <Icons.warning className='size-4' />
+ <AlertTitle className='mb-0 leading-normal'>
+ {result.serverError}
+ </AlertTitle>
+ </Alert>
+ )}
+ </form>
+ </Form>
+ );
+};