DEV Community

Cover image for Building a Github Portfolio
Emmanuel Os
Emmanuel Os

Posted on

Building a Github Portfolio

Building a GitHub portfolio using ReactJS is a great way to showcase your skills as a developer and stand out. In this article, we will walk through the steps of building a GitHub portfolio using React.

  1. Create a GitHub account
    The first step in building a GitHub portfolio is to create a GitHub account.

  2. Create a new repository
    Once you have a GitHub account, you can create a new repository to store your portfolio. A repository is a place where you can store your code and collaborate with others on a project. To create a new repository, go to your GitHub profile and click the "New" button. Give your repository a name and description, and choose whether it will be public or private.

  3. Set up the project
    Once you have created your repository, you will need to set up the project. To do this, you will need to initialize the project with Node.js and install the necessary dependencies. You can do this by running the following commands in your terminal:

npx create-react-app github-app
cd github-app
npm start
Enter fullscreen mode Exit fullscreen mode

This will create a new React project in a folder called "github-app", and install the necessary dependencies.

folder structure image

  • we need to install some more dependencies.
npm install react-router-dom
npm install react-icons
npm install react-error-boundary
Enter fullscreen mode Exit fullscreen mode

Now we need to use the react-router-dom to create routes in the react app, so we can switch from one page to another, using the react-router-dom link.

  • we will open the scr folder and create a folder called pages inside the src. That's where we will create the pages components we will use in our react app.
  1. Home.jsx
  2. ErrorPage.jsx
  3. NotFound.jsx
  4. Repos.jsx
  5. Repo.jsx These are the pages components we will use in building out our github portfolio app.
  • inside src we will create another folder called components. inside it we will create some little components for our app.
  • Navbar.jsx
  • Footer.jsx
  • HomeRepoItem.jsx
  • Pagination.jsx
  • RepoItems.jsx
  • SafeComponent.jsx
  • Spinner.jsx
  • UserProfile.jsx
    Phew!!, Lot of components created.

  • we will open the App.js inside the src. That's where we are going to implement our react-router-dom, because is the parent component.

import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import "./App.css";

