@@ -8,13 +8,21 @@ import { CopyTextButton } from "@/components/ui/CopyTextButton";
88import { Input } from "@/components/ui/input" ;
99import { useDashboardRouter } from "@/lib/DashboardRouter" ;
1010import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler" ;
11+ import { zodResolver } from "@hookform/resolvers/zod" ;
1112import { useMutation } from "@tanstack/react-query" ;
1213import { FileInput } from "components/shared/FileInput" ;
1314import { useState } from "react" ;
15+ import { useForm } from "react-hook-form" ;
1416import { toast } from "sonner" ;
1517import type { ThirdwebClient } from "thirdweb" ;
18+ import { z } from "zod" ;
1619import { TeamDomainVerificationCard } from "../_components/settings-cards/domain-verification" ;
17- import { teamSlugRegex } from "./common" ;
20+ import {
21+ maxTeamNameLength ,
22+ maxTeamSlugLength ,
23+ teamNameSchema ,
24+ teamSlugSchema ,
25+ } from "./common" ;
1826
1927type UpdateTeamField = ( team : Partial < Team > ) => Promise < void > ;
2028
@@ -59,111 +67,109 @@ export function TeamGeneralSettingsPageUI(props: {
5967 ) ;
6068}
6169
70+ const teamNameFormSchema = z . object ( {
71+ name : teamNameSchema ,
72+ } ) ;
73+
6274function TeamNameFormControl ( props : {
6375 team : Team ;
6476 updateTeamField : UpdateTeamField ;
6577} ) {
66- const [ teamName , setTeamName ] = useState ( props . team . name ) ;
67- const maxTeamNameLength = 32 ;
68-
78+ const form = useForm < { name : string } > ( {
79+ values : { name : props . team . name } ,
80+ resolver : zodResolver ( teamNameFormSchema ) ,
81+ } ) ;
6982 const updateTeamMutation = useMutation ( {
7083 mutationFn : ( name : string ) => props . updateTeamField ( { name } ) ,
7184 } ) ;
7285
73- function handleSave ( ) {
74- const promises = updateTeamMutation . mutateAsync ( teamName ) ;
75- toast . promise ( promises , {
76- success : "Team name updated successfully" ,
77- error : "Failed to update team name" ,
78- } ) ;
79- }
80-
8186 return (
82- < SettingsCard
83- header = { {
84- title : "Team Name" ,
85- description : "This is your team's name on thirdweb" ,
86- } }
87- bottomText = { `Please use ${ maxTeamNameLength } characters at maximum.` }
88- saveButton = { {
89- onClick : handleSave ,
90- disabled : teamName . length === 0 ,
91- isPending : updateTeamMutation . isPending ,
92- } }
93- errorText = { undefined }
94- noPermissionText = { undefined } // TODO
87+ < form
88+ onSubmit = { form . handleSubmit ( ( values ) => {
89+ const promise = updateTeamMutation . mutateAsync ( values . name ) ;
90+ toast . promise ( promise , {
91+ success : "Team name updated successfully" ,
92+ error : "Failed to update team name" ,
93+ } ) ;
94+ } ) }
9595 >
96- < Input
97- value = { teamName }
98- maxLength = { maxTeamNameLength }
99- onChange = { ( e ) => {
100- setTeamName ( e . target . value ) ;
96+ < SettingsCard
97+ header = { {
98+ title : "Team Name" ,
99+ description : "This is your team's name on thirdweb" ,
101100 } }
102- className = "md:w-[450px]"
103- />
104- </ SettingsCard >
101+ bottomText = { `Please use ${ maxTeamNameLength } characters at maximum.` }
102+ saveButton = { {
103+ type : "submit" ,
104+ disabled : ! form . formState . isDirty ,
105+ isPending : updateTeamMutation . isPending ,
106+ } }
107+ errorText = { form . formState . errors . name ?. message }
108+ noPermissionText = { undefined }
109+ >
110+ < Input
111+ { ...form . register ( "name" ) }
112+ maxLength = { maxTeamNameLength }
113+ className = "md:w-[450px]"
114+ />
115+ </ SettingsCard >
116+ </ form >
105117 ) ;
106118}
107119
120+ const teamSlugFormSchema = z . object ( {
121+ slug : teamSlugSchema ,
122+ } ) ;
123+
108124function TeamSlugFormControl ( props : {
109125 team : Team ;
110126 updateTeamField : ( team : Partial < Team > ) => Promise < void > ;
111127} ) {
112- const [ teamSlug , setTeamSlug ] = useState ( props . team . slug ) ;
113- const maxTeamURLLength = 48 ;
114- const [ errorMessage , setErrorMessage ] = useState < string | undefined > ( ) ;
115-
128+ const form = useForm < { slug : string } > ( {
129+ defaultValues : { slug : props . team . slug } ,
130+ resolver : zodResolver ( teamSlugFormSchema ) ,
131+ } ) ;
116132 const updateTeamMutation = useMutation ( {
117- mutationFn : ( slug : string ) => props . updateTeamField ( { slug : slug } ) ,
133+ mutationFn : ( slug : string ) => props . updateTeamField ( { slug } ) ,
118134 } ) ;
119135
120- function handleSave ( ) {
121- const promises = updateTeamMutation . mutateAsync ( teamSlug ) ;
122- toast . promise ( promises , {
123- success : "Team URL updated successfully" ,
124- error : "Failed to update team URL" ,
125- } ) ;
126- }
127-
128136 return (
129- < SettingsCard
130- header = { {
131- title : "Team URL" ,
132- description :
133- "This is your team's URL namespace on thirdweb. All your team's projects and settings can be accessed using this URL" ,
134- } }
135- bottomText = { `Please use ${ maxTeamURLLength } characters at maximum.` }
136- errorText = { errorMessage }
137- saveButton = { {
138- onClick : handleSave ,
139- disabled : errorMessage !== undefined ,
140- isPending : updateTeamMutation . isPending ,
141- } }
142- noPermissionText = { undefined } // TODO
137+ < form
138+ onSubmit = { form . handleSubmit ( ( values ) => {
139+ const promise = updateTeamMutation . mutateAsync ( values . slug ) ;
140+ toast . promise ( promise , {
141+ success : "Team URL updated successfully" ,
142+ error : "Failed to update team URL" ,
143+ } ) ;
144+ } ) }
143145 >
144- < div className = "relative flex rounded-lg border border-border md:w-[450px]" >
145- < div className = "flex items-center self-stretch rounded-l-lg border-border border-r bg-card px-3 font-mono text-muted-foreground/80 text-sm" >
146- thirdweb.com/team/
146+ < SettingsCard
147+ header = { {
148+ title : "Team URL" ,
149+ description :
150+ "This is your team's URL namespace on thirdweb. All your team's projects and settings can be accessed using this URL" ,
151+ } }
152+ bottomText = { `Please use ${ maxTeamSlugLength } characters at maximum.` }
153+ errorText = { form . formState . errors . slug ?. message }
154+ saveButton = { {
155+ type : "submit" ,
156+ disabled : ! form . formState . isDirty ,
157+ isPending : updateTeamMutation . isPending ,
158+ } }
159+ noPermissionText = { undefined }
160+ >
161+ < div className = "relative flex rounded-lg border border-border md:w-[450px]" >
162+ < div className = "flex items-center self-stretch rounded-l-lg border-border border-r bg-card px-3 font-mono text-muted-foreground/80 text-sm" >
163+ thirdweb.com/team/
164+ </ div >
165+ < Input
166+ { ...form . register ( "slug" ) }
167+ maxLength = { maxTeamSlugLength }
168+ className = "truncate border-0 font-mono"
169+ />
147170 </ div >
148- < Input
149- value = { teamSlug }
150- onChange = { ( e ) => {
151- const value = e . target . value . slice ( 0 , maxTeamURLLength ) ;
152- setTeamSlug ( value ) ;
153- if ( value . trim ( ) . length === 0 ) {
154- setErrorMessage ( "Team URL can not be empty" ) ;
155- } else if ( teamSlugRegex . test ( value ) ) {
156- setErrorMessage (
157- "Invalid Team URL. Only letters, numbers and hyphens are allowed" ,
158- ) ;
159- } else {
160- setErrorMessage ( undefined ) ;
161- }
162- } }
163- className = "truncate border-0 font-mono"
164- />
165- </ div >
166- </ SettingsCard >
171+ </ SettingsCard >
172+ </ form >
167173 ) ;
168174}
169175
@@ -186,8 +192,8 @@ function TeamAvatarFormControl(props: {
186192 } ) ;
187193
188194 function handleSave ( ) {
189- const promises = updateTeamAvatarMutation . mutateAsync ( teamAvatar ) ;
190- toast . promise ( promises , {
195+ const promise = updateTeamAvatarMutation . mutateAsync ( teamAvatar ) ;
196+ toast . promise ( promise , {
191197 success : "Team avatar updated successfully" ,
192198 error : "Failed to update team avatar" ,
193199 } ) ;
@@ -263,8 +269,8 @@ export function LeaveTeamCard(props: {
263269 } ) ;
264270
265271 function handleLeave ( ) {
266- const promises = leaveTeam . mutateAsync ( ) ;
267- toast . promise ( promises , {
272+ const promise = leaveTeam . mutateAsync ( ) ;
273+ toast . promise ( promise , {
268274 success : "Left team successfully" ,
269275 error : "Failed to leave team" ,
270276 } ) ;
@@ -308,8 +314,8 @@ export function DeleteTeamCard(props: {
308314 } ) ;
309315
310316 function handleDelete ( ) {
311- const promises = deleteTeam . mutateAsync ( ) ;
312- toast . promise ( promises , {
317+ const promise = deleteTeam . mutateAsync ( ) ;
318+ toast . promise ( promise , {
313319 success : "Team deleted successfully" ,
314320 error : "Failed to delete team" ,
315321 } ) ;
0 commit comments