Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Added Profile page with Bookmarks #1657

Merged
merged 10 commits into from
Dec 19, 2024
8 changes: 8 additions & 0 deletions gatsby-node.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const siteConfig = require('./site-config');
const { createFilePath } = require('gatsby-source-filesystem');
const appInsights = require('applicationinsights');
const WebpackAssetsManifest = require('webpack-assets-manifest');
Expand Down Expand Up @@ -237,6 +238,13 @@ exports.createPages = async ({ graphql, actions }) => {
isPermanent: true,
});
});

const profilePage = require.resolve('./src/pages/profile.js');
createPage({
path: `${siteConfig.pathPrefix}/people/`,
matchPath: `${siteConfig.pathPrefix}/people/:gitHubUsername`,
component: profilePage,
});
});
};

Expand Down
1 change: 1 addition & 0 deletions site-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const titles = {
'/user/': `User Rules`,
'/orphaned/': `Orphaned Rules`,
'/archived/': `Archived Rules`,
'/profile/': `Profile`,
};

module.exports = {
Expand Down
39 changes: 24 additions & 15 deletions src/components/dropdown-card/dropdown-card.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useAuth0 } from '@auth0/auth0-react';
import GitHubIcon from '-!svg-react-loader!../../images/github.svg';
import { navigate } from 'gatsby';
import PropTypes from 'prop-types';
import React from 'react';

