Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp UI #26

Merged
merged 24 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/nextjs/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { getTargetNetwork } from "~~/utils/scaffold-eth";
*/
export const Footer = () => {
return (
<div className="min-h-0 p-5 mb-11 lg:mb-0">
<div className="min-h-0 p-5 lg:mb-0">
<div className="fixed flex justify-between items-center w-full z-20 p-4 bottom-0 left-0 pointer-events-none">
<SwitchTheme className="pointer-events-auto" />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React, { useEffect, useState } from "react";
import Image from "next/image";
import LogoSvg from "../../../public/assets/flashbotRecovery/logo.svg";
import { CustomButton } from "../CustomButton/CustomButton";
import { ManualAssetSelection } from "../ManualAssetSelection/ManualAssetSelection";
import styles from "./assetSelectionStep.module.css";
import { motion } from "framer-motion";
import { useAutodetectAssets } from "~~/hooks/flashbotRecoveryBundle/useAutodetectAssets";
import { RecoveryTx } from "~~/types/business";

interface IProps {
isVisible: boolean;
hackedAddress: string;
safeAddress: string;
onSubmit: (txs: RecoveryTx[]) => void;
}
export const AssetSelectionStep = ({ isVisible, onSubmit, hackedAddress, safeAddress }: IProps) => {
const [isAddingManually, setIsAddingManually] = useState(false);
const { getAutodetectedAssets } = useAutodetectAssets();
const [selectedAssets, setSelectedAssets] = useState<number[]>([]);
const [accountAssets, setAccountAssets] = useState<RecoveryTx[]>([]);
const [isLoading, setIsLoading] = useState(true);
const onAssetSelected = (index: number) => {
const currentIndex = selectedAssets.indexOf(index);
let newAssets: number[] = [];
if (currentIndex === -1) {
newAssets.push(index);
newAssets.push(...selectedAssets);
} else {
newAssets = selectedAssets.filter(item => item !== index);
}
setSelectedAssets(newAssets);
};

useEffect(() => {
if (accountAssets.length > 0 || !isVisible) {
return;
}
init();
}, [isVisible]);

const init = async () => {
const result = await getAutodetectedAssets({ hackedAddress, safeAddress });
if (!result) {
return;
}
setAccountAssets(result);
setIsLoading(false);
};

const onAddAssetsClick = () => {
const txsToAdd = accountAssets.filter((item, i) => selectedAssets.indexOf(i) != -1);
onSubmit(txsToAdd);
};
if (!isVisible) {
return <></>;
}
return (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className={styles.container}>
<ManualAssetSelection
safeAddress={safeAddress}
hackedAddress={hackedAddress}
isVisible={isAddingManually}
close={() => setIsAddingManually(false)}
addAsset={item => {
setAccountAssets(current => [...current, item])
setIsAddingManually(false)
}}
/>
<h2 className={styles.title}>Your assets</h2>

<div className={styles.assetList}>
{!!isLoading
? [1, 2, 3].map((item, i) => (
<AssetItem
isLoading={true}
isSelected={selectedAssets.indexOf(i) != -1}
key={i}
onClick={() => onAssetSelected(i)}
/>
))
: accountAssets.map((item, i) => {
return (
<AssetItem
tx={item}
isLoading={false}
isSelected={selectedAssets.indexOf(i) != -1}
key={i}
onClick={() => onAssetSelected(i)}
/>
);
})}
</div>
<CustomButton type="btn-accent" text={"Add Manually"} onClick={() => setIsAddingManually(true)} />
<div className="m-2"></div>
<CustomButton type="btn-primary" text={"Continue"} onClick={() => onAddAssetsClick()} />
</motion.div>
);
};

interface IAssetProps {
onClick: () => void;
isSelected: boolean;
tx?: RecoveryTx;
isLoading: boolean;
}

const AssetItem = ({ onClick, isSelected, tx, isLoading }: IAssetProps) => {
const getSubtitleTitle = () => {
if (!tx) {
return "";
}
if (["erc1155", "erc721"].indexOf(tx.type) != -1) {
//@ts-ignore
return `Token ID: ${tx.tokenId!}`;
}
if (tx.type === "erc20") {
//@ts-ignore
return tx.value;
}
if (tx.type === "custom") {
//@ts-ignore
return tx.info.split(" to ")[1]
}
return "";
};
const getTitle = () => {
if (!tx) {
return "";
}
if (tx.type === "erc20") {
//@ts-ignore
return tx.symbol;
}
if (tx.type === "custom") {
//@ts-ignore
return tx.info.split(" to ")[0]
}
return tx.info;
};
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
onClick={() => onClick()}
className={`${isSelected ? "bg-base-200" : ""} ${styles.assetItem} ${isLoading ? styles.loading : ""}`}
>
<div className={`${styles.logoContainer}`}>
<Image className={styles.logo} src={LogoSvg} alt="" />
</div>
<div className={`${styles.data}`}>
<h3>{getTitle()}</h3>
<span>{getSubtitleTitle()}</span>
</div>
</motion.div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
.container{
width: 100%;

margin-bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.title{
font-size: 23px;
font-style: normal;
font-weight: 600;
line-height: 100%;
margin: 0;
}
.assetItem{
display: flex;
height: 92px;
max-width: 458px;
margin: 0 auto;
border-radius: 12px;
cursor: pointer;
margin-bottom: 12px;
}
.data{
display: flex;
flex-direction: column;
justify-content: center;
}
.assetList{
width: 100%;
max-height: calc(100vh - 400px);
height: calc(100vh - 400px);
overflow: auto;
margin: 36px 0;
}

.logo{
width: 60px;
height: 60px;
margin-right: 16px;
}

.logoContainer{
display: flex;
flex-direction: column;
justify-content: center;
margin-left:16px;
}

.loader{
height: 94px;
display: flex;
justify-content: center;
align-items: center;
}

.loading span{
background-color:#3a4862;
width: 100px;
height: 24px;
}
.loading h3{
background-color:#3a4862;
width: 130px;
height: 24px;
}

.loading{
animation: loading 1s infinite;
}

@keyframes loading {
from {opacity: 0.3;}
to {opacity: 0.8;}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useEffect } from "react";
import Image from "next/image";
import IllustrationSvg from "../../../public/assets/flashbotRecovery/logo.svg"
import styles from "./connectStep.module.css"
import { RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
import { AnimatePresence, motion } from "framer-motion";
interface IProps{
isVisible:boolean
safeAddress:string;
address:string;
setSafeAddress: (newAdd:string) => void
}
export const ConnectStep = ({isVisible, safeAddress, address, setSafeAddress}:IProps) => {


useEffect(() => {
if (!!safeAddress || !address) {
return;
}
setSafeAddress(address);
return () => {};
}, [address]);


return <AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={styles.container}
>
<h1 className={styles.title}>Welcome to Flashbot Recovery</h1>
<Image className={styles.illustration} src={IllustrationSvg} alt="" />
<p className={`${styles.text} text-secondary-content`}>
Connect your "Safe Wallet" where the assets will be send after the recovery process.
</p>
<div className={styles.buttonContainer}>
<RainbowKitCustomConnectButton/>
</div>
</motion.div>
)}
</AnimatePresence>
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.container{
max-width: 580px;
margin: 20px;
margin-bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.title{
text-align: center;
font-size: 24px;
font-style: normal;
font-weight: 700;
line-height: 32px;
}
.illustration{
width: 250px;
height: 250px;
margin: 40px 0;
}
.text{
font-size: 18px;
font-style: normal;
font-weight: 400;
font-size: 18px;
line-height: 32px;
text-align: center;
}
.buttonContainer button{
display: flex;

height: 40px;
padding: 0px 48px;
}
.buttonContainer > div > div >div{
display: none;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";
import styles from "./customButton.module.css";

interface IProps {
text: string;
onClick: () => void;
disabled?: boolean;
type: "btn-accent" | "btn-primary";
}
export const CustomButton = ({ text, onClick, disabled = false, type }: IProps) => {
return (
<button disabled={disabled} className={`${styles.button} btn ${type} btn-xs`} onClick={() => onClick()}>
{text}
</button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.button{
width: 250px;
margin: 0 auto;
padding: 0px 48px;
height: 40px;
font-size: 16px;
font-style: normal;
font-weight: 700;
line-height: normal;
border: 1px solid;
}
.button:hover{
border: 1px solid;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react'
import styles from "./customheader.module.css"
import Image from 'next/image'
import LogoSvg from "../../../public/assets/flashbotRecovery/logo.svg"

export const CustomHeader = () => {
return (
<div className={styles.header}>
<div className={styles.logoContainer}>
<Image className={styles.logo} src={LogoSvg} alt="" />
Recovery Flashbot
</div>
</div>
)
}
Loading