Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Onboarding page #132

Merged
merged 3 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"tsc": "tsc --noEmit"
},
"dependencies": {
"@emailjs/browser": "4.4.1",
"@pivanov/event-bus": "1.0.0",
"@pivanov/use-toggle-visibility": "0.0.1",
"@polkadot-api/descriptors": "file:.papi/descriptors",
Expand Down Expand Up @@ -63,6 +64,7 @@
"prismjs": "1.29.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-google-recaptcha": "3.1.0",
"react-helmet-async": "2.0.5",
"react-hot-toast": "2.4.1",
"react-resizable-panels": "2.0.19",
Expand All @@ -84,6 +86,7 @@
"@types/prismjs": "1.26.4",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@types/react-google-recaptcha": "2.1.9",
"@types/react-router-dom": "5.3.3",
"@typescript-eslint/eslint-plugin": "7.14.1",
"@typescript-eslint/parser": "7.13.1",
Expand Down
65 changes: 0 additions & 65 deletions src/components/modals/modalRequestExample/index.tsx

This file was deleted.

167 changes: 167 additions & 0 deletions src/components/modals/modalSendMail/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { sendForm } from '@emailjs/browser';
import {
useCallback,
useEffect,
useRef,
useState,
} from 'react';
// eslint-disable-next-line import/no-named-as-default
import ReCAPTCHA from 'react-google-recaptcha';
import { toast } from 'react-hot-toast';

import { Icon } from '@components/icon';
import { cn } from '@utils/helpers';

import {
type IModal,
Modal,
} from '../modal';

interface IModalGithubLogin extends Pick<IModal, 'onClose'> {
title: string;
}

const publicKey = import.meta.env.VITE_EMAIL_PUBLIC_KEY;
const serviceId = import.meta.env.VITE_EMAIL_SERVICE_ID;
const templateId = import.meta.env.VITE_EMAIL_TEMPLATE_ID;
const recaptchaKey = import.meta.env.VITE_RECAPTCHA_KEY;

const MIN_INTERVAL = 60000; // 1 minute in milliseconds

export const ModalSendMail = ({ title, onClose }: IModalGithubLogin) => {
const formRef = useRef<HTMLFormElement>(null);
const recaptchaRef = useRef<ReCAPTCHA>(null);
const [
isSending,
setIsSending,
] = useState(false);
const [
captchaVerified,
setCaptchaVerified,
] = useState(false);
const [
lastSentTime,
setLastSentTime,
] = useState<number | null>(null);

useEffect(() => {
const storedLastSentTime = localStorage.getItem('lastSentTime');
if (storedLastSentTime) {
setLastSentTime(Number(storedLastSentTime));
}
}, []);

const sendEmail = useCallback(async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSending(true);

if (!recaptchaRef.current?.getValue()) {
toast.error('Please complete the reCAPTCHA');
setIsSending(false);
return;
}

const now = Date.now();
if (lastSentTime && now - lastSentTime < MIN_INTERVAL) {
toast.error('You are sending emails too quickly. Please wait a moment.');
setIsSending(false);
return;
}

try {
await sendForm(serviceId, templateId, formRef.current!, publicKey);
toast.success('Email sent successfully');
setLastSentTime(now);
localStorage.setItem('lastSentTime', now.toString());
onClose();
} catch {
toast.error('Failed to send email');
} finally {
setIsSending(false);
recaptchaRef.current?.reset();
setCaptchaVerified(false);
}
}, [
lastSentTime,
onClose,
]);

const onCaptchaChange = useCallback((value: string | null) => {
setCaptchaVerified(!!value);
}, []);

return (
<Modal
onClose={onClose}
className={cn(
'w-96',
'flex flex-col gap-10 p-6',
'border border-dev-purple-300',
'dark:border-dev-purple-700',
)}
>
<h5 className="self-start font-h5-bold">{title}</h5>
<form
ref={formRef}
className="flex flex-col"
onSubmit={sendEmail}
>
<input
name="heading"
placeholder="Title"
className={cn(
'mb-6 p-4',
'border border-dev-white-900',
'placeholder:text-dev-black-1000 placeholder:font-body2-regular',
'dark:border-dev-purple-700 dark:bg-transparent dark:placeholder:text-white',
)}
/>
<textarea
name="message"
placeholder="Content"
className={cn(
'mb-6 p-4',
'border border-dev-white-900',
'placeholder:text-dev-black-1000 placeholder:font-body2-regular',
'dark:border-dev-purple-700 dark:bg-transparent dark:placeholder:text-white',
)}
/>
<ReCAPTCHA
ref={recaptchaRef}
className="m-auto"
onChange={onCaptchaChange}
sitekey={recaptchaKey}
/>
<button
disabled={isSending || !captchaVerified}
type="submit"
className={cn(
'flex justify-center',
'mb-2 mt-6 p-4 transition-colors',
'font-geist text-white font-body2-bold',
'bg-dev-pink-500',
'hover:bg-dev-pink-400',
'disabled:cursor-not-allowed disabled:opacity-50',
)}
>
{
isSending
? (
<Icon
className="animate-spin"
name="icon-loader"
/>
)
: 'Submit'
}
</button>
<button
className={cn('p-4 font-geist transition-colors font-body2-bold hover:text-dev-white-1000')}
onClick={onClose}
>
Cancel
</button>
</form>
</Modal>
);
};
Loading