├── .firebaserc ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── postcss.config.js ├── screenshots ├── coinbase-screenshot-1.png ├── coinbase-screenshot-2.png └── coinbase-screenshot-3.png ├── tailwind.config.js ├── firebase.json ├── src ├── routes │ ├── Home.jsx │ ├── Account.jsx │ ├── SignIn.jsx │ └── SingUp.jsx ├── index.js ├── hooks │ └── useDarkMode.js ├── index.css ├── components │ ├── MobileMenu.jsx │ ├── Switcher.jsx │ ├── Trending.jsx │ ├── TrendingItem.jsx │ ├── Navbar.jsx │ ├── Coins.jsx │ ├── CoinItem.jsx │ ├── Footer.jsx │ ├── WatchList.jsx │ └── CoinPage.jsx ├── firebase.js ├── context │ └── AuthContext.jsx ├── App.js └── helper │ └── MobileMenu.css ├── .gitignore ├── .firebase └── hosting.YnVpbGQ.cache ├── package.json └── README.md /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "coinbase-52f8f" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robo311/crypto-app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robo311/crypto-app/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robo311/crypto-app/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /screenshots/coinbase-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robo311/crypto-app/HEAD/screenshots/coinbase-screenshot-1.png -------------------------------------------------------------------------------- /screenshots/coinbase-screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robo311/crypto-app/HEAD/screenshots/coinbase-screenshot-2.png -------------------------------------------------------------------------------- /screenshots/coinbase-screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robo311/crypto-app/HEAD/screenshots/coinbase-screenshot-3.png -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: 'class', 4 | content: [ 5 | "./src/**/*.{js,jsx,ts,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/routes/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Coins from '../components/Coins' 3 | import Trending from '../components/Trending' 4 | 5 | function Home(props) { 6 | return ( 7 |
8 | 9 | 10 |
11 | ) 12 | } 13 | 14 | export default Home -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import { BrowserRouter } from "react-router-dom" 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | src/.env 25 | .env 26 | -------------------------------------------------------------------------------- /src/hooks/useDarkMode.js: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from "react" 2 | 3 | export default function useDarkMode() { 4 | 5 | const [isDarkMode, setDarkMode] = useState(localStorage.theme) 6 | const colorTheme = isDarkMode === "dark" ? "light" : "dark" 7 | 8 | useEffect(()=> { 9 | document.documentElement.classList.remove(colorTheme) 10 | document.documentElement.classList.add(isDarkMode) 11 | 12 | localStorage.setItem("theme", isDarkMode) 13 | }, [isDarkMode, colorTheme]) 14 | 15 | return [colorTheme, setDarkMode] 16 | 17 | } 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | *{ 6 | padding: 0; 7 | margin: 0; 8 | } 9 | 10 | body { 11 | @apply bg-white dark:bg-slate-800 transition-all ease-in duration-300 12 | } 13 | 14 | 15 | .rounded-div{ 16 | @apply border rounded-2xl shadow-xl border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-800 dark:text-white px-6 max-w-[1140px] w-full mx-auto; 17 | } 18 | 19 | .text-color{ 20 | @apply text-black dark:text-white 21 | } 22 | 23 | .sign-btn{ 24 | @apply bg-blue-300 text-black dark:text-white dark:bg-blue-900 rounded-xl w-full py-2 mt-3 25 | } 26 | 27 | .about-text > a{ 28 | @apply dark:text-blue-400 text-blue-700 29 | } 30 | -------------------------------------------------------------------------------- /src/components/MobileMenu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { slide as Menu } from 'react-burger-menu' 3 | import { Link } from "react-router-dom" 4 | import Switcher from './Switcher' 5 | import "../helper/MobileMenu.css" 6 | 7 | function MobileMenu({user, signOut}) { 8 | return ( 9 | 10 | Home 11 | {user?.email && Account} 12 | {user?.email ? ( 13 | ) : ( 14 | 15 | Sign In 16 | )} 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default MobileMenu -------------------------------------------------------------------------------- /src/firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp } from "firebase/app"; 2 | import { getAuth } from "firebase/auth"; 3 | import { getFirestore } from "firebase/firestore" 4 | 5 | const firebaseConfig = { 6 | apiKey: process.env.REACT_APP_FIREBASE_API_KEY, 7 | authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, 8 | projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, 9 | storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, 10 | messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER, 11 | appId: process.env.REACT_APP_FIREBASE_APP_ID, 12 | }; 13 | 14 | const app = initializeApp(firebaseConfig); 15 | export const auth = getAuth(app) 16 | export const db = getFirestore(app) 17 | 18 | export default app; -------------------------------------------------------------------------------- /src/components/Switcher.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useEffect } from 'react'; 3 | import { useState } from "react"; 4 | import { DarkModeSwitch } from "react-toggle-dark-mode"; 5 | import useDarkMode from '../hooks/useDarkMode'; 6 | 7 | function Switcher() { 8 | 9 | const [colorTheme, setTheme] = useDarkMode(); 10 | const [isDarkMode, setDarkMode] = useState( colorTheme === "light" ? true : false); 11 | 12 | const toggleDarkMode = (checked) => { 13 | setTheme(colorTheme) 14 | setDarkMode(checked); 15 | }; 16 | 17 | 18 | return ( 19 |
20 | 26 |
27 | ) 28 | } 29 | 30 | export default Switcher -------------------------------------------------------------------------------- /src/components/Trending.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import axios from "axios" 3 | import TrendingItem from './TrendingItem' 4 | 5 | function Trending() { 6 | 7 | const [data, setData] = useState([]) 8 | const url = "https://api.coingecko.com/api/v3/search/trending" 9 | 10 | useEffect(()=> { 11 | axios.get(url) 12 | .then((response) => { 13 | setData(response.data.coins) 14 | }) 15 | },[url]) 16 | 17 | return ( 18 |
19 |

Trending

20 |
21 | {data.map((coin)=> ( 22 | 23 | ))} 24 |
25 |
26 | ) 27 | } 28 | 29 | export default Trending -------------------------------------------------------------------------------- /src/routes/Account.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Navigate, useNavigate } from 'react-router-dom' 3 | import WatchList from '../components/WatchList' 4 | import {UserAuth} from "../context/AuthContext" 5 | 6 | 7 | 8 | function Account() { 9 | 10 | const {user, logout} = UserAuth() 11 | const navigate = useNavigate() 12 | 13 | const handleSignOut = async () => { 14 | try { 15 | await logout() 16 | navigate("/") 17 | } catch (error) { 18 | console.log(error.message); 19 | } 20 | } 21 | 22 | if (user){ 23 | return ( 24 |
25 |
26 |

Your account

27 |
28 |

Welcome {user?.email}

29 | 30 |
31 |
32 | 33 |
34 | ); 35 | } else { 36 | return 37 | } 38 | 39 | } 40 | 41 | export default Account -------------------------------------------------------------------------------- /src/components/TrendingItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { FaBitcoin } from "react-icons/fa" 3 | import { Link } from "react-router-dom" 4 | 5 | 6 | function TrendingItem({coin}) { 7 | return ( 8 |
9 |
10 |
11 |
12 | {coin.item.id} 13 |
14 | 15 |

{coin.item.name}

16 | 17 |
{coin.item.symbol}
18 |
19 |
20 |
21 | 22 |

{coin.item.price_btc.toFixed(9)}

23 |
24 |
25 |
26 |
27 | ) 28 | } 29 | 30 | export default TrendingItem -------------------------------------------------------------------------------- /.firebase/hosting.YnVpbGQ.cache: -------------------------------------------------------------------------------- 1 | favicon.ico,1662812283150,eae62e993eb980ec8a25058c39d5a51feab118bd2100c4deebb2a9c158ec11f9 2 | logo192.png,1662812298116,3ee59515172ee198f3be375979df15ac5345183e656720a381b8872b2a39dc8b 3 | logo512.png,1662812298409,ee7e2f3fdb8209c4b6fd7bef6ba50d1b9dba30a25bb5c3126df057e1cb6f5331 4 | manifest.json,1662812293176,aff3449bdc238776f5d6d967f19ec491b36aed5fb7f23ccff6500736fd58494a 5 | robots.txt,1662812299010,bfe106a3fb878dc83461c86818bf74fc1bdc7f28538ba613cd3e775516ce8b49 6 | index.html,1663250791902,b0f393084506953d8b552d73822fa359ece49f604c42ffd86bad56127d61d4a3 7 | static/css/main.1be6be75.css,1663250791922,d0ac5473db028095b41ebbad485f0fbb803d95161f0ca0e68dc73d88cfdba204 8 | static/css/main.1be6be75.css.map,1663250791922,c084d688aae03df92ba887582932bfc02265558022e56adbcc9e6904658810cf 9 | asset-manifest.json,1663250791903,a17ea75ee1f70d21337cfb037e1870287f0a15109461d821bd13dd49192e0d0b 10 | static/js/main.56bfb6d7.js.LICENSE.txt,1663250791922,6981a9f377c05c5521e8328d9f6a7065034e978d067f215609671f502288f5df 11 | static/js/main.56bfb6d7.js,1663250791922,c61d5cda44d57e37e4e64b8dd0f2a8eeeb994fa7f172c77246a6a5207ca53048 12 | static/js/main.56bfb6d7.js.map,1663250791925,df6abd750d0e4f4752a0400aafdf9dba4b2678da3673b87aa7e2fbeb67f624e8 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crypto-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^0.27.2", 10 | "firebase": "^9.9.4", 11 | "react": "^18.2.0", 12 | "react-axios": "^2.0.6", 13 | "react-burger-menu": "^3.0.8", 14 | "react-dom": "^18.2.0", 15 | "react-icons": "^4.4.0", 16 | "react-loading": "^2.0.3", 17 | "react-router-dom": "^6.3.0", 18 | "react-scripts": "5.0.1", 19 | "react-sparklines": "^1.7.0", 20 | "react-toggle-dark-mode": "^1.1.0", 21 | "web-vitals": "^2.1.4" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | }, 47 | "devDependencies": { 48 | "autoprefixer": "^10.4.8", 49 | "postcss": "^8.4.16", 50 | "tailwindcss": "^3.1.8" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/context/AuthContext.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useState, useEffect } from "react"; 2 | import { auth, db } from "../firebase" 3 | import { 4 | createUserWithEmailAndPassword, 5 | signInWithEmailAndPassword, 6 | signOut, 7 | onAuthStateChanged } from "firebase/auth"; 8 | import { doc, setDoc } from "firebase/firestore" 9 | 10 | const UserContext = createContext() 11 | 12 | export const AuthContextProvider = ({ children }) => { 13 | 14 | const [user, setUser] = useState({}) 15 | 16 | const signUp = (email, password) => { 17 | createUserWithEmailAndPassword(auth, email, password) 18 | return setDoc(doc(db, "user", email), { 19 | favorites: [], 20 | }) 21 | } 22 | const signIn = (email,password) => { 23 | return signInWithEmailAndPassword(auth, email, password) 24 | } 25 | 26 | const logout = () => { 27 | return signOut(auth) 28 | } 29 | 30 | useEffect(()=>{ 31 | const unsubscribe = onAuthStateChanged(auth, (currentUser) => { 32 | setUser(currentUser) 33 | }) 34 | return () => { 35 | unsubscribe() 36 | } 37 | },[]) 38 | 39 | return ( 40 | 41 | {children} 42 | 43 | ) 44 | } 45 | 46 | export const UserAuth = () => { 47 | return useContext(UserContext) 48 | } 49 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Navbar from "./components/Navbar"; 3 | import { Routes, Route } from "react-router-dom" 4 | import Home from "./routes/Home"; 5 | import SignIn from "./routes/SignIn"; 6 | import { useEffect } from "react"; 7 | import axios from "axios" 8 | import { useState } from "react"; 9 | import CoinPage from "./components/CoinPage"; 10 | import Footer from "./components/Footer"; 11 | import SignUp from "./routes/SingUp"; 12 | import Account from "./routes/Account"; 13 | import { AuthContextProvider } from "./context/AuthContext"; 14 | 15 | 16 | function App() { 17 | const [data, setData] = useState([]) 18 | const url = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=20&page=1&sparkline=true" 19 | 20 | useEffect(()=>{ 21 | axios.get(url) 22 | .then((response) => { 23 | setData(response.data) 24 | }) 25 | },[url]) 26 | 27 | return ( 28 | 29 |
30 | 31 | 32 | }/> 33 | }/> 34 | }/> 35 | }/> 36 | }> 37 | 38 | 39 | 40 |
41 |
42 |
43 | ); 44 | } 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CgProfile } from "react-icons/cg" 3 | import { Link, useNavigate } from "react-router-dom" 4 | import Switcher from './Switcher' 5 | import MobileMenu from './MobileMenu' 6 | import {UserAuth} from "../context/AuthContext" 7 | 8 | function Navbar() { 9 | 10 | const {user, logout} = UserAuth() 11 | const navigate = useNavigate() 12 | 13 | const handleSignOut = async () => { 14 | try { 15 | await logout() 16 | navigate("/signin") 17 | } catch (error) { 18 | console.log(error.message); 19 | } 20 | } 21 | 22 | return ( 23 |
24 | 25 |

Coinbase

26 | 27 |
28 | 29 |
30 |
31 | {user?.email ?( 32 |
33 | 34 | 35 | 36 | 37 |
38 | 39 | ) : ( 40 | 41 | Sign In 42 | )} 43 |
44 |
45 | 46 |
47 |
48 | ) 49 | } 50 | 51 | export default Navbar -------------------------------------------------------------------------------- /src/helper/MobileMenu.css: -------------------------------------------------------------------------------- 1 | /* Position and sizing of burger button */ 2 | .bm-burger-button { 3 | position: relative; 4 | width: 36px; 5 | height: 30px; 6 | right: 25px; 7 | top: 4px; 8 | } 9 | 10 | /* Color/shape of burger icon bars */ 11 | .bm-burger-bars { 12 | @apply dark:bg-white bg-slate-800 13 | } 14 | 15 | /* Color/shape of burger icon bars on hover*/ 16 | .bm-burger-bars-hover {} 17 | 18 | /* Position and sizing of clickable cross button */ 19 | .bm-cross-button { 20 | height: 24px; 21 | width: 24px; 22 | } 23 | 24 | /* Color/shape of close button cross */ 25 | .bm-cross { 26 | background: #bdc3c7; 27 | } 28 | 29 | /* 30 | Sidebar wrapper styles 31 | Note: Beware of modifying this element as it can break the animations - you should not need to touch it in most cases 32 | */ 33 | .bm-menu-wrap { 34 | position: fixed; 35 | top: 0px; 36 | height: 100%; 37 | } 38 | 39 | /* General sidebar styles */ 40 | .bm-menu { 41 | @apply bg-slate-100 dark:bg-slate-600 shadow-xl; 42 | padding: 2.5em 1.5em 0; 43 | font-size: 1.15em; 44 | border-radius: 20px 0px 0px 20px; 45 | } 46 | 47 | /* Morph shape necessary with bubble or elastic */ 48 | .bm-morph-shape { 49 | fill: #373a47; 50 | } 51 | 52 | /* Wrapper for item list */ 53 | .bm-item-list { 54 | @apply dark:text-slate-100 text-slate-800 p-4 55 | } 56 | 57 | /* Individual item */ 58 | .bm-item { 59 | display: inline-block; 60 | @apply py-4; 61 | } 62 | 63 | /* Styling of overlay */ 64 | .bm-overlay { 65 | background: rgba(0, 0, 0, 0.3); 66 | top: 0px; 67 | left: 0px; 68 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crypto website built with ReactJS and Firebase 2 | 3 | - This website let user follow crypto trends 4 | 5 | ## Functionality 6 | 7 | - Functional Register/Login page using Firebase database. 8 | - Website shows most popular crypto currencies and trending currencies via Coingecko API. 9 | - As mentioned above website using Coingecko API. 10 | - User can select certain coins and add them to their watchlist. 11 | - Each coin has designated page with informations/prices/changes. 12 | - Dark Mode / Light Mode. 13 | - Check out live [website](https://coinbase-52f8f.web.app/) 14 | 15 | ## Screenshots 16 | ![screenshot of website 1](https://github.com/robo311/crypto-app/blob/master/screenshots/coinbase-screenshot-1.png) 17 | ![screenshot of website 2](https://github.com/robo311/crypto-app/blob/master/screenshots/coinbase-screenshot-2.png) 18 | ![screenshot of website 3](https://github.com/robo311/crypto-app/blob/master/screenshots/coinbase-screenshot-3.png) 19 | 20 | ## Technologies used 21 | 22 | - ReactJS 23 | - Tailwind 24 | - Firebase 25 | - Axios 26 | - HTML 27 | - CSS 28 | 29 | ## Author 30 | 31 | - GitHub: [@robo311](https://github.com/robo311) 32 | - Discord: [@gatti#7613](https://discord.com) 33 | 34 | ### Additional info 35 | 36 | - Website is fully responsive (using Tailwind). 37 | - Mobile website using hambuger menu. (Fully working) 38 | 39 | ## Updates 40 | 41 | ##### v1.0 42 | - First website release 43 | 44 | ##### v1.1 45 | - Fixed heart button not being visible at light theme 46 | - Added text when watchlist is empty 47 | 48 | ##### v1.2 49 | - Added hamburger menu for smaller devices 50 | - Fixed duplicated coins in watchlist 51 | - Added loading bar at coin page 52 | - Increased number of cryptocurrencies on page (10 -> 20) 53 | 54 | -------------------------------------------------------------------------------- /src/components/Coins.jsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import React, { useEffect, useState } from 'react' 3 | import CoinItem from './CoinItem' 4 | 5 | 6 | function Coins({coins}) { 7 | 8 | const [searchText, setSearchText] = useState("") 9 | 10 | return ( 11 |
12 |
13 | setSearchText(e.target.value)} type="text" className='bg-gray-100 dark:bg-gray-600 rounded-md py-1 indent-3 outline-none' placeholder='Search'/> 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {coins.filter((value)=>{ 31 | if(searchText === ""){ 32 | return value 33 | }else if(value.name.toLowerCase().includes(searchText.toLowerCase())){ 34 | return value 35 | } 36 | }).map((coin)=> ( 37 | 38 | ))} 39 | 40 |
#CoinPrice24h24h VolumeMarket capLast 7 days
41 |
42 | ) 43 | } 44 | 45 | export default Coins -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/CoinItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { AiOutlineHeart } from "react-icons/ai" 3 | import { Sparklines, SparklinesLine } from 'react-sparklines'; 4 | import { Link } from "react-router-dom" 5 | import { db } from "../firebase" 6 | import { UserAuth } from "../context/AuthContext" 7 | import { arrayUnion, doc, updateDoc, onSnapshot } from "firebase/firestore" 8 | 9 | 10 | function CoinItem({coin}) { 11 | 12 | const [savedCoin, setSavedCoin] = useState(false) 13 | const { user } = UserAuth() 14 | 15 | const handleSavedCoin = () => { 16 | setSavedCoin(prevCoin => !prevCoin) 17 | } 18 | 19 | const coinPath = doc(db, "user", `${user?.email}`) 20 | const saveCoin = async () => { 21 | if(user?.email) { 22 | handleSavedCoin() 23 | await updateDoc(coinPath, { 24 | favorites: arrayUnion({ 25 | id: coin.id, 26 | name:coin.name, 27 | image: coin.image, 28 | rank: coin.market_cap_rank, 29 | symbol: coin.symbol, 30 | price: coin.current_price, 31 | }) 32 | }) 33 | }else{ 34 | alert("Please sign in.") 35 | } 36 | } 37 | 38 | return ( 39 | 40 | 41 | {savedCoin ? : } 42 | 43 | {coin.market_cap_rank} 44 | 45 |
46 | {coin.id} 47 |
{coin.name}
48 |
49 | 50 | 51 |
{coin.symbol.toUpperCase()}
52 | 53 | ${coin.current_price.toLocaleString()} 54 | 0 ? "text-green-500" : "text-red-600"}>{coin.price_change_percentage_24h.toFixed(2)}% 55 | ${coin.total_volume.toLocaleString()} 56 | ${coin.market_cap.toLocaleString()} 57 | 58 | 59 | 60 | 61 | 62 | 63 | ) 64 | } 65 | 66 | export default CoinItem -------------------------------------------------------------------------------- /src/routes/SignIn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { AiOutlineMail } from "react-icons/ai" 3 | import { RiLockPasswordLine } from "react-icons/ri" 4 | import { Link, useNavigate } from "react-router-dom" 5 | import {signIn, UserAuth} from "../context/AuthContext" 6 | 7 | function SignIn() { 8 | 9 | const [email, setEmail] = useState("") 10 | const [password, setPassword] = useState("") 11 | const [error, setError] = useState("") 12 | const navigate = useNavigate() 13 | const {signIn} = UserAuth() 14 | 15 | const handleSubmit = async (e) => { 16 | e.preventDefault() 17 | setError("") 18 | try { 19 | await signIn(email, password) 20 | navigate("/account") 21 | } catch (error) { 22 | setError(error.message) 23 | console.log(error.message) 24 | } 25 | } 26 | 27 | return ( 28 |
29 |

Sign in

30 |
31 |
32 | 33 |
34 | setEmail(e.target.value)} className='w-full p-2 border rounded-2xl' required id='email' type="email" placeholder='Enter Email' /> 35 | 36 |
37 |
38 |
39 | 40 |
41 | setPassword(e.target.value)} className='w-full p-2 border rounded-2xl' required id='password' type="password" placeholder='Enter Password' /> 42 | 43 |
44 |
45 | 46 |
47 |

