Passed
Pull Request — main (#346)
by Alejandro
04:14
created

src/short-urls/CreateShortUrl.tsx   A

Complexity

Total Complexity 8
Complexity/F 0

Size

Lines of Code 216
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 88%

Importance

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