├── .github └── workflows │ └── release.yaml ├── .gitignore ├── LICENSE ├── README.md ├── backend ├── .dockerignore ├── .env.example ├── .gitignore ├── Dockerfile ├── package-lock.json ├── package.json └── src │ ├── constants.js │ ├── index.js │ ├── middlewares │ ├── cors.js │ └── index.js │ ├── routes │ ├── download.js │ ├── index.js │ └── upload.js │ ├── server.js │ └── utils │ ├── initiate-resources.js │ ├── load-env.js │ └── remove-images.js ├── docker-compose.yml ├── docs └── images │ ├── example-video.gif │ ├── logo.png │ ├── screenshot.png │ ├── vallezw-Image-Uploader-dark.png │ └── vallezw-Image-Uploader-light.png └── frontend ├── .env ├── .gitignore ├── Dockerfile ├── README.md ├── conf └── conf.d │ ├── default.conf │ └── gzip.conf ├── env-config 2.js ├── env.sh ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo.png ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── Components ├── ComponentUtils │ ├── Css │ │ ├── Background.css │ │ ├── Footer.css │ │ ├── Image.css │ │ ├── Image.scss │ │ └── Navbar.css │ ├── Footer.js │ ├── Headers │ │ └── Navbar.js │ ├── ImageUploadCard.js │ ├── LoadingAnimation │ │ ├── Loading.js │ │ └── animation.json │ ├── SocialMedia │ │ ├── Css │ │ │ └── SocialMedia.css │ │ └── SocialMediaShareButtons.js │ ├── StyledDropzone.js │ ├── UploadButton.js │ └── UploadedImage.js └── Pages │ ├── 404Page.js │ ├── Css │ ├── 404Page.css │ └── UploadedImagePage.css │ ├── UploadPage.js │ └── UploadedImagePage.js ├── Images ├── 404.png ├── 404.svg ├── 404_blue.svg ├── 404_green.svg ├── Backgrounds │ ├── bg1.svg │ ├── bg2.svg │ ├── bg3.svg │ ├── bg4.svg │ ├── bg5.svg │ └── bg6.svg ├── background.svg ├── going_up.png ├── going_up.svg ├── going_up_rm.png └── logo.png ├── Utils └── sendRequest.js └── index.js /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Create Release 8 | jobs: 9 | dockerBackend: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v1 17 | 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v1 20 | 21 | - name: Login to DockerHub 22 | uses: docker/login-action@v1 23 | with: 24 | username: ${{ secrets.DOCKERHUB_USERNAME }} 25 | password: ${{ secrets.DOCKERHUB_TOKEN }} 26 | 27 | - name: Get the version 28 | id: get_version 29 | run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3) 30 | 31 | - name: Build and push 32 | uses: docker/build-push-action@v2 33 | with: 34 | context: ./backend 35 | push: true 36 | tags: vallezw/image-uploader-backend:${{ steps.get_version.outputs.VERSION }}, vallezw/image-uploader-backend:latest 37 | 38 | dockerFrontend: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v2 43 | 44 | - name: Set up QEMU 45 | uses: docker/setup-qemu-action@v1 46 | 47 | - name: Set up Docker Buildx 48 | uses: docker/setup-buildx-action@v1 49 | 50 | - name: Login to DockerHub 51 | uses: docker/login-action@v1 52 | with: 53 | username: ${{ secrets.DOCKERHUB_USERNAME }} 54 | password: ${{ secrets.DOCKERHUB_TOKEN }} 55 | 56 | - name: Get the version 57 | id: get_version 58 | run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3) 59 | 60 | - name: Build and push 61 | uses: docker/build-push-action@v2 62 | with: 63 | context: ./frontend 64 | push: true 65 | tags: vallezw/image-uploader-client:${{ steps.get_version.outputs.VERSION }}, vallezw/image-uploader-client:latest 66 | 67 | 68 | createRelease: 69 | name: Create Release 70 | needs: [dockerFrontend, dockerBackend] 71 | runs-on: ubuntu-latest 72 | steps: 73 | - name: Checkout code 74 | uses: actions/checkout@v2 75 | - name: Create Release 76 | id: create_release 77 | uses: actions/create-release@v1 78 | env: 79 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 80 | with: 81 | tag_name: ${{ github.ref }} 82 | release_name: Release ${{ github.ref }} 83 | draft: false 84 | prerelease: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .upload/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Valentin Zwerschke 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 | 2 |
3 |

4 | 5 | Logo 6 | 7 | 8 |

Image Uploader

9 | 10 |

11 | An open source, self hosted image uploader 12 |
13 | Explore the docs » 14 |
15 |
16 | Dockerhub Frontend 17 | · 18 | Dockerhub Backend 19 | · 20 | Report Bug 21 | · 22 | Request Feature 23 |

24 |

25 | 26 | 27 | 28 | 29 | 30 |
31 | Table of Contents 32 |
    33 |
  1. 34 | About The Project 35 | 38 |
  2. 39 |
  3. 40 | Getting Started 41 | 64 |
  4. 65 |
  5. Roadmap
  6. 66 |
  7. Contributing
  8. 67 |
  9. License
  10. 68 |
  11. Contact
  12. 69 |
