Skip to content

Use react-router for legal page tabs. #2622

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

Merged
merged 4 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions client/common/RouterTab.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import PropTypes from 'prop-types';
import React from 'react';
import { NavLink } from 'react-router-dom';

/**
* Wraps the react-router `NavLink` with dashboard-header__tab styling.
*/
const Tab = ({ children, to }) => (
<li className="dashboard-header__tab">
<NavLink
className="dashboard-header__tab__title"
activeClassName="dashboard-header__tab--selected"
to={{ pathname: to, state: { skipSavingPath: true } }}
>
{children}
</NavLink>
</li>
);

Tab.propTypes = {
children: PropTypes.string.isRequired,
to: PropTypes.string.isRequired
};

export default Tab;
22 changes: 6 additions & 16 deletions client/modules/Legal/pages/CodeOfConduct.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
import React, { useEffect, useState } from 'react';
import Helmet from 'react-helmet';
import axios from 'axios';
import PolicyContainer from '../components/PolicyContainer';
import React from 'react';
import { useTranslation } from 'react-i18next';
import Legal from './Legal';

function CodeOfConduct() {
const [codeOfConduct, setCodeOfConduct] = useState('');
useEffect(() => {
axios.get('code-of-conduct.md').then((response) => {
setCodeOfConduct(response.data);
});
}, []);
const { t } = useTranslation();

return (
<>
<Helmet>
<title>p5.js Web Editor | Code of Conduct</title>
</Helmet>
<PolicyContainer policy={codeOfConduct} />
</>
<Legal policyFile="code-of-conduct.md" title={t('Legal.CodeOfConduct')} />
);
}

Expand Down
119 changes: 50 additions & 69 deletions client/modules/Legal/pages/Legal.jsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,71 @@
import React, { useState, useEffect } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import styled from 'styled-components';
import axios from 'axios';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import Helmet from 'react-helmet';
import { useTranslation } from 'react-i18next';
import PrivacyPolicy from './PrivacyPolicy';
import TermsOfUse from './TermsOfUse';
import CodeOfConduct from './CodeOfConduct';
import styled from 'styled-components';
import RouterTab from '../../../common/RouterTab';
import RootPage from '../../../components/RootPage';
import { remSize } from '../../../theme';
import Loader from '../../App/components/loader';
import Nav from '../../IDE/components/Header/Nav';
import { remSize, prop } from '../../../theme';
import PolicyContainer from '../components/PolicyContainer';

