feat: add loading states

This commit is contained in:
Aarnav Tale 2024-03-30 04:12:37 -04:00
parent cc1427882f
commit 935e015be9
No known key found for this signature in database
5 changed files with 86 additions and 42 deletions

View File

@ -0,0 +1,23 @@
import clsx from 'clsx'
type Properties = {
// eslint-disable-next-line unicorn/no-keyword-prefix
className?: string;
}
export default function Spinner(properties: Properties) {
return (
<div className={clsx('mr-1.5 inline-block align-middle mb-0.5', properties.className)}>
<div
className={clsx(
'animate-spin rounded-full w-full h-full',
'border-2 border-current border-t-transparent',
properties.className
)}
role='status'
>
<span className='sr-only'>Loading...</span>
</div>
</div>
)
}

View File

@ -16,12 +16,13 @@ import {
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { Bars3Icon, LockClosedIcon } from '@heroicons/react/24/outline'
import { useFetcher } from '@remix-run/react'
import { type FetcherWithComponents, useFetcher } from '@remix-run/react'
import clsx from 'clsx'
import { useEffect, useState } from 'react'
import Button from '~/components/Button'
import Input from '~/components/Input'
import Spinner from '~/components/Spinner'
import TableList from '~/components/TableList'
type Properties = {
@ -97,6 +98,7 @@ export default function Domains({ baseDomain, searchDomains, disabled }: Propert
id={index + 1}
localDomains={localDomains}
disabled={disabled}
fetcher={fetcher}
/>
))}
<DragOverlay adjustScale>
@ -106,6 +108,7 @@ export default function Domains({ baseDomain, searchDomains, disabled }: Propert
localDomains={localDomains}
id={activeId as number - 1}
disabled={disabled}
fetcher={fetcher}
/> : undefined}
</DragOverlay>
</SortableContext>
@ -121,23 +124,27 @@ export default function Domains({ baseDomain, searchDomains, disabled }: Propert
setNewDomain(event.target.value)
}}
/>
<Button
className='text-sm'
disabled={newDomain.length === 0}
onClick={() => {
fetcher.submit({
{fetcher.state === 'idle' ? (
<Button
className='text-sm'
disabled={newDomain.length === 0}
onClick={() => {
fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.domains': [...localDomains, newDomain]
}, {
method: 'PATCH',
encType: 'application/json'
})
'dns_config.domains': [...localDomains, newDomain]
}, {
method: 'PATCH',
encType: 'application/json'
})
setNewDomain('')
}}
>
Add
</Button>
setNewDomain('')
}}
>
Add
</Button>
) : (
<Spinner className='w-3 h-3 mr-0'/>
)}
</TableList.Item>
)}
</TableList>
@ -153,11 +160,10 @@ type DomainProperties = {
readonly localDomains: string[];
// eslint-disable-next-line react/boolean-prop-naming
readonly disabled?: boolean;
readonly fetcher: FetcherWithComponents<unknown>;
}
function Domain({ domain, id, localDomains, isDrag, disabled }: DomainProperties) {
const fetcher = useFetcher()
function Domain({ domain, id, localDomains, isDrag, disabled, fetcher }: DomainProperties) {
const {
attributes,
listeners,
@ -167,14 +173,15 @@ function Domain({ domain, id, localDomains, isDrag, disabled }: DomainProperties
isDragging
} = useSortable({ id })
// TODO: Figure out why TableList.Item breaks dndkit
return (
<div
ref={setNodeRef}
className={clsx(
'flex items-center justify-between px-3 py-2',
'border-b border-gray-200 last:border-b-0',
isDragging ? 'text-gray-400' : 'bg-gray-50',
isDrag ? 'outline outline-1 outline-gray-500' : undefined
'border-b border-gray-200 last:border-b-0 dark:border-zinc-800',
isDragging ? 'text-gray-400' : '',
isDrag ? 'outline outline-1 outline-gray-500 bg-gray-200 dark:bg-zinc-800' : ''
)}
style={{
transform: CSS.Transform.toString(transform),
@ -198,7 +205,7 @@ function Domain({ domain, id, localDomains, isDrag, disabled }: DomainProperties
disabled={disabled}
onClick={() => {
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)
}, {
method: 'PATCH',

View File

@ -4,6 +4,7 @@ import clsx from 'clsx'
import { useState } from 'react'
import Button from '~/components/Button'
import Spinner from '~/components/Spinner'
type Properties = {
readonly isEnabled: boolean;
@ -25,6 +26,9 @@ export default function Modal({ isEnabled, disabled }: Properties) {
setIsOpen(true)
}}
>
{fetcher.state === 'idle' ? undefined : (
<Spinner className='w-3 h-3'/>
)}
{isEnabled ? 'Disable' : 'Enable'} Magic DNS
</Button>
<Dialog

View File

@ -7,6 +7,7 @@ import { useState } from 'react'
import Button from '~/components/Button'
import Code from '~/components/Code'
import Input from '~/components/Input'
import Spinner from '~/components/Spinner'
type Properties = {
readonly name: string;
@ -49,6 +50,9 @@ export default function Modal({ name, disabled }: Properties) {
setIsOpen(true)
}}
>
{fetcher.state === 'idle' ? undefined : (
<Spinner className='w-3 h-3'/>
)}
Rename Tailnet...
</Button>
<Dialog
@ -88,7 +92,6 @@ export default function Modal({ name, disabled }: Properties) {
})
setIsOpen(false)
setNewName(name)
}}
>
Rename

View File

@ -8,6 +8,7 @@ import Button from '~/components/Button'
import Code from '~/components/Code'
import Input from '~/components/Input'
import Notice from '~/components/Notice'
import Spinner from '~/components/Spinner'
import TableList from '~/components/TableList'
import { getConfig, getContext, patchConfig } from '~/utils/config'
import { restartHeadscale } from '~/utils/docker'
@ -80,12 +81,14 @@ export default function Page() {
Global Nameservers
</h2>
<div className='flex gap-2 items-center'>
<span className='text-sm opacity-50'>Override local DNS</span>
<span className='text-sm opacity-50'>
Override local DNS
</span>
<Switch
checked={localOverride}
disabled={!data.hasConfigWrite}
className={clsx(
localOverride ? 'bg-gray-800' : 'bg-gray-200',
localOverride ? 'bg-gray-800 dark:bg-gray-600' : 'bg-gray-200 dark:bg-gray-400',
'relative inline-flex h-4 w-9 items-center rounded-full'
)}
onChange={() => {
@ -145,23 +148,27 @@ export default function Page() {
setNs(event.target.value)
}}
/>
<Button
className='text-sm'
disabled={ns.length === 0}
onClick={() => {
fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.nameservers': [...data.nameservers, ns]
}, {
method: 'PATCH',
encType: 'application/json'
})
{fetcher.state === 'idle' ? (
<Button
className='text-sm'
disabled={ns.length === 0}
onClick={() => {
fetcher.submit({
// eslint-disable-next-line @typescript-eslint/naming-convention
'dns_config.nameservers': [...data.nameservers, ns]
}, {
method: 'PATCH',
encType: 'application/json'
})
setNs('')
}}
>
Add
</Button>
setNs('')
}}
>
Add
</Button>
) : (
<Spinner className='w-3 h-3 mr-0'/>
)}
</TableList.Item>
) : undefined}
</TableList>