48 | Don't have an account? Register now! 49 |

50 |
51 | ) 52 | } 53 | 54 | export default SignIn -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Switcher from './Switcher' 3 | import {AiOutlineTwitter, AiOutlineInstagram, AiOutlineGithub, AiFillFacebook} from "react-icons/ai" 4 | 5 | function Footer() { 6 | return ( 7 |
8 |
9 |
10 |

Coinbase

11 |

Coinbase is crypto site project made by robo311.
Website is powered by Coingecko API.

12 |
13 |
14 |

About

15 |
    16 |
  • About us
  • 17 |
  • Career
  • 18 |
  • Invest
  • 19 |
  • Contact
  • 20 |
21 |
22 |
23 |

Explore

24 |
    25 |
  • Crypto
  • 26 |
  • Metaverse
  • 27 |
  • NFT
  • 28 |
  • Blog
  • 29 |
30 |
31 |
32 |

Subscribe to our newsletter!

33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 |
46 |
47 |
48 | Made by @robo311. Powered by CoinGecko. 49 |
50 |
51 | ) 52 | } 53 | 54 | export default Footer -------------------------------------------------------------------------------- /src/routes/SingUp.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { AiOutlineMail } from "react-icons/ai" 3 | import { RiLockPasswordLine } from "react-icons/ri" 4 | import { Link, useNavigate } from "react-router-dom" 5 | import {UserAuth} from "../context/AuthContext" 6 | 7 | 8 | function SignUp() { 9 | 10 | const [email, setEmail] = useState("") 11 | const [password, setPassword] = useState("") 12 | const [error, setError] = useState("") 13 | const {signUp} = UserAuth() 14 | const navigate = useNavigate() 15 | 16 | const handleSubmit = async (e) => { 17 | e.preventDefault() 18 | setError("") 19 | try{ 20 | await signUp(email,password) 21 | navigate("/account") 22 | }catch{ 23 | setError(e.message) 24 | console.log(e.message) 25 | } 26 | } 27 | 28 | return ( 29 |
30 |

Register account

31 | {error ?

{error}

: null} 32 |
33 |
34 | 35 |
36 | setEmail(e.target.value)} className='w-full p-2 border rounded-2xl' required id='email' type="email" placeholder='Enter Email' /> 37 | 38 |
39 |
40 |
41 | 42 |
43 | setPassword(e.target.value)} className='w-full p-2 border rounded-2xl' required id='password' type="password" placeholder='Enter Password' /> 44 | 45 |
46 |
47 | 48 |
49 |

