├── .gitignore ├── License.md ├── README.md ├── README_images ├── contact.png ├── hero.png ├── heroDark.png └── speed.png ├── package-lock.json ├── package.json ├── public ├── GH.png ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── maskable_icon.png └── robots.txt └── src ├── App.js ├── app ├── apiSlice.js ├── appSlice.js ├── projectsSlice.js └── store.js ├── components ├── AboutMe.jsx ├── AppFallback.jsx ├── BackToTop.jsx ├── Contact.jsx ├── ContactForm.jsx ├── Footer.jsx ├── GlobalStyles.js ├── Hero.jsx ├── Loading.jsx ├── NavBar.jsx ├── ProjectCard.jsx ├── Projects.jsx ├── ScrollToTop.js ├── Skills.jsx ├── SocialLinks.jsx ├── ThemeToggle.jsx └── Title.jsx ├── config.js ├── custom.scss ├── images ├── GH.svg ├── defaultNavLogo.svg ├── hero-dark.jpg ├── hero-light.jpg └── logo.svg ├── index.js ├── pages ├── AllProjects.jsx ├── Home.jsx └── NotFound.jsx ├── service-worker.js ├── serviceWorkerRegistration.js └── utils.js /.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 | .eslintcache 21 | .prettierrc 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2020 Michael Huber 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A React Portfolio Template for GitHub 2 | 3 | [![GitHub Repo stars](https://img.shields.io/github/stars/mshuber1981/github-react-portfolio-template?color=%2361dbfb&style=for-the-badge&logo=github)](https://github.com/mshuber1981/github-react-portfolio-template/stargazers/) [![GitHub Repo Forks](https://img.shields.io/github/forks/mshuber1981/github-react-portfolio-template?color=%2361dbfb&style=for-the-badge&logo=github&label=Forks)](https://github.com/mshuber1981/github-react-portfolio-template/network/members) [![X (formerly Twitter) URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com&style=for-the-badge&logo=X&label=Say%20thank%20you!&labelColor=black&color=black)](https://twitter.com/intent/tweet?text=Thanks%20for%20the%20awesome%20Portfolio%20Template!%20https://github.com/mshuber1981/github-react-portfolio-template&via=MikeyHuber1981) 4 | 5 | A performant, accessible, progressive React portfolio template that uses the [GitHub REST API](https://docs.github.com/en/free-pro-team@latest/rest). 6 | 7 | Add your GitHub username once and all of your info will automatically be updated. Deploy to GitHub pages in a few simple steps. 8 | 9 | ## [Live Demo](https://mshuber1981.github.io/github-react-portfolio-template/#/) 10 | 11 | [Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) 12 | 13 | ![Page Speed](/README_images/speed.png) 14 | 15 | ## Light And Dark Themes 16 | 17 | ![Hero Light](/README_images/hero.png) 18 | 19 | ![Hero Dark](/README_images/heroDark.png) 20 | 21 | ### Getting Started 22 | 23 | 1. [Create a repository from this template](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template) 24 | 2. [Clone your new repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) 25 | 3. Make sure [Node](https://nodejs.org/en/) is installed 26 | 4. Open your project and install the dependencies 27 | 28 | ```bash 29 | npm install 30 | ``` 31 | 32 | 5. Navigate to the src directory and open src/config.js 33 | 6. Add your GitHub username ([src/config.js](https://github.com/mshuber1981/github-react-portfolio-template/blob/main/src/config.js#L18) line 18) 34 | 35 | ```javascript 36 | /* START HERE 37 | ************************************************************** 38 | Add your GitHub username (string - "YourUsername") below. 39 | */ 40 | export const githubUsername = "Your GitHub username here"; 41 | ``` 42 | 43 | 7. Start the development server to view the results 44 | 45 | ```bash 46 | npm start 47 | ``` 48 | 49 | ### Updating the Contact section 50 | 51 | ![Projects](/README_images/contact.png) 52 | 53 | 1. The contact form uses [Formspree](https://formspree.io/), create an account and add your endpoint URL ([src/config.js](https://github.com/mshuber1981/github-react-portfolio-template/blob/main/src/config.js#L114) line 114) 54 | 55 | ```javascript 56 | /* Contact Info 57 | ************************************************************** 58 | Add your formspree endpoint below. 59 | https://formspree.io/ 60 | */ 61 | export const formspreeUrl = "https://formspree.io/f/YourEndpoint"; 62 | ``` 63 | 64 | ### Deploy 65 | 66 | A helpful guide for Create React App deployments with GitHub Pages can be found [here](https://create-react-app.dev/docs/deployment#github-pages). 67 | 68 | 1. Update the homepage value ([package.json](https://github.com/mshuber1981/github-react-portfolio-template/blob/main/package.json#L3) line 3) 69 | 70 | ```json 71 | "homepage": "https://YourUserName.github.io/your-repo/", 72 | ``` 73 | 74 | 2. Run the deploy command 75 | 76 | ```bash 77 | npm run deploy 78 | ``` 79 | 80 | ### Customization Options 81 | 82 | Checkout the [Wiki](https://github.com/mshuber1981/github-react-portfolio-template/wiki) for additional customization options: 83 | 84 | - [Updating the Navbar Logo](https://github.com/mshuber1981/github-react-portfolio-template/wiki/Updating-the-Navbar-Logo) 85 | - [Updating the Main section](https://github.com/mshuber1981/github-react-portfolio-template/wiki/Updating-the-Main-section) 86 | - [Updating the About Me section](https://github.com/mshuber1981/github-react-portfolio-template/wiki/Updating-the-About-Me-section) 87 | - [Updating the Skills section](https://github.com/mshuber1981/github-react-portfolio-template/wiki/Updating-the-Skills-section) 88 | - [Updating the Projects section](https://github.com/mshuber1981/github-react-portfolio-template/wiki/Updating-the-Projects-section) 89 | - [Updating the theme color](https://github.com/mshuber1981/github-react-portfolio-template/wiki/Updating-the-theme-color) 90 | - [Updating the Footer icons theme (light or dark)](https://github.com/mshuber1981/github-react-portfolio-template/wiki/Updating-the-Footer-icons-theme) 91 | 92 | [Back to top :top:](#a-react-portfolio-template-for-github) 93 | 94 | ### License 95 | 96 | [MIT](https://choosealicense.com/licenses/mit/) 97 | -------------------------------------------------------------------------------- /README_images/contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/README_images/contact.png -------------------------------------------------------------------------------- /README_images/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/README_images/hero.png -------------------------------------------------------------------------------- /README_images/heroDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/README_images/heroDark.png -------------------------------------------------------------------------------- /README_images/speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/README_images/speed.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-react-portfolio-template", 3 | "homepage": "https://YourUserName.github.io/your-repo/", 4 | "version": "1.0.4", 5 | "private": true, 6 | "dependencies": { 7 | "@iconify/react": "^4.1.0", 8 | "@reduxjs/toolkit": "^2.5.0", 9 | "bootstrap": "^5.3.3", 10 | "prop-types": "^15.8.1", 11 | "react": "^18.3.1", 12 | "react-bootstrap": "^2.10.7", 13 | "react-dom": "^18.3.1", 14 | "react-error-boundary": "^4.1.2", 15 | "react-redux": "^9.2.0", 16 | "react-router-dom": "^6.28.1", 17 | "react-scroll": "^1.8.7", 18 | "sass": "^1.77.6", 19 | "styled-components": "^6.1.14", 20 | "workbox-core": "^7.3.0", 21 | "workbox-expiration": "^7.3.0", 22 | "workbox-precaching": "^7.3.0", 23 | "workbox-routing": "^7.3.0", 24 | "workbox-strategies": "^7.3.0" 25 | }, 26 | "devDependencies": { 27 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 28 | "gh-pages": "^6.3.0", 29 | "react-scripts": "^5.0.1" 30 | }, 31 | "scripts": { 32 | "predeploy": "npm run build", 33 | "deploy": "gh-pages -d build", 34 | "start": "react-scripts start", 35 | "build": "react-scripts build", 36 | "test": "react-scripts test", 37 | "eject": "react-scripts eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": [ 41 | "react-app", 42 | "react-app/jest" 43 | ] 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /public/GH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/public/GH.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 32 | 33 | 42 | React App 43 | 44 | 45 | 46 |
47 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Portfolio", 3 | "name": "My GitHub Portfolio", 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 | "src": "maskable_icon.png", 22 | "type": "image/png", 23 | "sizes": "240x240", 24 | "purpose": "any maskable" 25 | } 26 | ], 27 | "start_url": ".", 28 | "display": "standalone", 29 | "theme_color": "#000000", 30 | "background_color": "#ffffff" 31 | } 32 | -------------------------------------------------------------------------------- /public/maskable_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshuber1981/github-react-portfolio-template/e26304f7601af35cf0ac32ae959694ae5df84ee6/public/maskable_icon.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | // Styles 3 | import { ThemeProvider } from "styled-components"; 4 | // State 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { selectMode, setMode } from "./app/appSlice"; 7 | import { 8 | setProjects, 9 | setMainProjects, 10 | selectProjects, 11 | } from "./app/projectsSlice"; 12 | import { useGetUsersQuery, useGetProjectsQuery } from "./app/apiSlice"; 13 | import PropTypes from "prop-types"; 14 | // Router 15 | import { HashRouter, Routes, Route } from "react-router-dom"; 16 | // Pages 17 | import Home from "./pages/Home"; 18 | import AllProjects from "./pages/AllProjects"; 19 | import NotFound from "./pages/NotFound"; 20 | // Components 21 | import { ErrorBoundary } from "react-error-boundary"; 22 | import AppFallback from "./components/AppFallback"; 23 | import GlobalStyles from "./components/GlobalStyles"; 24 | import ScrollToTop from "./components/ScrollToTop"; 25 | import Loading from "./components/Loading"; 26 | import { Element } from "react-scroll"; 27 | import { Container } from "react-bootstrap"; 28 | import NavBar from "./components/NavBar"; 29 | import Footer from "./components/Footer"; 30 | // Config 31 | import { footerTheme, navLogo } from "./config"; 32 | // Util 33 | import { getStoredTheme, getPreferredTheme, setTheme } from "./utils"; 34 | 35 | // #region component 36 | const propTypes = { 37 | filteredProjects: PropTypes.arrayOf(PropTypes.string), 38 | projectCardImages: PropTypes.arrayOf( 39 | PropTypes.shape({ 40 | name: PropTypes.string.isRequired, 41 | image: PropTypes.node.isRequired, 42 | }) 43 | ), 44 | }; 45 | 46 | const App = ({ projectCardImages = [], filteredProjects = [] }) => { 47 | const theme = useSelector(selectMode); 48 | const projects = useSelector(selectProjects); 49 | const dispatch = useDispatch(); 50 | const { isLoading, isSuccess, isError, error } = useGetUsersQuery(); 51 | const { data: projectsData } = useGetProjectsQuery(); 52 | let content; 53 | 54 | // Set all projects state 55 | React.useEffect(() => { 56 | const tempData = []; 57 | if (projectsData !== undefined && projectsData.length !== 0) { 58 | projectsData.forEach((element) => { 59 | const tempObj = { 60 | id: null, 61 | homepage: null, 62 | description: null, 63 | image: null, 64 | name: null, 65 | html_url: null, 66 | }; 67 | tempObj.id = element.id; 68 | tempObj.homepage = element.homepage; 69 | tempObj.description = element.description; 70 | tempObj.name = element.name; 71 | tempObj.html_url = element.html_url; 72 | tempData.push(tempObj); 73 | }); 74 | if ( 75 | projectCardImages !== (undefined && null) && 76 | projectCardImages.length !== 0 77 | ) { 78 | projectCardImages.forEach((element) => { 79 | tempData.forEach((ele) => { 80 | if (element.name.toLowerCase() === ele.name.toLowerCase()) { 81 | ele.image = element.image; 82 | } 83 | }); 84 | }); 85 | } 86 | dispatch(setProjects(tempData)); 87 | } 88 | }, [projectsData, projectCardImages, dispatch]); 89 | 90 | // Set main projects state 91 | React.useEffect(() => { 92 | if (projects.length !== 0) { 93 | if ( 94 | filteredProjects !== (undefined && null) && 95 | filteredProjects.length !== 0 96 | ) { 97 | const tempArray = projects.filter((obj) => 98 | filteredProjects.includes(obj.name) 99 | ); 100 | tempArray.length !== 0 101 | ? dispatch(setMainProjects([...tempArray])) 102 | : dispatch(setMainProjects([...projects.slice(0, 3)])); 103 | } else { 104 | dispatch(setMainProjects([...projects.slice(0, 3)])); 105 | } 106 | } 107 | }, [projects, filteredProjects, dispatch]); 108 | 109 | // Theme 110 | const setThemes = React.useCallback( 111 | (theme) => { 112 | if (theme) { 113 | dispatch(setMode(theme)); 114 | setTheme(theme); 115 | } else { 116 | dispatch(setMode(getPreferredTheme())); 117 | setTheme(getPreferredTheme()); 118 | } 119 | }, 120 | [dispatch] 121 | ); 122 | 123 | React.useEffect(() => { 124 | setThemes(); 125 | }, [setThemes]); 126 | 127 | window 128 | .matchMedia("(prefers-color-scheme: dark)") 129 | .addEventListener("change", () => { 130 | const storedTheme = getStoredTheme(); 131 | if (storedTheme !== "light" && storedTheme !== "dark") { 132 | setThemes(); 133 | } 134 | }); 135 | 136 | if (isLoading) { 137 | content = ( 138 | 139 | 140 | 141 | ); 142 | } else if (isSuccess) { 143 | content = ( 144 | <> 145 | 146 | setThemes(theme)} /> 147 | 148 | 149 | } /> 150 | } /> 151 | } /> 152 | 153 |