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 (
+ <>
+
+ >
+ );
+}
+
+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 (
+ <>
+
+ >
+ );
+}
+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 (
+
+
+
+
+ );
+}
+
+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 (
+ <>
+
+
+
+
+ {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