Skip to content

Commit

Permalink
Adding RTK Query, updating social links functionality.
Browse files Browse the repository at this point in the history
Adding prop-types and recfactoring project
  • Loading branch information
mshuber1981 committed Jun 9, 2024
1 parent eae2935 commit cbd49df
Show file tree
Hide file tree
Showing 39 changed files with 1,241 additions and 971 deletions.
221 changes: 95 additions & 126 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
"private": true,
"dependencies": {
"@iconify/react": "^4.1.0",
"@reduxjs/toolkit": "^1.9.7",
"@reduxjs/toolkit": "^2.2.5",
"bootstrap": "^5.3.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.0",
"react-dom": "^18.2.0",
"react-redux": "^8.1.3",
"react-error-boundary": "^4.0.13",
"react-redux": "^9.1.2",
"react-router-dom": "^6.22.0",
"react-scroll": "^1.8.7",
"styled-components": "^6.1.8",
Expand Down
10 changes: 2 additions & 8 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,8 @@
content="Built with React and the GitHub REST API."
/>
<!-- Add an image for social media - https://css-tricks.com/essential-meta-tags-social-media/ -->
<meta
property="og:image"
content="https://YourUsername.github.io/your-app/GH.png"
/>
<meta
property="og:url"
content="https://YourUsername.github.io/your-app/"
/>
<meta property="og:image" content="" />
<meta property="og:url" content="" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image:alt" content="React and GitHub" />
<meta property="og:site_name" content="GitHub Portfolio" />
Expand Down
200 changes: 144 additions & 56 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import React from "react";
// Styles
import { ThemeProvider } from "styled-components";
// State
import { useAppContext } from "./appContext";
import { useDispatch, useSelector } from "react-redux";
import {
fetchGitHubInfo,
selectError,
selectIsLoading,
} from "./pages/homeSlice";
import { fetchGitHubReops } from "./pages/allProjectsSlice";
setProjects,
setMainProjects,
selectProjects,
} from "./app/projectsSlice";
import { useGetUsersQuery, useGetProjectsQuery } from "./app/apiSlice";
import PropTypes from "prop-types";
// Router
import { HashRouter, Routes, Route } from "react-router-dom";
import { Element } from "react-scroll";
import { ThemeProvider } from "styled-components";
// Data
import { navLogo } from "./data";
// Components
import { Container } from "react-bootstrap";
import { Loading } from "./components/globalStyledComponents";
import ScrollToTop from "./components/ScrollToTop";
import GlobalStyles from "./components/GlobalStyles";
import NavBar from "./components/NavBar";
// Pages
import Home from "./pages/Home";
import AllProjects from "./pages/AllProjects";
import NotFound from "./pages/NotFound";
// Components
import { ErrorBoundary } from "react-error-boundary";
import AppFallback from "./components/AppFallback";
import GlobalStyles from "./components/GlobalStyles";
import ScrollToTop from "./components/ScrollToTop";
import Loading from "./components/Loading";
import { Element } from "react-scroll";
import { Container } from "react-bootstrap";
import NavBar from "./components/NavBar";
import Footer from "./components/Footer";
// Config
import { navLogo } from "./config";

// #region constants
const darkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
const themes = {
light: {
Expand All @@ -36,23 +44,90 @@ const themes = {
background: "#27272A",
},
};
// #endregion

export default function App() {
// #region component
const propTypes = {
filteredProjects: PropTypes.arrayOf(PropTypes.string),
projectCardImages: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
image: PropTypes.node.isRequired,
})
),
};
const defaultProps = {
filteredProjects: [],
projectCardImages: [],
};

