fix: replace frontend with Rust OPAQUE API + Flutter keypad UI

- Full OPAQUE auth flow via WASM client SDK (client-wasm crate)
- New user: Key Register → Key Login → Code Register (icon selection) → done
- Existing user: Key Login → get login-data → icon keypad → Code Login → done
- Icon-based keypad matching Flutter design:
  - 2 cols portrait, 3 cols landscape
  - Key tiles with 3-col sub-grid of icons
  - Navy border press feedback
  - Dot display with backspace + submit
- SVGs rendered as-is (no color manipulation)
- SusiPage with Login/Signup tabs
- LoginKeypadPage and SignupKeypadPage for code flows
- Secret key display/copy on signup
- Unit tests for Keypad component
- WASM pkg bundled locally (no external dep)
This commit is contained in:
2026-01-29 17:05:32 +00:00
parent 7494bf7520
commit 5c3217e3d5
36 changed files with 2045 additions and 1149 deletions

View File

@@ -1,41 +1,41 @@
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { AuthContext, useAuthState } from '@/hooks/useAuth';
import { ROUTES } from '@/lib/types';
import { Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider, useAuth } from './hooks/useAuth'
import Layout from './components/Layout'
import SusiPage from './pages/SusiPage'
import LoginKeypadPage from './pages/LoginKeypadPage'
import SignupKeypadPage from './pages/SignupKeypadPage'
import HomePage from './pages/HomePage'
import NotFoundPage from './pages/NotFoundPage'
const HomePage = lazy(() => import('@/pages/HomePage').then((m) => ({ default: m.HomePage })));
const LoginPage = lazy(() => import('@/pages/LoginPage').then((m) => ({ default: m.LoginPage })));
const SignupPage = lazy(() => import('@/pages/SignupPage').then((m) => ({ default: m.SignupPage })));
const AdminPage = lazy(() => import('@/pages/AdminPage').then((m) => ({ default: m.AdminPage })));
const DeveloperPage = lazy(() => import('@/pages/DeveloperPage').then((m) => ({ default: m.DeveloperPage })));
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated } = useAuth()
if (!isAuthenticated) return <Navigate to="/" replace />
return <>{children}</>
}
function Loading() {
return (
<div className="flex items-center justify-center min-h-[50vh]">
<div className="w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
</div>
);
function GuestRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated } = useAuth()
if (isAuthenticated) return <Navigate to="/home" replace />
return <>{children}</>
}
export default function App() {
const auth = useAuthState();
return (
<AuthContext.Provider value={auth}>
<BrowserRouter>
<Suspense fallback={<Loading />}>
<Routes>
<Route element={<Layout />}>
<Route path={ROUTES.HOME} element={<HomePage />} />
<Route path={ROUTES.LOGIN} element={<LoginPage />} />
<Route path={ROUTES.SIGNUP} element={<SignupPage />} />
<Route path={ROUTES.ADMIN} element={<AdminPage />} />
<Route path={ROUTES.DEVELOPER} element={<DeveloperPage />} />
</Route>
</Routes>
</Suspense>
</BrowserRouter>
</AuthContext.Provider>
);
<AuthProvider>
<Routes>
<Route element={<Layout />}>
{/* Guest routes */}
<Route path="/" element={<GuestRoute><SusiPage /></GuestRoute>} />
<Route path="/login-keypad" element={<GuestRoute><LoginKeypadPage /></GuestRoute>} />
<Route path="/signup-keypad" element={<GuestRoute><SignupKeypadPage /></GuestRoute>} />
{/* Protected routes */}
<Route path="/home" element={<ProtectedRoute><HomePage /></ProtectedRoute>} />
{/* 404 */}
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</AuthProvider>
)
}