Skip to content

Commit fe67520

Browse files
committed
[BLD-80] Dashboard: in-app wallet settings page UI improvements
1 parent 8665e8f commit fe67520

File tree

4 files changed

+261
-168
lines changed

4 files changed

+261
-168
lines changed

apps/dashboard/src/@/components/ui/checkbox.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
4-
import { CheckIcon } from "lucide-react";
4+
import { CheckIcon, MinusIcon } from "lucide-react";
55
import * as React from "react";
66

77
import { cn } from "@/lib/utils";
@@ -21,7 +21,11 @@ const Checkbox = React.forwardRef<
2121
<CheckboxPrimitive.Indicator
2222
className={cn("flex items-center justify-center text-current")}
2323
>
24-
<CheckIcon className="size-4" />
24+
{props.checked === "indeterminate" ? (
25+
<MinusIcon className="size-3" />
26+
) : (
27+
<CheckIcon className="size-4" />
28+
)}
2529
</CheckboxPrimitive.Indicator>
2630
</CheckboxPrimitive.Root>
2731
));

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/settings/components/index.tsx

Lines changed: 120 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -240,32 +240,39 @@ export const InAppWalletSettingsUI: React.FC<
240240

241241
return (
242242
<Form {...form}>
243-
<form
244-
autoComplete="off"
245-
className="flex flex-col gap-6"
246-
onSubmit={handleSubmit}
247-
>
243+
<form autoComplete="off" className="space-y-6" onSubmit={handleSubmit}>
248244
{/* Branding */}
249245
<BrandingFieldset
250246
client={props.client}
251247
form={form}
252248
requiredPlan={brandingRequiredPlan}
253249
teamPlan={props.teamPlan}
254250
teamSlug={props.teamSlug}
251+
isUpdating={props.isUpdating}
255252
/>
256253

257-
<NativeAppsFieldset form={form} />
254+
<NativeAppsFieldset form={form} isUpdating={props.isUpdating} />
258255

259256
{/* Authentication */}
260-
<Fieldset legend="Authentication">
257+
<Fieldset
258+
legend="Authentication"
259+
footer={
260+
<div className="flex justify-end p-4 md:px-6 border-t border-dashed">
261+
<Button className="gap-2" type="submit" size="sm">
262+
{props.isUpdating && <Spinner className="size-4" />}
263+
Save
264+
</Button>
265+
</div>
266+
}
267+
>
261268
<JSONWebTokenFields
262269
form={form}
263270
requiredPlan={authRequiredPlan}
264271
teamPlan={props.teamPlan}
265272
teamSlug={props.teamSlug}
266273
/>
267274

268-
<div className="h-5" />
275+
<div className="my-5 border-t border-dashed" />
269276

270277
<AuthEndpointFields
271278
form={form}
@@ -274,7 +281,7 @@ export const InAppWalletSettingsUI: React.FC<
274281
teamSlug={props.teamSlug}
275282
/>
276283

277-
<div className="h-5" />
284+
<div className="my-5 border-t border-dashed" />
278285

279286
<SMSCountryFields
280287
form={form}
@@ -284,13 +291,6 @@ export const InAppWalletSettingsUI: React.FC<
284291
teamSlug={props.teamSlug}
285292
/>
286293
</Fieldset>
287-
288-
<div className="flex justify-end">
289-
<Button className="gap-2" type="submit" variant="primary">
290-
{props.isUpdating && <Spinner className="size-4" />}
291-
Save changes
292-
</Button>
293-
</div>
294294
</form>
295295
</Form>
296296
);
@@ -302,14 +302,22 @@ function BrandingFieldset(props: {
302302
teamSlug: string;
303303
requiredPlan: Team["billingPlan"];
304304
client: ThirdwebClient;
305+
isUpdating: boolean;
305306
}) {
306307
return (
307-
<Fieldset legend="Branding">
308-
<SwitchContainer
309-
description="Pass a custom logo and app name to be used in the emails sent to users."
310-
switchId="branding-switch"
311-
title="Custom email logo and name"
312-
>
308+
<FieldsetWithDescription
309+
legend="Branding"
310+
description="Set a app name and logo to be used in the emails sent to users."
311+
footer={
312+
<div className="flex justify-end p-4 md:px-6 border-t border-dashed">
313+
<Button className="gap-2" type="submit" size="sm">
314+
{props.isUpdating && <Spinner className="size-4" />}
315+
Save
316+
</Button>
317+
</div>
318+
}
319+
>
320+
<div className="absolute top-8 right-6">
313321
<GatedSwitch
314322
currentPlan={props.teamPlan}
315323
requiredPlan={props.requiredPlan}
@@ -330,26 +338,39 @@ function BrandingFieldset(props: {
330338
teamSlug={props.teamSlug}
331339
trackingLabel="customEmailLogoAndName"
332340
/>
333-
</SwitchContainer>
341+
</div>
334342

335343
<GatedCollapsibleContainer
336-
className="grid grid-cols-1 gap-6 lg:grid-cols-2"
344+
className="grid grid-cols-1 gap-16 lg:grid-cols-[1fr_1.5fr]"
337345
currentPlan={props.teamPlan}
338346
isExpanded={!!props.form.watch("branding")}
339347
requiredPlan={props.requiredPlan}
340348
>
341-
{/* Application Image */}
349+
{/* Application Name */}
350+
<FormField
351+
control={props.form.control}
352+
name="branding.applicationName"
353+
render={({ field }) => (
354+
<FormItem>
355+
<FormLabel>Application Name</FormLabel>
356+
<FormDescription>
357+
Name that will be displayed in the emails sent to users.{" "}
358+
<br className="max-sm:hidden" /> Defaults to your API Key's
359+
name.
360+
</FormDescription>
361+
<FormControl>
362+
<Input {...field} />
363+
</FormControl>
364+
<FormMessage />
365+
</FormItem>
366+
)}
367+
/>
368+
342369
<FormField
343370
control={props.form.control}
344371
name="branding.applicationImageUrl"
345372
render={() => (
346-
<FormItem className="space-y-1">
347-
<FormLabel>Application Image URL</FormLabel>
348-
<FormDescription className="!mb-4">
349-
Logo that will display in the emails sent to users.{" "}
350-
<br className="max-sm:hidden" /> The image must be squared with
351-
recommended size of 72x72 px.
352-
</FormDescription>
373+
<FormItem className="flex items-center gap-4">
353374
<FormControl>
354375
<AppImageFormControl
355376
client={props.client}
@@ -362,32 +383,22 @@ function BrandingFieldset(props: {
362383
uri={props.form.watch("branding.applicationImageUrl")}
363384
/>
364385
</FormControl>
365-
<FormMessage />
366-
</FormItem>
367-
)}
368-
/>
369386

370-
{/* Application Name */}
371-
<FormField
372-
control={props.form.control}
373-
name="branding.applicationName"
374-
render={({ field }) => (
375-
<FormItem>
376-
<FormLabel>Application Name</FormLabel>
377-
<FormDescription className="!mb-2">
378-
Name that will be displayed in the emails sent to users.{" "}
379-
<br className="max-sm:hidden" /> Defaults to your API Key's
380-
name.
381-
</FormDescription>
382-
<FormControl>
383-
<Input {...field} />
384-
</FormControl>
385-
<FormMessage />
387+
<div className="space-y-1">
388+
<FormLabel>Application Image URL</FormLabel>
389+
<FormDescription className="!mb-4">
390+
Logo that will display in the emails sent to users.{" "}
391+
<br className="max-sm:hidden" /> The image must be squared
392+
with recommended size of 72x72 px.
393+
</FormDescription>
394+
395+
<FormMessage />
396+
</div>
386397
</FormItem>
387398
)}
388399
/>
389400
</GatedCollapsibleContainer>
390-
</Fieldset>
401+
</FieldsetWithDescription>
391402
);
392403
}
393404

@@ -743,10 +754,21 @@ function AuthEndpointFieldsContent(props: {
743754

744755
function NativeAppsFieldset(props: {
745756
form: UseFormReturn<ApiKeyEmbeddedWalletsValidationSchema>;
757+
isUpdating: boolean;
746758
}) {
747759
const { form } = props;
748760
return (
749-
<Fieldset legend="Native Apps">
761+
<Fieldset
762+
legend="Native Apps"
763+
footer={
764+
<div className="flex justify-end p-4 md:px-6 border-t border-dashed">
765+
<Button className="gap-2" type="submit" size="sm">
766+
{props.isUpdating && <Spinner className="size-4" />}
767+
Save
768+
</Button>
769+
</div>
770+
}
771+
>
750772
<FormField
751773
control={form.control}
752774
name="redirectUrls"
@@ -789,21 +811,54 @@ function GatedCollapsibleContainer(props: {
789811
return null;
790812
}
791813

792-
return <div className={cn("mt-6", props.className)}>{props.children}</div>;
814+
return <div className={cn("mt-5", props.className)}>{props.children}</div>;
793815
}
794816

795-
function Fieldset(props: { legend: string; children: React.ReactNode }) {
817+
function Fieldset(props: {
818+
legend: string;
819+
children: React.ReactNode;
820+
footer?: React.ReactNode;
821+
}) {
796822
return (
797-
<DynamicHeight>
798-
<fieldset className="rounded-lg border border-border bg-card p-4 md:p-6">
799-
{/* put inside div to remove default styles on legend */}
800-
<div className="mb-4 font-semibold text-xl tracking-tight">
801-
<legend> {props.legend}</legend>
802-
</div>
823+
<div className="rounded-lg border bg-card relative">
824+
<DynamicHeight>
825+
<fieldset>
826+
{/* put inside div to remove default styles on legend */}
827+
<div className="p-4 md:py-5 md:px-6 border-b border-dashed font-semibold text-xl tracking-tight">
828+
<legend> {props.legend}</legend>
829+
</div>
803830

804-
{props.children}
805-
</fieldset>
806-
</DynamicHeight>
831+
<div className="p-4 md:p-6">{props.children}</div>
832+
</fieldset>
833+
</DynamicHeight>
834+
{props.footer}
835+
</div>
836+
);
837+
}
838+
839+
function FieldsetWithDescription(props: {
840+
legend: string;
841+
children: React.ReactNode;
842+
footer?: React.ReactNode;
843+
description: React.ReactNode;
844+
}) {
845+
return (
846+
<div className="rounded-lg border bg-card relative">
847+
<DynamicHeight>
848+
<fieldset className="p-4 md:p-6">
849+
{/* put inside div to remove default styles on legend */}
850+
<div className="pr-20">
851+
<legend className="font-semibold text-xl tracking-tight">
852+
{props.legend}
853+
</legend>
854+
<p className="text-muted-foreground text-sm">{props.description}</p>
855+
</div>
856+
857+
{props.children}
858+
</fieldset>
859+
</DynamicHeight>
860+
{props.footer}
861+
</div>
807862
);
808863
}
809864

0 commit comments

Comments
 (0)