feat: continue moving older components to aria

This commit is contained in:
Aarnav Tale
2024-05-05 23:38:41 -04:00
parent ee42016a67
commit 87430ccf7b
17 changed files with 184 additions and 186 deletions
+29 -19
View File
@@ -1,26 +1,36 @@
import clsx from 'clsx' import { type Dispatch, type SetStateAction } from 'react'
import { type ButtonHTMLAttributes, type DetailedHTMLProps } from 'react' import { Button as AriaButton } from 'react-aria-components'
type Properties = { import { cn } from '~/utils/cn'
readonly variant?: 'emphasized' | 'normal' | 'destructive';
} & DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
export default function Action(properties: Properties) { type ButtonProperties = Parameters<typeof AriaButton>[0] & {
readonly control?: [boolean, Dispatch<SetStateAction<boolean>>];
readonly variant?: 'heavy' | 'light';
}
export default function Button(properties: ButtonProperties) {
return ( return (
<button <AriaButton
type='button'
{...properties} {...properties}
className={clsx( className={cn(
'focus:outline-none focus:ring focus:ring-1', 'w-fit text-sm rounded-lg px-4 py-2',
'focus:ring-blue-500 dark:focus:ring-blue-300', properties.variant === 'heavy'
properties.className, ? 'bg-main-700 dark:bg-main-800'
properties.disabled && 'opacity-50 cursor-not-allowed', : 'bg-main-200 dark:bg-main-700/30',
properties.variant === 'destructive' ? 'text-red-700 dark:text-red-500' : '', properties.variant === 'heavy'
properties.variant === 'emphasized' ? 'rounded-lg px-4 py-2 bg-gray-800 dark:bg-gray-700 text-white' : '', ? 'hover:bg-main-800 dark:hover:bg-main-700'
!properties.variant || properties.variant === 'normal' ? 'text-blue-700 dark:text-blue-400' : '' : 'hover:bg-main-300 dark:hover:bg-main-600/30',
properties.variant === 'heavy'
? 'text-white'
: 'text-ui-700 dark:text-ui-300',
properties.isDisabled && 'opacity-50 cursor-not-allowed',
properties.className
)} )}
> // If control is passed, set the state value
{properties.children} onPress={properties.control ? () => {
</button> properties.control?.[1](true)
} : undefined}
/>
) )
} }
+40 -5
View File
@@ -1,14 +1,47 @@
import clsx from 'clsx'
import { type HTMLProps } from 'react' import { type HTMLProps } from 'react'
import { Heading as AriaHeading } from 'react-aria-components'
type Properties = HTMLProps<HTMLDivElement> import { cn } from '~/utils/cn'
export default function Card(properties: Properties) { function Title(properties: Parameters<typeof AriaHeading>[0]) {
return (
<AriaHeading
{...properties}
slot='title'
className={cn(
'text-lg font-semibold leading-6 mb-5',
properties.className
)}
/>
)
}
function Text(properties: React.HTMLProps<HTMLParagraphElement>) {
return (
<p
{...properties}
className={cn(
'text-base leading-6 my-0',
properties.className
)}
/>
)
}
type Properties = HTMLProps<HTMLDivElement> & {
variant?: 'raised' | 'flat';
}
function Card(properties: Properties) {
return ( return (
<div <div
{...properties} {...properties}
className={clsx( className={cn(
'p-4 md:p-6 border dark:border-zinc-700 rounded-lg', 'w-full max-w-md overflow-hidden rounded-xl p-4',
properties.variant === 'flat'
? 'bg-transparent shadow-none'
: 'bg-ui-50 dark:bg-ui-900 shadow-sm',
'border border-ui-200 dark:border-ui-700',
properties.className properties.className
)} )}
> >
@@ -16,3 +49,5 @@ export default function Card(properties: Properties) {
</div> </div>
) )
} }
export default Object.assign(Card, { Title, Text })
+1
View File
@@ -23,6 +23,7 @@ function Button(properties: ButtonProperties) {
className={cn( className={cn(
'w-fit text-sm rounded-lg px-4 py-2', 'w-fit text-sm rounded-lg px-4 py-2',
'bg-main-700 dark:bg-main-800 text-white', 'bg-main-700 dark:bg-main-800 text-white',
'hover:bg-main-800 dark:hover:bg-main-700',
properties.isDisabled && 'opacity-50 cursor-not-allowed', properties.isDisabled && 'opacity-50 cursor-not-allowed',
properties.className properties.className
)} )}
+27 -33
View File
@@ -1,52 +1,46 @@
import { AlertIcon } from '@primer/octicons-react' import { AlertIcon } from '@primer/octicons-react'
import { isRouteErrorResponse, useRouteError } from '@remix-run/react' import { isRouteErrorResponse, useRouteError } from '@remix-run/react'
import { useState } from 'react'
import { cn } from '~/utils/cn' import { cn } from '~/utils/cn'
import Card from './Card'
import Code from './Code' import Code from './Code'
import Dialog from './Dialog'
type Properties = { type Properties = {
readonly type?: 'full' | 'embedded'; readonly type?: 'full' | 'embedded';
} }
export function ErrorPopup({ type = 'full' }: Properties) { export function ErrorPopup({ type = 'full' }: Properties) {
// eslint-disable-next-line react/hook-use-state
const open = useState(true)
const error = useRouteError() const error = useRouteError()
const routing = isRouteErrorResponse(error) const routing = isRouteErrorResponse(error)
const message = (error instanceof Error ? error.message : 'An unexpected error occurred') const message = (error instanceof Error ? error.message : 'An unexpected error occurred')
return ( return (
<Dialog> <div
<Dialog.Panel className={cn(
className={cn( 'flex items-center justify-center',
type === 'embedded' ? 'pointer-events-none bg-transparent dark:bg-transparent' : '' type === 'embedded'
)} ? 'pointer-events-none mt-24'
control={open} : 'fixed inset-0 h-screen w-screen z-50'
> )}
{() => ( >
<> <Card>
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<Dialog.Title className='text-3xl mb-0'> <Card.Title className='text-3xl mb-0'>
{routing ? error.status : 'Error'} {routing ? error.status : 'Error'}
</Dialog.Title> </Card.Title>
<AlertIcon className='w-12 h-12 text-red-500'/> <AlertIcon className='w-12 h-12 text-red-500'/>
</div> </div>
<Dialog.Text className='mt-4 text-lg'> <Card.Text className='mt-4 text-lg'>
{routing ? ( {routing ? (
error.statusText error.statusText
) : ( ) : (
<Code> <Code>
{message} {message}
</Code> </Code>
)} )}
</Dialog.Text> </Card.Text>
</> </Card>
)} </div>
</Dialog.Panel>
</Dialog>
) )
} }
-26
View File
@@ -1,26 +0,0 @@
import clsx from 'clsx'
import { type DetailedHTMLProps, type InputHTMLAttributes } from 'react'
type Properties = {
readonly variant?: 'embedded' | 'normal';
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
export default function Input(properties: Properties) {
return (
<input
{...properties}
className={clsx(
'block w-full dark:text-gray-300',
'border-gray-300 dark:border-zinc-700',
'focus:outline-none focus:ring',
'focus:ring-blue-500 dark:focus:ring-blue-300',
properties.variant === 'embedded' ? 'bg-transparent' : 'dark:bg-zinc-800',
properties.variant === 'embedded' ? 'p-0' : 'px-2.5 py-1.5',
properties.variant === 'embedded' ? 'border-none' : 'border',
properties.variant === 'embedded' ? 'focus:ring-0' : 'focus:ring-1',
properties.variant === 'embedded' ? 'rounded-none' : 'rounded-lg',
properties.className
)}
/>
)
}
+5 -11
View File
@@ -15,23 +15,17 @@ export default function Switch(properties: SwitchProperties) {
> >
<div <div
className={cn( className={cn(
'flex h-[26px] w-[44px] shrink-0 cursor-default', 'flex h-[26px] w-[44px] p-[4px] shrink-0',
'rounded-full shadow-inner bg-clip-padding', 'rounded-full outline-none group-focus-visible:ring-2',
'border border-solid border-white/30 p-[3px]', 'bg-main-600/50 dark:bg-main-600/20 group-selected:bg-main-700',
'box-border transition duration-100 ease-in-out',
'outline-none group-focus-visible:ring-2 ring-black',
'bg-main-700 dark:bg-main-800',
'group-pressed:bg-main-800 dark:group-pressed:bg-main-900',
'group-selected:bg-main-900 group-selected:group-pressed:bg-main-900',
properties.isDisabled && 'opacity-50 cursor-not-allowed', properties.isDisabled && 'opacity-50 cursor-not-allowed',
properties.className properties.className
)} )}
> >
<span className={cn( <span className={cn(
'h-[18px] w-[18px] transform rounded-full', 'h-[18px] w-[18px] transform rounded-full',
'bg-white shadow transition duration-100', 'bg-white transition duration-100 ease-in-out',
'ease-in-out translate-x-0 group-selected:translate-x-[100%]' 'translate-x-0 group-selected:translate-x-[100%]'
)} )}
/> />
</div> </div>
+3 -3
View File
@@ -9,7 +9,7 @@ import { cn } from '~/utils/cn'
type TextFieldProperties = Parameters<typeof AriaTextField>[0] & { type TextFieldProperties = Parameters<typeof AriaTextField>[0] & {
readonly label: string; readonly label: string;
readonly placeholder: string; readonly placeholder: string;
readonly state: [string, Dispatch<SetStateAction<string>>]; readonly state?: [string, Dispatch<SetStateAction<string>>];
} }
export default function TextField(properties: TextFieldProperties) { export default function TextField(properties: TextFieldProperties) {
@@ -21,7 +21,7 @@ export default function TextField(properties: TextFieldProperties) {
> >
<Input <Input
placeholder={properties.placeholder} placeholder={properties.placeholder}
value={properties.state[0]} value={properties.state?.[0]}
name={properties.name} name={properties.name}
className={cn( className={cn(
'block px-2.5 py-1.5 w-full rounded-lg my-1', 'block px-2.5 py-1.5 w-full rounded-lg my-1',
@@ -30,7 +30,7 @@ export default function TextField(properties: TextFieldProperties) {
properties.className properties.className
)} )}
onChange={event => { onChange={event => {
properties.state[1](event.target.value) properties.state?.[1](event.target.value)
}} }}
/> />
</AriaTextField> </AriaTextField>
+1 -1
View File
@@ -46,7 +46,7 @@ export function Layout({ children }: { readonly children: React.ReactNode }) {
<Meta/> <Meta/>
<Links/> <Links/>
</head> </head>
<body className='overscroll-none dark:bg-zinc-900 dark:text-white'> <body className='overscroll-none dark:bg-ui-950 dark:text-ui-50'>
{children} {children}
<Toaster/> <Toaster/>
<ScrollRestoration/> <ScrollRestoration/>
+6 -12
View File
@@ -87,9 +87,9 @@ export default function Editor({ data, acl, setAcl, mode }: EditorProperties) {
</div> </div>
<Button <Button
variant='emphasized' variant='heavy'
className='text-sm w-fit mr-2' className='mr-2'
onClick={() => { onPress={() => {
fetcher.submit({ fetcher.submit({
acl acl
}, { }, {
@@ -105,15 +105,9 @@ export default function Editor({ data, acl, setAcl, mode }: EditorProperties) {
)} )}
Save Save
</Button> </Button>
<Button <Button onPress={() => {
variant='emphasized' setAcl(data.currentAcl)
className={clsx( }}
'text-sm w-fit bg-gray-100 dark:bg-transparent',
'border border-gray-200 dark:border-gray-700'
)}
onClick={() => {
setAcl(data.currentAcl)
}}
> >
Discard Changes Discard Changes
</Button> </Button>
+3 -11
View File
@@ -28,20 +28,12 @@ export default function Fallback({ acl, where }: FallbackProperties) {
{where === 'server' ? ( {where === 'server' ? (
<> <>
<Button <Button
disabled variant='heavy'
variant='emphasized' className='mr-2'
className='text-sm w-fit mr-2'
> >
Save Save
</Button> </Button>
<Button <Button>
disabled
variant='emphasized'
className={clsx(
'text-sm w-fit bg-gray-100 dark:bg-transparent',
'border border-gray-200 dark:border-gray-700'
)}
>
Discard Changes Discard Changes
</Button> </Button>
</> </>
+20 -13
View File
@@ -17,13 +17,12 @@ import {
import { CSS } from '@dnd-kit/utilities' import { CSS } from '@dnd-kit/utilities'
import { LockIcon, ThreeBarsIcon } from '@primer/octicons-react' import { LockIcon, ThreeBarsIcon } from '@primer/octicons-react'
import { type FetcherWithComponents, useFetcher } from '@remix-run/react' import { type FetcherWithComponents, useFetcher } from '@remix-run/react'
import clsx from 'clsx'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Button, Input } from 'react-aria-components'
import Button from '~/components/Button'
import Input from '~/components/Input'
import Spinner from '~/components/Spinner' import Spinner from '~/components/Spinner'
import TableList from '~/components/TableList' import TableList from '~/components/TableList'
import { cn } from '~/utils/cn'
type Properties = { type Properties = {
readonly baseDomain?: string; readonly baseDomain?: string;
@@ -115,9 +114,8 @@ export default function Domains({ baseDomain, searchDomains, disabled }: Propert
{disabled ? undefined : ( {disabled ? undefined : (
<TableList.Item key='add-sd'> <TableList.Item key='add-sd'>
<Input <Input
variant='embedded'
type='text' type='text'
className='font-mono text-sm' className='font-mono text-sm bg-transparent w-full mr-2'
placeholder='Search Domain' placeholder='Search Domain'
value={newDomain} value={newDomain}
onChange={event => { onChange={event => {
@@ -126,9 +124,14 @@ export default function Domains({ baseDomain, searchDomains, disabled }: Propert
/> />
{fetcher.state === 'idle' ? ( {fetcher.state === 'idle' ? (
<Button <Button
className='text-sm' className={cn(
disabled={newDomain.length === 0} 'text-sm font-semibold',
onClick={() => { 'text-blue-600 dark:text-blue-400',
'hover:text-blue-700 dark:hover:text-blue-300',
newDomain.length === 0 && 'opacity-50 cursor-not-allowed'
)}
isDisabled={newDomain.length === 0}
onPress={() => {
fetcher.submit({ fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.domains': [...localDomains, newDomain] 'dns_config.domains': [...localDomains, newDomain]
@@ -177,7 +180,7 @@ function Domain({ domain, id, localDomains, isDrag, disabled, fetcher }: DomainP
return ( return (
<div <div
ref={setNodeRef} ref={setNodeRef}
className={clsx( className={cn(
'flex items-center justify-between px-3 py-2', 'flex items-center justify-between px-3 py-2',
'border-b border-gray-200 last:border-b-0 dark:border-zinc-800', 'border-b border-gray-200 last:border-b-0 dark:border-zinc-800',
isDragging ? 'text-gray-400' : '', isDragging ? 'text-gray-400' : '',
@@ -200,10 +203,14 @@ function Domain({ domain, id, localDomains, isDrag, disabled, fetcher }: DomainP
</p> </p>
{isDrag ? undefined : ( {isDrag ? undefined : (
<Button <Button
variant='destructive' className={cn(
className='text-sm' 'text-sm',
disabled={disabled} 'text-red-600 dark:text-red-400',
onClick={() => { 'hover:text-red-700 dark:hover:text-red-300',
disabled && 'opacity-50 cursor-not-allowed'
)}
isDisabled={disabled}
onPress={() => {
fetcher.submit({ fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.domains': localDomains.filter((_, index) => index !== id - 1) 'dns_config.domains': localDomains.filter((_, index) => index !== id - 1)
+1 -9
View File
@@ -2,7 +2,6 @@ import { useFetcher } from '@remix-run/react'
import Dialog from '~/components/Dialog' import Dialog from '~/components/Dialog'
import Spinner from '~/components/Spinner' import Spinner from '~/components/Spinner'
import { cn } from '~/utils/cn'
type Properties = { type Properties = {
readonly isEnabled: boolean; readonly isEnabled: boolean;
@@ -15,14 +14,7 @@ export default function Modal({ isEnabled, disabled }: Properties) {
return ( return (
<Dialog> <Dialog>
<Dialog.Button <Dialog.Button isDisabled={disabled}>
isDisabled={disabled}
className={cn(
'w-fit text-sm rounded-lg px-4 py-2',
'bg-main-700 dark:bg-main-800 text-white',
disabled && 'opacity-50 cursor-not-allowed'
)}
>
{fetcher.state === 'idle' ? undefined : ( {fetcher.state === 'idle' ? undefined : (
<Spinner className='w-3 h-3'/> <Spinner className='w-3 h-3'/>
)} )}
+8 -10
View File
@@ -2,10 +2,10 @@
/* eslint-disable unicorn/no-keyword-prefix */ /* eslint-disable unicorn/no-keyword-prefix */
import { useFetcher } from '@remix-run/react' import { useFetcher } from '@remix-run/react'
import { useState } from 'react' import { useState } from 'react'
import { Input } from 'react-aria-components'
import Code from '~/components/Code' import Code from '~/components/Code'
import Dialog from '~/components/Dialog' import Dialog from '~/components/Dialog'
import Input from '~/components/Input'
import Spinner from '~/components/Spinner' import Spinner from '~/components/Spinner'
import TextField from '~/components/TextField' import TextField from '~/components/TextField'
import { cn } from '~/utils/cn' import { cn } from '~/utils/cn'
@@ -35,7 +35,12 @@ export default function Modal({ name, disabled }: Properties) {
</p> </p>
<Input <Input
readOnly readOnly
className='font-mono text-sm my-4 w-1/2' className={cn(
'block px-2.5 py-1.5 w-1/2 rounded-lg my-4',
'border border-ui-200 dark:border-ui-600',
'dark:bg-ui-800 dark:text-ui-300 text-sm',
'outline-none'
)}
type='text' type='text'
value={name} value={name}
onFocus={event => { onFocus={event => {
@@ -43,14 +48,7 @@ export default function Modal({ name, disabled }: Properties) {
}} }}
/> />
<Dialog> <Dialog>
<Dialog.Button <Dialog.Button isDisabled={disabled}>
isDisabled={disabled}
className={cn(
'w-fit text-sm rounded-lg px-4 py-2',
'bg-main-700 dark:bg-main-800 text-white',
disabled && 'opacity-50 cursor-not-allowed'
)}
>
{fetcher.state === 'idle' ? undefined : ( {fetcher.state === 'idle' ? undefined : (
<Spinner className='w-3 h-3'/> <Spinner className='w-3 h-3'/>
)} )}
+20 -12
View File
@@ -1,14 +1,14 @@
import { type ActionFunctionArgs } from '@remix-run/node' import { type ActionFunctionArgs } from '@remix-run/node'
import { json, useFetcher, useLoaderData } from '@remix-run/react' import { json, useFetcher, useLoaderData } from '@remix-run/react'
import { useState } from 'react' import { useState } from 'react'
import { Button, Input } from 'react-aria-components'
import Button from '~/components/Button'
import Code from '~/components/Code' import Code from '~/components/Code'
import Input from '~/components/Input'
import Notice from '~/components/Notice' import Notice from '~/components/Notice'
import Spinner from '~/components/Spinner' import Spinner from '~/components/Spinner'
import Switch from '~/components/Switch' import Switch from '~/components/Switch'
import TableList from '~/components/TableList' import TableList from '~/components/TableList'
import { cn } from '~/utils/cn'
import { getConfig, getContext, patchConfig } from '~/utils/config' import { getConfig, getContext, patchConfig } from '~/utils/config'
import { restartHeadscale } from '~/utils/docker' import { restartHeadscale } from '~/utils/docker'
import { getSession } from '~/utils/sessions' import { getSession } from '~/utils/sessions'
@@ -119,10 +119,14 @@ export default function Page() {
<TableList.Item key={index}> <TableList.Item key={index}>
<p className='font-mono text-sm'>{ns}</p> <p className='font-mono text-sm'>{ns}</p>
<Button <Button
variant='destructive' className={cn(
className='text-sm' 'text-sm',
disabled={!data.hasConfigWrite} 'text-red-600 dark:text-red-400',
onClick={() => { 'hover:text-red-700 dark:hover:text-red-300',
!data.hasConfigWrite && 'opacity-50 cursor-not-allowed'
)}
isDisabled={!data.hasConfigWrite}
onPress={() => {
fetcher.submit({ fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.nameservers': data.nameservers.filter((_, index_) => index_ !== index) 'dns_config.nameservers': data.nameservers.filter((_, index_) => index_ !== index)
@@ -139,9 +143,8 @@ export default function Page() {
{data.hasConfigWrite ? ( {data.hasConfigWrite ? (
<TableList.Item> <TableList.Item>
<Input <Input
variant='embedded'
type='text' type='text'
className='font-mono text-sm' className='font-mono text-sm bg-transparent w-full mr-2'
placeholder='Nameserver' placeholder='Nameserver'
value={ns} value={ns}
onChange={event => { onChange={event => {
@@ -150,9 +153,14 @@ export default function Page() {
/> />
{fetcher.state === 'idle' ? ( {fetcher.state === 'idle' ? (
<Button <Button
className='text-sm' className={cn(
disabled={ns.length === 0} 'text-sm font-semibold',
onClick={() => { 'text-blue-600 dark:text-blue-400',
'hover:text-blue-700 dark:hover:text-blue-300',
ns.length === 0 && 'opacity-50 cursor-not-allowed'
)}
isDisabled={ns.length === 0}
onPress={() => {
fetcher.submit({ fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.nameservers': [...data.nameservers, ns] 'dns_config.nameservers': [...data.nameservers, ns]
@@ -185,7 +193,7 @@ export default function Page() {
<div className='flex flex-col w-2/3'> <div className='flex flex-col w-2/3'>
<h1 className='text-2xl font-medium mb-4'>Magic DNS</h1> <h1 className='text-2xl font-medium mb-4'>Magic DNS</h1>
<p className='text-gray-700 dark:text-gray-300 mb-4'> <p className='text-gray-700 dark:text-gray-300 mb-4'>
Automaticall register domain names for each device Automatically register domain names for each device
on the tailnet. Devices will be accessible at on the tailnet. Devices will be accessible at
{' '} {' '}
<Code> <Code>
+1 -1
View File
@@ -42,7 +42,7 @@ export default function Page() {
</h1> </h1>
<StatusCircle isOnline={data.online} className='w-4 h-4'/> <StatusCircle isOnline={data.online} className='w-4 h-4'/>
</span> </span>
<Card> <Card variant='flat'>
<Attribute name='Creator' value={data.user.name}/> <Attribute name='Creator' value={data.user.name}/>
<Attribute name='Node ID' value={data.id}/> <Attribute name='Node ID' value={data.id}/>
<Attribute name='Node Name' value={data.givenName}/> <Attribute name='Node Name' value={data.givenName}/>
+1 -1
View File
@@ -44,7 +44,7 @@ export default function Page() {
return ( return (
<div className='grid grid-cols-2 gap-4 auto-rows-min'> <div className='grid grid-cols-2 gap-4 auto-rows-min'>
{data.map(user => ( {data.map(user => (
<Card key={user.id}> <Card key={user.id} variant='flat'>
<div className='flex items-center gap-4'> <div className='flex items-center gap-4'>
<PersonIcon className='w-6 h-6'/> <PersonIcon className='w-6 h-6'/>
<span className='text-lg font-mono'> <span className='text-lg font-mono'>
+18 -19
View File
@@ -5,7 +5,7 @@ import { useMemo } from 'react'
import Button from '~/components/Button' import Button from '~/components/Button'
import Card from '~/components/Card' import Card from '~/components/Card'
import Code from '~/components/Code' import Code from '~/components/Code'
import Input from '~/components/Input' import TextField from '~/components/TextField'
import { type Key } from '~/types' import { type Key } from '~/types'
import { getContext } from '~/utils/config' import { getContext } from '~/utils/config'
import { pull } from '~/utils/headscale' import { pull } from '~/utils/headscale'
@@ -108,11 +108,13 @@ export default function Page() {
return ( return (
<div className='flex min-h-screen items-center justify-center'> <div className='flex min-h-screen items-center justify-center'>
<Card className='w-96'> <Card className='max-w-sm m-4 sm:m-0 rounded-2xl'>
<h1 className='text-2xl mb-8'>Login</h1> <Card.Title>
Welcome to Headplane
</Card.Title>
{data.apiKey ? ( {data.apiKey ? (
<Form method='post'> <Form method='post'>
<p className='text-sm text-gray-500 mb-4'> <Card.Text className='mb-8 text-sm'>
Enter an API key to authenticate with Headplane. You can generate Enter an API key to authenticate with Headplane. You can generate
one by running one by running
{' '} {' '}
@@ -121,43 +123,40 @@ export default function Page() {
</Code> </Code>
{' '} {' '}
in your terminal. in your terminal.
</p> </Card.Text>
{actionData?.error ? ( {actionData?.error ? (
<p className='text-red-500 text-sm mb-2'>{actionData.error}</p> <p className='text-red-500 text-sm mb-2'>{actionData.error}</p>
) : undefined} ) : undefined}
<Input <TextField
required isRequired
type='text' label='API Key'
name='api-key' name='api-key'
id='api-key'
className='border rounded-md p-2 w-full'
placeholder='API Key' placeholder='API Key'
/> />
<Button <Button
variant='emphasized' className='w-full mt-2.5'
variant='heavy'
type='submit' type='submit'
className='bg-gray-800 text-white rounded-md p-2 w-full mt-4'
> >
Login Login
</Button> </Button>
</Form> </Form>
) : undefined} ) : undefined}
{showOr ? ( {showOr ? (
<div className='flex items-center gap-x-2 py-2'> <div className='flex items-center gap-x-1.5 py-1'>
<hr className='flex-1 dark:border-zinc-700'/> <hr className='flex-1 border-ui-300 dark:border-ui-800'/>
<span className='text-gray-500'>or</span> <span className='text-gray-500 text-sm'>or</span>
<hr className='flex-1 dark:border-zinc-700'/> <hr className='flex-1 border-ui-300 dark:border-ui-800'/>
</div> </div>
) : undefined} ) : undefined}
{data.oidc ? ( {data.oidc ? (
<Form method='POST'> <Form method='POST'>
<input type='hidden' name='oidc-start' value='true'/> <input type='hidden' name='oidc-start' value='true'/>
<Button <Button
variant='emphasized' className='w-full'
variant='heavy'
type='submit' type='submit'
className='bg-gray-800 text-white rounded-md p-2 w-full'
> >
Login with SSO Login with SSO
</Button> </Button>