Skip to content

Commit

Permalink
feat(app): adding low score alert for onchain push (#2802)
Browse files Browse the repository at this point in the history
* feat(app): adding low score alert for onchain push - WIP

* continued WIP

* working

* feat(app): added dynamic threshold, test

* build fix
  • Loading branch information
lucianHymer authored Aug 23, 2024
1 parent 520ea19 commit dcef4ab
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 82 deletions.
25 changes: 24 additions & 1 deletion app/__tests__/components/SyncToChainButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,34 @@ describe("SyncToChainButton component", () => {
},
<ChakraProvider>
<SyncToChainButton onChainStatus={OnChainStatus.NOT_MOVED} chain={chainWithEas} />
</ChakraProvider>
</ChakraProvider>,
{},
{ threshold: 10, rawScore: 20 }
);
const btn = screen.getByTestId("sync-to-chain-button");
fireEvent.click(btn);

await screen.findByText("Passport submitted to chain.");
});

it("should prompt user if score is low", async () => {
renderWithContext(
{
...mockCeramicContext,
passport: { ...mockCeramicContext.passport, stamps: [{ id: "test" } as any] },
},
<ChakraProvider>
<SyncToChainButton onChainStatus={OnChainStatus.NOT_MOVED} chain={chainWithEas} />
</ChakraProvider>,
{},
{ threshold: 15, rawScore: 10 }
);
const btn = screen.getByTestId("sync-to-chain-button");
fireEvent.click(btn);

await screen.findByText(
`While some benefits might be available with a lower score, many partners require a score of 15 or higher.`,
{ exact: false }
);
});
});
105 changes: 105 additions & 0 deletions app/components/ActionOrCancelModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from "react";
import { LoadButton } from "./LoadButton";
import { Button } from "./Button";
import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@chakra-ui/react";

// Modal with an action button and a cancel button
// Pass the body content as children
export const ActionOrCancelModal = ({
title,
buttonText,
onButtonClick,
buttonLoading,
buttonDisabled,
isOpen,
onClose,
children,
}: {
title: React.ReactNode;
buttonText: string;
onButtonClick: () => void;
isOpen: boolean;
onClose: () => void;
buttonLoading?: boolean;
buttonDisabled?: boolean;
children: React.ReactNode;
}) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay width="100%" height="100%" />
<ModalContent
rounded={"8px"}
padding={"28px"}
paddingBottom={"12px"}
maxW={"380px"}
border={"rgb(var(--color-foreground-5)) 1px solid"}
background={"linear-gradient(180deg, rgb(var(--color-background)) 0%, rgb(var(--color-foreground-5)) 100%)"}
>
<ModalHeader padding={0} fontWeight={"normal"} className="text-xl font-heading leading-tight text-focus my-4">
{title}
</ModalHeader>
<ModalBody padding={0}>{children}</ModalBody>
<ModalFooter padding={0} className="mt-8 flex font-medium flex-col items-center">
<LoadButton className="w-full" onClick={onButtonClick} isLoading={buttonLoading} disabled={buttonDisabled}>
{buttonText}
</LoadButton>
<Button variant="custom" className="mt-4 px-8" onClick={onClose}>
Cancel
</Button>
</ModalFooter>
</ModalContent>
</Modal>
// Headless UI version, doesn't work with Chakra modals
// <>
// <Transition appear show={isOpen} as={Fragment}>
// <Dialog as="div" className="relative z-100" onClose={onClose}>
// <Transition.Child
// enter="ease-out duration-300"
// enterFrom="opacity-0"
// enterTo="opacity-100"
// leave="ease-in duration-200"
// leaveFrom="opacity-100"
// leaveTo="opacity-0"
// >
// <Backdrop />
// </Transition.Child>

// <div className="fixed inset-0 overflow-y-auto">
// <div className="flex min-h-full items-center justify-center p-4 text-center">
// <Transition.Child
// as={Fragment}
// enter="ease-out duration-300"
// enterFrom="opacity-0 scale-95"
// enterTo="opacity-100 scale-100"
// leave="ease-in duration-200"
// leaveFrom="opacity-100 scale-100"
// leaveTo="opacity-0 scale-95"
// >
// <Dialog.Panel className="w-full max-w-sm overflow-hidden transition-all">
// <div className="p-7 text-base text-left text-color-1 align-middle w-full rounded-lg border border-foreground-5 bg-gradient-to-b from-background to-foreground-5">
// <Dialog.Title className="text-xl font-heading leading-tight text-focus my-4">{title}</Dialog.Title>
// <div>{children}</div>

// <div className="mt-4 flex font-medium flex-col items-center">
// <LoadButton
// className="w-full"
// onClick={onButtonClick}
// isLoading={buttonLoading}
// disabled={buttonDisabled}
// >
// {buttonText}
// </LoadButton>
// <Button variant="custom" className="mt-4 px-8" onClick={onClose}>
// Cancel
// </Button>
// </div>
// </div>
// </Dialog.Panel>
// </Transition.Child>
// </div>
// </div>
// </Dialog>
// </Transition>
// </>
);
};
1 change: 1 addition & 0 deletions app/components/CustomDashboardPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const DynamicCustomDashboardPanel = ({ className }: { className: string }
window.open(body.action.url, "_blank");
}
};

