Skip to content

Commit de43ff6

Browse files
authored
Merge pull request #464 from hypercerts-org/dev
Push to PRD
2 parents 1fb17f4 + 40bb9f3 commit de43ff6

File tree

9 files changed

+159
-46
lines changed

9 files changed

+159
-46
lines changed

app/hypercerts/new-transferable/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
import { getBlueprintById } from "@/blueprints/getBlueprints";
66
import { TransferRestrictions } from "@hypercerts-org/sdk";
77
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
8+
import { InfoSection } from "@/components/global/sections";
89

910
export default async function NewHypercertPage({
1011
searchParams,
@@ -18,7 +19,13 @@ export default async function NewHypercertPage({
1819
const fetchedBlueprint = await getBlueprintById(parsedId);
1920

2021
if (!fetchedBlueprint) {
21-
return <div>Blueprint not found</div>;
22+
return (
23+
<main className="flex flex-col p-8 md:px-16 pt-8 pb-24 space-y-4 flex-1 container max-w-screen-lg">
24+
<InfoSection>
25+
The blueprint you are trying to use does not exist.
26+
</InfoSection>
27+
</main>
28+
);
2229
}
2330

2431
formValues = fetchedBlueprint.form_values as HypercertFormValues;

app/hypercerts/new/page.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
HypercertMintingForm,
44
} from "@/components/hypercert/hypercert-minting-form";
55
import { getBlueprintById } from "@/blueprints/getBlueprints";
6+
import { InfoSection } from "@/components/global/sections";
67

78
export default async function NewHypercertPage({
89
searchParams,
@@ -18,10 +19,28 @@ export default async function NewHypercertPage({
1819
const fetchedBlueprint = await getBlueprintById(parsedId);
1920

2021
if (!fetchedBlueprint) {
21-
return <div>Blueprint not found</div>;
22+
return (
23+
<main className="flex flex-col p-8 md:px-16 pt-8 pb-24 space-y-4 flex-1 container max-w-screen-lg">
24+
<InfoSection>
25+
The blueprint you are trying to use does not exist.
26+
</InfoSection>
27+
</main>
28+
);
2229
}
2330

24-
formValues = fetchedBlueprint.form_values as HypercertFormValues;
31+
formValues = {
32+
...fetchedBlueprint.form_values,
33+
allowlistEntries: fetchedBlueprint.form_values.allowlistEntries?.map(
34+
(entry) => ({
35+
...entry,
36+
units: BigInt(entry.units),
37+
}),
38+
),
39+
projectDates: {
40+
from: new Date(fetchedBlueprint.form_values.projectDates.from),
41+
to: new Date(fetchedBlueprint.form_values.projectDates.to),
42+
},
43+
} as HypercertFormValues;
2544
blueprintChainId = fetchedBlueprint.admins[0].chain_id
2645
? parseInt(fetchedBlueprint.admins[0].chain_id)
2746
: undefined;

components/hypercert/hypercert-minting-form/index.tsx

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from "react";
44
import useIsWriteable from "@/hooks/useIsWriteable";
55
import { useMintHypercert } from "@/hypercerts/hooks/useMintHypercert";
66
import { useLocalStorage } from "react-use";
7-
import { type FieldErrors, useForm, useWatch } from "react-hook-form";
7+
import { type FieldErrors, useForm } from "react-hook-form";
88
import { zodResolver } from "@hookform/resolvers/zod";
99
import { toast } from "@/components/ui/use-toast";
1010
import {
@@ -178,17 +178,16 @@ export function HypercertMintingForm({
178178
}) {
179179
const [currentStep, setCurrentStep] = useState(1);
180180
const [language, setLanguage] = useState("en-US");
181-
const {
182-
writeable,
183-
errors: writeableErrors,
184-
resetErrors: resetWriteableErrors,
185-
} = useIsWriteable();
181+
const { errors: writeableErrors } = useIsWriteable();
186182

187183
const { mutateAsync: mintHypercert } = useMintHypercert();
188184
const { mutateAsync: createBlueprint } = useCreateBlueprint();
189-
const [value, setValue] = useLocalStorage<HypercertFormValues>(
190-
"user-hypercert-create-form-data",
191-
formDefaultValues,
185+
const localStorageKey = blueprintId
186+
? `user-hypercert-blueprint-${blueprintId}-form-data`
187+
: "user-hypercert-create-form-data";
188+
const [values, setValues] = useLocalStorage<HypercertFormValues>(
189+
localStorageKey,
190+
presetValues || formDefaultValues,
192191
{
193192
raw: false,
194193
serializer: JSON.stringify,
@@ -202,37 +201,24 @@ export function HypercertMintingForm({
202201
blueprint_minter_address: true,
203202
});
204203

205-
const defaultValues = presetValues
206-
? {
207-
...presetValues,
208-
allowlistEntries: presetValues.allowlistEntries?.map((entry) => ({
209-
...entry,
210-
units: BigInt(entry.units),
211-
})),
212-
projectDates: {
213-
to: new Date(presetValues.projectDates.to),
214-
from: new Date(presetValues.projectDates.from),
215-
},
216-
}
217-
: value;
218-
219204
const form = useForm<HypercertFormValues>({
220205
resolver: zodResolver(formSchemaUsed),
221-
defaultValues,
206+
defaultValues: values,
222207
mode: "onBlur",
223208
});
224209

225-
const watchedValues = useWatch({
226-
control: form.control,
227-
name: ["title", "banner", "logo", "tags", "projectDates"],
228-
});
210+
const title = form.watch("title");
211+
const banner = form.watch("banner");
212+
const logo = form.watch("logo");
213+
const tags = form.watch("tags");
214+
const projectDates = form.watch("projectDates");
229215

230216
const cardPreviewData = {
231-
title: watchedValues[0] ?? formDefaultValues.title,
232-
banner: watchedValues[1] ?? formDefaultValues.banner,
233-
logo: watchedValues[2] ?? formDefaultValues.logo,
234-
tags: watchedValues[3] ?? formDefaultValues.tags,
235-
projectDates: watchedValues[4] ?? formDefaultValues.projectDates,
217+
title: title ?? formDefaultValues.title,
218+
banner: banner ?? formDefaultValues.banner,
219+
logo: logo ?? formDefaultValues.logo,
220+
tags: tags ?? formDefaultValues.tags,
221+
projectDates: projectDates ?? formDefaultValues.projectDates,
236222
};
237223
const cardRef = useRef<HTMLDivElement>(null);
238224

@@ -241,12 +227,11 @@ export function HypercertMintingForm({
241227
}, []);
242228

243229
const onBlur = () => {
244-
console.log("blur");
245-
setValue(form.getValues());
230+
setValues(form.getValues());
246231
};
247232

248233
const onReset = () => {
249-
setValue(formDefaultValues);
234+
setValues(formDefaultValues);
250235
form.reset(formDefaultValues);
251236
setCurrentStep(1);
252237
};

components/hypercert/hypercert-window.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { SUPPORTED_CHAINS, SupportedChainIdType } from "@/configs/constants";
33
import { HypercertListFragment } from "@/hypercerts/fragments/hypercert-list.fragment";
44
import { getEvaluationStatus } from "@/hypercerts/getEvaluationStatus";
55
import { calculateBigIntPercentage } from "@/lib/calculateBigIntPercentage";
6+
import { formatPercentageToFirstNonZeroDigit } from "@/lib/formatPercentage";
67
import { getCurrencyByAddress } from "@/marketplace/utils";
78
import Image from "next/image";
89
import Link from "next/link";
@@ -95,7 +96,12 @@ const HypercertWindow = memo(
9596
<section className="flex text-xs justify-between">
9697
<section>
9798
<h6 className="opacity-70">for sale</h6>
98-
<p> {percentAvailable ? `${percentAvailable}%` : "--"}</p>
99+
<p>
100+
{" "}
101+
{percentAvailable
102+
? `${formatPercentageToFirstNonZeroDigit(percentAvailable)}%`
103+
: "--"}
104+
</p>
99105
</section>
100106
<section>
101107
<h6 className="text-end opacity-70">lowest per %</h6>

components/marketplace/list-dialog.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,16 @@ function ListDialogInner({
265265
</DialogTitle>
266266
</DialogHeader>
267267

268-
<DialogDescription>
269-
List your hypercert fraction for sale, allowing buyers to directly
270-
purchase all or part of it. Adjust settings to retain portions for
271-
yourself or set a minimum transaction amount as needed.
268+
<DialogDescription className="flex flex-col gap-2">
269+
<p>
270+
List your hypercert fraction for sale, allowing buyers to directly
271+
purchase all or part of it. Adjust settings to retain portions for
272+
yourself or set a minimum transaction amount as needed.
273+
</p>
274+
275+
<p className="text-sm text-slate-500">
276+
Any sale will include a 1% protocol fee.
277+
</p>
272278
</DialogDescription>
273279

274280
{fractionsOwnedByUser.length > 1 && (

hooks/use-hypercert-client.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ import { HypercertClient } from "@hypercerts-org/sdk";
33
import { useEffect, useState } from "react";
44
import { useAccount, useWalletClient } from "wagmi";
55
import { ENVIRONMENT, SUPPORTED_CHAINS } from "@/configs/constants";
6+
import { EvmClientFactory } from "@/lib/evmClient";
67

78
export const useHypercertClient = () => {
89
const { data: walletClient } = useWalletClient();
910
const { isConnected } = useAccount();
1011
const [client, setClient] = useState<HypercertClient>();
1112

13+
const publicClient = walletClient?.chain.id
14+
? EvmClientFactory.createClient(walletClient.chain.id)
15+
: undefined;
16+
1217
useEffect(() => {
1318
if (!walletClient || !isConnected) {
1419
return;
@@ -22,6 +27,8 @@ export const useHypercertClient = () => {
2227
environment: ENVIRONMENT,
2328
// @ts-ignore - wagmi and viem have different typing
2429
walletClient,
30+
// @ts-ignore - wagmi and viem have different typing
31+
publicClient,
2532
}),
2633
);
2734
}, [walletClient, isConnected]);

hypercerts/getAllHypercerts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ export async function getAllHypercerts({
192192
[],
193193
);
194194

195-
const difference = res.hypercerts?.count
196-
? res.hypercerts?.count - data.length
195+
const difference = res.hypercerts?.data?.length
196+
? res.hypercerts?.data?.length - data.length
197197
: 0;
198198
const totalCount = res.hypercerts?.count
199199
? res.hypercerts?.count - difference

lib/formatPercentage.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export const formatPercentageToFirstNonZeroDigit = (
2+
percentage: number,
3+
): string => {
4+
if (percentage === 0) return "0";
5+
6+
if (percentage < 1) {
7+
const NumberFormat = new Intl.NumberFormat("en-US", {
8+
style: "decimal",
9+
notation: "standard",
10+
maximumSignificantDigits: 21,
11+
});
12+
13+
const result = NumberFormat.format(percentage);
14+
15+
// Match the first 2 non-zero digits after the decimal point
16+
const match = result.match(/\d*\.\d*?[1-9](?:\d*[1-9]|\d)?/);
17+
18+
// If no decimal or no non-zero digits after decimal, return rounded integer
19+
if (!match) {
20+
return Math.round(percentage).toString();
21+
}
22+
23+
// Check if we found 1 or 2 non-zero digits after the decimal point
24+
// Skip leading zeros after decimal points
25+
const numbersAfterDecimal = match[0].split(".")[1];
26+
const isSingleNonZeroDigit =
27+
numbersAfterDecimal.replace(/^0+/, "").length === 1;
28+
29+
// If we have 1 significant digit, return the match
30+
if (isSingleNonZeroDigit) {
31+
return match[0];
32+
}
33+
34+
// If we have 2 significant digits, get the number of digits after the decimal point
35+
const fractionDigits = numbersAfterDecimal.length;
36+
return new Intl.NumberFormat("en-US", {
37+
style: "decimal",
38+
notation: "standard",
39+
// maximumSignificantDigits: decimalDigits - 1,
40+
maximumFractionDigits: fractionDigits - 1,
41+
}).format(percentage);
42+
}
43+
44+
// Round to 1 digit, strip trailing zeros
45+
return percentage.toFixed(1).replace(/\.?0+$/, "");
46+
};

test/lib/formatPercentage.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, it, expect } from "vitest";
2+
import { formatPercentageToFirstNonZeroDigit } from "@/lib/formatPercentage";
3+
4+
describe("formatPercentage", () => {
5+
it("should return the original integer if its more than 1", () => {
6+
expect(formatPercentageToFirstNonZeroDigit(97)).to.eq("97");
7+
});
8+
9+
it("should round to 1 decimal place if the integer is more than 1", () => {
10+
expect(formatPercentageToFirstNonZeroDigit(97.5)).to.eq("97.5");
11+
expect(formatPercentageToFirstNonZeroDigit(97.001)).to.eq("97");
12+
expect(formatPercentageToFirstNonZeroDigit(97.01)).to.eq("97");
13+
expect(formatPercentageToFirstNonZeroDigit(1.008)).to.eq("1");
14+
expect(formatPercentageToFirstNonZeroDigit(1.99)).to.eq("2");
15+
expect(formatPercentageToFirstNonZeroDigit(97.0000000000081)).to.eq("97");
16+
expect(formatPercentageToFirstNonZeroDigit(97.0000000000081)).to.eq("97");
17+
});
18+
19+
it("should round to the first non-zero digit", () => {
20+
expect(formatPercentageToFirstNonZeroDigit(0.008)).to.eq("0.008");
21+
expect(formatPercentageToFirstNonZeroDigit(0.0081)).to.eq("0.008");
22+
expect(formatPercentageToFirstNonZeroDigit(0.000000000008)).to.eq(
23+
"0.000000000008",
24+
);
25+
});
26+
27+
it("should take the second non-zero digit to round the first non-zero digit", () => {
28+
expect(formatPercentageToFirstNonZeroDigit(0.99)).to.eq("1");
29+
expect(formatPercentageToFirstNonZeroDigit(0.109)).to.eq("0.11");
30+
expect(formatPercentageToFirstNonZeroDigit(0.0000000000081)).to.eq(
31+
"0.000000000008",
32+
);
33+
expect(formatPercentageToFirstNonZeroDigit(0.0000000000085)).to.eq(
34+
"0.000000000009",
35+
);
36+
});
37+
});

0 commit comments

Comments
 (0)