Skip to content

Commit 29f343e

Browse files
committed
handled all campaign feedback
1 parent 1fb210f commit 29f343e

File tree

10 files changed

+118
-70
lines changed

10 files changed

+118
-70
lines changed

src/common/contracts/core/campaigns/client.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,23 @@ export const update_campaign = ({
6464
}: {
6565
args: CampaignFormFields & { campaign_id: CampaignId };
6666
}) => {
67-
return contractApi.call("update_campaign", {
67+
return contractApi.call<{}, Campaign>("update_campaign", {
6868
args,
6969
deposit: floatToYoctoNear(0.021),
7070
gas: "300000000000000",
7171
});
7272
};
7373

7474
export const delete_campaign = ({ args }: { args: { campaign_id: CampaignId } }) => {
75-
return contractApi.call("delete_campaign", {
75+
return contractApi.call<{}, Campaign>("delete_campaign", {
7676
args,
7777
deposit: floatToYoctoNear(0.021),
7878
gas: "300000000000000",
7979
});
8080
};
8181

8282
export const donate = (args: DirectCampaignDonationArgs, depositAmountYocto: string) =>
83-
contractApi.call("donate", {
83+
contractApi.call<{}, Campaign>("donate", {
8484
args,
8585
deposit: depositAmountYocto,
8686
gas: "300000000000000",

src/common/ui/layout/components/molecules/social-share.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ import { Popover, PopoverContent, PopoverTrigger } from "../atoms/popover";
1717
export const SocialsShare = ({
1818
shareContent,
1919
variant = "icon",
20+
shareText,
2021
}: {
2122
shareContent?: string;
2223
variant: "button" | "icon";
24+
shareText: string;
2325
}) => {
2426
const [copied, setCopied] = useState(false);
2527

@@ -58,7 +60,7 @@ export const SocialsShare = ({
5860
className="flex h-[48px] w-[48px] items-center justify-center rounded border border-[#DBDBDB] bg-[#F7F7F7]"
5961
onClick={() =>
6062
window.open(
61-
`https://twitter.com/intent/tweet?url=${share}&text=Check this out on POTLOCK @potlock_`,
63+
`https://twitter.com/intent/tweet?url=${share}&text=${shareText}`,
6264
"_blank",
6365
)
6466
}

src/entities/campaign/components/CampaignBanner.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { LazyLoadImage } from "react-lazy-load-image-component";
22

3+
import { PLATFORM_NAME } from "@/common/_config";
4+
import { PLATFORM_TWITTER_ACCOUNT_ID } from "@/common/constants";
35
import { campaignsContractHooks } from "@/common/contracts/core/campaigns";
46
import { yoctoNearToFloat } from "@/common/lib";
57
import getTimePassed from "@/common/lib/getTimePassed";
@@ -113,7 +115,11 @@ export const CampaignBanner: React.FC<CampaignBannerProps> = ({ campaignId }) =>
113115
}
114116
{...{ campaignId }}
115117
/>
116-
<SocialsShare variant="button" />
118+
<SocialsShare
119+
shareText={`Support ${campaign?.name} Campaign on ${PLATFORM_NAME} by donating or
120+
sharing, every contribution Counts! ${PLATFORM_TWITTER_ACCOUNT_ID}`}
121+
variant="button"
122+
/>
117123
</div>
118124
</div>
119125
</div>

src/entities/campaign/components/CampaignForm.tsx

+27-35
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const CampaignForm = ({
2525

2626
const isUpdate = campaignId !== undefined;
2727

28-
const { form, onChange, onSubmit, watch, isValid } = useCampaignForm({
28+
const { form, onChange, onSubmit, watch, isDisabled } = useCampaignForm({
2929
campaignId,
3030
});
3131

@@ -46,7 +46,7 @@ export const CampaignForm = ({
4646
form.setValue("max_amount", yoctoNearToFloat(existingData.max_amount));
4747
}
4848

49-
form.setValue("start_ms", existingData?.start_ms);
49+
// form.setValue("start_ms", existingData?.start_ms);
5050

5151
if (existingData?.end_ms) {
5252
form.setValue("end_ms", existingData?.end_ms);
@@ -167,7 +167,6 @@ export const CampaignForm = ({
167167
className="appearance-none md:w-[42%]"
168168
label="Target Amount"
169169
required
170-
onChange={(e) => field.onChange(Number(e.target.value))}
171170
/>
172171
)}
173172
/>
@@ -176,49 +175,42 @@ export const CampaignForm = ({
176175
control={form.control}
177176
name="min_amount"
178177
render={({ field }) => (
179-
<NearInputField
180-
{...field}
181-
className="lg:w-90"
182-
label="Minimum Target Amount"
183-
onChange={(e) => field.onChange(Number(e.target.value))}
184-
/>
178+
<NearInputField {...field} className="lg:w-90" label="Minimum Target Amount" />
185179
)}
186180
/>
187181
<FormField
188182
control={form.control}
189183
name="max_amount"
190184
render={({ field }) => (
191-
<NearInputField
192-
{...field}
193-
className="lg:w-90"
194-
label="Maximum Target Amount"
195-
onChange={(e) => field.onChange(Number(e.target.value))}
196-
/>
185+
<NearInputField {...field} className="lg:w-90" label="Maximum Target Amount" />
197186
)}
198187
/>
199188
</div>
200189
<div className="mt-8 flex w-full min-w-full flex-col justify-between md:flex-row">
201-
<FormField
202-
control={form.control}
203-
name="start_ms"
204-
render={({ field: { value, ...field } }) => (
205-
<TextField
206-
{...field}
207-
required={!!watch("min_amount")}
208-
label="Start Date"
209-
value={
210-
typeof value === "number"
211-
? Temporal.Instant.fromEpochMilliseconds(value)
212-
.toZonedDateTimeISO(Temporal.Now.timeZoneId())
213-
.toPlainDateTime()
214-
.toString({ smallestUnit: "minute" })
215-
: undefined
216-
}
217-
classNames={{ root: "lg:w-90" }}
218-
type="datetime-local"
190+
{existingData?.start_ms &&
191+
existingData?.start_ms > Temporal.Now.instant().epochMilliseconds && (
192+
<FormField
193+
control={form.control}
194+
name="start_ms"
195+
render={({ field: { value, ...field } }) => (
196+
<TextField
197+
{...field}
198+
required={!campaignId}
199+
label="Start Date"
200+
value={
201+
typeof value === "number"
202+
? Temporal.Instant.fromEpochMilliseconds(value)
203+
.toZonedDateTimeISO(Temporal.Now.timeZoneId())
204+
.toPlainDateTime()
205+
.toString({ smallestUnit: "minute" })
206+
: undefined
207+
}
208+
classNames={{ root: "lg:w-90" }}
209+
type="datetime-local"
210+
/>
211+
)}
219212
/>
220213
)}
221-
/>
222214
<FormField
223215
control={form.control}
224216
name="end_ms"
@@ -242,7 +234,7 @@ export const CampaignForm = ({
242234
/>
243235
</div>
244236
<div className="my-10 flex flex-row-reverse justify-between">
245-
<Button variant="standard-filled" disabled={!isValid} type="submit">
237+
<Button variant="standard-filled" disabled={isDisabled} type="submit">
246238
{isUpdate ? "Update" : "Create"} Campaign
247239
</Button>
248240
</div>

src/entities/campaign/components/CampaignProgressBar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const CampaignProgressBar: React.FC<CampaignProgressBarProps> = ({
3434
const isTimeUp = timeLeft?.includes("-");
3535

3636
const statusText = useMemo(() => {
37-
if (targetMet || isTimeUp) {
37+
if ((targetMet && endDate) || isTimeUp) {
3838
return "ENDED";
3939
} else if (isStarted) {
4040
return "NOT STARTED";

src/entities/campaign/components/CampaignSettings.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export const CampaignSettings: React.FC<CampaignSettingsProps> = ({ campaignId }
140140
role="button"
141141
className="text-red-500"
142142
>
143-
Edit Campaign
143+
{openEditCampaign ? "Show Campaign Details" : "Edit Campaign"}
144144
</p>
145145
</div>
146146
)}

src/entities/campaign/hooks/forms.ts

+26-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { useCallback, useEffect } from "react";
1+
import { useCallback, useEffect, useMemo } from "react";
22

33
import { zodResolver } from "@hookform/resolvers/zod";
44
import { useRouter } from "next/router";
55
import { SubmitHandler, useForm, useWatch } from "react-hook-form";
6-
import { Temporal } from "temporal-polyfill";
76
import { infer as FromSchema } from "zod";
87

98
import { campaignsContractClient } from "@/common/contracts/core/campaigns";
@@ -13,29 +12,41 @@ import { toast } from "@/common/ui/layout/hooks";
1312
import { useWalletUserSession } from "@/common/wallet";
1413
import { dispatch } from "@/store";
1514

16-
import { campaignFormSchema } from "../models/schema";
15+
import { createCampaignSchema, updateCampaignSchema } from "../models/schema";
1716
import { CampaignEnumType } from "../types";
1817

1918
export const useCampaignForm = ({ campaignId }: { campaignId?: CampaignId }) => {
2019
const viewer = useWalletUserSession();
2120
const router = useRouter();
2221

23-
type Values = FromSchema<typeof campaignFormSchema>;
22+
const schema = campaignId ? updateCampaignSchema : createCampaignSchema;
23+
24+
type Values = FromSchema<typeof schema>;
2425

2526
const self = useForm<Values>({
26-
resolver: zodResolver(campaignFormSchema),
27+
resolver: zodResolver(schema),
2728
mode: "onChange",
2829
resetOptions: { keepDirtyValues: false },
2930
});
3031

3132
const values = useWatch(self);
3233

34+
const isDisabled = useMemo(
35+
() => !self.formState.isDirty || !self.formState.isValid || self.formState.isSubmitting,
36+
[self.formState.isDirty, self.formState.isSubmitting, self.formState.isValid],
37+
);
38+
3339
useEffect(() => {
3440
const { min_amount, max_amount, target_amount } = values;
3541
const errors: Record<string, { message: string }> = {};
3642

43+
// Convert string values to numbers for comparison
44+
const minAmount = min_amount ? Number(min_amount) : null;
45+
const maxAmount = max_amount ? Number(max_amount) : null;
46+
const targetAmount = target_amount ? Number(target_amount) : null;
47+
3748
// Validate min_amount vs max_amount
38-
if (min_amount && max_amount && min_amount > max_amount) {
49+
if (minAmount && maxAmount && minAmount > maxAmount) {
3950
errors.min_amount = {
4051
message: "Minimum amount cannot be greater than maximum amount",
4152
};
@@ -46,7 +57,7 @@ export const useCampaignForm = ({ campaignId }: { campaignId?: CampaignId }) =>
4657
}
4758

4859
// Validate target_amount vs max_amount
49-
if (target_amount && max_amount && target_amount > max_amount) {
60+
if (targetAmount && maxAmount && targetAmount > maxAmount) {
5061
errors.target_amount = {
5162
message: "Target amount cannot be greater than maximum amount",
5263
};
@@ -57,7 +68,7 @@ export const useCampaignForm = ({ campaignId }: { campaignId?: CampaignId }) =>
5768
}
5869

5970
// Validate min_amount vs target_amount
60-
if (min_amount && target_amount && min_amount > target_amount) {
71+
if (minAmount && targetAmount && minAmount > targetAmount) {
6172
errors.min_amount = {
6273
message: "Minimum amount cannot be greater than target amount",
6374
};
@@ -169,9 +180,11 @@ export const useCampaignForm = ({ campaignId }: { campaignId?: CampaignId }) =>
169180
.update_campaign({
170181
args: { ...args, campaign_id: campaignId },
171182
})
172-
.then(() => {
183+
.then((updateValues) => {
184+
console.log(updateValues);
185+
173186
toast({
174-
title: `You’ve successfully updated this Campaign`,
187+
title: `You’ve successfully updated this ${updateValues.name} Campaign`,
175188
});
176189
})
177190
.catch((error) => {
@@ -220,8 +233,8 @@ export const useCampaignForm = ({ campaignId }: { campaignId?: CampaignId }) =>
220233
);
221234

222235
const onChange = async (field: keyof Values, value: string) => {
223-
self.setValue(field, value); // Update field value
224-
await self.trigger(); // Trigger validation
236+
self.setValue(field, value);
237+
await self.trigger();
225238
};
226239

227240
return {
@@ -236,7 +249,7 @@ export const useCampaignForm = ({ campaignId }: { campaignId?: CampaignId }) =>
236249
values,
237250
watch: self.watch,
238251
onChange,
239-
isValid: Object.keys(self.formState.errors).length === 0,
252+
isDisabled,
240253
handleDeleteCampaign,
241254
handleProcessEscrowedDonations,
242255
handleDonationsRefund,

src/entities/campaign/models/schema.ts

+42-13
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,28 @@ import { z } from "zod";
22

33
import { NETWORK } from "@/common/_config";
44
import { near } from "@/common/blockchains/near-protocol/client";
5-
import { futureTimestamp } from "@/common/lib";
5+
import { futureTimestamp, safePositiveNumber } from "@/common/lib";
66

7-
export const campaignFormSchema = z
7+
// Base schema with common fields
8+
const baseSchema = {
9+
name: z
10+
.string()
11+
.min(3, "Name must be at least 3 characters")
12+
.max(100, "Name must be less than 100 characters"),
13+
description: z.string().max(250, "Description must be less than 100 characters"),
14+
target_amount: safePositiveNumber.describe("Target Amount of the campaign"),
15+
min_amount: safePositiveNumber.optional().describe("Minimum Amount of the Campaign"),
16+
max_amount: safePositiveNumber.optional().describe("Maximum Amount of the Campaign"),
17+
cover_image_url: z.string().optional(),
18+
end_ms: futureTimestamp.describe("Campaign End Date"),
19+
owner: z.string()?.optional(),
20+
};
21+
22+
// Schema for creating new campaigns
23+
export const createCampaignSchema = z
824
.object({
9-
name: z
10-
.string()
11-
.min(3, "Name must be at least 3 characters")
12-
.max(100, "Name must be less than 100 characters"),
13-
description: z.string().max(250, "Description must be less than 100 characters"),
14-
target_amount: z.number().min(0.1, "Target amount must be at least 0.1"),
15-
min_amount: z.number().optional(),
16-
max_amount: z.number().optional(),
17-
cover_image_url: z.string().optional(),
25+
...baseSchema,
1826
start_ms: futureTimestamp.describe("Campaign Start Date"),
19-
end_ms: futureTimestamp.describe("Campaign End Date"),
20-
owner: z.string()?.optional(),
2127
recipient: z.string().refine(near.isAccountValid, {
2228
message: `Account does not exist on ${NETWORK}`,
2329
}),
@@ -37,3 +43,26 @@ export const campaignFormSchema = z
3743
});
3844
}
3945
});
46+
47+
// Schema for updating existing campaigns
48+
export const updateCampaignSchema = z
49+
.object({
50+
...baseSchema,
51+
start_ms: futureTimestamp.optional().describe("Campaign Start Date"),
52+
recipient: z.string().optional(),
53+
})
54+
.superRefine((data, ctx) => {
55+
if (data.end_ms && data.start_ms && data.start_ms >= data.end_ms) {
56+
ctx.addIssue({
57+
path: ["start_ms"],
58+
message: "Start time must be earlier than end time",
59+
code: "custom",
60+
});
61+
62+
ctx.addIssue({
63+
path: ["end_ms"],
64+
message: "End time must be later than start time",
65+
code: "custom",
66+
});
67+
}
68+
});

src/entities/list/components/ListDetails.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import { FaHeart, FaRegHeart } from "react-icons/fa";
88
import { LazyLoadImage } from "react-lazy-load-image-component";
99
import { prop } from "remeda";
1010

11+
import { PLATFORM_NAME } from "@/common/_config";
1112
import { List } from "@/common/api/indexer";
13+
import { PLATFORM_TWITTER_ACCOUNT_ID } from "@/common/constants";
1214
import { listsContractClient } from "@/common/contracts/core/lists";
1315
import { truncate } from "@/common/lib";
1416
import {
@@ -307,7 +309,11 @@ export const ListDetails = ({ admins, listId, listDetails, savedUsers }: ListDet
307309
)}
308310
</button>
309311
<div className="font-semibold">{listDetails && listDetails?.upvotes?.length}</div>
310-
<SocialsShare variant="icon" />
312+
<SocialsShare
313+
shareText={`support this List on ${PLATFORM_NAME} by donating to Projects or
314+
sharing—every contribution Counts! ${PLATFORM_TWITTER_ACCOUNT_ID}`}
315+
variant="icon"
316+
/>
311317
</div>
312318
</div>
313319
</div>

0 commit comments

Comments
 (0)