forked from cerc-io/snowballtools-base
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
175af0f9e0 |
@@ -5,19 +5,6 @@ import { authenticateUser, createUser } from '../turnkey-backend';
|
|||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
//
|
|
||||||
// Access Code
|
|
||||||
//
|
|
||||||
router.post('/accesscode', async (req, res) => {
|
|
||||||
console.log('Access Code', req.body);
|
|
||||||
const { accesscode } = req.body;
|
|
||||||
if (accesscode === '44444') {
|
|
||||||
return res.send({ isValid: true });
|
|
||||||
} else {
|
|
||||||
return res.sendStatus(204);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Turnkey
|
// Turnkey
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Repository URL
|
# Repository URL
|
||||||
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
REPO_URL="https://github.com/snowball-tools/snowballtools-base"
|
||||||
|
|
||||||
# Get the latest commit hash from the repository
|
# Get the latest commit hash from the repository
|
||||||
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Repository URL
|
# Repository URL
|
||||||
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
REPO_URL="https://github.com/snowball-tools/snowballtools-base"
|
||||||
|
|
||||||
# Get the latest commit hash from the repository
|
# Get the latest commit hash from the repository
|
||||||
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
||||||
|
|||||||
@@ -14,5 +14,5 @@ record:
|
|||||||
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||||
meta:
|
meta:
|
||||||
note: Added by Snowball @ Thu Apr 4 14:49:41 UTC 2024
|
note: Added by Snowball @ Thu Apr 4 14:49:41 UTC 2024
|
||||||
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
repository: "https://github.com/snowball-tools/snowballtools-base"
|
||||||
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
|
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ record:
|
|||||||
type: ApplicationRecord
|
type: ApplicationRecord
|
||||||
version: 0.0.2
|
version: 0.0.2
|
||||||
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
|
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
|
||||||
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
|
repository: ["https://github.com/snowball-tools/snowballtools-base"]
|
||||||
app_type: webapp
|
app_type: webapp
|
||||||
name: snowballtools-base-frontend
|
name: snowballtools-base-frontend
|
||||||
app_version: 0.1.8
|
app_version: 0.1.8
|
||||||
|
|||||||
@@ -20,5 +20,5 @@ record:
|
|||||||
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
|
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
|
||||||
meta:
|
meta:
|
||||||
note: Added by Snowball @ Tuesday 21 May 2024 06:17:23 AM UTC
|
note: Added by Snowball @ Tuesday 21 May 2024 06:17:23 AM UTC
|
||||||
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
repository: "https://github.com/snowball-tools/snowballtools-base"
|
||||||
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ record:
|
|||||||
type: ApplicationRecord
|
type: ApplicationRecord
|
||||||
version: 0.0.2
|
version: 0.0.2
|
||||||
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
||||||
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
|
repository: ["https://github.com/snowball-tools/snowballtools-base"]
|
||||||
app_type: webapp
|
app_type: webapp
|
||||||
name: staging-snowballtools-base-frontend
|
name: staging-snowballtools-base-frontend
|
||||||
app_version: 0.0.0
|
app_version: 0.0.0
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||||
|
|
||||||
import Projects from './pages/org-slug';
|
import Projects from './pages/org-slug';
|
||||||
@@ -11,8 +10,8 @@ import ProjectSearchLayout from './layouts/ProjectSearch';
|
|||||||
import Index from './pages';
|
import Index from './pages';
|
||||||
import AuthPage from './pages/AuthPage';
|
import AuthPage from './pages/AuthPage';
|
||||||
import { DashboardLayout } from './pages/org-slug/layout';
|
import { DashboardLayout } from './pages/org-slug/layout';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import Web3Provider from 'context/Web3Provider';
|
import Web3Provider from 'context/Web3Provider';
|
||||||
import { baseUrl } from 'utils/constants';
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -60,7 +59,7 @@ function App() {
|
|||||||
// Hacky way of checking session
|
// Hacky way of checking session
|
||||||
// TODO: Handle redirect backs
|
// TODO: Handle redirect backs
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${baseUrl}/auth/session`, {
|
fetch(`${import.meta.env.VITE_SERVER_URL}/auth/session`, {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import {
|
|
||||||
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO,
|
|
||||||
VITE_GITHUB_PWA_TEMPLATE_REPO,
|
|
||||||
} from 'utils/constants';
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Progressive Web App (PWA)',
|
name: 'Progressive Web App (PWA)',
|
||||||
icon: 'pwa',
|
icon: 'pwa',
|
||||||
repoFullName: `${VITE_GITHUB_PWA_TEMPLATE_REPO}`,
|
repoFullName: `${import.meta.env.VITE_GITHUB_PWA_TEMPLATE_REPO}`,
|
||||||
isComingSoon: false,
|
isComingSoon: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
name: 'Image Upload PWA',
|
name: 'Image Upload PWA',
|
||||||
icon: 'pwa',
|
icon: 'pwa',
|
||||||
repoFullName: `${VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
|
repoFullName: `${import.meta.env.VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
|
||||||
isComingSoon: false,
|
isComingSoon: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import { useToast } from '../../shared/Toast';
|
|||||||
import { IconWithFrame } from '../../shared/IconWithFrame';
|
import { IconWithFrame } from '../../shared/IconWithFrame';
|
||||||
import { Heading } from '../../shared/Heading';
|
import { Heading } from '../../shared/Heading';
|
||||||
import { MockConnectGitCard } from './MockConnectGitCard';
|
import { MockConnectGitCard } from './MockConnectGitCard';
|
||||||
import { VITE_GITHUB_CLIENT_ID } from 'utils/constants';
|
|
||||||
|
|
||||||
const SCOPES = 'repo user';
|
const SCOPES = 'repo user';
|
||||||
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${VITE_GITHUB_CLIENT_ID}&scope=${encodeURIComponent(SCOPES)}`;
|
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${
|
||||||
|
import.meta.env.VITE_GITHUB_CLIENT_ID
|
||||||
|
}&scope=${encodeURIComponent(SCOPES)}`;
|
||||||
|
|
||||||
interface ConnectAccountInterface {
|
interface ConnectAccountInterface {
|
||||||
onAuth: (token: string) => void;
|
onAuth: (token: string) => void;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { cn } from 'utils/classnames';
|
|||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
import { SIDEBAR_MENU } from './constants';
|
import { SIDEBAR_MENU } from './constants';
|
||||||
import { UserSelect } from 'components/shared/UserSelect';
|
import { UserSelect } from 'components/shared/UserSelect';
|
||||||
import { baseUrl } from 'utils/constants';
|
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
mobileOpen?: boolean;
|
mobileOpen?: boolean;
|
||||||
@@ -84,7 +83,7 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
|||||||
}, [orgSlug]);
|
}, [orgSlug]);
|
||||||
|
|
||||||
const handleLogOut = useCallback(async () => {
|
const handleLogOut = useCallback(async () => {
|
||||||
await fetch(`${baseUrl}/auth/logout`, {
|
await fetch(`${import.meta.env.VITE_SERVER_URL}/auth/logout`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
|
||||||
import { Input } from '../Input';
|
|
||||||
|
|
||||||
export interface VerifyCodeInputProps {
|
|
||||||
code: string;
|
|
||||||
setCode: (code: string) => void;
|
|
||||||
submitCode: () => void;
|
|
||||||
loading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VerifyCodeInput = ({
|
|
||||||
code,
|
|
||||||
setCode,
|
|
||||||
submitCode,
|
|
||||||
loading,
|
|
||||||
}: VerifyCodeInputProps) => {
|
|
||||||
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
|
|
||||||
|
|
||||||
const handlePaste = (
|
|
||||||
e: React.ClipboardEvent<HTMLInputElement>,
|
|
||||||
i: number,
|
|
||||||
) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const pasteData = e.clipboardData.getData('text').replace(/\D/g, ''); // Only digits
|
|
||||||
if (pasteData.length > 0) {
|
|
||||||
let newCodeArray = code.split('');
|
|
||||||
for (let j = 0; j < pasteData.length && i + j < 6; j++) {
|
|
||||||
newCodeArray[i + j] = pasteData[j];
|
|
||||||
}
|
|
||||||
const newCode = newCodeArray.join('');
|
|
||||||
setCode(newCode);
|
|
||||||
const nextIndex = Math.min(i + pasteData.length, 5);
|
|
||||||
const nextInput = inputRefs.current[nextIndex];
|
|
||||||
if (nextInput) nextInput.focus();
|
|
||||||
if (!newCode.includes(' ')) {
|
|
||||||
submitCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = (
|
|
||||||
e: React.KeyboardEvent<HTMLInputElement>,
|
|
||||||
i: number,
|
|
||||||
) => {
|
|
||||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
||||||
|
|
||||||
if (e.key === 'Backspace') {
|
|
||||||
e.preventDefault();
|
|
||||||
const isEmpty = code[i] === ' ';
|
|
||||||
const newCode = !isEmpty
|
|
||||||
? code.slice(0, i) + ' ' + code.slice(i + 1, 6)
|
|
||||||
: code.slice(0, i - 1) + ' ' + code.slice(i, 6);
|
|
||||||
|
|
||||||
setCode(newCode.slice(0, 6));
|
|
||||||
|
|
||||||
if (i === 0 || !isEmpty) return;
|
|
||||||
const prev = inputRefs.current[i - 1];
|
|
||||||
if (prev) prev.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!e.key.match(/[0-9]/)) return;
|
|
||||||
|
|
||||||
e.preventDefault(); // Prevent the default event to avoid duplicate input
|
|
||||||
|
|
||||||
const newCode = code.slice(0, i) + e.key + code.slice(i + 1, 6);
|
|
||||||
setCode(newCode);
|
|
||||||
|
|
||||||
if (i === 5) {
|
|
||||||
submitCode();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = inputRefs.current[i + 1];
|
|
||||||
if (next) next.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, i: number) => {
|
|
||||||
const value = e.target.value.slice(-1);
|
|
||||||
if (!value.match(/[0-9]/)) return;
|
|
||||||
|
|
||||||
const newCode = code.slice(0, i) + value + code.slice(i + 1, 6);
|
|
||||||
setCode(newCode);
|
|
||||||
|
|
||||||
if (i < 5) {
|
|
||||||
const next = inputRefs.current[i + 1];
|
|
||||||
if (next) next.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newCode.includes(' ')) {
|
|
||||||
submitCode();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (inputRefs.current[0]) {
|
|
||||||
inputRefs.current[0].focus();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{code.split('').map((char, i) => (
|
|
||||||
<Input
|
|
||||||
key={i}
|
|
||||||
value={char === ' ' ? '' : char}
|
|
||||||
ref={(el) => (inputRefs.current[i] = el)}
|
|
||||||
onChange={(e) => handleChange(e, i)}
|
|
||||||
onPaste={(e) => handlePaste(e, i)}
|
|
||||||
onKeyDown={(e) => handleKeyDown(e, i)}
|
|
||||||
disabled={!!loading}
|
|
||||||
style={{ textAlign: 'center' }} // Add this line to center text
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { VerifyCodeInput } from './VerifyCodeInput';
|
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { VITE_WALLET_CONNECT_ID } from 'utils/constants';
|
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
if (!VITE_WALLET_CONNECT_ID) {
|
if (!import.meta.env.VITE_WALLET_CONNECT_ID) {
|
||||||
throw new Error('Error: REACT_APP_WALLET_CONNECT_ID env config is not set');
|
throw new Error('Error: REACT_APP_WALLET_CONNECT_ID env config is not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { GQLClientProvider } from './context/GQLClientContext';
|
|||||||
import { SERVER_GQL_PATH } from './constants';
|
import { SERVER_GQL_PATH } from './constants';
|
||||||
import { Toaster } from 'components/shared/Toast';
|
import { Toaster } from 'components/shared/Toast';
|
||||||
import { LogErrorBoundary } from 'utils/log-error';
|
import { LogErrorBoundary } from 'utils/log-error';
|
||||||
import { baseUrl } from 'utils/constants';
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
console.log(`v-${__VERSION__}`);
|
console.log(`v-${__VERSION__}`);
|
||||||
@@ -23,8 +22,8 @@ const root = ReactDOM.createRoot(
|
|||||||
document.getElementById('root') as HTMLElement,
|
document.getElementById('root') as HTMLElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(baseUrl, 'VITE_SERVER_URL is not set in env');
|
assert(import.meta.env.VITE_SERVER_URL, 'VITE_SERVER_URL is not set in env');
|
||||||
const gqlEndpoint = `${baseUrl}/${SERVER_GQL_PATH}`;
|
const gqlEndpoint = `${import.meta.env.VITE_SERVER_URL}/${SERVER_GQL_PATH}`;
|
||||||
|
|
||||||
const gqlClient = new GQLClient({ gqlEndpoint });
|
const gqlClient = new GQLClient({ gqlEndpoint });
|
||||||
|
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
|
|
||||||
import { Button } from 'components/shared/Button';
|
|
||||||
import {
|
|
||||||
ArrowRightCircleFilledIcon,
|
|
||||||
LoaderIcon,
|
|
||||||
} from 'components/shared/CustomIcon';
|
|
||||||
import { WavyBorder } from 'components/shared/WavyBorder';
|
|
||||||
import { VerifyCodeInput } from 'components/shared/VerifyCodeInput';
|
|
||||||
import { verifyAccessCode } from 'utils/accessCode';
|
|
||||||
|
|
||||||
type AccessMethod = 'accesscode' | 'passkey';
|
|
||||||
|
|
||||||
type Err = { type: AccessMethod; message: string };
|
|
||||||
|
|
||||||
type AccessCodeProps = {
|
|
||||||
onCorrectAccessCode: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AccessCode: React.FC<AccessCodeProps> = ({
|
|
||||||
onCorrectAccessCode,
|
|
||||||
}) => {
|
|
||||||
const [accessCode, setAccessCode] = useState(' ');
|
|
||||||
const [error, setError] = useState<Err | null>();
|
|
||||||
const [accessMethod, setAccessMethod] = useState<AccessMethod | false>(false);
|
|
||||||
|
|
||||||
async function validateAccessCode() {
|
|
||||||
setAccessMethod('accesscode');
|
|
||||||
try {
|
|
||||||
const isValidAccessCode = await verifyAccessCode(accessCode);
|
|
||||||
|
|
||||||
// add a pause for ux
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
||||||
if (isValidAccessCode) {
|
|
||||||
localStorage.setItem('accessCode', accessCode);
|
|
||||||
onCorrectAccessCode();
|
|
||||||
} else {
|
|
||||||
setError({
|
|
||||||
type: 'accesscode',
|
|
||||||
message: 'Invalid access code',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
setError({ type: 'accesscode', message: err.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loading = accessMethod;
|
|
||||||
const isValidAccessCodeLength = accessCode.trim().length === 5;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="self-stretch p-3 xs:p-6 flex-col justify-center items-center gap-5 flex">
|
|
||||||
<div className="self-stretch text-center text-sky-950 text-2xl font-medium font-display leading-tight">
|
|
||||||
Access Code
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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 flex-col gap-8 flex">
|
|
||||||
<div className="flex-col justify-start items-start gap-2 inline-flex">
|
|
||||||
<VerifyCodeInput
|
|
||||||
loading={!!loading}
|
|
||||||
code={accessCode}
|
|
||||||
setCode={setAccessCode}
|
|
||||||
submitCode={validateAccessCode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
rightIcon={
|
|
||||||
loading && loading === 'accesscode' ? (
|
|
||||||
<LoaderIcon className="animate-spin" />
|
|
||||||
) : (
|
|
||||||
<ArrowRightCircleFilledIcon height="16" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={validateAccessCode}
|
|
||||||
variant={'secondary'}
|
|
||||||
disabled={!accessCode || !isValidAccessCodeLength || !!loading}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
{error && error.type === 'accesscode' && (
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<div className="justify-center items-center gap-2 inline-flex">
|
|
||||||
<div className="text-red-500 text-sm">
|
|
||||||
Error: {error.message}.{' '}
|
|
||||||
<a href="/signup" className="underline">
|
|
||||||
Try again?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
|
|
||||||
import { AccessCode } from './AccessCode';
|
|
||||||
import { SignUp } from './SignUp';
|
|
||||||
|
|
||||||
type AccessSignUpProps = {
|
|
||||||
onDone: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AccessSignUp: React.FC<AccessSignUpProps> = ({ onDone }) => {
|
|
||||||
const [isValidAccessCode, setIsValidAccessCode] = useState<boolean>(
|
|
||||||
!!localStorage.getItem('accessCode'),
|
|
||||||
);
|
|
||||||
|
|
||||||
return isValidAccessCode ? (
|
|
||||||
<SignUp onDone={onDone} />
|
|
||||||
) : (
|
|
||||||
<AccessCode onCorrectAccessCode={() => setIsValidAccessCode(true)} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
turnkeySignin,
|
turnkeySignin,
|
||||||
turnkeySignup,
|
turnkeySignup,
|
||||||
} from 'utils/turnkey-frontend';
|
} from 'utils/turnkey-frontend';
|
||||||
import { verifyAccessCode } from 'utils/accessCode';
|
|
||||||
|
|
||||||
type Provider = 'google' | 'github' | 'apple' | 'email';
|
type Provider = 'google' | 'github' | 'apple' | 'email';
|
||||||
|
|
||||||
@@ -112,29 +111,6 @@ export const SignUp = ({ onDone }: Props) => {
|
|||||||
const loading = provider;
|
const loading = provider;
|
||||||
const emailValid = /.@./.test(email);
|
const emailValid = /.@./.test(email);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const validateAccessCode = async () => {
|
|
||||||
const accessCode = localStorage.getItem('accessCode');
|
|
||||||
if (!accessCode) {
|
|
||||||
redirectToSignup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await verifyAccessCode(accessCode);
|
|
||||||
} catch (err: any) {
|
|
||||||
redirectToSignup();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const redirectToSignup = () => {
|
|
||||||
localStorage.removeItem('accessCode');
|
|
||||||
window.location.href = '/signup';
|
|
||||||
};
|
|
||||||
|
|
||||||
validateAccessCode();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="self-stretch p-3 xs:p-6 flex-col justify-center items-center gap-5 flex">
|
<div className="self-stretch p-3 xs:p-6 flex-col justify-center items-center gap-5 flex">
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { snowball } from 'utils/use-snowball';
|
import { snowball } from 'utils/use-snowball';
|
||||||
import { Login } from './Login';
|
import { Login } from './Login';
|
||||||
|
import { SignUp } from './SignUp';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { Done } from './Done';
|
import { Done } from './Done';
|
||||||
import { AccessSignUp } from './AccessSignUp';
|
|
||||||
|
|
||||||
type Screen = 'login' | 'signup' | 'success';
|
type Screen = 'login' | 'signup' | 'success';
|
||||||
|
|
||||||
const DASHBOARD_URL = '/';
|
const DASHBOARD_URL = '/';
|
||||||
|
|
||||||
export const SnowballAuth: React.FC = () => {
|
export const SnowballAuth = () => {
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
const [screen, setScreen] = useState<Screen>(
|
const [screen, setScreen] = useState<Screen>(
|
||||||
path === '/login' ? 'login' : 'signup',
|
path === '/login' ? 'login' : 'signup',
|
||||||
@@ -31,7 +30,7 @@ export const SnowballAuth: React.FC = () => {
|
|||||||
|
|
||||||
if (screen === 'signup') {
|
if (screen === 'signup') {
|
||||||
return (
|
return (
|
||||||
<AccessSignUp
|
<SignUp
|
||||||
onDone={() => {
|
onDone={() => {
|
||||||
setScreen('success');
|
setScreen('success');
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { baseUrl } from './constants';
|
|
||||||
|
|
||||||
export async function verifyAccessCode(
|
|
||||||
accesscode: string,
|
|
||||||
): Promise<boolean | null> {
|
|
||||||
const res = await fetch(`${baseUrl}/auth/accesscode`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
accesscode,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
// If API returns a non-empty 200, this is a valid access code.
|
|
||||||
if (res.status == 200) {
|
|
||||||
const isValid = (await res.json()).isValid as boolean;
|
|
||||||
console.log('isValid', isValid);
|
|
||||||
return isValid;
|
|
||||||
} else if (res.status === 204) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`Unexpected response from access code endpoint: ${res.status}: ${await res.text()}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
export const baseUrl = import.meta.env.VITE_SERVER_URL;
|
|
||||||
export const PASSKEY_WALLET_RPID = import.meta.env.VITE_PASSKEY_WALLET_RPID!;
|
|
||||||
export const TURNKEY_BASE_URL = import.meta.env.VITE_TURNKEY_API_BASE_URL!;
|
|
||||||
export const VITE_GITHUB_PWA_TEMPLATE_REPO = import.meta.env
|
|
||||||
.VITE_GITHUB_PWA_TEMPLATE_REPO;
|
|
||||||
export const VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = import.meta.env
|
|
||||||
.VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO;
|
|
||||||
export const VITE_GITHUB_CLIENT_ID = import.meta.env.VITE_GITHUB_CLIENT_ID;
|
|
||||||
export const VITE_WALLET_CONNECT_ID = import.meta.env.VITE_WALLET_CONNECT_ID;
|
|
||||||
export const VITE_BUGSNAG_API_KEY = import.meta.env.VITE_BUGSNAG_API_KEY;
|
|
||||||
export const VITE_LIT_RELAY_API_KEY = import.meta.env.VITE_LIT_RELAY_API_KEY;
|
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
import React from 'react';
|
|
||||||
import Bugsnag from '@bugsnag/js';
|
import Bugsnag from '@bugsnag/js';
|
||||||
import BugsnagPluginReact from '@bugsnag/plugin-react';
|
import BugsnagPluginReact from '@bugsnag/plugin-react';
|
||||||
import BugsnagPerformance from '@bugsnag/browser-performance';
|
import BugsnagPerformance from '@bugsnag/browser-performance';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { VITE_BUGSNAG_API_KEY } from './constants';
|
const bugsnagApiKey = import.meta.env.VITE_BUGSNAG_API_KEY;
|
||||||
|
|
||||||
if (VITE_BUGSNAG_API_KEY) {
|
if (bugsnagApiKey) {
|
||||||
Bugsnag.start({
|
Bugsnag.start({
|
||||||
apiKey: VITE_BUGSNAG_API_KEY,
|
apiKey: bugsnagApiKey,
|
||||||
plugins: [new BugsnagPluginReact()],
|
plugins: [new BugsnagPluginReact()],
|
||||||
});
|
});
|
||||||
BugsnagPerformance.start({ apiKey: VITE_BUGSNAG_API_KEY });
|
BugsnagPerformance.start({ apiKey: bugsnagApiKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
export const errorLoggingEnabled = !!VITE_BUGSNAG_API_KEY;
|
export const errorLoggingEnabled = !!bugsnagApiKey;
|
||||||
|
|
||||||
export const LogErrorBoundary = VITE_BUGSNAG_API_KEY
|
export const LogErrorBoundary = bugsnagApiKey
|
||||||
? Bugsnag.getPlugin('react')!.createErrorBoundary(React)
|
? Bugsnag.getPlugin('react')!.createErrorBoundary(React)
|
||||||
: ({ children }: any) => children;
|
: ({ children }: any) => children;
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export function logError(error: Error) {
|
|||||||
}
|
}
|
||||||
console.error(...errors);
|
console.error(...errors);
|
||||||
|
|
||||||
if (VITE_BUGSNAG_API_KEY) {
|
if (import.meta.env.VITE_BUGSNAG_API_KEY) {
|
||||||
Bugsnag.notify(error);
|
Bugsnag.notify(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { SiweMessage } from 'siwe';
|
|||||||
import { PKPEthersWallet } from '@lit-protocol/pkp-ethers';
|
import { PKPEthersWallet } from '@lit-protocol/pkp-ethers';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { baseUrl } from './constants';
|
|
||||||
|
|
||||||
const domain = window.location.host;
|
const domain = window.location.host;
|
||||||
const origin = window.location.origin;
|
const origin = window.location.origin;
|
||||||
|
|
||||||
@@ -19,7 +17,7 @@ export async function signInWithEthereum(
|
|||||||
);
|
);
|
||||||
const signature = await wallet.signMessage(message);
|
const signature = await wallet.signMessage(message);
|
||||||
|
|
||||||
const res = await fetch(`${baseUrl}/auth/validate`, {
|
const res = await fetch(`${import.meta.env.VITE_SERVER_URL}/auth/validate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { TurnkeyClient, getWebAuthnAttestation } from '@turnkey/http';
|
import { TurnkeyClient, getWebAuthnAttestation } from '@turnkey/http';
|
||||||
import { WebauthnStamper } from '@turnkey/webauthn-stamper';
|
import { WebauthnStamper } from '@turnkey/webauthn-stamper';
|
||||||
|
|
||||||
import { baseUrl, PASSKEY_WALLET_RPID, TURNKEY_BASE_URL } from './constants';
|
const baseUrl = import.meta.env.VITE_SERVER_URL;
|
||||||
|
|
||||||
|
const PASSKEY_WALLET_RPID = import.meta.env.VITE_PASSKEY_WALLET_RPID!;
|
||||||
|
const TURNKEY_BASE_URL = import.meta.env.VITE_TURNKEY_API_BASE_URL!;
|
||||||
|
|
||||||
// All algorithms can be found here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
|
// All algorithms can be found here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
|
||||||
// We only support ES256, which is listed here
|
// We only support ES256, which is listed here
|
||||||
|
|||||||
@@ -5,17 +5,16 @@ import {
|
|||||||
LitGoogleAuth,
|
LitGoogleAuth,
|
||||||
LitPasskeyAuth,
|
LitPasskeyAuth,
|
||||||
} from '@snowballtools/auth-lit';
|
} from '@snowballtools/auth-lit';
|
||||||
import { VITE_LIT_RELAY_API_KEY } from './constants';
|
|
||||||
|
|
||||||
export const snowball = Snowball.withAuth({
|
export const snowball = Snowball.withAuth({
|
||||||
google: LitGoogleAuth.configure({
|
google: LitGoogleAuth.configure({
|
||||||
litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
|
litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
|
||||||
}),
|
}),
|
||||||
// apple: LitAppleAuth.configure({
|
// apple: LitAppleAuth.configure({
|
||||||
// litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
|
// litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
|
||||||
// }),
|
// }),
|
||||||
passkey: LitPasskeyAuth.configure({
|
passkey: LitPasskeyAuth.configure({
|
||||||
litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
|
litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
|
||||||
}),
|
}),
|
||||||
}).create({
|
}).create({
|
||||||
initialChain: SnowballChain.sepolia,
|
initialChain: SnowballChain.sepolia,
|
||||||
|
|||||||
Reference in New Issue
Block a user