Skip to content

Matching estimates on checkout page #2022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e7340a7
chore: update indexer client version
vacekj Sep 29, 2023
080802d
feat: matching estimates on checkout and hook
vacekj Sep 29, 2023
68fb3ab
chore: fix lint
vacekj Sep 29, 2023
b28b93b
chore: total matching
vacekj Sep 29, 2023
ed9a0cc
chore: rename ge scripts and fix matching estimates
vacekj Sep 29, 2023
2af3ef6
feat: cleanup
vacekj Oct 2, 2023
c3a58e3
feat: loading and error states for estimates
vacekj Oct 3, 2023
a6b1dab
chore: comments
vacekj Oct 3, 2023
49c7e2d
feat: per-round matching, hide per-project matching
vacekj Oct 4, 2023
39d438c
chore: revert some unrelated changes
vacekj Oct 4, 2023
b43001b
feat: take passport into account in matching estimates
vacekj Oct 5, 2023
19d58c4
feat: take passport into account in matching estimates
vacekj Oct 5, 2023
e0de288
chore: update lock
vacekj Oct 5, 2023
01edc1d
chore: address feedback from self-review
vacekj Oct 5, 2023
3a6fee6
Merge branch 'main' into 1931-spike-matching-estimates-for-donations
vacekj Oct 5, 2023
2801fc3
feat(common): update closeDelay in MatchingEstimateTooltip component …
vacekj Oct 5, 2023
d310d54
chore: add common and verify-env tests to CI, address feedback from PR
vacekj Oct 6, 2023
ac8f4b2
Merge branch 'main' into 1931-spike-matching-estimates-for-donations
vacekj Oct 6, 2023
f360090
fix: test
vacekj Oct 6, 2023
2f1dd5a
Merge branch 'main' into 1931-spike-matching-estimates-for-donations
vacekj Oct 6, 2023
2b4662e
chore: drop a todoˆ
vacekj Oct 6, 2023
1412e78
feat: update frontend matching estimates
vacekj Oct 12, 2023
ebc5b74
Merge branch 'main' into 1931-spike-matching-estimates-for-donations
vacekj Oct 13, 2023
5c54fb3
fix: don't pass undefined as voter in matching estimates, pass zeroAd…
vacekj Oct 13, 2023
bd61965
fix: lint
vacekj Oct 13, 2023
a6e82cb
fix: minor fixes for feedback from review
vacekj Oct 13, 2023
a8c9f89
fix: don't estimate when round is not loaded, fix total matching bugˆ
vacekj Oct 13, 2023
e12ee25
fix: don't estimate when round is not loaded, fix total matching bug,…
vacekj Oct 13, 2023
f419f66
Merge branch 'main' into 1931-spike-matching-estimates-for-donations
vacekj Oct 13, 2023
86b16eb
Merge branch 'main' into 1931-spike-matching-estimates-for-donations
vacekj Oct 17, 2023
e431d64
feat: implement colors for matching estimates based on passport
boudra Oct 19, 2023
f47756b
Merge branch 'main' into 1931-spike-matching-estimates-for-donations
boudra Oct 20, 2023
e3894d6
Merge branch 'main' into 1931-spike-matching-estimates-for-donations
vacekj Oct 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/common.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Common - Test
on:
push:
branches:
- main
- release
pull_request:
branches:
- "**"
jobs:
lint-test-typecheck:
concurrency: ci-round-manager-${{ github.head_ref || github.run_id }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1

- uses: pnpm/action-setup@v2
with:
version: 8

- uses: actions/setup-node@v3
with:
node-version: "18"
cache: "pnpm"

- name: Install Dependencies
run: |
pnpm install

- name: Test Common
run: |
pnpm c-test
6 changes: 3 additions & 3 deletions .github/workflows/grant-explorer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ jobs:

- name: Lint Explorer
run: |
pnpm re-lint
pnpm ge-lint

- name: Test Explorer
run: |
pnpm re-test
pnpm ge-test

- name: Typecheck Explorer
run: |
pnpm re-typecheck
pnpm ge-typecheck
35 changes: 35 additions & 0 deletions .github/workflows/verify-env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Verify-Env - Test
on:
push:
branches:
- main
- release
pull_request:
branches:
- "**"
jobs:
lint-test-typecheck:
concurrency: ci-round-manager-${{ github.head_ref || github.run_id }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1

- uses: pnpm/action-setup@v2
with:
version: 8

- uses: actions/setup-node@v3
with:
node-version: "18"
cache: "pnpm"

- name: Install Dependencies
run: |
pnpm install

- name: Test Verify-Env
run: |
pnpm ve-test
3 changes: 3 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"trailingComma": "es5"
}
16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,26 @@
"rm-lint": "turbo run lint:ci --filter=round-manager",
"rm-typecheck": "turbo run typecheck --filter=round-manager",
"// grant explorer script": "====== packages/grant-explorer specific ======",
"re-build": "turbo run build --filter=grant-explorer",
"re-test": "turbo run test --filter=grant-explorer",
"re-start": "pnpm --filter grant-explorer run start",
"re-typecheck": "turbo run typecheck --filter=grant-explorer",
"re-lint": "turbo run lint:ci --filter=grant-explorer",
"ge-build": "turbo run build --filter=grant-explorer",
"ge-test": "turbo run test --filter=grant-explorer",
"ge-start": "pnpm --filter grant-explorer run start",
"ge-typecheck": "turbo run typecheck --filter=grant-explorer",
"ge-lint": "turbo run lint:ci --filter=grant-explorer",
"// builder script": "====== packages/builder specific ======",
"b-start": "pnpm --filter builder run start",
"b-lint": "turbo run lint:ci --filter=builder",
"b-test": "pnpm test --filter=builder",
"b-typecheck": "turbo run typecheck --filter=builder"
"b-typecheck": "turbo run typecheck --filter=builder",
"c-test": "turbo run test --filter=common",
"ve-test": "turbo run test --filter=verify-env"
},
"devDependencies": {
"@commitlint/cli": "^17.6.3",
"@commitlint/config-conventional": "^17.6.3"
},
"dependencies": {
"prettier": "^3.0.3",
"turbo": "^1.10.9"
"turbo": "^1.10.15"
},
"pnpm": {
"overrides": {
Expand Down
6 changes: 5 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"author": "Josef Vacek",
"license": "MIT",
"private": false,
"scripts": {
"test": "vitest --run"
},
"dependencies": {
"@ethersproject/providers": "^5.7.2",
"@rainbow-me/rainbowkit": "^0.12.16",
Expand All @@ -28,6 +31,7 @@
"@types/markdown-it": "^12.2.3",
"@types/node": "^18.15.5",
"@types/react": "^18.0.31",
"@types/react-dom": "^18.0.11"
"@types/react-dom": "^18.0.11",
"vitest": "^0.34.6"
}
}
39 changes: 39 additions & 0 deletions packages/common/src/chains.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { test, expect } from "vitest";
import { parseChainId, ChainId } from "./chains"; // Replace 'your-module' with the actual module path

test("Valid input: number", () => {
const input = 137;
const result = parseChainId(input);
expect(result).toBe(ChainId.POLYGON);
});

test("Valid input: string (number as string)", () => {
const input = "80001";
const result = parseChainId(input);
expect(result).toBe(ChainId.POLYGON_MUMBAI);
});

test("Invalid input: string (non-existent enum name)", () => {
const input = "NON_EXISTENT_CHAIN";
expect(() => parseChainId(input)).toThrow("Invalid chainId " + input);
});

test("Invalid input: string (non-numeric string)", () => {
const input = "invalid";
expect(() => parseChainId(input)).toThrow("Invalid chainId " + input);
});

test("Invalid input: number (non-existent enum value)", () => {
const input = 999;
expect(() => parseChainId(input)).toThrow("Invalid chainId " + input);
});

test("Invalid input: null", () => {
const input = null;
expect(() => parseChainId(input as any)).toThrow("Invalid chainId null");
});

test("Invalid input: undefined", () => {
const input = undefined;
expect(() => parseChainId(input as any)).toThrow("Invalid chainId undefined");
});
21 changes: 21 additions & 0 deletions packages/common/src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,24 @@ export const pgn: Chain = {
},
},
};

