2021-03-20 18:32:12 +03:00
|
|
|
import { FC, useEffect, useState } from 'react';
|
2021-03-19 21:11:27 +03:00
|
|
|
import { InputType } from 'reactstrap/lib/Input';
|
2021-03-20 13:18:00 +03:00
|
|
|
import { Button, FormGroup, Input, Row } from 'reactstrap';
|
2021-03-20 18:32:12 +03:00
|
|
|
import { isEmpty, pipe, replace, trim } from 'ramda';
|
2021-03-27 11:49:47 +03:00
|
|
|
import m from 'moment';
|
2021-03-19 21:11:27 +03:00
|
|
|
import DateInput, { DateInputProps } from '../utils/DateInput';
|
2021-03-20 13:18:00 +03:00
|
|
|
import {
|
|
|
|
supportsListingDomains,
|
|
|
|
supportsSettingShortCodeLength,
|
|
|
|
supportsShortUrlTitle,
|
|
|
|
} from '../utils/helpers/features';
|
2021-03-19 21:11:27 +03:00
|
|
|
import { SimpleCard } from '../utils/SimpleCard';
|
|
|
|
import { handleEventPreventingDefault, hasValue } from '../utils/utils';
|
|
|
|
import Checkbox from '../utils/Checkbox';
|
|
|
|
import { SelectedServer } from '../servers/data';
|
|
|
|
import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
|
|
|
|
import { Versions } from '../utils/helpers/version';
|
|
|
|
import { DomainSelectorProps } from '../domains/DomainSelector';
|
|
|
|
import { formatIsoDate } from '../utils/helpers/date';
|
|
|
|
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
|
|
|
|
import { ShortUrlData } from './data';
|
|
|
|
import './ShortUrlForm.scss';
|
|
|
|
|
|
|
|
type Mode = 'create' | 'create-basic' | 'edit';
|
|
|
|
type DateFields = 'validSince' | 'validUntil';
|
|
|
|
type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits' | 'title';
|
|
|
|
|
|
|
|
export interface ShortUrlFormProps {
|
|
|
|
mode: Mode;
|
|
|
|
saving: boolean;
|
|
|
|
initialState: ShortUrlData;
|
|
|
|
onSave: (shortUrlData: ShortUrlData) => Promise<unknown>;
|
|
|
|
selectedServer: SelectedServer;
|
|
|
|
}
|
|
|
|
|
2021-03-20 18:32:12 +03:00
|
|
|
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
|
|
|
|
2021-03-19 21:11:27 +03:00
|
|
|
export const ShortUrlForm = (
|
|
|
|
TagsSelector: FC<TagsSelectorProps>,
|
|
|
|
ForServerVersion: FC<Versions>,
|
|
|
|
DomainSelector: FC<DomainSelectorProps>,
|
2021-03-27 12:41:13 +03:00
|
|
|
): FC<ShortUrlFormProps> => ({ mode, saving, onSave, initialState, selectedServer }) => { // eslint-disable-line complexity
|
2021-03-19 21:11:27 +03:00
|
|
|
const [ shortUrlData, setShortUrlData ] = useState(initialState);
|
2021-03-20 18:32:12 +03:00
|
|
|
const isEdit = mode === 'edit';
|
2021-03-19 21:11:27 +03:00
|
|
|
const changeTags = (tags: string[]) => setShortUrlData({ ...shortUrlData, tags: tags.map(normalizeTag) });
|
|
|
|
const reset = () => setShortUrlData(initialState);
|
|
|
|
const submit = handleEventPreventingDefault(async () => onSave({
|
|
|
|
...shortUrlData,
|
2021-03-27 11:49:47 +03:00
|
|
|
validSince: formatIsoDate(shortUrlData.validSince) ?? null,
|
|
|
|
validUntil: formatIsoDate(shortUrlData.validUntil) ?? null,
|
|
|
|
maxVisits: !hasValue(shortUrlData.maxVisits) ? null : Number(shortUrlData.maxVisits),
|
2021-03-27 12:19:35 +03:00
|
|
|
title: !hasValue(shortUrlData.title) ? undefined : shortUrlData.title,
|
2021-03-20 18:32:12 +03:00
|
|
|
}).then(() => !isEdit && reset()).catch(() => {}));
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setShortUrlData(initialState);
|
|
|
|
}, [ initialState ]);
|
2021-03-19 21:11:27 +03:00
|
|
|
|
|
|
|
const renderOptionalInput = (id: NonDateFields, placeholder: string, type: InputType = 'text', props = {}) => (
|
|
|
|
<FormGroup>
|
|
|
|
<Input
|
|
|
|
id={id}
|
|
|
|
type={type}
|
|
|
|
placeholder={placeholder}
|
2021-03-20 18:32:12 +03:00
|
|
|
value={shortUrlData[id] ?? ''}
|
2021-03-19 21:11:27 +03:00
|
|
|
onChange={(e) => setShortUrlData({ ...shortUrlData, [id]: e.target.value })}
|
|
|
|
{...props}
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
);
|
|
|
|
const renderDateInput = (id: DateFields, placeholder: string, props: Partial<DateInputProps> = {}) => (
|
|
|
|
<div className="form-group">
|
|
|
|
<DateInput
|
2021-03-27 11:49:47 +03:00
|
|
|
selected={shortUrlData[id] ? m(shortUrlData[id]) : null}
|
2021-03-19 21:11:27 +03:00
|
|
|
placeholderText={placeholder}
|
|
|
|
isClearable
|
|
|
|
onChange={(date) => setShortUrlData({ ...shortUrlData, [id]: date })}
|
|
|
|
{...props}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
const basicComponents = (
|
|
|
|
<>
|
|
|
|
<FormGroup>
|
|
|
|
<Input
|
|
|
|
bsSize="lg"
|
|
|
|
type="url"
|
|
|
|
placeholder="URL to be shortened"
|
|
|
|
required
|
|
|
|
value={shortUrlData.longUrl}
|
|
|
|
onChange={(e) => setShortUrlData({ ...shortUrlData, longUrl: e.target.value })}
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
|
|
|
|
<FormGroup>
|
|
|
|
<TagsSelector tags={shortUrlData.tags ?? []} onChange={changeTags} />
|
|
|
|
</FormGroup>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
|
|
|
const showDomainSelector = supportsListingDomains(selectedServer);
|
|
|
|
const disableShortCodeLength = !supportsSettingShortCodeLength(selectedServer);
|
2021-03-20 13:18:00 +03:00
|
|
|
const supportsTitle = supportsShortUrlTitle(selectedServer);
|
2021-03-19 21:11:27 +03:00
|
|
|
|
|
|
|
return (
|
|
|
|
<form className="short-url-form" onSubmit={submit}>
|
|
|
|
{mode === 'create-basic' && basicComponents}
|
|
|
|
{mode !== 'create-basic' && (
|
|
|
|
<>
|
|
|
|
<SimpleCard title="Basic options" className="mb-3">
|
|
|
|
{basicComponents}
|
|
|
|
</SimpleCard>
|
|
|
|
|
2021-03-20 13:18:00 +03:00
|
|
|
<Row>
|
2021-03-19 21:11:27 +03:00
|
|
|
<div className="col-sm-6 mb-3">
|
|
|
|
<SimpleCard title="Customize the short URL">
|
2021-03-20 13:18:00 +03:00
|
|
|
{supportsTitle && renderOptionalInput('title', 'Title')}
|
|
|
|
{!isEdit && (
|
|
|
|
<>
|
|
|
|
<Row>
|
|
|
|
<div className="col-lg-6">
|
|
|
|
{renderOptionalInput('customSlug', 'Custom slug', 'text', {
|
|
|
|
disabled: hasValue(shortUrlData.shortCodeLength),
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
<div className="col-lg-6">
|
|
|
|
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
|
|
|
|
min: 4,
|
|
|
|
disabled: disableShortCodeLength || hasValue(shortUrlData.customSlug),
|
|
|
|
...disableShortCodeLength && {
|
|
|
|
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
|
|
|
|
},
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</Row>
|
|
|
|
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text')}
|
|
|
|
{showDomainSelector && (
|
|
|
|
<FormGroup>
|
|
|
|
<DomainSelector
|
|
|
|
value={shortUrlData.domain}
|
|
|
|
onChange={(domain?: string) => setShortUrlData({ ...shortUrlData, domain })}
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
)}
|
|
|
|
</>
|
2021-03-19 21:11:27 +03:00
|
|
|
)}
|
|
|
|
</SimpleCard>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="col-sm-6 mb-3">
|
|
|
|
<SimpleCard title="Limit access to the short URL">
|
|
|
|
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
|
2021-03-27 11:49:47 +03:00
|
|
|
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlData.validUntil ? m(shortUrlData.validUntil) : undefined })}
|
|
|
|
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlData.validSince ? m(shortUrlData.validSince) : undefined })}
|
2021-03-19 21:11:27 +03:00
|
|
|
</SimpleCard>
|
|
|
|
</div>
|
2021-03-20 13:18:00 +03:00
|
|
|
</Row>
|
2021-03-19 21:11:27 +03:00
|
|
|
|
|
|
|
<SimpleCard title="Extra validations" className="mb-3">
|
2021-03-20 13:18:00 +03:00
|
|
|
{!isEdit && (
|
|
|
|
<p>
|
|
|
|
Make sure the long URL is valid, or ensure an existing short URL is returned if it matches all
|
|
|
|
provided data.
|
|
|
|
</p>
|
|
|
|
)}
|
2021-03-19 21:11:27 +03:00
|
|
|
<ForServerVersion minVersion="2.4.0">
|
|
|
|
<p>
|
|
|
|
<Checkbox
|
|
|
|
inline
|
|
|
|
checked={shortUrlData.validateUrl}
|
|
|
|
onChange={(validateUrl) => setShortUrlData({ ...shortUrlData, validateUrl })}
|
|
|
|
>
|
|
|
|
Validate URL
|
|
|
|
</Checkbox>
|
|
|
|
</p>
|
|
|
|
</ForServerVersion>
|
2021-03-20 13:18:00 +03:00
|
|
|
{!isEdit && (
|
|
|
|
<p>
|
|
|
|
<Checkbox
|
|
|
|
inline
|
|
|
|
className="mr-2"
|
|
|
|
checked={shortUrlData.findIfExists}
|
|
|
|
onChange={(findIfExists) => setShortUrlData({ ...shortUrlData, findIfExists })}
|
|
|
|
>
|
|
|
|
Use existing URL if found
|
|
|
|
</Checkbox>
|
|
|
|
<UseExistingIfFoundInfoIcon />
|
|
|
|
</p>
|
|
|
|
)}
|
2021-03-19 21:11:27 +03:00
|
|
|
</SimpleCard>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
<Button
|
|
|
|
outline
|
|
|
|
color="primary"
|
|
|
|
disabled={saving || isEmpty(shortUrlData.longUrl)}
|
|
|
|
className="btn-xs-block"
|
|
|
|
>
|
2021-03-20 18:32:12 +03:00
|
|
|
{saving ? 'Saving...' : 'Save'}
|
2021-03-19 21:11:27 +03:00
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
);
|
|
|
|
};
|