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