Passed
Pull Request — main (#339)
by Alejandro
04:00
created

src/short-urls/CreateShortUrl.tsx   A

Complexity

Total Complexity 6
Complexity/F 0

Size

Lines of Code 191
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 92%

Importance

Changes 0
Metric Value
wmc 6
eloc 170
mnd 6
bc 6
fnc 0
dl 0
loc 191
ccs 23
cts 25
cp 0.92
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
};
42
43
type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits';
44
type DateFields = 'validSince' | 'validUntil';
45
46 1
const CreateShortUrl = (
47
  TagsSelector: FC<TagsSelectorProps>,
48
  CreateShortUrlResult: FC<CreateShortUrlResultProps>,
49
  ForServerVersion: FC<Versions>,
50
  DomainSelector: FC<DomainSelectorProps>,
51 1
) => ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }: CreateShortUrlProps) => {
52 10
  const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState);
53 10
  const [ moreOptionsVisible, toggleMoreOptionsVisible ] = useToggle();
54
55 10
  const changeTags = (tags: string[]) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) });
56 10
  const reset = () => setShortUrlCreation(initialState);
57 10
  const save = handleEventPreventingDefault(() => {
58 1
    const shortUrlData = {
59
      ...shortUrlCreation,
60
      validSince: formatIsoDate(shortUrlCreation.validSince),
61
      validUntil: formatIsoDate(shortUrlCreation.validUntil),
62
    };
63
64 1
    createShortUrl(shortUrlData).then(reset).catch(() => {});
65
  });
66 10
  const renderOptionalInput = (id: NonDateFields, placeholder: string, type: InputType = 'text', props = {}) => (
67 40
    <FormGroup>
68
      <Input
69
        id={id}
70
        type={type}
71
        placeholder={placeholder}
72
        value={shortUrlCreation[id]}
73 4
        onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, [id]: e.target.value })}
74
        {...props}
75
      />
76
    </FormGroup>
77
  );
78 10
  const renderDateInput = (id: DateFields, placeholder: string, props: Partial<DateInputProps> = {}) => (
79 20
    <div className="form-group">
80
      <DateInput
81
        selected={shortUrlCreation[id] as m.Moment | null}
82
        placeholderText={placeholder}
83
        isClearable
84 2
        onChange={(date) => setShortUrlCreation({ ...shortUrlCreation, [id]: date })}
85
        {...props}
86
      />
87
    </div>
88
  );
89
90 10
  const currentServerVersion = isReachableServer(selectedServer) ? selectedServer.version : '';
91 10
  const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' });
92 10
  const showDomainSelector = versionMatch(currentServerVersion, { minVersion: '2.4.0' });
93 10
  const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' });
94
95 10
  return (
96
    <form onSubmit={save}>
97
      <div className="form-group">
98
        <input
99
          className="form-control form-control-lg"
100
          type="url"
101
          placeholder="Insert the URL to be shortened"
102
          required
103
          value={shortUrlCreation.longUrl}
104 1
          onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })}
105
        />
106
      </div>
107
108
      <Collapse isOpen={moreOptionsVisible}>
109
        <div className="form-group">
110
          <TagsSelector tags={shortUrlCreation.tags ?? []} onChange={changeTags} />
111
        </div>
112
113
        <div className="row">
114
          <div className="col-sm-4">
115
            {renderOptionalInput('customSlug', 'Custom slug', 'text', {
116
              disabled: hasValue(shortUrlCreation.shortCodeLength),
117
            })}
118
          </div>
119
          <div className="col-sm-4">
120
            {renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
121
              min: 4,
122
              disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug),
123
              ...disableShortCodeLength && {
124
                title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
125
              },
126
            })}
127
          </div>
128
          <div className="col-sm-4">
129
            {!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text', {
130
              disabled: disableDomain,
131
              ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
132
            })}
133
            {showDomainSelector && (
134
              <FormGroup>
135
                <DomainSelector
136
                  value={shortUrlCreation.domain}
137
                  onChange={(domain?: string) => setShortUrlCreation({ ...shortUrlCreation, domain })}
138
                />
139
              </FormGroup>
140
            )}
141
          </div>
142
        </div>
143
144
        <div className="row">
145
          <div className="col-sm-4">
146
            {renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
147
          </div>
148
          <div className="col-sm-4">
149
            {renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })}
150
          </div>
151
          <div className="col-sm-4">
152
            {renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })}
153
          </div>
154
        </div>
155
156
        <ForServerVersion minVersion="1.16.0">
157
          <div className="mb-4 text-right">
158
            <Checkbox
159
              inline
160
              className="mr-2"
161
              checked={shortUrlCreation.findIfExists}
162
              onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
163
            >
164
              Use existing URL if found
165
            </Checkbox>
166
            <UseExistingIfFoundInfoIcon />
167
          </div>
168
        </ForServerVersion>
169
      </Collapse>
170
171
      <div>
172
        <button type="button" className="btn btn-outline-secondary" onClick={toggleMoreOptionsVisible}>
173
          <FontAwesomeIcon icon={moreOptionsVisible ? upIcon : downIcon} />
174
          &nbsp;
175
          {moreOptionsVisible ? 'Less' : 'More'} options
176
        </button>
177
        <button
178
          className="btn btn-outline-primary float-right"
179
          disabled={shortUrlCreationResult.saving || isEmpty(shortUrlCreation.longUrl)}
180
        >
181
          {shortUrlCreationResult.saving ? 'Creating...' : 'Create'}
182
        </button>
183
      </div>
184
185
      <CreateShortUrlResult {...shortUrlCreationResult} resetCreateShortUrl={resetCreateShortUrl} />
186
    </form>
187
  );
188
};
189
190
export default CreateShortUrl;
191