@@ -12,8 +12,9 @@ import {
1212 DialogTitle ,
1313} from "@/components/ui/dialog" ;
1414import { useAccount , useSignMessage , useSwitchChain } from "wagmi" ;
15- import { FormEvent , useCallback , useState } from "react" ;
15+ import { FormEvent , useCallback , useRef , useState } from "react" ;
1616import { CopyIcon , CopyCheckIcon , CopyXIcon , Loader2Icon } from "lucide-react" ;
17+ import { z } from "zod" ;
1718import { Input } from "@/components/ui/input" ;
1819import { Label } from "@/components/ui/label" ;
1920import { useFarcasterIdentity } from "../hooks/useFarcasterIdentity" ;
@@ -28,6 +29,7 @@ type FarcasterDomainAccountAssociationDialogProps = {
2829export function FarcasterDomainAccountAssociationDialog ( {
2930 onClose,
3031} : FarcasterDomainAccountAssociationDialogProps ) {
32+ const domainInputRef = useRef < HTMLInputElement > ( null ) ;
3133 const copyCompact = useCopyToClipboard ( ) ;
3234 const copyJSON = useCopyToClipboard ( ) ;
3335 const account = useAccount ( ) ;
@@ -44,7 +46,7 @@ export function FarcasterDomainAccountAssociationDialog({
4446 async ( event : FormEvent < HTMLFormElement > ) => {
4547 event . preventDefault ( ) ;
4648
47- const data = new FormData ( event . currentTarget ) ;
49+ const data = Object . fromEntries ( new FormData ( event . currentTarget ) ) ;
4850
4951 try {
5052 if ( farcasterSigner . signer ?. status !== "approved" ) {
@@ -55,12 +57,36 @@ export function FarcasterDomainAccountAssociationDialog({
5557 throw new Error ( "Account address is not available" ) ;
5658 }
5759
58- const domain = data . get ( "domain" ) ;
60+ const parser = z . object ( {
61+ domain : z
62+ . preprocess ( ( val ) => {
63+ if ( typeof val === "string" ) {
64+ // prepend with prefix because normally it is the domain but we want to validate
65+ // it is in valid format
66+ return `http://${ val } ` ;
67+ }
5968
60- if ( typeof domain !== "string" || ! domain ) {
61- throw new Error ( "Domain is required" ) ;
69+ return val ;
70+ } , z . string ( ) . url ( "Invalid domain" ) )
71+ // remove the protocol prefix
72+ . transform ( ( val ) => val . substring ( 7 ) ) ,
73+ } ) ;
74+
75+ const parseResult = parser . safeParse ( data ) ;
76+
77+ if ( ! parseResult . success ) {
78+ parseResult . error . errors . map ( ( error ) => {
79+ domainInputRef . current ?. setCustomValidity ( error . message ) ;
80+ } ) ;
81+
82+ event . currentTarget . reportValidity ( ) ;
83+
84+ return ;
6285 }
6386
87+ domainInputRef . current ?. setCustomValidity ( "" ) ;
88+ event . currentTarget . reportValidity ( ) ;
89+
6490 setIsGenerating ( true ) ;
6591
6692 await switchChainAsync ( {
@@ -69,8 +95,9 @@ export function FarcasterDomainAccountAssociationDialog({
6995
7096 const result = await sign ( {
7197 fid : farcasterSigner . signer . fid ,
72- payload :
73- constructJSONFarcasterSignatureAccountAssociationPaylod ( domain ) ,
98+ payload : constructJSONFarcasterSignatureAccountAssociationPaylod (
99+ parseResult . data . domain
100+ ) ,
74101 signer : {
75102 type : "custody" ,
76103 custodyAddress : account . address ,
@@ -117,15 +144,25 @@ export function FarcasterDomainAccountAssociationDialog({
117144 < DialogTitle > Domain Account Association</ DialogTitle >
118145 </ DialogHeader >
119146 { ! associationResult && (
120- < form id = "domain-account-association-form" onSubmit = { handleSubmit } >
147+ < form
148+ className = "flex flex-col gap-2"
149+ id = "domain-account-association-form"
150+ onSubmit = { handleSubmit }
151+ noValidate
152+ >
121153 < Label htmlFor = "domain" > Domain</ Label >
122154 < Input
123155 id = "domain"
124156 name = "domain"
125- pattern = "^.+\..+$"
126157 required
127158 type = "text"
159+ ref = { domainInputRef }
128160 />
161+ < span className = "text-muted-foreground text-sm" >
162+ A domain of your frame, e.g. for https://framesjs.org the domain
163+ is framesjs.org, for http://localhost:3000 the domain is
164+ localhost.
165+ </ span >
129166 </ form >
130167 ) }
131168 { associationResult && (
0 commit comments