diff --git a/components/HandleSearchInput.tsx b/components/HandleSearchInput.tsx index d8efa5b9..91f17691 100644 --- a/components/HandleSearchInput.tsx +++ b/components/HandleSearchInput.tsx @@ -1,4 +1,6 @@ "use client"; +import { useRef } from "react"; +import { isValidHandle } from "@atproto/syntax"; import { Input } from "components/Input"; import { Combobox, ComboboxResult } from "components/Combobox"; import { @@ -39,6 +41,9 @@ export const HandleSearchInput = (props: { // handle was entered); cleared on focus via onFocus. highlight?: boolean; onFocus?: () => void; + // Reject typed values that aren't syntactically valid handles with a native + // validation bubble instead of submitting them (suggestion picks are trusted). + validateHandle?: boolean; }) => { let { handleValue, @@ -51,10 +56,23 @@ export const HandleSearchInput = (props: { setHighlighted, } = useActorTypeahead(); + let triggerRef = useRef(null); + const handleSelect = async (handle?: string) => { - const selected = handle ?? handleValue; + let selected = handle ?? handleValue; if (!selected) return; const actor = suggestions.find((s) => s.handle === selected); + if (props.validateHandle && !actor) { + selected = selected.trim().replace(/^@/, ""); + if (!isValidHandle(selected)) { + let input = triggerRef.current?.querySelector("input"); + input?.setCustomValidity( + "Please enter a full handle, like name.bsky.social", + ); + input?.reportValidity(); + return; + } + } setHandleValue(selected); setDropdownOpen(false); setSuggestions([]); @@ -87,6 +105,7 @@ export const HandleSearchInput = (props: { className="w-(--radix-popover-trigger-width)!" trigger={
{ + e.currentTarget.setCustomValidity(""); setHandleValue(e.target.value); props.onChange?.(e.target.value); }} diff --git a/components/LoginButton.tsx b/components/LoginButton.tsx index 29cb3d61..944e6cb6 100644 --- a/components/LoginButton.tsx +++ b/components/LoginButton.tsx @@ -198,6 +198,7 @@ export const LoginContent = (props: { autoFocus={props.open !== false} action={} loading={loading} + validateHandle onSubmit={(handle) => { setLoading(true); window.location.href = buildOauthLoginUrl({ @@ -334,6 +335,7 @@ const LinkAtmosphereContent = (props: { autoFocus={props.open !== false} action={} loading={loading} + validateHandle onSubmit={(handle) => { setLoading(true); window.location.href = buildOauthLoginUrl({