Skip to content

Commit 8dc27ff

Browse files
committed
[TOOL-4228] Dashboard: Add file upload for in-app wallet branding form tool-4228 (#6811)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the `BrandingFieldset` component by introducing an `AppImageFormControl` for managing application images, while also refining the handling of branding properties in the form. ### Detailed summary - Added `Team` type import. - Introduced `useThirdwebClient` and `resolveSchemeWithErrorHandler`. - Modified `BrandingFieldset` to swap `applicationName` and `applicationImageUrl` fields. - Created `AppImageFormControl` for image upload handling. - Integrated image upload logic with error handling using `toast`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent f85163e commit 8dc27ff

File tree

1 file changed

+88
-20
lines changed
  • apps/dashboard/src/components/embedded-wallets/Configure

1 file changed

+88
-20
lines changed

apps/dashboard/src/components/embedded-wallets/Configure/index.tsx

+88-20
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import type { Project } from "@/api/projects";
44
import type { SMSCountryTiers } from "@/api/sms";
5+
import type { Team } from "@/api/team";
56
import { DynamicHeight } from "@/components/ui/DynamicHeight";
67
import { Spinner } from "@/components/ui/Spinner/Spinner";
78
import { UnderlineLink } from "@/components/ui/UnderlineLink";
@@ -20,6 +21,8 @@ import { Input } from "@/components/ui/input";
2021
import { Label } from "@/components/ui/label";
2122
import { Textarea } from "@/components/ui/textarea";
2223
import { TrackedLinkTW } from "@/components/ui/tracked-link";
24+
import { useThirdwebClient } from "@/constants/thirdweb.client";
25+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
2326
import { cn } from "@/lib/utils";
2427
import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi";
2528
import { zodResolver } from "@hookform/resolvers/zod";
@@ -33,10 +36,12 @@ import {
3336
import { useTrack } from "hooks/analytics/useTrack";
3437
import { CircleAlertIcon, PlusIcon, Trash2Icon } from "lucide-react";
3538
import type React from "react";
39+
import { useState } from "react";
3640
import { type UseFormReturn, useFieldArray, useForm } from "react-hook-form";
3741
import { toast } from "sonner";
42+
import { upload } from "thirdweb/storage";
3843
import { toArrFromList } from "utils/string";
39-
import type { Team } from "../../../@/api/team";
44+
import { FileInput } from "../../shared/FileInput";
4045
import CountrySelector from "./sms-country-select/country-selector";
4146

4247
type InAppWalletSettingsPageProps = {
@@ -339,41 +344,49 @@ function BrandingFieldset(props: {
339344
className="grid grid-cols-1 gap-6 lg:grid-cols-2"
340345
show={canEditAdvancedFeatures && !!form.watch("branding")}
341346
>
342-
{/* Application Name */}
347+
{/* Application Image */}
343348
<FormField
344349
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>
349359
<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+
/>
351369
</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>
357370
<FormMessage />
358371
</FormItem>
359372
)}
360373
/>
361374

362-
{/* Application Image */}
375+
{/* Application Name */}
363376
<FormField
364377
control={form.control}
365-
name="branding.applicationImageUrl"
378+
name="branding.applicationName"
366379
render={({ field }) => (
367380
<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>
369387
<FormControl>
370388
<Input {...field} />
371389
</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>
377390
<FormMessage />
378391
</FormItem>
379392
)}
@@ -383,6 +396,61 @@ function BrandingFieldset(props: {
383396
);
384397
}
385398

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+
try {
429+
setImage(v);
430+
const uri = await uploadImage.mutateAsync(v);
431+
props.setUri(uri);
432+
} catch (error) {
433+
setImage(undefined);
434+
toast.error("Failed to upload image", {
435+
description: error instanceof Error ? error.message : undefined,
436+
});
437+
}
438+
}}
439+
className="w-24 rounded-full bg-background lg:w-28"
440+
disableHelperText
441+
fileUrl={resolveUrl}
442+
/>
443+
444+
{uploadImage.isPending && (
445+
<div className="absolute inset-0 flex items-center justify-center rounded-full border bg-background/50">
446+
<Spinner className="size-7" />
447+
</div>
448+
)}
449+
</div>
450+
</div>
451+
);
452+
}
453+
386454
function SMSCountryFields(props: {
387455
form: UseFormReturn<ApiKeyEmbeddedWalletsValidationSchema>;
388456
canEditAdvancedFeatures: boolean;

0 commit comments

Comments
 (0)