Skip to content

Commit

Permalink
create basket
Browse files Browse the repository at this point in the history
  • Loading branch information
hckrg committed Jul 17, 2023
1 parent 7309bd9 commit ef6e9dc
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 18 deletions.
4 changes: 4 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module">
import { Buffer } from "buffer";
window.Buffer = Buffer;
</script>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
"@mui/x-data-grid": "^6.10.0",
"@onflow/fcl": "^1.4.1",
"@vitejs/plugin-react": "^4.0.3",
"Buffer": "^0.0.0",
"axios": "^1.4.0",
"buffer": "^6.0.3",
"classnames": "^2.3.2",
"elliptic": "^6.5.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.1",
"react-toastify": "^9.1.3",
"recoil": "^0.7.7"
},
"devDependencies": {
Expand Down
16 changes: 14 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,32 @@ import { atom, useRecoilState } from 'recoil'
import AppRoutes from './Routes'
import { useEffect } from 'react'
import * as fcl from '@onflow/fcl'
import { FTDetailType, getFTDetails } from './cadence/scripts/GetFTDetails'

export const userAtom = atom<{loggedIn: boolean, addr?: string} | null>({
key: 'userAtom',
default: null
})

export const ftTokens = atom<FTDetailType>({
key: 'ftTokens',
default: undefined
})

function App() {
// for any wrappers required on the entire app
const setUser = useRecoilState(userAtom)[1]
const [user, setUser] = useRecoilState(userAtom)
const setFTTokens = useRecoilState(ftTokens)[1]

useEffect(() => {
(async () => {
const data = await getFTDetails()
setFTTokens(data)
})()
fcl.currentUser.subscribe(setUser)
}, [])


// TODO
return (
<AppRoutes />
)
Expand Down
2 changes: 1 addition & 1 deletion src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function AppRoutes() {
Component={ExploreDashboard}
/>
<Route
path='/basket'
path='/basket/:contractName'
Component={ExploreBasket}
/>
<Route
Expand Down
84 changes: 84 additions & 0 deletions src/components/TokenLogoUpload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useCallback, useRef, useState } from "react";
import { useRecoilState } from "recoil";
import { handleUploadFileToIPFS } from "../utils/uploadFileToIpfs";

interface TokenLogoUploadProps {
onChange: (imageUrl: string) => void
}

function isValidUrl(url: string | undefined) {
// Regular expression for matching a URL
const urlRegex = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i;

if (!url) return false
// Test if the URL matches the regex
return urlRegex.test(url);
}

const TokenLogoUpload: React.FC<TokenLogoUploadProps> = ({onChange}) => {
const fileInputRef = useRef<HTMLInputElement>(null);
const [imageUrl, setImageUrl] = useState("https://assets.deform.cc/default/logo15.png")

const handlePhotoUpload = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files && event.target.files[0];
if (file) {
console.log("file", file)

try {
const ipfsImageHash = await handleUploadFileToIPFS(file, new Date().toTimeString())
const url = `https://ipfs.io/ipfs/${ipfsImageHash}`
console.log(url)
setImageUrl(url)
onChange(url)
} catch (error) {
console.log(error)
}
}
}, []);

const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
};

// const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
// event.preventDefault();
// const file = event.dataTransfer.files && event.dataTransfer.files[0];
// if (file) {
// setformImage(file);
// }
// };

const handleProfileClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};

return (
<div className="w-full flex items-center" >
<div
className="h-20 w-20 bg-white rounded-full relative"
// onDrop={handleDrop}
onDragOver={handleDragOver}
>
<div className={`h-20 w-20 rounded-full hover:opacity-80 overflow-hidden cursor-pointer`}>
<img
src={imageUrl}
alt="Profile"
className="h-20 w-20 rounded-full"
onClick={handleProfileClick}
/>
</div>
<input
type="file"
accept="image/*"
className="hidden"
ref={fileInputRef}
onChange={handlePhotoUpload}
/>
</div>
</div>
);
};

export default TokenLogoUpload;
4 changes: 4 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import { RecoilRoot } from 'recoil'
import './flowConfig'
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RecoilRoot>
<ToastContainer/>
<App />
</RecoilRoot>
</React.StrictMode>,
Expand Down
22 changes: 14 additions & 8 deletions src/pages/CreateBasket.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Dashboard_Navbar from '../components/Dashboard_Navbar'
import { allTokens, getTokenBySymbol } from '../utils/getTokenDetails'
import { TokenIcon } from './ExploreBasket'
import ToggleSwitch from '../components/ToggleSwitch'
import TokenLogoUpload from '../components/TokenLogoUpload'
import { AllTokens } from '../utils/constants'

