Skip to content

Commit f39c3fc

Browse files
✨ Added Profile page with Bookmarks (#1657)
* add link to profile page in dropdown menu * added profile page to gatsby node * added profile pages for bookmarks * added styles * fix eslint error * fix "remove bookmark" and responsive styling * delete comment * fixed user dropdown style as per feedback * simplified dropdown styling classes * fixed padding in mobile view
1 parent 9bc137b commit f39c3fc

File tree

9 files changed

+558
-54
lines changed

9 files changed

+558
-54
lines changed

gatsby-node.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const siteConfig = require('./site-config');
12
const { createFilePath } = require('gatsby-source-filesystem');
23
const appInsights = require('applicationinsights');
34
const WebpackAssetsManifest = require('webpack-assets-manifest');
@@ -237,6 +238,13 @@ exports.createPages = async ({ graphql, actions }) => {
237238
isPermanent: true,
238239
});
239240
});
241+
242+
const profilePage = require.resolve('./src/pages/profile.js');
243+
createPage({
244+
path: `${siteConfig.pathPrefix}/people/`,
245+
matchPath: `${siteConfig.pathPrefix}/people/:gitHubUsername`,
246+
component: profilePage,
247+
});
240248
});
241249
};
242250

site-config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const titles = {
66
'/user/': `User Rules`,
77
'/orphaned/': `Orphaned Rules`,
88
'/archived/': `Archived Rules`,
9+
'/profile/': `Profile`,
910
};
1011