const StyledTabList = styled(TabList)`
const StyledTabList = styled.nav`
display: flex;
max-width: ${remSize(680)};
padding-top: ${remSize(10)};
max-width: ${remSize(700)};
margin: 0 auto;
border-bottom: 1px solid ${prop('Modal.separator')};
`;

const TabTitle = styled.p`
padding: 0 ${remSize(5)} ${remSize(5)} ${remSize(5)};
cursor: pointer;
color: ${prop('inactiveTextColor')};
&:hover,
&:focus {
color: ${prop('primaryTextColor')};
padding: 0 ${remSize(10)};
& ul {
padding-top: ${remSize(10)};
gap: ${remSize(19)};
}
`;

function Legal() {
const [selectedIndex, setSelectedIndex] = useState(0);
function Legal({ policyFile, title }) {
const { t } = useTranslation();
const location = useLocation();
const history = useHistory();
const [isLoading, setIsLoading] = useState(true);
const [policy, setPolicy] = useState('');

useEffect(() => {
if (location.pathname === '/privacy-policy') {
setSelectedIndex(0);
} else if (location.pathname === '/terms-of-use') {
setSelectedIndex(1);
} else {
setSelectedIndex(2);
}
}, [location]);

function onSelect(index, lastIndex, event) {
if (index === lastIndex) return;
if (index === 0) {
setSelectedIndex(0);
history.push('/privacy-policy');
} else if (index === 1) {
setSelectedIndex(1);
history.push('/terms-of-use');
} else if (index === 2) {
setSelectedIndex(2);
history.push('/code-of-conduct');
}
}
axios.get(policyFile).then((response) => {
setPolicy(response.data);
setIsLoading(false);
});
}, [policyFile]);

return (
<RootPage>
{/* TODO: translate site name */}
<Helmet>
<title>p5.js Web Editor | {title}</title>
</Helmet>
<Nav layout="dashboard" />
<Tabs selectedIndex={selectedIndex} onSelect={onSelect}>
<StyledTabList>
<Tab>
<TabTitle>{t('Legal.PrivacyPolicy')}</TabTitle>
</Tab>
<Tab>
<TabTitle>{t('Legal.TermsOfUse')}</TabTitle>
</Tab>
<Tab>
<TabTitle>{t('Legal.CodeOfConduct')}</TabTitle>
</Tab>
</StyledTabList>
<TabPanel>
<PrivacyPolicy />
</TabPanel>
<TabPanel>
<TermsOfUse />
</TabPanel>
<TabPanel>
<CodeOfConduct />
</TabPanel>
</Tabs>
<StyledTabList className="dashboard-header__switcher">
<ul className="dashboard-header__tabs">
<RouterTab to="/privacy-policy">{t('Legal.PrivacyPolicy')}</RouterTab>
<RouterTab to="/terms-of-use">{t('Legal.TermsOfUse')}</RouterTab>
<RouterTab to="/code-of-conduct">
{t('Legal.CodeOfConduct')}
</RouterTab>
</ul>
</StyledTabList>
<PolicyContainer policy={policy} />
{isLoading && <Loader />}
</RootPage>
);
}

Legal.propTypes = {
/**
* Used in the HTML <title> tag.
* TODO: pass this to the Nav to use as the mobile title.
*/
title: PropTypes.string.isRequired,
/**
* Path of the markdown '.md' file, relative to the /public directory.
*/
policyFile: PropTypes.string.isRequired
};

export default Legal;
22 changes: 6 additions & 16 deletions client/modules/Legal/pages/PrivacyPolicy.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
import React, { useEffect, useState } from 'react';
import Helmet from 'react-helmet';
import axios from 'axios';
import PolicyContainer from '../components/PolicyContainer';
import React from 'react';
import { useTranslation } from 'react-i18next';
import Legal from './Legal';

function PrivacyPolicy() {
const [privacyPolicy, setPrivacyPolicy] = useState('');
useEffect(() => {
axios.get('privacy-policy.md').then((response) => {
setPrivacyPolicy(response.data);
});
}, []);
const { t } = useTranslation();

return (
<>
<Helmet>
<title>p5.js Web Editor | Privacy Policy</title>
</Helmet>
<PolicyContainer policy={privacyPolicy} />
</>
<Legal policyFile="privacy-policy.md" title={t('Legal.PrivacyPolicy')} />
);
}

Expand Down
24 changes: 6 additions & 18 deletions client/modules/Legal/pages/TermsOfUse.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
import React, { useEffect, useState } from 'react';
import Helmet from 'react-helmet';
import axios from 'axios';
import PolicyContainer from '../components/PolicyContainer';
import React from 'react';
import { useTranslation } from 'react-i18next';
import Legal from './Legal';

function TermsOfUse() {
const [termsOfUse, setTermsOfUse] = useState('');
useEffect(() => {
axios.get('terms-of-use.md').then((response) => {
setTermsOfUse(response.data);
});
}, []);
return (
<>
<Helmet>
<title>p5.js Web Editor | Terms of Use</title>
</Helmet>
<PolicyContainer policy={termsOfUse} />
</>
);
const { t } = useTranslation();

return <Legal policyFile="terms-of-use.md" title={t('Legal.TermsOfUse')} />;
}

export default TermsOfUse;
53 changes: 11 additions & 42 deletions client/modules/User/components/DashboardTabSwitcher.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { useTranslation } from 'react-i18next';
import MediaQuery from 'react-responsive';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { FilterIcon } from '../../../common/icons';
import IconButton from '../../../common/IconButton';
import RouterTab from '../../../common/RouterTab';
import { Options } from '../../IDE/components/Header/MobileNav';
import { toggleDirectionForField } from '../../IDE/actions/sorting';

Expand All @@ -16,28 +16,6 @@ export const TabKey = {
sketches: 'sketches'
};

const Tab = ({ children, isSelected, to }) => {
const selectedClassName = 'dashboard-header__tab--selected';

const location = { pathname: to, state: { skipSavingPath: true } };
const content = isSelected ? (
<span>{children}</span>
) : (
<Link to={location}>{children}</Link>
);
return (
<li className={`dashboard-header__tab ${isSelected && selectedClassName}`}>
<h4 className="dashboard-header__tab__title">{content}</h4>
</li>
);
};

Tab.propTypes = {
children: PropTypes.string.isRequired,
isSelected: PropTypes.bool.isRequired,
to: PropTypes.string.isRequired
};

// It is good for right now, because we need to separate the nav dropdown logic from the navBar before we can use it here
const FilterOptions = styled(Options)`
> div > button:focus + ul,
Expand All @@ -52,29 +30,20 @@ const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => {
const dispatch = useDispatch();

return (
<ul className="dashboard-header__switcher">
<div className="dashboard-header__tabs">
<Tab
to={`/${username}/sketches`}
isSelected={currentTab === TabKey.sketches}
>
<div className="dashboard-header__switcher">
<ul className="dashboard-header__tabs">
<RouterTab to={`/${username}/sketches`}>
{t('DashboardTabSwitcher.Sketches')}
</Tab>
<Tab
to={`/${username}/collections`}
isSelected={currentTab === TabKey.collections}
>
</RouterTab>
<RouterTab to={`/${username}/collections`}>
{t('DashboardTabSwitcher.Collections')}
</Tab>
</RouterTab>
{isOwner && (
<Tab
to={`/${username}/assets`}
isSelected={currentTab === TabKey.assets}
>
<RouterTab to={`/${username}/assets`}>
{t('DashboardTabSwitcher.Assets')}
</Tab>
</RouterTab>
)}
</div>
</ul>
<MediaQuery maxWidth={770}>
{(mobile) =>
mobile &&
Expand Down Expand Up @@ -125,7 +94,7 @@ const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => {
)
}
</MediaQuery>
</ul>
</div>
);
};

Expand Down
10 changes: 6 additions & 4 deletions client/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { Route as RouterRoute, Switch } from 'react-router-dom';
import App from './modules/App/App';
import IDEView from './modules/IDE/pages/IDEView';
import FullView from './modules/IDE/pages/FullView';
import CodeOfConduct from './modules/Legal/pages/CodeOfConduct';
import PrivacyPolicy from './modules/Legal/pages/PrivacyPolicy';
import TermsOfUse from './modules/Legal/pages/TermsOfUse';
import LoginView from './modules/User/pages/LoginView';
import SignupView from './modules/User/pages/SignupView';
import ResetPasswordView from './modules/User/pages/ResetPasswordView';
Expand All @@ -15,7 +18,6 @@ import AccountView from './modules/User/pages/AccountView';
import CollectionView from './modules/User/pages/CollectionView';
import DashboardView from './modules/User/pages/DashboardView';
import createRedirectWithUsername from './components/createRedirectWithUsername';
import Legal from './modules/Legal/pages/Legal';
import { getUser } from './modules/User/actions';
import {
userIsAuthenticated,
Expand Down Expand Up @@ -91,9 +93,9 @@ const routes = (
<Route path="/account" component={userIsAuthenticated(AccountView)} />
<Route path="/about" component={IDEView} />

<Route path="/privacy-policy" component={Legal} />
<Route path="/terms-of-use" component={Legal} />
<Route path="/code-of-conduct" component={Legal} />
<Route path="/privacy-policy" component={PrivacyPolicy} />
<Route path="/terms-of-use" component={TermsOfUse} />
<Route path="/code-of-conduct" component={CodeOfConduct} />
</Switch>
);

Expand Down
Loading