Skip to content

Commit

Permalink
added ethereum.org email support
Browse files Browse the repository at this point in the history
  • Loading branch information
AtHeartEngineer committed Sep 5, 2024
1 parent 4d3c79a commit 95b9ed4
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 104 deletions.
41 changes: 29 additions & 12 deletions packages/backend/src/types.ts
Original file line number Diff line number Diff line change
@@ -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<typeof SendOtpSchema>;

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<typeof VerifyOtpSchema>;
68 changes: 34 additions & 34 deletions packages/interface/src/features/signup/components/RegisterEmail.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="w-72 sm:w-96">
Expand All @@ -65,16 +65,16 @@ const RegisterEmail = ({
onSubmit={(email) => registerEmail(email)}
>
<FormSection
description="Please register with your 'pse.dev' email."
description="Please register with your 'pse.dev' or 'ethereum.org' email."
title="Register"
>
<FormControl
required
hint="This is your 'pse.dev' email address"
hint="This is your 'pse.dev' or 'ethereum.org' email address"
label="Email address"
name="email"
>
<Input placeholder="[email protected]" />
<Input placeholder="[email protected] | [email protected]" />
</FormControl>
<Button
suppressHydrationWarning
Expand All @@ -87,7 +87,7 @@ const RegisterEmail = ({
</FormSection>
</Form>
</div>
);
};
)
}

export default RegisterEmail;
export default RegisterEmail
116 changes: 58 additions & 58 deletions packages/interface/src/features/signup/components/VerifyOtp.tsx
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -51,62 +51,62 @@ 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,
address: semaphore.contracts.semaphore as Address,
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 (
<div className="w-72 sm:w-96">
Expand All @@ -118,7 +118,7 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => {
<FormControl
required
valueAsNumber
hint="Check your 'pse.dev' inbox for the OTP"
hint="Check your 'pse.dev' or 'ethereum.org' inbox for the OTP"
label="OTP"
name="otp"
>
Expand All @@ -135,7 +135,7 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => {
</FormSection>
</Form>
</div>
);
};
)
}

export default VerifyOtp;
export default VerifyOtp

0 comments on commit 95b9ed4

Please sign in to comment.