diff --git a/front/package.json b/front/package.json new file mode 100644 index 0000000..b2003ac --- /dev/null +++ b/front/package.json @@ -0,0 +1,40 @@ +{ + "name": "ubj_scoreboard", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.0", + "react-scripts": "5.0.1", + "web-vitals": "^2.1.4", + "web3": "^4.16.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/front/public/index.css b/front/public/index.css new file mode 100644 index 0000000..07bbcc5 --- /dev/null +++ b/front/public/index.css @@ -0,0 +1,10 @@ +:root{ + +} + +body { + background-color: black; + color: aliceblue; + margin: 0; + height: 100vh; +} diff --git a/front/public/index.html b/front/public/index.html new file mode 100644 index 0000000..a67c71e --- /dev/null +++ b/front/public/index.html @@ -0,0 +1,12 @@ + + + + + + + Score Board + + +
+ + diff --git a/front/src/Main.js b/front/src/Main.js new file mode 100644 index 0000000..b6dfd0b --- /dev/null +++ b/front/src/Main.js @@ -0,0 +1,77 @@ +import { Component } from 'react'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; + +import HomePage from './pages/HomePage'; +import IssueToken from './pages/IssueToken'; +import TradeToken from './pages/TradeToken'; +import TradeNFT from './pages/TradeNFT'; +import MintNFT from './pages/MintNFT'; + +import Web3 from 'web3'; + +function DynamicComponent() { + const { menu } = useParams(); + + if (menu === "토큰발행") { + return ; + } else if (menu === "토큰거래") { + return ; + } else if (menu === "NFT발행") { + return ; + } else if (menu === "NFT거래") { + return ; + } + return
Not Found
; +} + +class Main extends Component { + constructor(props) { + super(props); + this.state = { + account: "0x0", + balance: 0, + }; + } + + async componentDidMount() { + await this.loadWeb3(); + await this.loadBlockchainData(); + } + + async loadWeb3() { + if (window.ethereum) { + window.web3 = new Web3(window.ethereum); + await window.ethereum.enable(); + } else if (window.web3) { + window.web3 = new Web3(window.web3.currentProvider); + } else { + window.alert("No ethereum browser detected. You can check out MetaMask!"); + } + } + + async loadBlockchainData() { + const web3 = window.web3; + const accounts = await web3.eth.getAccounts(); + this.setState({ account: accounts[0] }); + console.log("Current Account:", accounts[0]); + const balance = await web3.eth.getBalance(accounts[0]); + + //잔액을 이더로 변환 + const balanceEther = web3.utils.fromWei(balance,'ether'); + this.setState({balance: balanceEther}) + } + + render() { + return ( + + + } /> + } /> + + + ); + } +} + +export default Main; diff --git a/front/src/assets/tokenus_logo.png b/front/src/assets/tokenus_logo.png new file mode 100644 index 0000000..ab62118 Binary files /dev/null and b/front/src/assets/tokenus_logo.png differ diff --git a/front/src/components/App.js b/front/src/components/App.js new file mode 100644 index 0000000..94d7320 --- /dev/null +++ b/front/src/components/App.js @@ -0,0 +1,12 @@ +import { Outlet } from 'react-router-dom'; + +function App() { + return ( + <> +
+ + ); + +} + +export default App; \ No newline at end of file diff --git a/front/src/components/Container.js b/front/src/components/Container.js new file mode 100644 index 0000000..fa2fd4e --- /dev/null +++ b/front/src/components/Container.js @@ -0,0 +1,11 @@ +import './styles/Container.css'; + +function Container({children }) { + return ( +
+ {children} +
+ ); +} + +export default Container; diff --git a/front/src/components/Header.js b/front/src/components/Header.js new file mode 100644 index 0000000..605c8c5 --- /dev/null +++ b/front/src/components/Header.js @@ -0,0 +1,13 @@ +import './styles/Header.css'; + +function Header({ menu }) { + return ( + <> +
+

{menu}

+
+ + ); +} + +export default Header; \ No newline at end of file diff --git a/front/src/components/MenuButton.js b/front/src/components/MenuButton.js new file mode 100644 index 0000000..7e0a787 --- /dev/null +++ b/front/src/components/MenuButton.js @@ -0,0 +1,12 @@ +import "./styles/MenuButton.css" + +function MenuButton({menu}) { + return ( + <> +
+

{menu}

+
+ + ); +} +export default MenuButton; \ No newline at end of file diff --git a/front/src/components/styles/App.css b/front/src/components/styles/App.css new file mode 100644 index 0000000..74b5e05 --- /dev/null +++ b/front/src/components/styles/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/front/src/components/styles/Container.css b/front/src/components/styles/Container.css new file mode 100644 index 0000000..4a38254 --- /dev/null +++ b/front/src/components/styles/Container.css @@ -0,0 +1,13 @@ +.container-col { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.container-row { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} diff --git a/front/src/components/styles/Header.css b/front/src/components/styles/Header.css new file mode 100644 index 0000000..4e3aaa0 --- /dev/null +++ b/front/src/components/styles/Header.css @@ -0,0 +1,31 @@ +.header { + width: 100%; + height: 10vh; + display: flex; + align-items: center; + background-color: rgb(255, 255, 255); +} + +.header img { + width: 3.2vw; +} + +.header p { + display: flex; + justify-content: center; + align-items: center; + margin: 0; + white-space: nowrap; +} + +.logo { + flex: 1; +} + + +.menu { + flex: 1; + font-size: 3.2vw; + font-weight: 900; + color: black; +} diff --git a/front/src/components/styles/MenuButton.css b/front/src/components/styles/MenuButton.css new file mode 100644 index 0000000..b4bf03c --- /dev/null +++ b/front/src/components/styles/MenuButton.css @@ -0,0 +1,18 @@ +.menu-btn { + display: flex; + justify-content: center; + align-items: center; + + height: 4.5rem; + + font-size: 2vw; + + border-radius: 10px; + background-color: #D9D9D9; + color: black; +} + +.menu-btn:hover { + background-color:tomato; + color:aliceblue; +} diff --git a/front/src/index.js b/front/src/index.js new file mode 100644 index 0000000..c5bc287 --- /dev/null +++ b/front/src/index.js @@ -0,0 +1,6 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Main from './Main'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(
); \ No newline at end of file diff --git a/front/src/pages/HomePage.js b/front/src/pages/HomePage.js new file mode 100644 index 0000000..9e05e6f --- /dev/null +++ b/front/src/pages/HomePage.js @@ -0,0 +1,44 @@ + +import { Link } from 'react-router-dom'; +import Container from '../components/Container'; +import MenuButton from '../components/MenuButton'; +import logo from '../assets/tokenus_logo.png'; +import './styles/HomePage.css' + + +function MenuButtons() { + const menus = ['토큰발행', '토큰거래', 'NFT발행', 'NFT거래']; + return ( +
    + {menus.map((menu) => ( +
  • + + + +
  • + ))} +
+ ); +} + + +function HomePage({ account, balance }) { + + return ( + <> +
+ +
+

TokenUs

+ 로고 +
+

Connected Account : {account}

+

잔액 : {balance} ETH

+ +
+
+ + ); +} + +export default HomePage; \ No newline at end of file diff --git a/front/src/pages/IssueToken.js b/front/src/pages/IssueToken.js new file mode 100644 index 0000000..cadd72a --- /dev/null +++ b/front/src/pages/IssueToken.js @@ -0,0 +1,101 @@ +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import Web3 from "web3"; +import Container from "../components/Container"; +import Header from "../components/Header"; +import "./styles/IssueToken.css"; + +// 컨트랙트 ABI 및 주소 +import ChannelToken from "../contracts/Channel.json"; + +function IssueToken() { + const { menu } = useParams(); + + const [tokenName, setTokenName] = useState(""); + const [supplyAmount, setSupplyAmount] = useState(""); + const [loading, setLoading] = useState(false); + + const handleTokenNameChange = (e) => { + setTokenName(e.target.value); + }; + + const handleSupplyAmountChange = (e) => { + setSupplyAmount(e.target.value); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!tokenName || !supplyAmount) { + alert("토큰 이름과 발행량을 입력해주세요."); + return; + } + + try { + setLoading(true); + + // Web3 인스턴스 생성 + const web3 = new Web3(window.ethereum); + await window.ethereum.request({ method: "eth_requestAccounts" }); + const accounts = await web3.eth.getAccounts(); + + // 컨트랙트 인스턴스 생성 + const networkId = await web3.eth.net.getId(); + const contractAddress = ChannelToken.networks[networkId]; + const tokenContract = new web3.eth.Contract(ChannelToken.abi, contractAddress); + + // 토큰 발행 호출 + const result = await tokenContract.methods + .mint(tokenName, supplyAmount) + .send({ from: accounts[0] }); + + console.log("토큰 발행 성공:", result); + alert("토큰 발행이 성공적으로 완료되었습니다!"); + } catch (error) { + console.error("토큰 발행 오류:", error); + alert("토큰 발행 중 오류가 발생했습니다."); + } finally { + setLoading(false); + } + }; + + return ( + <> + +
+ +
+
+
+ + +
+ +
+ + +
+ + +
+
+ + + ); +} + +export default IssueToken; diff --git a/front/src/pages/MintNFT.js b/front/src/pages/MintNFT.js new file mode 100644 index 0000000..f806e2e --- /dev/null +++ b/front/src/pages/MintNFT.js @@ -0,0 +1,146 @@ +import React, { useState } from "react"; +import { useParams } from "react-router-dom"; +import Web3 from "web3"; +import Container from "../components/Container"; +import Header from "../components/Header"; + +import VideoNFT from "../contracts/VideoNFT.json"; + +function VideoUpload() { + const { menu } = useParams(); + + const [videoFile, setVideoFile] = useState(null); + const [videoPreview, setVideoPreview] = useState(null); + const [minting, setMinting] = useState(false); + const [NFTname, setNFTname] = useState(""); + const [NFTsymbol, setNFTsymbol] = useState(""); + const [totalSupply, setTotalSupply] = useState(1); + + const handleFileChange = (e) => { + const file = e.target.files[0]; + if (file && file.type.startsWith("video/")) { + setVideoFile(file); + setVideoPreview(URL.createObjectURL(file)); + } else { + alert("영상 파일만 업로드할 수 있습니다."); + } + }; + + const handleUpload = async () => { + if (!videoFile) { + alert("업로드할 파일을 선택해주세요."); + return; + } + if (!NFTname || !NFTsymbol) { + alert("NFT 이름과 심볼을 입력해주세요."); + return; + } + + try { + if (window.ethereum) { + const web3 = new Web3(window.ethereum); + await window.ethereum.request({ method: "eth_requestAccounts" }); + + const accounts = await web3.eth.getAccounts(); + const account = accounts[0]; + + const networkId = await web3.eth.net.getId(); + const deployedNetwork = VideoNFT.networks[networkId]; + // const deployedNetwork = '0xCeF932F016Df7895EBe53977791669f0228a7Dba'; + console.log(networkId); + + if (!deployedNetwork) { + alert("스마트 컨트랙트가 현재 네트워크에 배포되지 않았습니다."); + return; + } + + const contract = new web3.eth.Contract( + VideoNFT.abi, + deployedNetwork.address + ); + + setMinting(true); + + const metadataURI = 'https://TokenUs.s3.us-west-2.amazonaws.com/550e8400-e29b-41d4-a716-446655440000.mp4' + // NFT 발행 트랜잭션 호출 + await contract.methods + .mintVideoNFT(metadataURI, totalSupply, NFTname, NFTsymbol) + .send({ from: account }); + + alert("NFT가 성공적으로 발행되었습니다!"); + setMinting(false); + } else { + alert("MetaMask가 설치되어 있지 않습니다."); + } + } catch (error) { + console.error("NFT 발행 오류:", error); + alert("NFT 발행 중 오류가 발생했습니다."); + setMinting(false); + } + }; + + return ( + +
+
+ + + {videoPreview && ( +
+

미리보기:

+
+ )} + + setNFTname(e.target.value)} + style={{ display: "block", margin: "10px auto", width: "100%" }} + /> + setNFTsymbol(e.target.value)} + style={{ display: "block", margin: "10px auto", width: "100%" }} + /> + setTotalSupply(e.target.value)} + min="1" + style={{ display: "block", margin: "10px auto", width: "100%" }} + /> + + +
+ + ); +} + +export default VideoUpload; diff --git a/front/src/pages/TradeNFT.js b/front/src/pages/TradeNFT.js new file mode 100644 index 0000000..3419e26 --- /dev/null +++ b/front/src/pages/TradeNFT.js @@ -0,0 +1,18 @@ +import { useParams } from "react-router-dom"; +import Container from "../components/Container"; +import Header from "../components/Header"; + +function TradeNFT() { + const { menu } = useParams(); + +return ( + <> + +
+ + + +) +} + +export default TradeNFT; \ No newline at end of file diff --git a/front/src/pages/TradeToken.js b/front/src/pages/TradeToken.js new file mode 100644 index 0000000..24a1891 --- /dev/null +++ b/front/src/pages/TradeToken.js @@ -0,0 +1,63 @@ +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import Container from "../components/Container"; +import Header from "../components/Header"; + +function TradeToken() { + const { menu } = useParams(); + + // const [receiverAddress, setReceiverAddress] = useState(""); + const [amount, setAmount] = useState(""); + const [account, setAccount] = useState(null); + + // const handleReceiverAddressChange = (e) => { + // setReceiverAddress(e.target.value); + // }; + + const handleAmountChange = (e) => { + setAmount(e.target.value); + }; + + //토큰 전송 컨트랙트 연결 + const handleSubmit = async (e) =>{ + + } + + return ( + <> + +
+
+ {/*
+ + +
*/} + +
+ + +
+

1 CNL = 0.01 TUS

+ + +
+ + {account &&

현재 연결된 계정: {account}

} + + + ); +} + +export default TradeToken; \ No newline at end of file diff --git a/front/src/pages/styles/HomePage.css b/front/src/pages/styles/HomePage.css new file mode 100644 index 0000000..978c299 --- /dev/null +++ b/front/src/pages/styles/HomePage.css @@ -0,0 +1,49 @@ +li { + list-style: none; +} + +a { + text-decoration: none; +} + +.align-row { + display: flex; + flex-direction: row; + justify-content: center; +} + +.align-col { + display: flex; + flex-direction: column; + align-items: center; +} + +.title { + margin-top: 10%; +} + +.title p { + margin: 0; + font-size: 6.3vw; + color: aliceblue; + font-weight: 500; +} + +.buttons { + display: flex; + align-items: center; + justify-content: space-around; + width: 85%; + padding: 0; + margin-top: 5%; +} + +.btn { + padding: 0 3rem; + width: 15%; +} + +.account, .balance{ + font-size: 20px; + font-weight: bold; +} \ No newline at end of file diff --git a/front/src/pages/styles/IssueToken.css b/front/src/pages/styles/IssueToken.css new file mode 100644 index 0000000..1a9fbf8 --- /dev/null +++ b/front/src/pages/styles/IssueToken.css @@ -0,0 +1,8 @@ +input, label { + font-size: 30px; +} + +button { + width: 120px; + height: 40px; +} \ No newline at end of file