export function parseChainId(input: string | number): ChainId {
if (typeof input === "string") {
// If the input is a string, try to parse it as a number
const parsedInput = parseInt(input, 10);
if (!isNaN(parsedInput)) {
// If parsing is successful, check if it's a valid enum value
if (Object.values(ChainId).includes(parsedInput)) {
return parsedInput as ChainId;
}
}
} else if (typeof input === "number") {
// If the input is a number, check if it's a valid enum value
if (Object.values(ChainId).includes(input)) {
return input as ChainId;
}
}

// If the input is not a valid enum value, return undefined
throw "Invalid chainId " + input;
}
2 changes: 1 addition & 1 deletion packages/grant-explorer/src/features/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export type VotingToken = {
decimal: number;
logo?: string;
default?: boolean;
redstoneTokenId?: string;
redstoneTokenId: string;
permitVersion?: string;
//TODO: remove if the previous default was intended to be used as defaultForVoting
defaultForVoting: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { InformationCircleIcon } from "@heroicons/react/24/solid";
import React from "react";
import { Tooltip } from "@chakra-ui/react";

export function MatchingEstimateTooltip(props: { isEligible: boolean }) {
return (
<div>
<Tooltip
hasArrow
closeDelay={2000}
placement={"bottom-end"}
label={
<p className="text-xs p-1 pointer-events-auto select-all">
{props.isEligible ? (
<>
Due to the nature of quadratic funding, this estimated match is
subject to change as the round progresses. Your match may start
at $0, but can change as the project receives more donations.
Read more about how quadratic funding works{" "}
<a
href="https://wtfisqf.com"
className={"underline"}
target={"_blank"}
>
here
</a>
.
</>
) : (
<>
Keep in mind that this is a potential match. By connecting to
Gitcoin Passport, you can update your score before or after
submitting your donation.{" "}
<a
href="https://passport.gitcoin.co"
className={"underline"}
target="_blank"
>
Click here
</a>{" "}
to configure your score.
</>
)}
</p>
}
id="matching-estimate-tooltip"
className={"max-w-sm bg-gray-500 text-gray-50"}
>
<InformationCircleIcon
data-background-color="#5932C4"
className="inline w-4 h-4 ml-2"
data-testid={"matching-estimate-tooltip"}
/>
</Tooltip>
</div>
);
}
49 changes: 49 additions & 0 deletions packages/grant-explorer/src/features/common/Passport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { PassportResponse } from "common";
import { useMemo } from "react";

export type PassportColor = "gray" | "orange" | "yellow" | "green";

export type PassportDisplay = {
score: number;
color: PassportColor;
};

export function passportColorTextClass(color: PassportColor): string {
switch (color) {
case "gray":
return "text-gray-400";
case "orange":
return "text-orange-400";
case "yellow":
return "text-yellow-400";
case "green":
return "text-green-400";
}
}

function passportDisplayColorFromScore(score: number | null): PassportColor {
if (score === null) {
return "gray";
} else if (score < 15) {
return "orange";
} else if (score < 25) {
return "yellow";
}

return "green";
}

export function usePassportScore(score?: PassportResponse) {
const passportScore = useMemo(() => {
if (score?.evidence?.rawScore === undefined) {
return null;
}

return Number(score.evidence.rawScore);
}, [score]);

return {
score: passportScore,
color: passportDisplayColorFromScore(passportScore),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const mockTokens: Record<string, VotingToken> = {
decimal: 18,
defaultForVoting: true,
canVote: true,
redstoneTokenId: "DAI",
},
};

Expand Down Expand Up @@ -98,9 +99,10 @@ vi.mock("@rainbow-me/rainbowkit", () => ({
}));

vi.mock("react-router-dom", async () => {
const actual = await vi.importActual<typeof import("react-router-dom")>(
"react-router-dom"
);
const actual =
await vi.importActual<typeof import("react-router-dom")>(
"react-router-dom",
);

return {
...actual,
Expand Down Expand Up @@ -136,28 +138,28 @@ describe("<ViewContributionHistory/>", () => {
addressLogo="mockedAddressLogo"
breadCrumbs={breadCrumbs}
/>
</MemoryRouter>
</MemoryRouter>,
);

expect(screen.getByText("Donation Impact")).toBeInTheDocument();
expect(screen.getByText("Donation History")).toBeInTheDocument();
expect(screen.getByText("Active Rounds")).toBeInTheDocument();
expect(screen.getByText("Past Rounds")).toBeInTheDocument();
expect(
screen.getByText(mockAddress.slice(0, 6) + "..." + mockAddress.slice(-6))
screen.getByText(mockAddress.slice(0, 6) + "..." + mockAddress.slice(-6)),
).toBeInTheDocument();
expect(screen.getByText("Share Profile")).toBeInTheDocument();

for (const contribution of mockContributions) {
for (const chainContribution of contribution.data) {
expect(
screen.getByText(chainContribution.roundName)
screen.getByText(chainContribution.roundName),
).toBeInTheDocument();
expect(
screen.getByText(chainContribution.projectTitle)
screen.getByText(chainContribution.projectTitle),
).toBeInTheDocument();
expect(screen.getAllByText("View transaction").length).toBeGreaterThan(
0
0,
);
}
}
Expand All @@ -177,14 +179,14 @@ describe("<ViewContributionHistoryWithoutDonations/>", () => {
addressLogo="mockedAddressLogo"
breadCrumbs={breadCrumbs}
/>
</MemoryRouter>
</MemoryRouter>,
);

await waitFor(() => {
expect(screen.getByText("Donation History")).toBeInTheDocument();
});
expect(
screen.getByText(mockAddress.slice(0, 6) + "..." + mockAddress.slice(-6))
screen.getByText(mockAddress.slice(0, 6) + "..." + mockAddress.slice(-6)),
).toBeInTheDocument();
expect(screen.getByText("Share Profile")).toBeInTheDocument();
});
Expand Down
Loading