2
2
3
3
import type { Project } from "@/api/projects" ;
4
4
import type { SMSCountryTiers } from "@/api/sms" ;
5
+ import type { Team } from "@/api/team" ;
5
6
import { DynamicHeight } from "@/components/ui/DynamicHeight" ;
6
7
import { Spinner } from "@/components/ui/Spinner/Spinner" ;
7
8
import { UnderlineLink } from "@/components/ui/UnderlineLink" ;
@@ -20,6 +21,8 @@ import { Input } from "@/components/ui/input";
20
21
import { Label } from "@/components/ui/label" ;
21
22
import { Textarea } from "@/components/ui/textarea" ;
22
23
import { TrackedLinkTW } from "@/components/ui/tracked-link" ;
24
+ import { useThirdwebClient } from "@/constants/thirdweb.client" ;
25
+ import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler" ;
23
26
import { cn } from "@/lib/utils" ;
24
27
import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi" ;
25
28
import { zodResolver } from "@hookform/resolvers/zod" ;
@@ -33,10 +36,12 @@ import {
33
36
import { useTrack } from "hooks/analytics/useTrack" ;
34
37
import { CircleAlertIcon , PlusIcon , Trash2Icon } from "lucide-react" ;
35
38
import type React from "react" ;
39
+ import { useState } from "react" ;
36
40
import { type UseFormReturn , useFieldArray , useForm } from "react-hook-form" ;
37
41
import { toast } from "sonner" ;
42
+ import { upload } from "thirdweb/storage" ;
38
43
import { toArrFromList } from "utils/string" ;
39
- import type { Team } from "../../../@/api/team " ;
44
+ import { FileInput } from "../../shared/FileInput " ;
40
45
import CountrySelector from "./sms-country-select/country-selector" ;
41
46
42
47
type InAppWalletSettingsPageProps = {
@@ -339,41 +344,49 @@ function BrandingFieldset(props: {
339
344
className = "grid grid-cols-1 gap-6 lg:grid-cols-2"
340
345
show = { canEditAdvancedFeatures && ! ! form . watch ( "branding" ) }
341
346
>
342
- { /* Application Name */ }
347
+ { /* Application Image */ }
343
348
< FormField
344
349
control = { form . control }
345
- name = "branding.applicationName"
346
- render = { ( { field } ) => (
347
- < FormItem >
348
- < FormLabel > Application Name</ FormLabel >
350
+ name = "branding.applicationImageUrl"
351
+ render = { ( ) => (
352
+ < FormItem className = "space-y-1" >
353
+ < FormLabel > Application Image URL</ FormLabel >
354
+ < FormDescription className = "!mb-4" >
355
+ Logo that will display in the emails sent to users.{ " " }
356
+ < br className = "max-sm:hidden" /> The image must be squared with
357
+ recommended size of 72x72 px.
358
+ </ FormDescription >
349
359
< FormControl >
350
- < Input { ...field } />
360
+ < AppImageFormControl
361
+ uri = { form . watch ( "branding.applicationImageUrl" ) }
362
+ setUri = { ( uri ) => {
363
+ form . setValue ( "branding.applicationImageUrl" , uri , {
364
+ shouldDirty : true ,
365
+ shouldTouch : true ,
366
+ } ) ;
367
+ } }
368
+ />
351
369
</ FormControl >
352
- < FormDescription >
353
- Name that will be displayed in the emails sent to users.{ " " }
354
- < br className = "max-sm:hidden" /> Defaults to your API Key's
355
- name.
356
- </ FormDescription >
357
370
< FormMessage />
358
371
</ FormItem >
359
372
) }
360
373
/>
361
374
362
- { /* Application Image */ }
375
+ { /* Application Name */ }
363
376
< FormField
364
377
control = { form . control }
365
- name = "branding.applicationImageUrl "
378
+ name = "branding.applicationName "
366
379
render = { ( { field } ) => (
367
380
< FormItem >
368
- < FormLabel > Application Image URL</ FormLabel >
381
+ < FormLabel > Application Name</ FormLabel >
382
+ < FormDescription className = "!mb-2" >
383
+ Name that will be displayed in the emails sent to users.{ " " }
384
+ < br className = "max-sm:hidden" /> Defaults to your API Key's
385
+ name.
386
+ </ FormDescription >
369
387
< FormControl >
370
388
< Input { ...field } />
371
389
</ FormControl >
372
- < FormDescription >
373
- Logo that will display in the emails sent to users.{ " " }
374
- < br className = "max-sm:hidden" /> The image must be squared with
375
- recommended size of 72x72 px.
376
- </ FormDescription >
377
390
< FormMessage />
378
391
</ FormItem >
379
392
) }
@@ -383,6 +396,54 @@ function BrandingFieldset(props: {
383
396
) ;
384
397
}
385
398
399
+ function AppImageFormControl ( props : {
400
+ uri : string | undefined ;
401
+ setUri : ( uri : string ) => void ;
402
+ } ) {
403
+ const client = useThirdwebClient ( ) ;
404
+ const [ image , setImage ] = useState < File | undefined > ( ) ;
405
+ const resolveUrl = resolveSchemeWithErrorHandler ( {
406
+ client : client ,
407
+ uri : props . uri || undefined ,
408
+ } ) ;
409
+
410
+ const uploadImage = useMutation ( {
411
+ mutationFn : async ( file : File ) => {
412
+ const uri = await upload ( {
413
+ client : client ,
414
+ files : [ file ] ,
415
+ } ) ;
416
+
417
+ return uri ;
418
+ } ,
419
+ } ) ;
420
+
421
+ return (
422
+ < div className = "flex" >
423
+ < div className = "relative" >
424
+ < FileInput
425
+ accept = { { "image/*" : [ ] } }
426
+ value = { image }
427
+ setValue = { async ( v ) => {
428
+ setImage ( v ) ;
429
+ const uri = await uploadImage . mutateAsync ( v ) ;
430
+ props . setUri ( uri ) ;
431
+ } }
432
+ className = "w-24 rounded-full bg-background lg:w-28"
433
+ disableHelperText
434
+ fileUrl = { resolveUrl }
435
+ />
436
+
437
+ { uploadImage . isPending && (
438
+ < div className = "absolute inset-0 flex items-center justify-center rounded-full border bg-background/50" >
439
+ < Spinner className = "size-7" />
440
+ </ div >
441
+ ) }
442
+ </ div >
443
+ </ div >
444
+ ) ;
445
+ }
446
+
386
447
function SMSCountryFields ( props : {
387
448
form : UseFormReturn < ApiKeyEmbeddedWalletsValidationSchema > ;
388
449
canEditAdvancedFeatures : boolean ;
0 commit comments