├── .gitignore ├── .DS_Store ├── .babelrc ├── client ├── styles │ ├── application.scss │ ├── _Video.scss │ ├── _IngredientsPopup.scss │ ├── _TestComponent.scss │ └── _App.scss ├── src │ ├── test.js │ └── index.js ├── templates │ ├── test.html │ └── index.html └── components │ ├── VideoModal.js │ ├── TestComponent.js │ ├── IngredientsPopup.js │ └── App.js ├── README.md ├── server └── server.js ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adeebbayat/Solo-Project/HEAD/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /client/styles/application.scss: -------------------------------------------------------------------------------- 1 | @import '_TestComponent'; 2 | @import '_App'; 3 | @import '_IngredientsPopup'; 4 | @import '_Video' 5 | -------------------------------------------------------------------------------- /client/styles/_Video.scss: -------------------------------------------------------------------------------- 1 | #videoButton { 2 | position: absolute; 3 | bottom:61px; 4 | left:395px; 5 | } 6 | 7 | #videoClose{ 8 | margin-bottom: 20px; 9 | } -------------------------------------------------------------------------------- /client/styles/_IngredientsPopup.scss: -------------------------------------------------------------------------------- 1 | #ingredientsButton { 2 | position: absolute; 3 | left:207px; 4 | top:695px; 5 | } 6 | 7 | #modalDiv { 8 | display:flex; 9 | justify-content: space-between; 10 | } 11 | -------------------------------------------------------------------------------- /client/src/test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from 'react-dom'; 3 | import {TestComponent} from '../components/TestComponent' 4 | import style from '../styles/application.scss' 5 | render(,document.getElementById('root')); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is my first solo project in CodeSmith 2 | 3 | Frontend 4 | HTML 5 | CSS 6 | SASS 7 | JS 8 | React 9 | Babel 10 | 11 | Server 12 | Node 13 | Express 14 | 15 | 16 | Total App 17 | webpack 18 | package.json - npm init -y 19 | .gitignore 20 | -------------------------------------------------------------------------------- /client/templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Build Tools Challenge 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Build Tools Challenge 5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /client/styles/_TestComponent.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(255, 158, 229); 3 | } 4 | 5 | #login { 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | margin-top: 200px; 10 | } 11 | 12 | #form { 13 | display:flex; 14 | flex-direction: column; 15 | gap:20px; 16 | align-items: center; 17 | justify-content: center; 18 | } 19 | 20 | #submit{ 21 | width:150px; 22 | height:30px; 23 | box-shadow: 5px 5px 5px black; 24 | border-radius: 20px; 25 | } 26 | 27 | #enterName { 28 | 29 | font-size: 30pt; 30 | } -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from 'react-dom' 3 | import App from "../components/App" 4 | import IngredientsPopup from "../components/IngredientsPopup"; 5 | import VideoModal from "../components/VideoModal" 6 | import style from '../styles/application.scss' 7 | 8 | const url = new URL(window.location.href) 9 | const params = new URLSearchParams(url.search) 10 | const name = params.get('name') 11 | 12 | if (!name || window.location.href === 'http://localhost:8080/recipe?name=null') window.location.href = "http://localhost:8080/login" 13 | render(,document.getElementById('root')); 14 | render(,document.getElementById('popup')); 15 | render(,document.getElementById('video')) -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path') 3 | const PORT = 3000; 4 | const app = express(); 5 | 6 | 7 | app.use(express.json()); 8 | app.use(express.urlencoded()); 9 | 10 | 11 | app.get('/login', (req,res) => { 12 | res.sendFile(path.resolve(__dirname,'../build/test.html')) 13 | }) 14 | 15 | app.post('/login',(req,res) => { 16 | 17 | const {name} = req.body 18 | res.redirect(`http://localhost:8080/recipe?name=${name}`) 19 | }) 20 | 21 | 22 | app.get('/recipe*',(req,res) => { 23 | 24 | res.sendFile(path.resolve(__dirname,'../build/index.html')) 25 | }) 26 | 27 | app.use((req,res) => { 28 | res.status(404).send('These aren\'t the droids you\'re looking for.'); 29 | }) 30 | // app.get('/homepage',(req,res) => { 31 | // res.sendFile(path.resolve(__dirname,'../build/test.html')) 32 | // }) 33 | app.listen(PORT) 34 | 35 | module.exports = app; -------------------------------------------------------------------------------- /client/styles/_App.scss: -------------------------------------------------------------------------------- 1 | $margin: 200px; 2 | 3 | #topBar { 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | gap:50px; 8 | margin-bottom: 30px; 9 | } 10 | 11 | .button { 12 | height:30px; 13 | width:150px; 14 | background-color: pink; 15 | box-shadow: 2px 2px 2px black; 16 | } 17 | 18 | #nameAndRecipe { 19 | width:700px; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | } 24 | 25 | #recipeName { 26 | font-size: 20pt; 27 | margin-top: -10px; 28 | } 29 | 30 | #img { 31 | width: 450px; 32 | height: 450px; 33 | margin-left: $margin; 34 | } 35 | 36 | #recipeText { 37 | margin-right: $margin; 38 | } 39 | 40 | #imgAndText { 41 | display:flex; 42 | gap:20px; 43 | } 44 | 45 | #heartDiv { 46 | position: relative; 47 | left:650px; 48 | bottom:20px; 49 | } 50 | 51 | #text { 52 | position: relative; 53 | bottom:100px; 54 | } 55 | 56 | #title{ 57 | text-align: center; 58 | margin-bottom: -10px; 59 | font-size: 40pt; 60 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solo-project", 3 | "version": "1.0.0", 4 | "description": "This is my first solo project in CodeSmith", 5 | "main": "./client/src/index.js", 6 | "scripts": { 7 | "start": "nodemon server/server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "webpack", 10 | "dev": "webpack-dev-server --hot --progress --color --open" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "express": "^4.18.3", 17 | "mongodb": "^6.4.0", 18 | "mongoose": "^8.2.1", 19 | "react": "^18.2.0", 20 | "react-animated-heart": "^0.0.8", 21 | "react-dom": "^18.2.0", 22 | "react-modal": "^3.16.1", 23 | "react-youtube": "^10.1.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.24.0", 27 | "@babel/preset-env": "^7.24.0", 28 | "@babel/preset-react": "^7.23.3", 29 | "babel-loader": "^9.1.3", 30 | "css-loader": "^6.10.0", 31 | "html-webpack-plugin": "^5.6.0", 32 | "sass": "^1.71.1", 33 | "sass-loader": "^14.1.1", 34 | "style-loader": "^3.3.4", 35 | "webpack": "^5.90.3", 36 | "webpack-cli": "^5.1.4", 37 | "webpack-dev-server": "^4.5.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/components/VideoModal.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react" 2 | import Modal from "react-modal" 3 | import YouTube from "react-youtube" 4 | 5 | class VideoModal extends Component{ 6 | constructor(props) { 7 | super(props) 8 | this.state = { 9 | modalOpen:false, 10 | videoId:'' 11 | } 12 | 13 | this.style = { 14 | content: { 15 | backgroundColor: 'rgb(255, 158, 229)', 16 | width: '650px', 17 | height: '420px', 18 | top: '50%', 19 | left: '50%', 20 | marginRight: '-50%', 21 | transform: 'translate(-50%, -50%)', 22 | }, 23 | } 24 | } 25 | handleClick() { 26 | const videoId = document.querySelector('#hiddenVideo').value 27 | this.setState(prevState => ({ 28 | modalOpen:!prevState.modalOpen, 29 | videoId: videoId 30 | }))} 31 | 32 | 33 | render() { 34 | 35 | return( 36 | <> 37 | 38 | 39 | 40 | {this.state.modalOpen && } 41 | 42 | 43 | ) 44 | } 45 | 46 | } 47 | 48 | export default VideoModal -------------------------------------------------------------------------------- /client/components/TestComponent.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | 3 | class TestComponent extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | value: null 8 | } 9 | } 10 | 11 | handleChange(e){ 12 | this.setState({ 13 | value: e.target.value 14 | }) 15 | } 16 | 17 | async handleSubmit(e) { 18 | e.preventDefault() 19 | const response = await fetch('http://localhost:8080/login', 20 | { 21 | method:'POST', 22 | headers:{ 23 | 'Content-Type': 'application/json' 24 | }, 25 | body:JSON.stringify({ 26 | name: this.state.value 27 | }) 28 | } 29 | ) 30 | 31 | if (response.redirected && this.state.value) { 32 | // Use window.location.href to navigate to the redirected URL 33 | window.location.href = response.url; 34 | } else { 35 | console.log(this.state.value); 36 | } 37 | console.log(this.state.value) 38 | } 39 | 40 | 41 | render() { 42 | return( 43 |
44 |
this.handleSubmit(event)} method="post" > 45 |
46 | 49 | this.handleChange(event)} type="text"/> 50 | 51 |
52 |
53 |
54 | ) 55 | } 56 | } 57 | 58 | export {TestComponent}; -------------------------------------------------------------------------------- /client/components/IngredientsPopup.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react" 2 | import Modal from "react-modal" 3 | class IngredientsPopup extends Component { 4 | constructor(props){ 5 | super(props) 6 | this.state = { 7 | modalOpen: false 8 | } 9 | 10 | this.style = { 11 | content: { 12 | backgroundColor: 'rgb(255, 158, 229)', 13 | width: '400px', 14 | height: '600px', 15 | top: '50%', 16 | left: '50%', 17 | marginRight: '-50%', 18 | transform: 'translate(-50%, -50%)', 19 | }, 20 | } 21 | } 22 | 23 | handleClick() { 24 | this.setState(prevState => ({ 25 | modalOpen:!prevState.modalOpen 26 | }) 27 | ) 28 | } 29 | 30 | 31 | render() { 32 | 33 | const listOfIngredients = document.querySelector('#hidden').value.split(',') 34 | const length = listOfIngredients.length 35 | 36 | return( 37 | <> 38 | 39 | 40 |
41 |