return (
<CustomDashboardPanel className={className} logo={logo}>
{body.displayInfoTooltip && body.displayInfoTooltip.shouldDisplay && body.displayInfoTooltip.text ? (
Expand Down
27 changes: 27 additions & 0 deletions app/components/LowScoreAlertModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import { ActionOrCancelModal } from "../components/ActionOrCancelModal";

export const LowScoreAlertModal = ({
isOpen,
onProceed,
onCancel,
threshold,
}: {
isOpen: boolean;
onProceed: () => void;
onCancel: () => void;
threshold: number;
}) => (
<ActionOrCancelModal
title="Try building up a higher score?"
buttonText="Proceed with mint"
isOpen={isOpen}
onButtonClick={onProceed}
onClose={onCancel}
>
While some benefits might be available with a lower score, many partners require a score of {threshold} or higher.
<br />
<br />
Do you still wish to proceed?
</ActionOrCancelModal>
);
9 changes: 4 additions & 5 deletions app/components/PlatformDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const PlatformJsonButton = ({
const { handleDeleteStamps } = useContext(CeramicContext);
const customization = useCustomization();
const [stampDetailsModal, setStampDetailsModal] = useState(false);
const [removeStampModal, setRemoveStampModal] = useState(false);
const [showRemoveStampModal, setShowRemoveStampModal] = useState(false);
const [referenceElement, setReferenceElement] = useState(null);

const providerIds = getStampProviderIds(platform.platform, customStampProviders(customization));
Expand Down Expand Up @@ -62,7 +62,7 @@ const PlatformJsonButton = ({
<button onClick={() => setStampDetailsModal(true)} className="w-full text-left">
Stamp Details
</button>
<button className="w-full text-left text-color-7" onClick={() => setRemoveStampModal(true)}>
<button className="w-full text-left text-color-7" onClick={() => setShowRemoveStampModal(true)}>
Remove Stamp
</button>
</Popover.Panel>
Expand All @@ -77,13 +77,12 @@ const PlatformJsonButton = ({
jsonOutput={platformPassportData}
/>
<RemoveStampModal
isOpen={removeStampModal}
onClose={onClose}
isOpen={showRemoveStampModal}
onClose={() => setShowRemoveStampModal(false)}
title={`Remove ${platform.name} Stamp`}
body={"This stamp will be removed from your Passport. You can still re-verify your stamp in the future."}
stampsToBeDeleted={providerIds}
handleDeleteStamps={onRemoveStamps}
platformId={platform.name as PLATFORM_ID}
/>
</>
);
Expand Down
57 changes: 12 additions & 45 deletions app/components/RemoveStampModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,29 @@
import React, { useState } from "react";

// --- Chakra Elements
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
useToast,
} from "@chakra-ui/react";
import { useToast } from "@chakra-ui/react";
import { PLATFORM_ID, PROVIDER_ID } from "@gitcoin/passport-types";
import { Button } from "./Button";
import { LoadButton } from "./LoadButton";

// --- Style Components
import { DoneToastContent } from "./DoneToastContent";
import { ActionOrCancelModal } from "./ActionOrCancelModal";

export type RemoveStampModalProps = {
isOpen: boolean;
onClose: () => void;
title: string;
body: string;
closeButtonText?: string;
stampsToBeDeleted?: PROVIDER_ID[];
handleDeleteStamps: Function;
platformId: PLATFORM_ID;
};

export const RemoveStampModal = ({
isOpen,
onClose,
title,
body,
closeButtonText,
stampsToBeDeleted,
handleDeleteStamps,
platformId,
}: RemoveStampModalProps): JSX.Element => {
const [isLoading, setIsLoading] = useState(false);
const toast = useToast();
Expand Down Expand Up @@ -78,35 +65,15 @@ export const RemoveStampModal = ({
};

return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay width="100%" height="100%" />
<ModalContent
rounded={"none"}
padding={5}
maxW={{
sm: "100%",
md: "600px",
}}
maxH="80%"
>
<ModalHeader>
<img alt="shield alert" src="../assets/shield-alert.svg" className="m-auto mb-4 w-10" />
<p className="text-center">{title}</p>
</ModalHeader>
<ModalCloseButton />
<ModalBody color="#757087" className="mb-10 overflow-auto text-center">
{body}
</ModalBody>

<div className="grid grid-cols-2 gap-4">
<Button data-testid="button-stamp-removal-cancel" variant="secondary" onClick={onClose}>
Cancel
</Button>
<LoadButton data-testid="button-stamp-removal" onClick={handleStampRemoval} isLoading={isLoading}>
{isLoading ? "Removing..." : closeButtonText || "Remove Stamp"}
</LoadButton>
</div>
</ModalContent>
</Modal>
<ActionOrCancelModal
title={title}
buttonText={isLoading ? "Removing..." : "Remove Stamp"}
onButtonClick={handleStampRemoval}
isOpen={isOpen}
onClose={onClose}
buttonLoading={isLoading}
>
{body}
</ActionOrCancelModal>
);
};
3 changes: 0 additions & 3 deletions app/components/SideBarContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import { PLATFORM_ID, PROVIDER_ID } from "@gitcoin/passport-types";
import { StampSelector } from "./StampSelector";
import { PlatformDetails } from "./PlatformDetails";
import { PlatformScoreSpec } from "../context/scorerContext";
import { RemoveStampModal } from "./RemoveStampModal";
import { STAMP_PROVIDERS } from "../config/providers";
import { CeramicContext } from "../context/ceramicContext";

export type SideBarContentProps = {
currentPlatform: PlatformScoreSpec | undefined;
Expand Down
Loading

0 comments on commit dcef4ab

Please sign in to comment.