const App = ({ projectCardImages, filteredProjects }) => {
const { theme, setTheme } = useAppContext();
const isLoading = useSelector(selectIsLoading);
const error = useSelector(selectError);
const projects = useSelector(selectProjects);
const dispatch = useDispatch();
const { isLoading, isSuccess, isError, error } = useGetUsersQuery();
const { data: projectsData } = useGetProjectsQuery();
let content;

React.useEffect(
function () {
const updateTheme = () =>
darkMode ? setTheme("dark") : setTheme("light");
updateTheme();
dispatch(fetchGitHubInfo());
dispatch(fetchGitHubReops());
},
[setTheme, dispatch]
);
// Set all projects state
React.useEffect(() => {
const tempData = [];
if (projectsData !== undefined && projectsData.length !== 0) {
projectsData.forEach((element) => {
const tempObj = {
id: null,
homepage: null,
description: null,
image: null,
name: null,
html_url: null,
};
tempObj.id = element.id;
tempObj.homepage = element.homepage;
tempObj.description = element.description;
tempObj.name = element.name;
tempObj.html_url = element.html_url;
tempData.push(tempObj);
});
if (
projectCardImages !== (undefined && null) &&
projectCardImages.length !== 0
) {
projectCardImages.forEach((element) => {
tempData.forEach((ele) => {
if (element.name.toLowerCase() === ele.name.toLowerCase()) {
ele.image = element.image;
}
});
});
}
dispatch(setProjects(tempData));
}
}, [projectsData, projectCardImages, dispatch]);

// Set main projects state
React.useEffect(() => {
if (projects.length !== 0) {
if (
filteredProjects !== (undefined && null) &&
filteredProjects.length !== 0
) {
const tempArray = projects.filter((obj) =>
filteredProjects.includes(obj.name)
);
tempArray.length !== 0
? dispatch(setMainProjects([...tempArray]))
: dispatch(setMainProjects([...projects.slice(0, 3)]));
} else {
dispatch(setMainProjects([...projects.slice(0, 3)]));
}
}
}, [projects, filteredProjects, dispatch]);

React.useEffect(() => {
const updateTheme = () => (darkMode ? setTheme("dark") : setTheme("light"));
updateTheme();
}, [setTheme]);

window
.matchMedia("(prefers-color-scheme: dark)")
Expand All @@ -61,39 +136,52 @@ export default function App() {
);

if (isLoading) {
return (
<ThemeProvider theme={themes[theme]}>
<GlobalStyles />
<Container className="d-flex vh-100 align-items-center">
<Loading />
</Container>
</ThemeProvider>
content = (
<Container className="d-flex vh-100 align-items-center">
<Loading />
</Container>
);
} else if (isSuccess) {
content = (
<>
<Element name={"Home"} id="home">
<NavBar Logo={navLogo} />
</Element>
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/All-Projects" element={<AllProjects />} />
<Route path="*" element={<NotFound />} />
</Routes>
<Footer />
</>
);
} else if (error) {
return (
<ThemeProvider theme={themes[theme]}>
<GlobalStyles />
<Container className="d-flex vh-100 align-items-center justify-content-center">
<h2>{error}</h2>
</Container>
</ThemeProvider>
} else if (isError) {
content = (
<Container className="d-flex vh-100 align-items-center justify-content-center">
<h2>
{error.status !== "FETCH_ERROR"
? `${error.status}: ${error.data.message} - check githubUsername in src/config.js`
: `${error.status} - check URLs in src/app/apiSlice.js`}
</h2>
</Container>
);
} else {
return (
}

return (
<ErrorBoundary FallbackComponent={AppFallback}>
<HashRouter>
<ThemeProvider theme={themes[theme]}>
<ScrollToTop />
<GlobalStyles />
<Element name={"Home"} id="home">
<NavBar Logo={navLogo} />
</Element>
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/All-Projects" element={<AllProjects />} />
<Route path="*" element={<NotFound />} />
</Routes>
{content}
</ThemeProvider>
</HashRouter>
);
}
}
</ErrorBoundary>
);
};

App.propTypes = propTypes;
App.defaultProps = defaultProps;
// #endregion

export default App;
16 changes: 13 additions & 3 deletions src/app/apiSlice.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
// https://redux-toolkit.js.org/rtk-query/overview
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// Config
import { gitHubUsername } from "../data";
import { githubUsername } from "../config";

export const apiSlice = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: "https://api.github.com" }),
endpoints: (builder) => ({
// https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-a-user
getUsers: builder.query({
query: () => `/users/${gitHubUsername}`,
query: () => `/users/${githubUsername}`,
}),
// https://docs.github.com/en/rest/users/social-accounts?apiVersion=2022-11-28#list-social-accounts-for-a-user
getSocials: builder.query({
query: () => `/users/${githubUsername}/social_accounts`,
}),
// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-a-user
getProjects: builder.query({
query: () => `/users/${githubUsername}/repos`,
}),
}),
});

export const { useGetUsersQuery } = apiSlice;
export const { useGetUsersQuery, useGetSocialsQuery, useGetProjectsQuery } =
apiSlice;
26 changes: 26 additions & 0 deletions src/app/projectsSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// https://redux-toolkit.js.org/usage/usage-guide#simplifying-slices-with-createslice
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
projects: [],
mainProjects: [],
};

export const projectsSlice = createSlice({
name: "projects",
initialState,
reducers: {
setProjects: (state, action) => {
state.projects = action.payload;
},
setMainProjects: (state, action) => {
state.mainProjects = action.payload;
},
},
});

export const selectProjects = (state) => state.projects.projects;
export const selectMainProjects = (state) => state.projects.mainProjects;
export const { setProjects, setMainProjects } = projectsSlice.actions;

export default projectsSlice.reducer;
2 changes: 2 additions & 0 deletions src/app/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import { configureStore } from "@reduxjs/toolkit";
//Reducers
import appReducer from "./appSlice";
import projectsReducer from "./projectsSlice";
// API
import { apiSlice } from "./apiSlice";

export const store = configureStore({
reducer: {
app: appReducer,
projects: projectsReducer,
[apiSlice.reducerPath]: apiSlice.reducer,
},
middleware: (getDefaultMiddleware) => {
Expand Down
Loading

0 comments on commit cbd49df

Please sign in to comment.