diff --git a/package.json b/package.json
index 29cb437b..bdb53a80 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"react-router-dom": "^4.2.2",
"react-swipeable": "^4.3.0",
"react-tagsinput": "^3.19.0",
- "reactstrap": "^6.0.1",
+ "reactstrap": "^7.1.0",
"redux": "^4.0.0",
"redux-actions": "^2.6.5",
"redux-thunk": "^2.3.0",
diff --git a/src/short-urls/CreateShortUrl.js b/src/short-urls/CreateShortUrl.js
index 89838947..38d1d4be 100644
--- a/src/short-urls/CreateShortUrl.js
+++ b/src/short-urls/CreateShortUrl.js
@@ -5,7 +5,9 @@ import React from 'react';
import { Collapse } from 'reactstrap';
import * as PropTypes from 'prop-types';
import DateInput from '../utils/DateInput';
+import Checkbox from '../utils/Checkbox';
import { createShortUrlResultType } from './reducers/shortUrlCreation';
+import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
const normalizeTag = pipe(trim, replace(/ /g, '-'));
const formatDate = (date) => isNil(date) ? date : date.format();
@@ -24,6 +26,7 @@ const CreateShortUrl = (TagsSelector, CreateShortUrlResult) => class CreateShort
validSince: undefined,
validUntil: undefined,
maxVisits: undefined,
+ findIfExists: false,
moreOptionsVisible: false,
};
@@ -93,22 +96,30 @@ const CreateShortUrl = (TagsSelector, CreateShortUrlResult) => class CreateShort
{renderDateInput('validUntil', 'Enabled until...', { minDate: this.state.validSince })}
+
+
+ this.setState({ findIfExists })}
+ >
+ Use existing URL if found
+
+
+
this.setState(({ moreOptionsVisible }) => ({ moreOptionsVisible: !moreOptionsVisible }))}
>
{this.state.moreOptionsVisible ? 'Less' : 'More'} options
-
+
{shortUrlCreationResult.loading ? 'Creating...' : 'Create'}
diff --git a/src/short-urls/UseExistingIfFoundInfoIcon.js b/src/short-urls/UseExistingIfFoundInfoIcon.js
new file mode 100644
index 00000000..e96457cb
--- /dev/null
+++ b/src/short-urls/UseExistingIfFoundInfoIcon.js
@@ -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) => (
+
+ Info
+
+
+ When the
+ "Use existing URL if found"
+ checkbox is checked, the server will return an existing short URL if it matches provided params.
+
+
+ These are the checks performed by Shlink in order to determine if an existing short URL should be returned:
+
+
+
+ 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
+
+
+ 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.
+
+ If the slug is being used by another long URL, an error will be returned.
+
+
+ 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
+
+
+
+ Important: This feature will be ignored while using a Shlink version older than v1.16.0.
+
+
+
+);
+
+const UseExistingIfFoundInfoIcon = () => {
+ const [ isModalOpen, setIsModalOpen ] = useState(false);
+ const toggleModal = () => setIsModalOpen(!isModalOpen);
+
+ return (
+
+
+
+
+ {renderInfoModal(isModalOpen, toggleModal)}
+
+ );
+};
+
+export default UseExistingIfFoundInfoIcon;
diff --git a/src/short-urls/UseExistingIfFoundInfoIcon.scss b/src/short-urls/UseExistingIfFoundInfoIcon.scss
new file mode 100644
index 00000000..835e5d2d
--- /dev/null
+++ b/src/short-urls/UseExistingIfFoundInfoIcon.scss
@@ -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;
+}
diff --git a/src/utils/Checkbox.js b/src/utils/Checkbox.js
new file mode 100644
index 00000000..c83d1a10
--- /dev/null
+++ b/src/utils/Checkbox.js
@@ -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 (
+
+ onChange(e.target.checked, e)}
+ />
+ {children}
+
+ );
+};
+
+Checkbox.propTypes = propTypes;
+
+export default Checkbox;
diff --git a/yarn.lock b/yarn.lock
index 639969e8..8c51b072 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -742,7 +742,7 @@
dependencies:
regenerator-runtime "^0.12.0"
-"@babel/runtime@^7.3.1":
+"@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1":
version "7.3.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83"
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"
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:
version "1.0.2"
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"
es-abstract "^1.7.0"
-array-map@^0.0.0:
+array-map@^0.0.0, array-map@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=
-array-reduce@^0.0.0:
+array-reduce@^0.0.0, array-reduce@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=
@@ -7617,11 +7622,6 @@ postcss-reduce-initial@^4.0.2:
postcss-reduce-transforms@^4.0.1:
version "4.0.1"
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:
version "3.0.0"
@@ -8214,10 +8214,12 @@ reactcss@^1.2.0:
dependencies:
lodash "^4.0.1"
-reactstrap@^6.0.1:
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-6.5.0.tgz#ba655e32646e2621829f61faa033e607ec6624e5"
+reactstrap@^7.1.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-7.1.0.tgz#fd7125901737a3001c8564c0f8b40e319eec23b2"
+ integrity sha512-wtc4RkgnGn1TsZ0AxOZ2OqT+b8YmCWZj/tErPujWLepxzlEEhveZGC+uDerdaHVSAzJUP2DTk605iper7hutQQ==
dependencies:
+ "@babel/runtime" "^7.2.0"
classnames "^2.2.3"
lodash.isfunction "^3.0.9"
lodash.isobject "^3.0.2"
@@ -8962,11 +8964,6 @@ shebang-regex@^1.0.0:
shell-quote@1.6.1:
version "1.6.1"
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:
version "0.1.1"