mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Added checkbox to control the findIfExists shlink flag
This commit is contained in:
parent
312c6cd550
commit
f4dbd03c7e
6 changed files with 125 additions and 22 deletions
|
@ -51,7 +51,7 @@
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.2.2",
|
||||||
"react-swipeable": "^4.3.0",
|
"react-swipeable": "^4.3.0",
|
||||||
"react-tagsinput": "^3.19.0",
|
"react-tagsinput": "^3.19.0",
|
||||||
"reactstrap": "^6.0.1",
|
"reactstrap": "^7.1.0",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
"redux-actions": "^2.6.5",
|
"redux-actions": "^2.6.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
|
|
|
@ -5,7 +5,9 @@ import React from 'react';
|
||||||
import { Collapse } from 'reactstrap';
|
import { Collapse } from 'reactstrap';
|
||||||
import * as PropTypes from 'prop-types';
|
import * as PropTypes from 'prop-types';
|
||||||
import DateInput from '../utils/DateInput';
|
import DateInput from '../utils/DateInput';
|
||||||
|
import Checkbox from '../utils/Checkbox';
|
||||||
import { createShortUrlResultType } from './reducers/shortUrlCreation';
|
import { createShortUrlResultType } from './reducers/shortUrlCreation';
|
||||||
|
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
|
||||||
|
|
||||||
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
||||||
const formatDate = (date) => isNil(date) ? date : date.format();
|
const formatDate = (date) => isNil(date) ? date : date.format();
|
||||||
|
@ -24,6 +26,7 @@ const CreateShortUrl = (TagsSelector, CreateShortUrlResult) => class CreateShort
|
||||||
validSince: undefined,
|
validSince: undefined,
|
||||||
validUntil: undefined,
|
validUntil: undefined,
|
||||||
maxVisits: undefined,
|
maxVisits: undefined,
|
||||||
|
findIfExists: false,
|
||||||
moreOptionsVisible: false,
|
moreOptionsVisible: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,22 +96,30 @@ const CreateShortUrl = (TagsSelector, CreateShortUrlResult) => class CreateShort
|
||||||
{renderDateInput('validUntil', 'Enabled until...', { minDate: this.state.validSince })}
|
{renderDateInput('validUntil', 'Enabled until...', { minDate: this.state.validSince })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-3 text-right">
|
||||||
|
<Checkbox
|
||||||
|
className="mr-2"
|
||||||
|
checked={this.state.findIfExists}
|
||||||
|
onChange={(findIfExists) => this.setState({ findIfExists })}
|
||||||
|
>
|
||||||
|
Use existing URL if found
|
||||||
|
</Checkbox>
|
||||||
|
<UseExistingIfFoundInfoIcon />
|
||||||
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-outline-secondary create-short-url__btn"
|
className="btn btn-outline-secondary"
|
||||||
onClick={() => this.setState(({ moreOptionsVisible }) => ({ moreOptionsVisible: !moreOptionsVisible }))}
|
onClick={() => this.setState(({ moreOptionsVisible }) => ({ moreOptionsVisible: !moreOptionsVisible }))}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={this.state.moreOptionsVisible ? upIcon : downIcon} />
|
<FontAwesomeIcon icon={this.state.moreOptionsVisible ? upIcon : downIcon} />
|
||||||
|
|
||||||
{this.state.moreOptionsVisible ? 'Less' : 'More'} options
|
{this.state.moreOptionsVisible ? 'Less' : 'More'} options
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className="btn btn-outline-primary float-right" disabled={shortUrlCreationResult.loading}>
|
||||||
className="btn btn-outline-primary create-short-url__btn float-right"
|
|
||||||
disabled={shortUrlCreationResult.loading}
|
|
||||||
>
|
|
||||||
{shortUrlCreationResult.loading ? 'Creating...' : 'Create'}
|
{shortUrlCreationResult.loading ? 'Creating...' : 'Create'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
56
src/short-urls/UseExistingIfFoundInfoIcon.js
Normal file
56
src/short-urls/UseExistingIfFoundInfoIcon.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
|
||||||
|
import './UseExistingIfFoundInfoIcon.scss';
|
||||||
|
|
||||||
|
const renderInfoModal = (isOpen, toggle) => (
|
||||||
|
<Modal isOpen={isOpen} toggle={toggle} centered size="lg">
|
||||||
|
<ModalHeader toggle={toggle}>Info</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<p>
|
||||||
|
When the
|
||||||
|
<b><i>"Use existing URL if found"</i></b>
|
||||||
|
checkbox is checked, the server will return an existing short URL if it matches provided params.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
These are the checks performed by Shlink in order to determine if an existing short URL should be returned:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
When only the long URL is provided: The most recent match will be returned, or a new short URL will be created
|
||||||
|
if none is found
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
When long URL and custom slug are provided: Same as in previous case, but it will try to match the short URL
|
||||||
|
using both the long URL and the slug.
|
||||||
|
<br />
|
||||||
|
If the slug is being used by another long URL, an error will be returned.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
When other params are provided: Same as in previous cases, but it will try to match existing short URLs with
|
||||||
|
all provided data. If any of them does not match, a new short URL will be created
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<blockquote className="use-existing-if-found-info-icon__modal-quote">
|
||||||
|
<b>Important:</b> This feature will be ignored while using a Shlink version older than v1.16.0.
|
||||||
|
</blockquote>
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
const UseExistingIfFoundInfoIcon = () => {
|
||||||
|
const [ isModalOpen, setIsModalOpen ] = useState(false);
|
||||||
|
const toggleModal = () => setIsModalOpen(!isModalOpen);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<span title="What does this mean?">
|
||||||
|
<FontAwesomeIcon icon={infoIcon} style={{ cursor: 'pointer' }} onClick={toggleModal} />
|
||||||
|
</span>
|
||||||
|
{renderInfoModal(isModalOpen, toggleModal)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UseExistingIfFoundInfoIcon;
|
7
src/short-urls/UseExistingIfFoundInfoIcon.scss
Normal file
7
src/short-urls/UseExistingIfFoundInfoIcon.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.use-existing-if-found-info-icon__modal-quote {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 17.5px;
|
||||||
|
border-left: 5px solid #eee;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
32
src/utils/Checkbox.js
Normal file
32
src/utils/Checkbox.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
checked: PropTypes.bool.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
children: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]),
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Checkbox = ({ checked, onChange, className, children }) => {
|
||||||
|
const id = uuid();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={classNames('custom-control custom-checkbox', className)} style={{ display: 'inline' }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="custom-control-input"
|
||||||
|
id={id}
|
||||||
|
checked={checked}
|
||||||
|
onChange={(e) => onChange(e.target.checked, e)}
|
||||||
|
/>
|
||||||
|
<label className="custom-control-label" htmlFor={id}>{children}</label>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Checkbox.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default Checkbox;
|
29
yarn.lock
29
yarn.lock
|
@ -742,7 +742,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.12.0"
|
regenerator-runtime "^0.12.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.3.1":
|
"@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1":
|
||||||
version "7.3.4"
|
version "7.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83"
|
||||||
integrity sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==
|
integrity sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==
|
||||||
|
@ -1221,6 +1221,11 @@ array-filter@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
|
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
|
||||||
integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
|
integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
|
||||||
|
|
||||||
|
array-filter@~0.0.0:
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
|
||||||
|
integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw=
|
||||||
|
|
||||||
array-find-index@^1.0.1:
|
array-find-index@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
|
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
|
||||||
|
@ -1244,12 +1249,12 @@ array-includes@^3.0.3:
|
||||||
define-properties "^1.1.2"
|
define-properties "^1.1.2"
|
||||||
es-abstract "^1.7.0"
|
es-abstract "^1.7.0"
|
||||||
|
|
||||||
array-map@^0.0.0:
|
array-map@^0.0.0, array-map@~0.0.0:
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
|
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
|
||||||
integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=
|
integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=
|
||||||
|
|
||||||
array-reduce@^0.0.0:
|
array-reduce@^0.0.0, array-reduce@~0.0.0:
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
|
resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
|
||||||
integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=
|
integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=
|
||||||
|
@ -7617,11 +7622,6 @@ postcss-reduce-initial@^4.0.2:
|
||||||
postcss-reduce-transforms@^4.0.1:
|
postcss-reduce-transforms@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.1.tgz#8600d5553bdd3ad640f43bff81eb52f8760d4561"
|
resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.1.tgz#8600d5553bdd3ad640f43bff81eb52f8760d4561"
|
||||||
dependencies:
|
|
||||||
cssnano-util-get-match "^4.0.0"
|
|
||||||
has "^1.0.0"
|
|
||||||
postcss "^7.0.0"
|
|
||||||
postcss-value-parser "^3.0.0"
|
|
||||||
|
|
||||||
postcss-replace-overflow-wrap@^3.0.0:
|
postcss-replace-overflow-wrap@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
@ -8214,10 +8214,12 @@ reactcss@^1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.0.1"
|
lodash "^4.0.1"
|
||||||
|
|
||||||
reactstrap@^6.0.1:
|
reactstrap@^7.1.0:
|
||||||
version "6.5.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-6.5.0.tgz#ba655e32646e2621829f61faa033e607ec6624e5"
|
resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-7.1.0.tgz#fd7125901737a3001c8564c0f8b40e319eec23b2"
|
||||||
|
integrity sha512-wtc4RkgnGn1TsZ0AxOZ2OqT+b8YmCWZj/tErPujWLepxzlEEhveZGC+uDerdaHVSAzJUP2DTk605iper7hutQQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.2.0"
|
||||||
classnames "^2.2.3"
|
classnames "^2.2.3"
|
||||||
lodash.isfunction "^3.0.9"
|
lodash.isfunction "^3.0.9"
|
||||||
lodash.isobject "^3.0.2"
|
lodash.isobject "^3.0.2"
|
||||||
|
@ -8962,11 +8964,6 @@ shebang-regex@^1.0.0:
|
||||||
shell-quote@1.6.1:
|
shell-quote@1.6.1:
|
||||||
version "1.6.1"
|
version "1.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
|
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
|
||||||
dependencies:
|
|
||||||
array-filter "~0.0.0"
|
|
||||||
array-map "~0.0.0"
|
|
||||||
array-reduce "~0.0.0"
|
|
||||||
jsonify "~0.0.0"
|
|
||||||
|
|
||||||
shellwords@^0.1.1:
|
shellwords@^0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
|
|
Loading…
Reference in a new issue