diff --git a/app/components/Header.tsx b/app/components/Header.tsx new file mode 100644 index 0000000..f31f576 --- /dev/null +++ b/app/components/Header.tsx @@ -0,0 +1,109 @@ +import { GearIcon, GlobeIcon, LockIcon, PaperAirplaneIcon, PeopleIcon, PersonIcon, ServerIcon } from '@primer/octicons-react' +import { Form } from '@remix-run/react' + +import { cn } from '~/utils/cn' +import { type Context } from '~/utils/config' +import { type SessionData } from '~/utils/sessions' + +import Menu from './Menu' +import TabLink from './TabLink' + +type Properties = { + readonly data?: Context & { user?: SessionData['user'] }; +} + +type LinkProperties = { + readonly href: string; + readonly text: string; + readonly isMenu?: boolean; +} + +function Link({ href, text, isMenu }: LinkProperties) { + return ( + + ) +} + +export default function Header({ data }: Properties) { + return ( +
+
+
+ +

Headplane

+
+
+ + + + {data?.user ? ( + + + + + + +

{data.user.name}

+

{data.user.email}

+
+ + + + + + + + + + +
+ +
+
+
+
+ ) : undefined} +
+
+ +
+ ) +} diff --git a/app/components/TabLink.tsx b/app/components/TabLink.tsx index 935547c..2b31284 100644 --- a/app/components/TabLink.tsx +++ b/app/components/TabLink.tsx @@ -13,8 +13,9 @@ export default function TabLink({ name, to, icon }: Properties) { clsx( - 'flex items-center gap-x-2 p-2 border-b-2 text-md', - isActive ? 'border-white' : 'border-transparent' + 'flex items-center gap-x-2 p-2 border-b-2 text-md text-nowrap', + isActive ? 'border-white' : 'border-transparent', + isPending && 'animate-pulse' )} > {icon} {name} diff --git a/app/routes/_data.tsx b/app/routes/_data.tsx index 373bc9f..7d5dd92 100644 --- a/app/routes/_data.tsx +++ b/app/routes/_data.tsx @@ -1,10 +1,8 @@ -import { Cog8ToothIcon, CpuChipIcon, GlobeAltIcon, LockClosedIcon, ServerStackIcon, UserCircleIcon, UsersIcon } from '@heroicons/react/24/outline' import { type LoaderFunctionArgs, redirect } from '@remix-run/node' -import { Form, Outlet, useLoaderData } from '@remix-run/react' +import { Outlet, useLoaderData } from '@remix-run/react' import { ErrorPopup } from '~/components/Error' -import Menu from '~/components/Menu' -import TabLink from '~/components/TabLink' +import Header from '~/components/Header' import { getContext } from '~/utils/config' import { HeadscaleError, pull } from '~/utils/headscale' import { destroySession, getSession } from '~/utils/sessions' @@ -20,7 +18,6 @@ export async function loader({ request }: LoaderFunctionArgs) { await pull('v1/apikey', session.get('hsApiKey')!) } catch (error) { if (error instanceof HeadscaleError) { - console.error(error) // Safest to just redirect to login if we can't pull return redirect('/login', { headers: { @@ -46,58 +43,9 @@ export default function Layout() { return ( <> -
- -
+
-
+
@@ -107,21 +55,7 @@ export default function Layout() { export function ErrorBoundary() { return ( <> -
- -
+
) diff --git a/app/utils/config.ts b/app/utils/config.ts index 341dbc7..bbc8093 100644 --- a/app/utils/config.ts +++ b/app/utils/config.ts @@ -196,7 +196,7 @@ export function registerConfigWatcher() { }) } -type Context = { +export type Context = { hasDockerSock: boolean; hasConfig: boolean; hasConfigWrite: boolean; diff --git a/app/utils/sessions.ts b/app/utils/sessions.ts index 1e9f087..39d93c3 100644 --- a/app/utils/sessions.ts +++ b/app/utils/sessions.ts @@ -1,6 +1,6 @@ import { createCookieSessionStorage } from '@remix-run/node' // Or cloudflare/deno -type SessionData = { +export type SessionData = { hsApiKey: string; authState: string; authNonce: string;