Repository: shlinkio/shlink-web-client (ts)

Issue:
Drop support for Shlink 1.x
<!--
Before opening an issue, just take into account that this is a completely free of charge and open source project.
I'm always happy to help and provide support, but some understanding will be expected.
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
You may also be asked to provide tests or ways to reproduce reported bugs.
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
-->

* Drop support for Shlink v1.x
* Drop support for API v1

Pull request description:
Dropped support for Shlink 1
Closes #344 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7eaf534..7ae2abb0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,7 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 * *Nothing*
 
 ### Removed
-* *Nothing*
+* [#344](https://github.com/shlinkio/shlink-web-client/issues/344) Dropped support for Shlink v1.
 
 ### Fixed
 * *Nothing*
diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx
index 480d0df9..aba3ab8c 100644
--- a/src/short-urls/CreateShortUrl.tsx
+++ b/src/short-urls/CreateShortUrl.tsx
@@ -115,7 +115,6 @@ const CreateShortUrl = (
   );
 
   const currentServerVersion = isReachableServer(selectedServer) ? selectedServer.version : '';
-  const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' });
   const showDomainSelector = versionMatch(currentServerVersion, { minVersion: '2.4.0' });
   const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' });
 
@@ -141,10 +140,7 @@ const CreateShortUrl = (
                     title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
                   },
                 })}