70 |
71 | 72 | 73 | 74 | 75 | ## About The Project 76 | ![Example Video](docs/images/example-video.gif) 77 | 78 | 79 | There are already a few image uploader out there in the internet, however, I didn't find any uploader which is fully open source, lightweight and made for self hosting. So I created one myself and hope you enjoy it. 80 | 81 | You may also suggest changes by forking this repo and creating a [pull request](https://github.com/vallezw/Image-Uploader/compare) or opening an [issue](https://github.com/vallezw/Image-Uploader/issues/new/choose). Thanks to all the people have have contributed to expanding this project! 82 | 83 | ### Built With 84 | 85 | Here a list of the major frameworks I used for this project. 86 | * [React](https://reactjs.org/) 87 | * [Express](https://expressjs.com/de/) 88 | * [Material-UI](https://material-ui.com/) 89 | 90 | 91 | ## Getting Started 92 | 93 | There are two ways on how to host this project. You can either use [docker](https://www.docker.com/) or host it with [node](https://nodejs.org/en/). I would highly suggest hosting it with docker since it's more flexible. 94 | 95 | ### Hosting it with Docker 96 | #### Setting it up 97 | In order to host the project you will need to create a docker-compose file. These files are combining multiple docker images to interact with each other. 98 | 99 | The file needs to be called `docker-compose.yml` paste the following code in the file. 100 | 101 | Insert in `docker-compose.yml`: 102 | ```yml 103 | version: "3.8" 104 | services: 105 | frontend: 106 | image: vallezw/image-uploader-client 107 | ports: 108 | - "80:80" 109 | environment: 110 | - "API_URL=http://localhost:5000" 111 | - "CLIENT_URL=http://localhost" 112 | 113 | backend: 114 | image: vallezw/image-uploader-backend 115 | ports: 116 | - "5000:5000" 117 | volumes: 118 | - .upload/:/usr/src/app/upload 119 | ``` 120 | 121 | #### Run the docker file 122 | Once you finished setting up the file you can go ahead and run it with 123 | 124 | 1. Building 125 | ```sh 126 | docker-compose build 127 | ``` 128 | 2. Hosting the project 129 | ```sh 130 | docker-compose up 131 | ``` 132 | 133 | #### Changing the URL 134 | In case you want to host the project without Docker you can do that too. 135 | 136 | ```yml 137 | ... 138 | environment: 139 | - "API_URL=http://your_api_url:your_port" 140 | - "CLIENT_URL=http://your_client_url:your_port" 141 | ``` 142 | 143 | After that you are good to go and host it on your custom domain 144 | ### Hosting with Node 145 | #### Running the project 146 | This is the option for those who don't want to host it with docker. 147 | 1. Go into the backend directory with a terminal/powershell 148 | ```sh 149 | cd backend/ 150 | ``` 151 | 2. Install the npm serve package to host the project 152 | ```sh 153 | npm install -g serve 154 | ``` 155 | 3. Build and run it 156 | ```sh 157 | npm run build 158 | ``` 159 | #### Changing the URL 160 | In case you want to change the URL you have to change the env variables. 161 | 1. Go into the frontend directory and open the `.env` file 162 | 2. Change the `API_URL` and the `CLIENT_URL` to your specific usecase 163 | ```env 164 | API_URL=http://your_api_url:your_port 165 | CLIENT_URL=http://your_client_url:your_port 166 | ``` 167 | After that you are good to go and host it on your custom domain 168 | 169 | 170 | ## Roadmap 171 | 172 | See the [open issues](https://github.com/vallezw/Image-Uploader/issues) for a list of proposed features (and known issues). 173 | 174 | 175 | ## Contributing 176 | 177 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 178 | 179 | 1. Fork the Project 180 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 181 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 182 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 183 | 5. Open a [Pull Request](https://github.com/vallezw/Image-Uploader/compare) 184 | 185 | 186 | ## License 187 | 188 | Distributed under the MIT License. See `LICENSE` for more information. 189 | 190 | 191 | ## Contact 192 | 193 | Valentin Zwerschke - [@vallezw](https://github.com/vallezw) 194 | 195 | Project Link: [github.com/vallezw/Image-Uploader](https://github.com/vallezw/Image-Uploader) 196 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | upload/ -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | REMOVE_IMAGES= -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Image safe point 14 | /upload/* 15 | 16 | /src/upload/* 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directory 31 | # https://docs.npmjs.com/cli/shrinkwrap#caveats 32 | node_modules 33 | 34 | # Debug log from npm 35 | npm-debug.log 36 | 37 | .DS_Store 38 | 39 | package-lock.json 40 | 41 | .env -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | # Development 8 | RUN npm install 9 | 10 | # Production 11 | # RUN npm ci --only=production 12 | 13 | COPY . . 14 | 15 | EXPOSE 5000 16 | CMD [ "node", "src/index.js" ] -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "server": "nodemon src/index.js", 9 | "client": "npm run dev --prefix ../frontend", 10 | "client-build": "cd ../frontend && npm run build && serve -s build -l 80", 11 | "dev": "concurrently \"npm run server\" \"npm run client\"", 12 | "build": "concurrently \"npm run server\" \"npm run client-build\"" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "dotenv": "^8.2.0", 19 | "express": "^4.17.1", 20 | "express-fileupload": "^1.2.1", 21 | "find-remove": "^2.0.3", 22 | "fs": "^0.0.1-security", 23 | "shortid": "^2.2.16" 24 | }, 25 | "devDependencies": { 26 | "concurrently": "^6.0.0", 27 | "nodemon": "^2.0.7" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/src/constants.js: -------------------------------------------------------------------------------- 1 | const endpoints = { 2 | UPLOAD_STATIC_DIRECTORY: '/upload', 3 | UPLOAD_FILE: '/upload', 4 | DOWNLOAD_FILE: '/download/:id' 5 | }; 6 | 7 | const time = { 8 | HOURS_24: 86400000, 9 | WEEK_1: 604800000 10 | }; 11 | 12 | module.exports = { endpoints, time }; -------------------------------------------------------------------------------- /backend/src/index.js: -------------------------------------------------------------------------------- 1 | require('./utils/load-env') 2 | 3 | const Server = require('./server'); 4 | 5 | new Server(process.env.PORT || 5000).start(); -------------------------------------------------------------------------------- /backend/src/middlewares/cors.js: -------------------------------------------------------------------------------- 1 | const cors = (req, res, next) => { 2 | res.header('Access-Control-Allow-Origin', req.get('Origin') || '*'); 3 | res.header('Access-Control-Allow-Credentials', 'true'); 4 | res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE'); 5 | res.header('Access-Control-Expose-Headers', 'Content-Length'); 6 | res.header('Access-Control-Allow-Headers', 'Accept, Authorization, Content-Type, X-Requested-With, Range'); 7 | 8 | if (req.method === 'OPTIONS') return res.send(200); 9 | 10 | return next(); 11 | }; 12 | 13 | module.exports = cors; -------------------------------------------------------------------------------- /backend/src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const fileUpload = require("express-fileupload"); 2 | const cors = require("./cors"); 3 | 4 | const applyMiddlewares = (app) => { 5 | app.use(fileUpload()); 6 | app.use(cors); 7 | }; 8 | 9 | module.exports = { applyMiddlewares }; -------------------------------------------------------------------------------- /backend/src/routes/download.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { endpoints } = require('../constants'); 3 | 4 | const router = Router(); 5 | 6 | router.get(endpoints.DOWNLOAD_FILE, (req, res) => { 7 | res.download(`${__dirname}/..${endpoints.UPLOAD_STATIC_DIRECTORY}/${req.params.id}`); 8 | }); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /backend/src/routes/index.js: -------------------------------------------------------------------------------- 1 | const uploadRouter = require('./upload'); 2 | const downloadRouter = require('./download'); 3 | 4 | const renderRoutes = (app) => { 5 | [uploadRouter, downloadRouter].forEach(router => app.use('/', router)); 6 | }; 7 | 8 | module.exports = { renderRoutes }; -------------------------------------------------------------------------------- /backend/src/routes/upload.js: -------------------------------------------------------------------------------- 1 | const generateId = require("shortid"); 2 | const express = require('express'); 3 | const { Router } = require('express'); 4 | const { endpoints } = require('../constants'); 5 | 6 | const router = Router(); 7 | 8 | router.use(endpoints.UPLOAD_STATIC_DIRECTORY, express.static( __dirname + endpoints.UPLOAD_STATIC_DIRECTORY)); 9 | 10 | router.post(endpoints.UPLOAD_FILE, (req, res) => { 11 | if(req.files === null){ 12 | console.log('No file uploaded'); 13 | return res.status(400).json({ msg: 'No file uploaded' }); 14 | } 15 | 16 | const file = req.files.file; 17 | 18 | fileEnding = file.name.split(".") 19 | fileEnding = fileEnding[fileEnding.length - 1] 20 | fileName = generateId() + '.' + fileEnding 21 | 22 | file.mv(`${__dirname}/..` + endpoints.UPLOAD_STATIC_DIRECTORY + `/${fileName}`, err => { 23 | if(err) { 24 | console.error(err); 25 | return res.status(500).send(err); 26 | } 27 | 28 | res.json({ filePath: `${endpoints.UPLOAD_STATIC_DIRECTORY}/${fileName}`}); 29 | }); 30 | }); 31 | 32 | module.exports = router; -------------------------------------------------------------------------------- /backend/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const initiateResources = require('./utils/initiate-resources'); 3 | 4 | class Server { 5 | _port; 6 | _app; 7 | 8 | constructor(port) { 9 | this._port = port; 10 | this._app = express(); 11 | } 12 | 13 | start() { 14 | initiateResources(this._app); 15 | this._app.use('/upload', express.static( __dirname + '/upload')); 16 | this._app.listen(this._port, () => console.log(`Server is running on port ${this._port}`)); 17 | } 18 | } 19 | 20 | module.exports = Server; -------------------------------------------------------------------------------- /backend/src/utils/initiate-resources.js: -------------------------------------------------------------------------------- 1 | const { applyMiddlewares } = require('../middlewares'); 2 | const { renderRoutes } = require('../routes/index'); 3 | const removeImages = require('./remove-images'); 4 | const fs = require('fs'); 5 | const { endpoints } = require('../constants'); 6 | 7 | 8 | const initiateResources = (app) => { 9 | applyMiddlewares(app); 10 | 11 | renderRoutes(app); 12 | 13 | const dir = `${__dirname}/..` + endpoints.UPLOAD_STATIC_DIRECTORY 14 | 15 | if (!fs.existsSync(dir)){ 16 | fs.mkdirSync(dir); 17 | } 18 | 19 | if(process.env.REMOVE_IMAGES === 'true') { 20 | removeImages(); 21 | }; 22 | }; 23 | 24 | module.exports = initiateResources; -------------------------------------------------------------------------------- /backend/src/utils/load-env.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | require('dotenv').config({ path: path.join(__dirname, '../../.env')}); -------------------------------------------------------------------------------- /backend/src/utils/remove-images.js: -------------------------------------------------------------------------------- 1 | const findRemove = require('find-remove'); 2 | const { time } = require('../constants'); 3 | 4 | const removeImages = () => { 5 | setInterval(findRemove.bind(this, __dirname + '/upload', { 6 | age: {seconds: time.WEEK_1 / 1000 }, extensions: ['.jpg', '.jpeg', '.png', '.gif'] 7 | }), time.HOURS_24); 8 | }; 9 | 10 | module.exports = removeImages; -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | frontend: 4 | image: vallezw/image-uploader-client 5 | ports: 6 | - "80:80" 7 | build: 8 | context: ./frontend 9 | dockerfile: ./Dockerfile 10 | depends_on: 11 | - "backend" 12 | environment: 13 | - "API_URL=http://localhost:5000" 14 | - "CLIENT_URL=http://localhost" 15 | container_name: "frontend" 16 | 17 | backend: 18 | image: vallezw/image-uploader-backend 19 | ports: 20 | - "5000:5000" 21 | build: 22 | context: ./backend 23 | dockerfile: ./Dockerfile 24 | container_name: backend 25 | volumes: 26 | - .upload/:/usr/src/app/src/upload -------------------------------------------------------------------------------- /docs/images/example-video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/docs/images/example-video.gif -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/docs/images/screenshot.png -------------------------------------------------------------------------------- /docs/images/vallezw-Image-Uploader-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/docs/images/vallezw-Image-Uploader-dark.png -------------------------------------------------------------------------------- /docs/images/vallezw-Image-Uploader-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/docs/images/vallezw-Image-Uploader-light.png -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | API_URL=http://localhost:5000 2 | CLIENT_URL=http://localhost -------------------------------------------------------------------------------- /frontend/.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 | /public/env-config.js 25 | /env-config.js -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # => Build container 2 | FROM node:alpine as build 3 | WORKDIR /app 4 | COPY package.json ./ 5 | COPY package-lock.json ./ 6 | RUN npm ci --silent 7 | RUN npm install react-scripts@3.4.1 -g --silent 8 | COPY . ./ 9 | RUN npm run build 10 | 11 | # => Run container 12 | FROM nginx:stable-alpine 13 | 14 | # Nginx config 15 | RUN rm -rf /etc/nginx/conf.d 16 | COPY conf /etc/nginx 17 | 18 | # Static build 19 | COPY --from=build /app/build /usr/share/nginx/html 20 | 21 | # Default port exposure 22 | EXPOSE 80 23 | 24 | # Copy .env file and shell script to container 25 | WORKDIR /usr/share/nginx/html 26 | COPY ./env.sh ./ 27 | COPY ./.env ./ 28 | 29 | # Add bash 30 | RUN apk add --no-cache bash 31 | 32 | # Make our shell script executable 33 | RUN chmod +x env.sh 34 | 35 | # Start Nginx server 36 | CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""] -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /frontend/conf/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | location / { 4 | root /usr/share/nginx/html; 5 | index index.html index.htm; 6 | try_files $uri $uri/ /index.html; 7 | expires -1; # Set it to different value depending on your standard requirements 8 | } 9 | error_page 500 502 503 504 /50x.html; 10 | location = /50x.html { 11 | root /usr/share/nginx/html; 12 | } 13 | } -------------------------------------------------------------------------------- /frontend/conf/conf.d/gzip.conf: -------------------------------------------------------------------------------- 1 | gzip on; 2 | gzip_http_version 1.0; 3 | gzip_comp_level 5; # 1-9 4 | gzip_min_length 256; 5 | gzip_proxied any; 6 | gzip_vary on; 7 | 8 | # MIME-types 9 | gzip_types 10 | application/atom+xml 11 | application/javascript 12 | application/json 13 | application/rss+xml 14 | application/vnd.ms-fontobject 15 | application/x-font-ttf 16 | application/x-web-app-manifest+json 17 | application/xhtml+xml 18 | application/xml 19 | font/opentype 20 | image/svg+xml 21 | image/x-icon 22 | text/css 23 | text/plain 24 | text/x-component; 25 | -------------------------------------------------------------------------------- /frontend/env-config 2.js: -------------------------------------------------------------------------------- 1 | window._env_ = { 2 | API_URL: "http://localhost:5000", 3 | CLIENT_URL: "http://localhost", 4 | } 5 | -------------------------------------------------------------------------------- /frontend/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Recreate config file 4 | rm -rf ./env-config.js 5 | touch ./env-config.js 6 | 7 | # Add assignment 8 | echo "window._env_ = {" >> ./env-config.js 9 | 10 | # Read each line in .env file 11 | # Each line represents key=value pairs 12 | while read -r line || [[ -n "$line" ]]; 13 | do 14 | # Split env variables by character `=` 15 | if printf '%s\n' "$line" | grep -q -e '='; then 16 | varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') 17 | varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') 18 | fi 19 | 20 | # Read value of current variable if exists as Environment variable 21 | value=$(printf '%s\n' "${!varname}") 22 | # Otherwise use value from .env file 23 | [[ -z $value ]] && value=${varvalue} 24 | 25 | # Append configuration property to JS file 26 | echo " $varname: \"$value\"," >> ./env-config.js 27 | done < .env 28 | 29 | echo "}" >> ./env-config.js 30 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.3", 7 | "@material-ui/icons": "^4.11.2", 8 | "@testing-library/jest-dom": "^5.11.9", 9 | "@testing-library/react": "^11.2.5", 10 | "@testing-library/user-event": "^12.8.3", 11 | "axios": "^0.21.1", 12 | "react": "^17.0.1", 13 | "react-code-blocks": "^0.0.8", 14 | "react-dom": "^17.0.1", 15 | "react-dropzone": "^11.3.1", 16 | "react-lottie": "^1.2.3", 17 | "react-router-dom": "^5.2.0", 18 | "react-scripts": "4.0.3", 19 | "sass": "^1.32.8", 20 | "sweetalert2": "^10.15.6", 21 | "web-vitals": "^1.1.0" 22 | }, 23 | "scripts": { 24 | "dev": "chmod +x ./env.sh && ./env.sh && cp env-config.js ./public/ && react-scripts start", 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Image Uploader 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/frontend/public/logo.png -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .cardContainer { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | } 7 | 8 | .loading { 9 | margin-top: 20vh; 10 | display: block; 11 | margin-left: auto; 12 | margin-right: auto; 13 | width: 43%; 14 | } 15 | 16 | body { 17 | font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; 18 | } 19 | 20 | 21 | @media screen and (max-height: 750px) { 22 | body { 23 | zoom: 90%; 24 | } 25 | } 26 | 27 | @media screen and (max-height: 700px) { 28 | body { 29 | zoom: 85%; 30 | } 31 | } 32 | 33 | @media screen and (max-height: 650px) { 34 | body { 35 | zoom: 80%; 36 | } 37 | } 38 | 39 | @media screen and (max-height: 600px) { 40 | body { 41 | zoom: 75%; 42 | } 43 | } 44 | 45 | @media screen and (max-height: 550px) { 46 | body { 47 | zoom: 70%; 48 | } 49 | } 50 | 51 | @media screen and (max-height: 500px) { 52 | body { 53 | zoom: 65%; 54 | } 55 | } 56 | 57 | @media screen and (max-height: 450px) { 58 | body { 59 | zoom: 60%; 60 | } 61 | } -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 3 | 4 | // Pages 5 | import UploadPage from './Components/Pages/UploadPage' 6 | import UploadedImage from './Components/Pages/UploadedImagePage'; 7 | import FZF from './Components/Pages/404Page.js' 8 | 9 | function App() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/Css/Background.css: -------------------------------------------------------------------------------- 1 | 2 | .allContainer { 3 | background: url(../../../Images/background.svg) no-repeat center center fixed; 4 | -webkit-background-size: cover; 5 | -moz-background-size: cover; 6 | -o-background-size: cover; 7 | background-size: cover; 8 | position: absolute; 9 | top: 0; 10 | right: 0; 11 | bottom: 0; 12 | left: 0; 13 | } 14 | 15 | .allContainerNoBackground { 16 | background-size: cover; 17 | position: absolute; 18 | top: 0; 19 | right: 0; 20 | bottom: 0; 21 | left: 0; 22 | background-color: whitesmoke; 23 | } -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/Css/Footer.css: -------------------------------------------------------------------------------- 1 | 2 | .copyright { 3 | text-align:center; 4 | font-size:13px; 5 | color:#aaa; 6 | font-family: "Roboto"; 7 | font-weight: lighter; 8 | } 9 | 10 | footer { 11 | position: absolute; 12 | bottom: 0; 13 | width: 99%; 14 | height: 2.5rem; 15 | } 16 | 17 | a { 18 | text-align:center; 19 | font-size:13px; 20 | color:#aaa; 21 | font-family: "Roboto"; 22 | font-weight: lighter; 23 | text-decoration: none; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/Css/Image.css: -------------------------------------------------------------------------------- 1 | .boxContainer { 2 | width: max-content; 3 | height: max-content; 4 | } 5 | 6 | .box{ 7 | width: max-content; 8 | height: max-content; 9 | } 10 | 11 | /* Style the Image Used to Trigger the Modal */ 12 | #myImg { 13 | margin-top: 100px; 14 | border-radius: 5px; 15 | cursor: pointer; 16 | transition: 0.3s; 17 | 18 | position: relative; 19 | display: block; 20 | margin-left: auto; 21 | margin-right: auto; 22 | 23 | /* Style image size */ 24 | width: auto; 25 | height: auto; 26 | 27 | max-width: 60vh; 28 | max-height: 60vh; 29 | 30 | /* For transparent images: */ 31 | background-color: rgb(255, 255, 255, 1); 32 | 33 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); /* Box shadow for the image */ 34 | } 35 | 36 | #myImg:hover {opacity: 0.7;} 37 | 38 | /* The Modal (background) */ 39 | .modal { 40 | display: none; /* Hidden by default */ 41 | position: fixed; /* Stay in place */ 42 | z-index: 1; /* Sit on top */ 43 | padding-top: 100px; /* Location of the box */ 44 | left: 0; 45 | top: 0; 46 | width: 100%; /* Full width */ 47 | height: 100%; /* Full height */ 48 | overflow: auto; /* Enable scroll if needed */ 49 | background-color: rgb(0,0,0); /* Fallback color */ 50 | background-color: rgba(0,0,0,0.9); /* Black w/ opacity */ 51 | } 52 | 53 | /* Modal Content (Image) */ 54 | .modal-content { 55 | margin: auto; 56 | display: block; 57 | width: 80%; 58 | max-width: 700px; 59 | /* For transparent images: */ 60 | background-color: rgb(255, 255, 255, 1); 61 | } 62 | 63 | /* Caption of Modal Image (Image Text) - Same Width as the Image */ 64 | #caption { 65 | margin: auto; 66 | display: block; 67 | width: 80%; 68 | max-width: 700px; 69 | text-align: center; 70 | color: #ccc; 71 | padding: 10px 0; 72 | height: 150px; 73 | } 74 | 75 | /* Add Animation - Zoom in the Modal */ 76 | .modal-content, #caption { 77 | animation-name: zoom; 78 | animation-duration: 0.6s; 79 | } 80 | 81 | @keyframes zoom { 82 | from {transform:scale(0)} 83 | to {transform:scale(1)} 84 | } 85 | 86 | /* The Close Button */ 87 | .close { 88 | position: absolute; 89 | top: 15px; 90 | right: 35px; 91 | color: #f1f1f1; 92 | font-size: 40px; 93 | font-weight: bold; 94 | transition: 0.3s; 95 | } 96 | 97 | .close:hover, 98 | .close:focus { 99 | color: #bbb; 100 | text-decoration: none; 101 | cursor: pointer; 102 | } 103 | 104 | /* 100% Image Width on Smaller Screens */ 105 | @media only screen and (max-width: 700px){ 106 | .modal-content { 107 | width: 100%; 108 | } 109 | } -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/Css/Image.scss: -------------------------------------------------------------------------------- 1 | /*.box { 2 | --border-width: 3px; 3 | 4 | position: relative; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | width: 300px; 9 | height: 200px; 10 | font-family: Lato, sans-serif; 11 | font-size: 2.5rem; 12 | text-transform: uppercase; 13 | color: white; 14 | background: #222; 15 | border-radius: var(--border-width); 16 | 17 | &::after { 18 | position: absolute; 19 | content: ""; 20 | top: calc(-1 * var(--border-width)); 21 | left: calc(-1 * var(--border-width)); 22 | z-index: -1; 23 | width: calc(100% + var(--border-width) * 2); 24 | height: calc(100% + var(--border-width) * 2); 25 | background: linear-gradient( 26 | 60deg, 27 | hsl(0, 0%, 0%), 28 | hsl(0, 2%, 38%), 29 | hsl(0, 28%, 60%), 30 | hsl(0, 6%, 85%), 31 | hsl(0, 91%, 40%), 32 | hsl(0, 0%, 79%), 33 | hsl(0, 10%, 80%), 34 | hsl(0, 0%, 0%) 35 | ); 36 | background-size: 300% 300%; 37 | background-position: 0 50%; 38 | border-radius: calc(2 * var(--border-width)); 39 | animation: moveGradient 4s alternate infinite; 40 | } 41 | } 42 | 43 | @keyframes moveGradient { 44 | 50% { 45 | background-position: 100% 50%; 46 | } 47 | } 48 | */ -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/Css/Navbar.css: -------------------------------------------------------------------------------- 1 | header { 2 | display: flex; 3 | justify-content: flex-end; 4 | align-items: center; 5 | padding: 0px 3%; 6 | background-color: rgba(36, 37, 42, 1); 7 | } 8 | 9 | .logo { 10 | margin-right: auto; 11 | color: #ECF0F1; 12 | font-family: 'Montserrat', sans-serif; 13 | font-size: 20px; 14 | display: flex; 15 | flex-direction: row; 16 | } 17 | 18 | .logo p { 19 | padding-top: 2px; 20 | } 21 | 22 | .imageNav { 23 | margin-top: 10px; 24 | width: 50px; 25 | height: 50px; 26 | padding-right: 15px; 27 | } 28 | 29 | .nav__links { 30 | list-style: none; 31 | display: flex; 32 | } 33 | 34 | .nav__links a, 35 | .cta, 36 | .overlay__content a { 37 | font-family: "Montserrat", sans-serif; 38 | font-weight: 500; 39 | color: #edf0f1; 40 | text-decoration: none; 41 | } 42 | 43 | .nav__links li { 44 | padding: 0px 20px; 45 | } 46 | 47 | .nav__links li a { 48 | transition: all 0.3s ease 0s; 49 | } 50 | 51 | .nav__links li a:hover { 52 | color: #0088a9; 53 | } 54 | 55 | .cta { 56 | margin-left: 20px; 57 | padding: 9px 25px; 58 | background-color: rgba(0, 136, 169, 1); 59 | border: none; 60 | border-radius: 50px; 61 | cursor: pointer; 62 | transition: all 0.3s ease 0s; 63 | } 64 | 65 | .cta:hover { 66 | background-color: rgba(0, 136, 169, 0.8); 67 | } 68 | 69 | /* Mobile Nav */ 70 | 71 | .menu { 72 | display: none; 73 | } 74 | 75 | .overlay { 76 | height: 100%; 77 | width: 0; 78 | position: fixed; 79 | z-index: 1; 80 | left: 0; 81 | top: 0; 82 | background-color: #24252a; 83 | overflow-x: hidden; 84 | transition: all 0.5s ease 0s; 85 | } 86 | 87 | .overlay--active { 88 | width: 100%; 89 | } 90 | 91 | .overlay__content { 92 | display: flex; 93 | height: 100%; 94 | flex-direction: column; 95 | align-items: center; 96 | justify-content: center; 97 | } 98 | 99 | .overlay a { 100 | padding: 15px; 101 | font-size: 36px; 102 | display: block; 103 | transition: all 0.3s ease 0s; 104 | } 105 | 106 | .overlay a:hover, 107 | .overlay a:focus { 108 | color: #0088a9; 109 | } 110 | .overlay .close { 111 | position: absolute; 112 | top: 20px; 113 | right: 45px; 114 | font-size: 60px; 115 | color: #edf0f1; 116 | cursor: pointer; 117 | } 118 | 119 | @media screen and (max-height: 450px) { 120 | .overlay a { 121 | font-size: 20px; 122 | } 123 | .overlay .close { 124 | font-size: 40px; 125 | top: 15px; 126 | right: 35px; 127 | } 128 | } 129 | 130 | @media only screen and (max-width: 800px) { 131 | .nav__links, 132 | .cta { 133 | display: none; 134 | } 135 | .menu { 136 | display: initial; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import './Css/Footer.css' 4 | 5 | function Footer() { 6 | return ( 7 | 10 | ) 11 | } 12 | 13 | export default Footer 14 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/Headers/Navbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import '../Css/Navbar.css' 4 | 5 | import logo from '../../../Images/logo.png' 6 | 7 | function Navbar() { 8 | return ( 9 |
10 |
Logo

