Skip to content

Commit 4410f84

Browse files
authored
Merge pull request #2622 from lindapaiste/chore/legal-router
Use react-router for legal page tabs.
2 parents 3778d18 + 06e6341 commit 4410f84

File tree

8 files changed

+122
-186
lines changed

8 files changed

+122
-186
lines changed

client/common/RouterTab.jsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
import { NavLink } from 'react-router-dom';
4+
5+
/**
6+
* Wraps the react-router `NavLink` with dashboard-header__tab styling.
7+
*/
8+
const Tab = ({ children, to }) => (
9+
<li className="dashboard-header__tab">
10+
<NavLink
11+
className="dashboard-header__tab__title"
12+
activeClassName="dashboard-header__tab--selected"
13+
to={{ pathname: to, state: { skipSavingPath: true } }}
14+
>
15+
{children}
16+
</NavLink>
17+
</li>
18+
);
19+
20+
Tab.propTypes = {
21+
children: PropTypes.string.isRequired,
22+
to: PropTypes.string.isRequired
23+
};
24+
25+
export default Tab;

client/modules/Legal/pages/CodeOfConduct.jsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
import React, { useEffect, useState } from 'react';
2-
import Helmet from 'react-helmet';
3-
import axios from 'axios';
4-
import PolicyContainer from '../components/PolicyContainer';
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import Legal from './Legal';
54

65
function CodeOfConduct() {
7-
const [codeOfConduct, setCodeOfConduct] = useState('');
8-
useEffect(() => {
9-
axios.get('code-of-conduct.md').then((response) => {
10-
setCodeOfConduct(response.data);
11-
});
12-
}, []);
6+
const { t } = useTranslation();
7+
138
return (
14-
<>
15-
<Helmet>
16-
<title>p5.js Web Editor | Code of Conduct</title>
17-
</Helmet>
18-
<PolicyContainer policy={codeOfConduct} />
19-
</>
9+
<Legal policyFile="code-of-conduct.md" title={t('Legal.CodeOfConduct')} />
2010
);
2111
}
2212

client/modules/Legal/pages/Legal.jsx

Lines changed: 50 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,71 @@
1-
import React, { useState, useEffect } from 'react';
2-
import { useHistory, useLocation } from 'react-router-dom';
3-
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
4-
import styled from 'styled-components';
1+
import axios from 'axios';
2+
import PropTypes from 'prop-types';
3+
import React, { useEffect, useState } from 'react';
4+
import Helmet from 'react-helmet';
55
import { useTranslation } from 'react-i18next';
6-
import PrivacyPolicy from './PrivacyPolicy';
7-
import TermsOfUse from './TermsOfUse';
8-
import CodeOfConduct from './CodeOfConduct';
6+
import styled from 'styled-components';
7+
import RouterTab from '../../../common/RouterTab';
98
import RootPage from '../../../components/RootPage';
9+
import { remSize } from '../../../theme';
10+
import Loader from '../../App/components/loader';
1011
import Nav from '../../IDE/components/Header/Nav';
11-
import { remSize, prop } from '../../../theme';
12+
import PolicyContainer from '../components/PolicyContainer';
1213

