소스 검색

Add ban URLs from for admin

poeti8 7 년 전
부모
커밋
5d2409c556
5개의 변경된 파일213개의 추가작업 그리고 66개의 파일을 삭제
  1. 3 61
      client/actions/index.js
  2. 14 0
      client/actions/settings.js
  3. 11 3
      client/components/Checkbox/Checkbox.js
  4. 87 2
      client/components/Settings/Settings.js
  5. 98 0
      client/components/Settings/SettingsBan.js

+ 3 - 61
client/actions/index.js

@@ -1,61 +1,3 @@
-import {
-  createShortUrl,
-  getUrlsList,
-  deleteShortUrl,
-  showShortenerLoading,
-  setShortenerFormError,
-} from './url';
-
-import {
-  setDomain,
-  setApiKey,
-  showDomainInput,
-  getUserSettings,
-  setCustomDomain,
-  deleteCustomDomain,
-  generateApiKey,
-} from './settings';
-
-import {
-  showPageLoading,
-  hidePageLoading,
-  authUser,
-  unauthUser,
-  sentVerification,
-  showAuthError,
-  showLoginLoading,
-  showSignupLoading,
-  authRenew,
-  signupUser,
-  loginUser,
-  logoutUser,
-  renewAuthUser,
-} from './auth';
-
-export {
-  createShortUrl,
-  getUrlsList,
-  deleteShortUrl,
-  showShortenerLoading,
-  setShortenerFormError,
-  setDomain,
-  setApiKey,
-  showDomainInput,
-  getUserSettings,
-  setCustomDomain,
-  deleteCustomDomain,
-  generateApiKey,
-  showPageLoading,
-  hidePageLoading,
-  authUser,
-  unauthUser,
-  sentVerification,
-  showAuthError,
-  showLoginLoading,
-  showSignupLoading,
-  authRenew,
-  signupUser,
-  loginUser,
-  logoutUser,
-  renewAuthUser,
-};
+export * from './url';
+export * from './settings';
+export * from './auth';

+ 14 - 0
client/actions/settings.js

@@ -8,12 +8,14 @@ import {
   SET_DOMAIN,
   SET_APIKEY,
   SHOW_DOMAIN_INPUT,
+  BAN_URL,
 } from './actionTypes';
 
 const deleteDomain = () => ({ type: DELETE_DOMAIN });
 const setDomainError = payload => ({ type: DOMAIN_ERROR, payload });
 const showDomainLoading = () => ({ type: DOMAIN_LOADING });
 const showApiLoading = () => ({ type: API_LOADING });
+const urlBanned = () => ({ type: BAN_URL });
 
 export const setDomain = payload => ({ type: SET_DOMAIN, payload });
 export const setApiKey = payload => ({ type: SET_APIKEY, payload });
@@ -65,3 +67,15 @@ export const generateApiKey = () => async dispatch => {
     //
   }
 };
+
+export const banUrl = params => async dispatch => {
+  try {
+    const { data } = await axios.post('/api/url/admin/ban', params, {
+      headers: { Authorization: cookie.get('token') },
+    });
+    dispatch(urlBanned());
+    return data.message;
+  } catch ({ response }) {
+    return Promise.reject(response.data && response.data.error);
+  }
+};

+ 11 - 3
client/components/Checkbox/Checkbox.js

@@ -6,7 +6,13 @@ const Wrapper = styled.div`
   display: flex;
   justify-content: flex-start;
   align-items: center;
-  margin: 24px 16px 24px;
+  margin: 16px 0 16px;
+
+  ${({ withMargin }) =>
+    withMargin &&
+    css`
+      margin: 24px 16px 24px;
+    `};
 
   :first-child {
     margin-left: 0;
@@ -77,8 +83,8 @@ const Box = styled.span`
     `};
 `;
 