Image Uploader

11 | 16 | Upload 17 |
18 | ) 19 | } 20 | 21 | export default Navbar 22 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/ImageUploadCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardContent from '@material-ui/core/CardContent'; 5 | import Grow from '@material-ui/core/Grow'; 6 | 7 | // Components 8 | import StyledDropzone from './StyledDropzone' 9 | import UploadButton from './UploadButton' 10 | import Loading from './LoadingAnimation/Loading'; 11 | 12 | import '../../App.css' 13 | 14 | const useStyles = makeStyles({ 15 | root: { 16 | paddingLeft: "40px", 17 | paddingRight: "40px", 18 | paddingTop: "10px", 19 | paddingBottom: "10px", 20 | borderRadius: "7px", 21 | boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)", 22 | display: "grid", 23 | placeItems: "center" 24 | }, 25 | headerText: { 26 | fontFamily: "roboto", 27 | fontWeight: "300", 28 | fontSize: 20, 29 | textAlign: "center", 30 | paddingBottom: 0, 31 | lineHeight: "0em" 32 | }, 33 | subheaderText: { 34 | fontFamily: "roboto", 35 | fontWeight: "300", 36 | fontSize: 11, 37 | color: "grey", 38 | textAlign: "center", 39 | lineHeight: "0.7em", 40 | paddingBottom: "20px" 41 | } 42 | }); 43 | 44 | 45 | export default function ImageUploadCard(props) { 46 | const classes = useStyles(); 47 | const checked = true 48 | return ( 49 |
50 | {!props.loading? 51 |
52 | 53 | 54 | 55 |

Upload your image

56 |

File should be Jpeg, Png, ...

57 | 58 | 59 |
60 |
61 |
62 |
63 | : 64 |
65 | 66 |
67 | } 68 |
69 | ); 70 | } 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/LoadingAnimation/Loading.js: -------------------------------------------------------------------------------- 1 | import '../../../App.css' 2 | 3 | import Lottie from 'react-lottie'; 4 | import animationData from './animation.json'; 5 | 6 | export default function Loading() { 7 | const defaultOptions = { 8 | loop: true, 9 | autoplay: true, 10 | animationData: animationData, 11 | rendererSettings: { 12 | preserveAspectRatio: "xMidYMid slice" 13 | }, 14 | }; 15 | 16 | return ( 17 |
18 | 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/SocialMedia/Css/SocialMedia.css: -------------------------------------------------------------------------------- 1 | i { 2 | opacity: 0; 3 | font-size: 28px; 4 | color: #1F1E1E; 5 | will-change: transform; 6 | -webkit-transform: scale(.1); 7 | transform: scale(.1); 8 | -webkit-transition: all .3s ease; 9 | transition: all .3s ease; 10 | } 11 | 12 | .btn_wrap { 13 | margin-top: 50px; 14 | display: block; 15 | margin-left: auto; 16 | margin-right: auto; 17 | position: relative; 18 | display: -webkit-box; 19 | display: -ms-flexbox; 20 | display: flex; 21 | -webkit-box-pack: center; 22 | -ms-flex-pack: center; 23 | justify-content: center; 24 | -webkit-box-align: center; 25 | -ms-flex-align: center; 26 | align-items: center; 27 | overflow: hidden; 28 | cursor: pointer; 29 | width: 232px; 30 | height: 60px; 31 | background-color: #EEEEED; 32 | border-radius: 80px; 33 | padding: 0 18px; 34 | will-change: transform; 35 | -webkit-transition: all .2s ease-in-out; 36 | transition: all .2s ease-in-out; 37 | } 38 | 39 | .btn_wrap:hover { 40 | /* transition-delay: .4s; */ 41 | -webkit-transform: scale(1.1); 42 | transform: scale(1.1) 43 | } 44 | 45 | .socialSpan { 46 | position: absolute; 47 | z-index: 99; 48 | width: 240px; 49 | height: 72px; 50 | border-radius: 80px; 51 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 52 | font-size: 20px; 53 | text-align: center; 54 | line-height: 70px; 55 | letter-spacing: 2px; 56 | color: #EEEEED; 57 | background-color: #25252A; 58 | padding: 0 18px; 59 | -webkit-transition: all 1.2s ease; 60 | transition: all 1.2s ease; 61 | } 62 | 63 | .shareWrap { 64 | display: -webkit-box; 65 | display: -ms-flexbox; 66 | display: flex; 67 | -ms-flex-pack: distribute; 68 | justify-content: space-around; 69 | -webkit-box-align: center; 70 | -ms-flex-align: center; 71 | align-items: center; 72 | width: 240px; 73 | height: 64px; 74 | border-radius: 80px; 75 | } 76 | 77 | .shareWrap i:nth-of-type(1) { 78 | -webkit-transition-delay: .5s; 79 | transition-delay: .5s; 80 | } 81 | 82 | .shareWrap i:nth-of-type(2) { 83 | -webkit-transition-delay: .9s; 84 | transition-delay: .9s; 85 | } 86 | 87 | .shareWrap i:nth-of-type(3) { 88 | -webkit-transition-delay: .7s; 89 | transition-delay: .7s; 90 | } 91 | 92 | .shareWrap i:nth-of-type(4) { 93 | -webkit-transition-delay: .4s; 94 | transition-delay: .4s; 95 | } 96 | 97 | .btn_wrap:hover span { 98 | -webkit-transition-delay: .25s; 99 | transition-delay: .25s; 100 | -webkit-transform: translateX(-520px); 101 | transform: translateX(-520px) 102 | } 103 | 104 | .btn_wrap:hover i { 105 | opacity: 1; 106 | -webkit-transform: scale(1); 107 | transform: scale(1); 108 | } 109 | 110 | .iconButton { 111 | cursor: pointer; 112 | border: none; 113 | transition: all .2s ease-in-out; 114 | } 115 | 116 | .iconButton:focus { 117 | outline: none; 118 | } 119 | 120 | .iconButton:hover { 121 | transform: scale(1.2) 122 | } 123 | 124 | .iconButton:after { 125 | content: ""; 126 | background: #f1f1f1; 127 | display: block; 128 | position: absolute; 129 | padding-top: 300%; 130 | padding-left: 350%; 131 | margin-left: -0px !important; 132 | margin-top: -120%; 133 | opacity: 0; 134 | transition: all 0.8s 135 | } 136 | 137 | .iconButton:active:after { 138 | padding: 0; 139 | margin: 0; 140 | opacity: 1; 141 | transition: 0s 142 | } 143 | 144 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/SocialMedia/SocialMediaShareButtons.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './Css/SocialMedia.css' 3 | 4 | export default class SocialMediaShareButtons extends Component { 5 | render() { 6 | const path = this.props.image_url 7 | const URL = `${window._env_.CLIENT_URL}/upload/${path}` 8 | const SERVER_URL = `${window._env_.API_URL}/download/${path}` 9 | const TEXT = `Hey, look at this cool image I uploaded!` 10 | return ( 11 |
12 | Share 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | ) 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/StyledDropzone.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useRef } from 'react'; 2 | import { useDropzone } from 'react-dropzone'; 3 | 4 | import { sendRequest } from '../../Utils/sendRequest' 5 | import goingUpImage from '../../Images/going_up.svg' 6 | 7 | 8 | const baseStyle = { 9 | flex: 1, 10 | display: 'flex', 11 | flexDirection: 'column', 12 | alignItems: 'center', 13 | padding: '20px', 14 | borderWidth: 2, 15 | borderRadius: 2, 16 | borderColor: '#eeeeee', 17 | borderStyle: 'dashed', 18 | backgroundColor: '#fafafa', 19 | color: '#bdbdbd', 20 | outline: 'none', 21 | transition: 'border .24s ease-in-out', 22 | }; 23 | 24 | const activeStyle = { 25 | borderColor: '#2196f3' 26 | }; 27 | 28 | const acceptStyle = { 29 | borderColor: '#00e676' 30 | }; 31 | 32 | const rejectStyle = { 33 | borderColor: '#ff1744' 34 | }; 35 | 36 | const textStyle = { 37 | fontFamily: "Roboto", 38 | fontWeight: "300", 39 | fontSize: "14px" 40 | } 41 | 42 | const divStyle = { 43 | float:"left", 44 | position:"absolute", 45 | marginTop: "56px", 46 | marginRight: "10px", 47 | padding:"20px", 48 | color:"#FFFFFF", 49 | cursor: "pointer" 50 | } 51 | 52 | 53 | export default function StyledDropzone(props) { 54 | const { 55 | getRootProps, 56 | isDragActive, 57 | isDragAccept, 58 | isDragReject 59 | } = useDropzone({accept: 'image/jpeg, image/png, image/gif', onDrop: (file) => { 60 | sendRequest(file[0], props.handleLoading, props.handleResponse) 61 | }}); 62 | 63 | const style = useMemo(() => ({ 64 | ...baseStyle, 65 | ...(isDragActive ? activeStyle : {}), 66 | ...(isDragAccept ? acceptStyle : {}), 67 | ...(isDragReject ? rejectStyle : {}) 68 | }), [ 69 | isDragActive, 70 | isDragReject, 71 | isDragAccept 72 | ]); 73 | 74 | const inputFile = useRef(null) 75 | 76 | const handleChange = event => { 77 | const fileUploaded = event.target.files[0]; 78 | sendRequest(fileUploaded, props.handleLoading, props.handleResponse) 79 | } 80 | 81 | const onDivClick = () => { 82 | inputFile.current.click(); 83 | } 84 | 85 | return ( 86 |
87 |
88 |

Drag 'n' drop your image here

89 |
90 | 91 |
92 | goingUpImage 93 |
94 |
95 | ); 96 | } -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/UploadButton.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useRef } from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | 4 | import { sendRequest } from '../../Utils/sendRequest'; 5 | 6 | import Button from '@material-ui/core/Button'; 7 | 8 | import CloudUploadIcon from '@material-ui/icons/CloudUpload'; 9 | 10 | const useStyles = makeStyles({ 11 | button: { 12 | margin: 10, 13 | marginTop: 20, 14 | left: "14%" 15 | }, 16 | }); 17 | 18 | export default function UploadButton(props) { 19 | const classes = useStyles(); 20 | 21 | const inputFile = useRef(null) 22 | 23 | const onButtonClick = () => { 24 | inputFile.current.click(); 25 | } 26 | 27 | const handleChange = event => { 28 | const fileUploaded = event.target.files[0]; 29 | sendRequest(fileUploaded, props.handleLoading, props.handleResponse) 30 | } 31 | 32 | return( 33 | 34 | 35 | 36 | 46 | 47 | ) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /frontend/src/Components/ComponentUtils/UploadedImage.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react' 2 | 3 | import './Css/Image.css' 4 | import './Css/Image.scss' 5 | 6 | export default class UploadedImage extends Component { 7 | state = { 8 | showModal: false, 9 | caption: '', 10 | modalSrc: '', 11 | }; 12 | 13 | render() { 14 | const image_url = window._env_.API_URL + "/upload/" + this.props.image_url 15 | 16 | return ( 17 | 18 | { 22 | this.setState({ showModal: true, caption: "Uploaded", modalSrc: image_url}); 23 | }} 24 | alt="Uploaded" 25 | onError={() => this.props.imageNotFound()} 26 | /> 27 | 28 |
33 |
34 | this.setState({ showModal: false })}> 35 | × 36 | 37 | Uploaded 38 |
39 |
40 |
41 | ); 42 | } 43 | } -------------------------------------------------------------------------------- /frontend/src/Components/Pages/404Page.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Navbar from '../ComponentUtils/Headers/Navbar' 3 | 4 | import './Css/404Page.css' 5 | 6 | function FZF() { 7 | return ( 8 |
9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | ) 31 | } 32 | 33 | export default FZF 34 | -------------------------------------------------------------------------------- /frontend/src/Components/Pages/Css/404Page.css: -------------------------------------------------------------------------------- 1 | .container404{ 2 | margin-top: 25vh; 3 | display: -ms-flexbox; 4 | display: -webkit-flex; 5 | display: flex; 6 | -webkit-flex-direction: row; 7 | -ms-flex-direction: row; 8 | flex-direction: row; 9 | -webkit-flex-wrap: nowrap; 10 | -ms-flex-wrap: nowrap; 11 | flex-wrap: nowrap; 12 | -webkit-justify-content: center; 13 | -ms-flex-pack: center; 14 | justify-content: center; 15 | -webkit-align-content: center; 16 | -ms-flex-line-pack: center; 17 | align-content: center; 18 | -webkit-align-items: center; 19 | -ms-flex-align: center; 20 | align-items: center; 21 | } 22 | 23 | .page404 { 24 | width: 400px; 25 | height: auto; 26 | } 27 | 28 | #tree{ 29 | stroke: #59513C; 30 | } 31 | 32 | #wood-stump{ 33 | stroke: #59513C; 34 | -webkit-animation: wood-stump 3s infinite ease-in-out; 35 | -moz-animation: wood-stump 3s infinite ease-in-out; 36 | -o-animation: wood-stump 3s infinite ease-in-out; 37 | animation: wood-stump 3s infinite ease-in-out; 38 | } 39 | 40 | @-webkit-keyframes wood-stump{ 0% { -webkit-transform: translate(100px) } 50% { -webkit-transform: translate(50px); } 100% { -webkit-transform: translate(100px); } } 41 | @-moz-keyframes wood-stump{ 0% { -moz-transform: translate(100px); } 50% { -moz-transform: translate(50px); } 100% { -moz-transform: translate(100px); } } 42 | @-o-keyframes wood-stump{ 0% { -o-transform: translate(100px); } 50% { -o-transform: translate(50px); } 100% { -o-transform: translate(100px); } } 43 | @keyframes wood-stump{ 0% {-webkit-transform: translate(100px);-moz-transform: translate(100px);-ms-transform: translate(100px);transform: translate(100px); } 50% {-webkit-transform: translate(0px);-moz-transform: translate(0px);-ms-transform: translate(0px);transform: translate(0px); } 100% {-webkit-transform: translate(100px); -moz-transform: translate(100px);-ms-transform: translate(100px);transform: translate(100px); } } 44 | 45 | 46 | #leaf{ 47 | stroke: #59513C; 48 | -webkit-animation: leaf 7s infinite ease-in-out; 49 | -moz-animation: leaf 7s infinite ease-in-out; 50 | -o-animation: leaf 7s infinite ease-in-out; 51 | animation: leaf 7s infinite ease-in-out; 52 | } 53 | 54 | @-webkit-keyframes leaf{ 0% { -webkit-transform: translate(0, 70px) } 50% { -webkit-transform: translate(0, 50px); } 100% { -webkit-transform: translate(0, 70px); } } 55 | @-moz-keyframes leaf{ 0% { -moz-transform: translate(0, 70px); } 50% { -moz-transform: translate(0, 50px); } 100% { -moz-transform: translate(0, 70px); } } 56 | @-o-keyframes leaf{ 0% { -o-transform: translate(0, 70px); } 50% { -o-transform: translate(0, 50px); } 100% { -o-transform: translate(0, 70px); } } 57 | @keyframes leaf{ 0% {-webkit-transform: translate(0, 70px);-moz-transform: translate(0, 70px);-ms-transform: translate(0, 70px);transform: translate(0, 70px); } 50% {-webkit-transform: translate(0px);-moz-transform: translate(0px);-ms-transform: translate(0px);transform: translate(0px); } 100% {-webkit-transform: translate(0, 70px); -moz-transform: translate(0, 70px);-ms-transform: translate(0, 70px);transform: translate(0, 70px); } } 58 | 59 | #border{ 60 | stroke: #59513C; 61 | } 62 | 63 | #Page{ 64 | fill: #59513C; 65 | } 66 | #notFound{ 67 | fill: #A7444B; 68 | } 69 | -------------------------------------------------------------------------------- /frontend/src/Components/Pages/Css/UploadedImagePage.css: -------------------------------------------------------------------------------- 1 | .rowContainer { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .rootUploadWrap { 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | } 11 | 12 | .FZFImage { 13 | display: block; 14 | margin-left: auto; 15 | margin-right: auto; 16 | width: 50%; 17 | height: 50%; 18 | padding-top: 50px; 19 | } -------------------------------------------------------------------------------- /frontend/src/Components/Pages/UploadPage.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import '../../App.css' 3 | import Footer from '../ComponentUtils/Footer' 4 | 5 | import ImageUploadCard from '../ComponentUtils/ImageUploadCard' 6 | 7 | import Navbar from '../ComponentUtils/Headers/Navbar' 8 | 9 | import { useHistory } from "react-router-dom"; 10 | import Swal from 'sweetalert2/dist/sweetalert2.js' 11 | import 'sweetalert2/src/sweetalert2.scss' 12 | 13 | import { sendRequest } from '../../Utils/sendRequest' 14 | 15 | import '../ComponentUtils/Css/Background.css' 16 | 17 | function UploadPage() { 18 | 19 | 20 | // History for pushing to a new link after uploading image 21 | const history = useHistory(); 22 | 23 | const [loading, setLoading] = useState(false) 24 | 25 | 26 | const handleLoading = () => { 27 | setLoading(true) 28 | } 29 | 30 | const handleResponse = (value) => { 31 | // Router push to uploadd page 32 | setTimeout(() => { 33 | setLoading(false) 34 | history.push(value.data.filePath) 35 | Swal.fire({ 36 | icon: 'success', 37 | title: "Your image was uploaded!", 38 | showConfirmButton: false, 39 | timer: 1500 40 | }) 41 | }, 1400) 42 | } 43 | 44 | const handlePaste = (event) => { 45 | const fileUploaded = event.clipboardData.files[0] 46 | sendRequest(fileUploaded, handleLoading, handleResponse) 47 | } 48 | 49 | return ( 50 |
51 | 52 | 53 |
54 |
55 |
56 |
57 | ) 58 | } 59 | 60 | export default UploadPage 61 | -------------------------------------------------------------------------------- /frontend/src/Components/Pages/UploadedImagePage.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useParams } from 'react-router' 3 | 4 | import './Css/UploadedImagePage.css' 5 | 6 | // Components 7 | import UploadedImage from '../ComponentUtils/UploadedImage' 8 | import Footer from '../ComponentUtils/Footer' 9 | import Navbar from '../ComponentUtils/Headers/Navbar' 10 | import SocialMediaShareButtons from '../ComponentUtils/SocialMedia/SocialMediaShareButtons' 11 | 12 | import FZF from './404Page' 13 | 14 | function UploadedImagePage() { 15 | 16 | // Get the uploaded image url by url 17 | const { image_url } = useParams() 18 | const [imageFound, setImageFound] = useState(true) 19 | 20 | return ( 21 |
22 | {imageFound? 23 |
24 | 25 |
26 | setImageFound(false)}/> 27 | 28 |
29 |
30 |
31 | : 32 | 33 | } 34 |
35 | ) 36 | } 37 | 38 | export default UploadedImagePage 39 | -------------------------------------------------------------------------------- /frontend/src/Images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/frontend/src/Images/404.png -------------------------------------------------------------------------------- /frontend/src/Images/404.svg: -------------------------------------------------------------------------------- 1 | page not found -------------------------------------------------------------------------------- /frontend/src/Images/404_blue.svg: -------------------------------------------------------------------------------- 1 | page not found -------------------------------------------------------------------------------- /frontend/src/Images/404_green.svg: -------------------------------------------------------------------------------- 1 | page not found -------------------------------------------------------------------------------- /frontend/src/Images/Backgrounds/bg1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/Images/Backgrounds/bg2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/Images/Backgrounds/bg3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/Images/Backgrounds/bg4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/Images/Backgrounds/bg5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/Images/Backgrounds/bg6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/Images/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/Images/going_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/frontend/src/Images/going_up.png -------------------------------------------------------------------------------- /frontend/src/Images/going_up.svg: -------------------------------------------------------------------------------- 1 | going up -------------------------------------------------------------------------------- /frontend/src/Images/going_up_rm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/frontend/src/Images/going_up_rm.png -------------------------------------------------------------------------------- /frontend/src/Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallezw/Image-Uploader/3cb4be209c4fced195893888f2a61b0384e9d216/frontend/src/Images/logo.png -------------------------------------------------------------------------------- /frontend/src/Utils/sendRequest.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | //import swal from 'sweetalert'; 4 | import Swal from 'sweetalert2/dist/sweetalert2.js' 5 | 6 | 7 | export async function sendRequest(file, handleLoading, handleResponse) { 8 | if (file === undefined || !file.name.match(/.(jpg|jpeg|png|gif)$/i)){ 9 | Swal.fire({ 10 | icon: 'error', 11 | title: 'Oops...', 12 | text: 'Seems like you didn\'t submit an image.', 13 | }) 14 | 15 | return 16 | } 17 | 18 | const formData = new FormData() 19 | formData.append("file", file) 20 | 21 | // Change loading state in root component 22 | handleLoading() 23 | 24 | try { 25 | const res = await axios.post(window._env_.API_URL + '/upload', formData, { 26 | headers: { 27 | "Content-Type": "multipart/form-data" 28 | } 29 | }) 30 | 31 | handleResponse(res) 32 | 33 | } 34 | catch(err) { 35 | console.error(err); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | --------------------------------------------------------------------------------