Skip to content

Commit

Permalink
feat(mfi-v2-ui): menu modal
Browse files Browse the repository at this point in the history
  • Loading branch information
losman0s committed Oct 2, 2023
1 parent 05de465 commit 6e4c8fa
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 82 deletions.
1 change: 0 additions & 1 deletion apps/marginfi-v2-ui/public/pie_chart.svg

This file was deleted.

1 change: 0 additions & 1 deletion apps/marginfi-v2-ui/public/receive_money.svg

This file was deleted.

1 change: 0 additions & 1 deletion apps/marginfi-v2-ui/public/token_swap.svg

This file was deleted.

11 changes: 11 additions & 0 deletions apps/marginfi-v2-ui/src/components/common/icons/PieChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React, { FC, HTMLProps } from "react";

const PieChart: FC<HTMLProps<HTMLDivElement>> = ({ color = "#868E95", ...props }) => (
<div {...props}>
<svg fill={color} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M11 2.04932V12.9999H21.9506C21.4489 18.0533 17.1853 21.9999 12 21.9999C6.47715 21.9999 2 17.5228 2 11.9999C2 6.81459 5.94668 2.55104 11 2.04932ZM13 2.04932C17.7244 2.51839 21.4816 6.27552 21.9506 10.9999H13V2.04932Z" />
</svg>
</div>
);

export { PieChart };
16 changes: 16 additions & 0 deletions apps/marginfi-v2-ui/src/components/common/icons/ReceiveMoney.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { FC, HTMLProps } from "react";

const ReceiveMoney: FC<HTMLProps<HTMLDivElement>> = ({ color = "#868E95", ...props }) => (
<div {...props}>
<svg fill={color} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<g transform="translate(0,0)">
<path
d="M258 21.89c-.5 0-1.2 0-1.8.12-4.6.85-10.1 5.1-13.7 14.81-3.8 9.7-4.6 23.53-1.3 38.34 3.4 14.63 10.4 27.24 18.2 34.94 7.6 7.7 14.5 9.8 19.1 9 4.8-.7 10.1-5.1 13.7-14.7 3.8-9.64 4.8-23.66 1.4-38.35-3.5-14.8-10.4-27.29-18.2-34.94-6.6-6.8-12.7-9.22-17.4-9.22zM373.4 151.4c-11 .3-24.9 3.2-38.4 8.9-15.6 6.8-27.6 15.9-34.2 24.5-6.6 8.3-7.2 14.6-5.1 18.3 2.2 3.7 8.3 7.2 20 7.7 11.7.7 27.5-2.2 43-8.8 15.5-6.7 27.7-15.9 34.3-24.3 6.6-8.3 7.1-14.8 5-18.5-2.1-3.8-8.3-7.1-20-7.5-1.6-.3-3-.3-4.6-.3zm-136.3 92.9c-6.6.1-12.6.9-18 2.3-11.8 3-18.6 8.4-20.8 14.9-2.5 6.5 0 14.3 7.8 22.7 8.2 8.2 21.7 16.1 38.5 20.5 16.7 4.4 32.8 4.3 44.8 1.1 12.1-3.1 18.9-8.6 21.1-15 2.3-6.5 0-14.2-8.1-22.7-7.9-8.2-21.4-16.1-38.2-20.4-9.5-2.5-18.8-3.5-27.1-3.4zm160.7 58.1L336 331.7c4.2.2 14.7.5 14.7.5l6.6 8.7 54.7-28.5-14.2-10zm-54.5.1l-57.4 27.2c5.5.3 18.5.5 23.7.8l49.8-23.6-16.1-4.4zm92.6 10.8l-70.5 37.4 14.5 18.7 74.5-44.6-18.5-11.5zm-278.8 9.1a40.33 40.33 0 0 0-9 1c-71.5 16.5-113.7 17.9-126.2 17.9H18v107.5s11.6-1.7 30.9-1.8c37.3 0 103 6.4 167 43.8 3.4 2.1 10.7 2.9 19.8 2.9 24.3 0 61.2-5.8 69.7-9C391 452.6 494 364.5 494 364.5l-32.5-28.4s-79.8 50.9-89.9 55.8c-91.1 44.7-164.9 16.8-164.9 16.8s119.9 3 158.4-27.3l-22.6-34s-82.8-2.3-112.3-6.2c-15.4-2-48.7-18.8-73.1-18.8z"
fill-opacity="1"
></path>
</g>
</svg>
</div>
);

export { ReceiveMoney };
11 changes: 11 additions & 0 deletions apps/marginfi-v2-ui/src/components/common/icons/TokenSwap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React, { FC, HTMLProps } from "react";

