feat: switch to normal form actions for dns
This commit is contained in:
parent
2a1c795d46
commit
5be3cb345e
10 changed files with 448 additions and 391 deletions
205
app/routes/dns/components/manage-domains.tsx
Normal file
205
app/routes/dns/components/manage-domains.tsx
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import { DndContext, DragOverlay, closestCorners } from '@dnd-kit/core';
|
||||
import {
|
||||
restrictToParentElement,
|
||||
restrictToVerticalAxis,
|
||||
} from '@dnd-kit/modifiers';
|
||||
import {
|
||||
SortableContext,
|
||||
arrayMove,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { GripVertical, Lock } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { type FetcherWithComponents, Form, useFetcher } from 'react-router';
|
||||
import Button from '~/components/Button';
|
||||
import Input from '~/components/Input';
|
||||
import TableList from '~/components/TableList';
|
||||
import cn from '~/utils/cn';
|
||||
|
||||
interface Props {
|
||||
searchDomains: string[];
|
||||
isDisabled: boolean;
|
||||
magic?: string;
|
||||
}
|
||||
|
||||
export default function ManageDomains({
|
||||
searchDomains,
|
||||
isDisabled,
|
||||
magic,
|
||||
}: Props) {
|
||||
const [activeId, setActiveId] = useState<number | string | null>(null);
|
||||
const [localDomains, setLocalDomains] = useState(searchDomains);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalDomains(searchDomains);
|
||||
}, [searchDomains]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-2/3">
|
||||
<h1 className="text-2xl font-medium mb-4">Search Domains</h1>
|
||||
<p className="mb-4">
|
||||
Set custom DNS search domains for your Tailnet. When using Magic DNS,
|
||||
your tailnet domain is used as the first search domain.
|
||||
</p>
|
||||
<DndContext
|
||||
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
|
||||
collisionDetection={closestCorners}
|
||||
onDragStart={(event) => {
|
||||
setActiveId(event.active.id);
|
||||
}}
|
||||
onDragEnd={(event) => {
|
||||
setActiveId(null);
|
||||
const { active, over } = event;
|
||||
if (!over) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeItem = localDomains[(active.id as number) - 1];
|
||||
const overItem = localDomains[(over.id as number) - 1];
|
||||
|
||||
if (!activeItem || !overItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldIndex = localDomains.indexOf(activeItem);
|
||||
const newIndex = localDomains.indexOf(overItem);
|
||||
|
||||
if (oldIndex !== newIndex) {
|
||||
setLocalDomains(arrayMove(localDomains, oldIndex, newIndex));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TableList>
|
||||
{magic ? (
|
||||
<TableList.Item key="magic-dns-sd">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-4',
|
||||
isDisabled ? 'flex-row-reverse justify-between w-full' : '',
|
||||
)}
|
||||
>
|
||||
<Lock className="p-0.5" />
|
||||
<p className="font-mono text-sm py-0.5">{magic}</p>
|
||||
</div>
|
||||
</TableList.Item>
|
||||
) : undefined}
|
||||
<SortableContext
|
||||
items={localDomains}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{localDomains.map((sd, index) => (
|
||||
<Domain
|
||||
key={sd}
|
||||
domain={sd}
|
||||
id={index + 1}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
))}
|
||||
<DragOverlay adjustScale>
|
||||
{activeId ? (
|
||||
<Domain
|
||||
isDragging
|
||||
domain={localDomains[(activeId as number) - 1]}
|
||||
id={(activeId as number) - 1}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
) : undefined}
|
||||
</DragOverlay>
|
||||
</SortableContext>
|
||||
{isDisabled ? undefined : (
|
||||
<TableList.Item key="add-sd">
|
||||
<Form
|
||||
method="POST"
|
||||
className="flex items-center justify-between w-full"
|
||||
>
|
||||
<input type="hidden" name="action_id" value="add_domain" />
|
||||
<Input
|
||||
type="text"
|
||||
className={cn(
|
||||
'border-none font-mono p-0 text-sm',
|
||||
'rounded-none focus:ring-0 w-full ml-1',
|
||||
)}
|
||||
placeholder="Search Domain"
|
||||
label="Search Domain"
|
||||
name="domain"
|
||||
labelHidden
|
||||
isRequired
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
className={cn(
|
||||
'px-2 py-1 rounded-md',
|
||||
'text-blue-500 dark:text-blue-400',
|
||||
)}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</Form>
|
||||
</TableList.Item>
|
||||
)}
|
||||
</TableList>
|
||||
</DndContext>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface DomainProps {
|
||||
domain: string;
|
||||
id: number;
|
||||
isDragging?: boolean;
|
||||
isDisabled: boolean;
|
||||
}
|
||||
|
||||
function Domain({ domain, id, isDragging, isDisabled }: DomainProps) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging: isSortableDragging,
|
||||
} = useSortable({ id });
|
||||
|
||||
return (
|
||||
<TableList.Item
|
||||
ref={setNodeRef}
|
||||
className={cn(
|
||||
isSortableDragging ? 'opacity-50' : '',
|
||||
isDragging ? 'ring bg-white dark:bg-headplane-900' : '',
|
||||
)}
|
||||
style={{
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
}}
|
||||
>
|
||||
<p className="font-mono text-sm flex items-center gap-4">
|
||||
{isDisabled ? undefined : (
|
||||
<GripVertical
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="p-0.5 focus:ring outline-none rounded-md"
|
||||
/>
|
||||
)}
|
||||
{domain}
|
||||
</p>
|
||||
{isDragging ? undefined : (
|
||||
<Form method="POST">
|
||||
<input type="hidden" name="action_id" value="remove_domain" />
|
||||
<input type="hidden" name="domain" value={domain} />
|
||||
<Button
|
||||
type="submit"
|
||||
isDisabled={isDisabled}
|
||||
className={cn(
|
||||
'px-2 py-1 rounded-md',
|
||||
'text-red-500 dark:text-red-400',
|
||||
)}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
</TableList.Item>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue