diff --git a/app/components/Attribute.tsx b/app/components/Attribute.tsx
index 8b89f0f..3cd4f4a 100644
--- a/app/components/Attribute.tsx
+++ b/app/components/Attribute.tsx
@@ -1,5 +1,6 @@
import { ClipboardIcon } from '@heroicons/react/24/outline'
-import toast from 'react-hot-toast/headless'
+
+import { toast } from './Toaster'
type Properties = {
readonly name: string;
diff --git a/app/components/Dialog.tsx b/app/components/Dialog.tsx
index c958604..1243bd0 100644
--- a/app/components/Dialog.tsx
+++ b/app/components/Dialog.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable unicorn/no-keyword-prefix */
import { type Dispatch, type ReactNode, type SetStateAction } from 'react'
import {
Button as AriaButton,
@@ -20,7 +21,9 @@ function Button(properties: ButtonProperties) {
{...properties}
aria-label='Dialog'
className={cn(
- 'outline-none',
+ 'w-fit text-sm rounded-lg px-4 py-2',
+ 'bg-main-700 dark:bg-main-800 text-white',
+ properties.isDisabled && 'opacity-50 cursor-not-allowed',
properties.className
)}
// If control is passed, set the state value
@@ -42,11 +45,12 @@ function Action(properties: ActionProperties) {
type={properties.variant === 'confirm' ? 'submit' : 'button'}
className={cn(
'px-4 py-2 rounded-lg',
+ properties.isDisabled && 'opacity-50 cursor-not-allowed',
properties.variant === 'cancel'
? 'text-ui-700 dark:text-ui-300'
- : 'text-ui-950 dark:text-ui-50',
+ : 'text-ui-300 dark:text-ui-300',
properties.variant === 'confirm'
- ? 'bg-blue-500 hover:bg-blue-600 pressed:bg-blue-700 text-white'
+ ? 'bg-main-700 dark:bg-main-700 pressed:bg-main-800 dark:pressed:bg-main-800'
: 'bg-ui-200 dark:bg-ui-800 pressed:bg-ui-300 dark:pressed:bg-ui-700',
properties.className
)}
@@ -82,9 +86,10 @@ function Text(properties: React.HTMLProps) {
type PanelProperties = {
readonly children: (close: () => void) => ReactNode;
readonly control?: [boolean, Dispatch>];
+ readonly className?: string;
}
-function Panel({ children, control }: PanelProperties) {
+function Panel({ children, control, className }: PanelProperties) {
return (
diff --git a/app/components/Error.tsx b/app/components/Error.tsx
index 01c664f..d3df0e2 100644
--- a/app/components/Error.tsx
+++ b/app/components/Error.tsx
@@ -1,66 +1,52 @@
-import { Transition } from '@headlessui/react'
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
import { isRouteErrorResponse, useRouteError } from '@remix-run/react'
-import clsx from 'clsx'
-import { Fragment, useEffect, useState } from 'react'
+import { useState } from 'react'
+
+import { cn } from '~/utils/cn'
import Code from './Code'
+import Dialog from './Dialog'
type Properties = {
readonly type?: 'full' | 'embedded';
}
export function ErrorPopup({ type = 'full' }: Properties) {
- const [isOpen, setIsOpen] = useState(false)
+ // eslint-disable-next-line react/hook-use-state
+ const open = useState(true)
+
const error = useRouteError()
const routing = isRouteErrorResponse(error)
const message = (error instanceof Error ? error.message : 'An unexpected error occurred')
- console.error(error)
-
- // Debounce the error modal so it doesn't show up for a split second
- // when the user navigates to a new page.
- useEffect(() => {
- setTimeout(() => {
- setIsOpen(true)
- }, 150)
- }, [])
return (
-
-
-
-
+
-
-
-
- {routing ? error.status : 'Error'}
-
- {routing ? (
-
- {error.statusText}
-
- ) : (
-
- {message}
-
- )}
-
-
-
-
+ control={open}
+ >
+ {() => (
+ <>
+
+
+ {routing ? error.status : 'Error'}
+
+
+
+
+ {routing ? (
+ error.statusText
+ ) : (
+
+ {message}
+
+ )}
+
+ >
+ )}
+
+
)
}
diff --git a/app/components/Menu.tsx b/app/components/Menu.tsx
index 300f5b4..6898320 100644
--- a/app/components/Menu.tsx
+++ b/app/components/Menu.tsx
@@ -1,4 +1,4 @@
-import { type ReactNode } from 'react'
+import { type Dispatch, type ReactNode, type SetStateAction } from 'react'
import {
Button as AriaButton,
Menu as AriaMenu,
@@ -49,6 +49,31 @@ function Items(properties: Parameters[0]) {
)
}
+type ButtonProperties = Parameters[0] & {
+ readonly control?: [boolean, Dispatch>];
+}
+
+function ItemButton(properties: ButtonProperties) {
+ return (
+
+ )
+}
+
function Item(properties: Parameters[0]) {
return (
-
-
+
-
- {({ selected }) => (
-
+ cn(
+ 'px-4 py-2 rounded-tl-lg',
+ 'focus:outline-none flex items-center gap-2',
+ 'border-x border-gray-200 dark:border-gray-700',
+ isSelected ? 'text-gray-900 dark:text-gray-100' : ''
)}
+ >
+
+ Edit file
-
- {({ selected }) => (
-
+ cn(
+ 'px-4 py-2',
+ 'focus:outline-none flex items-center gap-2',
+ 'border-x border-gray-200 dark:border-gray-700',
+ isSelected ? 'text-gray-900 dark:text-gray-100' : ''
)}
+ >
+
+ Preview changes
-
- {({ selected }) => (
-
+ cn(
+ 'px-4 py-2 rounded-tr-lg',
+ 'focus:outline-none flex items-center gap-2',
+ 'border-x border-gray-200 dark:border-gray-700',
+ isSelected ? 'text-gray-900 dark:text-gray-100' : ''
)}
+ >
+
+ Preview rules
-
-
-
- }>
- {() => (
-
- )}
-
-
-
- }>
- {() => (
-
- )}
-
-
-
-
-
-
- The Preview rules is very much still a work in progress.
- It is a bit complicated to implement right now but hopefully it will be available soon.
-
-
-
-
-
+
+
+ }>
+ {() => (
+
+ )}
+
+
+
+ }>
+ {() => (
+
+ )}
+
+
+
+
+
+
+ The Preview rules is very much still a work in progress.
+ It is a bit complicated to implement right now but hopefully it will be available soon.
+
+
+
+
)
}
diff --git a/app/routes/_data.dns._index/magic.tsx b/app/routes/_data.dns._index/magic.tsx
index 589ad13..e603f73 100644
--- a/app/routes/_data.dns._index/magic.tsx
+++ b/app/routes/_data.dns._index/magic.tsx
@@ -19,7 +19,7 @@ export default function Modal({ isEnabled, disabled }: Properties) {
isDisabled={disabled}
className={cn(
'w-fit text-sm rounded-lg px-4 py-2',
- 'bg-gray-700 dark:bg-gray-800 text-white',
+ 'bg-main-700 dark:bg-main-800 text-white',
disabled && 'opacity-50 cursor-not-allowed'
)}
>
diff --git a/app/routes/_data.dns._index/rename.tsx b/app/routes/_data.dns._index/rename.tsx
index 53cfbe6..c32b93e 100644
--- a/app/routes/_data.dns._index/rename.tsx
+++ b/app/routes/_data.dns._index/rename.tsx
@@ -47,7 +47,7 @@ export default function Modal({ name, disabled }: Properties) {
isDisabled={disabled}
className={cn(
'w-fit text-sm rounded-lg px-4 py-2',
- 'bg-gray-700 dark:bg-gray-800 text-white',
+ 'bg-main-700 dark:bg-main-800 text-white',
disabled && 'opacity-50 cursor-not-allowed'
)}
>
diff --git a/app/routes/_data.dns._index/route.tsx b/app/routes/_data.dns._index/route.tsx
index ab3144c..2aba7c6 100644
--- a/app/routes/_data.dns._index/route.tsx
+++ b/app/routes/_data.dns._index/route.tsx
@@ -1,7 +1,5 @@
-import { Switch } from '@headlessui/react'
import { type ActionFunctionArgs } from '@remix-run/node'
import { json, useFetcher, useLoaderData } from '@remix-run/react'
-import clsx from 'clsx'
import { useState } from 'react'
import Button from '~/components/Button'
@@ -9,6 +7,7 @@ import Code from '~/components/Code'
import Input from '~/components/Input'
import Notice from '~/components/Notice'
import Spinner from '~/components/Spinner'
+import Switch from '~/components/Switch'
import TableList from '~/components/TableList'
import { getConfig, getContext, patchConfig } from '~/utils/config'
import { restartHeadscale } from '~/utils/docker'
@@ -97,12 +96,9 @@ export default function Page() {
Override local DNS
{
fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -114,15 +110,7 @@ export default function Page() {
setLocalOverride(!localOverride)
}}
- >
- Override local DNS
-
-
+ />
diff --git a/app/routes/_data.tsx b/app/routes/_data.tsx
index 9f6fb6c..373bc9f 100644
--- a/app/routes/_data.tsx
+++ b/app/routes/_data.tsx
@@ -1,6 +1,6 @@
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, useRouteError } from '@remix-run/react'
+import { Form, Outlet, useLoaderData } from '@remix-run/react'
import { ErrorPopup } from '~/components/Error'
import Menu from '~/components/Menu'
@@ -43,9 +43,10 @@ export async function loader({ request }: LoaderFunctionArgs) {
export default function Layout() {
const data = useLoaderData()
+
return (
<>
-
+