about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2022-10-08 06:01:11 +0200
committerGitHub <noreply@github.com>2022-10-08 06:01:11 +0200
commita2ba01132603174c43c5788a95f9ee127b684c0a (patch)
tree3d5ab173a8a768f76cc14dc739ca64bedcdddf43 /app/javascript
parent7fb738c8372a700e1b42534cb202005b8c73b946 (diff)
Change privacy policy to be rendered in web UI, add REST API (#19310)
Source string no longer localized, Markdown instead of raw HTML
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/mastodon/features/privacy_policy/index.js60
-rw-r--r--app/javascript/mastodon/features/ui/components/link_footer.js2
-rw-r--r--app/javascript/mastodon/features/ui/index.js2
-rw-r--r--app/javascript/mastodon/features/ui/util/async-components.js4
-rw-r--r--app/javascript/styles/mastodon/components.scss88
5 files changed, 154 insertions, 2 deletions
diff --git a/app/javascript/mastodon/features/privacy_policy/index.js b/app/javascript/mastodon/features/privacy_policy/index.js
new file mode 100644
index 000000000..b7ca03d2c
--- /dev/null
+++ b/app/javascript/mastodon/features/privacy_policy/index.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { title } from 'mastodon/initial_state';
+import { Helmet } from 'react-helmet';
+import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl';
+import Column from 'mastodon/components/column';
+import api from 'mastodon/api';
+import Skeleton from 'mastodon/components/skeleton';
+
+const messages = defineMessages({
+  title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' },
+});
+
+export default @injectIntl
+class PrivacyPolicy extends React.PureComponent {
+
+  static propTypes = {
+    intl: PropTypes.object,
+  };
+
+  state = {
+    content: null,
+    lastUpdated: null,
+    isLoading: true,
+  };
+
+  componentDidMount () {
+    api().get('/api/v1/instance/privacy_policy').then(({ data }) => {
+      this.setState({ content: data.content, lastUpdated: data.updated_at, isLoading: false });
+    }).catch(() => {
+      this.setState({ isLoading: false });
+    });
+  }
+
+  render () {
+    const { intl } = this.props;
+    const { isLoading, content, lastUpdated } = this.state;
+
+    return (
+      <Column>
+        <div className='scrollable privacy-policy'>
+          <div className='column-title'>
+            <h3><FormattedMessage id='privacy_policy.title' defaultMessage='Privacy Policy' /></h3>
+            <p><FormattedMessage id='privacy_policy.last_updated' defaultMessage='Last updated {date}' values={{ date: isLoading ? <Skeleton width='10ch' /> : <FormattedDate value={lastUpdated} year='numeric' month='short' day='2-digit' /> }} /></p>
+          </div>
+
+          <div
+            className='privacy-policy__body'
+            dangerouslySetInnerHTML={{ __html: content }}
+          />
+        </div>
+
+        <Helmet>
+          <title>{intl.formatMessage(messages.title)} - {title}</title>
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js
index 2b092a182..c4ce2a985 100644
--- a/app/javascript/mastodon/features/ui/components/link_footer.js
+++ b/app/javascript/mastodon/features/ui/components/link_footer.js
@@ -54,7 +54,7 @@ class LinkFooter extends React.PureComponent {
     items.push(<a key='about' href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About' /></a>);
     items.push(<a key='mastodon' href='https://joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.what_is_mastodon' defaultMessage='About Mastodon' /></a>);
     items.push(<a key='docs' href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a>);
-    items.push(<a key='privacy-policy' href='/privacy-policy' target='_blank'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></a>);
+    items.push(<Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></Link>);
     items.push(<Link key='hotkeys' to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link>);
 
     if (profileDirectory) {
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index bc6ff1866..bd1930368 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -52,6 +52,7 @@ import {
   Explore,
   FollowRecommendations,
   About,
+  PrivacyPolicy,
 } from './util/async-components';
 import { me, title } from '../../initial_state';
 import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
@@ -173,6 +174,7 @@ class SwitchingColumnsArea extends React.PureComponent {
           <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
           <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
           <WrappedRoute path='/about' component={About} content={children} />
+          <WrappedRoute path='/privacy-policy' component={PrivacyPolicy} content={children} />
 
           <WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} />
           <WrappedRoute path={['/public', '/timelines/public']} exact component={PublicTimeline} content={children} />
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
index 5907e0772..c79dc014c 100644
--- a/app/javascript/mastodon/features/ui/util/async-components.js
+++ b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -169,3 +169,7 @@ export function FilterModal () {
 export function About () {
   return import(/*webpackChunkName: "features/about" */'../../about');
 }
+
+export function PrivacyPolicy () {
+  return import(/*webpackChunkName: "features/privacy_policy" */'../../privacy_policy');
+}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index a3dc4c637..cc8455ce3 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -2283,7 +2283,8 @@ $ui-header-height: 55px;
 
   > .scrollable {
     background: $ui-base-color;
-    border-radius: 0 0 4px 4px;
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
   }
 }
 
@@ -8226,3 +8227,88 @@ noscript {
     }
   }
 }
+
+.privacy-policy {
+  background: $ui-base-color;
+  padding: 20px;
+
+  @media screen and (min-width: $no-gap-breakpoint) {
+    border-radius: 4px;
+  }
+
+  &__body {
+    margin-top: 20px;
+    color: $secondary-text-color;
+    font-size: 15px;
+    line-height: 22px;
+
+    h1,
+    p,
+    ul,
+    ol {
+      margin-bottom: 20px;
+    }
+
+    ul {
+      list-style: disc;
+    }
+
+    ol {
+      list-style: decimal;
+    }
+
+    ul,
+    ol {
+      padding-left: 1em;
+    }
+
+    li {
+      margin-bottom: 10px;
+
+      &::marker {
+        color: $darker-text-color;
+      }
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+
+    h1 {
+      color: $primary-text-color;
+      font-size: 19px;
+      line-height: 24px;
+      font-weight: 700;
+      margin-top: 30px;
+
+      &:first-child {
+        margin-top: 0;
+      }
+    }
+
+    strong {
+      font-weight: 700;
+      color: $primary-text-color;
+    }
+
+    em {
+      font-style: italic;
+    }
+
+    a {
+      color: $highlight-text-color;
+      text-decoration: underline;
+
+      &:focus,
+      &:hover,
+      &:active {
+        text-decoration: none;
+      }
+    }
+
+    hr {
+      border: 1px solid lighten($ui-base-color, 4%);
+      margin: 30px 0;
+    }
+  }
+}