diff --git a/package-lock.json b/package-lock.json
index eb315d66..0205913e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,10 +25,12 @@
"@visx/vendor": "^3.5.0",
"ethers": "^6.13.1",
"framer-motion": "^11.3.17",
+ "input-otp": "^1.2.4",
"loglevel": "^1.9.1",
"mutative": "^1.0.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-hook-form": "^7.53.0",
"react-router-dom": "^6.25.1",
"use-mutative": "^1.1.5"
},
@@ -6833,6 +6835,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
+ "node_modules/input-otp": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz",
+ "integrity": "sha512-md6rhmD+zmMnUh5crQNSQxq3keBRYvE3odbr4Qb9g2NWzQv9azi+t1a3X4TBTbh98fsGHgEEJlzbe1q860uGCA==",
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
"node_modules/internal-slot": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
@@ -8212,6 +8223,21 @@
"react": "^18.3.1"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.53.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz",
+ "integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
diff --git a/package.json b/package.json
index 451f1794..5b3b803a 100644
--- a/package.json
+++ b/package.json
@@ -27,10 +27,12 @@
"@visx/vendor": "^3.5.0",
"ethers": "^6.13.1",
"framer-motion": "^11.3.17",
+ "input-otp": "^1.2.4",
"loglevel": "^1.9.1",
"mutative": "^1.0.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-hook-form": "^7.53.0",
"react-router-dom": "^6.25.1",
"use-mutative": "^1.1.5"
},
diff --git a/public/images/google.svg b/public/images/google.svg
new file mode 100644
index 00000000..ad9d7f7e
--- /dev/null
+++ b/public/images/google.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/onboarding/Login.jsx b/src/onboarding/Login.jsx
index bae5f72d..c03dd1be 100644
--- a/src/onboarding/Login.jsx
+++ b/src/onboarding/Login.jsx
@@ -1,7 +1,65 @@
+import { Button, Divider, Input } from '@nextui-org/react';
+import { useSignIn } from '@clerk/clerk-react';
import { Link } from 'react-router-dom';
-import { SignIn } from '@clerk/clerk-react';
+import { reduceState } from '../shared/helpers';
+import { useForm } from 'react-hook-form';
+import { useMutativeReducer } from 'use-mutative';
export default function Login() {
+ const {
+ register,
+ handleSubmit,
+ setError,
+ formState: { errors }
+ } = useForm();
+
+ const { isLoaded: isClerkLoaded, signIn, setActive } = useSignIn();
+ const [state, dispatch] = useMutativeReducer(reduceState, {
+ isLoading: false
+ });
+
+ const handleLogin = async data => {
+ if (!isClerkLoaded) {
+ return;
+ }
+ try {
+ dispatch({ isLoading: true });
+ const signInAttempt = await signIn.create({
+ identifier: data.email,
+ password: data.password
+ });
+ if (signInAttempt.status === 'complete') {
+ await setActive({ session: signInAttempt.createdSessionId });
+ } else {
+ console.error(JSON.stringify(signInAttempt, null, 2));
+ }
+ } catch (err) {
+ err.errors.forEach(e => {
+ if (e.meta.paramName === 'identifier') {
+ setError('email', {
+ message: e.message
+ });
+ }
+ if (e.meta.paramName === 'password') {
+ setError('password', {
+ message: e.message
+ });
+ }
+ });
+ } finally {
+ dispatch({ isLoading: false });
+ }
+ };
+
+ // const handleGoogleLogin = async () => {
+ // await signIn.authenticateWithRedirect({
+ // strategy: 'oauth_google',
+ // redirectUrl:
+ // 'https://hip-primate-84.clerk.accounts.dev/v1/oauth_callback',
+ // redirectUrlComplete: '/'
+ // });
+ // };
+
return (
@@ -12,44 +70,97 @@ export default function Login() {
Let's sign in to your account or if you don't have one, sign up
-
+ {/*
}
+ variant="bordered"
>
- Create account
-
-
+ Continue with Google
+ */}
+
+
+
+
+
Don't have an account?
+
+ Create account
+
+
+
);
}
diff --git a/src/onboarding/Register.jsx b/src/onboarding/Register.jsx
index 8ea934c5..87df9dd7 100644
--- a/src/onboarding/Register.jsx
+++ b/src/onboarding/Register.jsx
@@ -1,7 +1,164 @@
-import { Link } from 'react-router-dom';
-import { SignUp } from '@clerk/clerk-react';
+import { Button, Checkbox, cn, Input } from '@nextui-org/react';
+import { useSignUp } from '@clerk/clerk-react';
+import { Link, useNavigate } from 'react-router-dom';
+import { OTPInput } from 'input-otp';
+import { reduceState } from '../shared/helpers';
+import { useForm } from 'react-hook-form';
+import { useMutativeReducer } from 'use-mutative';
export default function Register() {
+ const {
+ register,
+ handleSubmit,
+ getValues,
+ setError,
+ // clearErrors,
+ // reset,
+ formState: { errors }
+ } = useForm();
+ const { isLoaded: isClerkLoaded, signUp, setActive } = useSignUp();
+ const [state, dispatch] = useMutativeReducer(reduceState, {
+ isLoading: false,
+ verifying: false,
+ code: '',
+ verificationError: undefined
+ });
+ const navigate = useNavigate();
+
+ const handleSignUp = async data => {
+ if (!isClerkLoaded) return;
+
+ try {
+ dispatch({ isLoading: true });
+ await signUp.create({
+ emailAddress: data.email,
+ password: data.password
+ });
+
+ await signUp.prepareEmailAddressVerification({
+ strategy: 'email_code'
+ });
+
+ dispatch({ verifying: true });
+ } catch (err) {
+ err.errors.forEach(e => {
+ if (e.meta.paramName === 'email_address') {
+ setError('email', {
+ message: e.message
+ });
+ }
+ if (e.meta.paramName === 'password') {
+ setError('password', {
+ message: e.message
+ });
+ }
+ });
+ } finally {
+ dispatch({ isLoading: false });
+ }
+ };
+
+ const handleVerify = async e => {
+ e.preventDefault();
+ if (!isClerkLoaded) return;
+ try {
+ dispatch({ isLoading: true });
+ const completeSignUp = await signUp.attemptEmailAddressVerification({
+ code: state.code
+ });
+ if (completeSignUp.status === 'complete') {
+ await setActive({ session: completeSignUp.createdSessionId });
+ navigate('/');
+ } else {
+ console.error(JSON.stringify(completeSignUp, null, 2));
+ }
+ } catch (err) {
+ err.errors.forEach(e => {
+ if (e.meta.paramName === 'code') {
+ dispatch({ verificationError: e.longMessage });
+ }
+ });
+ } finally {
+ dispatch({ isLoading: false });
+ }
+ };
+
+ // const handleGoogleLogin = async () => {
+ // reset();
+ // if (!getValues('terms')) {
+ // setError('terms', {
+ // message: 'Please accept terms and conditions',
+ // type: 'required'
+ // });
+ // return;
+ // }
+ // clearErrors('terms');
+ // await signUp.authenticateWithRedirect({
+ // strategy: 'oauth_google',
+ // redirectUrl:
+ // 'https://hip-primate-84.clerk.accounts.dev/v1/oauth_callback',
+ // redirectUrlComplete: '/'
+ // });
+ // };
+
+ if (state.verifying) {
+ return (
+
+
+
+ Register
+
+
+ Let's create your account or if you have one already, sign in
+
+
+
+
+ );
+ }
+
return (
@@ -12,44 +169,141 @@ export default function Register() {
Let's create your account or if you have one already, sign in
-
*/}
+
+ {/*
}
+ variant="bordered"
>
- Log in
-
-
+ Continue with Google
+ */}
+
+
+
+ Accept Terms & conditions
+
+
+ {errors?.terms && errors.terms.message}
+
+
+
+
+
+
+
Already have an account?
+
+ Log in
+
+
+
+
+ );
+}
+
+function Slot(props) {
+ return (
+
+ {props.char !== null &&
{props.char}
}
+ {props.hasFakeCaret &&
}
+
+ );
+}
+
+function FakeCaret() {
+ return (
+
);
}
diff --git a/tailwind.config.js b/tailwind.config.js
index 4641ffc3..dce05a12 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -8,7 +8,17 @@ export default {
'./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}'
],
theme: {
- extend: {},
+ extend: {
+ keyframes: {
+ 'caret-blink': {
+ '0%,70%,100%': { opacity: '1' },
+ '20%,50%': { opacity: '0' }
+ }
+ },
+ animation: {
+ 'caret-blink': 'caret-blink 1.2s ease-out infinite'
+ }
+ },
fontFamily: {
sans: ['"DM Sans", system-ui, sans-serif', { fontOpticalSizing: 'auto' }],
display: ['Exo, system-ui, sans-serif', { fontOpticalSizing: 'auto' }]
@@ -62,7 +72,8 @@ export default {
outline: '#293041',
primary: '#cad7f9',
secondary: '#ffcc80',
- success: '#7ccb69'
+ success: '#7ccb69',
+ danger: '#d24646'
}
}
}