-                {!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text', {
-                  disabled: disableDomain,
-                  ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
-                })}
+                {!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text')}
                 {showDomainSelector && (
                   <FormGroup>
                     <DomainSelector
@@ -165,36 +161,34 @@ const CreateShortUrl = (
             </div>
           </div>
 
-          <ForServerVersion minVersion="1.16.0">
-            <SimpleCard title="Extra validations" className="mb-3">
-              <p>
-                Make sure the long URL is valid, or ensure an existing short URL is returned if it matches all
-                provided data.
-              </p>
-              <ForServerVersion minVersion="2.4.0">
-                <p>
-                  <Checkbox
-                    inline
-                    checked={shortUrlCreation.validateUrl}
-                    onChange={(validateUrl) => setShortUrlCreation({ ...shortUrlCreation, validateUrl })}
-                  >
-                    Validate URL
-                  </Checkbox>
-                </p>
-              </ForServerVersion>
+          <SimpleCard title="Extra validations" className="mb-3">
+            <p>
+              Make sure the long URL is valid, or ensure an existing short URL is returned if it matches all
+              provided data.
+            </p>
+            <ForServerVersion minVersion="2.4.0">
               <p>
                 <Checkbox
                   inline
-                  className="mr-2"
-                  checked={shortUrlCreation.findIfExists}
-                  onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
+                  checked={shortUrlCreation.validateUrl}
+                  onChange={(validateUrl) => setShortUrlCreation({ ...shortUrlCreation, validateUrl })}
                 >
-                  Use existing URL if found
+                  Validate URL
                 </Checkbox>
-                <UseExistingIfFoundInfoIcon />
               </p>
-            </SimpleCard>
-          </ForServerVersion>
+            </ForServerVersion>
+            <p>
+              <Checkbox
+                inline
+                className="mr-2"
+                checked={shortUrlCreation.findIfExists}
+                onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
+              >
+                Use existing URL if found
+              </Checkbox>
+              <UseExistingIfFoundInfoIcon />
+            </p>
+          </SimpleCard>
         </>
       )}
 
diff --git a/src/short-urls/SearchBar.tsx b/src/short-urls/SearchBar.tsx
index b4d850c2..bd22a68b 100644
--- a/src/short-urls/SearchBar.tsx
+++ b/src/short-urls/SearchBar.tsx
@@ -1,6 +1,5 @@
 import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { FC } from 'react';
 import { isEmpty, pipe } from 'ramda';
 import moment from 'moment';
 import SearchField from '../utils/SearchField';
@@ -8,7 +7,6 @@ import Tag from '../tags/helpers/Tag';
 import DateRangeRow from '../utils/DateRangeRow';
 import { formatDate } from '../utils/helpers/date';
 import ColorGenerator from '../utils/services/ColorGenerator';
-import { Versions } from '../utils/helpers/version';
 import { ShortUrlsListParams } from './reducers/shortUrlsListParams';
 import './SearchBar.scss';
 
@@ -19,9 +17,7 @@ interface SearchBarProps {
 
 const dateOrNull = (date?: string) => date ? moment(date) : null;
 
-const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC<Versions>) => (
-  { listShortUrls, shortUrlsListParams }: SearchBarProps,
-) => {
+const SearchBar = (colorGenerator: ColorGenerator) => ({ listShortUrls, shortUrlsListParams }: SearchBarProps) => {
   const selectedTags = shortUrlsListParams.tags ?? [];
   const setDate = (dateName: 'startDate' | 'endDate') => pipe(
     formatDate(),
@@ -36,20 +32,18 @@ const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC<Versions
         }
       />
 
-      <ForServerVersion minVersion="1.21.0">
-        <div className="mt-3">
-          <div className="row">
-            <div className="col-lg-8 offset-lg-4 col-xl-6 offset-xl-6">
-              <DateRangeRow
-                startDate={dateOrNull(shortUrlsListParams.startDate)}
-                endDate={dateOrNull(shortUrlsListParams.endDate)}
-                onStartDateChange={setDate('startDate')}
-                onEndDateChange={setDate('endDate')}
-              />
-            </div>
+      <div className="mt-3">
+        <div className="row">
+          <div className="col-lg-8 offset-lg-4 col-xl-6 offset-xl-6">
+            <DateRangeRow
+              startDate={dateOrNull(shortUrlsListParams.startDate)}
+              endDate={dateOrNull(shortUrlsListParams.endDate)}
+              onStartDateChange={setDate('startDate')}
+              onEndDateChange={setDate('endDate')}
+            />
           </div>
         </div>
-      </ForServerVersion>
+      </div>
 
       {!isEmpty(selectedTags) && (
         <h4 className="search-bar__selected-tag mt-3">
diff --git a/src/short-urls/helpers/DeleteShortUrlModal.tsx b/src/short-urls/helpers/DeleteShortUrlModal.tsx
index c02fc663..20b16709 100644
--- a/src/short-urls/helpers/DeleteShortUrlModal.tsx
+++ b/src/short-urls/helpers/DeleteShortUrlModal.tsx
@@ -21,7 +21,7 @@ const DeleteShortUrlModal = (
   useEffect(() => resetDeleteShortUrl, []);
 
   const { error, errorData } = shortUrlDeletion;
-  const errorCode = error && (errorData?.type || errorData?.error);
+  const errorCode = error && errorData?.type;
   const hasThresholdError = errorCode === THRESHOLD_REACHED;
   const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED;
   const close = pipe(resetDeleteShortUrl, toggle);
diff --git a/src/short-urls/helpers/PreviewModal.scss b/src/short-urls/helpers/PreviewModal.scss
deleted file mode 100644
index 546b4980..00000000
--- a/src/short-urls/helpers/PreviewModal.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-@import '../../utils/mixins/horizontal-align';
-
-.preview-modal__img {
-  max-width: 100%;
-  position: relative;
-  z-index: 2;
-}
-
-.preview-modal__loader {
-  @include horizontal-align();
-  z-index: 1;
-  top: 1rem;
-}
diff --git a/src/short-urls/helpers/PreviewModal.tsx b/src/short-urls/helpers/PreviewModal.tsx
deleted file mode 100644
index bbba03d2..00000000
--- a/src/short-urls/helpers/PreviewModal.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Modal, ModalBody, ModalHeader } from 'reactstrap';
-import { ExternalLink } from 'react-external-link';
-import { ShortUrlModalProps } from '../data';
-import './PreviewModal.scss';
-
-const PreviewModal = ({ shortUrl: { shortUrl }, toggle, isOpen }: ShortUrlModalProps) => (
-  <Modal isOpen={isOpen} toggle={toggle} size="lg">
-    <ModalHeader toggle={toggle}>
-      Preview for <ExternalLink href={shortUrl}>{shortUrl}</ExternalLink>
-    </ModalHeader>
-    <ModalBody>
-      <div className="text-center">
-        <p className="preview-modal__loader">Loading...</p>
-        <img src={`${shortUrl}/preview`} className="preview-modal__img" alt="Preview" />
-      </div>
-    </ModalBody>
-  </Modal>
-);
-
-export default PreviewModal;
diff --git a/src/short-urls/helpers/ShortUrlsRowMenu.tsx b/src/short-urls/helpers/ShortUrlsRowMenu.tsx
index 0abcf081..18d711fc 100644
--- a/src/short-urls/helpers/ShortUrlsRowMenu.tsx
+++ b/src/short-urls/helpers/ShortUrlsRowMenu.tsx
@@ -1,4 +1,3 @@
-import { faImage as pictureIcon } from '@fortawesome/free-regular-svg-icons';
 import {
   faTags as tagsIcon,
   faChartPie as pieChartIcon,
@@ -15,7 +14,6 @@ import { useToggle } from '../../utils/helpers/hooks';
 import { ShortUrl, ShortUrlModalProps } from '../data';
 import { Versions } from '../../utils/helpers/version';
 import { SelectedServer } from '../../servers/data';
-import PreviewModal from './PreviewModal';
 import QrCodeModal from './QrCodeModal';
 import VisitStatsLink from './VisitStatsLink';
 import './ShortUrlsRowMenu.scss';
@@ -35,7 +33,6 @@ const ShortUrlsRowMenu = (
 ) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => {
   const [ isOpen, toggle ] = useToggle();
   const [ isQrModalOpen, toggleQrCode ] = useToggle();
-  const [ isPreviewModalOpen, togglePreview ] = useToggle();
   const [ isTagsModalOpen, toggleTags ] = useToggle();
   const [ isMetaModalOpen, toggleMeta ] = useToggle();
   const [ isDeleteModalOpen, toggleDelete ] = useToggle();
@@ -56,12 +53,10 @@ const ShortUrlsRowMenu = (
         </DropdownItem>
         <EditTagsModal shortUrl={shortUrl} isOpen={isTagsModalOpen} toggle={toggleTags} />
 
-        <ForServerVersion minVersion="1.18.0">
-          <DropdownItem onClick={toggleMeta}>
-            <FontAwesomeIcon icon={editIcon} fixedWidth /> Edit metadata
-          </DropdownItem>
-          <EditMetaModal shortUrl={shortUrl} isOpen={isMetaModalOpen} toggle={toggleMeta} />
-        </ForServerVersion>
+        <DropdownItem onClick={toggleMeta}>
+          <FontAwesomeIcon icon={editIcon} fixedWidth /> Edit metadata
+        </DropdownItem>
+        <EditMetaModal shortUrl={shortUrl} isOpen={isMetaModalOpen} toggle={toggleMeta} />
 
         <ForServerVersion minVersion="2.1.0">
           <DropdownItem onClick={toggleEdit}>
@@ -75,13 +70,6 @@ const ShortUrlsRowMenu = (
         </DropdownItem>
         <QrCodeModal shortUrl={shortUrl} isOpen={isQrModalOpen} toggle={toggleQrCode} />
 
-        <ForServerVersion maxVersion="1.x">
-          <DropdownItem onClick={togglePreview}>
-            <FontAwesomeIcon icon={pictureIcon} fixedWidth /> Preview
-          </DropdownItem>
-          <PreviewModal shortUrl={shortUrl} isOpen={isPreviewModalOpen} toggle={togglePreview} />
-        </ForServerVersion>
-
         <DropdownItem divider />
 
         <DropdownItem className="short-urls-row-menu__dropdown-item--danger" onClick={toggleDelete}>
diff --git a/src/short-urls/services/provideServices.ts b/src/short-urls/services/provideServices.ts
index f20be01c..96a1139c 100644
--- a/src/short-urls/services/provideServices.ts
+++ b/src/short-urls/services/provideServices.ts
@@ -70,7 +70,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
   bottle.decorator('EditShortUrlModal', connect([ 'shortUrlEdition' ], [ 'editShortUrl' ]));
 
   // Services
-  bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator', 'ForServerVersion');
+  bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator');
   bottle.decorator('SearchBar', connect([ 'shortUrlsListParams' ], [ 'listShortUrls' ]));
 
   // Actions
diff --git a/src/utils/services/ShlinkApiClient.ts b/src/utils/services/ShlinkApiClient.ts
index 089ffc35..706a72eb 100644
--- a/src/utils/services/ShlinkApiClient.ts
+++ b/src/utils/services/ShlinkApiClient.ts
@@ -125,7 +125,7 @@ export default class ShlinkApiClient {
 
       // When the request is not invalid or we have already tried both API versions, throw the error and let the
       // caller handle it
-      if (!apiVersionIsNotSupported || this.apiVersion === 1) {
+      if (!apiVersionIsNotSupported || this.apiVersion === 2) {
         throw e;
       }
 
diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts
index 365bc6d7..a098750b 100644
--- a/src/utils/services/types.ts
+++ b/src/utils/services/types.ts
@@ -65,8 +65,6 @@ export interface ProblemDetailsError {
   detail: string;
   title: string;
   status: number;
-  error?: string; // Deprecated
-  message?: string; // Deprecated
   [extraProps: string]: any;
 }
 


Test patch:
diff --git a/test/short-urls/SearchBar.test.tsx b/test/short-urls/SearchBar.test.tsx
index 94389bce..842fcb8f 100644
--- a/test/short-urls/SearchBar.test.tsx
+++ b/test/short-urls/SearchBar.test.tsx
@@ -9,7 +9,7 @@ import ColorGenerator from '../../src/utils/services/ColorGenerator';
 describe('<SearchBar />', () => {
   let wrapper: ShallowWrapper;
   const listShortUrlsMock = jest.fn();
-  const SearchBar = searchBarCreator(Mock.all<ColorGenerator>(), () => null);
+  const SearchBar = searchBarCreator(Mock.all<ColorGenerator>());
 
   afterEach(jest.clearAllMocks);
   afterEach(() => wrapper?.unmount());
diff --git a/test/short-urls/helpers/DeleteShortUrlModal.test.tsx b/test/short-urls/helpers/DeleteShortUrlModal.test.tsx
index bd83a87c..11498d80 100644
--- a/test/short-urls/helpers/DeleteShortUrlModal.test.tsx
+++ b/test/short-urls/helpers/DeleteShortUrlModal.test.tsx
@@ -33,18 +33,10 @@ describe('<DeleteShortUrlModal />', () => {
   afterEach(jest.clearAllMocks);
 
   it.each([
-    [
-      { error: 'INVALID_SHORTCODE_DELETION' },
-      'This short URL has received too many visits, and therefore, it cannot be deleted.',
-    ],
     [
       { type: 'INVALID_SHORTCODE_DELETION' },
       'This short URL has received too many visits, and therefore, it cannot be deleted.',
     ],
-    [
-      { error: 'INVALID_SHORTCODE_DELETION', threshold: 35 },
-      'This short URL has received more than 35 visits, and therefore, it cannot be deleted.',
-    ],
     [
       { type: 'INVALID_SHORTCODE_DELETION', threshold: 8 },
       'This short URL has received more than 8 visits, and therefore, it cannot be deleted.',
@@ -67,7 +59,7 @@ describe('<DeleteShortUrlModal />', () => {
       loading: false,
       error: true,
       shortCode: 'abc123',
-      errorData: Mock.of<ProblemDetailsError>({ error: 'OTHER_ERROR' }),
+      errorData: Mock.of<ProblemDetailsError>({ type: 'OTHER_ERROR' }),
     });
     const error = wrapper.find('.bg-danger');
 
diff --git a/test/short-urls/helpers/PreviewModal.test.tsx b/test/short-urls/helpers/PreviewModal.test.tsx
deleted file mode 100644
index a55d5336..00000000
--- a/test/short-urls/helpers/PreviewModal.test.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { shallow, ShallowWrapper } from 'enzyme';
-import { ExternalLink } from 'react-external-link';
-import { Mock } from 'ts-mockery';
-import PreviewModal from '../../../src/short-urls/helpers/PreviewModal';
-import { ShortUrl } from '../../../src/short-urls/data';
-
-describe('<PreviewModal />', () => {
-  let wrapper: ShallowWrapper;
-  const shortUrl = 'https://doma.in/abc123';
-
-  beforeEach(() => {
-    wrapper = shallow(<PreviewModal shortUrl={Mock.of<ShortUrl>({ shortUrl })} isOpen={true} toggle={() => {}} />);
-  });
-  afterEach(() => wrapper.unmount());
-
-  it('shows an external link to the URL', () => {
-    const externalLink = wrapper.find(ExternalLink);
-
-    expect(externalLink).toHaveLength(1);
-    expect(externalLink.prop('href')).toEqual(shortUrl);
-  });
-
-  it('displays an image with the preview of the URL', () => {
-    const img = wrapper.find('img');
-
-    expect(img).toHaveLength(1);
-    expect(img.prop('src')).toEqual(`${shortUrl}/preview`);
-  });
-});
diff --git a/test/short-urls/helpers/ShortUrlsRowMenu.test.tsx b/test/short-urls/helpers/ShortUrlsRowMenu.test.tsx
index f9984ad5..66af635b 100644
--- a/test/short-urls/helpers/ShortUrlsRowMenu.test.tsx
+++ b/test/short-urls/helpers/ShortUrlsRowMenu.test.tsx
@@ -2,7 +2,6 @@ import { shallow, ShallowWrapper } from 'enzyme';
 import { ButtonDropdown, DropdownItem } from 'reactstrap';
 import { Mock } from 'ts-mockery';
 import createShortUrlsRowMenu from '../../../src/short-urls/helpers/ShortUrlsRowMenu';
-import PreviewModal from '../../../src/short-urls/helpers/PreviewModal';
 import QrCodeModal from '../../../src/short-urls/helpers/QrCodeModal';
 import { ReachableServer } from '../../../src/servers/data';
 import { ShortUrl } from '../../../src/short-urls/data';
@@ -38,13 +37,11 @@ describe('<ShortUrlsRowMenu />', () => {
     const wrapper = createWrapper();
     const deleteShortUrlModal = wrapper.find(DeleteShortUrlModal);
     const editTagsModal = wrapper.find(EditTagsModal);
-    const previewModal = wrapper.find(PreviewModal);
     const qrCodeModal = wrapper.find(QrCodeModal);
     const editModal = wrapper.find(EditShortUrlModal);
 
     expect(deleteShortUrlModal).toHaveLength(1);
     expect(editTagsModal).toHaveLength(1);
-    expect(previewModal).toHaveLength(1);
     expect(qrCodeModal).toHaveLength(1);
     expect(editModal).toHaveLength(1);
   });
@@ -53,7 +50,7 @@ describe('<ShortUrlsRowMenu />', () => {
     const wrapper = createWrapper();
     const items = wrapper.find(DropdownItem);
 
-    expect(items).toHaveLength(8);
+    expect(items).toHaveLength(7);
     expect(items.find('[divider]')).toHaveLength(1);
   });
 
@@ -68,7 +65,6 @@ describe('<ShortUrlsRowMenu />', () => {
 
     it('DeleteShortUrlModal', () => assert(DeleteShortUrlModal));
     it('EditTagsModal', () => assert(EditTagsModal));
-    it('PreviewModal', () => assert(PreviewModal));
     it('QrCodeModal', () => assert(QrCodeModal));
     it('EditShortUrlModal', () => assert(EditShortUrlModal));
     it('EditShortUrlModal', () => assert(ButtonDropdown));