Add web3modal provider and verify the SIWE message
This commit is contained in:
parent
5aefda1248
commit
4a81ee86f6
@ -40,7 +40,7 @@ router.post('/register', async (req, res) => {
|
||||
userEmail: email,
|
||||
userName: email.split('@')[0],
|
||||
});
|
||||
req.session.userId = user.id;
|
||||
req.session.address = user.id;
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
@ -52,7 +52,7 @@ router.post('/authenticate', async (req, res) => {
|
||||
signedWhoamiRequest,
|
||||
);
|
||||
if (user) {
|
||||
req.session.userId = user.id;
|
||||
req.session.address = user.id;
|
||||
res.sendStatus(200);
|
||||
} else {
|
||||
res.sendStatus(401);
|
||||
@ -64,21 +64,19 @@ router.post('/authenticate', async (req, res) => {
|
||||
//
|
||||
|
||||
router.post('/validate', async (req, res) => {
|
||||
const { message, signature, action } = req.body;
|
||||
const { message, signature } = req.body;
|
||||
const { success, data } = await new SiweMessage(message).verify({
|
||||
signature,
|
||||
});
|
||||
|
||||
console.log("VALIDATE CALL",message, signature )
|
||||
if (!success) {
|
||||
return res.send({ success });
|
||||
return res.send({ success, error: 'SIWE verifcation failed' } );
|
||||
}
|
||||
const service: Service = req.app.get('service');
|
||||
const user = await service.getUserByEthAddress(data.address);
|
||||
|
||||
if (action === 'signup') {
|
||||
if (user) {
|
||||
return res.send({ success: false, error: 'user_already_exists' });
|
||||
}
|
||||
if (!user) {
|
||||
const newUser = await service.createUser({
|
||||
ethAddress: data.address,
|
||||
email: '',
|
||||
@ -86,13 +84,13 @@ router.post('/validate', async (req, res) => {
|
||||
subOrgId: '',
|
||||
turnkeyWalletId: '',
|
||||
});
|
||||
req.session.userId = newUser.id;
|
||||
} else if (action === 'login') {
|
||||
if (!user) {
|
||||
return res.send({ success: false, error: 'user_not_found' });
|
||||
}
|
||||
req.session.userId = user.id;
|
||||
req.session.address = newUser.id;
|
||||
req.session.chainId = data.chainId;
|
||||
} else {
|
||||
req.session.address = user.id;
|
||||
req.session.chainId = data.chainId;
|
||||
}
|
||||
console.log("VALIDATE CALL FINISHED", req.session)
|
||||
|
||||
res.send({ success });
|
||||
});
|
||||
@ -101,9 +99,10 @@ router.post('/validate', async (req, res) => {
|
||||
// General
|
||||
//
|
||||
router.get('/session', (req, res) => {
|
||||
if (req.session.userId) {
|
||||
if (req.session.address && req.session.chainId) {
|
||||
res.send({
|
||||
userId: req.session.userId,
|
||||
address: req.session.address,
|
||||
chainId: req.session.chainId
|
||||
});
|
||||
} else {
|
||||
res.status(401).send({ error: 'Unauthorized: No active session' });
|
||||
|
@ -24,7 +24,8 @@ const log = debug('snowball:server');
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
userId: string;
|
||||
address: string;
|
||||
chainId: number;
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,13 +55,13 @@ export const createAndStartServer = async (
|
||||
context: async ({ req }) => {
|
||||
// https://www.apollographql.com/docs/apollo-server/v3/security/authentication#api-wide-authorization
|
||||
|
||||
const { userId } = req.session;
|
||||
const { address } = req.session;
|
||||
|
||||
if (!userId) {
|
||||
if (!address) {
|
||||
throw new AuthenticationError('Unauthorized: No active session');
|
||||
}
|
||||
|
||||
const user = await service.getUser(userId);
|
||||
const user = await service.getUser(address);
|
||||
|
||||
return { user };
|
||||
},
|
||||
|
@ -38,7 +38,7 @@
|
||||
"@snowballtools/smartwallet-alchemy-light": "^0.2.0",
|
||||
"@snowballtools/types": "^0.2.0",
|
||||
"@snowballtools/utils": "^0.1.1",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "5.22.2",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
@ -46,8 +46,8 @@
|
||||
"@turnkey/sdk-react": "^0.1.0",
|
||||
"@turnkey/webauthn-stamper": "^0.5.0",
|
||||
"@walletconnect/ethereum-provider": "^2.12.2",
|
||||
"@web3modal/siwe": "^4.0.5",
|
||||
"@web3modal/wagmi": "^4.0.5",
|
||||
"@web3modal/siwe": "4.0.5",
|
||||
"@web3modal/wagmi": "4.0.5",
|
||||
"assert": "^2.1.0",
|
||||
"axios": "^1.6.7",
|
||||
"clsx": "^2.1.0",
|
||||
@ -68,11 +68,12 @@
|
||||
"react-oauth-popup": "^1.0.5",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-timer-hook": "^3.0.7",
|
||||
"siwe": "^2.1.4",
|
||||
"siwe": "2.1.4",
|
||||
"tailwind-variants": "^0.2.0",
|
||||
"usehooks-ts": "^2.15.1",
|
||||
"uuid": "^9.0.1",
|
||||
"viem": "^2.7.11",
|
||||
"wagmi": "2.5.7",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -59,19 +59,19 @@ const router = createBrowserRouter([
|
||||
function App() {
|
||||
// Hacky way of checking session
|
||||
// TODO: Handle redirect backs
|
||||
useEffect(() => {
|
||||
fetch(`${baseUrl}/auth/session`, {
|
||||
credentials: 'include',
|
||||
}).then((res) => {
|
||||
if (res.status !== 200) {
|
||||
localStorage.clear();
|
||||
const path = window.location.pathname;
|
||||
if (path !== '/login' && path !== '/signup') {
|
||||
window.location.pathname = '/login';
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// fetch(`${baseUrl}/auth/session`, {
|
||||
// credentials: 'include',
|
||||
// }).then((res) => {
|
||||
// if (res.status !== 200) {
|
||||
// localStorage.clear();
|
||||
// const path = window.location.pathname;
|
||||
// if (path !== '/login' && path !== '/signup') {
|
||||
// window.location.pathname = '/login';
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<Web3Provider>
|
||||
|
@ -1,16 +1,112 @@
|
||||
import { ReactNode } from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { SiweMessage, generateNonce } from 'siwe';
|
||||
import { WagmiProvider } from 'wagmi';
|
||||
import { arbitrum, mainnet } from 'wagmi/chains';
|
||||
import axios from 'axios';
|
||||
import { createWeb3Modal } from '@web3modal/wagmi/react';
|
||||
import { defaultWagmiConfig } from '@web3modal/wagmi/react/config';
|
||||
import { createSIWEConfig } from '@web3modal/siwe';
|
||||
import type {
|
||||
SIWECreateMessageArgs,
|
||||
SIWEVerifyMessageArgs,
|
||||
} from '@web3modal/core';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import { VITE_WALLET_CONNECT_ID } from 'utils/constants';
|
||||
|
||||
// if (!process.env.VITE_WALLET_CONNECT_ID) {
|
||||
// throw new Error('Error: VITE_WALLET_CONNECT_ID env config is not set');
|
||||
// }
|
||||
const WALLET_CONNECT_ID="d37f5a2f09d22f5e3ccaff4bbc93d37c"
|
||||
const queryClient = new QueryClient();
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: 'http://127.0.0.1:8000',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'Access-Control-Allow-Origin': '*',
|
||||
// },
|
||||
// withCredentials: true,
|
||||
});
|
||||
const metadata = {
|
||||
name: 'Web3Modal',
|
||||
description: 'Snowball Web3Modal',
|
||||
url: window.location.origin,
|
||||
icons: ['https://avatars.githubusercontent.com/u/37784886'],
|
||||
};
|
||||
const chains = [mainnet, arbitrum] as const;
|
||||
const config = defaultWagmiConfig({
|
||||
chains,
|
||||
projectId: WALLET_CONNECT_ID,
|
||||
metadata,
|
||||
});
|
||||
const siweConfig = createSIWEConfig({
|
||||
createMessage: ({ nonce, address, chainId }: SIWECreateMessageArgs) =>
|
||||
new SiweMessage({
|
||||
version: '1',
|
||||
domain: window.location.host,
|
||||
uri: window.location.origin,
|
||||
address,
|
||||
chainId,
|
||||
nonce,
|
||||
// Human-readable ASCII assertion that the user will sign, and it must not contain `\n`.
|
||||
statement: 'Sign in With Ethereum.',
|
||||
}).prepareMessage(),
|
||||
getNonce: async () => {
|
||||
return generateNonce()
|
||||
},
|
||||
getSession: async () => {
|
||||
try {
|
||||
const session = (await axiosInstance.get('/auth/session')).data;
|
||||
const { address, chainId } = session;
|
||||
return { address, chainId };
|
||||
} catch (err) {
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
throw new Error('Failed to get session!');
|
||||
}
|
||||
},
|
||||
verifyMessage: async ({ message, signature }: SIWEVerifyMessageArgs) => {
|
||||
try {
|
||||
const { success } = (
|
||||
await axiosInstance.post('/auth/validate', {
|
||||
message,
|
||||
signature,
|
||||
})
|
||||
).data;
|
||||
return success;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
signOut: async () => {
|
||||
// try {
|
||||
// const { success } = (await axiosInstance.post('/auth/logout')).data;
|
||||
// return success;
|
||||
// } catch (error) {
|
||||
// return false;
|
||||
// }
|
||||
return false
|
||||
},
|
||||
onSignOut: () => {
|
||||
window.location.href = '/login';
|
||||
},
|
||||
onSignIn: () => {
|
||||
window.location.href = '/';
|
||||
},
|
||||
});
|
||||
|
||||
if (!VITE_WALLET_CONNECT_ID) {
|
||||
throw new Error('Error: REACT_APP_WALLET_CONNECT_ID env config is not set');
|
||||
}
|
||||
|
||||
export default function Web3Provider({ children }: { children: ReactNode }) {
|
||||
createWeb3Modal({
|
||||
siweConfig,
|
||||
wagmiConfig: config,
|
||||
projectId: WALLET_CONNECT_ID,
|
||||
});
|
||||
export default function Web3ModalProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
<WagmiProvider config={config}>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
</WagmiProvider>
|
||||
);
|
||||
}
|
||||
}
|
@ -15,6 +15,8 @@ import { SERVER_GQL_PATH } from './constants';
|
||||
import { Toaster } from 'components/shared/Toast';
|
||||
import { LogErrorBoundary } from 'utils/log-error';
|
||||
import { baseUrl } from 'utils/constants';
|
||||
import Web3ModalProvider from './context/Web3Provider';
|
||||
|
||||
|
||||
console.log(`v-0.0.9`);
|
||||
|
||||
@ -31,10 +33,12 @@ root.render(
|
||||
<LogErrorBoundary>
|
||||
<React.StrictMode>
|
||||
<ThemeProvider>
|
||||
<Web3ModalProvider>
|
||||
<GQLClientProvider client={gqlClient}>
|
||||
<App />
|
||||
<Toaster />
|
||||
</GQLClientProvider>
|
||||
</Web3ModalProvider>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
</LogErrorBoundary>,
|
||||
|
@ -20,51 +20,52 @@ import { signInWithEthereum } from 'utils/siwe';
|
||||
import { useSnowball } from 'utils/use-snowball';
|
||||
import { logError } from 'utils/log-error';
|
||||
|
||||
type Provider = 'google' | 'github' | 'apple' | 'email' | 'passkey';
|
||||
// type Provider = 'google' | 'github' | 'apple' | 'email' | 'passkey';
|
||||
|
||||
type Props = {
|
||||
onDone: () => void;
|
||||
};
|
||||
|
||||
export const Login = ({ onDone }: Props) => {
|
||||
const snowball = useSnowball();
|
||||
const [error, setError] = useState<string>('');
|
||||
const [provider, setProvider] = useState<Provider | false>(false);
|
||||
// const snowball = useSnowball();
|
||||
// const [error, setError] = useState<string>('');
|
||||
// const [provider, setProvider] = useState<Provider | false>(false);
|
||||
|
||||
// const loading = snowball.auth.state.loading && provider;
|
||||
const loading = provider;
|
||||
const { toast } = useToast();
|
||||
// const loading = provider;
|
||||
// const { toast } = useToast();
|
||||
|
||||
if (provider === 'email') {
|
||||
return <CreatePasskey onDone={onDone} />;
|
||||
}
|
||||
console.log(">>ondone", onDone)
|
||||
// if (provider === 'email') {
|
||||
// return <CreatePasskey onDone={onDone} />;
|
||||
// }
|
||||
|
||||
async function handleSigninRedirect() {
|
||||
let wallet: PKPEthersWallet | undefined;
|
||||
const { google } = snowball.auth;
|
||||
if (google.canHandleOAuthRedirectBack()) {
|
||||
setProvider('google');
|
||||
console.log('Handling google redirect back');
|
||||
try {
|
||||
await google.handleOAuthRedirectBack();
|
||||
// @ts-ignore
|
||||
wallet = await google.getEthersWallet();
|
||||
// @ts-ignore
|
||||
const result = await signInWithEthereum(1, 'login', wallet);
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
setProvider(false);
|
||||
wallet = undefined;
|
||||
logError(new Error(result.error));
|
||||
return;
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
logError(err);
|
||||
setProvider(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// async function handleSigninRedirect() {
|
||||
// let wallet: PKPEthersWallet | undefined;
|
||||
// const { google } = snowball.auth;
|
||||
// if (google.canHandleOAuthRedirectBack()) {
|
||||
// setProvider('google');
|
||||
// console.log('Handling google redirect back');
|
||||
// try {
|
||||
// await google.handleOAuthRedirectBack();
|
||||
// // @ts-ignore
|
||||
// wallet = await google.getEthersWallet();
|
||||
// // @ts-ignore
|
||||
// const result = await signInWithEthereum(1, 'login', wallet);
|
||||
// if (result.error) {
|
||||
// setError(result.error);
|
||||
// setProvider(false);
|
||||
// wallet = undefined;
|
||||
// logError(new Error(result.error));
|
||||
// return;
|
||||
// }
|
||||
// } catch (err: any) {
|
||||
// setError(err.message);
|
||||
// logError(err);
|
||||
// setProvider(false);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// if (apple.canHandleOAuthRedirectBack()) {
|
||||
// setProvider('apple');
|
||||
// console.log('Handling apple redirect back');
|
||||
@ -86,14 +87,14 @@ export const Login = ({ onDone }: Props) => {
|
||||
// }
|
||||
// }
|
||||
|
||||
if (wallet) {
|
||||
window.location.pathname = '/';
|
||||
}
|
||||
}
|
||||
// if (wallet) {
|
||||
// window.location.pathname = '/';
|
||||
// }
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
handleSigninRedirect();
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// handleSigninRedirect();
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -105,8 +106,8 @@ export const Login = ({ onDone }: Props) => {
|
||||
<WavyBorder className="self-stretch" variant="stroke" />
|
||||
|
||||
<div className="self-stretch p-4 xs:p-6 flex-col justify-center items-center gap-8 flex">
|
||||
<div className="self-stretch p-5 bg-slate-50 rounded-xl shadow flex-col justify-center items-center gap-6 flex">
|
||||
<div className="self-stretch flex-col justify-center items-center gap-4 flex">
|
||||
{/* <div className="self-stretch p-5 bg-slate-50 rounded-xl shadow flex-col justify-center items-center gap-6 flex"> */}
|
||||
{/* <div className="self-stretch flex-col justify-center items-center gap-4 flex">
|
||||
<KeyIcon />
|
||||
<div className="self-stretch flex-col justify-center items-center gap-2 flex">
|
||||
<div className="self-stretch text-center text-sky-950 text-lg font-medium font-display leading-normal">
|
||||
@ -116,9 +117,9 @@ export const Login = ({ onDone }: Props) => {
|
||||
Use it to sign in securely without using a password.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="self-stretch justify-center items-stretch xxs:items-center gap-3 flex flex-col xxs:flex-row">
|
||||
<Button
|
||||
</div> */}
|
||||
{/* <div className="self-stretch justify-center items-stretch xxs:items-center gap-3 flex flex-col xxs:flex-row"> */}
|
||||
{/* <Button
|
||||
as="a"
|
||||
leftIcon={<QuestionMarkRoundFilledIcon />}
|
||||
variant={'tertiary'}
|
||||
@ -126,8 +127,8 @@ export const Login = ({ onDone }: Props) => {
|
||||
href="https://safety.google/authentication/passkey/"
|
||||
>
|
||||
Learn more
|
||||
</Button>
|
||||
<Button
|
||||
</Button> */}
|
||||
{/* <Button
|
||||
rightIcon={
|
||||
loading && loading === 'passkey' ? (
|
||||
<LoaderIcon className="animate-spin" />
|
||||
@ -142,10 +143,10 @@ export const Login = ({ onDone }: Props) => {
|
||||
}}
|
||||
>
|
||||
Sign In with Passkey
|
||||
</Button>
|
||||
</div>
|
||||
</Button> */}
|
||||
{/* </div> */}
|
||||
|
||||
<div className="h-5 justify-center items-center gap-2 inline-flex">
|
||||
{/* <div className="h-5 justify-center items-center gap-2 inline-flex">
|
||||
<div className="text-center text-slate-600 text-sm font-normal font-['Inter'] leading-tight">
|
||||
Lost your passkey?
|
||||
</div>
|
||||
@ -155,19 +156,20 @@ export const Login = ({ onDone }: Props) => {
|
||||
</button>
|
||||
<LinkIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
{/* </div> */}
|
||||
|
||||
<div className="self-stretch justify-start items-center gap-8 inline-flex">
|
||||
{/* <div className="self-stretch justify-start items-center gap-8 inline-flex">
|
||||
<DotBorder className="flex-1" />
|
||||
<div className="text-center text-slate-400 text-xs font-normal font-['JetBrains Mono'] leading-none">
|
||||
OR
|
||||
</div>
|
||||
<DotBorder className="flex-1" />
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="self-stretch flex-col justify-center items-center gap-3 flex">
|
||||
<Button
|
||||
<w3m-button />
|
||||
{/* <Button
|
||||
leftIcon={<GoogleIcon />}
|
||||
rightIcon={
|
||||
loading && loading === 'google' ? (
|
||||
@ -176,16 +178,16 @@ export const Login = ({ onDone }: Props) => {
|
||||
}
|
||||
onClick={() => {
|
||||
setProvider('google');
|
||||
snowball.auth.google.startOAuthRedirect();
|
||||
// snowball.auth.google.startOAuthRedirect();
|
||||
}}
|
||||
className="flex-1 self-stretch"
|
||||
variant={'tertiary'}
|
||||
disabled={!!loading}
|
||||
>
|
||||
Continue with Google
|
||||
</Button>
|
||||
</Button> */}
|
||||
|
||||
<Button
|
||||
{/* <Button
|
||||
leftIcon={<GithubIcon />}
|
||||
rightIcon={
|
||||
loading && loading === 'github' ? (
|
||||
@ -194,23 +196,23 @@ export const Login = ({ onDone }: Props) => {
|
||||
}
|
||||
onClick={async () => {
|
||||
setProvider('github');
|
||||
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||
setProvider(false);
|
||||
toast({
|
||||
id: 'coming-soon',
|
||||
title: 'Sign-in with GitHub is coming soon!',
|
||||
variant: 'info',
|
||||
onDismiss() {},
|
||||
});
|
||||
// await new Promise((resolve) => setTimeout(resolve, 800));
|
||||
// setProvider(false);
|
||||
// toast({
|
||||
// id: 'coming-soon',
|
||||
// title: 'Sign-in with GitHub is coming soon!',
|
||||
// variant: 'info',
|
||||
// onDismiss() {},
|
||||
// });
|
||||
}}
|
||||
className="flex-1 self-stretch"
|
||||
variant={'tertiary'}
|
||||
disabled={!!loading}
|
||||
>
|
||||
Continue with GitHub
|
||||
</Button>
|
||||
</Button> */}
|
||||
|
||||
<Button
|
||||
{/* <Button
|
||||
leftIcon={<AppleIcon />}
|
||||
rightIcon={
|
||||
loading && loading === 'apple' ? (
|
||||
@ -220,14 +222,14 @@ export const Login = ({ onDone }: Props) => {
|
||||
onClick={async () => {
|
||||
setProvider('apple');
|
||||
// snowball.auth.apple.startOAuthRedirect();
|
||||
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||
setProvider(false);
|
||||
toast({
|
||||
id: 'coming-soon',
|
||||
title: 'Sign-in with Apple is coming soon!',
|
||||
variant: 'info',
|
||||
onDismiss() {},
|
||||
});
|
||||
// await new Promise((resolve) => setTimeout(resolve, 800));
|
||||
// setProvider(false);
|
||||
// toast({
|
||||
// id: 'coming-soon',
|
||||
// title: 'Sign-in with Apple is coming soon!',
|
||||
// variant: 'info',
|
||||
// onDismiss() {},
|
||||
// });
|
||||
}}
|
||||
className={`flex-1 self-stretch border-black enabled:bg-black text-white ${
|
||||
loading && loading === 'apple' ? 'disabled:bg-black' : ''
|
||||
@ -236,17 +238,17 @@ export const Login = ({ onDone }: Props) => {
|
||||
disabled={!!loading}
|
||||
>
|
||||
Continue with Apple
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
{error && (
|
||||
{/* {error && (
|
||||
<div className="justify-center items-center gap-2 inline-flex">
|
||||
<div className="text-red-500 text-sm">Error: {error}</div>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
<div className="h-5 justify-center items-center gap-2 inline-flex">
|
||||
{/* <div className="h-5 justify-center items-center gap-2 inline-flex">
|
||||
<div className="text-center text-slate-600 text-sm font-normal font-['Inter'] leading-tight">
|
||||
Don't have an account?
|
||||
</div>
|
||||
@ -258,7 +260,7 @@ export const Login = ({ onDone }: Props) => {
|
||||
Sign up now
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user