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
chore: prettier
serenity
3 weeks ago
f5d65204
3cc3215f
+171
-159
20 changed files
expand all
collapse all
unified
split
src
components
Animated
UnderlinedLink.tsx
Auth
SignIn.tsx
Icons
Branding
StrandIcon.tsx
LucideAtSign.tsx
LucideCircleUserRound.tsx
LucideInfo.tsx
LucideLogIn.tsx
LucideSpool.tsx
Nav
NavBarUnauthed.tsx
integrations
tanstack-query
devtools.tsx
root-provider.tsx
lib
consts.ts
styles
theme.ts
router.tsx
routes
__root.tsx
_authed
oauth
callback.tsx
_authed.tsx
index.tsx
login.tsx
styles.css
+12
-12
src/components/Animated/UnderlinedLink.tsx
···
1
-
import { motion } from 'motion/react'
2
-
import { ReactNode } from 'react'
3
4
export const UnderlineLink = ({
5
href,
6
children,
7
-
className = 'text-[#cba6f7]',
8
-
underlineColor = 'bg-[#cba6f7]',
9
}: {
10
-
href: string
11
-
children: ReactNode
12
-
className?: string
13
-
underlineColor?: string
14
}) => {
15
return (
16
<motion.a
···
21
>
22
{children}
23
<motion.span
24
-
className={`absolute bottom-1 left-0 w-full h-0.25 origin-center ${underlineColor}`}
25
variants={{
26
initial: { scaleX: 0 },
27
hover: { scaleX: 1 },
28
}}
29
transition={{
30
-
type: 'spring',
31
duration: 0.25,
32
bounce: 0.3,
33
}}
34
/>
35
</motion.a>
36
-
)
37
-
}
···
1
+
import { motion } from "motion/react";
2
+
import { ReactNode } from "react";
3
4
export const UnderlineLink = ({
5
href,
6
children,
7
+
className = "text-[#cba6f7]",
8
+
underlineColor = "bg-[#cba6f7]",
9
}: {
10
+
href: string;
11
+
children: ReactNode;
12
+
className?: string;
13
+
underlineColor?: string;
14
}) => {
15
return (
16
<motion.a
···
21
>
22
{children}
23
<motion.span
24
+
className={`absolute bottom-1 left-0 h-0.25 w-full origin-center ${underlineColor}`}
25
variants={{
26
initial: { scaleX: 0 },
27
hover: { scaleX: 1 },
28
}}
29
transition={{
30
+
type: "spring",
31
duration: 0.25,
32
bounce: 0.3,
33
}}
34
/>
35
</motion.a>
36
+
);
37
+
};
+23
-22
src/components/Auth/SignIn.tsx
···
1
-
import { UnderlineLink } from '@/components/Animated/UnderlinedLink'
2
-
import { LucideAtSign } from '@/components/Icons/LucideAtSign'
3
-
import { LucideCircleUserRound } from '@/components/Icons/LucideCircleUserRound'
4
-
import { LucideInfo } from '@/components/Icons/LucideInfo'
5
-
import { LucideLogIn } from '@/components/Icons/LucideLogIn'
6
-
import { useState } from 'react'
7
8
export const SignIn = () => {
9
-
const [handle, setHandle] = useState('')
10
-
const isValidHandle = handle.includes('.')
11
12
return (
13
-
<div className="bg-surface0 flex flex-col items-center m-36 rounded-md max-w-1/4 px-6 py-4 border-1 border-surface1">
14
<LucideCircleUserRound
15
height={28}
16
width={28}
17
className="mt-4 mb-1"
18
/>
19
-
<h2 className="font-semibold text-xl tracking-wide">Sign In</h2>
20
<p className="text-subtext m-4">
21
-
Continue with an{' '}
22
<UnderlineLink
23
href="https://atproto.com"
24
underlineColor="bg-accent"
25
className="text-accent"
26
>
27
Atmosphere
28
-
</UnderlineLink>{' '}
29
account
30
</p>
31
<div className="w-full">
···
37
height={14}
38
width={14}
39
/>
40
-
<div className="absolute bottom-full left-1/2 mb-2 w-64 -translate-x-1/2 rounded-lg bg-surface1 px-3 py-2 text-sm opacity-0 pointer-events-none transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
41
If you have a Bluesky, Blacksky, Tangled, or any
42
-
other ATProto account, you can use that account's handle.
43
-
<div className="absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-surface1" />
0
44
</div>
45
</div>
46
</div>
47
-
<div className="flex items-center rounded-sm border border-surface1 overflow-hidden transition-all group has-[:focus]:border-accent">
48
-
<LucideAtSign className="text-subtext px-2 w-max h-full group-has-[:focus]:text-accent transition-all" />
49
-
<div className="w-px bg-surface1 group-has-[:focus]:bg-accent self-stretch transition-all" />
50
<input
51
placeholder="akshay.tngl.sh"
52
-
className="w-full p-1 focus:outline-0 rounded-tr-sm rounded-br-sm py-2 pl-2 peer transition-all"
53
onChange={(e) => setHandle(e.target.value)}
54
/>
55
</div>
56
</div>
57
<button
58
disabled={!isValidHandle}
59
-
className="cursor-pointer p-2 transition-all hover:bg-positive hover:text-crust rounded-sm w-full m-2 mt-6 mb-2 flex items-center gap-2 justify-center disabled:cursor-not-allowed hover:disabled:bg-surface1 hover:disabled:text-text bg-accent text-crust disabled:bg-surface1 disabled:text-text"
60
>
61
<p>Continue</p>
62
<LucideLogIn />
63
</button>
64
</div>
65
-
)
66
-
}
···
1
+
import { UnderlineLink } from "@/components/Animated/UnderlinedLink";
2
+
import { LucideAtSign } from "@/components/Icons/LucideAtSign";
3
+
import { LucideCircleUserRound } from "@/components/Icons/LucideCircleUserRound";
4
+
import { LucideInfo } from "@/components/Icons/LucideInfo";
5
+
import { LucideLogIn } from "@/components/Icons/LucideLogIn";
6
+
import { useState } from "react";
7
8
export const SignIn = () => {
9
+
const [handle, setHandle] = useState("");
10
+
const isValidHandle = handle.includes(".");
11
12
return (
13
+
<div className="bg-surface0 border-surface1 m-36 flex max-w-1/4 flex-col items-center rounded-md border-1 px-6 py-4">
14
<LucideCircleUserRound
15
height={28}
16
width={28}
17
className="mt-4 mb-1"
18
/>
19
+
<h2 className="text-xl font-semibold tracking-wide">Sign In</h2>
20
<p className="text-subtext m-4">
21
+
Continue with an{" "}
22
<UnderlineLink
23
href="https://atproto.com"
24
underlineColor="bg-accent"
25
className="text-accent"
26
>
27
Atmosphere
28
+
</UnderlineLink>{" "}
29
account
30
</p>
31
<div className="w-full">
···
37
height={14}
38
width={14}
39
/>
40
+
<div className="bg-surface1 pointer-events-none absolute bottom-full left-1/2 mb-2 w-64 -translate-x-1/2 rounded-lg px-3 py-2 text-sm opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
41
If you have a Bluesky, Blacksky, Tangled, or any
42
+
other ATProto account, you can use that account's
43
+
handle.
44
+
<div className="border-t-surface1 absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent" />
45
</div>
46
</div>
47
</div>
48
+
<div className="border-surface1 group has-[:focus]:border-accent flex items-center overflow-hidden rounded-sm border transition-all">
49
+
<LucideAtSign className="text-subtext group-has-[:focus]:text-accent h-full w-max px-2 transition-all" />
50
+
<div className="bg-surface1 group-has-[:focus]:bg-accent w-px self-stretch transition-all" />
51
<input
52
placeholder="akshay.tngl.sh"
53
+
className="peer w-full rounded-tr-sm rounded-br-sm p-1 py-2 pl-2 transition-all focus:outline-0"
54
onChange={(e) => setHandle(e.target.value)}
55
/>
56
</div>
57
</div>
58
<button
59
disabled={!isValidHandle}
60
+
className="hover:bg-positive hover:text-crust hover:disabled:bg-surface1 hover:disabled:text-text bg-accent text-crust disabled:bg-surface1 disabled:text-text m-2 mt-6 mb-2 flex w-full cursor-pointer items-center justify-center gap-2 rounded-sm p-2 transition-all disabled:cursor-not-allowed"
61
>
62
<p>Continue</p>
63
<LucideLogIn />
64
</button>
65
</div>
66
+
);
67
+
};
+3
-3
src/components/Icons/Branding/StrandIcon.tsx
···
1
-
import { LucideSpool } from '@/components/Icons/LucideSpool'
2
3
export const StrandIcon = () => {
4
-
return <LucideSpool />
5
-
}
···
1
+
import { LucideSpool } from "@/components/Icons/LucideSpool";
2
3
export const StrandIcon = () => {
4
+
return <LucideSpool />;
5
+
};
+2
-2
src/components/Icons/LucideAtSign.tsx
···
1
-
import { SVGProps } from 'react'
2
3
export function LucideAtSign(props: SVGProps<SVGSVGElement>) {
4
return (
···
21
<path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8" />
22
</g>
23
</svg>
24
-
)
25
}
···
1
+
import { SVGProps } from "react";
2
3
export function LucideAtSign(props: SVGProps<SVGSVGElement>) {
4
return (
···
21
<path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8" />
22
</g>
23
</svg>
24
+
);
25
}
+2
-2
src/components/Icons/LucideCircleUserRound.tsx
···
1
-
import { SVGProps } from 'react'
2
3
export function LucideCircleUserRound(props: SVGProps<SVGSVGElement>) {
4
return (
···
22
<circle cx="12" cy="12" r="10" />
23
</g>
24
</svg>
25
-
)
26
}
···
1
+
import { SVGProps } from "react";
2
3
export function LucideCircleUserRound(props: SVGProps<SVGSVGElement>) {
4
return (
···
22
<circle cx="12" cy="12" r="10" />
23
</g>
24
</svg>
25
+
);
26
}
+2
-2
src/components/Icons/LucideInfo.tsx
···
1
-
import { SVGProps } from 'react'
2
3
export function LucideInfo(props: SVGProps<SVGSVGElement>) {
4
return (
···
21
<path d="M12 16v-4m0-4h.01" />
22
</g>
23
</svg>
24
-
)
25
}
···
1
+
import { SVGProps } from "react";
2
3
export function LucideInfo(props: SVGProps<SVGSVGElement>) {
4
return (
···
21
<path d="M12 16v-4m0-4h.01" />
22
</g>
23
</svg>
24
+
);
25
}
+2
-2
src/components/Icons/LucideLogIn.tsx
···
1
-
import { SVGProps } from 'react'
2
3
export function LucideLogIn(props: SVGProps<SVGSVGElement>) {
4
return (
···
19
d="m10 17l5-5l-5-5m5 5H3m12-9h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"
20
/>
21
</svg>
22
-
)
23
}
···
1
+
import { SVGProps } from "react";
2
3
export function LucideLogIn(props: SVGProps<SVGSVGElement>) {
4
return (
···
19
d="m10 17l5-5l-5-5m5 5H3m12-9h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"
20
/>
21
</svg>
22
+
);
23
}
+2
-2
src/components/Icons/LucideSpool.tsx
···
1
-
import { SVGProps } from 'react'
2
3
export function LucideSpool(props: SVGProps<SVGSVGElement>) {
4
return (
···
21
<path d="m7 10.56l12.558-3.642A2 2 0 0 0 19.018 3H5a2 2 0 0 0-.558 3.921l1.115.32A2 2 0 0 1 7 9.163v7.178" />
22
</g>
23
</svg>
24
-
)
25
}
···
1
+
import { SVGProps } from "react";
2
3
export function LucideSpool(props: SVGProps<SVGSVGElement>) {
4
return (
···
21
<path d="m7 10.56l12.558-3.642A2 2 0 0 0 19.018 3H5a2 2 0 0 0-.558 3.921l1.115.32A2 2 0 0 1 7 9.163v7.178" />
22
</g>
23
</svg>
24
+
);
25
}
+7
src/components/Nav/NavBarUnauthed.tsx
···
0
0
0
0
0
0
0
···
1
+
export const NavBarUnauthed = () => {
2
+
return (
3
+
<div>
4
+
<p>Unauthed Nav Bar</p>
5
+
</div>
6
+
);
7
+
};
+3
-3
src/integrations/tanstack-query/devtools.tsx
···
1
-
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
2
3
export default {
4
-
name: 'Tanstack Query',
5
render: <ReactQueryDevtoolsPanel />,
6
-
}
···
1
+
import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools";
2
3
export default {
4
+
name: "Tanstack Query",
5
render: <ReactQueryDevtoolsPanel />,
6
+
};
+6
-6
src/integrations/tanstack-query/root-provider.tsx
···
1
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2
3
export function getContext() {
4
-
const queryClient = new QueryClient()
5
return {
6
queryClient,
7
-
}
8
}
9
10
export function Provider({
11
children,
12
queryClient,
13
}: {
14
-
children: React.ReactNode
15
-
queryClient: QueryClient
16
}) {
17
return (
18
<QueryClientProvider client={queryClient}>
19
{children}
20
</QueryClientProvider>
21
-
)
22
}
···
1
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
3
export function getContext() {
4
+
const queryClient = new QueryClient();
5
return {
6
queryClient,
7
+
};
8
}
9
10
export function Provider({
11
children,
12
queryClient,
13
}: {
14
+
children: React.ReactNode;
15
+
queryClient: QueryClient;
16
}) {
17
return (
18
<QueryClientProvider client={queryClient}>
19
{children}
20
</QueryClientProvider>
21
+
);
22
}
+47
-47
src/lib/consts.ts
···
1
-
const __DEV__oAuthCallbackUrl = 'http://127.0.0.1:8081/login/'
2
3
const oAuthScopes = [
4
-
'atproto',
5
6
-
'repo:sh.tangled.publicKey',
7
-
'repo:sh.tangled.repo',
8
-
'repo:sh.tangled.repo.pull',
9
-
'repo:sh.tangled.repo.pull.comment',
10
-
'repo:sh.tangled.repo.artifact',
11
-
'repo:sh.tangled.repo.issue',
12
-
'repo:sh.tangled.repo.issue.comment',
13
-
'repo:sh.tangled.repo.collaborator',
14
-
'repo:sh.tangled.knot',
15
-
'repo:sh.tangled.knot.member',
16
-
'repo:sh.tangled.spindle',
17
-
'repo:sh.tangled.spindle.member',
18
-
'repo:sh.tangled.graph.follow',
19
-
'repo:sh.tangled.feed.star',
20
-
'repo:sh.tangled.feed.reaction',
21
-
'repo:sh.tangled.label.definition',
22
-
'repo:sh.tangled.label.op',
23
-
'repo:sh.tangled.string',
24
-
'repo:sh.tangled.actor.profile',
25
26
-
'blob:*/*',
27
28
-
'rpc:sh.tangled.repo.create?aud=*',
29
-
'rpc:sh.tangled.repo.delete?aud=*',
30
-
'rpc:sh.tangled.repo.merge?aud=*',
31
-
'rpc:sh.tangled.repo.hiddenRef?aud=*',
32
-
'rpc:sh.tangled.repo.deleteBranch?aud=*',
33
-
'rpc:sh.tangled.repo.setDefaultBranch?aud=*',
34
-
'rpc:sh.tangled.repo.forkSync?aud=*',
35
-
'rpc:sh.tangled.repo.forkStatus?aud=*',
36
-
'rpc:sh.tangled.repo.mergeCheck?aud=*',
37
-
'rpc:sh.tangled.pipeline.cancelPipeline?aud=*',
38
-
'rpc:sh.tangled.repo.addSecret?aud=*',
39
-
'rpc:sh.tangled.repo.removeSecret?aud=*',
40
-
'rpc:sh.tangled.repo.listSecrets?aud=*',
41
-
]
42
43
const oAuthScopesString = oAuthScopes
44
.reduce((prev, curr) => `${prev} ${curr}`)
45
-
.trim()
46
47
export const __DEV__loopbackOAuthMetadata = {
48
client_id: `http://localhost?redirect_uri=${encodeURIComponent(__DEV__oAuthCallbackUrl)}&scope=${encodeURIComponent(oAuthScopesString)}`,
49
redirect_uris: [__DEV__oAuthCallbackUrl],
50
scope: oAuthScopesString,
51
-
token_endpoint_auth_method: 'none',
52
-
response_types: ['code'],
53
-
grant_types: ['authorization_code', 'refresh_token'],
54
-
application_type: 'web',
55
dpop_bound_access_tokens: true,
56
-
subject_type: 'public',
57
-
client_name: 'Strand Localhost',
58
-
}
59
60
-
export const DEFAULT_STALE_TIME = 5 * 60 * 1000
61
62
export const CONSTELLATION_URL = new URL(
63
-
'https://constellation.microcosm.blue/',
64
-
)
···
1
+
const __DEV__oAuthCallbackUrl = "http://127.0.0.1:8081/login/";
2
3
const oAuthScopes = [
4
+
"atproto",
5
6
+
"repo:sh.tangled.publicKey",
7
+
"repo:sh.tangled.repo",
8
+
"repo:sh.tangled.repo.pull",
9
+
"repo:sh.tangled.repo.pull.comment",
10
+
"repo:sh.tangled.repo.artifact",
11
+
"repo:sh.tangled.repo.issue",
12
+
"repo:sh.tangled.repo.issue.comment",
13
+
"repo:sh.tangled.repo.collaborator",
14
+
"repo:sh.tangled.knot",
15
+
"repo:sh.tangled.knot.member",
16
+
"repo:sh.tangled.spindle",
17
+
"repo:sh.tangled.spindle.member",
18
+
"repo:sh.tangled.graph.follow",
19
+
"repo:sh.tangled.feed.star",
20
+
"repo:sh.tangled.feed.reaction",
21
+
"repo:sh.tangled.label.definition",
22
+
"repo:sh.tangled.label.op",
23
+
"repo:sh.tangled.string",
24
+
"repo:sh.tangled.actor.profile",
25
26
+
"blob:*/*",
27
28
+
"rpc:sh.tangled.repo.create?aud=*",
29
+
"rpc:sh.tangled.repo.delete?aud=*",
30
+
"rpc:sh.tangled.repo.merge?aud=*",
31
+
"rpc:sh.tangled.repo.hiddenRef?aud=*",
32
+
"rpc:sh.tangled.repo.deleteBranch?aud=*",
33
+
"rpc:sh.tangled.repo.setDefaultBranch?aud=*",
34
+
"rpc:sh.tangled.repo.forkSync?aud=*",
35
+
"rpc:sh.tangled.repo.forkStatus?aud=*",
36
+
"rpc:sh.tangled.repo.mergeCheck?aud=*",
37
+
"rpc:sh.tangled.pipeline.cancelPipeline?aud=*",
38
+
"rpc:sh.tangled.repo.addSecret?aud=*",
39
+
"rpc:sh.tangled.repo.removeSecret?aud=*",
40
+
"rpc:sh.tangled.repo.listSecrets?aud=*",
41
+
];
42
43
const oAuthScopesString = oAuthScopes
44
.reduce((prev, curr) => `${prev} ${curr}`)
45
+
.trim();
46
47
export const __DEV__loopbackOAuthMetadata = {
48
client_id: `http://localhost?redirect_uri=${encodeURIComponent(__DEV__oAuthCallbackUrl)}&scope=${encodeURIComponent(oAuthScopesString)}`,
49
redirect_uris: [__DEV__oAuthCallbackUrl],
50
scope: oAuthScopesString,
51
+
token_endpoint_auth_method: "none",
52
+
response_types: ["code"],
53
+
grant_types: ["authorization_code", "refresh_token"],
54
+
application_type: "web",
55
dpop_bound_access_tokens: true,
56
+
subject_type: "public",
57
+
client_name: "Strand Localhost",
58
+
};
59
60
+
export const DEFAULT_STALE_TIME = 5 * 60 * 1000;
61
62
export const CONSTELLATION_URL = new URL(
63
+
"https://constellation.microcosm.blue/",
64
+
);
+2
-2
src/lib/styles/theme.ts
···
1
// TODO: restrict key to our expected strings
2
export const impurelyApplyThemeOverride = (key: string, value: string) => {
3
-
document.documentElement.style.setProperty(`--color-${key}`, value);
4
-
}
···
1
// TODO: restrict key to our expected strings
2
export const impurelyApplyThemeOverride = (key: string, value: string) => {
3
+
document.documentElement.style.setProperty(`--color-${key}`, value);
4
+
};
+10
-10
src/router.tsx
···
1
-
import { createRouter } from '@tanstack/react-router'
2
-
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
3
-
import * as TanstackQuery from './integrations/tanstack-query/root-provider'
4
5
// Import the generated route tree
6
-
import { routeTree } from './routeTree.gen'
7
8
// Create a new router instance
9
export const getRouter = () => {
10
-
const rqContext = TanstackQuery.getContext()
11
12
const router = createRouter({
13
routeTree,
···
15
...rqContext,
16
},
17
18
-
defaultPreload: 'intent',
19
-
})
20
21
setupRouterSsrQueryIntegration({
22
router,
23
queryClient: rqContext.queryClient,
24
-
})
25
26
-
return router
27
-
}
···
1
+
import { createRouter } from "@tanstack/react-router";
2
+
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
3
+
import * as TanstackQuery from "./integrations/tanstack-query/root-provider";
4
5
// Import the generated route tree
6
+
import { routeTree } from "./routeTree.gen";
7
8
// Create a new router instance
9
export const getRouter = () => {
10
+
const rqContext = TanstackQuery.getContext();
11
12
const router = createRouter({
13
routeTree,
···
15
...rqContext,
16
},
17
18
+
defaultPreload: "intent",
19
+
});
20
21
setupRouterSsrQueryIntegration({
22
router,
23
queryClient: rqContext.queryClient,
24
+
});
25
26
+
return router;
27
+
};
+16
-16
src/routes/__root.tsx
···
2
HeadContent,
3
Scripts,
4
createRootRouteWithContext,
5
-
} from '@tanstack/react-router'
6
-
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
7
-
import { TanStackDevtools } from '@tanstack/react-devtools'
8
9
-
import TanStackQueryDevtools from '../integrations/tanstack-query/devtools'
10
11
-
import appCss from '../styles.css?url'
12
13
-
import type { QueryClient } from '@tanstack/react-query'
14
15
interface MyRouterContext {
16
-
queryClient: QueryClient
17
}
18
19
export const Route = createRootRouteWithContext<MyRouterContext>()({
20
head: () => ({
21
meta: [
22
{
23
-
charSet: 'utf-8',
24
},
25
{
26
-
name: 'viewport',
27
-
content: 'width=device-width, initial-scale=1',
28
},
29
{
30
-
title: 'Strand',
31
},
32
],
33
links: [
34
{
35
-
rel: 'stylesheet',
36
href: appCss,
37
},
38
],
39
}),
40
41
shellComponent: RootDocument,
42
-
})
43
44
function RootDocument({ children }: { children: React.ReactNode }) {
45
return (
···
51
{children}
52
<TanStackDevtools
53
config={{
54
-
position: 'bottom-right',
55
}}
56
plugins={[
57
{
58
-
name: 'Tanstack Router',
59
render: <TanStackRouterDevtoolsPanel />,
60
},
61
TanStackQueryDevtools,
···
64
<Scripts />
65
</body>
66
</html>
67
-
)
68
}
···
2
HeadContent,
3
Scripts,
4
createRootRouteWithContext,
5
+
} from "@tanstack/react-router";
6
+
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
7
+
import { TanStackDevtools } from "@tanstack/react-devtools";
8
9
+
import TanStackQueryDevtools from "../integrations/tanstack-query/devtools";
10
11
+
import appCss from "../styles.css?url";
12
13
+
import type { QueryClient } from "@tanstack/react-query";
14
15
interface MyRouterContext {
16
+
queryClient: QueryClient;
17
}
18
19
export const Route = createRootRouteWithContext<MyRouterContext>()({
20
head: () => ({
21
meta: [
22
{
23
+
charSet: "utf-8",
24
},
25
{
26
+
name: "viewport",
27
+
content: "width=device-width, initial-scale=1",
28
},
29
{
30
+
title: "Strand",
31
},
32
],
33
links: [
34
{
35
+
rel: "stylesheet",
36
href: appCss,
37
},
38
],
39
}),
40
41
shellComponent: RootDocument,
42
+
});
43
44
function RootDocument({ children }: { children: React.ReactNode }) {
45
return (
···
51
{children}
52
<TanStackDevtools
53
config={{
54
+
position: "bottom-right",
55
}}
56
plugins={[
57
{
58
+
name: "Tanstack Router",
59
render: <TanStackRouterDevtoolsPanel />,
60
},
61
TanStackQueryDevtools,
···
64
<Scripts />
65
</body>
66
</html>
67
+
);
68
}
+4
-4
src/routes/_authed.tsx
···
1
-
import { createFileRoute, Outlet } from '@tanstack/react-router'
2
3
-
export const Route = createFileRoute('/_authed')({
4
component: RouteComponent,
5
-
})
6
7
function RouteComponent() {
8
return (
···
10
<p>Hello "/_authed"!</p>
11
<Outlet />
12
</div>
13
-
)
14
}
···
1
+
import { createFileRoute, Outlet } from "@tanstack/react-router";
2
3
+
export const Route = createFileRoute("/_authed")({
4
component: RouteComponent,
5
+
});
6
7
function RouteComponent() {
8
return (
···
10
<p>Hello "/_authed"!</p>
11
<Outlet />
12
</div>
13
+
);
14
}
+4
-4
src/routes/_authed/oauth/callback.tsx
···
1
-
import { createFileRoute } from '@tanstack/react-router'
2
3
-
export const Route = createFileRoute('/_authed/oauth/callback')({
4
component: RouteComponent,
5
-
})
6
7
function RouteComponent() {
8
-
return <div>Hello "/reserved/oauth/callback"!</div>
9
}
···
1
+
import { createFileRoute } from "@tanstack/react-router";
2
3
+
export const Route = createFileRoute("/_authed/oauth/callback")({
4
component: RouteComponent,
5
+
});
6
7
function RouteComponent() {
8
+
return <div>Hello "/reserved/oauth/callback"!</div>;
9
}
+5
-5
src/routes/index.tsx
···
1
-
import { createFileRoute, Link } from '@tanstack/react-router'
2
3
-
export const Route = createFileRoute('/')({ component: App })
4
5
function App() {
6
return (
7
-
<div className="min-w-screen flex items-center justify-center pt-8 flex-col gap-4">
8
<h1 className="text-xl font-bold">
9
The better frontend for the better forge.
10
</h1>
11
<Link
12
to="/login"
13
-
className="transition-all hover:bg-overlay0 bg-surface0 text-text p-2 rounded-xs"
14
>
15
Sign in
16
</Link>
17
</div>
18
-
)
19
}
···
1
+
import { createFileRoute, Link } from "@tanstack/react-router";
2
3
+
export const Route = createFileRoute("/")({ component: App });
4
5
function App() {
6
return (
7
+
<div className="flex min-w-screen flex-col items-center justify-center gap-4 pt-8">
8
<h1 className="text-xl font-bold">
9
The better frontend for the better forge.
10
</h1>
11
<Link
12
to="/login"
13
+
className="hover:bg-overlay0 bg-surface0 text-text rounded-xs p-2 transition-all"
14
>
15
Sign in
16
</Link>
17
</div>
18
+
);
19
}
+11
-7
src/routes/login.tsx
···
1
-
import { SignIn } from '@/components/Auth/SignIn'
2
-
import { createFileRoute } from '@tanstack/react-router'
0
3
4
-
export const Route = createFileRoute('/login')({
5
component: RouteComponent,
6
-
})
7
8
function RouteComponent() {
9
return (
10
-
<div className="min-w-screen flex items-center justify-center pt-8 flex-col gap-4">
11
-
<SignIn />
0
0
0
12
</div>
13
-
)
14
}
···
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
}
+8
-8
src/styles.css
···
1
-
@import 'tailwindcss';
2
3
-
@import '@fontsource-variable/hanken-grotesk';
4
-
@import '@fontsource/amiri';
5
-
@import '@fontsource/maple-mono';
6
-
@import '@fontsource/maple-mono/400-italic.css';
7
8
@theme {
9
-
--font-sans: 'Hanken Grotesk Variable', sans-serif;
10
-
--font-serif: 'Amiri', serif;
11
-
--font-mono: 'Maple Mono', monospace;
12
13
--color-crust: oklch(0.1286 0 0);
14
--color-mantle: oklch(0.1776 0 0);
···
1
+
@import "tailwindcss";
2
3
+
@import "@fontsource-variable/hanken-grotesk";
4
+
@import "@fontsource/amiri";
5
+
@import "@fontsource/maple-mono";
6
+
@import "@fontsource/maple-mono/400-italic.css";
7
8
@theme {
9
+
--font-sans: "Hanken Grotesk Variable", sans-serif;
10
+
--font-serif: "Amiri", serif;
11
+
--font-mono: "Maple Mono", monospace;
12
13
--color-crust: oklch(0.1286 0 0);
14
--color-mantle: oklch(0.1776 0 0);