alternative tangled frontend (extremely wip)

refactor: reorg and session gates

serenity 0019eaec 751268c1

+271 -91
+21
src/components/Nav/NavBar.tsx
··· 1 + import { NavBarAuthed } from "@/components/Nav/NavBarAuthed"; 2 + import { NavBarUnauthed } from "@/components/Nav/NavBarUnauthed"; 3 + import { useOAuth } from "@/lib/oauth"; 4 + 5 + export const NavBar = () => { 6 + const { session, client } = useOAuth(); 7 + 8 + if (client && session) { 9 + return ( 10 + <> 11 + <NavBarAuthed/> 12 + </> 13 + ); 14 + } 15 + 16 + return ( 17 + <> 18 + <NavBarUnauthed /> 19 + </> 20 + ); 21 + }
+40
src/components/Nav/NavBarAuthed.tsx
··· 1 + import { UnderlineIconRouterLink } from "@/components/Animated/UnderlineIconRouterLink"; 2 + import { StrandIcon } from "@/components/Icons/Branding/StrandIcon"; 3 + import { LucideLogIn } from "@/components/Icons/LucideLogIn"; 4 + 5 + export const NavBarAuthed = () => { 6 + return ( 7 + <div className="bg-surface0 flex w-full items-center justify-between p-3"> 8 + <div className="flex items-center gap-1"> 9 + <UnderlineIconRouterLink 10 + to="/" 11 + label="Strand" 12 + icon={StrandIcon()} 13 + iconClassName="text-text" 14 + labelClassName="text-text" 15 + underlineClassName="bg-text" 16 + className="text-lg font-semibold" 17 + /> 18 + </div> 19 + <div className="flex items-center gap-1"> 20 + <UnderlineIconRouterLink 21 + to="/login" 22 + label="Sign In" 23 + icon={LucideLogIn({})} 24 + iconClassName="text-crust" 25 + labelClassName="text-crust" 26 + underlineClassName="bg-crust" 27 + className="bg-accent rounded-sm p-1.5 pr-3 pl-3" 28 + position="right" 29 + iconTransitions={{ duration: 0.2, ease: "easeInOut" }} 30 + iconVariants={{ 31 + hover: { 32 + x: [0, 10, -10, 0], 33 + opacity: [1, 0, 0, 1], 34 + }, 35 + }} 36 + /> 37 + </div> 38 + </div> 39 + ); 40 + }
+29 -1
src/lib/oauth/index.tsx
··· 9 9 import { 10 10 createContext, 11 11 ReactNode, 12 + useCallback, 12 13 useContext, 13 14 useEffect, 14 15 useState, ··· 17 18 const OAuthContext = createContext<{ 18 19 client: BrowserOAuthClient | null; 19 20 session: OAuthSession | null; 21 + callbackHandler: (arg0: URLSearchParams) => void; 20 22 } | null>(null); 21 23 22 24 export const OAuthProvider = ({ children }: { children: ReactNode }) => { 23 25 const [client, setClient] = useState<BrowserOAuthClient | null>(null); 24 26 const [session, setSession] = useState<OAuthSession | null>(null); 27 + const callbackHandler = useCallback( 28 + (params: URLSearchParams) => { 29 + if (!client) 30 + throw new Error( 31 + "OAuth client was not initialised. Could not perform callback.", 32 + ); 33 + setClient((prevClient) => { 34 + if (!prevClient) 35 + throw new Error( 36 + "OAuth client was not initialised. Could not perform callback.", 37 + ); 38 + prevClient.callback(params); 39 + return Object.assign( 40 + Object.create(Object.getPrototypeOf(prevClient)), 41 + prevClient, 42 + ); 43 + }); 44 + }, 45 + [client], 46 + ); 25 47 26 48 useEffect(() => { 27 49 const oAuthClient = new BrowserOAuthClient({ ··· 57 79 const contextValue = { 58 80 client, 59 81 session, 82 + callbackHandler, 60 83 }; 61 84 62 85 return <OAuthContext value={contextValue}>{children}</OAuthContext>; ··· 65 88 export const useOAuth = () => { 66 89 const ctx = useContext(OAuthContext); 67 90 if (!ctx) 68 - throw new Error("useOAuthClient must be used within an AuthProvider"); 91 + throw new Error("useOAuth must be used within an AuthProvider"); 69 92 return ctx; 70 93 }; 71 94 ··· 73 96 const { client } = useOAuth(); 74 97 return client; 75 98 }; 99 + 100 + export const useOAuthSession = () => { 101 + const { session } = useOAuth(); 102 + return session; 103 + };
+70 -41
src/routeTree.gen.ts
··· 9 9 // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 10 11 11 import { Route as rootRouteImport } from './routes/__root' 12 - import { Route as LoginRouteImport } from './routes/login' 13 - import { Route as IndexRouteImport } from './routes/index' 14 - import { Route as OauthCallbackRouteImport } from './routes/oauth/callback' 12 + import { Route as LayoutRouteImport } from './routes/_layout' 13 + import { Route as LayoutIndexRouteImport } from './routes/_layout/index' 14 + import { Route as LayoutLoginRouteImport } from './routes/_layout/login' 15 + import { Route as LayoutOauthCallbackRouteImport } from './routes/_layout/oauth/callback' 15 16 16 - const LoginRoute = LoginRouteImport.update({ 17 - id: '/login', 18 - path: '/login', 17 + const LayoutRoute = LayoutRouteImport.update({ 18 + id: '/_layout', 19 19 getParentRoute: () => rootRouteImport, 20 20 } as any) 21 - const IndexRoute = IndexRouteImport.update({ 21 + const LayoutIndexRoute = LayoutIndexRouteImport.update({ 22 22 id: '/', 23 23 path: '/', 24 - getParentRoute: () => rootRouteImport, 24 + getParentRoute: () => LayoutRoute, 25 25 } as any) 26 - const OauthCallbackRoute = OauthCallbackRouteImport.update({ 26 + const LayoutLoginRoute = LayoutLoginRouteImport.update({ 27 + id: '/login', 28 + path: '/login', 29 + getParentRoute: () => LayoutRoute, 30 + } as any) 31 + const LayoutOauthCallbackRoute = LayoutOauthCallbackRouteImport.update({ 27 32 id: '/oauth/callback', 28 33 path: '/oauth/callback', 29 - getParentRoute: () => rootRouteImport, 34 + getParentRoute: () => LayoutRoute, 30 35 } as any) 31 36 32 37 export interface FileRoutesByFullPath { 33 - '/': typeof IndexRoute 34 - '/login': typeof LoginRoute 35 - '/oauth/callback': typeof OauthCallbackRoute 38 + '/login': typeof LayoutLoginRoute 39 + '/': typeof LayoutIndexRoute 40 + '/oauth/callback': typeof LayoutOauthCallbackRoute 36 41 } 37 42 export interface FileRoutesByTo { 38 - '/': typeof IndexRoute 39 - '/login': typeof LoginRoute 40 - '/oauth/callback': typeof OauthCallbackRoute 43 + '/login': typeof LayoutLoginRoute 44 + '/': typeof LayoutIndexRoute 45 + '/oauth/callback': typeof LayoutOauthCallbackRoute 41 46 } 42 47 export interface FileRoutesById { 43 48 __root__: typeof rootRouteImport 44 - '/': typeof IndexRoute 45 - '/login': typeof LoginRoute 46 - '/oauth/callback': typeof OauthCallbackRoute 49 + '/_layout': typeof LayoutRouteWithChildren 50 + '/_layout/login': typeof LayoutLoginRoute 51 + '/_layout/': typeof LayoutIndexRoute 52 + '/_layout/oauth/callback': typeof LayoutOauthCallbackRoute 47 53 } 48 54 export interface FileRouteTypes { 49 55 fileRoutesByFullPath: FileRoutesByFullPath 50 - fullPaths: '/' | '/login' | '/oauth/callback' 56 + fullPaths: '/login' | '/' | '/oauth/callback' 51 57 fileRoutesByTo: FileRoutesByTo 52 - to: '/' | '/login' | '/oauth/callback' 53 - id: '__root__' | '/' | '/login' | '/oauth/callback' 58 + to: '/login' | '/' | '/oauth/callback' 59 + id: 60 + | '__root__' 61 + | '/_layout' 62 + | '/_layout/login' 63 + | '/_layout/' 64 + | '/_layout/oauth/callback' 54 65 fileRoutesById: FileRoutesById 55 66 } 56 67 export interface RootRouteChildren { 57 - IndexRoute: typeof IndexRoute 58 - LoginRoute: typeof LoginRoute 59 - OauthCallbackRoute: typeof OauthCallbackRoute 68 + LayoutRoute: typeof LayoutRouteWithChildren 60 69 } 61 70 62 71 declare module '@tanstack/react-router' { 63 72 interface FileRoutesByPath { 64 - '/login': { 65 - id: '/login' 66 - path: '/login' 67 - fullPath: '/login' 68 - preLoaderRoute: typeof LoginRouteImport 73 + '/_layout': { 74 + id: '/_layout' 75 + path: '' 76 + fullPath: '' 77 + preLoaderRoute: typeof LayoutRouteImport 69 78 parentRoute: typeof rootRouteImport 70 79 } 71 - '/': { 72 - id: '/' 80 + '/_layout/': { 81 + id: '/_layout/' 73 82 path: '/' 74 83 fullPath: '/' 75 - preLoaderRoute: typeof IndexRouteImport 76 - parentRoute: typeof rootRouteImport 84 + preLoaderRoute: typeof LayoutIndexRouteImport 85 + parentRoute: typeof LayoutRoute 86 + } 87 + '/_layout/login': { 88 + id: '/_layout/login' 89 + path: '/login' 90 + fullPath: '/login' 91 + preLoaderRoute: typeof LayoutLoginRouteImport 92 + parentRoute: typeof LayoutRoute 77 93 } 78 - '/oauth/callback': { 79 - id: '/oauth/callback' 94 + '/_layout/oauth/callback': { 95 + id: '/_layout/oauth/callback' 80 96 path: '/oauth/callback' 81 97 fullPath: '/oauth/callback' 82 - preLoaderRoute: typeof OauthCallbackRouteImport 83 - parentRoute: typeof rootRouteImport 98 + preLoaderRoute: typeof LayoutOauthCallbackRouteImport 99 + parentRoute: typeof LayoutRoute 84 100 } 85 101 } 86 102 } 87 103 104 + interface LayoutRouteChildren { 105 + LayoutLoginRoute: typeof LayoutLoginRoute 106 + LayoutIndexRoute: typeof LayoutIndexRoute 107 + LayoutOauthCallbackRoute: typeof LayoutOauthCallbackRoute 108 + } 109 + 110 + const LayoutRouteChildren: LayoutRouteChildren = { 111 + LayoutLoginRoute: LayoutLoginRoute, 112 + LayoutIndexRoute: LayoutIndexRoute, 113 + LayoutOauthCallbackRoute: LayoutOauthCallbackRoute, 114 + } 115 + 116 + const LayoutRouteWithChildren = 117 + LayoutRoute._addFileChildren(LayoutRouteChildren) 118 + 88 119 const rootRouteChildren: RootRouteChildren = { 89 - IndexRoute: IndexRoute, 90 - LoginRoute: LoginRoute, 91 - OauthCallbackRoute: OauthCallbackRoute, 120 + LayoutRoute: LayoutRouteWithChildren, 92 121 } 93 122 export const routeTree = rootRouteImport 94 123 ._addFileChildren(rootRouteChildren)
+15
src/routes/_layout.tsx
··· 1 + import { NavBar } from "@/components/Nav/NavBar"; 2 + import { createFileRoute, Outlet } from "@tanstack/react-router"; 3 + 4 + export const Route = createFileRoute("/_layout")({ 5 + component: RouteComponent, 6 + }); 7 + 8 + function RouteComponent() { 9 + return ( 10 + <div className="flex min-w-screen flex-col items-center justify-center"> 11 + <NavBar /> 12 + <Outlet /> 13 + </div> 14 + ); 15 + }
+42
src/routes/_layout/index.tsx
··· 1 + import { Loading } from "@/components/Icons/Loading"; 2 + import { NavBarUnauthed } from "@/components/Nav/NavBarUnauthed"; 3 + import { useOAuth } from "@/lib/oauth"; 4 + import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; 5 + import { div } from "motion/react-client"; 6 + 7 + export const Route = createFileRoute("/_layout/")({ component: App }); 8 + 9 + function App() { 10 + return ( 11 + <div className="flex min-w-screen flex-col items-center justify-center gap-4"> 12 + <IndexInner /> 13 + </div> 14 + ); 15 + } 16 + 17 + const IndexInner = () => { 18 + const { session, client } = useOAuth(); 19 + if (!client) return <Loading />; 20 + 21 + if (client && session) { 22 + return ( 23 + <> 24 + <p>Logged in</p> 25 + </> 26 + ); 27 + } 28 + 29 + return ( 30 + <div className="flex flex-col gap-4 items-center justify-center pt-4"> 31 + <h1 className="text-xl font-bold"> 32 + The better frontend for the better forge. 33 + </h1> 34 + <Link 35 + to="/login" 36 + className="hover:bg-overlay0 bg-surface0 text-text rounded-xs p-2 transition-all" 37 + > 38 + Sign in 39 + </Link> 40 + </div> 41 + ); 42 + };
+25
src/routes/_layout/login.tsx
··· 1 + import { SignIn } from "@/components/Auth/SignIn"; 2 + import { NavBarUnauthed } from "@/components/Nav/NavBarUnauthed"; 3 + import { useOAuth } from "@/lib/oauth"; 4 + import { createFileRoute, useNavigate } from "@tanstack/react-router"; 5 + 6 + export const Route = createFileRoute("/_layout/login")({ 7 + component: RouteComponent, 8 + }); 9 + 10 + function RouteComponent() { 11 + const { session, client } = useOAuth(); 12 + const navigate = useNavigate(); 13 + 14 + if (client && session) { 15 + navigate({ to: "/" }); 16 + } 17 + 18 + return ( 19 + <> 20 + <div className="flex w-full justify-center pt-8"> 21 + <SignIn /> 22 + </div> 23 + </> 24 + ); 25 + }
+28
src/routes/_layout/oauth/callback.tsx
··· 1 + import { Loading } from "@/components/Icons/Loading"; 2 + import { useOAuthSession } from "@/lib/oauth"; 3 + import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router"; 4 + import { useEffect } from "react"; 5 + 6 + export const Route = createFileRoute("/_layout/oauth/callback")({ 7 + component: RouteComponent, 8 + }); 9 + 10 + function RouteComponent() { 11 + const session = useOAuthSession(); 12 + 13 + if (!session) return <Loading />; 14 + 15 + const navigate = useNavigate(); 16 + 17 + useEffect(() => { 18 + if (session) { 19 + navigate({ to: "/" }); 20 + } 21 + }, [session, navigate]); 22 + 23 + return ( 24 + <div> 25 + <p>Signed in as {session.did}</p> 26 + </div> 27 + ); 28 + }
-21
src/routes/index.tsx
··· 1 - import { NavBarUnauthed } from "@/components/Nav/NavBarUnauthed"; 2 - import { createFileRoute, Link } from "@tanstack/react-router"; 3 - 4 - export const Route = createFileRoute("/")({ component: App }); 5 - 6 - function App() { 7 - return ( 8 - <div className="flex min-w-screen flex-col items-center justify-center gap-4"> 9 - <NavBarUnauthed /> 10 - <h1 className="text-xl font-bold"> 11 - The better frontend for the better forge. 12 - </h1> 13 - <Link 14 - to="/login" 15 - className="hover:bg-overlay0 bg-surface0 text-text rounded-xs p-2 transition-all" 16 - > 17 - Sign in 18 - </Link> 19 - </div> 20 - ); 21 - }
-18
src/routes/login.tsx
··· 1 - import { SignIn } from "@/components/Auth/SignIn"; 2 - import { NavBarUnauthed } from "@/components/Nav/NavBarUnauthed"; 3 - import { createFileRoute } from "@tanstack/react-router"; 4 - 5 - export const Route = createFileRoute("/login")({ 6 - component: RouteComponent, 7 - }); 8 - 9 - function RouteComponent() { 10 - return ( 11 - <div className="flex min-w-screen flex-col items-center"> 12 - <NavBarUnauthed /> 13 - <div className="flex w-full justify-center pt-8"> 14 - <SignIn /> 15 - </div> 16 - </div> 17 - ); 18 - }
-9
src/routes/oauth/callback.tsx
··· 1 - import { createFileRoute } from "@tanstack/react-router"; 2 - 3 - export const Route = createFileRoute("/oauth/callback")({ 4 - component: RouteComponent, 5 - }); 6 - 7 - function RouteComponent() { 8 - return <div>Hello "/reserved/oauth/callback"!</div>; 9 - }
+1 -1
vite.config.ts
··· 6 6 import tailwindcss from "@tailwindcss/vite"; 7 7 import { nitro } from "nitro/vite"; 8 8 9 - const SERVER_HOST = '127.0.0.1'; 9 + const SERVER_HOST = "127.0.0.1"; 10 10 const SERVER_PORT = 3000; 11 11 12 12 const config = defineConfig({