1112
module.exports = {

src/components/dropdown-card/dropdown-card.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
1-
import React from 'react';
2-
import PropTypes from 'prop-types';
31
import { useAuth0 } from '@auth0/auth0-react';
4-
import GitHubIcon from '-!svg-react-loader!../../images/github.svg';
2+
import { navigate } from 'gatsby';
3+
import PropTypes from 'prop-types';
4+
import React from 'react';
55

6-
const DropdownCard = () => {
6+
const DropdownCard = ({ setOpen }) => {
77
const { logout, user } = useAuth0();
88
return (
99
<>
1010
<div className="dropdown-list">
11-
<div className="dropdown-userinfo-container">
12-
<p className="dropdown-username">@{user.nickname}</p>
13-
<a
14-
className="github-link"
15-
href={`https://www.github.com/${user.nickname}`}
16-
>
17-
<GitHubIcon className="dropdown-github-icon" />
18-
Manage GitHub Account
19-
</a>
20-
</div>
11+
<p className="dropdown-username">@{user.nickname}</p>
12+
<button
13+
className="dropdown-btn dropdown-icon dropdown-github-icon"
14+
onClick={() => {
15+
setOpen(false);
16+
window.open(`https://www.github.com/${user.nickname}`);
17+
}}
18+
>
19+
GitHub Profile
20+
</button>
21+
<button
22+
className="dropdown-btn dropdown-icon dropdown-user-icon"
23+
onClick={() => {
24+
setOpen(false);
25+
navigate('/profile');
26+
}}
27+
>
28+
SSW.Rules Profile
29+
</button>
2130
<hr />
2231
<button
23-
className="dropdown-signout-btn-container"
32+
className="dropdown-btn dropdown-icon dropdown-signout-icon"
2433
onClick={() => {
2534
logout({ returnTo: process.env.AUTH0_REDIRECT_URI });
2635
}}

src/components/dropdown-icon/dropdown-icon.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const DropdownIcon = () => {
2020
return (
2121
<div className="dropdown" ref={drop}>
2222
<DropdownBadge onClick={() => setOpen((open) => !open)} />
23-
{open && <DropdownCard />}
23+
{open && <DropdownCard setOpen={setOpen} />}
2424
</div>
2525
);
2626
};

src/components/footer/footer.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
import React from 'react';
2-
import preval from 'preval.macro';
3-
import moment from 'moment';
4-
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5-
import { faHeart } from '@fortawesome/free-solid-svg-icons';
61
import {
72
faFacebook,
83
faInstagram,
@@ -11,8 +6,13 @@ import {
116
faTiktok,
127
faYoutube,
138
} from '@fortawesome/free-brands-svg-icons';
14-
import { pathPrefix } from '../../../site-config';
9+
import { faHeart } from '@fortawesome/free-solid-svg-icons';
10+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11+
import moment from 'moment';
12+
import preval from 'preval.macro';
13+
import React from 'react';
1514
import GitHubButton from 'react-github-btn';
15+
import { pathPrefix } from '../../../site-config';
1616

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

@@ -33,8 +33,8 @@ const Footer = () => {
3333
rel="noreferrer"
3434
className="action-button-label footer-greybar-link"
3535
>
36-
Star us on GitHub
37-
</a>{' '}.
36+
Star us on GitHub.
37+
</a>{' '}
3838
</span>
3939
<GitHubButton
4040
href="https://github.com/SSWConsulting/SSW.Rules"
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import BookmarkIcon from '-!svg-react-loader!../../images/bookmarkIcon.svg';
2+
import { useAuth0 } from '@auth0/auth0-react';
3+
import {
4+
faArrowCircleRight,
5+
faBook,
6+
faFileLines,
7+
faQuoteLeft,
8+
} from '@fortawesome/free-solid-svg-icons';
9+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
10+
import { Link } from 'gatsby';
11+
import MD from 'gatsby-custom-md';
12+
import PropTypes from 'prop-types';
13+
import React, { useEffect, useRef, useState } from 'react';
14+
import useAppInsights from '../../hooks/useAppInsights';
15+
import { GetBookmarksForUser, RemoveBookmark } from '../../services/apiService';
16+
import { useAuthService } from '../../services/authService';
17+
import GreyBox from '../greybox/greybox';
18+
import { Filter } from '../profile-filter-menu/profile-filter-menu';
19+
import RadioButton from '../radio-button/radio-button';
20+
21+
const ProfileContent = (props) => {
22+
const [bookmarkedRules, setBookmarkedRules] = useState([]);
23+
const [change, setChange] = useState(0);
24+
const [viewStyle, setViewStyle] = useState('titleOnly');
25+
const { user, isAuthenticated } = useAuth0();
26+
const { fetchIdToken } = useAuthService();
27+
const { trackException } = useAppInsights();
28+
const handleOptionChange = (e) => {
29+
setViewStyle(e.target.value);
30+
};
31+
32+
async function onRemoveClick(ruleGuid) {
33+
const jwt = await fetchIdToken();
34+
if (isAuthenticated) {
35+
if (props.filter == Filter.Bookmarks) {
36+
RemoveBookmark({ ruleGuid: ruleGuid, UserId: user.sub }, jwt)
37+
.then(() => {
38+
setChange(change + 1);
39+
props.setState(props.state + 1);
40+
})
41+
.catch((err) => {
42+
trackException(err, 3);
43+
});
44+
}
45+
}
46+
}
47+
48+
function getBookmarkList() {
49+
GetBookmarksForUser(user.sub)
50+
.then((success) => {
51+
const allRules = props.data.allMarkdownRemark.nodes;
52+
const bookmarkedGuids =
53+
success.bookmarkedRules.size != 0
54+
? success.bookmarkedRules.map((r) => r.ruleGuid)
55+
: null;
56+
const bookmarkedRulesMap = allRules.filter((value) =>
57+
bookmarkedGuids.includes(value.frontmatter.guid)
58+
);
59+
const bookmarkedRulesSpread = bookmarkedRulesMap.map((r) => ({
60+
...r.frontmatter,
61+
excerpt: r.excerpt,
62+
htmlAst: r.htmlAst,
63+
}));
64+
setBookmarkedRules(bookmarkedRulesSpread);
65+
props.setBookmarkedRulesCount(bookmarkedRulesSpread.length);
66+
})
67+
.catch((err) => {
68+
trackException(err, 3);
69+
});
70+
}
71+
72+
useEffect(() => {
73+
if (isAuthenticated) {
74+
getBookmarkList();
75+
}
76+
}, [isAuthenticated, props.filter, change, props.state]);
77+
78+
return (
79+
<>
80+
<div className="border-b border-solid border-b-gray-100 grid grid-cols-1 gap-5 p-4 text-center lg:grid-cols-5">
81+
<div></div>
82+
<RadioButton
83+
id="customRadioInline1"
84+
name="customRadioInline1"
85+
value="titleOnly"
86+
selectedOption={viewStyle}
87+
handleOptionChange={handleOptionChange}
88+
labelText="View titles only"
89+
icon={faQuoteLeft}
90+
/>
91+
<RadioButton
92+
id="customRadioInline3"
93+
name="customRadioInline1"
94+
value="blurb"
95+
selectedOption={viewStyle}
96+
handleOptionChange={handleOptionChange}
97+
labelText="Show blurb"
98+
icon={faFileLines}
99+
/>
100+
<RadioButton
101+
id="customRadioInline2"
102+
name="customRadioInline1"
103+
value="all"
104+
selectedOption={viewStyle}
105+
handleOptionChange={handleOptionChange}
106+
labelText="Gimme everything!"
107+
icon={faBook}
108+
/>
109+
</div>
110+
{bookmarkedRules ? (
111+
<RuleList
112+
rules={props.filter == Filter.Bookmarks ? bookmarkedRules : null}
113+
viewStyle={viewStyle}
114+
state={props.state}
115+
type={props.filter == Filter.Bookmarks ? 'bookmark' : ''}
116+
onRemoveClick={onRemoveClick}
117+
/>
118+
) : (
119+
<p className="no-content-message">Loading...</p>
120+
)}
121+
</>
122+
);
123+
};
124+
ProfileContent.propTypes = {
125+
data: PropTypes.object.isRequired,
126+
filter: PropTypes.number.isRequired,
127+
setState: PropTypes.func.isRequired,
128+
state: PropTypes.number.isRequired,
129+
setBookmarkedRulesCount: PropTypes.func.isRequired,
130+
};
131+
132+
const RuleList = ({ rules, viewStyle, type, onRemoveClick, state }) => {
133+
const linkRef = useRef();
134+
const components = {
135+
greyBox: GreyBox,
136+
};
137+
138+
useEffect(() => {}, [state]);
139+
140+
return (
141+
<>
142+
{rules.length == 0 ? (
143+
<>
144+
<div className="no-content-message">
145+
<p className="no-tagged-message">
146+
{type == 'bookmark'
147+
? 'No bookmarks? Use them to save rules for later!'
148+
: ''}
149+
</p>
150+
</div>
151+
</>
152+
) : (
153+
<div className="p-12">
154+
<ol className="rule-number">
155+
{rules.map((rule) => {
156+
return (
157+
<div key={rule.guid} className="mb-3">
158+
<li key={rule.guid}>
159+
<section className="rule-content-title sm:pl-2">
160+
<div className="rule-header-container justify-between">
161+
<h2 className="flex flex-col justify-center">
162+
<Link ref={linkRef} to={`/${rule.uri}`}>
163+
{rule.title}
164+
</Link>
165+
</h2>
166+
{type == 'bookmark' ? (
167+
<div className="profile-rule-buttons flex flex-col justify-center">
168+
<button
169+
className="tooltip"
170+
onClick={() => onRemoveClick(rule.guid)}
171+
>
172+
<BookmarkIcon className="bookmark-icon-pressed" />
173+
<span className="tooltiptext">
174+
Remove Bookmark
175+
</span>
176+
</button>
177+
</div>
178+
) : (
179+
''
180+
)}
181+
</div>
182+
</section>
183+
<section
184+
className={`rule-content px-4 mb-4 pb-4
185+
${viewStyle === 'blurb' ? 'visible' : 'hidden'}`}
186+
>
187+
<div dangerouslySetInnerHTML={{ __html: rule.excerpt }} />
188+
<p className="pt-5 pb-0">
189+
<Link
190+
ref={linkRef}
191+
to={`/${rule.uri}`}
192+
title={`Read more about ${rule.title}`}
193+
>
194+
<FontAwesomeIcon icon={faArrowCircleRight} /> Read
195+
more
196+
</Link>
197+
</p>
198+
</section>
199+
<section
200+
className={`rule-content px-4 mb-4
201+
${viewStyle === 'all' ? 'visible' : 'hidden'}`}
202+
>
203+
<MD components={components} htmlAst={rule.htmlAst} />
204+
</section>
205+
</li>
206+
</div>
207+
);
208+
})}
209+
</ol>
210+
</div>
211+
)}
212+
</>
213+
);
214+
};
215+
216+
RuleList.propTypes = {
217+
rules: PropTypes.array.isRequired,
218+
viewStyle: PropTypes.string.isRequired,
219+
type: PropTypes.string.isRequired,
220+
onRemoveClick: PropTypes.func.isRequired,
221+
state: PropTypes.number,
222+
};
223+
224+
export default ProfileContent;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import BookmarkIcon from '-!svg-react-loader!../../images/bookmarkIcon.svg';
2+
import PropTypes from 'prop-types';
3+
import React from 'react';
4+
5+
const ProfileFilterMenu = ({
6+
selectedFilter,
7+
setSelectedFilter,
8+
bookmarkedRulesCount,
9+
}) => {
10+
return (
11+
<>
12+
<div className="filter-menu">
13+
<button
14+
className="menu-item"
15+
style={
16+
selectedFilter == Filter.Bookmarks
17+
? {
18+
gridColumn: 1,
19+
borderBottom: '0.25rem solid #cc4141',
20+
paddingBottom: '0.25rem',
21+
}
22+
: { gridColumn: 1 }
23+
}
24+
onClick={() => {
25+
setSelectedFilter(Filter.Bookmarks);
26+
}}
27+
>
28+
Bookmarks
29+
<BookmarkIcon
30+
className={
31+
selectedFilter != Filter.Bookmarks
32+
? 'filter-menu-bookmark-icon'
33+
: 'filter-menu-bookmark-icon-pressed'
34+
}
35+
/>
36+
<div className="mx-1">{bookmarkedRulesCount ?? 0}</div>
37+
</button>
38+
</div>
39+
</>
40+
);
41+
};
42+
43+
ProfileFilterMenu.propTypes = {
44+
selectedFilter: PropTypes.number.isRequired,
45+
setSelectedFilter: PropTypes.func.isRequired,
46+
bookmarkedRulesCount: PropTypes.number,
47+
};
48+
49+
export const Filter = {
50+
Bookmarks: 0,
51+
};
52+
export default ProfileFilterMenu;

0 commit comments

Comments
 (0)