-const Checkbox = ({ checked, label, id, onClick }) => (
-  <Wrapper>
+const Checkbox = ({ checked, label, id, withMargin, onClick }) => (
+  <Wrapper withMargin={withMargin}>
     <Box checked={checked} id={id} onClick={onClick}>
       {label}
     </Box>
@@ -87,12 +93,14 @@ const Checkbox = ({ checked, label, id, onClick }) => (
 
 Checkbox.propTypes = {
   checked: PropTypes.bool,
+  withMargin: PropTypes.bool,
   label: PropTypes.string.isRequired,
   id: PropTypes.string.isRequired,
   onClick: PropTypes.func,
 };
 
 Checkbox.defaultProps = {
+  withMargin: true,
   checked: false,
   onClick: f => f,
 };

+ 87 - 2
client/components/Settings/Settings.js

@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
 import PropTypes from 'prop-types';
 import Router from 'next/router';
 import { bindActionCreators } from 'redux';
@@ -9,6 +9,7 @@ import axios from 'axios';
 import SettingsWelcome from './SettingsWelcome';
 import SettingsDomain from './SettingsDomain';
 import SettingsPassword from './SettingsPassword';
+import SettingsBan from './SettingsBan';
 import SettingsApi from './SettingsApi';
 import Modal from '../Modal';
 import { fadeIn } from '../../helpers/animations';
@@ -18,6 +19,7 @@ import {
   getUserSettings,
   setCustomDomain,
   showDomainInput,
+  banUrl,
 } from '../../actions';
 
 const Wrapper = styled.div`
@@ -76,7 +78,17 @@ class Settings extends Component {
       showModal: false,
       passwordMessage: '',
       passwordError: '',
+      ban: {
+        domain: false,
+        error: '',
+        host: false,
+        loading: false,
+        message: '',
+        user: false,
+      },
     };
+    this.onSubmitBan = this.onSubmitBan.bind(this);
+    this.onChangeBanCheckboxes = this.onChangeBanCheckboxes.bind(this);
     this.handleCustomDomain = this.handleCustomDomain.bind(this);
     this.deleteDomain = this.deleteDomain.bind(this);
     this.showModal = this.showModal.bind(this);
@@ -89,6 +101,64 @@ class Settings extends Component {
     this.props.getUserSettings();
   }
 
+  async onSubmitBan(e) {
+    e.preventDefault();
+    const { ban: { domain, host, user } } = this.state;
+    this.setState(state => ({
+      ban: {
+        ...state.ban,
+        loading: true,
+      },
+    }));
+    const id = e.currentTarget.elements.id.value;
+    let message;
+    let error;
+    try {
+      message = await this.props.banUrl({
+        id,
+        domain,
+        host,
+        user,
+      });
+    } catch (err) {
+      error = err;
+    }
+    this.setState(
+      state => ({
+        ban: {
+          ...state.ban,
+          loading: false,
+          message,
+          error,
+        },
+      }),
+      () => {
+        setTimeout(() => {
+          this.setState(state => ({
+            ban: {
+              ...state.ban,
+              loading: false,
+              message: '',
+              error: '',
+            },
+          }));
+        }, 2000);
+      }
+    );
+  }
+
+  onChangeBanCheckboxes(type) {
+    return e => {
+      const { checked } = e.target;
+      this.setState(state => ({
+        ban: {
+          ...state.ban,
+          [type]: !checked,
+        },
+      }));
+    };
+  }
+
   handleCustomDomain(e) {
     e.preventDefault();
     if (this.props.domainLoading) return null;
@@ -148,9 +218,21 @@ class Settings extends Component {
   }
 
   render() {
+    const { auth: { user, admin } } = this.props;
     return (
       <Wrapper>
-        <SettingsWelcome user={this.props.auth.user} />
+        <SettingsWelcome user={user} />
+        <hr />
+        {admin && (
+          <Fragment>
+            <SettingsBan
+              {...this.state.ban}
+              onSubmitBan={this.onSubmitBan}
+              onChangeBanCheckboxes={this.onChangeBanCheckboxes}
+            />
+            <hr />
+          </Fragment>
+        )}
         <SettingsDomain
           handleCustomDomain={this.handleCustomDomain}
           loading={this.props.domainLoading}
@@ -180,12 +262,14 @@ class Settings extends Component {
 
 Settings.propTypes = {
   auth: PropTypes.shape({
+    admin: PropTypes.bool.isRequired,
     isAuthenticated: PropTypes.bool.isRequired,
     user: PropTypes.string.isRequired,
   }).isRequired,
   apiLoading: PropTypes.bool,
   deleteCustomDomain: PropTypes.func.isRequired,
   domainLoading: PropTypes.bool,
+  banUrl: PropTypes.func.isRequired,
   setCustomDomain: PropTypes.func.isRequired,
   generateApiKey: PropTypes.func.isRequired,
   getUserSettings: PropTypes.func.isRequired,
@@ -214,6 +298,7 @@ const mapStateToProps = ({
 });
 
 const mapDispatchToProps = dispatch => ({
+  banUrl: bindActionCreators(banUrl, dispatch),
   deleteCustomDomain: bindActionCreators(deleteCustomDomain, dispatch),
   setCustomDomain: bindActionCreators(setCustomDomain, dispatch),
   generateApiKey: bindActionCreators(generateApiKey, dispatch),

+ 98 - 0
client/components/Settings/SettingsBan.js

@@ -0,0 +1,98 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled from 'styled-components';
+import TextInput from '../TextInput';
+import Button from '../Button';
+import Checkbox from '../Checkbox';
+
+const Form = styled.form`
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  margin: 32px 0;
+
+  input {
+    flex: 0 0 auto;
+    margin-right: 16px;
+  }
+`;
+
+const InputWrapper = styled.div`
+  display: flex;
+`;
+
+const Message = styled.div`
+  position: absolute;
+  left: 0;
+  bottom: -32px;
+  color: green;
+`;
+
+const Error = styled.div`
+  position: absolute;
+  left: 0;
+  bottom: -32px;
+  color: red;
+`;
+
+const SettingsBan = props => (
+  <div>
+    <h3>Ban link</h3>
+    <Form onSubmit={props.onSubmitBan}>
+      <InputWrapper>
+        <Message>{props.message}</Message>
+        <TextInput
+          id="id"
+          name="id"
+          type="text"
+          placeholder="Link ID (e.g. K7b2A)"
+          height={44}
+          small
+        />
+        <Button type="submit" icon={props.loading ? 'loader' : 'lock'} disabled={props.loading}>
+          {props.loading ? 'Baning...' : 'Ban'}
+        </Button>
+      </InputWrapper>
+      <div>
+        <Checkbox
+          id="user"
+          name="user"
+          label="Ban user (and all of their links)"
+          withMargin={false}
+          checked={props.user}
+          onClick={props.onChangeBanCheckboxes('user')}
+        />
+        <Checkbox
+          id="domain"
+          name="domain"
+          label="Ban domain"
+          withMargin={false}
+          checked={props.domain}
+          onClick={props.onChangeBanCheckboxes('domain')}
+        />
+        <Checkbox
+          id="host"
+          name="host"
+          label="Ban Host/IP"
+          withMargin={false}
+          checked={props.host}
+          onClick={props.onChangeBanCheckboxes('host')}
+        />
+      </div>
+      <Error>{props.error}</Error>
+    </Form>
+  </div>
+);
+
+SettingsBan.propTypes = {
+  domain: PropTypes.bool.isRequired,
+  error: PropTypes.string.isRequired,
+  host: PropTypes.bool.isRequired,
+  loading: PropTypes.bool.isRequired,
+  message: PropTypes.string.isRequired,
+  onChangeBanCheckboxes: PropTypes.func.isRequired,
+  onSubmitBan: PropTypes.func.isRequired,
+  user: PropTypes.bool.isRequired,
+};
+
+export default SettingsBan;