13-
const StyledTabList = styled(TabList)`
14+
const StyledTabList = styled.nav`
1415
display: flex;
15-
max-width: ${remSize(680)};
16-
padding-top: ${remSize(10)};
16+
max-width: ${remSize(700)};
1717
margin: 0 auto;
18-
border-bottom: 1px solid ${prop('Modal.separator')};
19-
`;
20-
21-
const TabTitle = styled.p`
22-
padding: 0 ${remSize(5)} ${remSize(5)} ${remSize(5)};
23-
cursor: pointer;
24-
color: ${prop('inactiveTextColor')};
25-
&:hover,
26-
&:focus {
27-
color: ${prop('primaryTextColor')};
18+
padding: 0 ${remSize(10)};
19+
& ul {
20+
padding-top: ${remSize(10)};
21+
gap: ${remSize(19)};
2822
}
2923
`;
3024

31-
function Legal() {
32-
const [selectedIndex, setSelectedIndex] = useState(0);
25+
function Legal({ policyFile, title }) {
3326
const { t } = useTranslation();
34-
const location = useLocation();
35-
const history = useHistory();
27+
const [isLoading, setIsLoading] = useState(true);
28+
const [policy, setPolicy] = useState('');
3629

3730
useEffect(() => {
38-
if (location.pathname === '/privacy-policy') {
39-
setSelectedIndex(0);
40-
} else if (location.pathname === '/terms-of-use') {
41-
setSelectedIndex(1);
42-
} else {
43-
setSelectedIndex(2);
44-
}
45-
}, [location]);
46-
47-
function onSelect(index, lastIndex, event) {
48-
if (index === lastIndex) return;
49-
if (index === 0) {
50-
setSelectedIndex(0);
51-
history.push('/privacy-policy');
52-
} else if (index === 1) {
53-
setSelectedIndex(1);
54-
history.push('/terms-of-use');
55-
} else if (index === 2) {
56-
setSelectedIndex(2);
57-
history.push('/code-of-conduct');
58-
}
59-
}
31+
axios.get(policyFile).then((response) => {
32+
setPolicy(response.data);
33+
setIsLoading(false);
34+
});
35+
}, [policyFile]);
6036

6137
return (
6238
<RootPage>
39+
{/* TODO: translate site name */}
40+
<Helmet>
41+
<title>p5.js Web Editor | {title}</title>
42+
</Helmet>
6343
<Nav layout="dashboard" />
64-
<Tabs selectedIndex={selectedIndex} onSelect={onSelect}>
65-
<StyledTabList>
66-
<Tab>
67-
<TabTitle>{t('Legal.PrivacyPolicy')}</TabTitle>
68-
</Tab>
69-
<Tab>
70-
<TabTitle>{t('Legal.TermsOfUse')}</TabTitle>
71-
</Tab>
72-
<Tab>
73-
<TabTitle>{t('Legal.CodeOfConduct')}</TabTitle>
74-
</Tab>
75-
</StyledTabList>
76-
<TabPanel>
77-
<PrivacyPolicy />
78-
</TabPanel>
79-
<TabPanel>
80-
<TermsOfUse />
81-
</TabPanel>
82-
<TabPanel>
83-
<CodeOfConduct />
84-
</TabPanel>
85-
</Tabs>
44+
<StyledTabList className="dashboard-header__switcher">
45+
<ul className="dashboard-header__tabs">
46+
<RouterTab to="/privacy-policy">{t('Legal.PrivacyPolicy')}</RouterTab>
47+
<RouterTab to="/terms-of-use">{t('Legal.TermsOfUse')}</RouterTab>
48+
<RouterTab to="/code-of-conduct">
49+
{t('Legal.CodeOfConduct')}
50+
</RouterTab>
51+
</ul>
52+
</StyledTabList>
53+
<PolicyContainer policy={policy} />
54+
{isLoading && <Loader />}
8655
</RootPage>
8756
);
8857
}
8958

59+
Legal.propTypes = {
60+
/**
61+
* Used in the HTML <title> tag.
62+
* TODO: pass this to the Nav to use as the mobile title.
63+
*/
64+
title: PropTypes.string.isRequired,
65+
/**
66+
* Path of the markdown '.md' file, relative to the /public directory.
67+
*/
68+
policyFile: PropTypes.string.isRequired
69+
};
70+
9071
export default Legal;

client/modules/Legal/pages/PrivacyPolicy.jsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
import React, { useEffect, useState } from 'react';
2-
import Helmet from 'react-helmet';
3-
import axios from 'axios';
4-
import PolicyContainer from '../components/PolicyContainer';
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import Legal from './Legal';
54

65
function PrivacyPolicy() {
7-
const [privacyPolicy, setPrivacyPolicy] = useState('');
8-
useEffect(() => {
9-
axios.get('privacy-policy.md').then((response) => {
10-
setPrivacyPolicy(response.data);
11-
});
12-
}, []);
6+
const { t } = useTranslation();
7+
138
return (
14-
<>
15-
<Helmet>
16-
<title>p5.js Web Editor | Privacy Policy</title>
17-
</Helmet>
18-
<PolicyContainer policy={privacyPolicy} />
19-
</>
9+
<Legal policyFile="privacy-policy.md" title={t('Legal.PrivacyPolicy')} />
2010
);
2111
}
2212

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,11 @@
1-
import React, { useEffect, useState } from 'react';
2-
import Helmet from 'react-helmet';
3-
import axios from 'axios';
4-
import PolicyContainer from '../components/PolicyContainer';
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import Legal from './Legal';
54

65
function TermsOfUse() {
7-
const [termsOfUse, setTermsOfUse] = useState('');
8-
useEffect(() => {
9-
axios.get('terms-of-use.md').then((response) => {
10-
setTermsOfUse(response.data);
11-
});
12-
}, []);
13-
return (
14-
<>
15-
<Helmet>
16-
<title>p5.js Web Editor | Terms of Use</title>
17-
</Helmet>
18-
<PolicyContainer policy={termsOfUse} />
19-
</>
20-
);
6+
const { t } = useTranslation();
7+
8+
return <Legal policyFile="terms-of-use.md" title={t('Legal.TermsOfUse')} />;
219
}
2210

2311
export default TermsOfUse;

client/modules/User/components/DashboardTabSwitcher.jsx

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { useTranslation } from 'react-i18next';
44
import MediaQuery from 'react-responsive';
55
import { useDispatch } from 'react-redux';
66
import styled from 'styled-components';
7-
import { Link } from 'react-router-dom';
87
import { FilterIcon } from '../../../common/icons';
98
import IconButton from '../../../common/IconButton';
9+
import RouterTab from '../../../common/RouterTab';
1010
import { Options } from '../../IDE/components/Header/MobileNav';
1111
import { toggleDirectionForField } from '../../IDE/actions/sorting';
1212

@@ -16,28 +16,6 @@ export const TabKey = {
1616
sketches: 'sketches'
1717
};
1818

19-
const Tab = ({ children, isSelected, to }) => {
20-
const selectedClassName = 'dashboard-header__tab--selected';
21-
22-
const location = { pathname: to, state: { skipSavingPath: true } };
23-
const content = isSelected ? (
24-
<span>{children}</span>
25-
) : (
26-
<Link to={location}>{children}</Link>
27-
);
28-
return (
29-
<li className={`dashboard-header__tab ${isSelected && selectedClassName}`}>
30-
<h4 className="dashboard-header__tab__title">{content}</h4>
31-
</li>
32-
);
33-
};
34-
35-
Tab.propTypes = {
36-
children: PropTypes.string.isRequired,
37-
isSelected: PropTypes.bool.isRequired,
38-
to: PropTypes.string.isRequired
39-
};
40-
4119
// It is good for right now, because we need to separate the nav dropdown logic from the navBar before we can use it here
4220
const FilterOptions = styled(Options)`
4321
> div > button:focus + ul,
@@ -52,29 +30,20 @@ const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => {
5230
const dispatch = useDispatch();
5331

5432
return (
55-
<ul className="dashboard-header__switcher">
56-
<div className="dashboard-header__tabs">
57-
<Tab
58-
to={`/${username}/sketches`}
59-
isSelected={currentTab === TabKey.sketches}
60-
>
33+
<div className="dashboard-header__switcher">
34+
<ul className="dashboard-header__tabs">
35+
<RouterTab to={`/${username}/sketches`}>
6136
{t('DashboardTabSwitcher.Sketches')}
62-
</Tab>
63-
<Tab
64-
to={`/${username}/collections`}
65-
isSelected={currentTab === TabKey.collections}
66-
>
37+
</RouterTab>
38+
<RouterTab to={`/${username}/collections`}>
6739
{t('DashboardTabSwitcher.Collections')}
68-
</Tab>
40+
</RouterTab>
6941
{isOwner && (
70-
<Tab
71-
to={`/${username}/assets`}
72-
isSelected={currentTab === TabKey.assets}
73-
>
42+
<RouterTab to={`/${username}/assets`}>
7443
{t('DashboardTabSwitcher.Assets')}
75-
</Tab>
44+
</RouterTab>
7645
)}
77-
</div>
46+
</ul>
7847
<MediaQuery maxWidth={770}>
7948
{(mobile) =>
8049
mobile &&
@@ -125,7 +94,7 @@ const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => {
12594
)
12695
}
12796
</MediaQuery>
128-
</ul>
97+
</div>
12998
);
13099
};
131100

client/routes.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { Route as RouterRoute, Switch } from 'react-router-dom';
66
import App from './modules/App/App';
77
import IDEView from './modules/IDE/pages/IDEView';
88
import FullView from './modules/IDE/pages/FullView';
9+
import CodeOfConduct from './modules/Legal/pages/CodeOfConduct';
10+
import PrivacyPolicy from './modules/Legal/pages/PrivacyPolicy';
11+
import TermsOfUse from './modules/Legal/pages/TermsOfUse';
912
import LoginView from './modules/User/pages/LoginView';
1013
import SignupView from './modules/User/pages/SignupView';
1114
import ResetPasswordView from './modules/User/pages/ResetPasswordView';
@@ -15,7 +18,6 @@ import AccountView from './modules/User/pages/AccountView';
1518
import CollectionView from './modules/User/pages/CollectionView';
1619
import DashboardView from './modules/User/pages/DashboardView';
1720
import createRedirectWithUsername from './components/createRedirectWithUsername';
18-
import Legal from './modules/Legal/pages/Legal';
1921
import { getUser } from './modules/User/actions';
2022
import {
2123
userIsAuthenticated,
@@ -91,9 +93,9 @@ const routes = (
9193
<Route path="/account" component={userIsAuthenticated(AccountView)} />
9294
<Route path="/about" component={IDEView} />
9395

94-
<Route path="/privacy-policy" component={Legal} />
95-
<Route path="/terms-of-use" component={Legal} />
96-
<Route path="/code-of-conduct" component={Legal} />
96+
<Route path="/privacy-policy" component={PrivacyPolicy} />
97+
<Route path="/terms-of-use" component={TermsOfUse} />
98+
<Route path="/code-of-conduct" component={CodeOfConduct} />
9799
</Switch>
98100
);
99101

0 commit comments

Comments
 (0)