fix: address code quality issues and critical bugs

- Fix getBestURL HTTPS prioritization bug (critical security issue)
- Standardize equality operators to === for status code checks
- Add JSON parsing error handling with try-catch
- Improve IP address validation with explicit error messages
- Fix ARIA label accessibility (add id="search" to TextField)
- Remove unused callback hook in useLookup
- Add type detection debouncing (150ms) using @mantine/hooks
- Remove duplicate LookupInput component from src/components/form/
This commit is contained in:
2025-10-22 16:44:12 -05:00
parent 3ff347b81f
commit 99b65363b4
9 changed files with 50 additions and 282 deletions

View File

@@ -1,246 +0,0 @@
import { useForm, Controller } from "react-hook-form";
import type { FunctionComponent } from "react";
import { useState } from "react";
import { onPromise, preventDefault } from "@/lib/utils";
import type { SimplifiedTargetType, SubmitProps, TargetType } from "@/rdap/schemas";
import { TargetTypeEnum } from "@/rdap/schemas";
import { MagnifyingGlassIcon, ReloadIcon, LockClosedIcon } from "@radix-ui/react-icons";
import { TextField, Select, Flex, Checkbox, Text, IconButton } from "@radix-ui/themes";
import type { Maybe } from "true-myth";
import { placeholders } from "@/rdap/constants";
/**
* Props for the LookupInput component.
*/
type LookupInputProps = {
isLoading?: boolean;
/**
* Callback function called when a type of registry is detected when a user changes their input.
* @param type - The detected type of registry.
* @returns A promise.
*/
onRegistry?: (type: TargetType) => Promise<void>;
/**
* Callback function called when a user hits submit.
* @param props - The submit props.
* @returns A promise.
*/
onSubmit?: (props: SubmitProps) => Promise<void>;
/**
* Callback function called when a user changes their input (text search) or explicitly changes the type of search.
* @param target - The target object containing the search target and target type.
* @returns Nothing.
*/
onChange?: (target: { target: string; targetType: TargetType | null }) => Promise<void>;
detectedType: Maybe<TargetType>;
};
const LookupInput: FunctionComponent<LookupInputProps> = ({
isLoading,
onSubmit,
onChange,
detectedType,
}: LookupInputProps) => {
const { register, handleSubmit, getValues, control } = useForm<SubmitProps>({
defaultValues: {
target: "",
// Not used at this time.
followReferral: false,
requestJSContact: false,
},
});
/**
* A mapping of available (simple) target types to their long-form human-readable names.
*/
const objectNames: Record<SimplifiedTargetType | "auto", string> = {
auto: "Autodetect",
domain: "Domain",
ip: "IP/CIDR", // IPv4/IPv6 are combined into this option
tld: "TLD",
autnum: "AS Number",
entity: "Entity Handle",
registrar: "Registrar",
url: "URL",
json: "JSON",
};
/**
* Mapping of precise target types to their simplified short-form names.
*/
const targetShortNames: Record<TargetType, string> = {
domain: "Domain",
tld: "TLD",
ip4: "IPv4",
ip6: "IPv6",
autnum: "ASN",
entity: "Entity",
registrar: "Registrar",
url: "URL",
json: "JSON",
};
/**
* Represents the selected value in the LookupInput component.
*/
const [selected, setSelected] = useState<SimplifiedTargetType | "auto">("auto");
/**
* Retrieves the target type based on the provided value.
* @param value - The value to retrieve the target type for.
* @returns The target type as ObjectType or null.
*/
function retrieveTargetType(value?: string | null): TargetType | null {
// If the value is null and the selected value is null, return null.
if (value == null) value = selected;
// 'auto' means 'do whatever' so we return null.
if (value == "auto") return null;
// Validate the value is a valid TargetType
const result = TargetTypeEnum.safeParse(value);
return result.success ? result.data : null;
}
return (
<form
className="pb-2.5"
onSubmit={onSubmit != undefined ? onPromise(handleSubmit(onSubmit)) : preventDefault}
>
<Flex direction="column" gap="3">
<label htmlFor="search" className="sr-only">
Search
</label>
<Flex gap="0" style={{ position: "relative" }}>
<TextField.Root
size="3"
placeholder={placeholders[selected]}
disabled={isLoading}
{...register("target", {
required: true,
onChange: () => {
if (onChange != undefined)
void onChange({
target: getValues("target"),
targetType: retrieveTargetType(null),
});
},
})}
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
</Text>
</Flex>
<Flex asChild align="center" gap="2">
<Text as="label" size="2">
<Controller
name="followReferral"
control={control}
render={({ field }) => (
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
)}
/>
Follow referral to registrar&apos;s RDAP record
</Text>
</Flex>
</Flex>
</Flex>
</form>
);
};
export default LookupInput;