Passed
Pull Request — main (#341)
by Alejandro
04:32
created

src/short-urls/CreateShortUrl.tsx   A

Complexity

Total Complexity 6
Complexity/F 0

Size

Lines of Code 205
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 88.46%

Importance

Changes 0
Metric Value
wmc 6
eloc 183
mnd 6
bc 6
fnc 0
dl 0
loc 205
ccs 23
cts 26
cp 0.8846
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import { faAngleDoubleDown as downIcon, faAngleDoubleUp as upIcon } from '@fortawesome/free-solid-svg-icons';
2
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
import { isEmpty, pipe, replace, trim } from 'ramda';
4
import { FC, useState } from 'react';
5
import { Collapse, FormGroup, Input } from 'reactstrap';
6
import { InputType } from 'reactstrap/lib/Input';
7
import * as m from 'moment';
8
import DateInput, { DateInputProps } from '../utils/DateInput';
9
import Checkbox from '../utils/Checkbox';
10
import { versionMatch, Versions } from '../utils/helpers/version';
11
import { handleEventPreventingDefault, hasValue } from '../utils/utils';
12
import { useToggle } from '../utils/helpers/hooks';
13
import { isReachableServer, SelectedServer } from '../servers/data';
14
import { formatIsoDate } from '../utils/helpers/date';
15
import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
16
import { DomainSelectorProps } from '../domains/DomainSelector';
17
import { ShortUrlData } from './data';
18
import { ShortUrlCreation } from './reducers/shortUrlCreation';
19
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
20
import { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult';
21
22 1
const normalizeTag = pipe(trim, replace(/ /g, '-'));
23
24
interface CreateShortUrlProps {
25
  shortUrlCreationResult: ShortUrlCreation;
26
  selectedServer: SelectedServer;
27
  createShortUrl: Function;
28
  resetCreateShortUrl: () => void;
29
}
30
31 1
const initialState: ShortUrlData = {
32
  longUrl: '',
33
  tags: [],
34
  customSlug: '',
35
  shortCodeLength: undefined,
36
  domain: '',
37
  validSince: undefined,
38
  validUntil: undefined,
39
  maxVisits: undefined,
40
  findIfExists: false,
41
  validateUrl: true,
42
};
43
44
type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits';
45
type DateFields = 'validSince' | 'validUntil';
46
47 1
const CreateShortUrl = (
48
  TagsSelector: FC<TagsSelectorProps>,
49
  CreateShortUrlResult: FC<CreateShortUrlResultProps>,
50
  ForServerVersion: FC<Versions>,
51
  DomainSelector: FC<DomainSelectorProps>,
52 1
) => ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }: CreateShortUrlProps) => {
53 10
  const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState);
54 10
  const [ moreOptionsVisible, toggleMoreOptionsVisible ] = useToggle();
55
56 10
  const changeTags = (tags: string[]) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) });
57 10
  const reset = () => setShortUrlCreation(initialState);
58 10
  const save = handleEventPreventingDefault(() => {
59 1
    const shortUrlData = {
60
      ...shortUrlCreation,
61
      validSince: formatIsoDate(shortUrlCreation.validSince),
62
      validUntil: formatIsoDate(shortUrlCreation.validUntil),
63
    };
64
65 1
    createShortUrl(shortUrlData).then(reset).catch(() => {});
66
  });
67 10
  const renderOptionalInput = (id: NonDateFields, placeholder: string, type: InputType = 'text', props = {}) => (
68 40
    <FormGroup>
69
      <Input
70
        id={id}
71
        type={type}
72
        placeholder={placeholder}
73
        value={shortUrlCreation[id]}
74 4
        onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, [id]: e.target.value })}
75
        {...props}
76
      />
77
    </FormGroup>
78
  );
79 10
  const renderDateInput = (id: DateFields, placeholder: string, props: Partial<DateInputProps> = {}) => (
80 20
    <div className="form-group">
81
      <DateInput
82
        selected={shortUrlCreation[id] as m.Moment | null}
83
        placeholderText={placeholder}
84
        isClearable
85 2
        onChange={(date) => setShortUrlCreation({ ...shortUrlCreation, [id]: date })}
86
        {...props}
87
      />
88
    </div>
89
  );
