-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1867 from zeitgeistpm/staging
- Loading branch information
Showing
18 changed files
with
578 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
); | ||
}; |
Oops, something went wrong.