diff --git a/apps/dashboard/src/components/embedded-wallets/Configure/index.tsx b/apps/dashboard/src/components/embedded-wallets/Configure/index.tsx index cd35d7f432f..1355a8994af 100644 --- a/apps/dashboard/src/components/embedded-wallets/Configure/index.tsx +++ b/apps/dashboard/src/components/embedded-wallets/Configure/index.tsx @@ -2,6 +2,7 @@ import type { Project } from "@/api/projects"; import type { SMSCountryTiers } from "@/api/sms"; +import type { Team } from "@/api/team"; import { DynamicHeight } from "@/components/ui/DynamicHeight"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { UnderlineLink } from "@/components/ui/UnderlineLink"; @@ -20,6 +21,8 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { TrackedLinkTW } from "@/components/ui/tracked-link"; +import { useThirdwebClient } from "@/constants/thirdweb.client"; +import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { cn } from "@/lib/utils"; import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -33,10 +36,12 @@ import { import { useTrack } from "hooks/analytics/useTrack"; import { CircleAlertIcon, PlusIcon, Trash2Icon } from "lucide-react"; import type React from "react"; +import { useState } from "react"; import { type UseFormReturn, useFieldArray, useForm } from "react-hook-form"; import { toast } from "sonner"; +import { upload } from "thirdweb/storage"; import { toArrFromList } from "utils/string"; -import type { Team } from "../../../@/api/team"; +import { FileInput } from "../../shared/FileInput"; import CountrySelector from "./sms-country-select/country-selector"; type InAppWalletSettingsPageProps = { @@ -339,41 +344,49 @@ function BrandingFieldset(props: { className="grid grid-cols-1 gap-6 lg:grid-cols-2" show={canEditAdvancedFeatures && !!form.watch("branding")} > - {/* Application Name */} + {/* Application Image */} ( - - Application Name + name="branding.applicationImageUrl" + render={() => ( + + Application Image URL + + Logo that will display in the emails sent to users.{" "} +
The image must be squared with + recommended size of 72x72 px. +
- + { + form.setValue("branding.applicationImageUrl", uri, { + shouldDirty: true, + shouldTouch: true, + }); + }} + /> - - Name that will be displayed in the emails sent to users.{" "} -
Defaults to your API Key's - name. -
)} /> - {/* Application Image */} + {/* Application Name */} ( - Application Image URL + Application Name + + Name that will be displayed in the emails sent to users.{" "} +
Defaults to your API Key's + name. +
- - Logo that will display in the emails sent to users.{" "} -
The image must be squared with - recommended size of 72x72 px. -
)} @@ -383,6 +396,61 @@ function BrandingFieldset(props: { ); } +function AppImageFormControl(props: { + uri: string | undefined; + setUri: (uri: string) => void; +}) { + const client = useThirdwebClient(); + const [image, setImage] = useState(); + const resolveUrl = resolveSchemeWithErrorHandler({ + client: client, + uri: props.uri || undefined, + }); + + const uploadImage = useMutation({ + mutationFn: async (file: File) => { + const uri = await upload({ + client: client, + files: [file], + }); + + return uri; + }, + }); + + return ( +
+
+ { + try { + setImage(v); + const uri = await uploadImage.mutateAsync(v); + props.setUri(uri); + } catch (error) { + setImage(undefined); + toast.error("Failed to upload image", { + description: error instanceof Error ? error.message : undefined, + }); + } + }} + className="w-24 rounded-full bg-background lg:w-28" + disableHelperText + fileUrl={resolveUrl} + /> + + {uploadImage.isPending && ( +
+ +
+ )} +
+
+ ); +} + function SMSCountryFields(props: { form: UseFormReturn; canEditAdvancedFeatures: boolean;