const TokenSwap: FC<HTMLProps<HTMLDivElement>> = ({ color = "#868E95", ...props }) => (
<div {...props}>
<svg fill={color} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M21.5 9C21.5 11.7039 19.849 14.0223 17.5 15.0018L17.5 15C17.5 10.3056 13.6944 6.5 9.00001 6.5L8.99817 6.5C9.97773 4.15105 12.2961 2.5 15 2.5C18.5899 2.5 21.5 5.41015 21.5 9ZM7 3C4.79086 3 3 4.79086 3 7V8.5H5V7C5 5.89543 5.89543 5 7 5H8.5V3H7ZM19 15.5V17C19 18.1046 18.1046 19 17 19H15.5V21H17C19.2091 21 21 19.2091 21 17V15.5H19ZM9 21.5C12.5899 21.5 15.5 18.5899 15.5 15C15.5 11.4101 12.5899 8.5 9 8.5C5.41015 8.5 2.5 11.4101 2.5 15C2.5 18.5899 5.41015 21.5 9 21.5ZM9 12.5L11.5 15L9 17.5L6.5 15L9 12.5Z" />
</svg>
</div>
);

export { TokenSwap };
3 changes: 3 additions & 0 deletions apps/marginfi-v2-ui/src/components/common/icons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./PieChart"
export * from "./ReceiveMoney"
export * from "./TokenSwap"
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { FC, useEffect, useState } from "react";
import { FC, useEffect, useMemo, useState } from "react";
import Link from "next/link";
import Image from "next/image";
import { useUserProfileStore } from "~/store";
import { useRouter } from "next/router";
import { useFirebaseAccount } from "~/hooks/useFirebaseAccount";
import { useWalletContext } from "~/hooks/useWalletContext";
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import { MoreModal } from "./MoreModal";
import { ORDERED_MOBILE_NAVBAR_LINKS } from "~/config/navigationLinks";
import { Apps } from "@mui/icons-material";
import { useSwipeGesture } from "~/hooks/useSwipeGesture";

// @todo implement second pretty navbar row
const MobileNavbar: FC = () => {
Expand All @@ -23,41 +24,46 @@ const MobileNavbar: FC = () => {
fetchPoints(walletAddress.toBase58()).catch(console.error);
}, [fetchPoints, walletAddress]);

const activeLink = useMemo(() => {
const activeLinkIndex = ORDERED_MOBILE_NAVBAR_LINKS.findIndex((link) => link.href === router.pathname);
return activeLinkIndex >= 0 ? `link${activeLinkIndex + 1}` : null;
}, [router.pathname]);

useSwipeGesture(() => setIsMoreModalOpen(true));

return (
<header>
<nav className="fixed w-full bottom-0 h-[68px] z-50 bg-[#0F1111]">
<div className="h-full w-full border-t-2 pb-3 border-[#1C2125] border-solid text-sm font-[500] text-[#868E95] z-50 px-4 flex justify-around items-center z-10 gap-4 lg:gap-8">
<Link
href={"/"}
className={`${router.pathname === "/" ? "hover-underline-static" : "hover-underline-animation"} block`}
>
<Image className="m-auto" src="/receive_money.svg" alt="hand with money icon" width={18.9} height={18.9} />
lend
</Link>

<Link
href={"/swap"}
className={`${router.pathname === "/swap" ? "hover-underline-static" : "hover-underline-animation"}`}
>
<Image className="m-auto" src="/token_swap.svg" alt="coin swap icon" width={18.9} height={18.9} />
swap
</Link>

<Link
href={"/portfolio"}
className={`${router.pathname === "/portfolio" ? "hover-underline-static" : "hover-underline-animation"}`}
>
<Image className="m-auto" src="/pie_chart.svg" alt="pie chart icon" width={18.9} height={18.9} />
portfolio
</Link>

<div className="h-full w-full text-sm font-[500] text-[#868E95] z-50 flex justify-around relative items-center z-10 lg:gap-8">
<div
className="flex flex-col"
className="w-1/4 h-full flex flex-col justify-center items-center"
onClick={() => setIsMoreModalOpen(!isMoreModalOpen)}
>
<MoreHorizIcon className="m-auto w-[18.9px] h-[18.9px]" />
<Apps className="w-[18.9px] h-[18.9px]" />
<div className={`font-aeonik font-[400] text-[#999]`}>
more
</div>
</div>
{ORDERED_MOBILE_NAVBAR_LINKS.map((linkInfo, index) => {
const isActive = activeLink === `link${index + 1}`;
return (
<Link
key={linkInfo.label}
href={linkInfo.href}
className={`w-1/4 h-full flex flex-col justify-center items-center ${
isActive ? "current-mobile-nav-link" : ""
}`}
>
<linkInfo.Icon className="w-[18.9px] h-[18.9px]" color={isActive ? "#DCE85D" : "#999"} />
<div className={`font-aeonik font-[400] ${isActive ? "text-[#DCE85D]" : "text-[#999]"}`}>
{linkInfo.label}
</div>
</Link>
);
})}

<div className={`w-full absolute top-[1px] border-t-[1px] border-[#333]`} />
<div className={`border-slider ${activeLink}`} />
</div>
</nav>
<MoreModal isOpen={isMoreModalOpen} handleClose={() => setIsMoreModalOpen(false)} />
Expand Down
130 changes: 109 additions & 21 deletions apps/marginfi-v2-ui/src/components/mobile/MobileNavbar/MoreModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Close } from "@mui/icons-material";
import { Link, Modal, Slide, Typography } from "@mui/material";
import { ChevronLeft, GitHub, QuestionMark } from "@mui/icons-material";
import { Link, Modal, Slide } from "@mui/material";
import Image from "next/image";
import { useRouter } from "next/router";
import { FC } from "react";
import { NavLinkInfo, ORDERED_MOBILE_LAUNCHER_LINKS } from "~/config/navigationLinks";
import TwitterIcon from "@mui/icons-material/Twitter";
import TelegramIcon from "@mui/icons-material/Telegram";
import AutoStoriesOutlinedIcon from "@mui/icons-material/AutoStoriesOutlined";
import InsightsIcon from "@mui/icons-material/Insights";