50 | Already have an account? Login now! 51 |

52 |
53 | ) 54 | } 55 | 56 | export default SignUp -------------------------------------------------------------------------------- /src/components/WatchList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { UserAuth } from "../context/AuthContext" 3 | import { doc, onSnapshot, updateDoc } from "firebase/firestore" 4 | import { db } from "../firebase" 5 | import { Link } from 'react-router-dom' 6 | import { AiOutlineClose } from "react-icons/ai" 7 | 8 | 9 | function WatchList() { 10 | const [coins, setCoins] = useState([]) 11 | const { user } = UserAuth() 12 | 13 | useEffect(()=>{ 14 | onSnapshot(doc(db,"user", `${user.email}`), (doc)=> { 15 | setCoins(doc.data()?.favorites) 16 | }) 17 | },[user?.email]) 18 | 19 | const coinPath = doc(db, "user", `${user?.email}`) 20 | const deleteCoin = async (id) => { 21 | try{ 22 | const result = coins.filter((item) => item.id !== id) 23 | await updateDoc(coinPath, { 24 | favorites: result 25 | }) 26 | }catch(e){ 27 | console.log(e) 28 | } 29 | } 30 | 31 | 32 | return ( 33 | 34 |
35 |

Watchlist

36 | {coins?.length === 0 ? ( 37 | <> 38 |

You don't have any coins in your watchlist.

39 | Click here to add coins. 40 | 41 | ) : ( 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {coins?.map((coin) => ( 54 | 55 | 56 | 64 | 65 | 68 | 69 | ))} 70 | 71 |
Rank #CoinPriceRemove
{coin?.rank} 57 |
58 | {coin.id} 59 |
60 | {coin?.symbol.toUpperCase()} 61 |
62 |
63 |
${coin?.price.toLocaleString()} 66 | deleteCoin(coin?.id)} className='ml-5 cursor-pointer'/> 67 |
72 |
73 | )} 74 |
75 | 76 | ) 77 | } 78 | 79 | export default WatchList -------------------------------------------------------------------------------- /src/components/CoinPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import axios from "axios" 3 | import {AiFillCaretUp, AiFillCaretDown} from "react-icons/ai" 4 | import { Sparklines, SparklinesLine} from 'react-sparklines'; 5 | import { useParams } from 'react-router-dom'; 6 | import ReactLoading from "react-loading" 7 | 8 | 9 | function CoinPage() { 10 | 11 | const [data, setData] = useState([]) 12 | const params = useParams() 13 | const [loading, setLoading] = useState(false) 14 | 15 | const url = `https://api.coingecko.com/api/v3/coins/${params.coinId}?localization=false&sparkline=true` 16 | 17 | const coinData = async () => { 18 | try{ 19 | await axios.get(url) 20 | .then((response)=>{ 21 | setData(response.data) 22 | }) 23 | setLoading(true) 24 | }catch(error){ 25 | console.error(`ERROR: ${error}`) 26 | } 27 | } 28 | 29 | useEffect(()=>{ 30 | coinData() 31 | setLoading(false) 32 | },[url]) 33 | 34 | return ( 35 |
36 | {loading ? ( 37 |
38 |
39 |
40 | {data.id}/ 41 |

{data.name} ({data.symbol && data.symbol.toUpperCase()})

42 |
43 |
44 |

${data.market_data?.current_price.usd.toLocaleString()}

45 |
46 | {data.market_data?.price_change_percentage_24h > 0 ? : } 47 |

0 ? "text-green-600 font-semibold text-[18px]" : "text-red-600 font-semibold"}>{data.market_data?.price_change_percentage_24h.toFixed(2)}%

48 |
49 |
50 | {data.market_data?.current_price.btc} BTC 51 |
52 | 53 | 54 | 55 |
56 |
57 |
58 |

Info

59 |
60 |
61 | Website 62 |
63 | Official site 64 |
65 |
66 |
67 | Community 68 |
69 | Reddit 70 | Facebook 71 | Twitter 72 | Forum 73 |
74 |
75 |
76 | Source code 77 |
78 | Github 79 |
80 |
81 |
82 |
83 |
84 | ) : (
85 | 86 |
)} 87 | {data.market_data && ( 88 |
89 |
90 |

Market Cap

91 |

${data.market_data.market_cap.usd.toLocaleString()}

92 |
93 |
94 |

24 Hour Trading Volume

95 |

${data.market_data.total_volume.usd.toLocaleString()}

96 |
97 | {data.market_data.fully_diluted_valuation.usd && 98 |
99 |

Fully Diluted Valuation

100 |

${data.market_data.fully_diluted_valuation.usd.toLocaleString()}

101 |
102 | } 103 |
104 |

Circulating Supply

105 |

{data.market_data.circulating_supply.toLocaleString()}

106 |
107 |
108 |

24h High

109 |

${data.market_data.high_24h.usd.toLocaleString()}

110 |
111 |
112 |

24h Low

113 |

${data.market_data.low_24h.usd.toLocaleString()}

114 |
115 |
116 | )} 117 | {loading &&
118 |

About {data.name} ({data.symbol?.toUpperCase()})

119 |
120 |
} 121 |
122 | ) 123 | } 124 | 125 | 126 | 127 | export default CoinPage --------------------------------------------------------------------------------