Skip to content

Commit

Permalink
Merge pull request #1846 from zeitgeistpm/#61-oracle-reporting-notifi…
Browse files Browse the repository at this point in the history
…cations

Alerts, show market ready to report for oracle
  • Loading branch information
yornaath authored Oct 9, 2023
2 parents fe9e2c6 + 2b7a545 commit 9cbe474
Show file tree
Hide file tree
Showing 18 changed files with 578 additions and 7 deletions.
6 changes: 3 additions & 3 deletions components/front-page/HeroBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ export const HeroBanner = ({
className="py-3 px-4 w-full rounded-md flex gap-2"
style={{ backgroundColor: "rgba(28, 100, 242, 0.2)" }}
>
<div className="flex justify-start items-center gap-2 w-1/3">
<div className="flex justify-start items-center gap-3 w-1/3">
<div>
<ZeitgeistIcon variant="blue" height={28} width={28} />
<ZeitgeistIcon variant="blue" height={32} width={32} />
</div>
<div>
<div className="font-bold text-md">Zeitgeist</div>
<div className="text-lg font-medium">Zeitgeist</div>
<div className="text-sm">
{chainProperties.tokenSymbol.toString()}
</div>
Expand Down
232 changes: 232 additions & 0 deletions components/top-bar/Alerts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import { Menu, Transition, Portal } from "@headlessui/react";
import Modal from "components/ui/Modal";
import {
ReadyToReportMarketAlertData,
RedeemableMarketsAlertData,
RelevantMarketDisputeAlertData,
useAlerts,
} from "lib/state/alerts";
import { useWallet } from "lib/state/wallet";
import { useRouter } from "next/router";
import { Fragment, PropsWithChildren, useEffect, useState } from "react";
import { AiOutlineFileAdd } from "react-icons/ai";
import { BiMoneyWithdraw } from "react-icons/bi";
import { IoMdNotificationsOutline } from "react-icons/io";

export const Alerts = () => {
const wallet = useWallet();
const { alerts, setAsRead } = useAlerts(wallet.realAddress);

const hasNotifications = alerts.length > 0;

const [hoveringMenu, setHoveringMenu] = useState(false);

const mouseEnterMenuHandler = () => {
setHoveringMenu(true);
};
const mouseLeaveMenuHandler = () => {
setHoveringMenu(false);
};

return (
<Menu as="div" className="relative z-50">
{({ open, close }) => {
return (
<>
<div className="flex gap-2">
<Menu.Button
disabled={alerts.length === 0}
className="text-white font-light relative flex center gap-2"
>
<div
className={`transition-all ${
hasNotifications
? "text-gray-200 cursor-pointer"
: "text-gray-500"
}`}
>
<IoMdNotificationsOutline
className="transition-all"
size={24}
/>
{hasNotifications && (
<div className="absolute animate-pulse-scale top-0 right-0 w-3 h-3 bg-red-500 rounded-full"></div>
)}
</div>
</Menu.Button>
</div>

<Transition
as={Fragment}
show={open && hoveringMenu}
enter="transition-opacity ease-out duration-100"
enterFrom="transform opacity-0"
enterTo="transform opacity-1"
leave="transition-opacity ease-in opacity-0 duration-75"
leaveFrom="transform opacity-1"
leaveTo="transform opacity-0 "
>
<div
className="fixed z-40 left-0 top-0 h-screen w-screen bg-black/10 backdrop-blur-sm"
aria-hidden="true"
/>
</Transition>

<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform -translate-y-2"
enterTo="transform translate-y-0 "
leave="transition ease-in translate-y-2 duration-75"
leaveFrom="transform translate-y-0"
leaveTo="transform opacity-0 -translate-y-2"
>
<Menu.Items
onMouseEnter={mouseEnterMenuHandler}
onMouseLeave={mouseLeaveMenuHandler}
onWheelCapture={(e) => {
e.stopPropagation();
e.preventDefault();
}}
className={`
fixed md:absolute left-0 md:translate-x-[50%] md:left-auto p-2 md:px-4 md:max-h-[664px]
overflow-y-scroll md:right-0 bottom-0 md:bottom-auto z-50 py-3 top-11
md:top-auto mt-6 md:mt-6 w-full overflow-hidden h-full md:h-auto md:w-96 pb-20 md:pb-0
origin-top-right divide-gray-100 md:rounded-md focus:outline-none
bg-black/20 md:bg-transparent subtle-scroll-bar subtle-scroll-bar-on-hover
`}
>
{alerts.map((alert) => (
<Menu.Item key={alert.id}>
<div className={`${!hoveringMenu && "backdrop-blur-lg"}`}>
{alert.type === "ready-to-report-market" ? (
<ReadyToReportMarketAlertItem alert={alert} />
) : alert.type === "market-dispute" ? (
<RelevantMarketDisputeItem alert={alert} />
) : alert.type === "redeemable-markets" ? (
<RedeemableMarketAlertItem alert={alert} />
) : (
// Including this prevents us from not exhausting the switch on alert type.
// Should never be reached but caught by the type system.
<UnknownAlertItem alert={alert} />
)}
</div>
</Menu.Item>
))}
</Menu.Items>
</Transition>
</>
);
}}
</Menu>
);
};

const AlertCard: React.FC<PropsWithChildren & { onClick?: () => void }> = ({
children,
onClick,
}) => (
<div className="mb-2 md:hover:scale-105 hover:ring-1 ring-[#fa8cce] rounded-md transition-all cursor-pointer">
<div
className={`transition-all bg-white/80 md:bg-white/60 hover:md:bg-white/80 border-1 border-solid border-black/10 py-3 px-4 rounded-md`}
onClick={onClick}
style={{
transform: "translate3d(0,0,0)",
backfaceVisibility: "hidden",
}}
>
{children}
</div>
</div>
);

const ReadyToReportMarketAlertItem = ({
alert,
}: {
alert: ReadyToReportMarketAlertData;
}) => {
const router = useRouter();

useEffect(() => {
router.prefetch(`/markets/${alert.market.marketId}`);
}, [alert]);

return (
<AlertCard
onClick={() => {
router.push(`/markets/${alert.market.marketId}`);
}}
>
<div className="mb-1">
<div
className="rounded-full py-1 px-1.5 inline-flex text-xxs items-center gap-1"
style={{
background:
"linear-gradient(131.15deg, rgba(240, 206, 135, 0.4) 11.02%, rgba(254, 0, 152, 0.4) 93.27%)",
}}
>
<AiOutlineFileAdd size={12} className="text-gray-700" />
Submit Report
</div>
</div>
<div>
<h3 className="text-sm font-medium pl-1">{alert.market.question}</h3>
</div>
</AlertCard>
);
};

const RedeemableMarketAlertItem = ({
alert,
}: {
alert: RedeemableMarketsAlertData;
}) => {
const router = useRouter();
const wallet = useWallet();

useEffect(() => {
router.prefetch(`/portfolio/${wallet.realAddress}`);
}, [alert, wallet.realAddress]);

return (
<AlertCard
onClick={() => {
router.push(`/portfolio/${wallet.realAddress}`);
}}
>
<div className="mb-1">
<div
className="rounded-full py-1 px-1.5 inline-flex text-xxs items-center gap-1"
style={{
background:
"linear-gradient(131.15deg, rgba(50, 255, 157, 0.4) 11.02%, rgb(142 185 231 / 38%) 93.27%)",
}}
>
<BiMoneyWithdraw size={12} className="text-gray-600" />
Redeemable Tokens
</div>
</div>
<div>
<h3 className="text-sm font-medium pl-1">
You have {alert.markets.length} redeemable markets.
</h3>
</div>
</AlertCard>
);
};

const RelevantMarketDisputeItem = ({
alert,
}: {
alert: RelevantMarketDisputeAlertData;
}) => {
return <AlertCard></AlertCard>;
};

/**
* @note Since the param here is `never` it prevents us from forgetting to add a case for a new alert type
* If a case for a alert type is missing in the rendering of the list, the compiler will complain.
*/
const UnknownAlertItem = ({ alert }: { alert: never }) => {
return <></>;
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 6 additions & 2 deletions components/menu/index.tsx → components/top-bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Fragment, useState } from "react";

import { Menu, Transition } from "@headlessui/react";
import { CATEGORIES } from "components/front-page/PopularCategories";
import MenuLogo from "components/menu/MenuLogo";
import MenuLogo from "components/top-bar/MenuLogo";
import dynamic from "next/dynamic";
import Image from "next/image";
import Link from "next/link";
Expand All @@ -17,6 +17,7 @@ import {
FiList,
} from "react-icons/fi";
import { useCategoryCounts } from "lib/hooks/queries/useCategoryCounts";
import { Alerts } from "./Alerts";

const AccountButton = dynamic(() => import("../account/AccountButton"), {
ssr: false,
Expand Down Expand Up @@ -158,7 +159,10 @@ const TopBar = () => {
<div>Leaderboard</div>
</Link>
</div>
<AccountButton />
<div className="center gap-2">
<Alerts />
<AccountButton />
</div>
</div>
</div>
);
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion layouts/DefaultLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useResizeDetector } from "react-resize-detector";
import Image from "next/image";
import dynamic from "next/dynamic";

import TopBar from "components/menu";
import TopBar from "components/top-bar";
import Footer from "components/ui/Footer";
import NotificationCenter from "components/ui/NotificationCenter";
import GrillChat from "components/grillchat";
Expand Down
48 changes: 48 additions & 0 deletions lib/hooks/queries/useReadyToReportMarkets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useQuery } from "@tanstack/react-query";
import { MarketStatus } from "@zeitgeistpm/indexer";
import { isFullSdk } from "@zeitgeistpm/sdk-next";
import { isNotNull } from "@zeitgeistpm/utility/dist/null";
import { useSdkv2 } from "../useSdkv2";
import { useChainTime } from "lib/state/chaintime";

export const useReadyToReportMarkets = (account?: string) => {
const [sdk, id] = useSdkv2();
const chainTime = useChainTime();

const enabled = sdk && isFullSdk(sdk) && account && chainTime;

return useQuery(
[id, "ready-to-report-markets", account],
async () => {
if (enabled) {
const closedMarketsForAccount = await sdk.indexer.markets({
where: {
oracle_eq: account,
status_eq: MarketStatus.Closed,
},
});

let readyToReportMarkets = (
await Promise.all(
closedMarketsForAccount.markets.map(async (market) => {
const stage = await sdk.model.markets.getStage(market, chainTime);
if (
stage.type === "OracleReportingPeriod" ||
stage.type === "OpenReportingPeriod"
) {
return market;
}
return null;
}),
)
).filter(isNotNull);

return readyToReportMarkets;
}
},
{
enabled: Boolean(enabled),
refetchInterval: 1000 * 60,
},
);
};
Loading

0 comments on commit 9cbe474

Please sign in to comment.