Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
// ./src/App.tsx

import { Route, Routes } from "react-router";
import { Route, Routes, useLocation } from "react-router";
import { BrowserRouter } from "react-router-dom";
import { ChakraProvider } from "@chakra-ui/react";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

import CreateSafe from "./pages/CreateSafe";
import CreateSafe2026 from "./pages/CreateSafe2026";
import theme from "./theme";
import TopNav from "./components/TopNav";

import { StoreModel } from "./types";
import { StoreProvider, createStore, action, computed } from "easy-peasy";

// Routes that use the 2026 design (which has its own header)
const ROUTES_WITH_OWN_HEADER = ["/create-2026"];

const store = createStore<StoreModel>({
// State
identity: null,
Expand Down Expand Up @@ -50,17 +54,30 @@ const store = createStore<StoreModel>({
}),
});

// Inner component that can use routing hooks (must be inside BrowserRouter)
function AppContent() {
const location = useLocation();
const showTopNav = !ROUTES_WITH_OWN_HEADER.includes(location.pathname);

return (
<>
{showTopNav && <TopNav />}

<Routes>
<Route path="/create-2026" element={<CreateSafe2026 />} />
<Route path="/*" element={<CreateSafe />} />
</Routes>
<ToastContainer />
</>
);
}

function App() {
return (
<StoreProvider store={store}>
<ChakraProvider theme={theme}>
<BrowserRouter>
<TopNav />

<Routes>
<Route path="/*" element={<CreateSafe />} />
</Routes>
<ToastContainer />
<AppContent />
</BrowserRouter>
</ChakraProvider>
</StoreProvider>
Expand Down
188 changes: 188 additions & 0 deletions client/src/components/2026/ConfirmKeyImageWorks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { useState, useRef } from "react";
import { Box, Flex, Text, Input, UnorderedList, ListItem } from "@chakra-ui/react";
import { FiUpload } from "react-icons/fi";
import PageHeader from "./PageHeader";
import StepIndicator from "./StepIndicator";

interface ConfirmKeyImageWorksProps {
onBack: () => void;
onExit: () => void;
onComplete: (keyImageFile: File) => void;
onSignIn: () => void;
currentStep?: number;
}

const ConfirmKeyImageWorks = ({
onBack,
onExit,
onComplete,
onSignIn,
currentStep = 3,
}: ConfirmKeyImageWorksProps) => {
const [selectedFile, setSelectedFile] = useState<File | undefined>();
const [isDragging, setIsDragging] = useState(false);
const fileRef = useRef<HTMLInputElement>(null);

const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
setIsDragging(true);
};

const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
setIsDragging(false);
};

const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
};

const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
setIsDragging(false);
const file = event.dataTransfer.files[0];
if (file) {
setSelectedFile(file);
}
};

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
setSelectedFile(file);
}
};

const browseFile = () => fileRef.current?.click();

const handleComplete = () => {
if (selectedFile) {
onComplete(selectedFile);
}
};

return (
<Box minH="100vh" bg="#F8F9FB">
<PageHeader onBack={onBack} onExit={onExit} />

<Box maxW="400px" mx="auto" px="1.5rem" pb="2rem">
{/* Title Section */}
<Text fontSize="28px" fontWeight="600" color="#1A2B4A" mb="0.5rem">
Create account
</Text>
<Text fontSize="15px" color="#6B7C93" mb="1.5rem">
Start keeping your records safe
</Text>

{/* Step Indicator */}
<StepIndicator currentStep={currentStep} totalSteps={3} />

{/* Confirm Key Image Section */}
<Text fontSize="18px" fontWeight="600" color="#1A2B4A" mb="0.5rem">
Confirm your key image works
</Text>
<Text fontSize="14px" color="#6B7C93" mb="1.5rem" lineHeight="1.5">
Upload the key image you just saved to make sure it works correctly.
</Text>

{/* Upload Area */}
<Box
border="2px dashed"
borderColor={isDragging ? "#1B5086" : "#D1D5DB"}
borderRadius="12px"
p="2.5rem"
bg={isDragging ? "#F0F7FF" : "white"}
cursor="pointer"
onClick={browseFile}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
transition="all 0.2s ease"
mb="1.5rem"
_hover={{
borderColor: "#1B5086",
bg: "#FAFBFC",
}}
>
<Input
type="file"
ref={fileRef}
onChange={handleFileChange}
accept="image/png"
hidden
/>

<Flex
direction="column"
alignItems="center"
justifyContent="center"
gap="0.75rem"
>
<Box color="#6B7C93">
<FiUpload size={36} />
</Box>
<Text fontSize="15px" fontWeight="400" color="#1A2B4A">
{selectedFile ? selectedFile.name : "Upload your key image"}
</Text>
<Text fontSize="13px" color="#9CA3AF">
The file you just downloaded
</Text>
</Flex>
</Box>

{/* Complete Setup Button */}
<Flex justifyContent="center" mb="1.5rem">
<Text
fontSize="14px"
color={selectedFile ? "#1A2B4A" : "#9CA3AF"}
cursor={selectedFile ? "pointer" : "not-allowed"}
onClick={selectedFile ? handleComplete : undefined}
_hover={{
color: selectedFile ? "#1B5086" : "#9CA3AF",
}}
>
Complete setup
</Text>
</Flex>

{/* Tips Box */}
<Box bg="#F3F4F6" borderRadius="12px" p="1rem" mb="1.5rem">
<UnorderedList
spacing={2}
color="#4A5568"
fontSize="13px"
lineHeight="1.5"
ml="1rem"
>
<ListItem>Use the exact file you just downloaded</ListItem>
<ListItem>Don't use a screenshot or re-saved version</ListItem>
<ListItem>If it doesn't work, go back and download again</ListItem>
</UnorderedList>
</Box>

{/* Sign In Link */}
<Flex justifyContent="center" alignItems="center" gap="0.5rem">
<Text fontSize="14px" color="#6B7C93">
Already have an account?
</Text>
<Text
fontSize="14px"
fontWeight="600"
color="#1A2B4A"
cursor="pointer"
onClick={onSignIn}
_hover={{
color: "#1B5086",
textDecoration: "underline",
}}
>
Sign in
</Text>
</Flex>
</Box>
</Box>
);
};

export default ConfirmKeyImageWorks;
Loading
Loading