const DropdownCard = () => {
const DropdownCard = ({ setOpen }) => {
const { logout, user } = useAuth0();
return (
<>
<div className="dropdown-list">
<div className="dropdown-userinfo-container">
<p className="dropdown-username">@{user.nickname}</p>
<a
className="github-link"
href={`https://www.github.com/${user.nickname}`}
>
<GitHubIcon className="dropdown-github-icon" />
Manage GitHub Account
</a>
</div>
<p className="dropdown-username">@{user.nickname}</p>
<button
className="dropdown-btn dropdown-icon dropdown-github-icon"
onClick={() => {
setOpen(false);
window.open(`https://www.github.com/${user.nickname}`);
}}
>
GitHub Profile
</button>
<button
className="dropdown-btn dropdown-icon dropdown-user-icon"
onClick={() => {
setOpen(false);
navigate('/profile');
}}
>
SSW.Rules Profile
</button>
<hr />
<button
className="dropdown-signout-btn-container"
className="dropdown-btn dropdown-icon dropdown-signout-icon"
onClick={() => {
logout({ returnTo: process.env.AUTH0_REDIRECT_URI });
}}
Expand Down
2 changes: 1 addition & 1 deletion src/components/dropdown-icon/dropdown-icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const DropdownIcon = () => {
return (
<div className="dropdown" ref={drop}>
<DropdownBadge onClick={() => setOpen((open) => !open)} />
{open && <DropdownCard />}
{open && <DropdownCard setOpen={setOpen} />}
</div>
);
};
Expand Down
16 changes: 8 additions & 8 deletions src/components/footer/footer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import React from 'react';
import preval from 'preval.macro';
import moment from 'moment';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faHeart } from '@fortawesome/free-solid-svg-icons';
import {
faFacebook,
faInstagram,
Expand All @@ -11,8 +6,13 @@ import {
faTiktok,
faYoutube,
} from '@fortawesome/free-brands-svg-icons';
import { pathPrefix } from '../../../site-config';
import { faHeart } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import moment from 'moment';
import preval from 'preval.macro';
import React from 'react';
import GitHubButton from 'react-github-btn';
import { pathPrefix } from '../../../site-config';

const buildTimestamp = preval`module.exports = new Date().getTime();`;

Expand All @@ -33,8 +33,8 @@ const Footer = () => {
rel="noreferrer"
className="action-button-label footer-greybar-link"
>
Star us on GitHub
</a>{' '}.
Star us on GitHub.
</a>{' '}
</span>
<GitHubButton
href="https://github.com/SSWConsulting/SSW.Rules"
Expand Down
224 changes: 224 additions & 0 deletions src/components/profile-content/profile-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import BookmarkIcon from '-!svg-react-loader!../../images/bookmarkIcon.svg';
import { useAuth0 } from '@auth0/auth0-react';
import {
faArrowCircleRight,
faBook,
faFileLines,
faQuoteLeft,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Link } from 'gatsby';
import MD from 'gatsby-custom-md';
import PropTypes from 'prop-types';
import React, { useEffect, useRef, useState } from 'react';
import useAppInsights from '../../hooks/useAppInsights';
import { GetBookmarksForUser, RemoveBookmark } from '../../services/apiService';
import { useAuthService } from '../../services/authService';
import GreyBox from '../greybox/greybox';
import { Filter } from '../profile-filter-menu/profile-filter-menu';
import RadioButton from '../radio-button/radio-button';

const ProfileContent = (props) => {
const [bookmarkedRules, setBookmarkedRules] = useState([]);
const [change, setChange] = useState(0);
const [viewStyle, setViewStyle] = useState('titleOnly');
const { user, isAuthenticated } = useAuth0();
const { fetchIdToken } = useAuthService();
const { trackException } = useAppInsights();
const handleOptionChange = (e) => {
setViewStyle(e.target.value);
};

async function onRemoveClick(ruleGuid) {
const jwt = await fetchIdToken();
if (isAuthenticated) {
if (props.filter == Filter.Bookmarks) {
RemoveBookmark({ ruleGuid: ruleGuid, UserId: user.sub }, jwt)
.then(() => {
setChange(change + 1);
props.setState(props.state + 1);
})
.catch((err) => {
trackException(err, 3);
});
}
}
}

function getBookmarkList() {
GetBookmarksForUser(user.sub)
.then((success) => {
const allRules = props.data.allMarkdownRemark.nodes;
const bookmarkedGuids =
success.bookmarkedRules.size != 0
? success.bookmarkedRules.map((r) => r.ruleGuid)
: null;
const bookmarkedRulesMap = allRules.filter((value) =>
bookmarkedGuids.includes(value.frontmatter.guid)
);
const bookmarkedRulesSpread = bookmarkedRulesMap.map((r) => ({
...r.frontmatter,
excerpt: r.excerpt,
htmlAst: r.htmlAst,
}));
setBookmarkedRules(bookmarkedRulesSpread);
props.setBookmarkedRulesCount(bookmarkedRulesSpread.length);
})
.catch((err) => {
trackException(err, 3);
});
}

useEffect(() => {
if (isAuthenticated) {
getBookmarkList();
}
}, [isAuthenticated, props.filter, change, props.state]);

return (
<>
<div className="border-b border-solid border-b-gray-100 grid grid-cols-1 gap-5 p-4 text-center lg:grid-cols-5">
<div></div>
<RadioButton
id="customRadioInline1"
name="customRadioInline1"
value="titleOnly"
selectedOption={viewStyle}
handleOptionChange={handleOptionChange}
labelText="View titles only"
icon={faQuoteLeft}
/>
<RadioButton
id="customRadioInline3"
name="customRadioInline1"
value="blurb"
selectedOption={viewStyle}
handleOptionChange={handleOptionChange}
labelText="Show blurb"
icon={faFileLines}
/>
<RadioButton
id="customRadioInline2"
name="customRadioInline1"
value="all"
selectedOption={viewStyle}
handleOptionChange={handleOptionChange}
labelText="Gimme everything!"
icon={faBook}
/>
</div>
{bookmarkedRules ? (
<RuleList
rules={props.filter == Filter.Bookmarks ? bookmarkedRules : null}
viewStyle={viewStyle}
state={props.state}
type={props.filter == Filter.Bookmarks ? 'bookmark' : ''}
onRemoveClick={onRemoveClick}
/>
) : (
<p className="no-content-message">Loading...</p>
)}
</>
);
};
ProfileContent.propTypes = {
data: PropTypes.object.isRequired,
filter: PropTypes.number.isRequired,
setState: PropTypes.func.isRequired,
state: PropTypes.number.isRequired,
setBookmarkedRulesCount: PropTypes.func.isRequired,
};

const RuleList = ({ rules, viewStyle, type, onRemoveClick, state }) => {
const linkRef = useRef();
const components = {
greyBox: GreyBox,
};

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

return (
<>
{rules.length == 0 ? (
<>
<div className="no-content-message">
<p className="no-tagged-message">
{type == 'bookmark'
? 'No bookmarks? Use them to save rules for later!'
: ''}
</p>
</div>
</>
) : (
<div className="p-12">
<ol className="rule-number">
{rules.map((rule) => {
return (
<div key={rule.guid} className="mb-3">
<li key={rule.guid}>
<section className="rule-content-title sm:pl-2">
<div className="rule-header-container justify-between">
<h2 className="flex flex-col justify-center">
<Link ref={linkRef} to={`/${rule.uri}`}>
{rule.title}
</Link>
</h2>
{type == 'bookmark' ? (
<div className="profile-rule-buttons flex flex-col justify-center">
<button
className="tooltip"
onClick={() => onRemoveClick(rule.guid)}
>
<BookmarkIcon className="bookmark-icon-pressed" />
<span className="tooltiptext">
Remove Bookmark
</span>
</button>
</div>
) : (
''
)}
</div>
</section>
<section
className={`rule-content px-4 mb-4 pb-4
${viewStyle === 'blurb' ? 'visible' : 'hidden'}`}
>
<div dangerouslySetInnerHTML={{ __html: rule.excerpt }} />
<p className="pt-5 pb-0">
<Link
ref={linkRef}
to={`/${rule.uri}`}
title={`Read more about ${rule.title}`}
>
<FontAwesomeIcon icon={faArrowCircleRight} /> Read
more
</Link>
</p>
</section>
<section
className={`rule-content px-4 mb-4
${viewStyle === 'all' ? 'visible' : 'hidden'}`}
>
<MD components={components} htmlAst={rule.htmlAst} />
</section>
</li>
</div>
);
})}
</ol>
</div>
)}
</>
);
};

RuleList.propTypes = {
rules: PropTypes.array.isRequired,
viewStyle: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
onRemoveClick: PropTypes.func.isRequired,
state: PropTypes.number,
};

export default ProfileContent;
52 changes: 52 additions & 0 deletions src/components/profile-filter-menu/profile-filter-menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import BookmarkIcon from '-!svg-react-loader!../../images/bookmarkIcon.svg';
import PropTypes from 'prop-types';
import React from 'react';

const ProfileFilterMenu = ({
selectedFilter,
setSelectedFilter,
bookmarkedRulesCount,
}) => {
return (
<>
<div className="filter-menu">
<button
className="menu-item"
style={
selectedFilter == Filter.Bookmarks
? {
gridColumn: 1,
borderBottom: '0.25rem solid #cc4141',
paddingBottom: '0.25rem',
}
: { gridColumn: 1 }
}
onClick={() => {
setSelectedFilter(Filter.Bookmarks);
}}
>
Bookmarks
<BookmarkIcon
className={
selectedFilter != Filter.Bookmarks
? 'filter-menu-bookmark-icon'
: 'filter-menu-bookmark-icon-pressed'
}
/>
<div className="mx-1">{bookmarkedRulesCount ?? 0}</div>
</button>
</div>
</>
);
};

ProfileFilterMenu.propTypes = {
selectedFilter: PropTypes.number.isRequired,
setSelectedFilter: PropTypes.func.isRequired,
bookmarkedRulesCount: PropTypes.number,
};

export const Filter = {
Bookmarks: 0,
};
export default ProfileFilterMenu;
Loading
Loading