export type tokenDetails = {
tokenName?: string,
Expand All @@ -20,20 +22,20 @@ function CreateBasket() {

const handleTokenDetailsChange = (key: keyof tokenDetails, value: string) => {
setTokenDetails((prev) => {
if (prev) return {...prev, [key]: value};
if (prev && Object.keys(prev).length) return {...prev, [key]: value};
else return {[key]: value}
})
}

console.log(selectedTokens, selectedTokenValues)
console.log(tokenDetails, selectedTokens, selectedTokenValues)

return (
<div className="flex flex-col min-h-screen items-center bg-custom-400">
<Dashboard_Navbar navbarShadow />
<div className='flex flex-col items-center pb-10 md:w-2/3 w-5/6 bg-white-100 rounded-xl my-48 font-mono'>
<div className='flex w-full justify-between items-center p-10'>
<div className='text-custom-600 text-2xl font-medium'>Create Basket</div>
<button onClick={() => { }} className="bg-custom-500 text-white-100 font-bold py-2 px-6 rounded-lg cursor-pointer min-w-[140px]">Publish</button>
<button onClick={() => {}} className="bg-custom-500 text-white-100 font-bold py-2 px-6 rounded-lg cursor-pointer min-w-[140px]">Publish</button>
</div>
<hr className='bg-custom-400 w-5/6' />
<div className='flex flex-col w-full gap-4 items-center'>
Expand Down Expand Up @@ -65,25 +67,29 @@ function CreateBasket() {
className="rounded-md w-full text-base px-6 py-4 focus:outline-none border-b-[1px] border-gray-200"
/>
</div>
<div className='flex items-center justify-center w-5/6 gap-4'>
<div className='w-full'>Token Logo : </div>
<TokenLogoUpload onChange={(url) => handleTokenDetailsChange("tokenIcon", url)} />
</div>
<hr className='bg-custom-600 my-8 w-5/6' />
<div className='text-custom-600 font-medium text-lg'>Choose Underlying Tokens</div>
<div className='text-gray-600 text-sm'>We source tokens from increment.fi </div>
<div className='flex flex-col items-center w-5/6'>
{
data.underlyingTokens.map(s => getTokenBySymbol(s)).map((token) => (
Object.entries(AllTokens).map(([key, token]) => (
<div className="flex justify-between w-full py-6 px-10 ">
<TokenIcon logo={token?.logo} symbol={token?.name} />
<ToggleSwitch
id={token?.symbol!}
checked={selectedTokens.includes(token?.symbol!)}
onChange={() => selectedTokens.includes(token?.symbol!) ? setSelectedTokens((prev) => prev.filter(t => t!=token?.symbol)) : setSelectedTokens((prev) => [...prev, token?.symbol!])}
checked={selectedTokens.includes(key)}
onChange={() => selectedTokens.includes(key) ? setSelectedTokens((prev) => prev.filter(t => t!=key)) : setSelectedTokens((prev) => [...prev, key])}
/>
{
selectedTokens.includes(token?.symbol!) &&
selectedTokens.includes(key) &&
<input
placeholder={`${token?.symbol} value`}
value={selectedTokenValues[token?.symbol!] ?? ""}
onChange={(e) => setSelectedTokenValues((prev) => ({...prev, [token?.symbol!]: e.target.value}))}
onChange={(e) => setSelectedTokenValues((prev) => ({...prev, [key]: e.target.value}))}
className="rounded-md text-base px-2 focus:outline-none border-b-[1px] border-gray-200"
/>
}
Expand Down
36 changes: 36 additions & 0 deletions src/utils/uploadFileToIpfs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import axios from "axios";
import FormData from "form-data";
import { pinataApiKey, pinataApiSecret } from "./constants";

export const handleUploadFileToIPFS = async (file: File, postID: string) : Promise<string | null> => {
const formData = new FormData();
formData.append("file", file);
const metadata = JSON.stringify({
name: postID,
});
formData.append("pinataMetadata", metadata);

const options = JSON.stringify({
cidVersion: 0,
});
formData.append("pinataOptions", options);

try {
const res = await axios.post(
"https://api.pinata.cloud/pinning/pinFileToIPFS",
formData,
{
maxBodyLength: -1,
headers: {
"Content-Type": `multipart/form-data`,
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataApiSecret,
},
}
);
return res.data.IpfsHash;
} catch (error) {
console.log(error);
}
return null
};
14 changes: 8 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1035,11 +1035,6 @@
"@babel/plugin-transform-react-jsx-source" "^7.22.5"
react-refresh "^0.14.0"

Buffer@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/Buffer/-/Buffer-0.0.0.tgz#82cf8e986a2109ff6d1d6f1c436e47d07127aea4"
integrity sha512-+zdncl8lI5TCkARStn9F1BwcuJYofYmD0oEHe5FNfCvGfeDJwf6+dSikCdQN6BMXXmHMhNNUagBN367WST1AIQ==

abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
Expand Down Expand Up @@ -1259,7 +1254,7 @@ classnames@^2.3.2:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==

clsx@^1.2.1:
clsx@^1.1.1, clsx@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
Expand Down Expand Up @@ -2372,6 +2367,13 @@ [email protected]:
dependencies:
"@remix-run/router" "1.7.1"

react-toastify@^9.1.3:
version "9.1.3"
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.3.tgz#1e798d260d606f50e0fab5ee31daaae1d628c5ff"
integrity sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==
dependencies:
clsx "^1.1.1"

react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
Expand Down

0 comments on commit ef6e9dc

Please sign in to comment.