mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-10 09:33:51 +00:00
Added form to create short URLs to overview page
This commit is contained in:
@@ -7,6 +7,7 @@ import { prettify } from '../utils/helpers/numbers';
|
|||||||
import { TagsList } from '../tags/reducers/tagsList';
|
import { TagsList } from '../tags/reducers/tagsList';
|
||||||
import { ShortUrlsTableProps } from '../short-urls/ShortUrlsTable';
|
import { ShortUrlsTableProps } from '../short-urls/ShortUrlsTable';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
|
import { CreateShortUrlProps } from '../short-urls/CreateShortUrl';
|
||||||
import { VisitsOverview } from '../visits/reducers/visitsOverview';
|
import { VisitsOverview } from '../visits/reducers/visitsOverview';
|
||||||
import { isServerWithId, SelectedServer } from './data';
|
import { isServerWithId, SelectedServer } from './data';
|
||||||
import './Overview.scss';
|
import './Overview.scss';
|
||||||
@@ -21,7 +22,10 @@ interface OverviewConnectProps {
|
|||||||
loadVisitsOverview: Function;
|
loadVisitsOverview: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Overview = (ShortUrlsTable: FC<ShortUrlsTableProps>) => boundToMercureHub(({
|
export const Overview = (
|
||||||
|
ShortUrlsTable: FC<ShortUrlsTableProps>,
|
||||||
|
CreateShortUrl: FC<CreateShortUrlProps>,
|
||||||
|
) => boundToMercureHub(({
|
||||||
shortUrlsList,
|
shortUrlsList,
|
||||||
listShortUrls,
|
listShortUrls,
|
||||||
listTags,
|
listTags,
|
||||||
@@ -67,10 +71,12 @@ export const Overview = (ShortUrlsTable: FC<ShortUrlsTableProps>) => boundToMerc
|
|||||||
</div>
|
</div>
|
||||||
<Card className="mb-4">
|
<Card className="mb-4">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
Create short URL
|
Create a short URL
|
||||||
<Link className="float-right" to={`/server/${serverId}/create-short-url`}>Advanced options »</Link>
|
<Link className="float-right" to={`/server/${serverId}/create-short-url`}>Advanced options »</Link>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>Create</CardBody>
|
<CardBody>
|
||||||
|
<CreateShortUrl basicMode />
|
||||||
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
|
|||||||
bottle.serviceFactory('ServerError', ServerError, 'DeleteServerButton');
|
bottle.serviceFactory('ServerError', ServerError, 'DeleteServerButton');
|
||||||
bottle.decorator('ServerError', connect([ 'servers', 'selectedServer' ]));
|
bottle.decorator('ServerError', connect([ 'servers', 'selectedServer' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('Overview', Overview, 'ShortUrlsTable');
|
bottle.serviceFactory('Overview', Overview, 'ShortUrlsTable', 'CreateShortUrl');
|
||||||
bottle.decorator('Overview', connect(
|
bottle.decorator('Overview', connect(
|
||||||
[ 'shortUrlsList', 'tagsList', 'selectedServer', 'mercureInfo', 'visitsOverview' ],
|
[ 'shortUrlsList', 'tagsList', 'selectedServer', 'mercureInfo', 'visitsOverview' ],
|
||||||
[ 'listShortUrls', 'listTags', 'createNewVisits', 'loadMercureInfo', 'loadVisitsOverview' ],
|
[ 'listShortUrls', 'listTags', 'createNewVisits', 'loadMercureInfo', 'loadVisitsOverview' ],
|
||||||
|
|||||||
@@ -17,15 +17,19 @@ import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
|
|||||||
import { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult';
|
import { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult';
|
||||||
import './CreateShortUrl.scss';
|
import './CreateShortUrl.scss';
|
||||||
|
|
||||||
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
export interface CreateShortUrlProps {
|
||||||
|
basicMode?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateShortUrlProps {
|
interface CreateShortUrlConnectProps extends CreateShortUrlProps {
|
||||||
shortUrlCreationResult: ShortUrlCreation;
|
shortUrlCreationResult: ShortUrlCreation;
|
||||||
selectedServer: SelectedServer;
|
selectedServer: SelectedServer;
|
||||||
createShortUrl: Function;
|
createShortUrl: (data: ShortUrlData) => Promise<void>;
|
||||||
resetCreateShortUrl: () => void;
|
resetCreateShortUrl: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
||||||
|
|
||||||
const initialState: ShortUrlData = {
|
const initialState: ShortUrlData = {
|
||||||
longUrl: '',
|
longUrl: '',
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -47,7 +51,13 @@ const CreateShortUrl = (
|
|||||||
CreateShortUrlResult: FC<CreateShortUrlResultProps>,
|
CreateShortUrlResult: FC<CreateShortUrlResultProps>,
|
||||||
ForServerVersion: FC<Versions>,
|
ForServerVersion: FC<Versions>,
|
||||||
DomainSelector: FC<DomainSelectorProps>,
|
DomainSelector: FC<DomainSelectorProps>,
|
||||||
) => ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }: CreateShortUrlProps) => {
|
) => ({
|
||||||
|
createShortUrl,
|
||||||
|
shortUrlCreationResult,
|
||||||
|
resetCreateShortUrl,
|
||||||
|
selectedServer,
|
||||||
|
basicMode = false,
|
||||||
|
}: CreateShortUrlConnectProps) => {
|
||||||
const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState);
|
const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState);
|
||||||
|
|
||||||
const changeTags = (tags: string[]) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) });
|
const changeTags = (tags: string[]) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) });
|
||||||
@@ -55,8 +65,8 @@ const CreateShortUrl = (
|
|||||||
const save = handleEventPreventingDefault(() => {
|
const save = handleEventPreventingDefault(() => {
|
||||||
const shortUrlData = {
|
const shortUrlData = {
|
||||||
...shortUrlCreation,
|
...shortUrlCreation,
|
||||||
validSince: formatIsoDate(shortUrlCreation.validSince),
|
validSince: formatIsoDate(shortUrlCreation.validSince) ?? undefined,
|
||||||
validUntil: formatIsoDate(shortUrlCreation.validUntil),
|
validUntil: formatIsoDate(shortUrlCreation.validUntil) ?? undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
createShortUrl(shortUrlData).then(reset).catch(() => {});
|
createShortUrl(shortUrlData).then(reset).catch(() => {});
|
||||||
@@ -92,90 +102,94 @@ const CreateShortUrl = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={save}>
|
<form onSubmit={save}>
|
||||||
<div className="form-group">
|
<FormGroup>
|
||||||
<input
|
<Input
|
||||||
className="form-control form-control-lg"
|
bsSize="lg"
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="Insert the URL to be shortened"
|
placeholder="Insert the URL to be shortened"
|
||||||
required
|
required
|
||||||
value={shortUrlCreation.longUrl}
|
value={shortUrlCreation.longUrl}
|
||||||
onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })}
|
onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</FormGroup>
|
||||||
|
|
||||||
<div className="form-group">
|
<FormGroup>
|
||||||
<TagsSelector tags={shortUrlCreation.tags ?? []} onChange={changeTags} />
|
<TagsSelector tags={shortUrlCreation.tags ?? []} onChange={changeTags} />
|
||||||
</div>
|
</FormGroup>
|
||||||
|
|
||||||
<div className="row">
|
{!basicMode && (
|
||||||
<div className="col-sm-4">
|
<>
|
||||||
{renderOptionalInput('customSlug', 'Custom slug', 'text', {
|
<div className="row">
|
||||||
disabled: hasValue(shortUrlCreation.shortCodeLength),
|
<div className="col-sm-4">
|
||||||
})}
|
{renderOptionalInput('customSlug', 'Custom slug', 'text', {
|
||||||
</div>
|
disabled: hasValue(shortUrlCreation.shortCodeLength),
|
||||||
<div className="col-sm-4">
|
})}
|
||||||
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
|
</div>
|
||||||
min: 4,
|
<div className="col-sm-4">
|
||||||
disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug),
|
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
|
||||||
...disableShortCodeLength && {
|
min: 4,
|
||||||
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
|
disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug),
|
||||||
},
|
...disableShortCodeLength && {
|
||||||
})}
|
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
|
||||||
</div>
|
},
|
||||||
<div className="col-sm-4">
|
})}
|
||||||
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text', {
|
</div>
|
||||||
disabled: disableDomain,
|
<div className="col-sm-4">
|
||||||
...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
|
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text', {
|
||||||
})}
|
disabled: disableDomain,
|
||||||
{showDomainSelector && (
|
...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
|
||||||
<FormGroup>
|
})}
|
||||||
<DomainSelector
|
{showDomainSelector && (
|
||||||
value={shortUrlCreation.domain}
|
<FormGroup>
|
||||||
onChange={(domain?: string) => setShortUrlCreation({ ...shortUrlCreation, domain })}
|
<DomainSelector
|
||||||
/>
|
value={shortUrlCreation.domain}
|
||||||
</FormGroup>
|
onChange={(domain?: string) => setShortUrlCreation({ ...shortUrlCreation, domain })}
|
||||||
)}
|
/>
|
||||||
</div>
|
</FormGroup>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
<div className="row">
|
|
||||||
<div className="col-sm-4">
|
|
||||||
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-4">
|
|
||||||
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })}
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-4">
|
|
||||||
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ForServerVersion minVersion="1.16.0">
|
|
||||||
<div className="mb-4 row">
|
|
||||||
<div className="col-sm-6 text-center text-sm-left mb-2 mb-sm-0">
|
|
||||||
<ForServerVersion minVersion="2.4.0">
|
|
||||||
<Checkbox
|
|
||||||
inline
|
|
||||||
checked={shortUrlCreation.validateUrl}
|
|
||||||
onChange={(validateUrl) => setShortUrlCreation({ ...shortUrlCreation, validateUrl })}
|
|
||||||
>
|
|
||||||
Validate URL
|
|
||||||
</Checkbox>
|
|
||||||
</ForServerVersion>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6 text-center text-sm-right">
|
|
||||||
<Checkbox
|
<div className="row">
|
||||||
inline
|
<div className="col-sm-4">
|
||||||
className="mr-2"
|
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
|
||||||
checked={shortUrlCreation.findIfExists}
|
</div>
|
||||||
onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
|
<div className="col-sm-4">
|
||||||
>
|
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })}
|
||||||
Use existing URL if found
|
</div>
|
||||||
</Checkbox>
|
<div className="col-sm-4">
|
||||||
<UseExistingIfFoundInfoIcon />
|
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</ForServerVersion>
|
<ForServerVersion minVersion="1.16.0">
|
||||||
|
<div className="mb-4 row">
|
||||||
|
<div className="col-sm-6 text-center text-sm-left mb-2 mb-sm-0">
|
||||||
|
<ForServerVersion minVersion="2.4.0">
|
||||||
|
<Checkbox
|
||||||
|
inline
|
||||||
|
checked={shortUrlCreation.validateUrl}
|
||||||
|
onChange={(validateUrl) => setShortUrlCreation({ ...shortUrlCreation, validateUrl })}
|
||||||
|
>
|
||||||
|
Validate URL
|
||||||
|
</Checkbox>
|
||||||
|
</ForServerVersion>
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-6 text-center text-sm-right">
|
||||||
|
<Checkbox
|
||||||
|
inline
|
||||||
|
className="mr-2"
|
||||||
|
checked={shortUrlCreation.findIfExists}
|
||||||
|
onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
|
||||||
|
>
|
||||||
|
Use existing URL if found
|
||||||
|
</Checkbox>
|
||||||
|
<UseExistingIfFoundInfoIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ForServerVersion>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||||||
bottle.serviceFactory('ShortUrls', ShortUrls, 'SearchBar', 'ShortUrlsList');
|
bottle.serviceFactory('ShortUrls', ShortUrls, 'SearchBar', 'ShortUrlsList');
|
||||||
bottle.decorator('ShortUrls', connect([ 'shortUrlsList' ]));
|
bottle.decorator('ShortUrls', connect([ 'shortUrlsList' ]));
|
||||||
|
|
||||||
// Services
|
|
||||||
bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator', 'ForServerVersion');
|
|
||||||
bottle.decorator('SearchBar', connect([ 'shortUrlsListParams' ], [ 'listShortUrls' ]));
|
|
||||||
|
|
||||||
bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsTable');
|
bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsTable');
|
||||||
bottle.decorator('ShortUrlsList', connect(
|
bottle.decorator('ShortUrlsList', connect(
|
||||||
[ 'selectedServer', 'shortUrlsListParams', 'mercureInfo' ],
|
[ 'selectedServer', 'shortUrlsListParams', 'mercureInfo' ],
|
||||||
@@ -73,6 +69,10 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||||||
bottle.serviceFactory('EditShortUrlModal', () => EditShortUrlModal);
|
bottle.serviceFactory('EditShortUrlModal', () => EditShortUrlModal);
|
||||||
bottle.decorator('EditShortUrlModal', connect([ 'shortUrlEdition' ], [ 'editShortUrl' ]));
|
bottle.decorator('EditShortUrlModal', connect([ 'shortUrlEdition' ], [ 'editShortUrl' ]));
|
||||||
|
|
||||||
|
// Services
|
||||||
|
bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator', 'ForServerVersion');
|
||||||
|
bottle.decorator('SearchBar', connect([ 'shortUrlsListParams' ], [ 'listShortUrls' ]));
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
bottle.serviceFactory('editShortUrlTags', editShortUrlTags, 'buildShlinkApiClient');
|
bottle.serviceFactory('editShortUrlTags', editShortUrlTags, 'buildShlinkApiClient');
|
||||||
bottle.serviceFactory('resetShortUrlsTags', () => resetShortUrlsTags);
|
bottle.serviceFactory('resetShortUrlsTags', () => resetShortUrlsTags);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { shallow, ShallowWrapper } from 'enzyme';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { identity } from 'ramda';
|
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 DateInput from '../../src/utils/DateInput';
|
||||||
import { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation';
|
import { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation';
|
||||||
@@ -31,7 +32,7 @@ describe('<CreateShortUrl />', () => {
|
|||||||
const validSince = moment('2017-01-01');
|
const validSince = moment('2017-01-01');
|
||||||
const validUntil = moment('2017-01-06');
|
const validUntil = moment('2017-01-06');
|
||||||
|
|
||||||
wrapper.find('.form-control-lg').simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } });
|
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('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]);
|
||||||
wrapper.find('#customSlug').simulate('change', { target: { value: 'my-slug' } });
|
wrapper.find('#customSlug').simulate('change', { target: { value: 'my-slug' } });
|
||||||
wrapper.find('#domain').simulate('change', { target: { value: 'example.com' } });
|
wrapper.find('#domain').simulate('change', { target: { value: 'example.com' } });
|
||||||
|
|||||||
Reference in New Issue
Block a user