Skip to content

Commit 0c6d75d

Browse files
committed
Dashboard: Migrate Claim condition components from chakra to tailwind, UI improvements (#7731)
<!-- ## 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 user interface and functionality of the claim conditions forms in the dashboard application. It includes updates to component structures, styling improvements, and the introduction of new features for better user experience. ### Detailed summary - Removed unused CSV files from the assets. - Updated `variant` prop in `tx-button` to include "outline". - Adjusted styles in `drop-zone` for improved layout. - Enhanced text styles in `price-preview` and `claim-conditions` components. - Replaced `CustomFormControl` with `FormFieldSetup` for better form handling. - Added new components for displaying and managing claim conditions. - Improved error handling and user feedback in various input components. - Refactored `ClaimPriceInput` and other input components to streamline functionality. - Introduced a new `SnapshotDataTable` for better display of CSV data. - Enhanced snapshot upload functionality with clearer requirements and examples. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for an "outline" button style variant in transaction buttons. * **Refactor** * Replaced Chakra UI components with a custom UI library and Tailwind CSS across claim conditions forms and related components. * Simplified and reorganized the layout of claim conditions phases, downloadable code blocks, and snapshot upload views for improved clarity and usability. * Updated CSV upload and data table experience with enhanced error display and a new code example download feature. * **Style** * Improved spacing, typography, and overall visual consistency throughout claim conditions and related dashboard components. * **Chores** * Removed unused or redundant components and files related to previous form controls and CSV tables. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 11d9f3e commit 0c6d75d

23 files changed

+648
-910
lines changed

apps/dashboard/public/assets/examples/snapshot-with-maxclaimable.csv

Lines changed: 0 additions & 3 deletions
This file was deleted.

apps/dashboard/public/assets/examples/snapshot-with-overrides.csv

Lines changed: 0 additions & 3 deletions
This file was deleted.

apps/dashboard/public/assets/examples/snapshot.csv

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client";
2-
import { ArrowDownToLineIcon } from "lucide-react";
2+
import { ArrowDownToLineIcon, FileTextIcon } from "lucide-react";
33
import { Button } from "@/components/ui/button";
44
import { CodeClient } from "@/components/ui/code/code.client";
55
import { handleDownload } from "../download-file-button";
@@ -10,27 +10,36 @@ export function DownloadableCode(props: {
1010
fileNameWithExtension: string;
1111
}) {
1212
return (
13-
<div className="!my-3 relative">
14-
<CodeClient
15-
code={props.code}
16-
lang={props.lang}
17-
scrollableClassName="max-h-[300px] bg-background"
18-
/>
19-
20-
<Button
21-
className="absolute top-3.5 right-14 mt-[1px] h-auto bg-background p-2"
22-
onClick={() => {
23-
handleDownload({
24-
fileContent: props.code,
25-
fileFormat: props.lang === "csv" ? "text/csv" : "application/json",
26-
fileNameWithExtension: props.fileNameWithExtension,
27-
});
28-
}}
29-
size="sm"
30-
variant="outline"
31-
>
32-
<ArrowDownToLineIcon className="size-3" />
33-
</Button>
13+
<div className="!my-5 bg-card">
14+
<p className="text-sm text-muted-foreground border border-b-0 px-4 py-3 pr-3.5 rounded-lg rounded-b-none flex items-center justify-between">
15+
<span className="flex items-center gap-1.5">
16+
<FileTextIcon className="size-3.5" />
17+
{props.fileNameWithExtension}
18+
</span>
19+
<Button
20+
className="h-auto bg-background p-2"
21+
onClick={() => {
22+
handleDownload({
23+
fileContent: props.code,
24+
fileFormat:
25+
props.lang === "csv" ? "text/csv" : "application/json",
26+
fileNameWithExtension: props.fileNameWithExtension,
27+
});
28+
}}
29+
size="sm"
30+
variant="outline"
31+
>
32+
<ArrowDownToLineIcon className="size-3" />
33+
</Button>
34+
</p>
35+
<div className="relative">
36+
<CodeClient
37+
code={props.code}
38+
lang={props.lang}
39+
scrollableClassName="max-h-[300px] bg-background bg-card"
40+
className="rounded-t-none"
41+
/>
42+
</div>
3443
</div>
3544
);
3645
}

apps/dashboard/src/@/components/blocks/drop-zone/drop-zone.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function DropZone(props: {
2626
return (
2727
<div
2828
className={cn(
29-
"flex cursor-pointer items-center justify-center rounded-md border border-dashed bg-card py-10 hover:border-active-border",
29+
"flex cursor-pointer items-center justify-center rounded-md border border-dashed bg-card py-10 hover:border-active-border px-4",
3030
props.isError &&
3131
"border-red-500 bg-red-200/30 text-red-500 hover:border-red-600 dark:border-red-900 dark:bg-red-900/30 dark:hover:border-red-800",
3232
props.className,

apps/dashboard/src/@/components/tx-button/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type TransactionButtonProps = Omit<ButtonProps, "variant"> & {
2828
transactionCount: number | undefined; // support for unknown number of tx count
2929
isPending: boolean;
3030
txChainID: number;
31-
variant?: "destructive" | "primary" | "default";
31+
variant?: "destructive" | "primary" | "default" | "outline";
3232
isLoggedIn: boolean;
3333
checkBalance?: boolean;
3434
client: ThirdwebClient;
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,40 @@
1-
import { Box, Flex } from "@chakra-ui/react";
21
import { NATIVE_TOKEN_ADDRESS } from "thirdweb";
32
import { CurrencySelector } from "@/components/blocks/CurrencySelector";
3+
import { FormFieldSetup } from "@/components/blocks/FormFieldSetup";
4+
import { cn } from "@/lib/utils";
45
import { PriceInput } from "../../price-input";
56
import { useClaimConditionsFormContext } from "..";
6-
import { CustomFormControl } from "../common";
77

88
/**
99
* Allows the user to select how much they want to charge to claim each NFT
1010
*/
1111
export const ClaimPriceInput = (props: { contractChainId: number }) => {
12-
const {
13-
formDisabled,
14-
isErc20,
15-
form,
16-
phaseIndex,
17-
field,
18-
isColumn,
19-
claimConditionType,
20-
} = useClaimConditionsFormContext();
12+
const { formDisabled, isErc20, form, phaseIndex, field, claimConditionType } =
13+
useClaimConditionsFormContext();
2114

2215
if (claimConditionType === "creator") {
2316
return null;
2417
}
2518

2619
return (
27-
<CustomFormControl
28-
disabled={formDisabled}
29-
error={
20+
<FormFieldSetup
21+
isRequired={false}
22+
errorMessage={
3023
form.getFieldState(`phases.${phaseIndex}.price`, form.formState).error
24+
?.message
3125
}
3226
label={`How much do you want to charge to claim each ${
3327
isErc20 ? "token" : "NFT"
3428
}?`}
3529
>
36-
<Flex flexDir={{ base: "column", md: "row" }} gap={2}>
37-
<Box minW="70px" w={{ base: "100%", md: "50%" }}>
38-
<PriceInput
39-
onChange={(val) => form.setValue(`phases.${phaseIndex}.price`, val)}
40-
value={field.price?.toString() || ""}
41-
w="full"
42-
/>
43-
</Box>
44-
<Box w={{ base: "100%", md: isColumn ? "50%" : "100%" }}>
30+
<div className={cn("flex flex-col md:flex-row gap-3")}>
31+
<PriceInput
32+
onChange={(val) => form.setValue(`phases.${phaseIndex}.price`, val)}
33+
value={field.price?.toString() || ""}
34+
disabled={formDisabled}
35+
className="max-w-48"
36+
/>
37+
<div className="grow max-w-md">
4538
<CurrencySelector
4639
contractChainId={props.contractChainId}
4740
isDisabled={formDisabled}
@@ -53,8 +46,8 @@ export const ClaimPriceInput = (props: { contractChainId: number }) => {
5346
}
5447
value={field?.currencyAddress || NATIVE_TOKEN_ADDRESS}
5548
/>
56-
</Box>
57-
</Flex>
58-
</CustomFormControl>
49+
</div>
50+
</div>
51+
</FormFieldSetup>
5952
);
6053
};

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/Inputs/ClaimerSelection.tsx

Lines changed: 50 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
import { Box, Flex, Select } from "@chakra-ui/react";
21
import { UploadIcon } from "lucide-react";
2+
import { FormFieldSetup } from "@/components/blocks/FormFieldSetup";
33
import { Button } from "@/components/ui/button";
4-
import { useClaimConditionsFormContext } from "..";
5-
import { CustomFormControl } from "../common";
4+
import {
5+
Select,
6+
SelectContent,
7+
SelectItem,
8+
SelectTrigger,
9+
SelectValue,
10+
} from "@/components/ui/select";
11+
import { cn } from "@/lib/utils";
12+
import { useClaimConditionsFormContext } from "../index";
613

714
/**
815
* Allows the user to
@@ -19,12 +26,11 @@ export const ClaimerSelection = () => {
1926
isErc20,
2027
setOpenSnapshotIndex: setOpenIndex,
2128
isAdmin,
22-
isColumn,
2329
claimConditionType,
2430
} = useClaimConditionsFormContext();
2531

26-
const handleClaimerChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
27-
const val = e.currentTarget.value as "any" | "specific" | "overrides";
32+
const handleClaimerChange = (value: string) => {
33+
const val = value as "any" | "specific" | "overrides";
2834

2935
if (val === "any") {
3036
form.setValue(`phases.${phaseIndex}.snapshot`, undefined);
@@ -80,79 +86,69 @@ export const ClaimerSelection = () => {
8086
: `Who can claim ${isErc20 ? "tokens" : "NFTs"} during this phase?`;
8187

8288
return (
83-
<CustomFormControl
84-
disabled={formDisabled}
85-
error={
89+
<FormFieldSetup
90+
errorMessage={
8691
form.getFieldState(`phases.${phaseIndex}.snapshot`, form.formState)
87-
.error
92+
?.error?.message
8893
}
8994
helperText={helperText}
9095
label={label}
96+
isRequired={false}
9197
>
92-
<Flex direction={{ base: "column", md: "row" }} gap={4}>
98+
<div className="flex flex-col md:flex-row gap-4">
9399
{claimConditionType === "overrides" ||
94100
claimConditionType === "specific" ? null : (
95101
<Select
96-
isDisabled={formDisabled}
97-
onChange={handleClaimerChange}
102+
disabled={formDisabled}
103+
onValueChange={handleClaimerChange}
98104
value={dropType}
99-
w={{ base: "100%", md: "50%" }}
100105
>
101-
<option value="any">Any wallet</option>
102-
<option value="overrides">Any wallet (with overrides)</option>
103-
<option value="specific">Only specific wallets</option>
106+
<SelectTrigger className="w-full md:w-1/2">
107+
<SelectValue />
108+
</SelectTrigger>
109+
<SelectContent>
110+
<SelectItem value="any">Any wallet</SelectItem>
111+
<SelectItem value="overrides">
112+
Any wallet (with overrides)
113+
</SelectItem>
114+
<SelectItem value="specific">Only specific wallets</SelectItem>
115+
</SelectContent>
104116
</Select>
105117
)}
106118

107119
{/* Edit or See Snapshot */}
108120
{field.snapshot ? (
109-
<Flex
110-
direction={{
111-
base: "column",
112-
md: isColumn ? "column" : "row",
113-
}}
114-
gap={1.5}
115-
>
121+
<div className="flex items-center gap-3">
116122
{/* disable the "Edit" button when form is disabled, but not when it's a "See" button */}
117123
<Button
118124
className="gap-2 rounded-md"
119125
disabled={disabledSnapshotButton}
120126
onClick={() => setOpenIndex(phaseIndex)}
121-
variant="primary"
127+
size="sm"
122128
>
123129
{isAdmin ? "Edit" : "See"} Claimer Snapshot
124130
<UploadIcon className="size-4" />
125131
</Button>
126132

127-
<Flex
128-
_light={{
129-
color: field.snapshot?.length === 0 ? "red.500" : "green.500",
130-
}}
131-
align="center"
132-
color={field.snapshot?.length === 0 ? "red.400" : "green.400"}
133-
direction="row"
134-
gap={2}
135-
justify="center"
136-
ml={2}
137-
opacity={disabledSnapshotButton ? 0.5 : 1}
133+
<div
134+
className={cn(
135+
"flex gap-2 items-center",
136+
field.snapshot?.length === 0
137+
? "text-muted-foreground"
138+
: "text-green-600 dark:text-green-500",
139+
disabledSnapshotButton ? "opacity-50" : "",
140+
)}
138141
>
139-
<p>
140-
{" "}
141-
<strong>
142-
{field.snapshot?.length} address
143-
{field.snapshot?.length === 1 ? "" : "es"}
144-
</strong>{" "}
145-
in snapshot
146-
</p>
147-
</Flex>
148-
</Flex>
149-
) : (
150-
<Box
151-
display={{ base: "none", md: "block" }}
152-
w={{ base: "100%", md: "50%" }}
153-
/>
154-
)}
155-
</Flex>
156-
</CustomFormControl>
142+
<div className="size-2 bg-current rounded-full" />
143+
<span className="text-sm">
144+
{field.snapshot?.length}{" "}
145+
{field.snapshot?.length === 1 ? "address" : "addresses"} in
146+
snapshot
147+
</span>
148+
</div>
149+
</div>
150+
) : null}
151+
</div>
152+
</FormFieldSetup>
157153
);
158154
};

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/Inputs/CreatorInput.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useActiveAccount } from "thirdweb/react";
2+
import { FormFieldSetup } from "@/components/blocks/FormFieldSetup";
23
import { Input } from "@/components/ui/input";
3-
import { CustomFormControl } from "../common";
44
import { useClaimConditionsFormContext } from "../index";
55

66
/**
@@ -14,17 +14,17 @@ interface CreatorInputProps {
1414
export const CreatorInput: React.FC<CreatorInputProps> = ({
1515
creatorAddress,
1616
}) => {
17-
const { formDisabled, claimConditionType, isAdmin } =
18-
useClaimConditionsFormContext();
17+
const { claimConditionType, isAdmin } = useClaimConditionsFormContext();
1918
const walletAddress = useActiveAccount()?.address;
2019

2120
if (claimConditionType !== "creator") {
2221
return null;
2322
}
2423

2524
return (
26-
<CustomFormControl
27-
disabled={formDisabled}
25+
<FormFieldSetup
26+
isRequired={false}
27+
errorMessage={undefined}
2828
helperText={
2929
<>
3030
This wallet address will be able to indefinitely claim.{" "}
@@ -34,7 +34,12 @@ export const CreatorInput: React.FC<CreatorInputProps> = ({
3434
}
3535
label="Creator address"
3636
>
37-
<Input disabled readOnly value={creatorAddress || walletAddress} />
38-
</CustomFormControl>
37+
<Input
38+
disabled
39+
readOnly
40+
value={creatorAddress || walletAddress}
41+
className="disabled:opacity-100 max-w-sm"
42+
/>
43+
</FormFieldSetup>
3944
);
4045
};

0 commit comments

Comments
 (0)