├── .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 |
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 |

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 | 
17 | 
18 | 
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 | Coin |
21 | |
22 | Price |
23 | 24h |
24 | 24h Volume |
25 | Market cap |
26 | Last 7 days |
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 |
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 | 
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 |
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 |
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 |
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 | | Rank # |
47 | Coin |
48 | Price |
49 | Remove |
50 |
51 |
52 |
53 | {coins?.map((coin) => (
54 |
55 | | {coin?.rank} |
56 |
57 |
58 | 
59 |
60 | {coin?.symbol.toUpperCase()}
61 |
62 |
63 | |
64 | ${coin?.price.toLocaleString()} |
65 |
66 | deleteCoin(coin?.id)} className='ml-5 cursor-pointer'/>
67 | |
68 |
69 | ))}
70 |
71 |
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 |

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 |
66 |
75 |
76 |
Source code
77 |
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
--------------------------------------------------------------------------------