mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-03 23:07:26 +03:00
Created SimpleCard component to reduce duplicated code when rendering cards
This commit is contained in:
parent
e60d241fcf
commit
2017ee7456
4 changed files with 126 additions and 93 deletions
|
@ -1,6 +1,7 @@
|
||||||
import { Card, CardBody, CardHeader, FormGroup, Input } from 'reactstrap';
|
import { FormGroup, Input } from 'reactstrap';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import ToggleSwitch from '../utils/ToggleSwitch';
|
import ToggleSwitch from '../utils/ToggleSwitch';
|
||||||
|
import { SimpleCard } from '../utils/SimpleCard';
|
||||||
import { Settings } from './reducers/settings';
|
import { Settings } from './reducers/settings';
|
||||||
|
|
||||||
interface RealTimeUpdatesProps {
|
interface RealTimeUpdatesProps {
|
||||||
|
@ -14,39 +15,36 @@ const intervalValue = (interval?: number) => !interval ? '' : `${interval}`;
|
||||||
const RealTimeUpdates = (
|
const RealTimeUpdates = (
|
||||||
{ settings: { realTimeUpdates }, toggleRealTimeUpdates, setRealTimeUpdatesInterval }: RealTimeUpdatesProps,
|
{ settings: { realTimeUpdates }, toggleRealTimeUpdates, setRealTimeUpdatesInterval }: RealTimeUpdatesProps,
|
||||||
) => (
|
) => (
|
||||||
<Card>
|
<SimpleCard title="Real-time updates">
|
||||||
<CardHeader>Real-time updates</CardHeader>
|
<FormGroup>
|
||||||
<CardBody>
|
<ToggleSwitch checked={realTimeUpdates.enabled} onChange={toggleRealTimeUpdates}>
|
||||||
<FormGroup>
|
Enable or disable real-time updates, when using Shlink v2.2.0 or newer.
|
||||||
<ToggleSwitch checked={realTimeUpdates.enabled} onChange={toggleRealTimeUpdates}>
|
</ToggleSwitch>
|
||||||
Enable or disable real-time updates, when using Shlink v2.2.0 or newer.
|
</FormGroup>
|
||||||
</ToggleSwitch>
|
<FormGroup className="mb-0">
|
||||||
</FormGroup>
|
<label className={classNames({ 'text-muted': !realTimeUpdates.enabled })}>
|
||||||
<FormGroup className="mb-0">
|
Real-time updates frequency (in minutes):
|
||||||
<label className={classNames({ 'text-muted': !realTimeUpdates.enabled })}>
|
</label>
|
||||||
Real-time updates frequency (in minutes):
|
<Input
|
||||||
</label>
|
type="number"
|
||||||
<Input
|
min={0}
|
||||||
type="number"
|
placeholder="Immediate"
|
||||||
min={0}
|
disabled={!realTimeUpdates.enabled}
|
||||||
placeholder="Immediate"
|
value={intervalValue(realTimeUpdates.interval)}
|
||||||
disabled={!realTimeUpdates.enabled}
|
onChange={(e) => setRealTimeUpdatesInterval(Number(e.target.value))}
|
||||||
value={intervalValue(realTimeUpdates.interval)}
|
/>
|
||||||
onChange={(e) => setRealTimeUpdatesInterval(Number(e.target.value))}
|
{realTimeUpdates.enabled && (
|
||||||
/>
|
<small className="form-text text-muted">
|
||||||
{realTimeUpdates.enabled && (
|
{realTimeUpdates.interval !== undefined && realTimeUpdates.interval > 0 && (
|
||||||
<small className="form-text text-muted">
|
<span>
|
||||||
{realTimeUpdates.interval !== undefined && realTimeUpdates.interval > 0 && (
|
Updates will be reflected in the UI every <b>{realTimeUpdates.interval}</b> minute{realTimeUpdates.interval > 1 && 's'}.
|
||||||
<span>
|
</span>
|
||||||
Updates will be reflected in the UI every <b>{realTimeUpdates.interval}</b> minute{realTimeUpdates.interval > 1 && 's'}.
|
)}
|
||||||
</span>
|
{!realTimeUpdates.interval && 'Updates will be reflected in the UI as soon as they happen.'}
|
||||||
)}
|
</small>
|
||||||
{!realTimeUpdates.interval && 'Updates will be reflected in the UI as soon as they happen.'}
|
)}
|
||||||
</small>
|
</FormGroup>
|
||||||
)}
|
</SimpleCard>
|
||||||
</FormGroup>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default RealTimeUpdates;
|
export default RealTimeUpdates;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { isEmpty, pipe, replace, trim } from 'ramda';
|
import { isEmpty, pipe, replace, trim } from 'ramda';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { Button, Card, CardBody, CardHeader, FormGroup, Input } from 'reactstrap';
|
import { Button, FormGroup, Input } from 'reactstrap';
|
||||||
import { InputType } from 'reactstrap/lib/Input';
|
import { InputType } from 'reactstrap/lib/Input';
|
||||||
import * as m from 'moment';
|
import * as m from 'moment';
|
||||||
import DateInput, { DateInputProps } from '../utils/DateInput';
|
import DateInput, { DateInputProps } from '../utils/DateInput';
|
||||||
|
@ -11,6 +11,7 @@ import { isReachableServer, SelectedServer } from '../servers/data';
|
||||||
import { formatIsoDate } from '../utils/helpers/date';
|
import { formatIsoDate } from '../utils/helpers/date';
|
||||||
import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
|
import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
|
||||||
import { DomainSelectorProps } from '../domains/DomainSelector';
|
import { DomainSelectorProps } from '../domains/DomainSelector';
|
||||||
|
import { SimpleCard } from '../utils/SimpleCard';
|
||||||
import { ShortUrlData } from './data';
|
import { ShortUrlData } from './data';
|
||||||
import { ShortUrlCreation } from './reducers/shortUrlCreation';
|
import { ShortUrlCreation } from './reducers/shortUrlCreation';
|
||||||
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
|
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
|
||||||
|
@ -121,55 +122,48 @@ const CreateShortUrl = (
|
||||||
<>
|
<>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-6 mb-3">
|
<div className="col-sm-6 mb-3">
|
||||||
<Card>
|
<SimpleCard title="Customize the short URL">
|
||||||
<CardHeader>Customize the short URL</CardHeader>
|
<p>
|
||||||
<CardBody>
|
Use a custom slug for your marketing campaigns, change the domain or set a specific length for
|
||||||
<p>
|
the auto-generated short code.
|
||||||
Use a custom slug for your marketing campaigns, change the domain or set a specific length for
|
</p>
|
||||||
the auto-generated short code.
|
{renderOptionalInput('customSlug', 'Custom slug', 'text', {
|
||||||
</p>
|
disabled: hasValue(shortUrlCreation.shortCodeLength),
|
||||||
{renderOptionalInput('customSlug', 'Custom slug', 'text', {
|
})}
|
||||||
disabled: hasValue(shortUrlCreation.shortCodeLength),
|
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
|
||||||
})}
|
min: 4,
|
||||||
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
|
disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug),
|
||||||
min: 4,
|
...disableShortCodeLength && {
|
||||||
disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug),
|
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
|
||||||
...disableShortCodeLength && {
|
},
|
||||||
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
|
})}
|
||||||
},
|
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text', {
|
||||||
})}
|
disabled: disableDomain,
|
||||||
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text', {
|
...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
|
||||||
disabled: disableDomain,
|
})}
|
||||||
...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
|
{showDomainSelector && (
|
||||||
})}
|
<FormGroup>
|
||||||
{showDomainSelector && (
|
<DomainSelector
|
||||||
<FormGroup>
|
value={shortUrlCreation.domain}
|
||||||
<DomainSelector
|
onChange={(domain?: string) => setShortUrlCreation({ ...shortUrlCreation, domain })}
|
||||||
value={shortUrlCreation.domain}
|
/>
|
||||||
onChange={(domain?: string) => setShortUrlCreation({ ...shortUrlCreation, domain })}
|
</FormGroup>
|
||||||
/>
|
)}
|
||||||
</FormGroup>
|
</SimpleCard>
|
||||||
)}
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6 mb-3">
|
<div className="col-sm-6 mb-3">
|
||||||
<Card>
|
<SimpleCard title="Limit access to the short URL">
|
||||||
<CardHeader>Limit access to the short URL</CardHeader>
|
<p>Determine when and how many times your short URL can be accessed.</p>
|
||||||
<CardBody>
|
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
|
||||||
<p>Determine when and how many times your short URL can be accessed.</p>
|
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })}
|
||||||
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
|
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })}
|
||||||
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })}
|
</SimpleCard>
|
||||||
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })}
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="mb-3">
|
<ForServerVersion minVersion="1.16.0">
|
||||||
<CardHeader>Extra validations</CardHeader>
|
<SimpleCard title="Extra validations" className="mb-3">
|
||||||
<CardBody>
|
|
||||||
<p>
|
<p>
|
||||||
Make sure the long URL is valid, or ensure an existing short URL is returned if it matches all
|
Make sure the long URL is valid, or ensure an existing short URL is returned if it matches all
|
||||||
provided data.
|
provided data.
|
||||||
|
@ -185,21 +179,19 @@ const CreateShortUrl = (
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</p>
|
</p>
|
||||||
</ForServerVersion>
|
</ForServerVersion>
|
||||||
<ForServerVersion minVersion="1.16.0">
|
<p>
|
||||||
<p>
|
<Checkbox
|
||||||
<Checkbox
|
inline
|
||||||
inline
|
className="mr-2"
|
||||||
className="mr-2"
|
checked={shortUrlCreation.findIfExists}
|
||||||
checked={shortUrlCreation.findIfExists}
|
onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
|
||||||
onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
|
>
|
||||||
>
|
Use existing URL if found
|
||||||
Use existing URL if found
|
</Checkbox>
|
||||||
</Checkbox>
|
<UseExistingIfFoundInfoIcon />
|
||||||
<UseExistingIfFoundInfoIcon />
|
</p>
|
||||||
</p>
|
</SimpleCard>
|
||||||
</ForServerVersion>
|
</ForServerVersion>
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
13
src/utils/SimpleCard.tsx
Normal file
13
src/utils/SimpleCard.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { CardProps } from 'reactstrap/lib/Card';
|
||||||
|
import { Card, CardBody, CardHeader } from 'reactstrap';
|
||||||
|
|
||||||
|
interface SimpleCardProps extends CardProps {
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SimpleCard = ({ title, children, ...rest }: SimpleCardProps) => (
|
||||||
|
<Card {...rest}>
|
||||||
|
{title && <CardHeader>{title}</CardHeader>}
|
||||||
|
<CardBody>{children}</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
30
test/utils/SimpleCard.test.tsx
Normal file
30
test/utils/SimpleCard.test.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { Card, CardBody, CardHeader } from 'reactstrap';
|
||||||
|
import { SimpleCard } from '../../src/utils/SimpleCard';
|
||||||
|
|
||||||
|
describe('<SimpleCard />', () => {
|
||||||
|
it.each([
|
||||||
|
[{}, 0 ],
|
||||||
|
[{ title: 'Cool title' }, 1 ],
|
||||||
|
])('renders header only if title is provided', (props, expectedAmountOfHeaders) => {
|
||||||
|
const wrapper = shallow(<SimpleCard {...props} />);
|
||||||
|
|
||||||
|
expect(wrapper.find(CardHeader)).toHaveLength(expectedAmountOfHeaders);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders children inside body', () => {
|
||||||
|
const wrapper = shallow(<SimpleCard>Hello world</SimpleCard>);
|
||||||
|
const body = wrapper.find(CardBody);
|
||||||
|
|
||||||
|
expect(body).toHaveLength(1);
|
||||||
|
expect(body.html()).toContain('Hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes extra props to nested card', () => {
|
||||||
|
const wrapper = shallow(<SimpleCard className="foo" color="primary">Hello world</SimpleCard>);
|
||||||
|
const card = wrapper.find(Card);
|
||||||
|
|
||||||
|
expect(card.prop('className')).toEqual('foo');
|
||||||
|
expect(card.prop('color')).toEqual('primary');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue