diff --git a/.env.template b/.env.template index c2e4ef7..7299a46 100644 --- a/.env.template +++ b/.env.template @@ -1,2 +1,8 @@ REACT_APP_ANONYMOUS_USER=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -REACT_APP_VERSION=$npm_package_version \ No newline at end of file +REACT_APP_VERSION=$npm_package_version +FLAG_CONSOLE_USER=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +FLAG_BOOKWORM=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +FLAG_FAKER=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +FLAG_PATH_EXPLORER=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +FLAG_REAL_GAMER=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +FLAG_INSPECTOR=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ No newline at end of file diff --git a/package.json b/package.json index f671938..35e9444 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "homepage": "https://snek.at", "license": "(EUPL-1.2)", "dependencies": { + "faker": "^5.1.0", "fuzzysort": "^1.1.4", "gh-pages": "^3.1.0", "js-sha256": "^0.9.0", @@ -18,14 +19,15 @@ "react-redux": "^7.2.0", "react-redux-loading-bar": "^4.6.0", "react-router-dom": "^5.0.1", + "react-simple-maps": "^2.1.2", "react-text-loop": "^2.3.0", "react-typed": "^1.2.0", - "reactjs-oauth": "0.1.0", + "reactjs-oauth": "^0.2.0", "redux": "^4.0.5", "redux-thunk": "^2.3.0", "serialize-error": "^7.0.1", - "snek-client": "file:snek-client-0.2.0-beta.tgz", - "snek-intel": "^0.1.1", + "snek-client": "file:snek-client-0.2.0-rc.7.tgz", + "snek-intel": "file:snek-intel-0.2.0-rc.6.tgz", "tippy.js": "^6.2.3" }, "repository": { @@ -40,11 +42,11 @@ "eject": "react-scripts eject" }, "devDependencies": { - "renamer": "^1.0.0", - "rimraf": "^2.6.2", "react": "^16.8.6", "react-dom": "^16.8.6", - "react-scripts": "^3.4.1" + "react-scripts": "^4.0.0", + "renamer": "^1.0.0", + "rimraf": "^2.6.2" }, "browserslist": [ ">0.2%", diff --git a/public/index.html b/public/index.html index 96946a0..22bd469 100644 --- a/public/index.html +++ b/public/index.html @@ -10,6 +10,9 @@ name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> + @@ -76,4 +79,9 @@ To create a production bundle, use `npm run build` or `yarn build`. --> + diff --git a/src/App.js b/src/App.js index bb0f031..3bb75b2 100644 --- a/src/App.js +++ b/src/App.js @@ -13,7 +13,7 @@ import LoadingBarContainer from "react-redux-loading-bar"; //> Actions // Functions to send data from the application to the store -import { loginAction } from "./store/actions/authActions"; +import { loginAction } from "./store/actions/userActions"; //> Components import { Footer, Navbar } from "./components/molecules"; // Starts the page on top when reloaded or redirected diff --git a/src/Routes.js b/src/Routes.js index d981784..2fb7ab9 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -8,11 +8,10 @@ import { Route, Switch } from "react-router-dom"; //> Components import { HomePage, - ProfilePage, + PersonPage, CompanyPage, TalkPage, SettingsPage, - TempPage, } from "./components/pages"; //#endregion @@ -26,7 +25,7 @@ class Routes extends React.Component { } + component={(props) => } /> } /> - } - /> } /> + + Here is a flag for you {process.env.FLAG_PATH_EXPLORER}. Keep on + searching and find them all. + + ); + }} + /> Not Found; diff --git a/src/components/atoms/Achievement/index.jsx b/src/components/atoms/Achievement/index.jsx new file mode 100644 index 0000000..e8b0254 --- /dev/null +++ b/src/components/atoms/Achievement/index.jsx @@ -0,0 +1,83 @@ +//#region > Imports +//> React +// Contains all the functionality necessary to define React components +import React from "react"; +// Runtime type checking for React props and similar objects +import PropTypes from "prop-types"; +//> MDB +// "Material Design for Bootstrap" is a great UI design framework +import { + MDBCol, + MDBIcon, + MDBRow, + MDBPopover, + MDBPopoverHeader, + MDBPopoverBody, +} from "mdbreact"; +//#endregion + +//#region > Components +/** @class A component to display an achievement */ +class Achievement extends React.Component { + state = { + isVisible: false, + }; + + togglePopover = () => { + this.setState({ + isVisible: !this.state.isVisible, + }); + }; + + render() { + const { achievement } = this.props; + + return ( + { + this.togglePopover(); + }} + onMouseLeave={() => { + this.togglePopover(); + }} + > + +
  • + + + + + + + {achievement.title} + + X days ago + + +
  • +
    + {achievement.title} + {achievement.description} +
    +
    +
    + ); + } +} +//#endregion + +//#region > Exports +//> Default Component +export default Achievement; +//#endregion + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/atoms/Project/index.jsx b/src/components/atoms/Project/index.jsx index 84f8722..ee4890d 100644 --- a/src/components/atoms/Project/index.jsx +++ b/src/components/atoms/Project/index.jsx @@ -35,7 +35,7 @@ class Project extends React.Component { /> @ - {repo.owner.username ? ( + {repo.owner ? ( repo.owner.username ) : ( unknown @@ -43,19 +43,17 @@ class Project extends React.Component {
    - {repo.languages.length > 0 && ( - - )} - {repo.languages.length > 0 && ( - - - {repo.languages[0].name ? repo.languages[0].name : "Unknown"} - - )} + + + + {repo.languages ? repo.languages[0].name : "Unknown"} +
    diff --git a/src/components/atoms/SearchBar/index.jsx b/src/components/atoms/SearchBar/index.jsx index 32204fc..eab315c 100644 --- a/src/components/atoms/SearchBar/index.jsx +++ b/src/components/atoms/SearchBar/index.jsx @@ -22,7 +22,7 @@ import { connect } from "react-redux"; //> Actions // Functions to send data from the application to the store -import { getUserSearchItems } from "../../../store/actions/generalActions"; +import { getPersonsBrief } from "../../../store/actions/generalActions"; //> Style sheet import "./search.scss"; //#endregion @@ -52,8 +52,6 @@ class SearchBar extends React.Component { handleSelection = (event, value) => { if (event === "user") { this.props.history.push("/u/" + value); - } else if (event === "search_page") { - this.props.history.push("/search?q=" + value); } }; @@ -84,24 +82,28 @@ class SearchBar extends React.Component { {!this.state.loading && this.state.searchItems ? ( - this.state.searchItems.length > 0 && this.state.filter.length > 0 ? ( - + this.state.searchItems.length > 0 && + this.state.filter.length > 0 ? ( fuzzysort .go(this.state.filter, this.state.searchItems, { key: "title" }) .map((element, i) => { return ( - {element.target} + {element.obj.slug.split("-")[1]} ); }) ) : null ) : ( - Loading - )} + Loading + )} ); @@ -111,12 +113,12 @@ class SearchBar extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - allUserSearchItems: state.general.allUserSearchItems, + allUserSearchItems: state.general.allPersonBrief, }); const mapDispatchToProps = (dispatch) => { return { - allsearchItems: () => dispatch(getUserSearchItems()), + allsearchItems: () => dispatch(getPersonsBrief()), }; }; //#endregion diff --git a/src/components/atoms/charts/AIContribCalendar/index.jsx b/src/components/atoms/charts/AIContribCalendar/index.jsx index 98fb231..ad80a51 100644 --- a/src/components/atoms/charts/AIContribCalendar/index.jsx +++ b/src/components/atoms/charts/AIContribCalendar/index.jsx @@ -80,7 +80,7 @@ class AIContribCalendar extends React.Component { // Get current month let current; - if (this.props.year) { + if (this.props.year !== undefined) { current = 0; } else { current = new Date().getMonth(); diff --git a/src/components/atoms/charts/AILanguageChart/index.jsx b/src/components/atoms/charts/AILanguageChart/index.jsx deleted file mode 100644 index a8cc899..0000000 --- a/src/components/atoms/charts/AILanguageChart/index.jsx +++ /dev/null @@ -1,77 +0,0 @@ -//#region > Imports -//> React -// Contains all the functionality necessary to define React components -import React from "react"; - -//> Style sheet -import "./languages.scss"; -//#endregion - -//#region > Components -/** - * @class A language chart which contains several items with their shares. - */ -class AILanguageChart 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 -//> Default Component -export default AILanguageChart; -//#endregion - -/** - * SPDX-License-Identifier: (EUPL-1.2) - * Copyright © 2020 Simon Prast - */ diff --git a/src/components/atoms/charts/AILanguageChart/languages.scss b/src/components/atoms/charts/AILanguageChart/languages.scss deleted file mode 100644 index 2c37c75..0000000 --- a/src/components/atoms/charts/AILanguageChart/languages.scss +++ /dev/null @@ -1,27 +0,0 @@ -.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 © 2020 Simon Prast - */ diff --git a/src/components/atoms/charts/AILineChart/index.jsx b/src/components/atoms/charts/AILineChart/index.jsx index 48dfbbd..770ff63 100644 --- a/src/components/atoms/charts/AILineChart/index.jsx +++ b/src/components/atoms/charts/AILineChart/index.jsx @@ -79,8 +79,6 @@ class AILineChart extends React.Component { init = () => { const data = this.props.data; - console.log("DATAAAAA", data); - if (data) { const year = this.props.year; const weeks = @@ -101,24 +99,21 @@ class AILineChart extends React.Component { : val.total ); - this.setState( - { - dataBar: { - ...this.state.dataBar, - labels: dates, - datasets: [ - { - data: contribs, - borderColor: "#77bd43", - fill: false, - borderWidth: 1, - lineTension: 0.4, - }, - ], - }, + this.setState({ + dataBar: { + ...this.state.dataBar, + labels: dates, + datasets: [ + { + data: contribs, + borderColor: "#77bd43", + fill: false, + borderWidth: 1, + lineTension: 0.4, + }, + ], }, - () => console.log(this.state) - ); + }); } }; diff --git a/src/components/atoms/charts/Calendar2D/index.jsx b/src/components/atoms/charts/Calendar2D/index.jsx index 6af5382..a8e4f28 100644 --- a/src/components/atoms/charts/Calendar2D/index.jsx +++ b/src/components/atoms/charts/Calendar2D/index.jsx @@ -61,19 +61,18 @@ class Calender2D extends React.Component { }; setCalendar = async (props) => { - if (props.platformData) { + const { currentStatistic, yearsStatistic, year } = props; + if (currentStatistic && yearsStatistic) { // Get contribution data let contribData; - if (props.year) { - contribData = props.platformData.statistic.years.find( - (element) => element.year === props.year - ); + if (year !== undefined) { + contribData = yearsStatistic[year]; } else { - contribData = props.platformData.statistic.current; + contribData = currentStatistic; } - let contributions = contribData.calendar; + let contributions = JSON.parse(contribData.calendarData); this.setState({ width: this.myInput.current.offsetWidth, @@ -96,7 +95,7 @@ class Calender2D extends React.Component { // Get current month let current; - if (this.props.year) { + if (this.props.year !== undefined) { current = 0; } else { current = new Date().getMonth(); @@ -156,7 +155,9 @@ class Calender2D extends React.Component { }; render() { - if (this.props.platformData) { + const { currentStatistic, yearsStatistic, year } = this.props; + + if (currentStatistic && yearsStatistic) { return (
    diff --git a/src/components/atoms/charts/Calendar3D/index.jsx b/src/components/atoms/charts/Calendar3D/index.jsx index a5036fa..e070fa1 100644 --- a/src/components/atoms/charts/Calendar3D/index.jsx +++ b/src/components/atoms/charts/Calendar3D/index.jsx @@ -2,6 +2,8 @@ //> React // Contains all the functionality necessary to define React components import React from "react"; +// Runtime type checking for React props and similar objects +import PropTypes from "prop-types"; //> Additional packages // Used to display the time in a readable format import moment from "moment"; @@ -31,7 +33,7 @@ class Calendar3D extends React.Component { } componentDidMount = () => { - if (this.props.platformData) { + if (this.props.currentStatistic && this.props.yearsStatistic) { // Add resize listener window.addEventListener("resize", this.updateDimensions); @@ -79,22 +81,18 @@ class Calendar3D extends React.Component { renderTopStats() { let countTotal, averageCount, datesTotal, maxCount, dateBest, contribData; - - if (this.props.year) { - contribData = this.props.platformData.statistic.years.find( - (element) => element.year === this.props.year - ); + if (this.props.year !== undefined) { + contribData = this.props.yearsStatistic[this.props.year]; } else { - contribData = this.props.platformData.statistic.current; + contribData = this.props.currentStatistic; } - let contributionCalendar = contribData.calendar; + let contributionCalendar = JSON.parse(contribData.calendarData); + let contributionDetails = JSON.parse(contribData.contributionTypeData); + let busiestDay = contribData.busiestDay; - countTotal = contribData.contributions.total; - averageCount = - Math.round( - (contribData.contributions.total / 365 + Number.EPSILON) * 100 - ) / 100; + countTotal = contributionDetails.total; + averageCount = Math.round((countTotal / 365 + Number.EPSILON) * 100) / 100; datesTotal = moment(contributionCalendar.startDate).format("MMM D") + @@ -102,7 +100,7 @@ class Calendar3D extends React.Component { moment(contributionCalendar.endDate).format("MMM D"); /* Busiest day */ - maxCount = contribData.busiestDay.total; + maxCount = busiestDay.total; dateBest = moment(contribData.busiestDay.date); dateBest = dateBest.isValid() ? dateBest.format("MMM DD") : "-"; @@ -118,31 +116,35 @@ class Calendar3D extends React.Component { renderBottomStats() { let streakLongest, datesLongest, streakCurrent, datesCurrent, contribData; - if (this.props.year) { - contribData = this.props.platformData.statistic.years.find( - (element) => element.year === this.props.year - ); + if (this.props.year !== undefined) { + contribData = this.props.yearsStatistic[this.props.year]; } else { - contribData = this.props.platformData.statistic.current; + contribData = this.props.currentStatistic; } - if (contribData.streak.longest) { - streakLongest = contribData.streak.longest.totalDays; + if ( + contribData.longestStreak && + contribData.longestStreak.totalDays !== null + ) { + streakLongest = contribData.longestStreak.totalDays; datesLongest = - moment(contribData.streak.longest.startDate).format("MMM D") + + moment(contribData.longestStreak.startDate).format("MMM D") + " → " + - moment(contribData.streak.longest.endDate).format("MMM D"); + moment(contribData.longestStreak.endDate).format("MMM D"); } else { streakLongest = "0"; datesLongest = "-"; } - if (contribData.streak.current.id !== -1) { - streakCurrent = contribData.streak.current.totalDays; + if ( + contribData.currentStreak && + contribData.currentStreak.totalDays != null + ) { + streakCurrent = contribData.currentStreak.totalDays; datesCurrent = - moment(contribData.streak.current.startDate).format("MMM D") + + moment(contribData.currentStreak.startDate).format("MMM D") + " → " + - moment(contribData.streak.current.endDate).format("MMM D"); + moment(contribData.currentStreak.endDate).format("MMM D"); } else { streakCurrent = "0"; datesCurrent = "-"; @@ -169,15 +171,13 @@ class Calendar3D extends React.Component { // Get contributions of the selected year let contribData; - if (this.props.year) { - contribData = this.props.platformData.statistic.years.find( - (element) => element.year === this.props.year - ); + if (this.props.year !== undefined) { + contribData = this.props.yearsStatistic[this.props.year]; } else { - contribData = this.props.platformData.statistic.current; + contribData = this.props.currentStatistic; } - let contributions = contribData.calendar; + let contributions = JSON.parse(contribData.calendarData); // Define basic variables let size = 2 * Math.round(this.state.width / 80 / 2); @@ -397,6 +397,13 @@ class Calendar3D extends React.Component { } //#endregion +//#region > PropTypes +Calendar3D.propTypes = { + currentStatistic: PropTypes.object, + yearsStatistic: PropTypes.array, +}; +//#endregion + //#region > Exports //> Default Component export default Calendar3D; diff --git a/src/components/atoms/charts/ContribRadar/index.jsx b/src/components/atoms/charts/ContribRadar/index.jsx index 086ecde..f27a2bf 100644 --- a/src/components/atoms/charts/ContribRadar/index.jsx +++ b/src/components/atoms/charts/ContribRadar/index.jsx @@ -103,40 +103,28 @@ class ContribRadar extends React.Component { } }; - calculateSources = (nextPropsYear) => { - const { statistic } = this.props; + calculateSources = (nextPropsYearIndex) => { + const { yearsStatistic, currentStatistic } = this.props; let totalReviews = 0; let totalIssues = 0; let totalRequests = 0; let totalCommits = 0; - let totalSources = 1; - let year; + let contribData; let results = []; - if (nextPropsYear === null) { - year = this.props.year; + if (nextPropsYearIndex) { + contribData = yearsStatistic[nextPropsYearIndex]; } else { - year = nextPropsYear; + contribData = currentStatistic; } - if (year) { - let selectedYear = statistic.years.find( - (element) => element.year === year - ); - - totalIssues = selectedYear.contributions.issue.share; - totalRequests = selectedYear.contributions.pullRequest.share; - totalCommits = selectedYear.contributions.commit.share; - totalReviews = selectedYear.contributions.pullRequestReview.share; - } else { - let contributions = statistic.current.contributions; + let contributionDetails = JSON.parse(contribData.contributionTypeData); - totalIssues = contributions.issue.share; - totalRequests = contributions.pullRequest.share; - totalCommits = contributions.commit.share; - totalReviews = contributions.pullRequestReview.share; - } + totalIssues = contributionDetails.issue.share; + totalRequests = contributionDetails.pullRequest.share; + totalCommits = contributionDetails.commit.share; + totalReviews = contributionDetails.pullRequestReview.share; let values = [totalReviews, totalIssues, totalRequests, totalCommits]; @@ -151,14 +139,6 @@ class ContribRadar extends React.Component { data: values, }); - // Calculate averages - let avgReviews, avgIssues, avgRequests, avgCommits; - - avgReviews = parseInt(totalReviews) / parseInt(totalSources); - avgIssues = parseInt(totalIssues) / parseInt(totalSources); - avgRequests = parseInt(totalRequests) / parseInt(totalSources); - avgCommits = parseInt(totalCommits) / parseInt(totalSources); - this.fillChart(results); } }; diff --git a/src/components/atoms/charts/LanguageBar/index.jsx b/src/components/atoms/charts/LanguageBar/index.jsx index ddf69c1..e6d2e04 100644 --- a/src/components/atoms/charts/LanguageBar/index.jsx +++ b/src/components/atoms/charts/LanguageBar/index.jsx @@ -17,7 +17,7 @@ class LanguageBar extends React.Component { renderBars = (languages) => { let latest = 0; - if (languages[0].size !== 0) { + if (languages && languages[0].size !== 0) { return languages.map((language, i) => { const { color, share } = language; const value = latest + share; diff --git a/src/components/atoms/charts/LatestActivity/index.jsx b/src/components/atoms/charts/LatestActivity/index.jsx index b572edc..848d4ea 100644 --- a/src/components/atoms/charts/LatestActivity/index.jsx +++ b/src/components/atoms/charts/LatestActivity/index.jsx @@ -117,19 +117,18 @@ class LatestActivity extends React.Component { }; calculateSources = (year, activity) => { - const { statistic } = this.props; + const { yearsStatistic, currentStatistic } = this.props; - if (statistic) { + if (yearsStatistic && currentStatistic) { // Real data let contribData, week, lastWeek, lastWeekValues; - if (!year) { - contribData = statistic.current; + if (year) { + contribData = yearsStatistic[year]; } else { - contribData = statistic.years.find((element) => element.year === year); + contribData = currentStatistic; } - - const weeks = contribData.calendar.weeks; + const weeks = JSON.parse(contribData.calendarData).weeks; if (!activity) { week = weeks[weeks.length - 1]; diff --git a/src/components/atoms/index.js b/src/components/atoms/index.js index 9107f6c..9e4aef4 100644 --- a/src/components/atoms/index.js +++ b/src/components/atoms/index.js @@ -11,11 +11,11 @@ import Calendar3D from "./charts/Calendar3D"; import ContribRadar from "./charts/ContribRadar"; import LatestActivity from "./charts/LatestActivity"; import LanguageChart from "./charts/LanguageBar"; +import Achievement from "./Achievement"; //> Enterprise page import AIBarChart from "./charts/AIBarChart"; import AILineChart from "./charts/AILineChart"; import AIContribCalendar from "./charts/AIContribCalendar"; -import AILanguageChart from "./charts/AILanguageChart"; //> General import SearchBar from "./SearchBar"; import ErrorBoundary from "./ErrorBoundary"; @@ -34,11 +34,11 @@ export { ContribRadar, LatestActivity, LanguageChart, + Achievement, /* Enterprise page */ AIBarChart, AILineChart, AIContribCalendar, - AILanguageChart, /* General */ SearchBar, ErrorBoundary, diff --git a/src/components/molecules/MovableBoundary/index.jsx b/src/components/molecules/MovableBoundary/index.jsx index b7d0fd5..a3eed8b 100644 --- a/src/components/molecules/MovableBoundary/index.jsx +++ b/src/components/molecules/MovableBoundary/index.jsx @@ -62,8 +62,6 @@ class MovableBoundary extends React.Component { }); indexArray = baseIndexArray; - } else { - indexArray = JSON.parse(indexArray); } this.setState({ indexArray }); diff --git a/src/components/molecules/Navbar/index.jsx b/src/components/molecules/Navbar/index.jsx index 9fd2ef1..d79e6e6 100644 --- a/src/components/molecules/Navbar/index.jsx +++ b/src/components/molecules/Navbar/index.jsx @@ -30,7 +30,7 @@ import { connect } from "react-redux"; //> Actions // Functions to send data from the application to the store -import { logoutAction } from "../../../store/actions/authActions"; +import { logoutAction } from "../../../store/actions/userActions"; //> SearchBar import { SearchBar } from "../../atoms"; //> Images @@ -65,9 +65,7 @@ class Navbar extends React.Component { render() { const { location, loggedUser } = this.props; - const avatarUrl = loggedUser.platformData?.user?.avatarUrl - ? loggedUser.platformData.user.avatarUrl - : loggedUser.avatarUrl; + const avatarUrl = loggedUser?.person?.avatarImage?.src; return ( @@ -85,7 +83,7 @@ class Navbar extends React.Component { ) : ( <> - {!loggedUser.anonymous ? ( + {loggedUser.anonymous === false ? ( - {!loggedUser.anonymous ? ( + {loggedUser.anonymous === false ? ( <> @@ -141,7 +139,7 @@ class Navbar extends React.Component { Settings @@ -186,7 +184,7 @@ Navbar.propTypes = { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: { ...state.auth.loggedUser, ...state.user.loggedUser }, + loggedUser: { ...state.user.user }, }); const mapDispatchToProps = (dispatch) => { diff --git a/src/components/molecules/UserActionCard/index.jsx b/src/components/molecules/UserActionCard/index.jsx index 999c998..c701e0e 100644 --- a/src/components/molecules/UserActionCard/index.jsx +++ b/src/components/molecules/UserActionCard/index.jsx @@ -44,64 +44,25 @@ class UserActionCard extends React.Component {
    {activeItem === 0 && ( <> - this.setActiveItem(1)}> + this.setActiveItem(1)}> + Login to SNEK
    -
    +
    or
    -

    Choose your snek

    -

    What is your main profession?

    - - -
    this.setActiveItem(2)} - > -

    Software Engineer

    - -
    -
    - -
    this.setActiveItem(3)} - > -

    Media Engineer

    - -
    -
    -
    +

    Sign up

    +

    + Join the biggest network for engineers today! +

    + )} {activeItem === 1 && } - {activeItem === 2 && } - {activeItem === 3 && ( - <> -
    - this.goTo(0)} - > - - Back - -
    - -

    - - Media Engineer profiles are not yet supported -

    - this.goTo(2)}> - Create Software Engineer profile - -
    - - )}
    ); } diff --git a/src/components/molecules/forms/LoginForm/index.jsx b/src/components/molecules/forms/LoginForm/index.jsx index 07dcfab..878d4d0 100644 --- a/src/components/molecules/forms/LoginForm/index.jsx +++ b/src/components/molecules/forms/LoginForm/index.jsx @@ -7,9 +7,6 @@ import PropTypes from "prop-types"; //> MDB // "Material Design for Bootstrap" is a great UI design framework import { MDBAlert, MDBBtn, MDBIcon } from "mdbreact"; -//> Additional -// SHA hashing algorithm -import { sha256 } from "js-sha256"; //> Redux // Allows to React components read data from a Redux store, and dispatch actions // to the store to update data. @@ -17,7 +14,7 @@ import { connect } from "react-redux"; //> Actions // Functions to send data from the application to the store -import { loginAction } from "../../../../store/actions/authActions"; +import { loginAction } from "../../../../store/actions/userActions"; //#endregion //#region > Components @@ -130,7 +127,7 @@ class LoginForm extends React.Component { // Proceed to login const result = await this.props.login({ username: this.state.login_username, - password: sha256(this.state.login_password), // Hash password + password: this.state.login_password, // Hash password }); //#TSID6 @@ -190,7 +187,7 @@ class LoginForm extends React.Component { } value={this.state.login_password} /> - + Login diff --git a/src/components/molecules/forms/RegisterForm/index.jsx b/src/components/molecules/forms/RegisterForm/index.jsx index 5aa8832..1fe5ec7 100644 --- a/src/components/molecules/forms/RegisterForm/index.jsx +++ b/src/components/molecules/forms/RegisterForm/index.jsx @@ -29,18 +29,17 @@ import { MDBListGroup, MDBListGroupItem, } from "mdbreact"; -//> OAuth -import GitHubOAuth from "reactjs-oauth"; //> 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"; - //> Actions // Functions to send data from the application to the store -import { registerAction } from "../../../../store/actions/userActions"; -import { fetchGitLabServersAction } from "../../../../store/actions/generalActions"; -import { loginAction } from "../../../../store/actions/authActions"; +import { + register, + isValidUsername, + loginAction, +} from "../../../../store/actions/userActions"; //#endregion //#region > Components @@ -51,178 +50,15 @@ import { loginAction } from "../../../../store/actions/authActions"; class RegisterForm extends React.Component { state = { loading: false, + errors: [], + username: "", firstname: "", lastname: "", email: "", password1: "", password2: "", - username: "", - gitlab_username: "", - gitlab_servers: undefined, - gitlab_server: "Choose your organisation", - sourceList: [], - usernames: [], - promoCode: true, - code: "", - }; - - componentDidMount = () => { - this.getGitLabServers(); - }; - - toggle = () => { - if (!this.state.modalGitLab) { - this.setState({ - modalGitLab: true, - }); - } else { - this.setState({ - modalGitLab: false, - }); - } - }; - - getGitLabServers = async () => { - // Check if GitLab Servers have already been set - if (this.state.gitlab_servers === undefined) { - // Retrieve GitLab servers - this.props.fetchGitLabServers().then(() => { - this.setState({ - gitlab_servers: this.props.gitlabServers, - }); - }); - } - }; - - addGitLab = () => { - const username = this.state.gitlab_username; - const server = this.state.gitlab_server; - - //#TSID7 - //console.log("REGISTER FORM ADD GITLAB", username, server); - - if (username.trim() && server.trim()) { - if (server !== "Choose your organisation") { - this.connectGitLab(username, "https://" + server); - } - } - }; - - connectGitLab = async (username, platformUrl) => { - this.setState( - { - modalGitLab: false, - gitlab_username: "", - gitlab_server: "Choose your organisation", - }, - () => this.pushToSourceList("gitlab", username, platformUrl) - ); - }; - - oauthGitHubSuccess = (response) => { - this.setState( - { - loadingGitHub: true, - }, - async () => { - this.pushToSourceList( - "github", - response.username, - "https://api.github.com/graphql", - response.accessToken - ); - } - ); - }; - - oauthGitHubFailure = (response) => { - //#ERROR - console.error(response); - }; - - pushToSourceList = (platformName, username, platformUrl, token) => { - let sourceList = this.state.sourceList; - - this.addToUsernames(username, platformName); - - sourceList.push({ - id: username.length + platformName.length + username + platformName, - user: username, - authorization: token ? "token " + token : null, - platform: { - name: platformName, - url: platformUrl, - }, - }); - - if (platformName === "github") { - this.setState({ - username, - hasGitHub: true, - sourceList, - loadingGitHub: false, - }); - } else { - // Set the new list of user information - this.setState({ - sourceList, - }); - } - }; - - addToUsernames = (username, source) => { - let usernames = this.state.usernames; - let found = false; - - for (let i = 0; i < usernames.length; i++) { - if ( - usernames[i].username === username && - usernames[i].source === source - ) { - found = true; - break; - } - } - - if (!found) { - // Make sure that only GitHub usernames are available for selection - // This aims to prevent name abuse in the first versions of this application - usernames.push({ - id: username.length + source.length + username + source, - username, - source, - verified: source === "github" ? true : false, - }); - this.setState({ - usernames, - }); - } - }; - - removeSource = (id) => { - let sourceList = this.state.sourceList.filter(function (obj) { - return obj.id !== id; - }); - let usernames = this.state.usernames.filter(function (obj) { - return obj.id !== id; - }); - - this.setState({ - sourceList, - usernames, - }); - }; - - handleUserNamePick = (username) => { - this.setState({ - username, - }); - }; - - handleSelectChange = (e) => { - this.setState({ - gitlab_server: e[0], - }); + redemptionCodeValue: "", + redemptionCode: true, }; testForError = (id) => { @@ -278,6 +114,25 @@ class RegisterForm extends React.Component { ); }; + handleCodeChange = (e) => { + let code = e.target.value; + + if (code.length <= 14) { + if (code.length === 4 || code.length === 9) { + code = code + "-"; + } + + this.setState( + { + redemptionCodeValue: code.toUpperCase(), + }, + () => this.removeError(9) + ); + } else { + return false; + } + }; + removeError = (id) => { // Preset errors to local variable let errors = this.state.errors; @@ -301,25 +156,17 @@ class RegisterForm extends React.Component { } }; - // Handle sumbit of register form handleSubmit = async () => { - //#TSID8 - //console.log("REGISTER FORM HANDLE SUBMIT"); - - // CHANGE TO CONST - let { - password1, - password2, + const { + username, firstname, lastname, email, - sourceList, - username, - promoCode, - code, + password1, + password2, + redemptionCodeValue, + redemptionCode, } = this.state; - - // Error let errors = []; // Check if passwords match @@ -331,14 +178,6 @@ class RegisterForm extends React.Component { }); } - if (sourceList.length === 0) { - errors.push({ - code: 2, - msg: "No platforms are connected.", - weight: 10, - }); - } - if (firstname === "") { errors.push({ code: 3, @@ -363,10 +202,12 @@ class RegisterForm extends React.Component { }); } - if (username === "") { + const isUsernameTaken = !(await this.props.isValidUsername(username)); + + if (username === "" || (username && isUsernameTaken)) { errors.push({ code: 6, - msg: "Please select a username from the list above.", + msg: "Please enter a valid username.", weight: 10, }); } @@ -387,7 +228,7 @@ class RegisterForm extends React.Component { }); } - if (code === "") { + if (redemptionCodeValue === "" && redemptionCode) { errors.push({ code: 9, msg: "Please enter your promo code or contact us to receive one.", @@ -400,53 +241,21 @@ class RegisterForm extends React.Component { { loading: true, }, - () => { - //#TSID9 - //console.log("REGISTER FORM REGISTER", this.state); - - let registrationData = { - sources: sourceList, + async () => { + await this.props.register( username, + firstname, + lastname, email, - first_name: firstname, - last_name: lastname, - gift_code: promoCode && code !== "" ? code : null, - password: password1, - }; + password1, + redemptionCodeValue ? redemptionCodeValue : "none" + ); - this.props.register(registrationData).then(() => { - const { username, password } = registrationData; - - this.props.login({ - username, - password, - }); - }); + await this.props.login(username, password1); } ); } else { - this.setState({ - errors, - }); - } - }; - - handleCodeChange = (e) => { - let code = e.target.value; - - if (code.length <= 14) { - if (code.length === 4 || code.length === 9) { - code = code + "-"; - } - - this.setState( - { - code: code.toUpperCase(), - }, - () => this.removeError(9) - ); - } else { - return false; + this.setState({ errors }); } }; @@ -455,385 +264,128 @@ class RegisterForm extends React.Component { return ( <> - {!this.state.loading ? ( - <> -
    - this.setState({ errors: [] }, () => goTo(0))} - > - - Back - -
    -

    So you're a Software Engineer...

    -

    - We just need a bit more information to get you started. -

    -
    - - - this.handleChange(e, 3)} - value={this.state.firstname} - /> - - - this.handleChange(e, 4)} - value={this.state.lastname} - /> - - + + Username + this.handleChange(e, 6)} + value={this.state.username} + /> + + + Firstname this.handleChange(e, 5)} - value={this.state.email} + name="firstname" + onChange={(e) => this.handleChange(e, 3)} + value={this.state.firstname} /> - - - this.handleChange(e, [7, 1])} - value={this.state.password1} - /> - - - this.handleChange(e, [8, 1])} - value={this.state.password2} - /> - - - -
    - - this.setState({ promoCode: !this.state.promoCode }) - } - > - {!this.state.promoCode - ? "I have a promo code" - : "I don't have a promo code"} - -
    - {this.state.promoCode && ( +
    + + Lastname this.handleChange(e, 4)} + value={this.state.lastname} /> - )} -

    Connect your work

    - - You need to connect at least one account to continue. - -
    - - - - Why do I need to connect my accounts? - -
    - - - Connecting accounts - - - To generate your expressive and meaningful profile, we - require data about your work, which we acquire by fetching - it from platforms like GitHub, GitLab and BitBucket. It also - helps us verify your data. -
    - - Learn more - -
    -
    -
    -
    -
    - this.setState({ modalGitLab: true })} - disabled={ - !this.state.gitlab_servers || - (this.state.gitlab_server && - this.state.gitlab_server.length < 1) + + + E-Mail + this.handleChange(e, 5)} + value={this.state.email} + /> + + + Password + - - - {!process.env.NODE_ENV || - process.env.NODE_ENV === "development" ? ( - - ) : ( - - )} - - - -
    -
    - - {this.state.loadingGitHub && } - {this.state.usernames.map((source, i) => { - return ( - -
    - - {source.username} - {source.verified ? ( - - - - -
    - Verified - - - - - - - This source has been{" "} - - verified - {" "} - by logging into it. - - - -
    -
    - ) : ( - - - - -
    - Not verified - - - - - - - We can not verify your identity with GitLab. - Your data is still being included. - - - -
    -
    - )} -
    - this.removeSource(source.id)} - /> -
    - ); - })} -
    -
    - - Join now - -

    - - Don't worry, you can easily connect further accounts in the - future. - -

    - - ) : ( -
    -

    - Hey, {this.state.firstname}! -

    -

    Your profile is being generated.

    -
    -
    -
    - -
    - )} - {this.state.modalGitLab && ( - - - - Add GitLab profile - - + name="password1" + onChange={(e) => this.handleChange(e, [7, 1])} + value={this.state.password1} + /> +
    + + Confirm password - this.setState({ [e.target.name]: e.target.value }) + type="password" + className={ + this.testForError([8, 1]) + ? "form-control error" + : "form-control" } - value={this.state.gitlab_username} + name="password2" + onChange={(e) => this.handleChange(e, [8, 1])} + value={this.state.password2} /> - - - - - Choose your organisation - - {this.state.gitlab_servers && - this.state.gitlab_servers.map((source, i) => { - return ( - - {source.organisation} - - ); - })} - - - - - - - Add - - - Cancel - - - + +
    + +
    + + this.setState({ redemptionCode: !this.state.redemptionCode }) + } + > + {!this.state.redemptionCode + ? "I have a promo code" + : "I don't have a promo code"} + +
    + {this.state.redemptionCode && ( + )} + + + Join now + ); } @@ -848,17 +400,28 @@ RegisterForm.propTypes = { //#region > Redux Mapping const mapStateToProps = (state) => ({ - registrationHistory: state.user.registrationHistory, - gitlabServers: state.general.allGitlabServers, + test: "", }); const mapDispatchToProps = (dispatch) => { return { - register: (registrationData) => dispatch(registerAction(registrationData)), - login: (user) => dispatch(loginAction(user)), - fetchGitLabServers: () => dispatch(fetchGitLabServersAction()), + register: ( + username, + firstName, + lastName, + email, + password, + redemptionCode + ) => + dispatch( + register(username, firstName, lastName, email, password, redemptionCode) + ), + login: (username, password) => + dispatch(loginAction({ username, password })), + isValidUsername: (username) => dispatch(isValidUsername(username)), }; }; + //#endregion //#region > Exports diff --git a/src/components/molecules/index.js b/src/components/molecules/index.js index fae23e9..51c0f52 100644 --- a/src/components/molecules/index.js +++ b/src/components/molecules/index.js @@ -5,7 +5,7 @@ import Footer from "./Footer"; import Navbar from "./Navbar"; import LoginForm from "./forms/LoginForm"; import RegisterForm from "./forms/RegisterForm"; -import TalkUploadModal from "./modals/TalkUploadModal"; +import UploadModal from "./modals/UploadModal"; import UserActionCard from "./UserActionCard"; import MovableBoundary from "./MovableBoundary"; //#endregion @@ -17,7 +17,7 @@ export { Navbar, LoginForm, RegisterForm, - TalkUploadModal, + UploadModal, UserActionCard, MovableBoundary, }; diff --git a/src/components/molecules/modals/AddSongModal/index.jsx b/src/components/molecules/modals/AddSongModal/index.jsx index 8e4a33d..28adf45 100644 --- a/src/components/molecules/modals/AddSongModal/index.jsx +++ b/src/components/molecules/modals/AddSongModal/index.jsx @@ -120,7 +120,7 @@ class AddSongModal extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, + //loggedUser: state.auth.loggedUser, }); const mapDispatchToProps = (dispatch) => { diff --git a/src/components/molecules/modals/AddVideoModal/index.jsx b/src/components/molecules/modals/AddVideoModal/index.jsx index 125b24b..cddff16 100644 --- a/src/components/molecules/modals/AddVideoModal/index.jsx +++ b/src/components/molecules/modals/AddVideoModal/index.jsx @@ -94,7 +94,7 @@ class AddVideoModal extends React.Component {

    Add YouTube Video

    this.props.save(this.state)} > @@ -134,7 +134,7 @@ class AddVideoModal extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, + //loggedUser: state.auth.loggedUser, }); const mapDispatchToProps = (dispatch) => { diff --git a/src/components/molecules/modals/ConnectModal/connectmodal.scss b/src/components/molecules/modals/ConnectModal/connectmodal.scss new file mode 100644 index 0000000..31d7e7a --- /dev/null +++ b/src/components/molecules/modals/ConnectModal/connectmodal.scss @@ -0,0 +1,12 @@ +#connectmodal { + .btn { + i { + margin: 0; + } + } +} + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/ConnectModal/index.jsx b/src/components/molecules/modals/ConnectModal/index.jsx new file mode 100644 index 0000000..43c8ea2 --- /dev/null +++ b/src/components/molecules/modals/ConnectModal/index.jsx @@ -0,0 +1,388 @@ +//#region > Imports +//> React +// Contains all the functionality necessary to define React components +import React from "react"; +//> Time management +import moment from "moment"; +//> Redux +// Allows React components to read data, update data and dispatch actions +// from/to a Redux store. +import { connect } from "react-redux"; +//> MDB +// "Material Design for Bootstrap" is a great UI design framework +import { + MDBBtn, + MDBModal, + MDBModalBody, + MDBIcon, + MDBFormInline, + MDBRow, + MDBCol, + MDBModalFooter, + MDBModalHeader, + MDBSelect, + MDBSelectInput, + MDBSelectOption, + MDBSelectOptions, +} from "mdbreact"; + +//> Actions +// Functions to send data from the application to the store +import { addProfile } from "../../../../store/actions/personActions"; +import { getGitlabServers } from "../../../../store/actions/generalActions"; +//> OAuth +import { GitHubOAuth, InstagramOAuth } from "reactjs-oauth"; +//> Style +import "./connectmodal.scss"; +//#endregion + +//#region > Components +class ConnectModal extends React.Component { + state = { sourceList: [], gitlab_server: undefined }; + + componentDidMount = () => { + this.props.getGitlabServers(); + }; + + componentDidUpdate = () => { + if (!this.state.gitlab_servers && this.props.gitlabServer) { + this.setState({ + gitlab_servers: this.props.gitlabServer, + }); + } + }; + + // Activate or deactivate GitLab modal + toggle = () => { + this.setState({ modalGitLab: !this.state.modalGitLab }); + }; + + addGitLab = () => { + // Deactivate GitLab modal + this.setState( + { + modalGitLab: false, + }, + () => { + const gitlab = { + server: this.state.gitlab_server, + username: this.state.gitlab_username, + }; + + this.props + .addProfile({ + URL: gitlab.server, + type: "GITLAB", + authorization: undefined, + username: gitlab.username, + }) + .then(() => this.props.refetch()); + } + ); + }; + + oauthGitHubSuccess = (response) => { + this.setState( + { + loadingGitHub: true, + }, + async () => { + this.props + .addProfile({ + URL: "https://api.github.com/graphql", + type: "GITHUB", + authorization: response.accessToken, + username: response.username, + }) + .then(() => this.props.refetch()); + } + ); + }; + + oauthGitHubFailure = (response) => { + //#ERROR + console.error(response); + }; + + oauthInstagramSuccess = (response) => { + this.setState( + { + loadingInstagram: true, + }, + async () => { + this.props + .addProfile({ + URL: "https://graph.instagram.com", + type: "INSTAGRAM", + authorization: response.accessToken, + username: response.username, + }) + .then(() => this.props.refetch()); + } + ); + }; + + oauthInstagramFailure = (response) => { + //#ERROR + console.error(response); + }; + + handleSelectChange = (e) => { + this.setState({ + gitlab_server: e[0], + }); + }; + + render() { + const { selectedVideoId } = this.props; + + return ( + <> + {this.props.isModal ? ( + + +
    +
    + + Later + +
    +

    Connect your work

    +

    + You can add GitHub, Instagram and GitLab to share parts of + your work. +

    + {this.props.disabled ? ( + <> + + + + + + + + + + + ) : ( + <> + {!process.env.NODE_ENV || + process.env.NODE_ENV === "development" ? ( + + ) : ( + + )} + {!process.env.NODE_ENV || + process.env.NODE_ENV === "development" ? ( + + ) : ( + + )} + + + + + )} +

    SNEK's no fun without those connections!

    +
    +
    +
    + ) : ( +
    + {this.props.disabled ? ( + <> + + + + + + + + + + + ) : ( + <> + {!process.env.NODE_ENV || + process.env.NODE_ENV === "development" ? ( + + ) : ( + + )} + {!process.env.NODE_ENV || + process.env.NODE_ENV === "development" ? ( + + ) : ( + + )} + + + + + )} +
    + )} + {this.state.modalGitLab && ( + + + + Add GitLab profile + + + + this.setState({ [e.target.name]: e.target.value }) + } + value={this.state.gitlab_username} + /> + + + + + Choose your organisation + + {this.state.gitlab_servers && + this.state.gitlab_servers.map((source, i) => { + return ( + + {source.organisation} + + ); + })} + + + + + + + Add + + + Cancel + + + + )} + + ); + } +} +//#endregion + +//#region > Redux Mapping +const mapStateToProps = (state) => ({ + gitlabServer: state.general.gitlabServer, +}); + +const mapDispatchToProps = (dispatch) => { + return { + addProfile: (source) => dispatch(addProfile(source)), + getGitlabServers: () => dispatch(getGitlabServers()), + }; +}; +//#endregion + +//#region > Exports +/** + * Provides its connected component with the pieces of the data it needs from + * the store, and the functions it can use to dispatch actions to the store. + * + * Got access to the history object’s properties and the closest + * 's match. + */ +export default connect(mapStateToProps, mapDispatchToProps)(ConnectModal); +//#endregion + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/FollowModal/follow.scss b/src/components/molecules/modals/FollowModal/follow.scss new file mode 100644 index 0000000..4f72b52 --- /dev/null +++ b/src/components/molecules/modals/FollowModal/follow.scss @@ -0,0 +1,13 @@ +#follow { + img { + width: 4rem; + height: 4rem; + margin-bottom: 1rem; + border-radius: 50%; + } +} + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/FollowModal/index.jsx b/src/components/molecules/modals/FollowModal/index.jsx new file mode 100644 index 0000000..64cfac3 --- /dev/null +++ b/src/components/molecules/modals/FollowModal/index.jsx @@ -0,0 +1,241 @@ +//#region > Imports +//> React +// Contains all the functionality necessary to define React components +import React from "react"; +// DOM bindings for React Router +import { Link } from "react-router-dom"; +//> MDB +// "Material Design for Bootstrap" is a great UI design framework +import { + MDBModal, + MDBIcon, + MDBModalBody, + MDBBtn, + MDBModalHeader, + MDBRow, + MDBCol, +} from "mdbreact"; + +//> Stylesheet +import "./follow.scss"; +//#endregion + +//#region > Components +/** @class A modal that shows a list of follows */ +class FollowModal extends React.Component { + state = { + loggedUser: null, + followers: [], + anonymous: undefined, + loading: false, + }; + + componentDidMount = () => { + const { loggedUser } = this.props; + + this.setState({ loading: true }); + + // this.setState({ + // followers: loggedUser.person?.followers, + // anonymous: loggedUser.anonymous, + // }); + }; + + // shouldComponentUpdate(prevProps, prevState) { + // console.log( + // "SHOUDL UPDATE?", + // this.props.loggedUser.person !== prevProps.loggedUser.person + // ); + // return this.props.loggedUser.person !== prevProps.loggedUser.person; + // } + + componentDidUpdate = (prevProps, prevState) => { + const { loggedUser, fetchedPerson } = this.props; + + if ( + this.state.loading || + this.props.loggedUser.person !== prevProps.loggedUser.person + ) { + this.setState({ + followers: fetchedPerson.follows, + anonymous: loggedUser.anonymous, + loading: false, + }); + } + }; + + follow = async (personToFollow) => { + if (this.state.anonymous == false) { + await this.props.follow(personToFollow); + + const followers = this.state.followers.concat({ + slug: `p-${personToFollow}`, + }); + + this.setState({ followers }); + } else { + this.props.toContinue(); + } + }; + + unfollow = async (personToUnfollow) => { + if (!this.state.anonymous) { + await this.props.unfollow(personToUnfollow); + + let followers = [...this.state.followers]; + + const idx = followers.findIndex( + (o) => o.slug === `p-${personToUnfollow}` + ); + + followers.splice(idx, 1); + + this.setState({ followers }); + } + }; + + isFollowing = (follower) => { + return this.state.followers.find((e) => e.slug === follower.slug); + }; + + render() { + const { followType, fetchedPerson } = this.props; + + return ( + + + {followType} + + + {followType === "Followers" && + fetchedPerson.followedBy.map((follower, i) => { + return ( +
    + + + + + + {follower.slug.substring(2)} +

    {follower.firstName + " " + follower.lastName}

    +
    + {follower.slug !== this.props.loggedUser?.person?.slug && ( + + {this.isFollowing(follower) ? ( + + this.unfollow(follower.slug.substring(2)) + } + > + + Unfollow + + ) : ( + + this.follow(follower.slug.substring(2)) + } + > + + Follow + + )} + + )} +
    +
    + ); + })} + {followType === "Following" && + fetchedPerson.follows.map((follower, i) => { + return ( +
    + + + { + + } + + + {follower.slug.substring(2)} +

    {follower.firstName + " " + follower.lastName}

    +
    + {follower.slug !== this.props.loggedUser?.person?.slug && ( + + {this.isFollowing(follower) ? ( + + this.unfollow(follower.slug.substring(2)) + } + > + + Unfollow + + ) : ( + + this.follow(follower.slug.substring(2)) + } + > + + Follow + + )} + + )} +
    +
    + ); + })} +
    +
    + ); + } +} +//#endregion + +//#region > Exports +//> Default Component +export default FollowModal; +//#endregion + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/ImageModal/imagemodal.scss b/src/components/molecules/modals/ImageModal/imagemodal.scss new file mode 100644 index 0000000..57626fb --- /dev/null +++ b/src/components/molecules/modals/ImageModal/imagemodal.scss @@ -0,0 +1,10 @@ +#imagemodal { + img { + width: 100%; + } +} + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/ImageModal/index.jsx b/src/components/molecules/modals/ImageModal/index.jsx index 4a84be0..8db22ca 100644 --- a/src/components/molecules/modals/ImageModal/index.jsx +++ b/src/components/molecules/modals/ImageModal/index.jsx @@ -23,6 +23,9 @@ import { MDBView, MDBMask, } from "mdbreact"; + +//> Style +import "./imagemodal.scss"; //#endregion //#region > Components @@ -35,44 +38,16 @@ class ImageModal extends React.Component { - - - - {selectedPicture.img.alt} - - - - -
    - {selectedPicture.data ? ( - - by {selectedPicture.data.artist} - - ) : ( -
    - )} - -
    - {selectedPicture.data && ( -
    -

    {selectedPicture.data.title}

    -
    - )} - - + + + + @@ -83,11 +58,11 @@ class ImageModal extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, + //loggedUser: state.auth.loggedUser, }); const mapDispatchToProps = (dispatch) => { - return null; + return {}; }; //#endregion diff --git a/src/components/molecules/modals/InstagramSelectorModal/index.jsx b/src/components/molecules/modals/InstagramSelectorModal/index.jsx index b014e8f..824dcc0 100644 --- a/src/components/molecules/modals/InstagramSelectorModal/index.jsx +++ b/src/components/molecules/modals/InstagramSelectorModal/index.jsx @@ -22,8 +22,12 @@ import { MDBAlert, MDBView, MDBMask, + MDBCard, } from "mdbreact"; +//> Actions +// Functions to send data from the application to the store +import { getInstagramPosts } from "../../../../store/actions/personActions"; //> Style import "./instagramselector.scss"; //#endregion @@ -41,10 +45,47 @@ const DUMMY = [ class InstagramSelectorModal extends React.Component { state = { selection: [] }; + componentDidMount = () => { + this.init(); + }; + + init = async (refetch) => { + let next; + let posts; + + if ( + localStorage.getItem("instagram_posts") && + localStorage.getItem("instagram_posts") !== "undefined" && + !refetch + ) { + posts = JSON.parse(localStorage.getItem("instagram_posts")); + next = undefined; + } else { + const res = await this.props.getInstagramPosts(); + + posts = res.posts; + next = res.next; + } + + this.setState( + { + posts, + next, + selection: this.props.selection ? this.props.selection : [], + }, + () => + localStorage.setItem( + "instagram_posts", + JSON.stringify(this.state.posts) + ) + ); + }; + updateList = (picture) => { - if (this.state.selection.includes(picture)) { - const current = this.state.selection; - const index = current.indexOf(picture); + const current = this.state.selection; + + if (current.filter((item) => item.url === picture.mediaLink).length > 0) { + const index = current.findIndex((x) => x.url === picture.mediaLink); if (index > -1) { current.splice(index, 1); @@ -54,12 +95,49 @@ class InstagramSelectorModal extends React.Component { selection: current, }); } else { + const pic = { + url: picture.mediaLink, + linkType: "INSTAGRAM", + }; + this.setState({ - selection: [...this.state.selection, picture], + selection: [...current, pic], }); } }; + loadMore = async () => { + const isLocalStorage = localStorage.getItem("instagram_posts") + ? true + : false; + + if (isLocalStorage) { + await this.init(true); + } + + // const morePosts + + const nextFns = this.state.next; + let nextPosts = []; + let nextNextFns = []; + + for (const index in nextFns) { + const fn = nextFns[index]; + const res = await fn(); + + nextPosts.concat(res.posts); + + if (res.nex) { + nextNextFns.concat(res.nex); + } + } + + this.setState({ + posts: nextPosts, + next: nextNextFns, + }); + }; + render() { return ( <> @@ -74,37 +152,63 @@ class InstagramSelectorModal extends React.Component {

    Select Instagram images

    - this.props.save(this.state)} - > - Confirm selection - +
    + this.init(true)}> + Refresh + + this.props.save(this.state.selection)} + > + Confirm selection + +
    - - {DUMMY.map((picture, i) => { - const selected = this.state.selection.includes(picture); - - return ( - - - - this.updateList(picture)} - className="text-white d-flex justify-content-center align-items-center" +
    + {this.state.posts && + this.state.posts.map((picture, i) => { + const selected = + this.state.selection.filter( + (item) => item.url === picture.mediaLink + ).length > 0 + ? true + : false; + + if (picture.mediaType === "IMAGE") { + return ( + - {selected && } - - - - ); - })} - + + + this.updateList(picture)} + className="text-white d-flex justify-content-center align-items-center" + > + {selected && ( + + )} + + + + ); + } else { + return null; + } + })} +
    + {this.state.posts && ( +
    + this.loadMore()} + disabled + > + Load more + +
    + )}
    @@ -115,11 +219,13 @@ class InstagramSelectorModal extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, + //loggedUser: state.auth.loggedUser, }); const mapDispatchToProps = (dispatch) => { - return {}; + return { + getInstagramPosts: (id) => dispatch(getInstagramPosts(id)), + }; }; //#endregion diff --git a/src/components/molecules/modals/InstagramSelectorModal/instagramselector.scss b/src/components/molecules/modals/InstagramSelectorModal/instagramselector.scss index 887184b..1e4c572 100644 --- a/src/components/molecules/modals/InstagramSelectorModal/instagramselector.scss +++ b/src/components/molecules/modals/InstagramSelectorModal/instagramselector.scss @@ -6,7 +6,7 @@ } .view { - max-height: 150px; + max-height: 300px; overflow: hidden; .mask { diff --git a/src/components/molecules/modals/LikesModal/index.jsx b/src/components/molecules/modals/LikesModal/index.jsx new file mode 100644 index 0000000..8c958ad --- /dev/null +++ b/src/components/molecules/modals/LikesModal/index.jsx @@ -0,0 +1,180 @@ +//#region > Imports +//> React +// Contains all the functionality necessary to define React components +import React from "react"; +// DOM bindings for React Router +import { Link } from "react-router-dom"; +//> MDB +// "Material Design for Bootstrap" is a great UI design framework +import { + MDBModal, + MDBIcon, + MDBModalBody, + MDBBtn, + MDBModalHeader, + MDBRow, + MDBCol, +} from "mdbreact"; + +//> Stylesheet +import "./likes.scss"; +//#endregion + +//#region > Components +/** @class A modal that shows a list of people that liked */ +class LikesModal extends React.Component { + state = { + loggedUser: null, + }; + + componentDidMount = () => { + const { loggedUser } = this.props; + + this.setState({ + loggedUser: loggedUser, + }); + }; + + componentDidUpdate = () => { + const { loggedUser } = this.props; + + if (loggedUser !== this.state.loggedUser) { + this.setState({ + loggedUser: loggedUser, + }); + } + }; + + follow = (personToFollow) => { + if (!this.props.loggedUser?.anonymous) { + this.props.follow(personToFollow).then(() => { + let loggedUser = this.props.loggedUser; + let follows = []; + + for (let count in loggedUser.person.follows) { + follows.push(loggedUser.person.follows[count]); + } + + follows.push({ slug: "p-" + personToFollow }); + + loggedUser.person.follows = follows; + + this.setState({ loggedUser }); + }); + } else { + this.props.toContinue(); + } + }; + + unfollow = (personToUnfollow) => { + this.props.unfollow(personToUnfollow).then(() => { + let loggedUser = this.state.loggedUser; + let follows = []; + + for (let count in loggedUser?.person?.follows) { + if (loggedUser.person.follows[count].slug !== "p-" + personToUnfollow) { + follows.push(loggedUser.person.follows[count]); + } + } + + loggedUser.person.follows = follows; + + this.setState({ loggedUser }); + }); + }; + + isFollowing = (liker) => { + const { loggedUser } = this.state; + + for (let count in loggedUser?.person?.follows) { + if (loggedUser.person.follows[count].slug === liker.slug) { + return true; + } + } + + return false; + }; + + render() { + const { fetchedPerson } = this.props; + + return ( + + + Likes + + + {fetchedPerson.likedBy.map((liker, i) => { + return ( +
    + + + + + + {liker.slug.substring(2)} +

    {liker.firstName + " " + liker.lastName}

    +
    + {liker.slug !== this.props.loggedUser?.person?.slug && ( + + {this.isFollowing(liker) ? ( + this.unfollow(liker.slug.substring(2))} + > + + Unfollow + + ) : ( + this.follow(liker.slug.substring(2))} + > + + Follow + + )} + + )} +
    +
    + ); + })} +
    +
    + ); + } +} +//#endregion + +//#region > Exports +//> Default Component +export default LikesModal; +//#endregion + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/LikesModal/likes.scss b/src/components/molecules/modals/LikesModal/likes.scss new file mode 100644 index 0000000..18b764d --- /dev/null +++ b/src/components/molecules/modals/LikesModal/likes.scss @@ -0,0 +1,13 @@ +#likes { + img { + width: 4rem; + height: 4rem; + margin-bottom: 1rem; + border-radius: 50%; + } +} + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/ToContinueModal/index.jsx b/src/components/molecules/modals/ToContinueModal/index.jsx new file mode 100644 index 0000000..822ec4a --- /dev/null +++ b/src/components/molecules/modals/ToContinueModal/index.jsx @@ -0,0 +1,79 @@ +//#region > Imports +//> React +// Contains all the functionality necessary to define React components +import React from "react"; +// DOM bindings for React Router +import { Link } from "react-router-dom"; +//> MDB +// "Material Design for Bootstrap" is a great UI design framework +import { MDBModal, MDBIcon, MDBModalBody, MDBBtn } from "mdbreact"; + +//> Stylesheet +import "./tocontinue.scss"; +//#endregion + +//#region > Components +/** @class A modal that shows if a login is required to continue an action */ +class ToContinueModal extends React.Component { + render() { + return ( + + + {"Avatar +
    + Create a new SNEK account or login to SNEK, to continue. +
    + + + Login + + + + + + Create new account + + + +
    +
    + ); + } +} +//#endregion + +//#region > Exports +//> Default Component +export default ToContinueModal; +//#endregion + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/ToContinueModal/tocontinue.scss b/src/components/molecules/modals/ToContinueModal/tocontinue.scss new file mode 100644 index 0000000..fb81275 --- /dev/null +++ b/src/components/molecules/modals/ToContinueModal/tocontinue.scss @@ -0,0 +1,17 @@ +#continue { + img { + width: 5rem; + height: 5rem; + border-radius: 50%; + margin-bottom: 2rem; + } + + .font-weight-normal { + margin-bottom: 1rem; + } +} + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/molecules/modals/TalkUploadModal/index.jsx b/src/components/molecules/modals/UploadModal/index.jsx similarity index 68% rename from src/components/molecules/modals/TalkUploadModal/index.jsx rename to src/components/molecules/modals/UploadModal/index.jsx index 59b3638..f204af1 100644 --- a/src/components/molecules/modals/TalkUploadModal/index.jsx +++ b/src/components/molecules/modals/UploadModal/index.jsx @@ -2,6 +2,8 @@ //> React // Contains all the functionality necessary to define React components import React from "react"; +// Runtime type checking for React props and similar objects +import PropTypes from "prop-types"; // Contains the functionality for uploading a file import Dropzone from "react-dropzone"; //> MDB @@ -17,22 +19,25 @@ import { // Allows to React components read data from a Redux store, and dispatch actions // to the store to update data. import { connect } from "react-redux"; +//> Intel +import { anonfiles } from "snek-intel/lib/utils/upload"; +import IMGUR_PROVIDER from "snek-intel/lib/utils/imgur"; //> Actions // Functions to send data from the application to the store -import { uploadTalkAction } from "../../../../store/actions/userActions"; +// import { uploadTalkAction } from "../../../../store/actions/userActions"; //#endregion //#region > Components /** @class A upload modal component for uploading files including a drop-zone */ -class TalkUploadModal extends React.Component { +class UploadModal extends React.Component { state = { loading: false, error: [], }; onDrop = async (files) => { - const { loggedUser, fetchedUser } = this.props; + const { storageEngine } = this.props; if (files.length > 0) { this.setState({ @@ -40,25 +45,30 @@ class TalkUploadModal extends React.Component { loading: true, }); - this.props - .uploadTalk(files[0], { - avatarUrl: fetchedUser.platformData.user.avatarUrl, - owner: { - username: loggedUser.username, - }, - }) - .then(() => { - this.setState({ - loading: false, - }); - - this.props.closeModal(); - }); + let res; + + if (storageEngine === "anonfiles") { + res = await anonfiles.uploadFile(files[0]); + } else if (storageEngine === "imgur") { + res = await IMGUR_PROVIDER.upload(files[0]); + } else { + this.setState({ error: ["no valid storageEngine specified"] }); + } + + this.props.onSuccess(res); + + this.setState({ + loading: false, + }); + + this.props.closeModal(); } else { - this.setState({ error: ["Only PDF files can be uploaded!"] }); + this.setState({ error: [this.props.invalidTypeMessage] }); } }; + uploadToAnonfiles = async (file) => {}; + render() { return ( - - Upload -
    - + {({ getRootProps, getInputProps, acceptedFiles }) => (
    @@ -93,12 +98,13 @@ class TalkUploadModal extends React.Component { key={i} > -

    -

    {acceptedFile.name}

    +

    + {acceptedFile.name} +

    ))} @@ -109,6 +115,7 @@ class TalkUploadModal extends React.Component { animated height="25px" color="success" + className="mb-0 pb-0" > Uploading file @@ -132,14 +139,15 @@ class TalkUploadModal extends React.Component {
    ) : ( -
    +
    -

    -

    Click here or drop a file to upload!

    +

    + Click here or drop a file to upload! +

    )}
    @@ -153,6 +161,15 @@ class TalkUploadModal extends React.Component { } //#endregion +//#region > PropTypes +UploadModal.propTypes = { + acceptTypes: PropTypes.string.isRequired, + invalidTypeMessage: PropTypes.string.isRequired, + storageEngine: PropTypes.oneOf(["anonfiles", "imgur"]).isRequired, + onSuccess: PropTypes.func, +}; +//#endregion + //#region > Redux Mapping const mapStateToProps = (state) => ({ loggedUser: state.user.fetchedUser, @@ -160,7 +177,7 @@ const mapStateToProps = (state) => ({ }); const mapDispatchToProps = (dispatch) => { - return { uploadTalk: (file) => dispatch(uploadTalkAction(file)) }; + return {}; }; //#endregion @@ -170,7 +187,7 @@ const mapDispatchToProps = (dispatch) => { * Provides its connected component with the pieces of the data it needs from * the store, and the functions it can use to dispatch actions to the store. */ -export default connect(mapStateToProps, mapDispatchToProps)(TalkUploadModal); +export default connect(mapStateToProps, mapDispatchToProps)(UploadModal); //#endregion /** diff --git a/src/components/molecules/modals/VideoModal/index.jsx b/src/components/molecules/modals/VideoModal/index.jsx index c3a649c..752a62d 100644 --- a/src/components/molecules/modals/VideoModal/index.jsx +++ b/src/components/molecules/modals/VideoModal/index.jsx @@ -59,11 +59,11 @@ class VideoModal extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, + //loggedUser: state.auth.loggedUser, }); const mapDispatchToProps = (dispatch) => { - return null; + return {}; }; //#endregion diff --git a/src/components/molecules/modals/enterprise/ProjectModal/index.jsx b/src/components/molecules/modals/enterprise/ProjectModal/index.jsx index 2fbb967..8799b16 100644 --- a/src/components/molecules/modals/enterprise/ProjectModal/index.jsx +++ b/src/components/molecules/modals/enterprise/ProjectModal/index.jsx @@ -78,9 +78,10 @@ class ProjectModal extends React.Component {

    Contributors

    - - {project.contributors && - project.contributors.map((contributor, i) => { + {project.contributors && + project.contributors.length > 0 ? ( + + {project.contributors.map((contributor, i) => { return (

    {contributor.name}

    @@ -97,56 +98,77 @@ class ProjectModal extends React.Component {
    ); })} -
    +
    + ) : ( +
    + + No contributors yet. + +
    + )}

    Activity

    - {this.props.chart && - this.props.chart.years.map((year, y) => { - return ( -

    - this.setState({ selectedYearIndex: y }) - } - > - {moment(year.endDate).format("YYYY")} -

    - ); - })} -

    - this.setState({ selectedYearIndex: undefined }) - } - > - Current -

    -
    -
    - + {this.props.chart && this.props.chart.years.length > 0 ? ( + <> + {this.props.chart.years.map((year, y) => { + return ( +

    + this.setState({ selectedYearIndex: y }) + } + > + {moment(year.endDate).format("YYYY")} +

    + ); + })} + + ) : ( +
    + + No statistics yet. + +
    + )} + {this.props.chart && this.props.chart.years.length > 0 && ( +

    + this.setState({ selectedYearIndex: undefined }) + } + > + Current +

    + )}
    + {this.props.chart && this.props.chart.years.length > 0 && ( +
    + +
    + )}

    History

    - - {project.contributionFeed && - project.contributionFeed.map((contrib, i) => { + {project.contributionFeed && + project.contributionFeed.length > 0 ? ( + + {project.contributionFeed.map((contrib, i) => { return (

    {contrib.message}

    @@ -165,7 +187,14 @@ class ProjectModal extends React.Component {
    ); })} -
    +
    + ) : ( +
    + + No history yet. + +
    + )}
    diff --git a/src/components/molecules/modals/enterprise/UserModal/index.jsx b/src/components/molecules/modals/enterprise/UserModal/index.jsx index 436424f..dc52c5a 100644 --- a/src/components/molecules/modals/enterprise/UserModal/index.jsx +++ b/src/components/molecules/modals/enterprise/UserModal/index.jsx @@ -20,7 +20,7 @@ import { import moment from "moment"; //> Components -import { AIContribCalendar, AILanguageChart } from "../../../../atoms"; +import { AIContribCalendar } from "../../../../atoms"; //#endregion //#region > Components @@ -48,8 +48,6 @@ class UserModal extends React.Component { render() { const { user } = this.state; - console.log("USER", user); - return ( @@ -62,63 +60,66 @@ class UserModal extends React.Component {

    {user.name}

    -

    {user.username}

    + {user.username}

    Code statistics

    - -
    -
    - {[ - { - color: "rgb(241, 224, 90)", - share: 40, - name: "JavaScript", - }, - { - color: "rgb(299, 150, 90)", - share: 60, - name: "FooScript", - }, - ].map((language, i) => { - return ( - -
    -
    - - {language.name} + {user.codelanguages && user.codelanguages.length > 0 ? ( +
    + {user.codelanguages.map((language, i) => { + return ( + +
    +
    + + {language.name} +
    + + + {language.insertions} + + + + {language.deletions} + + +
    - - {language.share}% - -
    - - ); - })} -
    +
    + ); + })} +
    + ) : ( +
    + + No information yet. + +
    + )} @@ -144,31 +145,44 @@ class UserModal extends React.Component {

    ); })} -

    - this.setState({ selectedYearIndex: undefined }) - } - > - Current -

    + {user.mergedContributionFeed && + user.mergedContributionFeed.years.length > 0 && ( +

    + this.setState({ selectedYearIndex: undefined }) + } + > + Current +

    + )}
    - + {user.mergedContributionFeed && + user.mergedContributionFeed.years.length > 0 ? ( + + ) : ( +
    + + No statistics yet. + +
    + )}

    Activity

    - - {user.contributionFeed && - user.contributionFeed.map((contrib, i) => { + {user.contributionFeed && + user.contributionFeed.length > 0 ? ( + + {user.contributionFeed.map((contrib, i) => { return (

    {contrib.message}

    @@ -187,7 +201,14 @@ class UserModal extends React.Component {
    ); })} -
    +
    + ) : ( +
    + + No activities yet. + +
    + )}
    diff --git a/src/components/molecules/modals/index.js b/src/components/molecules/modals/index.js index 5de1d91..8335bcd 100644 --- a/src/components/molecules/modals/index.js +++ b/src/components/molecules/modals/index.js @@ -1,26 +1,34 @@ //#region > Imports //> Components // Import all components to export them for easy access from parent components -import TalkUploadModal from "./TalkUploadModal"; +import UploadModal from "./UploadModal"; import ProfilePictureModal from "./ProfilePictureModal"; import ImageModal from "./ImageModal"; import VideoModal from "./VideoModal"; import AddVideoModal from "./AddVideoModal"; import InstagramSelectorModal from "./InstagramSelectorModal"; import AddSongModal from "./AddSongModal"; +import ConnectModal from "./ConnectModal"; +import ToContinueModal from "./ToContinueModal"; +import FollowModal from "./FollowModal"; +import LikesModal from "./LikesModal"; //#endregion //#region > Exports //> Components // Export the components for easy access from parent components export { - TalkUploadModal, + UploadModal, ProfilePictureModal, ImageModal, VideoModal, AddVideoModal, InstagramSelectorModal, AddSongModal, + ConnectModal, + ToContinueModal, + FollowModal, + LikesModal, }; //#endregion diff --git a/src/components/organisms/ProfileInfo/index.jsx b/src/components/organisms/ProfileInfo/index.jsx deleted file mode 100644 index d386a8d..0000000 --- a/src/components/organisms/ProfileInfo/index.jsx +++ /dev/null @@ -1,380 +0,0 @@ -//#region > Imports -//> React -// Contains all the functionality necessary to define React components -import React from "react"; -//> MDB -// "Material Design for Bootstrap" is a great UI design framework -import { - MDBBtn, - MDBBadge, - MDBView, - MDBMask, - MDBPopover, - MDBPopoverBody, - MDBPopoverHeader, - MDBIcon, - MDBTooltip, -} 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"; - -//> Components -import { LanguageChart } from "../../atoms"; -//> Style Sheet -import "./profileinfo.scss"; -//#endregion - -//#region > Components -/** @class This component displays personal information and status of a user */ -class ProfileInfo extends React.Component { - state = { limitLanguages: true }; - - componentDidMount = () => { - const { fetchedUser } = this.props; - - if (fetchedUser && !this.state.sources) { - this.displaySources(fetchedUser.platformData.profile.sources); - } - }; - - displaySources = (sources) => { - let res = sources.map((source, i) => { - switch (source.source) { - case "github": - return "github"; - case "gitlab": - return "gitlab"; - case "bitbucket": - return "bitbucket"; - default: - return false; - } - }); - - this.setState({ - sources: res, - }); - }; - - render() { - const { fetchedUser } = this.props; - - return ( -
    - - - - -
    -

    - {fetchedUser && fetchedUser.platformData.user.firstName && ( - <>{fetchedUser.platformData.user.firstName} - )}{" "} - {fetchedUser && fetchedUser.platformData.user.lastName && ( - <>{fetchedUser.platformData.user.lastName} - )} -

    - {fetchedUser && - fetchedUser.platformData.user.settings && - fetchedUser.platformData.user.settings.showLocalRanking && ( -

    - - #3 in your region - -

    - )} - {fetchedUser && fetchedUser.platformData.user.company && ( - <> - {fetchedUser && - fetchedUser.platformData.user.settings.showCompanyPublic && ( - - {fetchedUser.platformData.user.company} - - )} - - )} -
    - {fetchedUser && fetchedUser.accessories.badges && ( - <> - {fetchedUser.accessories.badges.bids.map((bid, i) => { - switch (bid) { - case "6403bf4d17b8472735a93b71a37e0bd0": - return ( - - Alpha - - ); - } - })} - - )} -
    - {fetchedUser && fetchedUser.platformData.profile.statusMessage && ( -
    - {fetchedUser.platformData.profile.statusEmojiHTML && ( -
    - )} - - {fetchedUser.platformData.profile.statusMessage} - -
    - )} -
    -
    - - - Follow - - - - Upvote - -
    -
    -

    Connected accounts

    -
    - - - -
    -
    -

    Organisations

    - {fetchedUser && ( -
    = 5 - ? "orgs text-center" - : "orgs" - } - > - {fetchedUser.platformData.profile.organizations.length > 0 ? ( - <> - {fetchedUser.platformData.profile.organizations.map( - (org, i) => { - return ( - - -
    - {org.avatarUrl ? ( - {org.name} - ) : ( - - )} - {org.members && ( -
    {org.members.length}
    - )} -
    -
    -
    - -
    - {org.platformName}/ - - {org.name} - -
    -
    -
    - - {org.members - ? org.members.length - : "Unknown"}{" "} - members - -
    -
    -
    - {org.members && - org.members.length > 0 && - org.members - .slice(0, 8) - .map((member, m) => { - return ( - - - {member.username} - - {member.username} - - ); - })} -
    - {org.members && org.members.length > 9 && ( -
    - {org.platformName === "github" && ( - - Show all - - )} -
    - )} -
    -
    -
    - -

    {org.description}

    - - Show more - - -
    -
    -
    - ); - } - )} - - ) : ( - - {fetchedUser.username} hasn't joined an organisation yet. - - )} -
    - )} - {fetchedUser.platformData?.statistic?.languages?.length > 0 && ( -
    -
    -

    Top languages

    - - {fetchedUser.platformData.statistic.languages - .slice( - 0, - this.state.limitLanguages - ? 3 - : fetchedUser.platformData.statistic.languages.length - 1 - ) - .map((language, i) => { - return ( - -
    -
    - - {language.name} -
    - - {language.share}% - -
    -
    - ); - })} - {this.state.limitLanguages && - fetchedUser.platformData.statistic.languages.length > 3 ? ( -

    this.setState({ limitLanguages: false })} - > - Show more -

    - ) : ( -

    this.setState({ limitLanguages: true })} - > - Show less -

    - )} -
    - )} -
    -
    - ); - } -} -//#endregion - -//#region > Redux Mapping -const mapStateToProps = (state) => ({ - fetchedUser: state.user.fetchedUser, -}); - -const mapDispatchToProps = (dispatch) => { - return {}; -}; -//#endregion - -//#region > Exports -//> Default Component -/** - * Provides its connected component with the pieces of the data it needs from - * the store, and the functions it can use to dispatch actions to the store. - */ -export default connect(mapStateToProps, mapDispatchToProps)(ProfileInfo); -//#endregion - -/** - * SPDX-License-Identifier: (EUPL-1.2) - * Copyright © 2019-2020 Simon Prast - */ diff --git a/src/components/organisms/index.js b/src/components/organisms/index.js index 8f09366..64a46c5 100644 --- a/src/components/organisms/index.js +++ b/src/components/organisms/index.js @@ -1,13 +1,14 @@ //#region > Imports //> Organisms // Import all components to export them for easy access from parent components -import SoftwareTabs from "./SoftwareTabs"; -import ProfileInfo from "./ProfileInfo"; +import SoftwareTabs from "./sections/person/Tabs"; +import PersonInfoCard from "./sections/person/InfoCard"; +import PersonTabs from "./sections/person/Tabs"; //#endregion //#region > Exports //> Organisms -export { SoftwareTabs, ProfileInfo }; +export { SoftwareTabs, PersonInfoCard, PersonTabs }; //#endregion /** diff --git a/src/components/organisms/profiles/SoftwareEngineer/index.jsx b/src/components/organisms/profiles/SoftwareEngineer/index.jsx deleted file mode 100644 index dfbdbfe..0000000 --- a/src/components/organisms/profiles/SoftwareEngineer/index.jsx +++ /dev/null @@ -1,43 +0,0 @@ -//#region > Imports -//> React -// Contains all the functionality necessary to define React components -import React from "react"; -//> MDB -// "Material Design for Bootstrap" is a great UI design framework -import { MDBContainer, MDBRow, MDBCol } from "mdbreact"; - -//> Components -import { SoftwareTabs, ProfileInfo } from "../../index"; -//#endregion - -//#region > Components -/** @class The profile component of a Software Engineer user */ -class SoftwareEngineer extends React.Component { - state = {}; - - render() { - return ( - - - - - - - - - - - ); - } -} -//#endregion - -//#region > Exports -//> Default Component -export default SoftwareEngineer; -//#endregion - -/** - * SPDX-License-Identifier: (EUPL-1.2) - * Copyright © 2019-2020 Simon Prast - */ diff --git a/src/components/organisms/profiles/index.js b/src/components/organisms/profiles/index.js deleted file mode 100644 index e0173b4..0000000 --- a/src/components/organisms/profiles/index.js +++ /dev/null @@ -1,16 +0,0 @@ -//#region > Imports -//> Components -// Import all components to export them for easy access from parent components -import SoftwareEngineer from "./SoftwareEngineer"; -//#endregion - -//#region > Exports -//> Components -// Export the components for easy access from parent components -export { SoftwareEngineer }; -//#endregion - -/** - * SPDX-License-Identifier: (EUPL-1.2) - * Copyright © 2019-2020 Simon Prast - */ diff --git a/src/components/organisms/sections/media/AIGallery/aigallery.scss b/src/components/organisms/sections/media/AIGallery/aigallery.scss index ee67a2e..c144ded 100644 --- a/src/components/organisms/sections/media/AIGallery/aigallery.scss +++ b/src/components/organisms/sections/media/AIGallery/aigallery.scss @@ -1,22 +1,31 @@ -#media { - #gallery { - .card { - .card-body { - padding: 0; +#gallery { + .card { + .card-body { + padding: 0; - .mask { - background: transparentize($color: white, $amount: 1); - transition: background 0.1s ease; - cursor: pointer; + img { + width: 100%; + } + + .mask { + background: transparentize($color: white, $amount: 1); + transition: background 0.1s ease; + cursor: pointer; - &:hover { - background: transparentize($color: white, $amount: 0.9); - } + &:hover { + background: transparentize($color: white, $amount: 0.9); } } } } + .video-preview { + bottom: 0; + right: 0; + z-index: 3; + position: absolute; + } + .fa-times { &.text-muted { transition: color 0.1s ease; diff --git a/src/components/organisms/sections/media/AIGallery/index.jsx b/src/components/organisms/sections/media/AIGallery/index.jsx index 50c287d..82db3af 100644 --- a/src/components/organisms/sections/media/AIGallery/index.jsx +++ b/src/components/organisms/sections/media/AIGallery/index.jsx @@ -32,41 +32,61 @@ import { import { ImageModal, InstagramSelectorModal, + UploadModal, } from "../../../../molecules/modals"; +//> Actions +// Functions to send data from the application to the store +import { + addMetaLink, + deleteMetaLink, +} from "../../../../../store/actions/personActions"; //> Style import "./aigallery.scss"; -//#endregion - -//#region > Dummy data -const DUMMY = [ - { - img: { - url: "https://mdbootstrap.com/img/Photos/Others/images/43.jpg", - alt: "Dummy image", - }, - data: { - title: "Dummy", - }, - }, - { - img: { - url: "https://mdbootstrap.com/img/Photos/Others/images/12.jpg", - alt: "Dummy image", - }, - data: { - title: "Dummy 2", - }, - }, -]; +//> Intel +import INTEL_IMGUR from "snek-intel/lib/utils/imgur"; //#endregion //#region > Components class AIGallery extends React.Component { - state = { modalPicture: false }; + state = { modalPicture: false, showUpload: false }; componentDidMount = () => { this.setState({ - images: DUMMY, + images: this.props.images, + }); + }; + + componentDidUpdate = () => {}; + + handleUploadClose = () => { + if (this.state.showUpload) { + this.setState({ + showUpload: false, + }); + } + }; + + handleSuccess = async (event) => { + let photo = { + url: event.link, + linkType: "PHOTO", + deleteHash: event.deletehash, + }; + + const res = await this.props.addMetaLink({ + url: photo.url, + linkType: photo.linkType, + imgurDeleteHash: photo.deleteHash, + }); + + photo = { + ...photo, + id: res.id, + imgurDeleteHash: res.imgurDeleteHash, + }; + + this.setState({ + images: [...this.state.images, photo], }); }; @@ -77,67 +97,139 @@ class AIGallery extends React.Component { }); }; - save = (urlList) => { - const res = urlList.selection.map((url) => { - return { - img: { - url, - }, - }; - }); + save = async (pictureList) => { + let pics = []; - this.setState({ - images: [...this.state.images, ...res], - modalSelectPictures: false, - }); + for (const index in pictureList) { + const picture = pictureList[index]; + const samePic = this.props.images.filter( + (pic) => pic.url === picture.url + ); + + if (samePic.length === 0) { + let pic = { + url: picture.url, + linkType: "INSTAGRAM", + }; + + const res = await this.props.addMetaLink({ + url: pic.url, + linkType: pic.linkType, + }); + + pic = { + ...pic, + id: res.id, + }; + + pics = [...pics, pic]; + } + } + + this.setState( + { + images: [...this.state.images, ...pics], + modalSelectPictures: false, + }, + () => { + const currentPics = this.props.images; + const newPics = this.state.images; + + const intersection = currentPics.filter((x) => !newPics.includes(x)); + + intersection.forEach((curItem) => { + this.props.deleteMetaLink(curItem.id); + }); + } + ); }; - removeImage = (url) => { - // TODO + removeImage = async (id, deleteHash) => { + if (deleteHash !== undefined && deleteHash !== null) { + await INTEL_IMGUR.delete(deleteHash); + } + this.props.deleteMetaLink(id); + }; + + checkProfileTypeExists = (sourceType) => { + return this.props.loggedUser.person.profiles.some( + (e) => e.sourceType === sourceType && e.isActive + ) + ? true + : false; }; render() { - const { loggedUser } = this.props; + const { loggedUser, sameOrigin } = this.props; return ( -
    -
    - this.setState({ modalSelectPictures: true })} - > - Select images - -
    - + ); } @@ -158,11 +269,14 @@ class AIGallery extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, + loggedUser: state.user.user, }); const mapDispatchToProps = (dispatch) => { - return {}; + return { + addMetaLink: (linkOptions) => dispatch(addMetaLink(linkOptions)), + deleteMetaLink: (id) => dispatch(deleteMetaLink(id)), + }; }; //#endregion diff --git a/src/components/organisms/sections/media/AISongGallery/index.jsx b/src/components/organisms/sections/media/AISongGallery/index.jsx index be77665..06d2bbd 100644 --- a/src/components/organisms/sections/media/AISongGallery/index.jsx +++ b/src/components/organisms/sections/media/AISongGallery/index.jsx @@ -11,25 +11,22 @@ import { connect } from "react-redux"; //> MDB // "Material Design for Bootstrap" is a great UI design framework import { - MDBContainer, MDBRow, MDBCol, MDBCard, MDBCardBody, - MDBCardFooter, MDBBtn, - MDBBadge, - MDBProgress, - MDBTooltip, MDBIcon, - MDBTimeline, - MDBTimelineStep, - MDBView, - MDBMask, } from "mdbreact"; //> Components import { AddSongModal } from "../../../../molecules/modals"; +//> Actions +// Functions to send data from the application to the store +import { + addMetaLink, + deleteMetaLink, +} from "../../../../../store/actions/personActions"; //> Style import "./aisonggallery.scss"; //#endregion @@ -48,7 +45,7 @@ class AISongGallery extends React.Component { componentDidMount = () => { this.setState({ - songs: DUMMY, + songs: this.props.songs, }); }; @@ -59,12 +56,22 @@ class AISongGallery extends React.Component { }); }; - addSong = (url) => { - const song = { - type: "SOUNDCLOUD", + addSong = async (url) => { + let song = { + linkType: "SOUNDCLOUD", url, }; + const rtn = await this.props.addMetaLink({ + url: song.url, + linkType: song.linkType, + }); + + song = { + ...song, + id: rtn.id, + }; + this.setState({ modalAddSong: false, songs: [...this.state.songs, song], @@ -72,19 +79,21 @@ class AISongGallery extends React.Component { }; render() { - const { loggedUser } = this.props; + const { sameOrigin } = this.props; return (
    -
    - this.setState({ modalAddSong: true })} - > - - Add song - -
    + {sameOrigin && ( +
    + this.setState({ modalAddSong: true })} + > + + Add song + +
    + )} {this.state.songs && this.state.songs.map((song, i) => { @@ -92,11 +101,31 @@ class AISongGallery extends React.Component { + {sameOrigin && song.id && ( +
    + { + this.setState( + { + songs: this.state.songs.filter( + (s) => s.id !== song.id + ), + }, + () => this.props.deleteMetaLink(song.id) + ); + }} + > + + +
    + )} @@ -120,11 +149,14 @@ class AISongGallery extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, + //loggedUser: state.auth.loggedUser, }); const mapDispatchToProps = (dispatch) => { - return {}; + return { + addMetaLink: (linkOptions) => dispatch(addMetaLink(linkOptions)), + deleteMetaLink: (id) => dispatch(deleteMetaLink(id)), + }; }; //#endregion diff --git a/src/components/organisms/sections/media/AIVideoGallery/aivideogallery.scss b/src/components/organisms/sections/media/AIVideoGallery/aivideogallery.scss index 7deb7e1..c465968 100644 --- a/src/components/organisms/sections/media/AIVideoGallery/aivideogallery.scss +++ b/src/components/organisms/sections/media/AIVideoGallery/aivideogallery.scss @@ -1,17 +1,15 @@ -#media { - #videogallery { - .card { - .card-body { - padding: 0; +#videogallery { + .card { + .card-body { + padding: 0; - .mask { - background: transparentize($color: white, $amount: 1); - transition: background 0.1s ease; - cursor: pointer; + .mask { + background: transparentize($color: white, $amount: 1); + transition: background 0.1s ease; + cursor: pointer; - &:hover { - background: transparentize($color: white, $amount: 0.9); - } + &:hover { + background: transparentize($color: white, $amount: 0.9); } } } @@ -23,7 +21,9 @@ .video-preview { bottom: 0; - background: transparentize($color: black, $amount: 0.4); + right: 0; + z-index: 3; + position: absolute; } .video-title { @@ -31,7 +31,6 @@ width: 100%; position: absolute; color: white; - background: transparentize($color: black, $amount: 0.3); } } diff --git a/src/components/organisms/sections/media/AIVideoGallery/index.jsx b/src/components/organisms/sections/media/AIVideoGallery/index.jsx index dd24ea7..80a9a7e 100644 --- a/src/components/organisms/sections/media/AIVideoGallery/index.jsx +++ b/src/components/organisms/sections/media/AIVideoGallery/index.jsx @@ -30,29 +30,23 @@ import { //> Components import { VideoModal, AddVideoModal } from "../../../../molecules/modals"; +//> Actions +// Functions to send data from the application to the store +import { + addMetaLink, + deleteMetaLink, +} from "../../../../../store/actions/personActions"; //> Style import "./aivideogallery.scss"; //#endregion -//#region > Dummy data -const DUMMY = [ - { - id: "rX0kyTpTIw0", - }, - { - id: "viek5d1_0VA", - }, - { id: "pPw_izFr5PA" }, -]; -//#endregion - //#region > Components class AIVideoGallery extends React.Component { state = { modalPicture: false }; componentDidMount = () => { this.setState({ - videos: DUMMY, + videos: this.props.videos, }); }; @@ -63,10 +57,34 @@ class AIVideoGallery extends React.Component { }); }; - addVideo = (state) => { - const video = { - type: "YOUTUBE", - id: state.youtubeId, + /** + * Retrieving YouTube video ID from URL + * + * @param {string} url YouTube video URL + * @author SithCult + * @license EUPL-1.2 + */ + getYouTubeVideoId = (url) => { + const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; + const match = url.match(regExp); + + return match && match[2].length === 11 ? match[2] : null; + }; + + addVideo = async (state) => { + let video = { + linkType: "YOUTUBE", + url: state.youtubeLink, + }; + + const rtn = await this.props.addMetaLink({ + url: video.url, + linkType: video.linkType, + }); + + video = { + ...video, + id: rtn.id, }; this.setState({ @@ -76,21 +94,26 @@ class AIVideoGallery extends React.Component { }; render() { - const { loggedUser } = this.props; + const { sameOrigin } = this.props; return ( -
    -
    - this.setState({ modalAddVideo: true })} - > - Add video - -
    - +
    + {sameOrigin && ( +
    + this.setState({ modalAddVideo: true })} + > + + Add video + +
    + )} + {this.state.videos && this.state.videos.map((video, i) => { + const videoId = this.getYouTubeVideoId(video.url); + return ( @@ -98,35 +121,54 @@ class AIVideoGallery extends React.Component {
    Video thumbnail -
    - Titel ausständig +
    + {sameOrigin && video.id && ( +
    + { + this.setState( + { + videos: this.state.videos.filter( + (video) => video.id !== video.id + ), + }, + () => this.props.deleteMetaLink(video.id) + ); + }} + > + + +
    + )}
    @@ -137,7 +179,7 @@ class AIVideoGallery extends React.Component { onClick={() => this.setState({ modalVideo: true, - selectedVideoId: video.id, + selectedVideoId: videoId, }) } /> @@ -168,11 +210,14 @@ class AIVideoGallery extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, + //loggedUser: state.auth.loggedUser, }); const mapDispatchToProps = (dispatch) => { - return {}; + return { + addMetaLink: (linkOptions) => dispatch(addMetaLink(linkOptions)), + deleteMetaLink: (id) => dispatch(deleteMetaLink(id)), + }; }; //#endregion diff --git a/src/components/organisms/sections/media/PhotoMap/index.jsx b/src/components/organisms/sections/media/PhotoMap/index.jsx new file mode 100644 index 0000000..535d4f9 --- /dev/null +++ b/src/components/organisms/sections/media/PhotoMap/index.jsx @@ -0,0 +1,140 @@ +//#region > Imports +//> React +// Contains all the functionality necessary to define React components +import React from "react"; + +import { + ComposableMap, + Geographies, + Geography, + ZoomableGroup, + Marker, +} from "react-simple-maps"; + +//import "./photomap.scss"; +//#endregion + +//#region > Components +/** @class This component displays the landing page including login and register */ +class PhotoMap extends React.Component { + constructor(props) { + super(props); + + this.state = { + geoUrl: + "https://raw.githubusercontent.com/zcreativelabs/react-simple-maps/master/topojson-maps/world-110m.json", + markers: [ + { + cords: [14.180588, 46.722203], + hover: false, + click: false, + img: + "https://scontent-frx5-1.cdninstagram.com/v/t51.2885-15/100073723_290663115667352_2200766204448190569_n.jpg?_nc_cat=110&_nc_sid=8ae9d6&_nc_ohc=lElXlAfEmOYAX96tO71&_nc_ht=scontent-frx5-1.cdninstagram.com&oh=8fa19466614fa0c8ed50703c5445081c&oe=5F8317DB", + }, + { + cords: [18, 46.722203], + hover: false, + click: false, + img: + "https://scontent-frt3-1.cdninstagram.com/v/t51.2885-15/100811582_247790446433980_4259144092930484238_n.jpg?_nc_cat=108&_nc_sid=8ae9d6&_nc_ohc=owEiRBaPxJMAX-0OHyA&_nc_ht=scontent-frt3-1.cdninstagram.com&oh=ede6635099373fb510ff6f33c7854a01&oe=5F84435C", + }, + { + cords: [14.180588, 50], + hover: false, + click: false, + img: + "https://scontent-frt3-1.cdninstagram.com/v/t51.2885-15/100089905_1550981121744980_7352234614965268133_n.jpg?_nc_cat=102&_nc_sid=8ae9d6&_nc_ohc=Oi7WmFJKmo4AX86q6iC&_nc_ht=scontent-frt3-1.cdninstagram.com&oh=bd2b3613661c89a70fb82f2c350d8b26&oe=5F854976", + }, + ], + position: { + zoom: 1, + cords: [0, 0], + }, + }; + } + + hoverMarker = (marker) => { + let markers = this.state.markers; + + for (let count = 0; count < markers.length; count++) { + if (marker !== undefined) { + if (markers[count].cords === marker.cords) { + markers[count].hover = !markers[count].hover; + } + } else { + markers[count].hover = !markers[count].hover; + } + } + + this.setState({ markers }); + }; + + clickMarker = (marker) => { + let markers = this.state.markers; + + for (let count = 0; count < markers.length; count++) { + if (markers[count].cords === marker.cords) { + markers[count].click = !markers[count].click; + } + } + + this.setState({ markers }); + }; + + render() { + return ( +
    + + + {({ geographies }) => + geographies.map((geo) => ( + + )) + } + + {this.state.markers.map((marker, i) => ( + this.hoverMarker(marker)} + onMouseLeave={() => this.hoverMarker(marker)} + onClick={() => this.clickMarker(marker)} + > + + {(marker.hover || marker.click) && ( + window.open(marker.img)} + /> + )} + + ))} + +
    + ); + } +} +//#endregion + +//#region > Exports +//> Default Component +/** + * Provides its connected component with the pieces of the data it needs from + * the store, and the functions it can use to dispatch actions to the store. + */ +export default PhotoMap; +//#endregion + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/organisms/sections/media/index.js b/src/components/organisms/sections/media/index.js index 591684d..d2bdaa7 100644 --- a/src/components/organisms/sections/media/index.js +++ b/src/components/organisms/sections/media/index.js @@ -4,11 +4,12 @@ import AIGallery from "./AIGallery"; import AIVideoGallery from "./AIVideoGallery"; import AISongGallery from "./AISongGallery"; +import PhotoMap from "./PhotoMap"; //#endregion //#region > Exports //> Organisms -export { AIGallery, AIVideoGallery, AISongGallery }; +export { AIGallery, AIVideoGallery, AISongGallery, PhotoMap }; //#endregion /** diff --git a/src/components/organisms/sections/person/InfoCard/index.jsx b/src/components/organisms/sections/person/InfoCard/index.jsx new file mode 100644 index 0000000..9238edc --- /dev/null +++ b/src/components/organisms/sections/person/InfoCard/index.jsx @@ -0,0 +1,637 @@ +//#region > Imports +//> React +// Contains all the functionality necessary to define React components +import React from "react"; +//> MDB +// "Material Design for Bootstrap" is a great UI design framework +import { + MDBBtn, + MDBBadge, + MDBView, + MDBMask, + MDBPopover, + MDBPopoverBody, + MDBPopoverHeader, + MDBIcon, + MDBTooltip, + MDBRow, + MDBCol, +} 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"; + +//> Components +import { LanguageChart } from "../../../../atoms"; +import { + ToContinueModal, + FollowModal, + LikesModal, +} from "../../../../molecules/modals"; +//> Actions +// Functions to send data from the application to the store +import { + follow, + unfollow, + like, + unlike, + getPerson, +} from "../../../../../store/actions/personActions"; +//> Style Sheet +import "./infocard.scss"; +//#endregion + +//#region > Components +/** @class This component displays personal information and status of a user */ +class InfoCard extends React.Component { + state = { + limitLanguages: true, + showToContinue: false, + showFollow: false, + showLikes: false, + followType: "", + fetchedPerson: null, + }; + + componentDidMount = () => { + const { fetchedPerson } = this.props; + + this.setState({ + fetchedPerson: fetchedPerson, + }); + }; + + componentDidUpdate = () => { + const { fetchedPerson } = this.props; + + if (fetchedPerson !== this.state.fetchedPerson) { + this.setState({ + fetchedPerson: fetchedPerson, + }); + } + }; + + follow = (personToFollow) => { + if (!this.props.loggedUser?.anonymous) { + this.props.follow(personToFollow).then(() => { + let fetchedPerson = this.state.fetchedPerson; + let followedBy = []; + + for (let count in fetchedPerson.followedBy) { + followedBy.push(fetchedPerson.followedBy[count]); + } + + followedBy.push(this.props.loggedUser.person); + + fetchedPerson.followedBy = followedBy; + + this.setState({ fetchedPerson }); + }); + } else { + this.setState({ showToContinue: true }); + } + }; + + unfollow = (personToUnfollow) => { + if (!this.props.loggedUser?.anonymous) { + this.props.unfollow(personToUnfollow).then(() => { + let fetchedPerson = this.state.fetchedPerson; + let followedBy = []; + + const { loggedUser } = this.props; + + for (let count in fetchedPerson.followedBy) { + if (fetchedPerson.followedBy[count].slug !== loggedUser.person.slug) { + followedBy.push(fetchedPerson.followedBy[count]); + } + } + + fetchedPerson.followedBy = followedBy; + + this.setState({ fetchedPerson }); + }); + } else { + this.setState({ showToContinue: true }); + } + }; + + isFollower = () => { + if (!this.props.loggedUser?.anonymous) { + const loggedUser = this.props.loggedUser.username; + const followerList = this.state.fetchedPerson?.followedBy; + + for (let count in followerList) { + let follower = followerList[count]; + + if (follower.slug.substring(2) === loggedUser) { + return true; + } + } + } + + return false; + }; + + like = (personToLike) => { + if (!this.props.loggedUser?.anonymous) { + this.props.like(personToLike).then(() => { + let fetchedPerson = this.state.fetchedPerson; + let likedBy = []; + + for (let count in fetchedPerson.likedBy) { + likedBy.push(fetchedPerson.likedBy[count]); + } + + likedBy.push(this.props.loggedUser.person); + + fetchedPerson.likedBy = likedBy; + + this.setState({ fetchedPerson }); + }); + } else { + this.setState({ showToContinue: true }); + } + }; + + unlike = (personToUnlike) => { + if (!this.props.loggedUser?.anonymous) { + this.props.unlike(personToUnlike).then(() => { + let fetchedPerson = this.state.fetchedPerson; + let likedBy = []; + + const { loggedUser } = this.props; + + for (let count in fetchedPerson.likedBy) { + if (fetchedPerson.likedBy[count].slug !== loggedUser.person.slug) { + likedBy.push(fetchedPerson.likedBy[count]); + } + } + + fetchedPerson.likedBy = likedBy; + + this.setState({ fetchedPerson }); + }); + } else { + this.setState({ showToContinue: true }); + } + }; + + isLiker = () => { + if (!this.props.loggedUser?.anonymous) { + const loggedUser = this.props.loggedUser.username; + const likerList = this.state.fetchedPerson?.likedBy; + + for (let count in likerList) { + let liker = likerList[count]; + + if (liker.slug.substring(2) === loggedUser) { + return true; + } + } + } + + return false; + }; + + handleModalClose = () => { + const username = this.props.loggedUser.username; + + this.props.getPerson(username); + + if (this.state.showToContinue) { + this.setState({ + showToContinue: false, + }); + } else if (this.state.showFollow) { + this.setState({ + showFollow: false, + }); + } else if (this.state.showLikes) { + this.setState({ + showLikes: false, + }); + } + }; + + toContinue = () => { + this.handleModalClose(); + + this.setState({ + showToContinue: true, + }); + }; + + render() { + const { fetchedPerson, loggedUser } = this.props; + + return ( + <> +
    + + + + +
    +

    + {fetchedPerson?.firstName && <>{fetchedPerson.firstName}}{" "} + {fetchedPerson?.lastName && <>{fetchedPerson.lastName}} +

    + {fetchedPerson?.showLocalRanking && ( +

    + + #3 in your region + +

    + )} + {fetchedPerson?.company && ( + <> + {fetchedPerson?.showCompanyPublic && ( + + {fetchedPerson.company} + + )} + + )} + + + this.setState({ showFollow: true, followType: "Followers" }) + } + > + {this.state.fetchedPerson?.followedBy.length} + Followers + + + this.setState({ showFollow: true, followType: "Following" }) + } + > + {this.state.fetchedPerson?.follows.length} + Following + + +
    + + this.setState({ showLikes: true })}> + {this.state.fetchedPerson?.likedBy.length} + Likes + + +
    +
    + {fetchedPerson?.bids && ( + <> + {fetchedPerson.bids.map((bid, i) => { + switch (bid) { + case "6403bf4d17b8472735a93b71a37e0bd0": + return ( + + Alpha + + ); + } + })} + + )} +
    + {fetchedPerson?.status && ( +
    + {fetchedPerson.status && ( +
    + )} +
    + )} +
    + {loggedUser.person?.slug !== fetchedPerson.slug && ( +
    + {this.isFollower() ? ( + this.unfollow(fetchedPerson.slug.substring(2))} + > + + Unfollow + + ) : ( + this.follow(fetchedPerson.slug.substring(2))} + > + + Follow + + )} + {this.isLiker() ? ( + this.unlike(fetchedPerson.slug.substring(2))} + > + + Unlike + + ) : ( + this.like(fetchedPerson.slug.substring(2))} + > + + Like + + )} +
    + )} +
    +

    Connected accounts

    +
    + e.sourceType == "GITHUB" && e.isActive + ) + ? "active" + : "" + } + /> + e.sourceType == "GITLAB" && e.isActive + ) + ? "active" + : "" + } + /> + e.sourceType == "INSTAGRAM" && e.isActive + ) + ? "active" + : "" + } + /> +
    +
    +

    Organisations

    + {fetchedPerson && ( +
    = 5 + ? "orgs text-center" + : "orgs" + } + > + {fetchedPerson.person.organisations.length > 0 ? ( + <> + {fetchedPerson.person.organisations.map((org, i) => { + return ( + + +
    + {org.avatarUrl ? ( + {org.name} + ) : ( + + )} + {org.members && ( +
    {org.members.length}
    + )} +
    +
    +
    + +
    + {org.platformName}/ + + {org.name} + +
    +
    +
    + + {org.members + ? org.members.length + : "Unknown"}{" "} + members + +
    +
    +
    + {org.members && + org.members.length > 0 && + org.members + .slice(0, 8) + .map((member, m) => { + return ( + + + {member.username} + + {member.username} + + ); + })} +
    + {org.members && org.members.length > 9 && ( +
    + {org.platformName === "github" && ( + + Show all + + )} +
    + )} +
    +
    +
    + +

    {org.description}

    + + Show more + + +
    +
    +
    + ); + })} + + ) : ( + + {fetchedPerson.title} hasn't joined an organisation yet. + + )} +
    + )} + {fetchedPerson.person.statistic?.languages?.length > 0 && ( +
    +
    +

    Top languages

    + + {fetchedPerson.person.statistic.languages + .slice( + 0, + this.state.limitLanguages + ? 3 + : fetchedPerson.person.statistic.languages.length - 1 + ) + .map((language, i) => { + return ( + +
    +
    + + {language.name} +
    + + {language.share}% + +
    +
    + ); + })} + {this.state.limitLanguages && + fetchedPerson.person.statistic.languages.length > 3 ? ( +

    this.setState({ limitLanguages: false })} + > + Show more +

    + ) : ( +

    this.setState({ limitLanguages: true })} + > + Show less +

    + )} +
    + )} +
    +
    + {this.state.showToContinue && ( + + )} + {this.state.showFollow && ( + + )} + {this.state.showLikes && ( + + )} + + ); + } +} +//#endregion + +//#region > Redux Mapping +const mapStateToProps = (state) => ({ + fetchedPerson: state.person.fetchedPerson, + loggedUser: state.user.user, +}); + +const mapDispatchToProps = (dispatch) => { + return { + follow: (personToFollow) => dispatch(follow(personToFollow)), + unfollow: (personToUnfollow) => dispatch(unfollow(personToUnfollow)), + like: (personToLike) => dispatch(like(personToLike)), + unlike: (personToUnlike) => dispatch(unlike(personToUnlike)), + getPerson: (personName) => dispatch(getPerson(personName)), + }; +}; +//#endregion + +//#region > Exports +//> Default Component +/** + * Provides its connected component with the pieces of the data it needs from + * the store, and the functions it can use to dispatch actions to the store. + */ +export default connect(mapStateToProps, mapDispatchToProps)(InfoCard); +//#endregion + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/organisms/ProfileInfo/profileinfo.scss b/src/components/organisms/sections/person/InfoCard/infocard.scss similarity index 100% rename from src/components/organisms/ProfileInfo/profileinfo.scss rename to src/components/organisms/sections/person/InfoCard/infocard.scss diff --git a/src/components/organisms/SoftwareTabs/index.jsx b/src/components/organisms/sections/person/Tabs/index.jsx similarity index 70% rename from src/components/organisms/SoftwareTabs/index.jsx rename to src/components/organisms/sections/person/Tabs/index.jsx index 3772496..7612219 100644 --- a/src/components/organisms/SoftwareTabs/index.jsx +++ b/src/components/organisms/sections/person/Tabs/index.jsx @@ -11,9 +11,14 @@ import { MDBBadge } from "mdbreact"; import { connect } from "react-redux"; //> Components -import { ProjectTab, OverviewTab, TalksTab } from "../tabs"; +import { + ProjectTab, + OverviewTab, + TalksTab, + AchievementsTab, +} from "../../../tabs"; //> Style sheet -import "./softwaretabs.scss"; +import "./tabs.scss"; //#endregion //#region > Components @@ -30,13 +35,13 @@ class SoftwareTabs extends React.Component { }; isSameOrigin = () => { - const { fetchedUser, loggedUser } = this.props; + const { fetchedPerson, loggedUser } = this.props; - return fetchedUser.username === loggedUser.username; + return fetchedPerson.slug === loggedUser?.person?.slug; }; render() { - const { fetchedUser } = this.props; + const { fetchedPerson } = this.props; const { activeTab } = this.state; const tabItems = [ { @@ -48,8 +53,8 @@ class SoftwareTabs extends React.Component { { title: "Projects", visible: true, - pill: this.props.fetchedUser?.platformData?.profile?.repositories - ? this.props.fetchedUser?.platformData?.profile?.repositories?.length + pill: fetchedPerson?.person?.projects + ? fetchedPerson?.person?.projects.length : "0", notification: false, }, @@ -65,18 +70,18 @@ class SoftwareTabs extends React.Component { notification: false, }, { - title: "Papers", + title: "Achievements", visible: true, - pill: "0", + pill: fetchedPerson?.achievements + ? fetchedPerson?.achievements.length + : "0", notification: false, }, { title: "Talks", visible: true, notification: false, - pill: this.props.fetchedUser?.platformData.talks - ? this.props.fetchedUser?.platformData.talks.length - : "0", + pill: fetchedPerson?.talks ? fetchedPerson?.talks.length : "0", notification: false, }, ]; @@ -99,25 +104,33 @@ class SoftwareTabs extends React.Component { })}
    - {activeTab === 0 && ( - - )} + {activeTab === 0 && } {activeTab === 1 && ( )} - {activeTab > 1 && activeTab < 5 && ( + {activeTab > 1 && activeTab < 4 && (

    This feature is not available just yet.

    )} - {activeTab === 5 && } + {activeTab === 4 && ( + + )} + {activeTab === 5 && ( + + )}
    ); @@ -127,8 +140,8 @@ class SoftwareTabs extends React.Component { //#region > Redux Mapping const mapStateToProps = (state) => ({ - loggedUser: state.auth.loggedUser, - fetchedUser: state.user.fetchedUser, + loggedUser: state.user.user, + fetchedPerson: state.person.fetchedPerson, }); const mapDispatchToProps = (dispatch) => { diff --git a/src/components/organisms/SoftwareTabs/softwaretabs.scss b/src/components/organisms/sections/person/Tabs/tabs.scss similarity index 100% rename from src/components/organisms/SoftwareTabs/softwaretabs.scss rename to src/components/organisms/sections/person/Tabs/tabs.scss diff --git a/src/components/organisms/tabs/AchievementsTab/achievementstab.scss b/src/components/organisms/tabs/AchievementsTab/achievementstab.scss new file mode 100644 index 0000000..483dfa8 --- /dev/null +++ b/src/components/organisms/tabs/AchievementsTab/achievementstab.scss @@ -0,0 +1,26 @@ +.achievement-list { + margin-top: 2rem; + + img { + height: 10rem; + } + + li { + cursor: pointer; + list-style: none; + } + + .col-md-4 { + margin-top: 3.5rem; + } +} + +.submit-sequence { + padding: 0.5rem 2rem !important; + margin-top: 0 !important; +} + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/organisms/tabs/AchievementsTab/index.jsx b/src/components/organisms/tabs/AchievementsTab/index.jsx new file mode 100644 index 0000000..7e808f3 --- /dev/null +++ b/src/components/organisms/tabs/AchievementsTab/index.jsx @@ -0,0 +1,146 @@ +//#region > Imports +//> React +// Contains all the functionality necessary to define React components +import React from "react"; +// Runtime type checking for React props and similar objects +import PropTypes from "prop-types"; +//> 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"; +//> MDB +// "Material Design for Bootstrap" is a great UI design framework +import { MDBRow, MDBBtn, MDBCol } from "mdbreact"; + +//> Actions +// Functions to send data from the application to the store +import { redeemAchievement } from "../../../../store/actions/personActions"; +//> Components +import { Achievement } from "../../../atoms"; +//> Style sheet +import "./achievementstab.scss"; +//#endregion + +//#region > Components +/** @class A component which lists all achievements of a user */ +class AchievementsTab extends React.Component { + state = { + sequence: "", + loading: false, + achievements: [], + }; + + componentDidMount = () => { + const { achievements } = this.props; + + this.setState({ + achievements: achievements, + }); + }; + + componentDidUpdate = () => { + const { achievements } = this.props; + + if (achievements !== this.state.achievements) { + this.setState({ + achievements: achievements, + }); + } + }; + + handleChange = (e) => { + this.setState({ + [e.target.name]: e.target.value, + }); + }; + + handleSubmit = () => { + this.setState( + { + loading: true, + }, + async () => { + this.props.redeemAchievement(this.state.sequence).then((res) => { + if (res.achievement !== null) { + let achievements = []; + + for (let count in this.state.achievements) { + achievements.push(this.state.achievements[count]); + } + + achievements.push(res.achievement); + + this.setState({ achievements }); + } + }); + } + ); + }; + + render() { + const { achievements } = this.state; + + return ( + <> +

    Achievements

    + {this.props.loggedUser?.person?.slug === + this.props.fetchedPerson?.slug && ( +
    + Flag + + + this.handleChange(e)} + value={this.state.sequence} + /> + + + + Redeem flag + + + +
    + )} + + {achievements && + achievements.map((achievement, i) => { + return ; + })} + + + ); + } +} +//#endregion + +//#region > Redux Mapping +const mapStateToProps = (state) => ({ + fetchedPerson: state.person.fetchedPerson, + loggedUser: state.user.user, +}); + +const mapDispatchToProps = (dispatch) => { + return { + redeemAchievement: (sequence) => dispatch(redeemAchievement(sequence)), + }; +}; +//#endregion + +//#region > Exports +//> Default Component +export default connect(mapStateToProps, mapDispatchToProps)(AchievementsTab); +//#endregion + +/** + * SPDX-License-Identifier: (EUPL-1.2) + * Copyright © 2019-2020 Simon Prast + */ diff --git a/src/components/organisms/tabs/OverviewTab/index.jsx b/src/components/organisms/tabs/OverviewTab/index.jsx index 4fa4188..5c04fc5 100644 --- a/src/components/organisms/tabs/OverviewTab/index.jsx +++ b/src/components/organisms/tabs/OverviewTab/index.jsx @@ -14,7 +14,7 @@ import { connect } from "react-redux"; //> Actions // Functions to send data from the application to the store -import { writeCacheAction } from "../../../../store/actions/userActions"; +// import { writeCacheAction } from "../../../../store/actions/userActions"; //> Style sheet import "./overviewtab.scss"; //> Components @@ -26,7 +26,14 @@ import { LatestActivity, ErrorBoundary, } from "../../../atoms"; +import { + AIGallery, + AISongGallery, + AIVideoGallery, + PhotoMap, +} from "../../../organisms/sections/media"; import { MovableBoundary } from "../../../molecules"; +import { updateSettings } from "../../../../store/actions/personActions"; //#endregion //#region > Constant Variables @@ -61,8 +68,9 @@ const pinned = [ /** @class This component implements the overview tab */ class OverviewTab extends React.Component { state = { - selectedYear: undefined, + selectedYearIndex: undefined, edit: false, + toggleEdit: false, }; selectDay = (day, wkey, dkey) => { @@ -75,24 +83,40 @@ class OverviewTab extends React.Component { }); }; - handleEditClick = (platformData) => { + handleEditClick = (movablePool) => { if (this.state.edit) { - this.props.writeCache(platformData); + this.props.saveSettings({ movablePool: JSON.stringify(movablePool) }); } this.setState({ edit: !this.state.edit }); }; render() { - const { fetchedUser, sameOrigin } = this.props; - const platformData = fetchedUser.platformData; + const { fetchedPerson, sameOrigin } = this.props; - // Create empty pool if there isn't already one - if (!fetchedUser.platformData.user.movablePool) { - fetchedUser.platformData.user.movablePool = {}; - } + // // Create empty pool if there isn't already one + // if (!fetchedUser.platformData.user.movablePool) { + // fetchedUser.platformData.user.movablePool = {}; + // } + + const { + currentStatistic, + yearsStatistic, + languages, + } = fetchedPerson.person; - const movablePool = fetchedUser.platformData.user.movablePool; + const { + displayProgrammingLanguages, + display2dCalendar, + display3dCalendar, + displayContributionTypes, + displayWeekActivity, + displayImageGallery, + displayVideoGallery, + displayMusicGallery, + displayMap, + } = fetchedPerson; + let platformData = ""; return ( <> @@ -100,31 +124,48 @@ class OverviewTab extends React.Component { {sameOrigin && (
    + {this.state.toggleEdit && ( + + this.handleEditClick(fetchedPerson.movablePool) + } + > + {this.state.edit ? ( + + + Save + + ) : ( + + + Move items + + )} + + )} this.handleEditClick(platformData)} + onClick={() => + this.setState({ toggleEdit: !this.state.toggleEdit }) + } + disabled={this.state.edit} > - {this.state.edit ? ( - - - Save items - - ) : ( - Move items - )} + {this.state.toggleEdit ? Done : Edit}
    )} - {platformData && ( + {languages && displayProgrammingLanguages && ( - {platformData.statistic.languages.map((language, i) => { + {languages?.map((language, i) => { if (i < 6) { return ( @@ -163,96 +204,139 @@ class OverviewTab extends React.Component { )} , <> - {platformData && - (platformData.user.settings.show3DDiagram || - platformData.user.settings.show2DDiagram) && ( -
    - {platformData.statistic.years.map((year, i) => { - return ( - - this.setState({ selectedYear: year.year }) - } - > - {year.year} - - ); - })} - - this.setState({ selectedYear: undefined }) - } - > - Current - -
    - )} + {(display3dCalendar || display3dCalendar) && ( +
    + {yearsStatistic.map((year, i) => { + return ( + + this.setState({ selectedYearIndex: i }) + } + > + {year.year} + + ); + })} + + this.setState({ selectedYearIndex: undefined }) + } + > + Current + +
    + )} , <> - {platformData && platformData.user.settings.show3DDiagram && ( + {display3dCalendar && currentStatistic && yearsStatistic && ( + )} + , + <> + {displayImageGallery && ( + + link.linkType === "PHOTO" || + link.linkType === "INSTAGRAM" + )} + sameOrigin={sameOrigin && this.state.toggleEdit} /> )} , <> - {platformData && platformData.user.settings.show2DDiagram && ( + {displayImageGallery && ( + link.linkType === "YOUTUBE" + )} + sameOrigin={sameOrigin && this.state.toggleEdit} + /> + )} + , + <> + {displayImageGallery && ( + link.linkType === "SOUNDCLOUD" + )} + sameOrigin={sameOrigin && this.state.toggleEdit} + /> + )} + , + <>{displayMap && }, + <> + {display2dCalendar && currentStatistic && yearsStatistic && ( )} ,
    -

    Contribution Types

    - {platformData && - !platformData.user.settings.showContribDiagram && ( + {true && + currentStatistic && + yearsStatistic && + displayContributionTypes && (
    +

    Contribution Types

    )} , <> -

    - Activity - -

    -

    Weekly overview

    - + {currentStatistic && + yearsStatistic && + displayWeekActivity && ( +
    +

    + Activity + +

    +

    Weekly overview

    + +
    + )} , ]} movementAxis="x" @@ -268,20 +352,14 @@ class OverviewTab extends React.Component { } //#endregion -//#region > PropTypes -OverviewTab.propTypes = { - platformData: PropTypes.object.isRequired, -}; -//#endregion - //#region > Redux Mapping const mapStateToProps = (state) => ({ - fetchedUser: state.user.fetchedUser, + fetchedPerson: state.person.fetchedPerson, }); const mapDispatchToProps = (dispatch) => { return { - writeCache: (platformData) => dispatch(writeCacheAction(platformData)), + saveSettings: (nextSettings) => dispatch(updateSettings(nextSettings)), }; }; //#endregion diff --git a/src/components/organisms/tabs/TalksTab/index.jsx b/src/components/organisms/tabs/TalksTab/index.jsx index 597b599..8ca79c3 100644 --- a/src/components/organisms/tabs/TalksTab/index.jsx +++ b/src/components/organisms/tabs/TalksTab/index.jsx @@ -2,6 +2,8 @@ //> React // Contains all the functionality necessary to define React components import React from "react"; +// Runtime type checking for React props and similar objects +import PropTypes from "prop-types"; //> React Router bindings to DOM import { withRouter, Link } from "react-router-dom"; //> MDB @@ -20,19 +22,21 @@ import { // Allows to React components read data from a Redux store, and dispatch actions // to the store to update data. import { connect } from "react-redux"; +//> Additional +// Used to display the time in a readable format +import moment from "moment"; -//> Actions -// Functions to send data from the application to the store -import { deleteTalkAction } from "../../../../store/actions/userActions"; //> Style sheet import "./talkstab.scss"; -//> Modules -import { TalkUploadModal } from "../../../molecules/modals"; +//> Actions +// Functions to send data from the application to the store +import { addTalk, deleteTalk } from "../../../../store/actions/personActions"; +import { UploadModal } from "../../../molecules/modals"; //#endregion //#region > Components /** @class A component which lists all talks */ -class Talks extends React.Component { +class TalkTab extends React.Component { state = { showUpload: false, loading: true, @@ -46,23 +50,35 @@ class Talks extends React.Component { } }; + handleSuccess = (event) => { + this.props.addTalk( + event.name, + "", + "https://docs.google.com/viewer?embedded=true&url=" + event.displayUrl, + event.downloadUrl, + event.path, + event.html_url + ); + }; + updateIframe = (talk) => { let iframe; if (talk.interval.loaded === false) { - if (document.getElementById(talk.uid)) { - iframe = document.getElementById(talk.uid); + if (document.getElementById(talk.id)) { + iframe = document.getElementById(talk.id); iframe.src = talk.displayUrl; } } }; render() { - const { loggedUser, fetchedUser } = this.props; - const talkList = fetchedUser?.platformData?.talks; + const { loggedUser, fetchedPerson, talkList } = this.props; - if (talkList) { - talkList.map((talk) => { + const talks = JSON.parse(JSON.stringify(talkList)); + + if (talks) { + talks.map((talk) => { talk.social = { likes: 17, date: new Date().toLocaleDateString("en-US", { @@ -87,7 +103,7 @@ class Talks extends React.Component {

    Talks

    - {loggedUser.username === fetchedUser.username && ( + {loggedUser?.person?.slug === fetchedPerson.slug && ( - {talkList && - talkList.map((talk, i) => { + {talks && + talks.map((talk, i) => { return ( - {talk.name.length > 25 - ? talk.name.substring(0, 25) + "..." - : talk.name} + {talk.title?.length > 25 + ? talk.title?.substring(0, 25) + "..." + : talk.title} - {loggedUser.username === fetchedUser.username && ( - this.props.deleteTalk(talk)}> + {loggedUser?.person?.slug === fetchedPerson.slug && ( + this.props.deleteTalk(talk.id)} + > @@ -139,11 +152,11 @@ class Talks extends React.Component {