mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-07 01:16:08 -06:00
feat: migrate to Radix UI and implement dark mode with next-themes
Replace @headlessui/react and @heroicons/react with @radix-ui/themes and @radix-ui/react-icons for a more comprehensive component library. Add next-themes for dark mode support with a new ThemeToggle component. Update all components to use Radix UI primitives and theming system, including AbstractCard, DynamicDate, ErrorCard, Property, PropertyList, LookupInput, and all card components (AutnumCard, DomainCard, EntityCard, IPCard, NameserverCard). Update global styles and app configuration to support theme switching.
This commit is contained in:
@@ -22,12 +22,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||||
"@headlessui/react": "^2.2.9",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@heroicons/react": "^2.0.16",
|
"@radix-ui/themes": "^3.2.1",
|
||||||
"@swc/helpers": "^0.5.11",
|
"@swc/helpers": "^0.5.11",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"next": "^15.5.6",
|
"next": "^15.5.6",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-hook-form": "^7.42.1",
|
"react-hook-form": "^7.42.1",
|
||||||
|
|||||||
1788
pnpm-lock.yaml
generated
1788
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,8 @@
|
|||||||
import type { FunctionComponent, ReactNode } from "react";
|
import type { FunctionComponent, ReactNode } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useBoolean } from "usehooks-ts";
|
import { useBoolean } from "usehooks-ts";
|
||||||
import {
|
import { Link2Icon, CodeIcon, DownloadIcon, ClipboardCopyIcon } from "@radix-ui/react-icons";
|
||||||
LinkIcon,
|
import { Card, Flex, Box, IconButton, Code, ScrollArea } from "@radix-ui/themes";
|
||||||
CodeBracketIcon,
|
|
||||||
DocumentArrowDownIcon,
|
|
||||||
ClipboardDocumentIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
type AbstractCardProps = {
|
type AbstractCardProps = {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
@@ -26,82 +22,125 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
|
|||||||
const { value: showRaw, toggle: toggleRaw } = useBoolean(false);
|
const { value: showRaw, toggle: toggleRaw } = useBoolean(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 overflow-clip rounded bg-zinc-800 shadow">
|
<Box mb="4">
|
||||||
{header != undefined || data != undefined ? (
|
<Card size="2">
|
||||||
<div className="flex bg-zinc-700 p-2 pl-3 md:pl-5">
|
{(header != undefined || data != undefined) && (
|
||||||
<div className="flex grow gap-2">{header}</div>
|
<Flex
|
||||||
{url != undefined ? (
|
justify="between"
|
||||||
<div className="pr-2">
|
align="center"
|
||||||
<a href={url} target="_blank" rel="noreferrer">
|
p="3"
|
||||||
<LinkIcon className="mt-1 h-5 w-5 cursor-pointer" />
|
style={{
|
||||||
</a>
|
borderBottom: "1px solid var(--gray-a5)",
|
||||||
</div>
|
}}
|
||||||
) : null}
|
>
|
||||||
{data != undefined ? (
|
<Flex gap="2" style={{ flex: 1 }}>
|
||||||
<>
|
{header}
|
||||||
<div className="pr-2">
|
</Flex>
|
||||||
<ClipboardDocumentIcon
|
<Flex gap="2" align="center">
|
||||||
onClick={() => {
|
{url != undefined && (
|
||||||
// stringify the JSON object, then begin the async clipboard write
|
<IconButton variant="ghost" size="2" asChild>
|
||||||
navigator.clipboard
|
<a
|
||||||
.writeText(JSON.stringify(data, null, 4))
|
href={url}
|
||||||
.then(
|
target="_blank"
|
||||||
() => {
|
rel="noreferrer"
|
||||||
// Successfully copied to clipboard
|
aria-label="Open RDAP URL"
|
||||||
},
|
>
|
||||||
(err) => {
|
<Link2Icon width="18" height="18" />
|
||||||
if (err instanceof Error)
|
</a>
|
||||||
console.error(
|
</IconButton>
|
||||||
`Failed to copy to clipboard (${err.toString()}).`
|
)}
|
||||||
);
|
{data != undefined && (
|
||||||
else
|
<>
|
||||||
console.error(
|
<IconButton
|
||||||
"Failed to copy to clipboard."
|
variant="ghost"
|
||||||
);
|
size="2"
|
||||||
}
|
onClick={() => {
|
||||||
);
|
navigator.clipboard
|
||||||
}}
|
.writeText(JSON.stringify(data, null, 4))
|
||||||
className="h-6 w-6 cursor-pointer"
|
.then(
|
||||||
/>
|
() => {
|
||||||
</div>
|
// Successfully copied to clipboard
|
||||||
<div className="pr-2">
|
},
|
||||||
<DocumentArrowDownIcon
|
(err) => {
|
||||||
onClick={() => {
|
if (err instanceof Error)
|
||||||
const file = new Blob([JSON.stringify(data, null, 4)], {
|
console.error(
|
||||||
type: "application/json",
|
`Failed to copy to clipboard (${err.toString()}).`
|
||||||
});
|
);
|
||||||
|
else
|
||||||
|
console.error(
|
||||||
|
"Failed to copy to clipboard."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
aria-label="Copy JSON to clipboard"
|
||||||
|
>
|
||||||
|
<ClipboardCopyIcon width="18" height="18" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
variant="ghost"
|
||||||
|
size="2"
|
||||||
|
onClick={() => {
|
||||||
|
const file = new Blob([JSON.stringify(data, null, 4)], {
|
||||||
|
type: "application/json",
|
||||||
|
});
|
||||||
|
|
||||||
const anchor = document.createElement("a");
|
const anchor = document.createElement("a");
|
||||||
anchor.href = URL.createObjectURL(file);
|
anchor.href = URL.createObjectURL(file);
|
||||||
anchor.download = "response.json";
|
anchor.download = "response.json";
|
||||||
anchor.click();
|
anchor.click();
|
||||||
}}
|
}}
|
||||||
className="h-6 w-6 cursor-pointer"
|
aria-label="Download JSON"
|
||||||
/>
|
>
|
||||||
</div>
|
<DownloadIcon width="18" height="18" />
|
||||||
<div className="pr-1">
|
</IconButton>
|
||||||
<CodeBracketIcon
|
<IconButton
|
||||||
onClick={toggleRaw}
|
variant="ghost"
|
||||||
className="h-6 w-6 cursor-pointer"
|
size="2"
|
||||||
/>
|
onClick={toggleRaw}
|
||||||
</div>
|
aria-label={
|
||||||
</>
|
showRaw ? "Show formatted view" : "Show raw JSON"
|
||||||
) : null}
|
}
|
||||||
</div>
|
>
|
||||||
) : null}
|
<CodeIcon width="18" height="18" />
|
||||||
<div className="max-w-full overflow-x-auto p-2 px-4">
|
</IconButton>
|
||||||
{showRaw ? (
|
</>
|
||||||
<pre className="scrollbar-thin m-2 max-h-[40rem] max-w-full overflow-y-auto rounded whitespace-pre-wrap">
|
)}
|
||||||
{JSON.stringify(data, null, 4)}
|
</Flex>
|
||||||
</pre>
|
</Flex>
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
)}
|
||||||
</div>
|
<Box p="4">
|
||||||
{footer != null ? (
|
{showRaw ? (
|
||||||
<div className="flex gap-2 bg-zinc-700 p-2 pl-5">{footer}</div>
|
<ScrollArea type="auto" scrollbars="both" style={{ maxHeight: "40rem" }}>
|
||||||
) : null}
|
<Code
|
||||||
</div>
|
variant="ghost"
|
||||||
|
size="2"
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
fontFamily: "var(--font-mono)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{JSON.stringify(data, null, 4)}
|
||||||
|
</Code>
|
||||||
|
</ScrollArea>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{footer != null && (
|
||||||
|
<Flex
|
||||||
|
gap="2"
|
||||||
|
p="3"
|
||||||
|
style={{
|
||||||
|
borderTop: "1px solid var(--gray-a5)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{footer}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { FunctionComponent } from "react";
|
|||||||
import { useBoolean } from "usehooks-ts";
|
import { useBoolean } from "usehooks-ts";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import TimeAgo from "react-timeago";
|
import TimeAgo from "react-timeago";
|
||||||
|
import { Button, Text } from "@radix-ui/themes";
|
||||||
|
|
||||||
type DynamicDateProps = {
|
type DynamicDateProps = {
|
||||||
value: Date | number;
|
value: Date | number;
|
||||||
@@ -18,15 +19,20 @@ const DynamicDate: FunctionComponent<DynamicDateProps> = ({ value, absoluteForma
|
|||||||
|
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
return (
|
return (
|
||||||
<button onClick={toggleFormat}>
|
<Button
|
||||||
<span className="dashed" title={date.toISOString()}>
|
variant="ghost"
|
||||||
|
size="1"
|
||||||
|
onClick={toggleFormat}
|
||||||
|
style={{ padding: 0, height: "auto" }}
|
||||||
|
>
|
||||||
|
<Text className="dashed" title={date.toISOString()} size="2">
|
||||||
{showAbsolute ? (
|
{showAbsolute ? (
|
||||||
format(date, absoluteFormat ?? "LLL do, y HH:mm:ss xxx")
|
format(date, absoluteFormat ?? "LLL do, y HH:mm:ss xxx")
|
||||||
) : (
|
) : (
|
||||||
<TimeAgo date={date} />
|
<TimeAgo date={date} />
|
||||||
)}
|
)}
|
||||||
</span>
|
</Text>
|
||||||
</button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { FunctionComponent, ReactNode } from "react";
|
import type { FunctionComponent, ReactNode } from "react";
|
||||||
import { XCircleIcon } from "@heroicons/react/20/solid";
|
import { CrossCircledIcon } from "@radix-ui/react-icons";
|
||||||
import { cn } from "@/lib/utils";
|
import { Callout, Box, Flex } from "@radix-ui/themes";
|
||||||
|
|
||||||
export type ErrorCardProps = {
|
export type ErrorCardProps = {
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
@@ -16,35 +16,46 @@ const ErrorCard: FunctionComponent<ErrorCardProps> = ({
|
|||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<Box className={className}>
|
||||||
className={cn(
|
<Callout.Root color="red" role="alert">
|
||||||
className,
|
<Callout.Icon>
|
||||||
"rounded-md border border-red-700/30 bg-zinc-800 px-3 pt-3 pb-1"
|
<CrossCircledIcon />
|
||||||
)}
|
</Callout.Icon>
|
||||||
>
|
<Flex direction="column" gap="2">
|
||||||
<div className="flex">
|
<Callout.Text weight="medium" size="3">
|
||||||
<div className="shrink-0">
|
{title}
|
||||||
<XCircleIcon className="h-5 w-5 text-red-300" aria-hidden="true" />
|
</Callout.Text>
|
||||||
</div>
|
|
||||||
<div className="ml-3 w-full text-sm text-red-300">
|
|
||||||
<h3 className="font-medium text-red-200">{title}</h3>
|
|
||||||
{description != undefined ? (
|
{description != undefined ? (
|
||||||
<div className="mt-2 max-h-24 w-full overflow-y-auto whitespace-pre-wrap">
|
<Box
|
||||||
{description}
|
style={{
|
||||||
</div>
|
maxHeight: "6rem",
|
||||||
|
overflowY: "auto",
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Callout.Text size="2">{description}</Callout.Text>
|
||||||
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="mt-2">
|
{issues != undefined && issues.length > 0 ? (
|
||||||
{issues != undefined ? (
|
<Box asChild>
|
||||||
<ul role="list" className="flex list-disc flex-col gap-1 pl-5">
|
<ul
|
||||||
|
role="list"
|
||||||
|
style={{
|
||||||
|
listStyleType: "disc",
|
||||||
|
paddingLeft: "1.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{issues.map((issueText, index) => (
|
{issues.map((issueText, index) => (
|
||||||
<li key={index}>{issueText}</li>
|
<li key={index}>
|
||||||
|
<Callout.Text size="2">{issueText}</Callout.Text>
|
||||||
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : null}
|
</Box>
|
||||||
</div>
|
) : null}
|
||||||
</div>
|
</Flex>
|
||||||
</div>
|
</Callout.Root>
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { FunctionComponent, ReactNode } from "react";
|
import type { FunctionComponent, ReactNode } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { DataList } from "@radix-ui/themes";
|
||||||
|
|
||||||
type PropertyProps = {
|
type PropertyProps = {
|
||||||
title: string | ReactNode;
|
title: string | ReactNode;
|
||||||
@@ -9,6 +9,11 @@ type PropertyProps = {
|
|||||||
valueClass?: string;
|
valueClass?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple wrapper around Radix DataList for displaying key-value pairs.
|
||||||
|
* This component uses DataList.Item, DataList.Label, and DataList.Value
|
||||||
|
* to provide semantic HTML and consistent styling.
|
||||||
|
*/
|
||||||
const Property: FunctionComponent<PropertyProps> = ({
|
const Property: FunctionComponent<PropertyProps> = ({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
@@ -16,10 +21,10 @@ const Property: FunctionComponent<PropertyProps> = ({
|
|||||||
valueClass,
|
valueClass,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<DataList.Item>
|
||||||
<dt className={cn("font-medium", titleClass)}>{title}:</dt>
|
<DataList.Label className={titleClass}>{title}</DataList.Label>
|
||||||
<dd className={cn("mt-2 mb-2 ml-6", valueClass)}>{children}</dd>
|
<DataList.Value className={valueClass}>{children}</DataList.Value>
|
||||||
</>
|
</DataList.Item>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import type { FunctionComponent, ReactNode } from "react";
|
import type { FunctionComponent, ReactNode } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Property from "@/components/common/Property";
|
import { Text, Box, DataList } from "@radix-ui/themes";
|
||||||
|
|
||||||
const PropertyListItem: FunctionComponent<{
|
const PropertyListItem: FunctionComponent<{
|
||||||
title: string;
|
title: string;
|
||||||
children: string;
|
children: string;
|
||||||
}> = ({ title, children }) => {
|
}> = ({ title, children }) => {
|
||||||
return (
|
return (
|
||||||
<li>
|
<Box asChild>
|
||||||
<span className="dashed" title={title}>
|
<li>
|
||||||
{children}
|
<Text className="dashed" title={title} size="2">
|
||||||
</span>
|
{children}
|
||||||
</li>
|
</Text>
|
||||||
|
</li>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,15 +22,30 @@ type PropertyListProps = {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PropertyList displays a labeled list of items (not key-value pairs).
|
||||||
|
* Uses DataList.Item for the label, and renders children as a bulleted list.
|
||||||
|
*/
|
||||||
const PropertyList: FunctionComponent<PropertyListProps> & {
|
const PropertyList: FunctionComponent<PropertyListProps> & {
|
||||||
Item: typeof PropertyListItem;
|
Item: typeof PropertyListItem;
|
||||||
} = ({ title, children }) => {
|
} = ({ title, children }) => {
|
||||||
return (
|
return (
|
||||||
<Property title={title}>
|
<DataList.Item>
|
||||||
<ul key={2} className="list-disc">
|
<DataList.Label>{title}</DataList.Label>
|
||||||
{children}
|
<DataList.Value>
|
||||||
</ul>
|
<Box asChild>
|
||||||
</Property>
|
<ul
|
||||||
|
style={{
|
||||||
|
listStyleType: "disc",
|
||||||
|
paddingLeft: "1.25rem",
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
</Box>
|
||||||
|
</DataList.Value>
|
||||||
|
</DataList.Item>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
40
src/components/common/ThemeToggle.tsx
Normal file
40
src/components/common/ThemeToggle.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { IconButton } from "@radix-ui/themes";
|
||||||
|
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export const ThemeToggle = () => {
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
// Avoid hydration mismatch by only rendering after mount
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
setTheme(theme === "light" ? "dark" : "light");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
size="3"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
title={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
|
||||||
|
>
|
||||||
|
{theme === "light" ? (
|
||||||
|
<MoonIcon width="18" height="18" />
|
||||||
|
) : (
|
||||||
|
<SunIcon width="18" height="18" />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,24 +1,11 @@
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import type { FunctionComponent } from "react";
|
import type { FunctionComponent } from "react";
|
||||||
import { Fragment, useState } from "react";
|
import { useState } from "react";
|
||||||
import { onPromise, preventDefault } from "@/helpers";
|
import { onPromise, preventDefault } from "@/helpers";
|
||||||
import type { SimplifiedTargetType, SubmitProps, TargetType } from "@/types";
|
import type { SimplifiedTargetType, SubmitProps, TargetType } from "@/types";
|
||||||
import { TargetTypeEnum } from "@/schema";
|
import { TargetTypeEnum } from "@/schema";
|
||||||
import {
|
import { MagnifyingGlassIcon, ReloadIcon, LockClosedIcon } from "@radix-ui/react-icons";
|
||||||
CheckIcon,
|
import { TextField, Select, Flex, Checkbox, Text, IconButton } from "@radix-ui/themes";
|
||||||
ChevronUpDownIcon,
|
|
||||||
LockClosedIcon,
|
|
||||||
MagnifyingGlassIcon,
|
|
||||||
ArrowPathIcon,
|
|
||||||
} from "@heroicons/react/20/solid";
|
|
||||||
import {
|
|
||||||
Listbox,
|
|
||||||
ListboxButton,
|
|
||||||
ListboxOptions,
|
|
||||||
ListboxOption,
|
|
||||||
Transition,
|
|
||||||
} from "@headlessui/react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import type { Maybe } from "true-myth";
|
import type { Maybe } from "true-myth";
|
||||||
import { placeholders } from "@/constants";
|
import { placeholders } from "@/constants";
|
||||||
|
|
||||||
@@ -54,7 +41,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
detectedType,
|
detectedType,
|
||||||
}: LookupInputProps) => {
|
}: LookupInputProps) => {
|
||||||
const { register, handleSubmit, getValues } = useForm<SubmitProps>({
|
const { register, handleSubmit, getValues, control } = useForm<SubmitProps>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
target: "",
|
target: "",
|
||||||
// Not used at this time.
|
// Not used at this time.
|
||||||
@@ -115,200 +102,143 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
return result.success ? result.data : null;
|
return result.success ? result.data : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchIcon = (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className={cn({
|
|
||||||
"absolute inset-y-0 left-0 flex items-center pl-3": true,
|
|
||||||
"pointer-events-none": isLoading,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<ArrowPathIcon
|
|
||||||
className="h-5 w-5 animate-spin text-zinc-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<MagnifyingGlassIcon className="h-5 w-5 text-zinc-400" aria-hidden="true" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchInput = (
|
|
||||||
<input
|
|
||||||
className={cn(
|
|
||||||
"block w-full rounded-l-md border border-transparent lg:py-4.5",
|
|
||||||
"bg-zinc-700 py-2 pr-1.5 pl-10 text-sm placeholder-zinc-400 placeholder:translate-y-2 focus:text-zinc-200",
|
|
||||||
"focus:outline-hidden sm:text-sm md:py-3 md:text-base lg:text-lg"
|
|
||||||
)}
|
|
||||||
disabled={isLoading}
|
|
||||||
placeholder={placeholders[selected]}
|
|
||||||
type="search"
|
|
||||||
{...register("target", {
|
|
||||||
required: true,
|
|
||||||
onChange: () => {
|
|
||||||
if (onChange != undefined)
|
|
||||||
void onChange({
|
|
||||||
target: getValues("target"),
|
|
||||||
// dropdown target will be pulled from state anyways, so no need to provide it here
|
|
||||||
targetType: retrieveTargetType(null),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const dropdown = (
|
|
||||||
<Listbox
|
|
||||||
value={selected}
|
|
||||||
onChange={(value) => {
|
|
||||||
setSelected(value);
|
|
||||||
|
|
||||||
if (onChange != undefined)
|
|
||||||
void onChange({
|
|
||||||
target: getValues("target"),
|
|
||||||
// we provide the value as the state will not have updated yet for this context
|
|
||||||
targetType: retrieveTargetType(value),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<div className="relative">
|
|
||||||
<ListboxButton
|
|
||||||
className={cn(
|
|
||||||
"relative h-full w-full cursor-default rounded-r-lg bg-zinc-700 py-2 pr-10 pl-1 text-right whitespace-nowrap",
|
|
||||||
"text-xs focus:outline-hidden focus-visible:border-indigo-500 sm:text-sm md:text-base lg:text-lg",
|
|
||||||
"focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{/* Fetch special text for 'auto' mode, otherwise just use the options. */}
|
|
||||||
<span className="block">
|
|
||||||
{selected == "auto" ? (
|
|
||||||
// If the detected type was provided, then notate which in parentheses. Compact object naming might be better in the future.
|
|
||||||
detectedType.isJust ? (
|
|
||||||
<>
|
|
||||||
Auto (
|
|
||||||
<span className="animate-pulse">
|
|
||||||
{targetShortNames[detectedType.value]}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
objectNames["auto"]
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<LockClosedIcon
|
|
||||||
className="mr-2.5 mb-1 inline h-4 w-4 animate-pulse text-zinc-500"
|
|
||||||
aria-hidden
|
|
||||||
/>
|
|
||||||
{objectNames[selected]}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
|
||||||
<ChevronUpDownIcon className="h-5 w-5 text-zinc-200" aria-hidden="true" />
|
|
||||||
</span>
|
|
||||||
</ListboxButton>
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
leave="transition ease-in duration-100"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<ListboxOptions
|
|
||||||
className={cn(
|
|
||||||
"scrollbar-thin absolute right-0 mt-1 max-h-60 min-w-full overflow-auto rounded-md bg-zinc-700 py-1",
|
|
||||||
"text-zinc-200 shadow-lg ring-1 ring-black/5 focus:outline-hidden sm:text-sm"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{Object.entries(objectNames).map(([key, value]) => (
|
|
||||||
<ListboxOption
|
|
||||||
key={key}
|
|
||||||
className={({ focus }) =>
|
|
||||||
cn(
|
|
||||||
"relative cursor-default py-2 pr-4 pl-10 select-none",
|
|
||||||
focus ? "bg-zinc-800 text-zinc-300" : null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
value={key}
|
|
||||||
>
|
|
||||||
{({ selected }) => (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"block text-right text-xs whitespace-nowrap md:text-sm lg:text-base",
|
|
||||||
selected ? "font-medium" : null
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
{selected ? (
|
|
||||||
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-blue-500">
|
|
||||||
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
// TODO: Show Help Explanation
|
|
||||||
}}
|
|
||||||
className="absolute inset-y-0 left-0 flex items-center pl-4 text-lg font-bold opacity-20 hover:animate-pulse"
|
|
||||||
>
|
|
||||||
?
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ListboxOption>
|
|
||||||
))}
|
|
||||||
</ListboxOptions>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</Listbox>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="pb-3"
|
className="pb-2.5"
|
||||||
onSubmit={onSubmit != undefined ? onPromise(handleSubmit(onSubmit)) : preventDefault}
|
onSubmit={onSubmit != undefined ? onPromise(handleSubmit(onSubmit)) : preventDefault}
|
||||||
>
|
>
|
||||||
<div className="col">
|
<Flex direction="column" gap="3">
|
||||||
<label htmlFor="search" className="sr-only">
|
<label htmlFor="search" className="sr-only">
|
||||||
Search
|
Search
|
||||||
</label>
|
</label>
|
||||||
<div className="relative flex">
|
<Flex gap="0" style={{ position: "relative" }}>
|
||||||
{searchIcon}
|
<TextField.Root
|
||||||
{searchInput}
|
size="3"
|
||||||
{dropdown}
|
placeholder={placeholders[selected]}
|
||||||
</div>
|
disabled={isLoading}
|
||||||
</div>
|
{...register("target", {
|
||||||
<div className="col">
|
required: true,
|
||||||
<div className="flex flex-wrap pt-3 pb-1 text-sm">
|
onChange: () => {
|
||||||
<div className="whitespace-nowrap">
|
if (onChange != undefined)
|
||||||
<input
|
void onChange({
|
||||||
className="mr-1 ml-2 whitespace-nowrap text-zinc-800 accent-blue-700"
|
target: getValues("target"),
|
||||||
type="checkbox"
|
targetType: retrieveTargetType(null),
|
||||||
{...register("requestJSContact")}
|
});
|
||||||
/>
|
},
|
||||||
<label className="text-zinc-300" htmlFor="requestJSContact">
|
})}
|
||||||
|
style={{
|
||||||
|
borderTopRightRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
border: "1px solid var(--gray-7)",
|
||||||
|
borderRight: "none",
|
||||||
|
boxShadow: "none",
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField.Slot side="left">
|
||||||
|
<IconButton
|
||||||
|
size="1"
|
||||||
|
variant="ghost"
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
tabIndex={-1}
|
||||||
|
style={{ cursor: isLoading ? "not-allowed" : "pointer" }}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<ReloadIcon className="animate-spin" width="16" height="16" />
|
||||||
|
) : (
|
||||||
|
<MagnifyingGlassIcon width="16" height="16" />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</TextField.Slot>
|
||||||
|
</TextField.Root>
|
||||||
|
|
||||||
|
<Select.Root
|
||||||
|
value={selected}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelected(value as SimplifiedTargetType | "auto");
|
||||||
|
|
||||||
|
if (onChange != undefined)
|
||||||
|
void onChange({
|
||||||
|
target: getValues("target"),
|
||||||
|
targetType: retrieveTargetType(value),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={isLoading}
|
||||||
|
size="3"
|
||||||
|
>
|
||||||
|
<Select.Trigger
|
||||||
|
style={{
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
|
||||||
|
minWidth: "150px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selected == "auto" ? (
|
||||||
|
detectedType.isJust ? (
|
||||||
|
<Text>Auto ({targetShortNames[detectedType.value]})</Text>
|
||||||
|
) : (
|
||||||
|
objectNames["auto"]
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
<LockClosedIcon width="14" height="14" />
|
||||||
|
{objectNames[selected]}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Select.Trigger>
|
||||||
|
|
||||||
|
<Select.Content position="popper">
|
||||||
|
{Object.entries(objectNames).map(([key, value]) => (
|
||||||
|
<Select.Item key={key} value={key}>
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
justify="between"
|
||||||
|
gap="2"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Flex>
|
||||||
|
</Select.Item>
|
||||||
|
))}
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex pl="3" gapX="5" gapY="2" wrap="wrap">
|
||||||
|
<Flex asChild align="center" gap="2">
|
||||||
|
<Text as="label" size="2">
|
||||||
|
<Controller
|
||||||
|
name="requestJSContact"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
Request JSContact
|
Request JSContact
|
||||||
</label>
|
</Text>
|
||||||
</div>
|
</Flex>
|
||||||
<div className="whitespace-nowrap">
|
<Flex asChild align="center" gap="2">
|
||||||
<input
|
<Text as="label" size="2">
|
||||||
className="mr-1 ml-2 bg-zinc-500 text-inherit accent-blue-700"
|
<Controller
|
||||||
type="checkbox"
|
name="followReferral"
|
||||||
{...register("followReferral")}
|
control={control}
|
||||||
/>
|
render={({ field }) => (
|
||||||
<label className="text-zinc-300" htmlFor="followReferral">
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
Follow referral to registrar's RDAP record
|
Follow referral to registrar's RDAP record
|
||||||
</label>
|
</Text>
|
||||||
</div>
|
</Flex>
|
||||||
</div>
|
</Flex>
|
||||||
</div>
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Events from "@/components/lookup/Events";
|
|||||||
import Property from "@/components/common/Property";
|
import Property from "@/components/common/Property";
|
||||||
import PropertyList from "@/components/common/PropertyList";
|
import PropertyList from "@/components/common/PropertyList";
|
||||||
import AbstractCard from "@/components/common/AbstractCard";
|
import AbstractCard from "@/components/common/AbstractCard";
|
||||||
|
import { Flex, Text, DataList, Badge } from "@radix-ui/themes";
|
||||||
|
|
||||||
export type AutnumCardProps = {
|
export type AutnumCardProps = {
|
||||||
data: AutonomousNumber;
|
data: AutonomousNumber;
|
||||||
@@ -22,14 +23,13 @@ const AutnumCard: FunctionComponent<AutnumCardProps> = ({ data, url }: AutnumCar
|
|||||||
data={data}
|
data={data}
|
||||||
url={url}
|
url={url}
|
||||||
header={
|
header={
|
||||||
<>
|
<Flex gap="2" align="center" wrap="wrap">
|
||||||
<span className="font-mono tracking-tighter">AUTONOMOUS SYSTEM</span>
|
<Text size="5">{asnRange}</Text>
|
||||||
<span className="font-mono tracking-wide">{asnRange}</span>
|
<Badge color="gray">AUTONOMOUS SYSTEM</Badge>
|
||||||
<span className="whitespace-nowrap">({data.handle})</span>
|
</Flex>
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<dl>
|
<DataList.Root orientation={{ initial: "vertical", sm: "horizontal" }} size="2">
|
||||||
<Property title="Name">{data.name}</Property>
|
<Property title="Name">{data.name}</Property>
|
||||||
<Property title="Handle">{data.handle}</Property>
|
<Property title="Handle">{data.handle}</Property>
|
||||||
<Property title="ASN Range">
|
<Property title="ASN Range">
|
||||||
@@ -49,7 +49,7 @@ const AutnumCard: FunctionComponent<AutnumCardProps> = ({ data, url }: AutnumCar
|
|||||||
</PropertyList.Item>
|
</PropertyList.Item>
|
||||||
))}
|
))}
|
||||||
</PropertyList>
|
</PropertyList>
|
||||||
</dl>
|
</DataList.Root>
|
||||||
</AbstractCard>
|
</AbstractCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Events from "@/components/lookup/Events";
|
|||||||
import Property from "@/components/common/Property";
|
import Property from "@/components/common/Property";
|
||||||
import PropertyList from "@/components/common/PropertyList";
|
import PropertyList from "@/components/common/PropertyList";
|
||||||
import AbstractCard from "@/components/common/AbstractCard";
|
import AbstractCard from "@/components/common/AbstractCard";
|
||||||
|
import { Flex, Text, DataList, Badge } from "@radix-ui/themes";
|
||||||
|
|
||||||
export type DomainProps = {
|
export type DomainProps = {
|
||||||
data: Domain;
|
data: Domain;
|
||||||
@@ -18,16 +19,13 @@ const DomainCard: FunctionComponent<DomainProps> = ({ data, url }: DomainProps)
|
|||||||
data={data}
|
data={data}
|
||||||
url={url}
|
url={url}
|
||||||
header={
|
header={
|
||||||
<>
|
<Flex gap="2" align="center" wrap="wrap">
|
||||||
<span className="font-mono tracking-tighter">DOMAIN</span>
|
<Text size="5">{data.ldhName ?? data.unicodeName}</Text>
|
||||||
<span className="font-mono tracking-wide">
|
<Badge color="gray">DOMAIN</Badge>
|
||||||
{data.ldhName ?? data.unicodeName}
|
</Flex>
|
||||||
</span>
|
|
||||||
<span className="whitespace-nowrap">({data.handle})</span>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<dl>
|
<DataList.Root orientation={{ initial: "vertical", sm: "horizontal" }} size="2">
|
||||||
{data.unicodeName != undefined ? (
|
{data.unicodeName != undefined ? (
|
||||||
<Property title="Unicode Name">{data.unicodeName}</Property>
|
<Property title="Unicode Name">{data.unicodeName}</Property>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -45,7 +43,7 @@ const DomainCard: FunctionComponent<DomainProps> = ({ data, url }: DomainProps)
|
|||||||
</PropertyList.Item>
|
</PropertyList.Item>
|
||||||
))}
|
))}
|
||||||
</PropertyList>
|
</PropertyList>
|
||||||
</dl>
|
</DataList.Root>
|
||||||
</AbstractCard>
|
</AbstractCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { Entity } from "@/types";
|
|||||||
import Property from "@/components/common/Property";
|
import Property from "@/components/common/Property";
|
||||||
import PropertyList from "@/components/common/PropertyList";
|
import PropertyList from "@/components/common/PropertyList";
|
||||||
import AbstractCard from "@/components/common/AbstractCard";
|
import AbstractCard from "@/components/common/AbstractCard";
|
||||||
|
import { Flex, DataList, Badge, Text } from "@radix-ui/themes";
|
||||||
|
|
||||||
export type EntityCardProps = {
|
export type EntityCardProps = {
|
||||||
data: Entity;
|
data: Entity;
|
||||||
@@ -16,15 +17,13 @@ const EntityCard: FunctionComponent<EntityCardProps> = ({ data, url }: EntityCar
|
|||||||
data={data}
|
data={data}
|
||||||
url={url}
|
url={url}
|
||||||
header={
|
header={
|
||||||
<>
|
<Flex gap="2" align="center" wrap="wrap">
|
||||||
<span className="font-mono tracking-tighter">ENTITY</span>
|
<Text size="5">{data.handle || data.roles.join(", ")}</Text>
|
||||||
<span className="font-mono tracking-wide">
|
<Badge color="gray">ENTITY</Badge>
|
||||||
{data.handle || data.roles.join(", ")}
|
</Flex>
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<dl>
|
<DataList.Root orientation={{ initial: "vertical", sm: "horizontal" }} size="2">
|
||||||
{data.handle && <Property title="Handle">{data.handle}</Property>}
|
{data.handle && <Property title="Handle">{data.handle}</Property>}
|
||||||
<PropertyList title="Roles">
|
<PropertyList title="Roles">
|
||||||
{data.roles.map((role, index) => (
|
{data.roles.map((role, index) => (
|
||||||
@@ -42,7 +41,7 @@ const EntityCard: FunctionComponent<EntityCardProps> = ({ data, url }: EntityCar
|
|||||||
))}
|
))}
|
||||||
</PropertyList>
|
</PropertyList>
|
||||||
)}
|
)}
|
||||||
</dl>
|
</DataList.Root>
|
||||||
</AbstractCard>
|
</AbstractCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,26 +1,42 @@
|
|||||||
import type { FunctionComponent } from "react";
|
import type { FunctionComponent } from "react";
|
||||||
import type { Event } from "@/types";
|
import type { Event } from "@/types";
|
||||||
import { Fragment } from "react";
|
|
||||||
import DynamicDate from "@/components/common/DynamicDate";
|
import DynamicDate from "@/components/common/DynamicDate";
|
||||||
|
import { Table, Text } from "@radix-ui/themes";
|
||||||
|
|
||||||
export type EventsProps = {
|
export type EventsProps = {
|
||||||
data: Event[];
|
data: Event[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const Events: FunctionComponent<EventsProps> = ({ data }) => {
|
const Events: FunctionComponent<EventsProps> = ({ data }) => {
|
||||||
return (
|
return (
|
||||||
<dl>
|
<Table.Root size="1" variant="surface">
|
||||||
{data.map(({ eventAction, eventDate, eventActor }, index) => {
|
<Table.Header>
|
||||||
return (
|
<Table.Row>
|
||||||
<Fragment key={index}>
|
<Table.ColumnHeaderCell>Event</Table.ColumnHeaderCell>
|
||||||
<dt className="font-weight-bolder">{eventAction}:</dt>
|
<Table.ColumnHeaderCell>Date</Table.ColumnHeaderCell>
|
||||||
<dd>
|
<Table.ColumnHeaderCell>Actor</Table.ColumnHeaderCell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body>
|
||||||
|
{data.map(({ eventAction, eventDate, eventActor }, index) => (
|
||||||
|
<Table.Row key={index}>
|
||||||
|
<Table.Cell>
|
||||||
|
<Text size="2" weight="medium">
|
||||||
|
{eventAction}
|
||||||
|
</Text>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
<DynamicDate value={new Date(eventDate)} />
|
<DynamicDate value={new Date(eventDate)} />
|
||||||
{eventActor != null ? ` (by ${eventActor})` : null}
|
</Table.Cell>
|
||||||
</dd>
|
<Table.Cell>
|
||||||
</Fragment>
|
<Text size="2" color="gray">
|
||||||
);
|
{eventActor ?? "—"}
|
||||||
})}
|
</Text>
|
||||||
</dl>
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
))}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,6 @@ const Generic: FunctionComponent<ObjectProps> = ({ data, url }: ObjectProps) =>
|
|||||||
</AbstractCard>
|
</AbstractCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// const title: string = (data.unicodeName ?? data.ldhName ?? data.handle)?.toUpperCase() ?? "Response";
|
|
||||||
// return <div className="card">
|
|
||||||
// <div className="card-header">{title}</div>
|
|
||||||
// {objectFragment}
|
|
||||||
// </div>
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Generic;
|
export default Generic;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Events from "@/components/lookup/Events";
|
|||||||
import Property from "@/components/common/Property";
|
import Property from "@/components/common/Property";
|
||||||
import PropertyList from "@/components/common/PropertyList";
|
import PropertyList from "@/components/common/PropertyList";
|
||||||
import AbstractCard from "@/components/common/AbstractCard";
|
import AbstractCard from "@/components/common/AbstractCard";
|
||||||
|
import { Flex, Text, DataList, Badge } from "@radix-ui/themes";
|
||||||
|
|
||||||
export type IPCardProps = {
|
export type IPCardProps = {
|
||||||
data: IpNetwork;
|
data: IpNetwork;
|
||||||
@@ -17,17 +18,15 @@ const IPCard: FunctionComponent<IPCardProps> = ({ data, url }: IPCardProps) => {
|
|||||||
data={data}
|
data={data}
|
||||||
url={url}
|
url={url}
|
||||||
header={
|
header={
|
||||||
<>
|
<Flex gap="2" align="center" wrap="wrap">
|
||||||
<span className="font-mono tracking-tighter">IP NETWORK</span>
|
<Text size="5">
|
||||||
<span className="font-mono tracking-wide">
|
{data.startAddress} - {data.endAddress}
|
||||||
{data.startAddress}
|
</Text>
|
||||||
{data.startAddress !== data.endAddress && ` - ${data.endAddress}`}
|
<Badge color="gray">IP NETWORK</Badge>
|
||||||
</span>
|
</Flex>
|
||||||
<span className="whitespace-nowrap">({data.handle})</span>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<dl>
|
<DataList.Root orientation={{ initial: "vertical", sm: "horizontal" }} size="2">
|
||||||
<Property title="Name">{data.name}</Property>
|
<Property title="Name">{data.name}</Property>
|
||||||
<Property title="Handle">{data.handle}</Property>
|
<Property title="Handle">{data.handle}</Property>
|
||||||
<Property title="IP Version">{data.ipVersion.toUpperCase()}</Property>
|
<Property title="IP Version">{data.ipVersion.toUpperCase()}</Property>
|
||||||
@@ -48,7 +47,7 @@ const IPCard: FunctionComponent<IPCardProps> = ({ data, url }: IPCardProps) => {
|
|||||||
</PropertyList.Item>
|
</PropertyList.Item>
|
||||||
))}
|
))}
|
||||||
</PropertyList>
|
</PropertyList>
|
||||||
</dl>
|
</DataList.Root>
|
||||||
</AbstractCard>
|
</AbstractCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from "react";
|
|||||||
import type { Nameserver } from "@/types";
|
import type { Nameserver } from "@/types";
|
||||||
import Property from "@/components/common/Property";
|
import Property from "@/components/common/Property";
|
||||||
import AbstractCard from "@/components/common/AbstractCard";
|
import AbstractCard from "@/components/common/AbstractCard";
|
||||||
|
import { Flex, DataList, Badge, Text } from "@radix-ui/themes";
|
||||||
|
|
||||||
export type NameserverCardProps = {
|
export type NameserverCardProps = {
|
||||||
data: Nameserver;
|
data: Nameserver;
|
||||||
@@ -18,15 +19,15 @@ const NameserverCard: FunctionComponent<NameserverCardProps> = ({
|
|||||||
data={data}
|
data={data}
|
||||||
url={url}
|
url={url}
|
||||||
header={
|
header={
|
||||||
<>
|
<Flex gap="2" align="center" wrap="wrap">
|
||||||
<span className="font-mono tracking-tighter">NAMESERVER</span>
|
<Text size="5">{data.ldhName}</Text>
|
||||||
<span className="font-mono tracking-wide">{data.ldhName}</span>
|
<Badge color="gray">NAMESERVER</Badge>
|
||||||
</>
|
</Flex>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<dl>
|
<DataList.Root orientation={{ initial: "vertical", sm: "horizontal" }} size="2">
|
||||||
<Property title="LDH Name">{data.ldhName}</Property>
|
<Property title="LDH Name">{data.ldhName}</Property>
|
||||||
</dl>
|
</DataList.Root>
|
||||||
</AbstractCard>
|
</AbstractCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import { type AppType } from "next/dist/shared/lib/utils";
|
import { type AppType } from "next/dist/shared/lib/utils";
|
||||||
|
import { ThemeProvider } from "next-themes";
|
||||||
|
import { Theme } from "@radix-ui/themes";
|
||||||
|
|
||||||
import "@fontsource-variable/inter";
|
import "@fontsource-variable/inter";
|
||||||
import "@fontsource/ibm-plex-mono/400.css";
|
import "@fontsource/ibm-plex-mono/400.css";
|
||||||
|
import "@radix-ui/themes/styles.css";
|
||||||
|
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
|
|
||||||
const MyApp: AppType = ({ Component, pageProps }) => {
|
const MyApp: AppType = ({ Component, pageProps }) => {
|
||||||
return <Component {...pageProps} />;
|
return (
|
||||||
|
<ThemeProvider attribute="class" defaultTheme="system">
|
||||||
|
<Theme accentColor="indigo" grayColor="slate" radius="medium" scaling="100%">
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</Theme>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MyApp;
|
export default MyApp;
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import type { MetaParsedGeneric } from "@/hooks/useLookup";
|
|||||||
import useLookup from "@/hooks/useLookup";
|
import useLookup from "@/hooks/useLookup";
|
||||||
import LookupInput from "@/components/form/LookupInput";
|
import LookupInput from "@/components/form/LookupInput";
|
||||||
import ErrorCard from "@/components/common/ErrorCard";
|
import ErrorCard from "@/components/common/ErrorCard";
|
||||||
|
import { ThemeToggle } from "@/components/common/ThemeToggle";
|
||||||
import { Maybe } from "true-myth";
|
import { Maybe } from "true-myth";
|
||||||
import type { TargetType } from "@/types";
|
import type { TargetType } from "@/types";
|
||||||
|
import { Flex, Container, Section, Text, Link } from "@radix-ui/themes";
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
const Index: NextPage = () => {
|
||||||
const { error, setTarget, setTargetType, submit, getType } = useLookup();
|
const { error, setTarget, setTargetType, submit, getType } = useLookup();
|
||||||
@@ -37,16 +39,30 @@ const Index: NextPage = () => {
|
|||||||
content="xevion, rdap, whois, rdap, domain name, dns, ip address"
|
content="xevion, rdap, whois, rdap, domain name, dns, ip address"
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
<nav className="bg-zinc-850 px-5 py-4 shadow-xs">
|
<Flex
|
||||||
<span className="text-xl font-medium text-white" style={{ fontSize: "larger" }}>
|
asChild
|
||||||
<a href="https://github.com/Xevion/rdap">rdap</a>
|
justify="between"
|
||||||
<a href={"https://xevion.dev"} className="text-zinc-400 hover:animate-pulse">
|
align="center"
|
||||||
.xevion.dev
|
px="5"
|
||||||
</a>
|
py="4"
|
||||||
</span>
|
style={{
|
||||||
</nav>
|
borderBottom: "1px solid var(--gray-a5)",
|
||||||
<div className="mx-auto max-w-screen-sm px-5 lg:max-w-screen-md xl:max-w-screen-lg">
|
}}
|
||||||
<div className="dark container mx-auto w-full py-6 md:py-12">
|
>
|
||||||
|
<nav>
|
||||||
|
<Text size="5" weight="medium">
|
||||||
|
<Link href="https://github.com/Xevion/rdap" color="gray" highContrast>
|
||||||
|
rdap
|
||||||
|
</Link>
|
||||||
|
<Link href="https://xevion.dev" color="gray">
|
||||||
|
.xevion.dev
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
<ThemeToggle />
|
||||||
|
</nav>
|
||||||
|
</Flex>
|
||||||
|
<Container size="3" px="5">
|
||||||
|
<Section size="2">
|
||||||
<LookupInput
|
<LookupInput
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
detectedType={detectedType}
|
detectedType={detectedType}
|
||||||
@@ -83,8 +99,8 @@ const Index: NextPage = () => {
|
|||||||
{response.isJust ? (
|
{response.isJust ? (
|
||||||
<Generic url={response.value.url} data={response.value.data} />
|
<Generic url={response.value.url} data={response.value.data} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</Section>
|
||||||
</div>
|
</Container>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,31 +7,27 @@
|
|||||||
--font-mono:
|
--font-mono:
|
||||||
"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
"Courier New", monospace;
|
"Courier New", monospace;
|
||||||
--color-zinc-850: #1d1d20;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin: 0.5em 0 1em 2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Utility classes */
|
||||||
.dashed {
|
.dashed {
|
||||||
border-bottom: 1px dashed silver;
|
border-bottom: 1px dashed var(--gray-a6);
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
color-scheme: dark;
|
|
||||||
@apply bg-zinc-900 font-sans text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd,
|
|
||||||
dl {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl {
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-thin {
|
.scrollbar-thin {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Keep animate-spin for loading states */
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user