90
91 10
  const currentServerVersion = isReachableServer(selectedServer) ? selectedServer.version : '';
92 10
  const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' });
93 10
  const showDomainSelector = versionMatch(currentServerVersion, { minVersion: '2.4.0' });
94 10
  const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' });
95
96 10
  return (
97
    <form onSubmit={save}>
98
      <div className="form-group">
99
        <input
100
          className="form-control form-control-lg"
101
          type="url"
102
          placeholder="Insert the URL to be shortened"
103
          required
104
          value={shortUrlCreation.longUrl}
105 1
          onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })}
106
        />
107
      </div>
108
109
      <Collapse isOpen={moreOptionsVisible}>
110
        <div className="form-group">
111
          <TagsSelector tags={shortUrlCreation.tags ?? []} onChange={changeTags} />
112
        </div>
113
114
        <div className="row">
115
          <div className="col-sm-4">
116
            {renderOptionalInput('customSlug', 'Custom slug', 'text', {
117
              disabled: hasValue(shortUrlCreation.shortCodeLength),
118
            })}
119
          </div>
120
          <div className="col-sm-4">
121
            {renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
122
              min: 4,
123
              disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug),
124
              ...disableShortCodeLength && {
125
                title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
126
              },
127
            })}
128
          </div>
129
          <div className="col-sm-4">
130
            {!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text', {
131
              disabled: disableDomain,
132
              ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
133
            })}
134
            {showDomainSelector && (
135
              <FormGroup>
136
                <DomainSelector
137
                  value={shortUrlCreation.domain}
138
                  onChange={(domain?: string) => setShortUrlCreation({ ...shortUrlCreation, domain })}
139
                />
140
              </FormGroup>
141
            )}
142
          </div>
143
        </div>
144
145
        <div className="row">
146
          <div className="col-sm-4">
147
            {renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
148
          </div>
149
          <div className="col-sm-4">
150
            {renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })}
151
          </div>
152
          <div className="col-sm-4">
153
            {renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })}
154
          </div>
155
        </div>
156
157
        <ForServerVersion minVersion="1.16.0">
158
          <div className="mb-4 row">
159
            <div className="col-sm-6 text-center text-sm-left mb-2 mb-sm-0">
160
              <ForServerVersion minVersion="2.4.0">
161
                <Checkbox
162
                  inline
163
                  checked={shortUrlCreation.validateUrl}
164
                  onChange={(validateUrl) => setShortUrlCreation({ ...shortUrlCreation, validateUrl })}
165
                >
166
                  Validate URL
167
                </Checkbox>
168
              </ForServerVersion>
169
            </div>
170
            <div className="col-sm-6 text-center text-sm-right">
171
              <Checkbox
172
                inline
173
                className="mr-2"
174
                checked={shortUrlCreation.findIfExists}
175
                onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
176
              >
177
                Use existing URL if found
178
              </Checkbox>
179
              <UseExistingIfFoundInfoIcon />
180
            </div>
181
          </div>
182
        </ForServerVersion>
183
      </Collapse>
184
185
      <div>
186
        <button type="button" className="btn btn-outline-secondary" onClick={toggleMoreOptionsVisible}>
187
          <FontAwesomeIcon icon={moreOptionsVisible ? upIcon : downIcon} />
188
          &nbsp;
189
          {moreOptionsVisible ? 'Less' : 'More'} options
190
        </button>
191
        <button
192
          className="btn btn-outline-primary float-right"
193
          disabled={shortUrlCreationResult.saving || isEmpty(shortUrlCreation.longUrl)}
194
        >
195
          {shortUrlCreationResult.saving ? 'Creating...' : 'Create'}
196
        </button>
197
      </div>
198
199
      <CreateShortUrlResult {...shortUrlCreationResult} resetCreateShortUrl={resetCreateShortUrl} />
200
    </form>
201
  );
202
};
203
204
export default CreateShortUrl;
205