diff --git a/package.json b/package.json
index e878db2d4..11321d545 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"@floating-ui/core": "1.2.6",
"@headlessui-float/react": "0.11.2",
"@headlessui/react": "1.7.15",
+ "@hookform/resolvers": "^3.9.1",
"@mapbox/mapbox-gl-draw": "1.4.1",
"@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0",
@@ -37,6 +38,7 @@
"@radix-ui/react-dropdown-menu": "2.0.4",
"@radix-ui/react-hover-card": "^1.0.5",
"@radix-ui/react-icons": "^1.3.0",
+ "@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "1.0.5",
"@radix-ui/react-progress": "^1.0.2",
"@radix-ui/react-radio-group": "1.1.2",
@@ -46,6 +48,7 @@
"@radix-ui/react-switch": "^1.0.2",
"@radix-ui/react-toggle": "^1.0.2",
"@radix-ui/react-tooltip": "^1.0.5",
+ "@react-email/components": "^0.0.31",
"@tailwindcss/forms": "0.5.3",
"@tailwindcss/line-clamp": "0.4.2",
"@tailwindcss/typography": "0.5.9",
@@ -74,6 +77,8 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-dropzone": "14.2.3",
+ "react-email": "^3.0.4",
+ "react-hook-form": "^7.54.2",
"react-map-gl": "7.0.21",
"react-markdown": "^9.0.1",
"react-toastify": "^10.0.5",
@@ -81,13 +86,15 @@
"recharts": "^2.5.0",
"recoil": "^0.7.7",
"recoil-sync": "0.2.0",
+ "resend": "^4.0.1",
"shadcn-ui": "latest",
"tailwind-merge": "1.11.0",
"tailwind-scrollbar-hide": "1.1.7",
"tailwindcss": "3.2.7",
"tailwindcss-animate": "^1.0.5",
"use-debounce": "9.0.3",
- "usehooks-ts": "2.9.1"
+ "usehooks-ts": "2.9.1",
+ "zod": "^3.24.1"
},
"devDependencies": {
"@playwright/test": "^1.41.0",
diff --git a/src/components/contact/email-template.tsx b/src/components/contact/email-template.tsx
new file mode 100644
index 000000000..c4f6fb9c8
--- /dev/null
+++ b/src/components/contact/email-template.tsx
@@ -0,0 +1,52 @@
+import { Body, Container, Head, Html, Markdown, Preview, Text } from '@react-email/components';
+import { CSSProperties } from 'react';
+
+interface ContactUsEmailProps {
+ name: string;
+ email: string;
+ message: string;
+}
+
+export const ContactUsEmail = ({ name, email, message }: ContactUsEmailProps) => (
+
+
+
+ Hi {name},
+ We have received your message
+
+ {message}
+
+ We will get back to you as soon as possible at {email}.
+
+
+
+);
+
+const main: CSSProperties = {
+ backgroundColor: '#ffffff',
+ borderRadius: '8px',
+ border: '1px solid #e5e7eb',
+ boxShadow: '0 0 10px rgba(0, 0, 0, 0.05)',
+ fontFamily:
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
+};
+
+const container: CSSProperties = {
+ margin: '0 auto',
+ padding: '20px 0 48px',
+};
+
+const paragraph: CSSProperties = {
+ fontSize: '16px',
+ lineHeight: '26px',
+};
diff --git a/src/components/contact/index.tsx b/src/components/contact/index.tsx
new file mode 100644
index 000000000..baa4b75c9
--- /dev/null
+++ b/src/components/contact/index.tsx
@@ -0,0 +1,234 @@
+'use client';
+
+import { useCallback, useRef, useState } from 'react';
+
+import { useForm } from 'react-hook-form';
+import { HiCheck } from 'react-icons/hi2';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { z } from 'zod';
+
+// import subscribeNewsletter from '@/containers/newsletter/action';
+import cn from 'lib/classnames';
+import { HiChevronDown } from 'react-icons/hi';
+import { Button } from 'components/ui/button';
+import { Checkbox, CheckboxIndicator } from 'components/ui/checkbox';
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from 'components/ui/form';
+import { Input } from 'components/ui/input';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from 'components/ui/select';
+import { Label } from 'components/ui/label';
+
+// import { ContactUsEmail } from './email-template';
+// import { postContactForm } from 'services/api';
+const TOPICS = [
+ { label: 'General', value: 'general' },
+ { label: 'Datasets', value: 'datasets' },
+ { label: 'GMW Platform', value: 'gmw-platform' },
+ { label: 'Mangrove Restoration Tracker Tool', value: 'mrtt' },
+ { label: 'Global Mangrove Alliance', value: 'gma' },
+] as const;
+
+const TOPICS_VALUES = TOPICS.map((topic) => topic.value) as [string, ...string[]];
+
+export const ContactFormSchema = z.object({
+ name: z.string({ message: 'Name is required' }).min(2, 'Name must contain at least 2 characters'),
+ organization: z.string(),
+ email: z
+ .string({ message: 'Email is required' })
+ .min(1, 'Email is required')
+ .email('Invalid email'),
+ topic: z.enum(TOPICS_VALUES, { message: 'Please, select a topic' }),
+ message: z.string().optional(),
+});
+
+type FormSchema = z.infer;
+
+export function ContactForm() {
+ const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
+ const [isOpen, setIsOpen] = useState(false);
+ const [privacyPolicy, setPrivacyPolicy] = useState(false);
+
+ const formRef = useRef(null);
+ const form = useForm>({
+ resolver: zodResolver(ContactFormSchema),
+ defaultValues: {
+ name: '',
+ organization: '',
+ email: '',
+ topic: undefined,
+ message: '',
+ },
+ mode: 'onSubmit',
+ });
+
+ const handlePrivacyPolicy = useCallback(() => {
+ setPrivacyPolicy((prev) => !prev);
+ }, [setPrivacyPolicy]);
+
+ const onSubmitData = async (values: FormSchema) => {
+ try {
+ const response = await fetch('api/contact', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(values), // Send form data to the API
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to send email: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ console.info('Email sent successfully:', data);
+ setStatus('success'); // Update form submission status
+ } catch (error) {
+ console.error('Error submitting form:', error);
+ setStatus('error'); // Update status in case of an error
+ }
+ };
+ return (
+
+
+ );
+}
+
+export default ContactForm;
diff --git a/src/components/ui/button/index.tsx b/src/components/ui/button/index.tsx
new file mode 100644
index 000000000..80fc8bcac
--- /dev/null
+++ b/src/components/ui/button/index.tsx
@@ -0,0 +1,54 @@
+import * as React from 'react';
+
+import cn from 'lib/classnames';
+
+import { Slot } from '@radix-ui/react-slot';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+const buttonVariants = cva(
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-3xl transition-colors text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border h-8',
+ {
+ variants: {
+ variant: {
+ default: 'bg-brand-800 text-white hover:bg-opacity-90',
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
+ outline:
+ 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground border-brand-800/15',
+ secondary: 'bg-white text-brand-800 hover:bg-accent hover:text-accent-foreground',
+ ghost: 'bg-brand-800/15 text-black/85 hover:bg-white hover:text-grey-800',
+ link: 'text-primary rounded-full underline-offset-4 hover:underline',
+ rounded: 'rounded-full',
+ },
+ size: {
+ default: 'px-4 py-2',
+ sm: 'px-3',
+ lg: 'px-5',
+ xl: 'px-8',
+ icon: 'h-11 w-11',
+ none: '',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ }
+);
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean;
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'button';
+ return (
+
+ );
+ }
+);
+Button.displayName = 'Button';
+
+export { Button, buttonVariants };
diff --git a/src/components/ui/dialog/index.tsx b/src/components/ui/dialog/index.tsx
index 605c1bcfa..d56740154 100644
--- a/src/components/ui/dialog/index.tsx
+++ b/src/components/ui/dialog/index.tsx
@@ -84,7 +84,7 @@ const DialogClose = ({