interface MoreModalProps {
isOpen: boolean;
Expand All @@ -10,36 +16,118 @@ interface MoreModalProps {

export const MoreModal: FC<MoreModalProps> = ({ isOpen, handleClose }) => {
return (
<Modal open={isOpen} onClose={handleClose} className="border-none">
<Slide direction="up" in={isOpen} mountOnEnter unmountOnExit>
<div className="absolute top-0 left-0 w-full h-[calc(100%)] bg-[#0F1111] p-4">
<div className="flex flex-row justify-between mb-3">
<Modal open={isOpen} onClose={handleClose} className="h-full">
<Slide direction="right" in={isOpen} mountOnEnter unmountOnExit>
<div className="absolute top-0 left-0 w-[70%] h-full bg-[#0F1111] p-4 bg-lines border-r-[1px] border-[#333]">
<div className="h-[40px] flex flex-row justify-between mb-3">
<Link
href={"https://app.marginfi.com"}
className="h-[35.025px] w-[31.0125px] min-h-[35.025px] min-w-[31.0125px] flex justify-center items-center"
>
<Image src="/marginfi_logo.png" alt="marginfi logo" height={35.025} width={31.0125} />
</Link>
<div className="cursor-pointer" onClick={handleClose}>
<Close />
<div className="flex items-center cursor-pointer" onClick={handleClose}>
<ChevronLeft />
</div>
</div>
<div className="flex flex-col gap-2">
<Link href={"/"} className="hover-underline-animation">
<Typography className="font-aeonik font-[400] text-lg">Lend</Typography>
</Link>
<Link href={"/stake"} className="hover-underline-animation">
<Typography className="font-aeonik font-[400] text-lg">Stake</Typography>
</Link>
<Link href={"/swap"} className="hover-underline-animation">
<Typography className="font-aeonik font-[400] text-lg">Swap</Typography>
</Link>
<Link href={"/portfolio"} className="hover-underline-animation">
<Typography className="font-aeonik font-[400] text-lg">Portfolio</Typography>
</Link>
<div className="h-[calc(100%-40px)] flex flex-col justify-between">
<div className="grid grid-cols-2 gap-3 p-7">
{ORDERED_MOBILE_LAUNCHER_LINKS.map((link) => (
<AppLink
key={link.href}
href={link.href}
label={link.label}
alt={link.alt}
Icon={link.Icon}
onClick={handleClose}
/>
))}
</div>
<div className="h-[30px] flex flex-row justify-around items-center">
<Link
href="https://discord.gg/mrgn"
target="_blank"
rel="noopener noreferrer"
className="p-0 m-0 h-full flex justify-center items-center"
>
<svg fill="#868E95" width="23" height="21" className="cursor-pointer hover:fill-[#DCE85D]">
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z"></path>
</svg>
</Link>
<Link
href="https://twitter.com/marginfi"
target="_blank"
rel="noopener noreferrer"
className="p-0 m-0 h-full flex justify-center items-center"
>
<TwitterIcon className="pb-1 text-xl cursor-pointer hover:fill-[#DCE85D] text-[#868E95]" />
</Link>
<Link
href="https://t.me/mrgncommunity"
target="_blank"
rel="noopener noreferrer"
className="p-0 m-0 h-full flex justify-center items-center"
>
<TelegramIcon className="pb-1 text-xl cursor-pointer hover:fill-[#DCE85D] text-[#868E95]" />
</Link>
<Link
href="https://docs.marginfi.com/"
target="_blank"
rel="noopener noreferrer"
className="p-0 m-0 h-full flex justify-center items-center"
>
<AutoStoriesOutlinedIcon className="pb-1 text-xl cursor-pointer hover:fill-[#DCE85D] text-[#868E95]" />
</Link>
<Link
href="https://mrgn.grafana.net/public-dashboards/a2700f1bbca64aeaa5582a90dbaeb276?orgId=1&refresh=1m"
target="_blank"
rel="noopener noreferrer"
className="p-0 m-0 h-full flex justify-center items-center"
>
<InsightsIcon className="pb-1 text-xl cursor-pointer hover:fill-[#DCE85D] text-[#868E95]" />
</Link>
<Link
href="https://github.com/mrgnlabs"
target="_blank"
rel="noopener noreferrer"
className="p-0 m-0 h-full flex justify-center items-center"
>
<GitHub className="pb-1 text-xl cursor-pointer hover:fill-[#DCE85D] text-[#868E95]" />
</Link>
<Link
href="https://marginfi.canny.io/mrgnlend"
target="_blank"
rel="noopener noreferrer"
className="p-0 m-0 h-full flex justify-center items-center"
>
<QuestionMark className="pb-1 text-xl cursor-pointer hover:fill-[#DCE85D] text-[#868E95]" />
</Link>
</div>
</div>
</div>
</Slide>
</Modal>
);
};

const AppLink: FC<NavLinkInfo & { onClick?: () => void }> = ({ href, label, Icon, alt, onClick }) => {
const router = useRouter();
const isActive = router.pathname === href;

return (
<div className="w-full h-[70px]">
<button
onClick={() => {
router.push(href);
if (onClick) onClick();
}}
className={`w-full h-full flex flex-col justify-center items-center rounded-md bg-black border-[1px] ${
isActive ? "border-[#DCE85D]" : "border-[#999]"
}`}
>
<Icon className="w-[20px] h-[20px]" color={isActive ? "#DCE85D" : "#999"} />
<div className={`font-aeonik font-[400] text-sm ${isActive ? "text-[#DCE85D]" : "text-[#999]"}`}>{label}</div>
</button>
</div>
);
};
51 changes: 51 additions & 0 deletions apps/marginfi-v2-ui/src/config/navigationLinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { FC, HTMLProps } from "react";
import {PieChart, ReceiveMoney, TokenSwap} from "~/components/common/icons";

export interface NavLinkInfo {
href: string;
alt: string;
label: string;
Icon: FC<HTMLProps<HTMLDivElement>>;
}

export const ORDERED_MOBILE_NAVBAR_LINKS: NavLinkInfo[] = [
{
href: "/",
alt: "hand with money icon",
label: "lend",
Icon: ReceiveMoney,
},
{
href: "/stake",
alt: "coin swap icon",
label: "stake",
Icon: TokenSwap,
},
{
href: "/portfolio",
alt: "pie chart icon",
label: "portfolio",
Icon: PieChart,
},
];

export const ORDERED_MOBILE_LAUNCHER_LINKS: NavLinkInfo[] = [
{
href: "/",
alt: "hand with money icon",
label: "lend",
Icon: ReceiveMoney,
},
{
href: "/stake",
alt: "coin swap icon",
label: "stake",
Icon: TokenSwap,
},
{
href: "/portfolio",
alt: "pie chart icon",
label: "portfolio",
Icon: PieChart,
},
];
38 changes: 38 additions & 0 deletions apps/marginfi-v2-ui/src/hooks/useSwipeGesture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useState, useEffect } from "react";

export function useSwipeGesture(cb: () => void) {
const [startTouchX, setStartTouchX] = useState<number | null>(null);

useEffect(() => {
const handleTouchStart = (e: TouchEvent) => {
if (e.touches[0].clientX < 20) {
// 20px is an arbitrary width from the left
setStartTouchX(e.touches[0].clientX);
}
};

const handleTouchEnd = (e: TouchEvent) => {
const endTouchX = e.changedTouches[0].clientX;

// If the touch ends more than 50 pixels from where it started, call the callback
if (startTouchX && endTouchX - startTouchX > 50) {
cb();
}

// Reset the starting touch position
setStartTouchX(null);
};

// Attach the event listeners
document.addEventListener("touchstart", handleTouchStart);
document.addEventListener("touchend", handleTouchEnd);

return () => {
// Clean up the event listeners
document.removeEventListener("touchstart", handleTouchStart);
document.removeEventListener("touchend", handleTouchEnd);
};
}, [startTouchX, cb]);

return; // this hook doesn't need to return anything, but you can return values if needed
}
Loading

0 comments on commit 6e4c8fa

Please sign in to comment.