Skip to content

Commit

Permalink
1481 blockchain network selection (#1518)
Browse files Browse the repository at this point in the history
* feat(app): moved sync to chain logic to sidebar

* feat(app): move sync to chain logic to its own component

* feat(app): disable on chain button if chain is not active

* feat(app): save on chain providers by chainid and indicate in on chain form

* feat(app): update checkOnChainStatus to look for differences between both onchain and db

* fix(app): update tests to match chain and provider state

* fix(app): update provider list to look at gitcoin stamp

* fix(app): remove old providers from test helper
  • Loading branch information
schultztimothy authored Jul 27, 2023
1 parent bbca98e commit 3ca1a1f
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 185 deletions.
75 changes: 2 additions & 73 deletions app/__test-fixtures__/contextTestHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,54 +61,6 @@ export const makeTestCeramicContext = (initialState?: Partial<CeramicContextStat
providerSpec: getProviderSpec("Poh", "Poh"),
stamp: undefined,
},
Twitter: {
providerSpec: getProviderSpec("Twitter", "Twitter"),
stamp: undefined,
},
TwitterFollowerGT100: {
providerSpec: getProviderSpec("Twitter", "TwitterFollowerGT100"),
stamp: undefined,
},
TwitterFollowerGT500: {
providerSpec: getProviderSpec("Twitter", "TwitterFollowerGT500"),
stamp: undefined,
},
TwitterFollowerGTE1000: {
providerSpec: getProviderSpec("Twitter", "TwitterFollowerGTE1000"),
stamp: undefined,
},
TwitterFollowerGT5000: {
providerSpec: getProviderSpec("Twitter", "TwitterFollowerGT5000"),
stamp: undefined,
},
TwitterTweetGT10: {
providerSpec: getProviderSpec("Twitter", "TwitterTweetGT10"),
stamp: undefined,
},
"twitterAccountAgeGte#180": {
providerSpec: getProviderSpec("Twitter", "twitterAccountAgeGte#180"),
stamp: undefined,
},
"twitterAccountAgeGte#365": {
providerSpec: getProviderSpec("Twitter", "twitterAccountAgeGte#365"),
stamp: undefined,
},
"twitterAccountAgeGte#730": {
providerSpec: getProviderSpec("Twitter", "twitterAccountAgeGte#730"),
stamp: undefined,
},
"twitterTweetDaysGte#30": {
providerSpec: getProviderSpec("Twitter", "twitterTweetDaysGte#30"),
stamp: undefined,
},
"twitterTweetDaysGte#60": {
providerSpec: getProviderSpec("Twitter", "twitterTweetDaysGte#60"),
stamp: undefined,
},
"twitterTweetDaysGte#120": {
providerSpec: getProviderSpec("Twitter", "twitterTweetDaysGte#120"),
stamp: undefined,
},
POAP: {
providerSpec: getProviderSpec("POAP", "POAP"),
stamp: undefined,
Expand All @@ -125,30 +77,6 @@ export const makeTestCeramicContext = (initialState?: Partial<CeramicContextStat
providerSpec: getProviderSpec("Brightid", "Brightid"),
stamp: undefined,
},
Github: {
providerSpec: getProviderSpec("Github", "Github"),
stamp: undefined,
},
TenOrMoreGithubFollowers: {
providerSpec: getProviderSpec("Github", "TenOrMoreGithubFollowers"),
stamp: undefined,
},
FiftyOrMoreGithubFollowers: {
providerSpec: getProviderSpec("Github", "FiftyOrMoreGithubFollowers"),
stamp: undefined,
},
ForkedGithubRepoProvider: {
providerSpec: getProviderSpec("Github", "ForkedGithubRepoProvider"),
stamp: undefined,
},
StarredGithubRepoProvider: {
providerSpec: getProviderSpec("Github", "StarredGithubRepoProvider"),
stamp: undefined,
},
FiveOrMoreGithubRepos: {
providerSpec: getProviderSpec("Github", "FiveOrMoreGithubRepos"),
stamp: undefined,
},
Linkedin: {
providerSpec: getProviderSpec("Linkedin", "Linkedin"),
stamp: undefined,
Expand Down Expand Up @@ -264,7 +192,8 @@ export const renderWithContext = (

export const testOnChainContextState = (initialState?: Partial<OnChainContextState>): OnChainContextState => {
return {
onChainProviders: [
onChainProviders: {},
activeChainProviders: [
{
providerName: "githubAccountCreationGte#90",
credentialHash: "v0.0.0:rnutMGjNA2yPx/8xzJdn6sXDsY46lLUNV3DHAHoPJJg=",
Expand Down
52 changes: 21 additions & 31 deletions app/__tests__/components/ExpiredStampModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,38 +32,39 @@ describe("ExpiredStampModal", () => {
{} as UserContextState,
{
...mockCeramicContext,
expiredProviders: [
"Github",
"FiveOrMoreGithubRepos",
"ForkedGithubRepoProvider",
"StarredGithubRepoProvider",
"TenOrMoreGithubFollowers",
"FiftyOrMoreGithubFollowers",
"FirstEthTxnProvider",
"EthGTEOneTxnProvider",
],
expiredProviders: ["EthGTEOneTxnProvider"],
},
<ExpiredStampModal isOpen={true} onClose={() => {}} />
);

expect(screen.getAllByText("Github").length).toBe(1);
expect(screen.getAllByText("ETH").length).toBe(1);
});
it("should get a list of all PROVIDER_ID for a given platform", () => {
expect(getProviderIdsFromPlatformId("Github")).toEqual([
"Github",
"FiveOrMoreGithubRepos",
"ForkedGithubRepoProvider",
"StarredGithubRepoProvider",
"TenOrMoreGithubFollowers",
"FiftyOrMoreGithubFollowers",
expect(getProviderIdsFromPlatformId("Gitcoin")).toEqual([
"GitcoinContributorStatistics#numGrantsContributeToGte#1",
"GitcoinContributorStatistics#numGrantsContributeToGte#10",
"GitcoinContributorStatistics#numGrantsContributeToGte#25",
"GitcoinContributorStatistics#numGrantsContributeToGte#100",
"GitcoinContributorStatistics#totalContributionAmountGte#10",
"GitcoinContributorStatistics#totalContributionAmountGte#100",
"GitcoinContributorStatistics#totalContributionAmountGte#1000",
"GitcoinContributorStatistics#numGr14ContributionsGte#1",
"GitcoinContributorStatistics#numRoundsContributedToGte#1",
"GitcoinGranteeStatistics#numOwnedGrants#1",
"GitcoinGranteeStatistics#numGrantContributors#10",
"GitcoinGranteeStatistics#numGrantContributors#25",
"GitcoinGranteeStatistics#numGrantContributors#100",
"GitcoinGranteeStatistics#totalContributionAmount#100",
"GitcoinGranteeStatistics#totalContributionAmount#1000",
"GitcoinGranteeStatistics#totalContributionAmount#10000",
"GitcoinGranteeStatistics#numGrantsInEcoAndCauseRound#1",
]);
});
it("should delete all stamps within each expired platform", async () => {
const handleDeleteStamps = jest.fn();
renderWithContext(
{} as UserContextState,
{ ...mockCeramicContext, expiredProviders: ["Github", "Ens", "Linkedin", "Facebook"], handleDeleteStamps },
{ ...mockCeramicContext, expiredProviders: ["Ens", "Linkedin", "Facebook"], handleDeleteStamps },
<ExpiredStampModal isOpen={true} onClose={() => {}} />
);

Expand All @@ -74,18 +75,7 @@ describe("ExpiredStampModal", () => {
deleteButton.click();
expect(deleteButton.getAttribute("disabled")).not.toBeNull();

expect(handleDeleteStamps).toHaveBeenCalledWith([
"Github",
"FiveOrMoreGithubRepos",
"ForkedGithubRepoProvider",
"StarredGithubRepoProvider",
"TenOrMoreGithubFollowers",
"FiftyOrMoreGithubFollowers",
"Facebook",
"FacebookProfilePicture",
"Linkedin",
"Ens",
]);
expect(handleDeleteStamps).toHaveBeenCalledWith(["Facebook", "FacebookProfilePicture", "Linkedin", "Ens"]);
await waitFor(() => expect(deleteButton.getAttribute("disabled")).toBeNull());
});
});
6 changes: 4 additions & 2 deletions app/__tests__/pages/Dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import {
import { CeramicContextState, IsLoadingPassportState } from "../../context/ceramicContext";
import { closeAllToasts } from "../../__test-fixtures__/toastTestHelpers";

jest.mock("../../utils/onboard.ts");
jest.mock("../../utils/onboard.ts", () => ({
chains: [],
}));

jest.mock("../../components/RefreshStampModal", () => ({
RefreshStampModal: () => <div>Refresh Modal</div>,
}));

jest.mock("../../components/SyncToChainButton", () => () => <div>Sync to Chain</div>);
jest.mock("../../components/SyncToChainButton", () => <div>Sync to Chain</div>);

jest.mock("@self.id/framework", () => {
return {
Expand Down
26 changes: 26 additions & 0 deletions app/components/InitiateOnChainButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable react-hooks/exhaustive-deps */
// --- React Methods
import React, { useState } from "react";

// --Chakra UI Elements
import { LinkIcon } from "@heroicons/react/20/solid";

// --- Style Components
import { OnchainSidebar } from "./OnchainSidebar";

const InitiateOnChainButton = () => {
const [showSidebar, setShowSidebar] = useState<boolean>(false);

return (
<>
<button className="h-10 w-10 rounded-md border border-muted" onClick={() => setShowSidebar(true)}>
<div className="flex justify-center">
<LinkIcon width="24" />
</div>
</button>
<OnchainSidebar isOpen={showSidebar} onClose={() => setShowSidebar(false)} />
</>
);
};

export default InitiateOnChainButton;
47 changes: 19 additions & 28 deletions app/components/NetworkCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Stamp } from "@gitcoin/passport-types";
import { useContext, useEffect, useState } from "react";
import { CeramicContext, AllProvidersState, ProviderState } from "../context/ceramicContext";
import { OnChainContext, OnChainProviderType } from "../context/onChainContext";
import { UserContext } from "../context/userContext";
import { SyncToChainButton } from "./SyncToChainButton";

type Chain = {
id: string;
Expand All @@ -18,41 +20,33 @@ export enum OnChainStatus {
}

type ProviderWithStamp = ProviderState & { stamp: Stamp };

export const checkOnChainStatus = (
allProvidersState: AllProvidersState,
onChainProviders: OnChainProviderType[]
): OnChainStatus => {
if (onChainProviders.length === 0) {
return OnChainStatus.NOT_MOVED;
}
if (onChainProviders.length === 0) return OnChainStatus.NOT_MOVED;

const verifiedDbProviders: ProviderWithStamp[] = Object.values(allProvidersState).filter(
(provider): provider is ProviderWithStamp => provider.stamp !== undefined
);

const onChainDifference = verifiedDbProviders.filter(
(provider) =>
!onChainProviders.some(
const [equivalentProviders, differentProviders] = verifiedDbProviders.reduce(
([eq, diff], provider): [ProviderWithStamp[], ProviderWithStamp[]] => {
const isEquivalent = onChainProviders.some(
(onChainProvider) =>
onChainProvider.providerName === provider.stamp.provider &&
onChainProvider.credentialHash === provider.stamp.credential.credentialSubject?.hash
)
);
return isEquivalent ? [[...eq, provider], diff] : [eq, [...diff, provider]];
},
[[], []] as [ProviderWithStamp[], ProviderWithStamp[]]
);

return onChainDifference.length > 0 ? OnChainStatus.MOVED_OUT_OF_DATE : OnChainStatus.MOVED_UP_TO_DATE;
return equivalentProviders.length === onChainProviders.length && differentProviders.length === 0
? OnChainStatus.MOVED_UP_TO_DATE
: OnChainStatus.MOVED_OUT_OF_DATE;
};

export function getButtonMsg(onChainStatus: OnChainStatus): string {
switch (onChainStatus) {
case OnChainStatus.NOT_MOVED:
return "Up to date";
case OnChainStatus.MOVED_OUT_OF_DATE:
return "Update";
case OnChainStatus.MOVED_UP_TO_DATE:
return "Up to date";
}
}

export function NetworkCard({ chain, activeChains }: { chain: Chain; activeChains: string[] }) {
const { allProvidersState } = useContext(CeramicContext);
const { onChainProviders } = useContext(OnChainContext);
Expand All @@ -65,14 +59,15 @@ export function NetworkCard({ chain, activeChains }: { chain: Chain; activeChain

useEffect(() => {
const checkStatus = async () => {
const stampStatus = await checkOnChainStatus(allProvidersState, onChainProviders);
const savedNetworkProviders = onChainProviders[chain.id] || [];
const stampStatus = checkOnChainStatus(allProvidersState, savedNetworkProviders);
setOnChainStatus(stampStatus);
};
checkStatus();
}, [allProvidersState, onChainProviders]);
}, [allProvidersState, chain.id, onChainProviders]);

return (
<div className="border border-accent-2 bg-background-2 p-0">
<div className="mb-6 border border-accent-2 bg-background-2 p-0">
<div className="mx-4 my-2">
<div className="flex w-full">
<div className="mr-4">
Expand All @@ -86,11 +81,7 @@ export function NetworkCard({ chain, activeChains }: { chain: Chain; activeChain
</div>
</div>
</div>
<button className="verify-btn center" data-testid="card-menu-button">
<span className="mx-2 translate-y-[1px] text-muted">
{isActive ? getButtonMsg(onChainStatus) : "Coming Soon"}
</span>
</button>
<SyncToChainButton onChainStatus={onChainStatus} isActive={isActive} />
</div>
);
}
5 changes: 2 additions & 3 deletions app/components/PlatformCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { pillLocalStorage } from "../context/userContext";
import { JsonOutputModal } from "./JsonOutputModal";
import { RemoveStampModal } from "./RemoveStampModal";
import { getStampProviderFilters } from "../config/filters";
import { getProviderSpec } from "../utils/helpers";

type SelectedProviders = Record<PLATFORM_ID, PROVIDER_ID[]>;

Expand Down Expand Up @@ -50,7 +49,7 @@ export const PlatformCard = ({
}: PlatformCardProps): JSX.Element => {
// import all providers
const { allProvidersState, passportHasCacaoError, handleDeleteStamps } = useContext(CeramicContext);
const { onChainProviders } = useContext(OnChainContext);
const { activeChainProviders } = useContext(OnChainContext);

// stamp filter
const router = useRouter();
Expand Down Expand Up @@ -78,7 +77,7 @@ export const PlatformCard = ({
if (!providers.length) return false;

return providers.some((provider: PROVIDER_ID) => {
const providerObj = onChainProviders.find((p) => p.providerName === provider);
const providerObj = activeChainProviders.find((p) => p.providerName === provider);
if (providerObj) {
return providerObj.credentialHash === allProvidersState[provider]?.stamp?.credential.credentialSubject.hash;
}
Expand Down
4 changes: 2 additions & 2 deletions app/components/StampSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function StampSelector({
setSelectedProviders,
}: StampSelectorProps) {
const { allProvidersState } = useContext(CeramicContext);
const { onChainProviders } = useContext(OnChainContext);
const { activeChainProviders } = useContext(OnChainContext);
// stamp filter
const router = useRouter();
const { filter } = router.query;
Expand All @@ -35,7 +35,7 @@ export function StampSelector({
// check if provider is on-chain
const isProviderOnChain = (provider: PROVIDER_ID) => {
if (currentPlatform) {
const providerObj = onChainProviders.find((p) => p.providerName === provider);
const providerObj = activeChainProviders.find((p) => p.providerName === provider);
if (providerObj) {
return providerObj.credentialHash === allProvidersState[provider]?.stamp?.credential.credentialSubject.hash;
}
Expand Down
Loading

0 comments on commit 3ca1a1f

Please sign in to comment.