Ingredients

42 | 43 |
44 | {Array.from({ length }, (_, i) => ( 45 |

{`Ingredient ${i + 1}:`} {`${listOfIngredients[i]}`}

46 | ))} 47 | 48 |
49 | 50 | 51 | ) 52 | } 53 | } 54 | 55 | export default IngredientsPopup -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: { 6 | index: './client/src/index.js', 7 | test: './client/src/test.js' 8 | }, 9 | 10 | output: { 11 | path: path.resolve(__dirname,'./build'), 12 | filename: '[name].bundle.js' 13 | }, 14 | 15 | devServer: { 16 | host:'localhost', 17 | port:8080, 18 | hot:true, 19 | 20 | proxy: { 21 | '/**' : { 22 | target: 'http://localhost:3000/', 23 | secure:false 24 | } 25 | }, 26 | }, 27 | devtool: 'eval-source-map', 28 | mode: 'production', 29 | performance: { 30 | hints: false, 31 | maxEntrypointSize: 512000, 32 | maxAssetSize: 512000 33 | }, 34 | 35 | plugins: [ 36 | new HtmlWebpackPlugin({ 37 | template:'./client/templates/index.html', 38 | filename: 'index.html', 39 | chunks: ['index'] 40 | }), 41 | new HtmlWebpackPlugin({ 42 | template:'./client/templates/test.html', 43 | filename: 'test.html', 44 | chunks: ['test'] 45 | }) 46 | ], 47 | 48 | module: { 49 | rules: [ 50 | { 51 | test: /\.jsx?/, 52 | exclude: /node_modules/, 53 | use: { 54 | loader: 'babel-loader', 55 | options: { 56 | presets: [ 57 | '@babel/preset-env', 58 | '@babel/preset-react' 59 | ] 60 | } 61 | } 62 | }, 63 | { 64 | test: /\.s[ac]ss$/i, 65 | use: [ 66 | // Creates `style` nodes from JS strings 67 | "style-loader", 68 | // Translates CSS into CommonJS 69 | "css-loader", 70 | // Compiles Sass to CSS 71 | "sass-loader", 72 | ], 73 | }, 74 | ] 75 | } 76 | } -------------------------------------------------------------------------------- /client/components/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import Heart from "react-animated-heart"; 3 | import YouTube from "react-youtube"; 4 | 5 | 6 | class App extends Component { 7 | 8 | constructor(props) { 9 | super(props) 10 | this.state = { 11 | menu: null, 12 | didLoad: false, 13 | isClick: false, 14 | } 15 | } 16 | 17 | 18 | 19 | 20 | fetchData() { 21 | fetch('https://www.themealdb.com/api/json/v1/1/random.php') 22 | .then((response) => response.json()) 23 | .then((data) => { 24 | const actualIngredients = []; let i = 1; 25 | 26 | while (data.meals[0][`strIngredient${i}`] !== "") { 27 | actualIngredients[i - 1] = data.meals[0][`strIngredient${i}`] 28 | i++ 29 | } 30 | 31 | document.querySelector('#hidden').value = actualIngredients; 32 | 33 | const videoUrl = data.meals[0].strYoutube; 34 | const videoId = videoUrl.split('v=')[1] || videoUrl.split('youtu.be/')[1]; 35 | 36 | document.querySelector('#hiddenVideo').value = videoId 37 | 38 | this.setState({ 39 | menu: data, 40 | didLoad:true, 41 | isClick:false 42 | }) 43 | 44 | }) 45 | .catch((err) => console.log(err)) 46 | } 47 | 48 | 49 | componentDidMount(){ 50 | this.fetchData(); 51 | } 52 | 53 | handleClick = () => { 54 | this.setState(prevState => ({ 55 | isClick: !prevState.isClick 56 | })); 57 | } 58 | 59 | 60 | render() { 61 | 62 | 63 | 64 | return( 65 | <> 66 | 67 | 68 |

Egg-straordinaryRecipes.com

69 |
70 | 71 |
72 |

Hello {this.props.name}!

73 |

Here is your recipe: {this.state.didLoad && this.state.menu 74 | ? `${this.state.menu.meals[0].strMeal}` 75 | : 'Loading...'}

76 |
77 | 78 |
79 | 80 |
81 | 82 |
83 | 84 |
85 |
86 | this.handleClick()}/> 87 |
88 |
89 |

Origin: {this.state.didLoad && this.state.menu ? `${this.state.menu.meals[0].strArea}`: 'Loading...'}

90 |

Category: {this.state.didLoad && this.state.menu ? `${this.state.menu.meals[0].strCategory}`: 'Loading...'}

91 |

Instructions: {this.state.didLoad && this.state.menu ? `${this.state.menu.meals[0].strInstructions}`: 'Loading...'}

92 |
93 |
94 |
95 | 96 | ) 97 | } 98 | } 99 | 100 | export default App --------------------------------------------------------------------------------