function App() {
  return (
    <>
      <div className="App">
        <Router>
          <Navbar />
            <Routes>
             <Route path="/" element={<Home />} />
             <Route path="/repo-list/*" element={<Repos />} />
             <Route path="/error-boundary-page" element={<ErrorPage />} />
             <Route path="/not-found" element={<NotFound />} />
             <Route path="*" element={<NotFound />} />
            </Routes>
         </Router>
      </div>
      <Footer />
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
  • The css file for this project is inside the index.css

  • Inside our Navbar.js we will write these codes to create a navbar we will use in our app.

import { useState } from "react";
import { NavLink, useLocation } from "react-router-dom";
import { FaBug, FaUser, FaAtom, FaHome } from "react-icons/fa";

function Navbar() {
  const [navOpen, setNavOpen] = useState(false);

  const location = useLocation();
  const handleNavOpen = () => {
    setNavOpen((prev) => !prev);
  };

  return (
    <nav className={navOpen ? "nav open" : "nav close"}>
      <header onClick={handleNavOpen}>
        <FaAtom />
      </header>
      <ul>
        <li className={location.pathname === "/" ? "link-hover" : "none"}>
          <NavLink to="/">
            <span className="icon">
              <FaHome />
            </span>
            <span className="path">User</span>
          </NavLink>
        </li>
        <li
          className={location.pathname === "/repo-list" ? "link-hover" : "none"}
        >
          <NavLink to="/repo-list">
            <span className="icon">
              <FaUser />
            </span>
            <span className="path">Repos</span>
          </NavLink>
        </li>
        <li
          className={
            location.pathname === "/error-boundary-page" ? "link-hover" : "none"
          }
        >
          <NavLink to="/error-boundary-page">
            <span className="icon">
              <FaBug />
            </span>
            <span className="path">Error</span>
          </NavLink>
        </li>
      </ul>
    </nav>
  );
}

export default Navbar;
Enter fullscreen mode Exit fullscreen mode
  • Inside our Footer.jsx we will write these codes to create a footer we will use in our app
import React from "react";

function Footer() {
  return (
    <footer>
      <div className="footer-container">
        <p>© 2022 by "Put your name or anything"</p>
      </div>
    </footer>
  );
}

export default Footer;
Enter fullscreen mode Exit fullscreen mode
  • Inside our Spinner.jsx we will write these codes to create a spinner we will use in our app
import React from "react";
import { FaSpinner } from "react-icons/fa";

function Spinner() {
  return (
    <div>
      <div className="spinner-container">
        <FaSpinner className="spinner" />
      </div>
    </div>
  );
}

export default Spinner;
Enter fullscreen mode Exit fullscreen mode
  • we will create a context folder inside the src folder. we will use react context because it will be an easy way to manage state globally. It can be used together with the useState Hook to share state between deeply nested components more easily than with useState alone.
  • inside context folder we will these files.
  • UserContext.js
  • RepoContext.js

  • Inside our UserContext.js we will write these codes to handle our github user.

import { createContext, useState, useEffect } from "react";

const UserContext = createContext();

export const UserProvider = ({ children }) => {
  const [user, setUser] = useState({});
  const [loading, setLoading] = useState(true);

  const url = process.env.REACT_APP_GITHUB_URL;
  const token = process.env.REACT_APP_GITHUB_TOKEN;

  useEffect(() => {
    fetchUser();
  }, []);

  // fetching the user
  const fetchUser = async () => {
    const response = await fetch(`${url}/users/josephe44`, {
      headers: {
        Authorization: `token ${token}`,
      },
    });
    const data = await response.json();
    setUser(data);
    setLoading(false);
  };

  return (
    <UserContext.Provider value={{ user, loading }}>
      {children}
    </UserContext.Provider>
  );
};

export default UserContext;
Enter fullscreen mode Exit fullscreen mode
  • Inside our RepoContext.js we will write these codes to handle our github repo.
import { createContext, useState, useEffect } from "react";

const RepoContext = createContext();

export const RepoProvider = ({ children }) => {
  const [loading, setLoading] = useState(true);
  const [repos, setRepos] = useState([]);
  const [repo, setRepo] = useState({});
  const [currentPage, setCurrentPage] = useState(1);
  const [repoPerPage] = useState(9);

  const url = process.env.REACT_APP_GITHUB_URL;
  const token = process.env.REACT_APP_GITHUB_TOKEN;

  useEffect(() => {
    fetchRepos();
  }, []);

  //   fetch all repos
  const fetchRepos = async () => {
    const params = new URLSearchParams({
      sort: "created",
      per_page: 36,
    });
    const response = await fetch(`${url}/users/josephe44/repos?${params}`, {
      headers: {
        Authorization: `token ${token}`,
      },
    });
    const data = await response.json();
    setRepos(data);
    setLoading(false);
  };

  //   fetch a repo by the name
  const fetchRepo = async (repoName) => {
    const response = await fetch(`${url}/repos/josephe44/${repoName}`, {
      headers: {
        Authorization: `token ${token}`,
      },
    });
    const data = await response.json();
    setRepo(data);
    setLoading(false);
  };

  // Pagination logic
  const indexOfLastNumber = currentPage * repoPerPage;
  const indexOfFirstNumber = indexOfLastNumber - repoPerPage;
  const currentRepo = repos.slice(indexOfFirstNumber, indexOfLastNumber);
  const numberOfPages = Math.ceil(repos.length / repoPerPage);

  return (
    <RepoContext.Provider
      value={{
        repo,
        repos,
        loading,
        currentRepo,
        numberOfPages,
        currentPage,
        setCurrentPage,
        fetchRepo,
      }}
    >
      {children}
    </RepoContext.Provider>
  );
};

export default RepoContext;
Enter fullscreen mode Exit fullscreen mode

Now we have our logic been written inside the context, we can now use it inside any component. But before that we need to wrap the context with all the pages that was created inside the parent component App.js

import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { Navbar, Footer } from "./components";
import { Home, ErrorPage, NotFound, Repos } from "./pages";
import { RepoProvider } from "./context/RepoContext";
import { UserProvider } from "./context/UserContext";
import "./App.css";

function App() {
  return (
    <>
      <RepoProvider>
        <UserProvider>
          <div className="App">
            <Router>
              <Navbar />
              <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/repo-list/*" element={<Repos />} />
                <Route path="/error-boundary-page" element={<ErrorPage />} />
                <Route path="/not-found" element={<NotFound />} />
                <Route path="*" element={<NotFound />} />
              </Routes>
            </Router>
          </div>
          <Footer />
        </UserProvider>
      </RepoProvider>
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now we will be building out all the pages we have listed above, starting from Home.jsx.
Inside the Home.jsx we have two component to build out, which we will use.

  • UserProfile.jsx
import React, { useContext } from "react";
import { Link } from "react-router-dom";
import {
  FaFileAlt,
  FaMapMarkerAlt,
  FaPencilAlt,
  FaUsers,
  FaUserFriends,
  FaRegEnvelope,
} from "react-icons/fa";
import UserContext from "../context/users/UserContext";

function UserProfile() {
  const { user } = useContext(UserContext);
  return (
    <>
      <section className="user-container">
        <div className="user">
          <div className="img-card">
            <img src={user.avatar_url} alt="profile-img" />
          </div>
          <div className="user-details">
            <h2>{user.name}</h2>
            <p className="username">{user.login}</p>
            <p className="user-bio">{user.bio}</p>
            <section className="social-container">
              <div className="social-card">
                <div>
                  <FaUsers />
                </div>
                <p>{user.followers}</p>
              </div>

              <div className="social-card">
                <div>
                  <FaFileAlt />
                </div>
                <p>{user.public_repos}</p>
              </div>
              <div className="social-card">
                <div>
                  <FaUserFriends />
                </div>
                <p>{user.following}</p>
              </div>
              <div className="social-card">
                <div>
                  <FaPencilAlt />
                </div>
                <p>{user.public_gists}</p>
              </div>
            </section>
            <div className="contact">
              <div className="social-flex">
                <div className="location">
                  <p>
                    <FaMapMarkerAlt />
                  </p>
                  <p>
                    <span> {user.location}</span>
                  </p>
                </div>
              </div>
              <div className="email">
                <p>
                  <FaRegEnvelope />
                </p>
                <p>
                  <span>{user.email}</span>
                </p>
              </div>

              <div className="group-btn">
                <div className="portfolio-btn">
                  <button>
                    <a target="_blank" rel="noreferrer" href={user.html_url}>
                      <span>View Profile</span>
                    </a>
                  </button>
                </div>
                <div className="portfolio-btn">
                  <button>
                    <Link to="repo-list">
                      <span>View More Repo</span>
                    </Link>
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </>
  );
}

export default UserProfile;
Enter fullscreen mode Exit fullscreen mode
  • HomeRepoItem.jsx
import React, { useContext } from "react";
import RepoContext from "../context/repos/RepoContext";

function HomeRepoItem() {
  const { currentRepo } = useContext(RepoContext);

  const repos = currentRepo.slice(0, 6);
  return (
    <div>
      <div className="repo-container">
        {repos.map((repo) => (
          <div key={repo.id}>
            <div className="repo-card">
              <div className="repo-flex">
                <div className="repo-header">
                  <h4>{repo.name}</h4>
                  <div className="repo-avatar">
                    <img src={repo.owner.avatar_url} alt="avatar" />
                  </div>
                </div>
                <p className="desc">{repo.description}</p>

                <div className="repo-desc">
                  <p>{repo.owner.login}</p>
                  <p>{repo.language ? repo.language : ""}</p>
                </div>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

export default HomeRepoItem;
Enter fullscreen mode Exit fullscreen mode

So inside the Home.jsx we will import it and use those components.

import React, { useContext } from "react";
import { UserProfile, Spinner, HomeRepoItem } from "../components";
import UserContext from "../context/users/UserContext";

function Home() {
  const { loading } = useContext(UserContext);

  if (loading) {
    return <Spinner />;
  }

  return (
    <div className="home">
      <UserProfile />
      <HomeRepoItem />
    </div>
  );
}

export default Home;
Enter fullscreen mode Exit fullscreen mode

Now we will be build out the Repos page we listed above.
Inside the Repos.jsx we have two component to build out, which we will use.

  • RepoItems.jsx
import React, { useContext } from "react";
import { Link } from "react-router-dom";
import RepoContext from "../context/repos/RepoContext";

function RepoItems() {
  const { currentRepo } = useContext(RepoContext);
  const repos = currentRepo;

  return (
    <div className="repo-bottom">
      <div className="repo-container">
        {repos.map((repo) => (
          <div key={repo.id}>
            <Link to={`repo/${repo.name}`}>
              <div className="repo-card">
                <div className="repo-flex">
                  <div className="repo-header">
                    <h4>{repo.name}</h4>
                    <div className="repo-avatar">
                      <img src={repo.owner.avatar_url} alt="avatar" />
                    </div>
                  </div>
                  <p className="desc">{repo.description}</p>

                  <div className="repo-desc">
                    <p>{repo.owner.login}</p>
                    <p>{repo.language ? repo.language : ""}</p>
                  </div>
                </div>
              </div>
            </Link>
          </div>
        ))}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • Pagination.jsx
import React, { useState, useEffect, useContext } from "react";
import { FaAngleDoubleLeft, FaAngleDoubleRight } from "react-icons/fa";
import RepoContext from "../context/repos/RepoContext";

function Pagination() {
  const { numberOfPages, currentPage, setCurrentPage } =
    useContext(RepoContext);
  const [disabledPrev, setDisabledPrev] = useState(true);
  const [disabledNext, setDisabledNext] = useState(true);

  const pageNumbers = [...Array(numberOfPages + 1).keys()].slice(1);

  const nextPage = () => {
    if (currentPage !== numberOfPages) setCurrentPage(currentPage + 1);
  };
  const prevPage = () => {
    if (currentPage !== 1) setCurrentPage(currentPage - 1);
  };

  useEffect(() => {
    if (currentPage > 1) {
      setDisabledPrev(false);
    } else {
      setDisabledPrev(true);
    }

    if (currentPage === numberOfPages) {
      setDisabledNext(true);
    } else {
      setDisabledNext(false);
    }
  }, [currentPage, numberOfPages]);
  return (
    <>
      <section>
        <ul className="pagination">
          <li>
            <button
              className={disabledPrev ? "disable-btn" : "page-link"}
              onClick={prevPage}
            >
              <FaAngleDoubleLeft />
            </button>
          </li>
          {pageNumbers.map((pgNumber) => (
            <li key={pgNumber}>
              <button
                onClick={() => setCurrentPage(pgNumber)}
                className="page-link"
              >
                {pgNumber}
              </button>
            </li>
          ))}
          <li>
            <button
              className={disabledNext ? "disable-btn" : "page-link"}
              onClick={nextPage}
            >
              <FaAngleDoubleRight />
            </button>
          </li>
        </ul>
      </section>
    </>
  );
}

export default Pagination;
Enter fullscreen mode Exit fullscreen mode

So inside the Repos.jsx we will import it and use those components.

import React, { useContext } from "react";
import { useLocation, Routes, Route } from "react-router-dom";
import { RepoItems, Spinner, Pagination } from "../components";
import RepoContext from "../context/repos/RepoContext";
import Repo from "./Repo";

function Repos() {
  const { loading } = useContext(RepoContext);

  const location = useLocation();

  if (loading) {
    return <Spinner />;
  }

  return (
    <>
      <div className="home">
        {location.pathname === "/repo-list" ? (
          <>
            <RepoItems />
            <Pagination />
          </>
        ) : (
          <Routes>
            <Route path="repo/:repoName" element={<Repo />} />
          </Routes>
        )}
      </div>
    </>
  );
}

export default Repos;
Enter fullscreen mode Exit fullscreen mode

Now we will be build out the Repo page we listed above.

  • Repo.jsx
import { useEffect, useContext } from "react";
import { Link, useParams } from "react-router-dom";
import { FaAngleDoubleLeft } from "react-icons/fa";
import { Spinner } from "../components";
import RepoContext from "../context/repos/RepoContext";

function Repo() {
  const { loading, repo, fetchRepo } = useContext(RepoContext);
  const { repoName } = useParams();

  useEffect(() => {
    fetchRepo(repoName);
  }, [repoName]);

  if (loading) return <Spinner />;

  return (
    <div className="eachRepo-container">
      <div>
        <Link to="/repo-list">
          <button className="back-btn">
            <FaAngleDoubleLeft />
            <span> Back</span>
          </button>
        </Link>
      </div>
      <div className="eachRepo-card">
        <div className="eachRepo-item">
          <div>
            <h3>{repo.name}</h3>
            <p>{repo.description}</p>
            <div className="eachRepo-visit">
              <button className="visit-button">
                <a target="_blank" rel="noreferrer" href={repo.html_url}>
                  view on github
                </a>
              </button>
            </div>
          </div>
          <div className="banner-grid">
            <p className="eachRepo-banner">Fork: {repo.forks_count}</p>
            <p className="eachRepo-banner">
              Language: {repo.language ? repo.language : "none"}
            </p>
            <p className="eachRepo-banner">File Size: {repo.size}kb</p>
            <p className="eachRepo-banner">Visibility : {repo.visibility}</p>
            <p className="eachRepo-banner">Watchers : {repo.watchers}</p>
            <p className="eachRepo-banner">Open Issues : {repo.open_issues}</p>
            <p className="eachRepo-banner">
              Network Count : {repo?.network_count}
            </p>
            {repo?.parent?.default_branch ? (
              <p className="eachRepo-banner">
                Branch : {repo?.parent.default_branch}
              </p>
            ) : null}

            {repo?.license?.name ? (
              <p className="eachRepo-banner">License: {repo.license.name}</p>
            ) : null}
          </div>
        </div>
      </div>
    </div>
  );
}

export default Repo;
Enter fullscreen mode Exit fullscreen mode

Now we will be build out the NotFound page we listed above.

  • NotFound.jsx
import React from "react";
import { Link } from "react-router-dom";
import { FaReply, FaRegTired } from "react-icons/fa";

function NotFound() {
  return (
    <div className="not-found">
      <h1>404</h1>
      <div className="not-found-icon">
        <div>
          <FaRegTired />
        </div>
        <p>Page Not Found</p>
      </div>
      <button className="not-found-btn">
        <Link to="/">
          <div>
            <FaReply />
          </div>
          <span>Go Back Home</span>
        </Link>
      </button>
    </div>
  );
}

export default NotFound;
Enter fullscreen mode Exit fullscreen mode

Inside the ErrorPage.jsx we have one component to build out, which we will use.

  • SafeComponent
function SafeComponent({ error }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre style={{ color: "red" }}>{error.message}</pre>
    </div>
  );
}

export default SafeComponent;
Enter fullscreen mode Exit fullscreen mode

Now we will be build out the ErrorPage page we listed above.

  • ErrorPage.jsx
import React from "react";
import { ErrorBoundary } from "react-error-boundary";
import { SafeComponent } from "../components";

function ErrorPage() {
  return (
    <div className="error">
      <h1>Implementation of ErrorBoundary</h1>
      <ErrorBoundary FallbackComponent={SafeComponent}>
        <Error />
      </ErrorBoundary>
    </div>
  );
}

function Error() {
  throw new Error("Error");
}

export default ErrorPage;
Enter fullscreen mode Exit fullscreen mode

Top comments (0)