mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-10 02:07:26 +03:00
Created reusable component to have a short URL form
This commit is contained in:
parent
ea01d22369
commit
98aa85ca14
8 changed files with 270 additions and 212 deletions
|
@ -1,6 +0,0 @@
|
||||||
@import '../utils/base';
|
|
||||||
|
|
||||||
.create-short-url .form-group:last-child,
|
|
||||||
.create-short-url p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
|
@ -1,24 +1,11 @@
|
||||||
import { isEmpty, pipe, replace, trim } from 'ramda';
|
import { pipe, replace, trim } from 'ramda';
|
||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo } from 'react';
|
||||||
import { Button, FormGroup, Input } from 'reactstrap';
|
|
||||||
import { InputType } from 'reactstrap/lib/Input';
|
|
||||||
import * as m from 'moment';
|
|
||||||
import DateInput, { DateInputProps } from '../utils/DateInput';
|
|
||||||
import Checkbox from '../utils/Checkbox';
|
|
||||||
import { Versions } from '../utils/helpers/version';
|
|
||||||
import { supportsListingDomains, supportsSettingShortCodeLength } from '../utils/helpers/features';
|
|
||||||
import { handleEventPreventingDefault, hasValue } from '../utils/utils';
|
|
||||||
import { SelectedServer } from '../servers/data';
|
import { SelectedServer } from '../servers/data';
|
||||||
import { formatIsoDate } from '../utils/helpers/date';
|
|
||||||
import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
|
|
||||||
import { DomainSelectorProps } from '../domains/DomainSelector';
|
|
||||||
import { SimpleCard } from '../utils/SimpleCard';
|
|
||||||
import { Settings, ShortUrlCreationSettings } from '../settings/reducers/settings';
|
import { Settings, ShortUrlCreationSettings } from '../settings/reducers/settings';
|
||||||
import { ShortUrlData } from './data';
|
import { ShortUrlData } from './data';
|
||||||
import { ShortUrlCreation } from './reducers/shortUrlCreation';
|
import { ShortUrlCreation } from './reducers/shortUrlCreation';
|
||||||
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
|
|
||||||
import { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult';
|
import { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult';
|
||||||
import './CreateShortUrl.scss';
|
import { ShortUrlFormProps } from './ShortUrlForm';
|
||||||
|
|
||||||
export interface CreateShortUrlProps {
|
export interface CreateShortUrlProps {
|
||||||
basicMode?: boolean;
|
basicMode?: boolean;
|
||||||
|
@ -38,6 +25,7 @@ const getInitialState = (settings?: ShortUrlCreationSettings): ShortUrlData => (
|
||||||
longUrl: '',
|
longUrl: '',
|
||||||
tags: [],
|
tags: [],
|
||||||
customSlug: '',
|
customSlug: '',
|
||||||
|
title: undefined,
|
||||||
shortCodeLength: undefined,
|
shortCodeLength: undefined,
|
||||||
domain: '',
|
domain: '',
|
||||||
validSince: undefined,
|
validSince: undefined,
|
||||||
|
@ -47,15 +35,7 @@ const getInitialState = (settings?: ShortUrlCreationSettings): ShortUrlData => (
|
||||||
validateUrl: settings?.validateUrls ?? false,
|
validateUrl: settings?.validateUrls ?? false,
|
||||||
});
|
});
|
||||||
|
|
||||||
type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits';
|
const CreateShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>, CreateShortUrlResult: FC<CreateShortUrlResultProps>) => ({
|
||||||
type DateFields = 'validSince' | 'validUntil';
|
|
||||||
|
|
||||||
const CreateShortUrl = (
|
|
||||||
TagsSelector: FC<TagsSelectorProps>,
|
|
||||||
CreateShortUrlResult: FC<CreateShortUrlResultProps>,
|
|
||||||
ForServerVersion: FC<Versions>,
|
|
||||||
DomainSelector: FC<DomainSelectorProps>,
|
|
||||||
) => ({
|
|
||||||
createShortUrl,
|
createShortUrl,
|
||||||
shortUrlCreationResult,
|
shortUrlCreationResult,
|
||||||
resetCreateShortUrl,
|
resetCreateShortUrl,
|
||||||
|
@ -64,154 +44,21 @@ const CreateShortUrl = (
|
||||||
settings: { shortUrlCreation: shortUrlCreationSettings },
|
settings: { shortUrlCreation: shortUrlCreationSettings },
|
||||||
}: CreateShortUrlConnectProps) => {
|
}: CreateShortUrlConnectProps) => {
|
||||||
const initialState = useMemo(() => getInitialState(shortUrlCreationSettings), [ shortUrlCreationSettings ]);
|
const initialState = useMemo(() => getInitialState(shortUrlCreationSettings), [ shortUrlCreationSettings ]);
|
||||||
const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState);
|
|
||||||
const changeTags = (tags: string[]) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) });
|
|
||||||
const reset = () => setShortUrlCreation(initialState);
|
|
||||||
const save = handleEventPreventingDefault(() => {
|
|
||||||
const shortUrlData = {
|
|
||||||
...shortUrlCreation,
|
|
||||||
validSince: formatIsoDate(shortUrlCreation.validSince) ?? undefined,
|
|
||||||
validUntil: formatIsoDate(shortUrlCreation.validUntil) ?? undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
createShortUrl(shortUrlData).then(reset).catch(() => {});
|
|
||||||
});
|
|
||||||
const renderOptionalInput = (id: NonDateFields, placeholder: string, type: InputType = 'text', props = {}) => (
|
|
||||||
<FormGroup>
|
|
||||||
<Input
|
|
||||||
id={id}
|
|
||||||
type={type}
|
|
||||||
placeholder={placeholder}
|
|
||||||
value={shortUrlCreation[id]}
|
|
||||||
onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, [id]: e.target.value })}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
|
||||||
const renderDateInput = (id: DateFields, placeholder: string, props: Partial<DateInputProps> = {}) => (
|
|
||||||
<div className="form-group">
|
|
||||||
<DateInput
|
|
||||||
selected={shortUrlCreation[id] as m.Moment | null}
|
|
||||||
placeholderText={placeholder}
|
|
||||||
isClearable
|
|
||||||
onChange={(date) => setShortUrlCreation({ ...shortUrlCreation, [id]: date })}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
const basicComponents = (
|
|
||||||
<>
|
|
||||||
<FormGroup>
|
|
||||||
<Input
|
|
||||||
bsSize="lg"
|
|
||||||
type="url"
|
|
||||||
placeholder="URL to be shortened"
|
|
||||||
required
|
|
||||||
value={shortUrlCreation.longUrl}
|
|
||||||
onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<TagsSelector tags={shortUrlCreation.tags ?? []} onChange={changeTags} />
|
|
||||||
</FormGroup>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const showDomainSelector = supportsListingDomains(selectedServer);
|
|
||||||
const disableShortCodeLength = !supportsSettingShortCodeLength(selectedServer);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="create-short-url" onSubmit={save}>
|
<ShortUrlForm
|
||||||
{basicMode && basicComponents}
|
initialState={initialState}
|
||||||
{!basicMode && (
|
saving={shortUrlCreationResult.saving}
|
||||||
<>
|
selectedServer={selectedServer}
|
||||||
<SimpleCard title="Basic options" className="mb-3">
|
mode={basicMode ? 'create-basic' : 'create'}
|
||||||
{basicComponents}
|
onSave={createShortUrl}
|
||||||
</SimpleCard>
|
>
|
||||||
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-sm-6 mb-3">
|
|
||||||
<SimpleCard title="Customize the short URL">
|
|
||||||
{renderOptionalInput('customSlug', 'Custom slug', 'text', {
|
|
||||||
disabled: hasValue(shortUrlCreation.shortCodeLength),
|
|
||||||
})}
|
|
||||||
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
|
|
||||||
min: 4,
|
|
||||||
disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug),
|
|
||||||
...disableShortCodeLength && {
|
|
||||||
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text')}
|
|
||||||
{showDomainSelector && (
|
|
||||||
<FormGroup>
|
|
||||||
<DomainSelector
|
|
||||||
value={shortUrlCreation.domain}
|
|
||||||
onChange={(domain?: string) => setShortUrlCreation({ ...shortUrlCreation, domain })}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</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 })}
|
|
||||||
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })}
|
|
||||||
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })}
|
|
||||||
</SimpleCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<p>
|
|
||||||
<Checkbox
|
|
||||||
inline
|
|
||||||
className="mr-2"
|
|
||||||
checked={shortUrlCreation.findIfExists}
|
|
||||||
onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
|
|
||||||
>
|
|
||||||
Use existing URL if found
|
|
||||||
</Checkbox>
|
|
||||||
<UseExistingIfFoundInfoIcon />
|
|
||||||
</p>
|
|
||||||
</SimpleCard>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="text-center">
|
|
||||||
<Button
|
|
||||||
outline
|
|
||||||
color="primary"
|
|
||||||
disabled={shortUrlCreationResult.saving || isEmpty(shortUrlCreation.longUrl)}
|
|
||||||
className="btn-xs-block"
|
|
||||||
>
|
|
||||||
{shortUrlCreationResult.saving ? 'Creating...' : 'Create'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CreateShortUrlResult
|
<CreateShortUrlResult
|
||||||
{...shortUrlCreationResult}
|
{...shortUrlCreationResult}
|
||||||
resetCreateShortUrl={resetCreateShortUrl}
|
resetCreateShortUrl={resetCreateShortUrl}
|
||||||
canBeClosed={basicMode}
|
canBeClosed={basicMode}
|
||||||
/>
|
/>
|
||||||
</form>
|
</ShortUrlForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
6
src/short-urls/ShortUrlForm.scss
Normal file
6
src/short-urls/ShortUrlForm.scss
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@import '../utils/base';
|
||||||
|
|
||||||
|
.short-url-form .form-group:last-child,
|
||||||
|
.short-url-form p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
180
src/short-urls/ShortUrlForm.tsx
Normal file
180
src/short-urls/ShortUrlForm.tsx
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import { FC, useState } from 'react';
|
||||||
|
import { InputType } from 'reactstrap/lib/Input';
|
||||||
|
import { Button, FormGroup, Input } from 'reactstrap';
|
||||||
|
import { isEmpty } from 'ramda';
|
||||||
|
import * as m from 'moment';
|
||||||
|
import DateInput, { DateInputProps } from '../utils/DateInput';
|
||||||
|
import { supportsListingDomains, supportsSettingShortCodeLength } from '../utils/helpers/features';
|
||||||
|
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 { normalizeTag } from './CreateShortUrl';
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShortUrlForm = (
|
||||||
|
TagsSelector: FC<TagsSelectorProps>,
|
||||||
|
ForServerVersion: FC<Versions>,
|
||||||
|
DomainSelector: FC<DomainSelectorProps>,
|
||||||
|
): FC<ShortUrlFormProps> => ({ mode, saving, onSave, initialState, selectedServer, children }) => {
|
||||||
|
const [ shortUrlData, setShortUrlData ] = useState(initialState);
|
||||||
|
const changeTags = (tags: string[]) => setShortUrlData({ ...shortUrlData, tags: tags.map(normalizeTag) });
|
||||||
|
const reset = () => setShortUrlData(initialState);
|
||||||
|
const submit = handleEventPreventingDefault(async () => onSave({
|
||||||
|
...shortUrlData,
|
||||||
|
validSince: formatIsoDate(shortUrlData.validSince) ?? undefined,
|
||||||
|
validUntil: formatIsoDate(shortUrlData.validUntil) ?? undefined,
|
||||||
|
}).then(reset).catch(() => {}));
|
||||||
|
|
||||||
|
const renderOptionalInput = (id: NonDateFields, placeholder: string, type: InputType = 'text', props = {}) => (
|
||||||
|
<FormGroup>
|
||||||
|
<Input
|
||||||
|
id={id}
|
||||||
|
type={type}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={shortUrlData[id]}
|
||||||
|
onChange={(e) => setShortUrlData({ ...shortUrlData, [id]: e.target.value })}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
const renderDateInput = (id: DateFields, placeholder: string, props: Partial<DateInputProps> = {}) => (
|
||||||
|
<div className="form-group">
|
||||||
|
<DateInput
|
||||||
|
selected={shortUrlData[id] as m.Moment | null}
|
||||||
|
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);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="short-url-form" onSubmit={submit}>
|
||||||
|
{mode === 'create-basic' && basicComponents}
|
||||||
|
{mode !== 'create-basic' && (
|
||||||
|
<>
|
||||||
|
<SimpleCard title="Basic options" className="mb-3">
|
||||||
|
{basicComponents}
|
||||||
|
</SimpleCard>
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-sm-6 mb-3">
|
||||||
|
<SimpleCard title="Customize the short URL">
|
||||||
|
{renderOptionalInput('customSlug', 'Custom slug', 'text', {
|
||||||
|
disabled: hasValue(shortUrlData.shortCodeLength),
|
||||||
|
})}
|
||||||
|
{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',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text')}
|
||||||
|
{showDomainSelector && (
|
||||||
|
<FormGroup>
|
||||||
|
<DomainSelector
|
||||||
|
value={shortUrlData.domain}
|
||||||
|
onChange={(domain?: string) => setShortUrlData({ ...shortUrlData, domain })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</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 })}
|
||||||
|
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlData.validUntil as m.Moment | undefined })}
|
||||||
|
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlData.validSince as m.Moment | undefined })}
|
||||||
|
</SimpleCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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={shortUrlData.validateUrl}
|
||||||
|
onChange={(validateUrl) => setShortUrlData({ ...shortUrlData, validateUrl })}
|
||||||
|
>
|
||||||
|
Validate URL
|
||||||
|
</Checkbox>
|
||||||
|
</p>
|
||||||
|
</ForServerVersion>
|
||||||
|
<p>
|
||||||
|
<Checkbox
|
||||||
|
inline
|
||||||
|
className="mr-2"
|
||||||
|
checked={shortUrlData.findIfExists}
|
||||||
|
onChange={(findIfExists) => setShortUrlData({ ...shortUrlData, findIfExists })}
|
||||||
|
>
|
||||||
|
Use existing URL if found
|
||||||
|
</Checkbox>
|
||||||
|
<UseExistingIfFoundInfoIcon />
|
||||||
|
</p>
|
||||||
|
</SimpleCard>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Button
|
||||||
|
outline
|
||||||
|
color="primary"
|
||||||
|
disabled={saving || isEmpty(shortUrlData.longUrl)}
|
||||||
|
className="btn-xs-block"
|
||||||
|
>
|
||||||
|
{saving ? 'Creating...' : 'Create'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
|
@ -5,6 +5,7 @@ export interface ShortUrlData {
|
||||||
longUrl: string;
|
longUrl: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
customSlug?: string;
|
customSlug?: string;
|
||||||
|
title?: string;
|
||||||
shortCodeLength?: number;
|
shortCodeLength?: number;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
validSince?: m.Moment | string;
|
validSince?: m.Moment | string;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { editShortUrl } from '../reducers/shortUrlEdition';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
import { ShortUrlsTable } from '../ShortUrlsTable';
|
import { ShortUrlsTable } from '../ShortUrlsTable';
|
||||||
import QrCodeModal from '../helpers/QrCodeModal';
|
import QrCodeModal from '../helpers/QrCodeModal';
|
||||||
|
import { ShortUrlForm } from '../ShortUrlForm';
|
||||||
|
|
||||||
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
// Components
|
// Components
|
||||||
|
@ -45,15 +46,9 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
'ForServerVersion',
|
'ForServerVersion',
|
||||||
);
|
);
|
||||||
bottle.serviceFactory('CreateShortUrlResult', CreateShortUrlResult, 'useStateFlagTimeout');
|
bottle.serviceFactory('CreateShortUrlResult', CreateShortUrlResult, 'useStateFlagTimeout');
|
||||||
|
bottle.serviceFactory('ShortUrlForm', ShortUrlForm, 'TagsSelector', 'ForServerVersion', 'DomainSelector');
|
||||||
|
|
||||||
bottle.serviceFactory(
|
bottle.serviceFactory('CreateShortUrl', CreateShortUrl, 'ShortUrlForm', 'CreateShortUrlResult');
|
||||||
'CreateShortUrl',
|
|
||||||
CreateShortUrl,
|
|
||||||
'TagsSelector',
|
|
||||||
'CreateShortUrlResult',
|
|
||||||
'ForServerVersion',
|
|
||||||
'DomainSelector',
|
|
||||||
);
|
|
||||||
bottle.decorator(
|
bottle.decorator(
|
||||||
'CreateShortUrl',
|
'CreateShortUrl',
|
||||||
connect([ 'shortUrlCreationResult', 'selectedServer', 'settings' ], [ 'createShortUrl', 'resetCreateShortUrl' ]),
|
connect([ 'shortUrlCreationResult', 'selectedServer', 'settings' ], [ 'createShortUrl', 'resetCreateShortUrl' ]),
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import moment from 'moment';
|
|
||||||
import { identity } from 'ramda';
|
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { Input } from 'reactstrap';
|
|
||||||
import createShortUrlsCreator from '../../src/short-urls/CreateShortUrl';
|
import createShortUrlsCreator from '../../src/short-urls/CreateShortUrl';
|
||||||
import DateInput from '../../src/utils/DateInput';
|
|
||||||
import { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation';
|
import { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation';
|
||||||
import { Settings } from '../../src/settings/reducers/settings';
|
import { Settings } from '../../src/settings/reducers/settings';
|
||||||
|
|
||||||
describe('<CreateShortUrl />', () => {
|
describe('<CreateShortUrl />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const TagsSelector = () => null;
|
const ShortUrlForm = () => null;
|
||||||
|
const CreateShortUrlResult = () => null;
|
||||||
const shortUrlCreation = { validateUrls: true };
|
const shortUrlCreation = { validateUrls: true };
|
||||||
const shortUrlCreationResult = Mock.all<ShortUrlCreation>();
|
const shortUrlCreationResult = Mock.all<ShortUrlCreation>();
|
||||||
const createShortUrl = jest.fn(async () => Promise.resolve());
|
const createShortUrl = jest.fn(async () => Promise.resolve());
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const CreateShortUrl = createShortUrlsCreator(TagsSelector, () => null, () => null, () => null);
|
const CreateShortUrl = createShortUrlsCreator(ShortUrlForm, CreateShortUrlResult);
|
||||||
|
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<CreateShortUrl
|
<CreateShortUrl
|
||||||
|
@ -31,32 +28,11 @@ describe('<CreateShortUrl />', () => {
|
||||||
afterEach(() => wrapper.unmount());
|
afterEach(() => wrapper.unmount());
|
||||||
afterEach(jest.clearAllMocks);
|
afterEach(jest.clearAllMocks);
|
||||||
|
|
||||||
it('saves short URL with data set in form controls', () => {
|
it('renders a ShortUrlForm with a computed initial state', () => {
|
||||||
const validSince = moment('2017-01-01');
|
const form = wrapper.find(ShortUrlForm);
|
||||||
const validUntil = moment('2017-01-06');
|
const result = wrapper.find(CreateShortUrlResult);
|
||||||
|
|
||||||
wrapper.find(Input).first().simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } });
|
expect(form).toHaveLength(1);
|
||||||
wrapper.find('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]);
|
expect(result).toHaveLength(1);
|
||||||
wrapper.find('#customSlug').simulate('change', { target: { value: 'my-slug' } });
|
|
||||||
wrapper.find('#domain').simulate('change', { target: { value: 'example.com' } });
|
|
||||||
wrapper.find('#maxVisits').simulate('change', { target: { value: '20' } });
|
|
||||||
wrapper.find('#shortCodeLength').simulate('change', { target: { value: 15 } });
|
|
||||||
wrapper.find(DateInput).at(0).simulate('change', validSince);
|
|
||||||
wrapper.find(DateInput).at(1).simulate('change', validUntil);
|
|
||||||
wrapper.find('form').simulate('submit', { preventDefault: identity });
|
|
||||||
|
|
||||||
expect(createShortUrl).toHaveBeenCalledTimes(1);
|
|
||||||
expect(createShortUrl).toHaveBeenCalledWith({
|
|
||||||
longUrl: 'https://long-domain.com/foo/bar',
|
|
||||||
tags: [ 'tag_foo', 'tag_bar' ],
|
|
||||||
customSlug: 'my-slug',
|
|
||||||
domain: 'example.com',
|
|
||||||
validSince: validSince.format(),
|
|
||||||
validUntil: validUntil.format(),
|
|
||||||
maxVisits: '20',
|
|
||||||
findIfExists: false,
|
|
||||||
shortCodeLength: 15,
|
|
||||||
validateUrl: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
59
test/short-urls/ShortUrlForm.test.tsx
Normal file
59
test/short-urls/ShortUrlForm.test.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { identity } from 'ramda';
|
||||||
|
import { Mock } from 'ts-mockery';
|
||||||
|
import { Input } from 'reactstrap';
|
||||||
|
import { ShortUrlForm as createShortUrlForm } from '../../src/short-urls/ShortUrlForm';
|
||||||
|
import DateInput from '../../src/utils/DateInput';
|
||||||
|
import { ShortUrlData } from '../../src/short-urls/data';
|
||||||
|
|
||||||
|
describe('<ShortUrlForm />', () => {
|
||||||
|
let wrapper: ShallowWrapper;
|
||||||
|
const TagsSelector = () => null;
|
||||||
|
const createShortUrl = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const ShortUrlForm = createShortUrlForm(TagsSelector, () => null, () => null);
|
||||||
|
|
||||||
|
wrapper = shallow(
|
||||||
|
<ShortUrlForm
|
||||||
|
selectedServer={null}
|
||||||
|
mode="create"
|
||||||
|
saving={false}
|
||||||
|
initialState={Mock.of<ShortUrlData>({ validateUrl: true, findIfExists: false })}
|
||||||
|
onSave={createShortUrl}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
afterEach(() => wrapper.unmount());
|
||||||
|
afterEach(jest.clearAllMocks);
|
||||||
|
|
||||||
|
it('saves short URL with data set in form controls', () => {
|
||||||
|
const validSince = moment('2017-01-01');
|
||||||
|
const validUntil = moment('2017-01-06');
|
||||||
|
|
||||||
|
wrapper.find(Input).first().simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } });
|
||||||
|
wrapper.find('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]);
|
||||||
|
wrapper.find('#customSlug').simulate('change', { target: { value: 'my-slug' } });
|
||||||
|
wrapper.find('#domain').simulate('change', { target: { value: 'example.com' } });
|
||||||
|
wrapper.find('#maxVisits').simulate('change', { target: { value: '20' } });
|
||||||
|
wrapper.find('#shortCodeLength').simulate('change', { target: { value: 15 } });
|
||||||
|
wrapper.find(DateInput).at(0).simulate('change', validSince);
|
||||||
|
wrapper.find(DateInput).at(1).simulate('change', validUntil);
|
||||||
|
wrapper.find('form').simulate('submit', { preventDefault: identity });
|
||||||
|
|
||||||
|
expect(createShortUrl).toHaveBeenCalledTimes(1);
|
||||||
|
expect(createShortUrl).toHaveBeenCalledWith({
|
||||||
|
longUrl: 'https://long-domain.com/foo/bar',
|
||||||
|
tags: [ 'tag_foo', 'tag_bar' ],
|
||||||
|
customSlug: 'my-slug',
|
||||||
|
domain: 'example.com',
|
||||||
|
validSince: validSince.format(),
|
||||||
|
validUntil: validUntil.format(),
|
||||||
|
maxVisits: '20',
|
||||||
|
findIfExists: false,
|
||||||
|
shortCodeLength: 15,
|
||||||
|
validateUrl: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue