├── LICENSE ├── README.md ├── backend ├── .gitignore ├── app.py ├── models.py ├── requirements.txt ├── routes.py └── wsgi.py └── frontend ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public ├── explode.png ├── python.png ├── react.png └── vite.svg ├── src ├── App.jsx ├── components │ ├── CreateUserModal.jsx │ ├── EditModal.jsx │ ├── Navbar.jsx │ ├── UserCard.jsx │ └── UserGrid.jsx ├── dummy │ └── dummy.js └── main.jsx └── vite.config.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Burak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Friend Store 🚀 2 | 3 | Build a Friend Store with Python(Flask) and JavaScript(React) 4 | 5 | ![Demo App](https://i.ibb.co/S6Xgb6c/Group-98.png) 6 | 7 | [Video Tutorial on Youtube](https://youtu.be/tWHXaSC2T_s) 8 | 9 | ### Table of Contents 10 | 11 | - ⚙️ Tech Stack: Python, React, SQLite, SQLAlchemy, Flask, Chakra UI 12 | - ✅ CRUD Functionality: Seamlessly create, read, update, and delete friends from your store. 13 | - 🔒 Best Practices: Utilizes best practices such as creating virtual environments (venv) for a clean and isolated development environment. 14 | - 🌐 Deployment: Deployed on Render for free. 15 | - 🎨 Stylish UI Components: Enhanced user experience with stylish UI components provided by Chakra UI. 16 | - 🌓 Light and Dark Mode: Enjoy a personalized user interface experience with light and dark mode options. 17 | - 📱 Responsive Design: The app is designed to adapt to various screen sizes, ensuring a consistent experience across devices. 18 | 19 | ### Run the App Locally 20 | 21 | 1. Clone the repository: 22 | 23 | ```bash 24 | git clone https://github.com/burakorkmez/react-python-tutorial 25 | ``` 26 | 27 | 2. Navigate to the project directory: 28 | 29 | ```bash 30 | cd react-python-tutorial 31 | ``` 32 | 33 | 3. Navigate to the backend directory: 34 | 35 | ```bash 36 | cd backend 37 | ``` 38 | 39 | 4. Create a virtual environment: 40 | 41 | - On macOS and Linux: 42 | 43 | ```bash 44 | python3 -m venv venv 45 | ``` 46 | 47 | - On Windows: 48 | 49 | ```bash 50 | python -m venv venv 51 | ``` 52 | 53 | 5. Activate the virtual environment: 54 | 55 | - On macOS and Linux: 56 | 57 | ```bash 58 | source venv/bin/activate 59 | ``` 60 | 61 | - On Windows: 62 | 63 | ```bash 64 | venv\Scripts\activate 65 | ``` 66 | 67 | 6. Install the dependencies: 68 | 69 | - On macOS and Linux: 70 | 71 | ```bash 72 | pip3 install -r requirements.txt 73 | ``` 74 | 75 | - On Windows: 76 | 77 | ```bash 78 | pip install -r requirements.txt 79 | ``` 80 | 81 | 7. Navigate to the frontend directory: 82 | 83 | ```bash 84 | cd ../frontend 85 | ``` 86 | 87 | 8. Install the dependencies: 88 | 89 | ```bash 90 | npm install 91 | ``` 92 | 93 | 9. Build the frontend: 94 | 95 | ```bash 96 | npm run build 97 | ``` 98 | 99 | 10. Navigate to the backend directory: 100 | 101 | ```bash 102 | cd ../backend 103 | ``` 104 | 105 | 11. Run the Flask app: 106 | 107 | ```bash 108 | flask run 109 | ``` 110 | 111 | 12. Open your browser and go to `http://localhost:5000/` to view the app. 112 | 113 | 13. Don't forget to Like && Subscribe 🚀 114 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Virtual environment 2 | venv/ 3 | venv*/ 4 | .Python 5 | bin/ 6 | include/ 7 | lib/ 8 | lib64/ 9 | pip-selfcheck.json 10 | 11 | # IDE files 12 | .vscode/ 13 | .idea/ 14 | *.iml 15 | *.pyc 16 | 17 | # Environment variables 18 | .env 19 | 20 | # Database 21 | friends.db 22 | 23 | # Compiled Python files 24 | *.pyc 25 | 26 | # Dependency directories 27 | node_modules/ 28 | /dist/ 29 | -------------------------------------------------------------------------------- /backend/app.py: -------------------------------------------------------------------------------- 1 | # TODO: UPDATE THIS FILE FOR DEPLOYMENT 2 | from flask import Flask, send_from_directory 3 | from flask_sqlalchemy import SQLAlchemy 4 | from flask_cors import CORS 5 | import os 6 | 7 | app = Flask(__name__) 8 | 9 | # We can comment this CORS config for the production because we are running the frontend and backend on the same server 10 | # CORS(app) 11 | 12 | app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///friends.db" 13 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 14 | 15 | db = SQLAlchemy(app) 16 | 17 | frontend_folder = os.path.join(os.getcwd(),"..","frontend") 18 | dist_folder = os.path.join(frontend_folder,"dist") 19 | 20 | # Server static files from the "dist" folder under the "frontend" directory 21 | @app.route("/",defaults={"filename":""}) 22 | @app.route("/") 23 | def index(filename): 24 | if not filename: 25 | filename = "index.html" 26 | return send_from_directory(dist_folder,filename) 27 | 28 | # api routes 29 | import routes 30 | 31 | with app.app_context(): 32 | db.create_all() 33 | 34 | if __name__ == "__main__": 35 | app.run(debug=True) -------------------------------------------------------------------------------- /backend/models.py: -------------------------------------------------------------------------------- 1 | from app import db 2 | 3 | class Friend(db.Model): 4 | id = db.Column(db.Integer, primary_key=True) 5 | name = db.Column(db.String(100), nullable=False) 6 | role = db.Column(db.String(50), nullable=False) 7 | description = db.Column(db.Text, nullable=False) 8 | gender = db.Column(db.String(10), nullable=False) 9 | img_url = db.Column(db.String(200), nullable=True) 10 | 11 | 12 | def to_json(self): 13 | return { 14 | "id":self.id, 15 | "name":self.name, 16 | "role":self.role, 17 | "description":self.description, 18 | "gender":self.gender, 19 | "imgUrl":self.img_url, 20 | } 21 | 22 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burakorkmez/react-python-tutorial/c3eb786db5067edffe31a8cd6e6fa2cdc44e4677/backend/requirements.txt -------------------------------------------------------------------------------- /backend/routes.py: -------------------------------------------------------------------------------- 1 | from app import app, db 2 | from flask import request, jsonify 3 | from models import Friend 4 | 5 | # Get all friends 6 | @app.route("/api/friends",methods=["GET"]) 7 | def get_friends(): 8 | friends = Friend.query.all() 9 | result = [friend.to_json() for friend in friends] 10 | return jsonify(result) 11 | 12 | # Create a friend 13 | @app.route("/api/friends",methods=["POST"]) 14 | def create_friend(): 15 | try: 16 | data = request.json 17 | 18 | # Validations 19 | required_fields = ["name","role","description","gender"] 20 | for field in required_fields: 21 | if field not in data or not data.get(field): 22 | return jsonify({"error":f'Missing required field: {field}'}), 400 23 | 24 | name = data.get("name") 25 | role = data.get("role") 26 | description = data.get("description") 27 | gender = data.get("gender") 28 | 29 | # Fetch avatar image based on gender 30 | if gender == "male": 31 | img_url = f"https://avatar.iran.liara.run/public/boy?username={name}" 32 | elif gender == "female": 33 | img_url = f"https://avatar.iran.liara.run/public/girl?username={name}" 34 | else: 35 | img_url = None 36 | 37 | new_friend = Friend(name=name, role=role, description=description, gender= gender, img_url=img_url) 38 | 39 | db.session.add(new_friend) 40 | db.session.commit() 41 | 42 | return jsonify(new_friend.to_json()), 201 43 | 44 | except Exception as e: 45 | db.session.rollback() 46 | return jsonify({"error":str(e)}), 500 47 | 48 | # Delete a friend 49 | @app.route("/api/friends/",methods=["DELETE"]) 50 | def delete_friend(id): 51 | try: 52 | friend = Friend.query.get(id) 53 | if friend is None: 54 | return jsonify({"error":"Friend not found"}), 404 55 | 56 | db.session.delete(friend) 57 | db.session.commit() 58 | return jsonify({"msg":"Friend deleted"}), 200 59 | except Exception as e: 60 | db.session.rollback() 61 | return jsonify({"error":str(e)}),500 62 | 63 | # Update a friend profile 64 | @app.route("/api/friends/",methods=["PATCH"]) 65 | def update_friend(id): 66 | try: 67 | friend = Friend.query.get(id) 68 | if friend is None: 69 | return jsonify({"error":"Friend not found"}), 404 70 | 71 | data = request.json 72 | 73 | friend.name = data.get("name",friend.name) 74 | friend.role = data.get("role",friend.role) 75 | friend.description = data.get("description",friend.description) 76 | friend.gender = data.get("gender",friend.gender) 77 | 78 | db.session.commit() 79 | return jsonify(friend.to_json()),200 80 | except Exception as e: 81 | db.session.rollback() 82 | return jsonify({"error":str(e)}),500 83 | 84 | -------------------------------------------------------------------------------- /backend/wsgi.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | if __name__ == "__main__": 4 | app.run() 5 | 6 | # Gunicorn and WSGI (Web Server Gateway Interface) are both components used in deploying and serving Python web applications, particularly those built with web frameworks like Flask and Django. -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:react/recommended", 7 | "plugin:react/jsx-runtime", 8 | "plugin:react-hooks/recommended", 9 | ], 10 | ignorePatterns: ["dist", ".eslintrc.cjs"], 11 | parserOptions: { ecmaVersion: "latest", sourceType: "module" }, 12 | settings: { react: { version: "18.2" } }, 13 | plugins: ["react-refresh"], 14 | rules: { 15 | "react/jsx-no-target-blank": "off", 16 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 17 | "react/prop-types": "off", 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@chakra-ui/react": "^2.8.2", 14 | "@emotion/react": "^11.11.4", 15 | "@emotion/styled": "^11.11.5", 16 | "framer-motion": "^11.1.9", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-icons": "^5.2.1" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^18.2.66", 23 | "@types/react-dom": "^18.2.22", 24 | "@vitejs/plugin-react": "^4.2.1", 25 | "eslint": "^8.57.0", 26 | "eslint-plugin-react": "^7.34.1", 27 | "eslint-plugin-react-hooks": "^4.6.0", 28 | "eslint-plugin-react-refresh": "^0.4.6", 29 | "vite": "^5.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/public/explode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burakorkmez/react-python-tutorial/c3eb786db5067edffe31a8cd6e6fa2cdc44e4677/frontend/public/explode.png -------------------------------------------------------------------------------- /frontend/public/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burakorkmez/react-python-tutorial/c3eb786db5067edffe31a8cd6e6fa2cdc44e4677/frontend/public/python.png -------------------------------------------------------------------------------- /frontend/public/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/burakorkmez/react-python-tutorial/c3eb786db5067edffe31a8cd6e6fa2cdc44e4677/frontend/public/react.png -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { Container, Stack, Text } from "@chakra-ui/react"; 2 | import Navbar from "./components/Navbar"; 3 | import UserGrid from "./components/UserGrid"; 4 | import { useState } from "react"; 5 | 6 | // updated this after recording. Make sure you do the same so that it can work in production 7 | export const BASE_URL = import.meta.env.MODE === "development" ? "http://127.0.0.1:5000/api" : "/api"; 8 | 9 | function App() { 10 | const [users, setUsers] = useState([]); 11 | 12 | return ( 13 | 14 | 15 | 16 | 17 | 25 | 26 | My Besties 27 | 28 | 🚀 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /frontend/src/components/CreateUserModal.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Flex, 4 | FormControl, 5 | FormLabel, 6 | Input, 7 | Modal, 8 | ModalBody, 9 | ModalCloseButton, 10 | ModalContent, 11 | ModalFooter, 12 | ModalHeader, 13 | ModalOverlay, 14 | Radio, 15 | RadioGroup, 16 | Textarea, 17 | useDisclosure, 18 | useToast, 19 | } from "@chakra-ui/react"; 20 | import { useState } from "react"; 21 | import { BiAddToQueue } from "react-icons/bi"; 22 | import { BASE_URL } from "../App"; 23 | 24 | const CreateUserModal = ({ setUsers }) => { 25 | const { isOpen, onOpen, onClose } = useDisclosure(); 26 | const [isLoading, setIsLoading] = useState(false); 27 | const [inputs, setInputs] = useState({ 28 | name: "", 29 | role: "", 30 | description: "", 31 | gender: "", 32 | }); 33 | const toast = useToast(); 34 | 35 | const handleCreateUser = async (e) => { 36 | e.preventDefault(); // prevent page refresh 37 | setIsLoading(true); 38 | try { 39 | const res = await fetch(BASE_URL + "/friends", { 40 | method: "POST", 41 | headers: { 42 | "Content-Type": "application/json", 43 | }, 44 | body: JSON.stringify(inputs), 45 | }); 46 | 47 | const data = await res.json(); 48 | if (!res.ok) { 49 | throw new Error(data.error); 50 | } 51 | 52 | toast({ 53 | status: "success", 54 | title: "Yayy! 🎉", 55 | description: "Friend created successfully.", 56 | duration: 2000, 57 | position: "top-center", 58 | }); 59 | onClose(); 60 | setUsers((prevUsers) => [...prevUsers, data]); 61 | 62 | setInputs({ 63 | name: "", 64 | role: "", 65 | description: "", 66 | gender: "", 67 | }); // clear inputs 68 | } catch (error) { 69 | toast({ 70 | status: "error", 71 | title: "An error occurred.", 72 | description: error.message, 73 | duration: 4000, 74 | }); 75 | } finally { 76 | setIsLoading(false); 77 | } 78 | }; 79 | 80 | return ( 81 | <> 82 | 85 | 86 | 87 | 88 |
89 | 90 | My new BFF 😍 91 | 92 | 93 | 94 | 95 | {/* Left */} 96 | 97 | Full Name 98 | setInputs({ ...inputs, name: e.target.value })} 102 | /> 103 | 104 | 105 | {/* Right */} 106 | 107 | Role 108 | setInputs({ ...inputs, role: e.target.value })} 112 | /> 113 | 114 | 115 | 116 | 117 | Description 118 |