From 95b9ed42dbb232f7271c11aaad878699b0fe623f Mon Sep 17 00:00:00 2001 From: AtHeartEngineer <1675654+AtHeartEngineer@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:10:54 -0400 Subject: [PATCH] added ethereum.org email support --- packages/backend/src/types.ts | 41 +++++-- .../signup/components/RegisterEmail.tsx | 68 +++++----- .../features/signup/components/VerifyOtp.tsx | 116 +++++++++--------- 3 files changed, 121 insertions(+), 104 deletions(-) diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index ce587bf..7e6e7da 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -1,25 +1,42 @@ -import { isAddress } from "viem"; -import z from "zod"; +import { isAddress } from 'viem'; +import z from 'zod'; export const SendOtpSchema = z.object({ - email: z.string().email().endsWith("@pse.dev").refine(email => !email.includes('+'), { - message: "Email must not contain '+'" - }), + email: z + .string() + .email() + .refine( + (email) => { + return ( + (email.endsWith('@pse.dev') || email.endsWith('@ethereum.org')) && !email.includes('+') + ); + }, + { + message: "Email must be from '@pse.dev' or '@ethereum.org' and must not contain '+'" + } + ) }); export type SendOtp = z.infer; export const VerifyOtpSchema = z.object({ - email: z.string() + email: z + .string() .email() - .endsWith("@pse.dev") - .refine(email => !email.includes('+'), { - message: "Email must not contain '+'" - }), + .refine( + (email) => { + return ( + (email.endsWith('@pse.dev') || email.endsWith('@ethereum.org')) && !email.includes('+') + ); + }, + { + message: "Email must be from '@pse.dev' or '@ethereum.org' and must not contain '+'" + } + ), otp: z.number().int().gte(100000).lte(999999), address: z.string().refine(isAddress, { - message: "Invalid address", - }), + message: 'Invalid address' + }) }); export type VerifyOtp = z.infer; diff --git a/packages/interface/src/features/signup/components/RegisterEmail.tsx b/packages/interface/src/features/signup/components/RegisterEmail.tsx index 4fc9494..1221d32 100644 --- a/packages/interface/src/features/signup/components/RegisterEmail.tsx +++ b/packages/interface/src/features/signup/components/RegisterEmail.tsx @@ -1,62 +1,62 @@ -import { Dispatch, SetStateAction, useState } from "react"; -import { toast } from "sonner"; +import { Dispatch, SetStateAction, useState } from "react" +import { toast } from "sonner" -import { config } from "~/config"; -import { Form, FormControl, FormSection } from "~/components/ui/Form"; -import { Input } from "~/components/ui/Input"; -import { EmailFieldSchema, EmailField } from "../types"; -import { Button } from "~/components/ui/Button"; -import { Spinner } from "~/components/ui/Spinner"; +import { config } from "~/config" +import { Form, FormControl, FormSection } from "~/components/ui/Form" +import { Input } from "~/components/ui/Input" +import { EmailFieldSchema, EmailField } from "../types" +import { Button } from "~/components/ui/Button" +import { Spinner } from "~/components/ui/Spinner" interface IRegisterEmailProps { emailField: - | { - email: string; - } - | undefined; + | { + email: string + } + | undefined setEmail: Dispatch< SetStateAction< | { - email: string; - } + email: string + } | undefined > - >; + > } const RegisterEmail = ({ emailField, setEmail, }: IRegisterEmailProps): JSX.Element => { - const [registering, setRegistering] = useState(false); + const [registering, setRegistering] = useState(false) const registerEmail = async (emailField: EmailField) => { try { - setRegistering(true); + setRegistering(true) const response = await fetch(`${config.backendUrl}/send-otp`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(emailField), - }); - const json = await response.json(); + }) + const json = await response.json() if (!response.ok) { - console.log(response.status); - console.error(json); - toast.error((json.errors && json.errors[0]) ?? json.message); + console.log(response.status) + console.error(json) + toast.error((json.errors && json.errors[0]) ?? json.message) } else { - setEmail(emailField); - toast.success(`OTP has been sent to ${emailField.email}`); + setEmail(emailField) + toast.success(`OTP has been sent to ${emailField.email}`) } } catch (error: any) { - console.error(error); - toast.error("An unexpected error occured registering your email"); + console.error(error) + toast.error("An unexpected error occured registering your email") } finally { - setRegistering(false); + setRegistering(false) } - }; + } return (
@@ -65,16 +65,16 @@ const RegisterEmail = ({ onSubmit={(email) => registerEmail(email)} > - +
- ); -}; + ) +} -export default RegisterEmail; +export default RegisterEmail diff --git a/packages/interface/src/features/signup/components/VerifyOtp.tsx b/packages/interface/src/features/signup/components/VerifyOtp.tsx index f868a23..1c74fc7 100644 --- a/packages/interface/src/features/signup/components/VerifyOtp.tsx +++ b/packages/interface/src/features/signup/components/VerifyOtp.tsx @@ -1,45 +1,45 @@ -import { useState } from "react"; -import { useRouter } from "next/router"; -import { toast } from "sonner"; -import { Address, encodeAbiParameters, parseAbiParameters } from "viem"; -import { publicClient } from "~/utils/permissionless"; -import { Identity } from "@semaphore-protocol/core"; -import SemaphoreAbi from "~/utils/Semaphore.json"; - -import { config, semaphore } from "~/config"; -import { Form, FormControl, FormSection } from "~/components/ui/Form"; -import { Input } from "~/components/ui/Input"; -import { OtpFieldSchema, OtpField } from "../types"; -import { Button } from "~/components/ui/Button"; -import useSmartAccount from "~/hooks/useSmartAccount"; -import { getSemaphoreProof } from "~/utils/semaphore"; -import { useMaci } from "~/contexts/Maci"; -import { useEthersSigner } from "~/hooks/useEthersSigner"; -import { Spinner } from "~/components/ui/Spinner"; +import { useState } from "react" +import { useRouter } from "next/router" +import { toast } from "sonner" +import { Address, encodeAbiParameters, parseAbiParameters } from "viem" +import { publicClient } from "~/utils/permissionless" +import { Identity } from "@semaphore-protocol/core" +import SemaphoreAbi from "~/utils/Semaphore.json" + +import { config, semaphore } from "~/config" +import { Form, FormControl, FormSection } from "~/components/ui/Form" +import { Input } from "~/components/ui/Input" +import { OtpFieldSchema, OtpField } from "../types" +import { Button } from "~/components/ui/Button" +import useSmartAccount from "~/hooks/useSmartAccount" +import { getSemaphoreProof } from "~/utils/semaphore" +import { useMaci } from "~/contexts/Maci" +import { useEthersSigner } from "~/hooks/useEthersSigner" +import { Spinner } from "~/components/ui/Spinner" interface IVerifyOtpProps { emailField: { - email: string; - }; + email: string + } } const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => { - const { address, smartAccount, smartAccountClient } = useSmartAccount(); - const signer = useEthersSigner({ client: smartAccountClient }); - const { updateEligibility } = useMaci(); - const router = useRouter(); + const { address, smartAccount, smartAccountClient } = useSmartAccount() + const signer = useEthersSigner({ client: smartAccountClient }) + const { updateEligibility } = useMaci() + const router = useRouter() - const [verifying, setVerifying] = useState(false); + const [verifying, setVerifying] = useState(false) const verifyOtp = async (otpField: OtpField) => { try { - setVerifying(true); + setVerifying(true) if (!address) { - throw new Error("Smart account does not exist"); + throw new Error("Smart account does not exist") } - const { email: email } = emailField; // the component that can call this function only renders when the email exists - const { otp: otp } = otpField; + const { email: email } = emailField // the component that can call this function only renders when the email exists + const { otp: otp } = otpField const response = await fetch(`${config.backendUrl}/verify-otp`, { method: "POST", @@ -51,40 +51,40 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => { otp, address, }), - }); - const json = await response.json(); + }) + const json = await response.json() if (!response.ok) { - console.log(response.status); - console.error(json); - toast.error((json.errors && json.errors[0]) ?? json.message); + console.log(response.status) + console.error(json) + toast.error((json.errors && json.errors[0]) ?? json.message) } else { - toast.success("OTP verified - now joining Semaphore group"); - await joinSemaphoreGroup(); - router.push("/signup"); + toast.success("OTP verified - now joining Semaphore group") + await joinSemaphoreGroup() + router.push("/signup") } } catch (error: any) { - console.error(error); - toast.error("An unexpected error occured verifying the OTP"); + console.error(error) + toast.error("An unexpected error occured verifying the OTP") } finally { - setVerifying(false); + setVerifying(false) } - }; + } const joinSemaphoreGroup = async () => { if (!smartAccount || !smartAccountClient) { - throw new Error("Smart account does not exist"); + throw new Error("Smart account does not exist") } - const semaphoreIdentity = localStorage.getItem("semaphoreIdentity"); + const semaphoreIdentity = localStorage.getItem("semaphoreIdentity") if (!semaphoreIdentity || !signer) { - throw new Error("No Semaphore Identity or signer"); + throw new Error("No Semaphore Identity or signer") } - const identityCommitment = new Identity(semaphoreIdentity).commitment; + const identityCommitment = new Identity(semaphoreIdentity).commitment const data = encodeAbiParameters(parseAbiParameters("uint"), [ semaphore.hatId, - ]); + ]) const { request } = await publicClient.simulateContract({ account: smartAccount, @@ -92,21 +92,21 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => { abi: SemaphoreAbi.abi, functionName: "gateAndAddMember", args: [identityCommitment, data], - }); - const txHash = await smartAccountClient.writeContract(request); - console.log("txHash", txHash); + }) + const txHash = await smartAccountClient.writeContract(request) + console.log("txHash", txHash) // TODO: (merge-ok) come up with a better fix - await new Promise((resolve) => setTimeout(resolve, 20000)); + await new Promise((resolve) => setTimeout(resolve, 20000)) const proof = await getSemaphoreProof( signer, new Identity(semaphoreIdentity) - ); - await updateEligibility(proof, address); + ) + await updateEligibility(proof, address) - toast.success("Joined Semaphore group"); - }; + toast.success("Joined Semaphore group") + } return (
@@ -118,7 +118,7 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => { @@ -135,7 +135,7 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => {
- ); -}; + ) +} -export default VerifyOtp; +export default VerifyOtp