- {this.props.platformData && this.state.width > 500 && (
+ {this.state.width > 500 && (
<>
-
-
+
+
Contributions
+
+
+
+ {this.state.contrib.countTotal}
+
+
Total
+
+ {this.state.contrib.datesTotal}
+
+
+
+
+ {this.state.contrib.maxCount}
+
+
Best day
+
+ {this.state.contrib.dateBest}
+
+
+
+
+ Average:{" "}
+
+ {this.state.contrib.averageCount}
+ {" "}
+ / day
+
+
+
+
Streaks
+
+
+
+ {this.state.streak.streakLongest}{" "}
+
+ {this.state.streak.streakLongest === 1 ? "day" : "days"}
+
+
+
Longest
+
+ {this.state.streak.datesLongest}
+
+
+
+
+ {this.state.streak.streakCurrent}{" "}
+
+ {this.state.streak.streakCurrent === 1 ? "day" : "days"}
+
+
+
Current
+
+ {this.state.streak.datesCurrent}
+
+
+
+
>
)}
{this.state.cache &&

}
@@ -364,7 +386,6 @@ class Calendar3D extends React.Component {
//#endregion
//#region > Exports
-//> Default Class
export default Calendar3D;
//#endregion
diff --git a/src/components/atoms/charts/ContribRadar/index.jsx b/src/components/atoms/charts/ContribRadar/index.jsx
index 1acd626..f57bdc7 100644
--- a/src/components/atoms/charts/ContribRadar/index.jsx
+++ b/src/components/atoms/charts/ContribRadar/index.jsx
@@ -180,7 +180,6 @@ class ContribRadar extends React.Component {
//#endregion
//#region > Exports
-//> Default Class
export default ContribRadar;
//#endregion
diff --git a/src/components/atoms/charts/LanguageBar/index.jsx b/src/components/atoms/charts/LanguageBar/index.jsx
new file mode 100644
index 0000000..4e00db3
--- /dev/null
+++ b/src/components/atoms/charts/LanguageBar/index.jsx
@@ -0,0 +1,76 @@
+//#region > Imports
+//> React
+// Contains all the functionality necessary to define React components
+import React from "react";
+
+//> CSS
+import "./languages.scss";
+//#endregion
+
+//#region > Components
+/**
+ * @class A language chart which contains several items with their shares.
+ */
+class LanguageBar extends React.Component {
+ state = {};
+
+ renderBars = (languages) => {
+ let latest = 0;
+
+ if (languages[0].size !== 0) {
+ return languages.map((language, i) => {
+ const { color, share } = language;
+ const value = latest + share;
+
+ latest += share;
+
+ return (
+
+ );
+ });
+ } else {
+ return (
+
+ );
+ }
+ };
+
+ render() {
+ const { languages, height } = this.props;
+
+ return (
+
+ {this.renderBars(languages)}
+
+ );
+ }
+}
+//#endregion
+
+//#region > Exports
+export default LanguageBar;
+//#endregion
+
+/**
+ * SPDX-License-Identifier: (EUPL-1.2)
+ * Copyright © 2019-2020 Simon Prast
+ */
diff --git a/src/components/atoms/charts/LanguageBar/languages.scss b/src/components/atoms/charts/LanguageBar/languages.scss
new file mode 100644
index 0000000..dd3fc01
--- /dev/null
+++ b/src/components/atoms/charts/LanguageBar/languages.scss
@@ -0,0 +1,27 @@
+.languages {
+ width: "100%";
+ background-color: "#e0e0de";
+ margin: 0.3rem 0;
+ position: relative;
+ transition: height 0.2s ease;
+
+ .filler {
+ height: 100%;
+ border-radius: inherit;
+ text-align: right;
+ position: absolute;
+
+ &:last-child {
+ border-top-right-radius: 0.3rem;
+ border-bottom-right-radius: 0.3rem;
+ }
+
+ border-top-left-radius: 0.3rem;
+ border-bottom-left-radius: 0.3rem;
+ }
+}
+
+/**
+ * SPDX-License-Identifier: (EUPL-1.2)
+ * Copyright © 2019-2020 Simon Prast
+ */
diff --git a/src/components/atoms/charts/LatestActivity/index.jsx b/src/components/atoms/charts/LatestActivity/index.jsx
index 2842de1..fdc8cc6 100644
--- a/src/components/atoms/charts/LatestActivity/index.jsx
+++ b/src/components/atoms/charts/LatestActivity/index.jsx
@@ -175,7 +175,6 @@ class LatestActivity extends React.Component {
//#endregion
//#region > Exports
-//> Default Class
export default LatestActivity;
//#endregion
diff --git a/src/components/atoms/index.js b/src/components/atoms/index.js
index b9e0f83..0b72d1f 100644
--- a/src/components/atoms/index.js
+++ b/src/components/atoms/index.js
@@ -10,6 +10,7 @@ import Calendar2D from "./charts/Calendar2D";
import Calendar3D from "./charts/Calendar3D";
import ContribRadar from "./charts/ContribRadar";
import LatestActivity from "./charts/LatestActivity";
+import LanguageChart from "./charts/LanguageBar";
//> General
import SearchBar from "./SearchBar";
import ErrorBoundary from "./ErrorBoundary";
@@ -25,6 +26,7 @@ export {
Calendar3D,
ContribRadar,
LatestActivity,
+ LanguageChart,
SearchBar,
ErrorBoundary,
};
diff --git a/src/components/molecules/Footer/index.jsx b/src/components/molecules/Footer/index.jsx
index ccaa3b8..7074437 100644
--- a/src/components/molecules/Footer/index.jsx
+++ b/src/components/molecules/Footer/index.jsx
@@ -207,13 +207,13 @@ class Footer extends React.PureComponent {
{this.state.slogan}
{this.props.location.pathname === "/" ? (
-
+
Join now
) : (
-
+
Join now
@@ -254,7 +254,10 @@ class Footer extends React.PureComponent {
//#endregion
//#region > Exports
-//> Default Class
+/**
+ * Got access to the history object’s properties and the closest
+ * 's match.
+ */
export default withRouter(Footer);
//#endregion
diff --git a/src/components/molecules/MovableBoundary/index.jsx b/src/components/molecules/MovableBoundary/index.jsx
new file mode 100644
index 0000000..4429767
--- /dev/null
+++ b/src/components/molecules/MovableBoundary/index.jsx
@@ -0,0 +1,166 @@
+//#region > Imports
+//> React
+// Contains all the functionality necessary to define React components
+import React from "react";
+// React PropTypes
+import PropTypes from "prop-types";
+//> MDB Sortable
+// MDB plugin for sortable item lists
+import MDBSortable from "mdb-react-sortable";
+
+//> CSS
+import "./movableboundary.scss";
+//#endregion
+
+//#region > Components
+/**
+ * @class This enables implementation of horizontally or vertically movable
+ * items into a page.
+ */
+class MovableBoundary extends React.Component {
+ state = {
+ items: this.props.items ? this.props.items : null,
+ indexArray: null,
+ };
+
+ componentWillReceiveProps = (nextProps) => {
+ this.setState({
+ items: nextProps.items ? nextProps.items : null,
+ });
+
+ // If props.edit changes to false, save indexArray
+ if (!nextProps.edit && this.props.edit) {
+ this.saveItemOrder();
+ }
+ };
+
+ componentDidMount() {
+ // Load item order after mounting
+ this.loadItemOrder();
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ // Load items when pool changes
+ if (prevProps.pool !== this.props.pool) {
+ this.loadItemOrder();
+ }
+ }
+
+ /**
+ * Load the indexArray from storage.
+ * If there's no indexArray,
+ * default indexArray [0, 1, 2, 3, ...] will be generated.
+ */
+ loadItemOrder() {
+ let indexArray = this.props.pool[this.props.uid];
+
+ if (!indexArray) {
+ let baseIndexArray = [];
+
+ this.state.items.map((item, i) => {
+ baseIndexArray = [...baseIndexArray, i];
+ });
+
+ indexArray = baseIndexArray;
+ } else {
+ indexArray = JSON.parse(indexArray);
+ }
+
+ this.setState({ indexArray });
+ }
+
+ // Returns items ordered by indexArray
+ reorderItems() {
+ let orderedItems = [];
+
+ this.state.indexArray.map((index) => {
+ orderedItems = [...orderedItems, this.state.items[index]];
+ });
+
+ return orderedItems;
+ }
+
+ // Store indexArray
+ saveItemOrder() {
+ this.props.pool[this.props.uid] = JSON.stringify(this.state.indexArray);
+ }
+
+ // Returns items to be rendered in the Sortable
+ renderItems(edit) {
+ const items = this.reorderItems();
+
+ return items.map((item) => {
+ if (edit) {
+ return {item}
;
+ } else {
+ return (
+
+ {item}
+
+ );
+ }
+ });
+ }
+
+ // Swaps indexArray position of element
+ swap = (newIndex, oldIndex) => {
+ let indexArray = this.state.indexArray;
+
+ if (newIndex >= indexArray.length) {
+ let k = newIndex - indexArray.length + 1;
+
+ while (k--) {
+ indexArray.push(undefined);
+ }
+ }
+
+ indexArray.splice(newIndex, 0, indexArray.splice(oldIndex, 1)[0]);
+
+ // Save item order when items swap
+ this.saveItemOrder();
+ this.setState({ indexArray });
+ };
+
+ render() {
+ return (
+
+ {this.state.items && this.state.indexArray && (
+ <>
+ {
+ // When item is let go of, change indexArray position of item
+ this.swap(obj.newIndex, obj.oldIndex);
+ }}
+ />
+ >
+ )}
+
+ );
+ }
+}
+//#endregion
+
+//#region > PropTypes
+MovableBoundary.propTypes = {
+ pool: PropTypes.object,
+ movementAxis: PropTypes.string,
+ items: PropTypes.array.isRequired,
+ uid: PropTypes.string.isRequired,
+ edit: PropTypes.bool.isRequired,
+};
+//#endregion
+
+//#region > Exports
+export default MovableBoundary;
+//#endregion
+
+/**
+ * SPDX-License-Identifier: (EUPL-1.2)
+ * Copyright © 2019-2020 Simon Prast
+ */
diff --git a/src/components/molecules/MovableBoundary/movableboundary.scss b/src/components/molecules/MovableBoundary/movableboundary.scss
new file mode 100644
index 0000000..65d28c3
--- /dev/null
+++ b/src/components/molecules/MovableBoundary/movableboundary.scss
@@ -0,0 +1,30 @@
+.SortableItem {
+ list-style: none;
+ cursor: move;
+ outline: none;
+ user-select: text !important;
+
+ // View when sorting is enabled
+ &:not(.unsortable) {
+ background: transparentize($color: white, $amount: 0.5) !important;
+ margin-bottom: 1rem;
+ border-radius: 0.3rem;
+ padding-top: 0.3rem;
+ user-select: none !important;
+ }
+}
+
+.SortableList {
+ display: grid;
+ grid: initial;
+ padding: 0;
+}
+
+.unsortable {
+ cursor: default;
+}
+
+/**
+ * SPDX-License-Identifier: (EUPL-1.2)
+ * Copyright © 2019-2020 Simon Prast
+ */
diff --git a/src/components/molecules/Navbar/index.jsx b/src/components/molecules/Navbar/index.jsx
index 02713c8..701825e 100644
--- a/src/components/molecules/Navbar/index.jsx
+++ b/src/components/molecules/Navbar/index.jsx
@@ -1,9 +1,9 @@
//#region > Imports
//> React
// Contains all the functionality necessary to define React components
-import React, { lazy, Suspense } from "react";
+import React from "react";
// DOM bindings for React Router
-import { Link, withRouter } from "react-router-dom";
+import { Link, withRouter, NavLink } from "react-router-dom";
// React PropTypes
import PropTypes from "prop-types";
//> MDB
@@ -17,21 +17,26 @@ import {
MDBCollapse,
MDBContainer,
MDBDropdown,
- MDBDropdownItem,
MDBDropdownToggle,
MDBDropdownMenu,
MDBSmoothScroll,
MDBBtn,
+ MDBIcon,
} from "mdbreact";
+//> Redux
+// Allows to React components read data from a Redux store, and dispatch actions
+// to the store to update data.
+import { connect } from "react-redux";
-//> Searchbar
+//> Actions
+// Functions to send data from the application to the store
+import { logoutAction } from "../../../store/actions/authActions";
+//> SearchBar
import { SearchBar } from "../../atoms";
//> Images
import SNEKLogo from "../../../assets/navigation/logo.png";
//> CSS
import "./navbar.scss";
-//> Components
-const Settings = lazy(() => import("../modals/SettingsModal"));
//#endregion
//#region > Components
@@ -59,120 +64,115 @@ class Navbar extends React.Component {
};
render() {
- const { globalState, globalFunctions, location } = this.props;
+ const { location, loggedUser } = this.props;
+ const avatarUrl = loggedUser.platformData?.user?.avatarUrl
+ ? loggedUser.platformData.user.avatarUrl
+ : loggedUser.avatarUrl;
return (
- <>
-
-
- {location.pathname === "/" ? (
-
-
-
- SNEK
-
-
- ) : (
- <>
- {!globalState.loading && globalState.loggedUser ? (
-
-
-
- SNEK
-
+
+
+ {location.pathname === "/" ? (
+
+
+
+ SNEK
+
+
+ ) : (
+ <>
+ {!loggedUser.anonymous ? (
+
+
+
+ SNEK
+
+
+ ) : (
+
+
+
+ SNEK
+
+
+ )}
+ >
+ )}
+
+
+
+
+
+
+
+
+ {!loggedUser.anonymous ? (
+ <>
+
+
+
+ Profile
+
- ) : (
-
-
-
- SNEK
-
-
- )}
- >
- )}
-
-
-
-
-
-
-
-
- {!globalState.loading && globalState.loggedUser ? (
- <>
-
-
-
-
-
-
-
-
- My profile
-
-
- this.setState({ showSettings: true })
- }
- >
- Settings
-
-
- Sign Out
-
-
-
-
- >
- ) : (
- <>
- {location.pathname !== "/" && (
-
+
+
+
+
+
+
+
+
+ Settings
+
+
+ Sign Out
+
+
+
+
+ >
+ ) : (
+ <>
+ {location.pathname !== "/" && (
+
+
Sign In
- )}
- >
- )}
-
-
-
-
- {this.state.showSettings && (
- Loading... }>
-