From 2017ee7456bcc88ea687bcbe694cbe5d1eaf4e06 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 8 Dec 2020 19:10:29 +0100 Subject: [PATCH] Created SimpleCard component to reduce duplicated code when rendering cards --- src/settings/RealTimeUpdates.tsx | 66 +++++++++--------- src/short-urls/CreateShortUrl.tsx | 110 ++++++++++++++---------------- src/utils/SimpleCard.tsx | 13 ++++ test/utils/SimpleCard.test.tsx | 30 ++++++++ 4 files changed, 126 insertions(+), 93 deletions(-) create mode 100644 src/utils/SimpleCard.tsx create mode 100644 test/utils/SimpleCard.test.tsx diff --git a/src/settings/RealTimeUpdates.tsx b/src/settings/RealTimeUpdates.tsx index b5281bb3..914c5a6f 100644 --- a/src/settings/RealTimeUpdates.tsx +++ b/src/settings/RealTimeUpdates.tsx @@ -1,6 +1,7 @@ -import { Card, CardBody, CardHeader, FormGroup, Input } from 'reactstrap'; +import { FormGroup, Input } from 'reactstrap'; import classNames from 'classnames'; import ToggleSwitch from '../utils/ToggleSwitch'; +import { SimpleCard } from '../utils/SimpleCard'; import { Settings } from './reducers/settings'; interface RealTimeUpdatesProps { @@ -14,39 +15,36 @@ const intervalValue = (interval?: number) => !interval ? '' : `${interval}`; const RealTimeUpdates = ( { settings: { realTimeUpdates }, toggleRealTimeUpdates, setRealTimeUpdatesInterval }: RealTimeUpdatesProps, ) => ( - - Real-time updates - - - - Enable or disable real-time updates, when using Shlink v2.2.0 or newer. - - - - - setRealTimeUpdatesInterval(Number(e.target.value))} - /> - {realTimeUpdates.enabled && ( - - {realTimeUpdates.interval !== undefined && realTimeUpdates.interval > 0 && ( - - Updates will be reflected in the UI every {realTimeUpdates.interval} minute{realTimeUpdates.interval > 1 && 's'}. - - )} - {!realTimeUpdates.interval && 'Updates will be reflected in the UI as soon as they happen.'} - - )} - - - + + + + Enable or disable real-time updates, when using Shlink v2.2.0 or newer. + + + + + setRealTimeUpdatesInterval(Number(e.target.value))} + /> + {realTimeUpdates.enabled && ( + + {realTimeUpdates.interval !== undefined && realTimeUpdates.interval > 0 && ( + + Updates will be reflected in the UI every {realTimeUpdates.interval} minute{realTimeUpdates.interval > 1 && 's'}. + + )} + {!realTimeUpdates.interval && 'Updates will be reflected in the UI as soon as they happen.'} + + )} + + ); export default RealTimeUpdates; diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx index 2df2978b..d3abc7c9 100644 --- a/src/short-urls/CreateShortUrl.tsx +++ b/src/short-urls/CreateShortUrl.tsx @@ -1,6 +1,6 @@ import { isEmpty, pipe, replace, trim } from 'ramda'; 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 * as m from 'moment'; import DateInput, { DateInputProps } from '../utils/DateInput'; @@ -11,6 +11,7 @@ import { isReachableServer, 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 { ShortUrlData } from './data'; import { ShortUrlCreation } from './reducers/shortUrlCreation'; import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; @@ -121,55 +122,48 @@ const CreateShortUrl = ( <>
- - Customize the short URL - -

- Use a custom slug for your marketing campaigns, change the domain or set a specific length for - the auto-generated short code. -

- {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', { - disabled: disableDomain, - ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' }, - })} - {showDomainSelector && ( - - setShortUrlCreation({ ...shortUrlCreation, domain })} - /> - - )} -
-
+ +

+ Use a custom slug for your marketing campaigns, change the domain or set a specific length for + the auto-generated short code. +

+ {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', { + disabled: disableDomain, + ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' }, + })} + {showDomainSelector && ( + + setShortUrlCreation({ ...shortUrlCreation, domain })} + /> + + )} +
- - Limit access to the short URL - -

Determine when and how many times your short URL can be accessed.

- {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 })} -
-
+ +

Determine when and how many times your short URL can be accessed.

+ {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 })} +
- - Extra validations - + +

Make sure the long URL is valid, or ensure an existing short URL is returned if it matches all provided data. @@ -185,21 +179,19 @@ const CreateShortUrl = (

- -

- setShortUrlCreation({ ...shortUrlCreation, findIfExists })} - > - Use existing URL if found - - -

-
-
-
+

+ setShortUrlCreation({ ...shortUrlCreation, findIfExists })} + > + Use existing URL if found + + +

+ + )} diff --git a/src/utils/SimpleCard.tsx b/src/utils/SimpleCard.tsx new file mode 100644 index 00000000..ccee2de2 --- /dev/null +++ b/src/utils/SimpleCard.tsx @@ -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) => ( + + {title && {title}} + {children} + +); diff --git a/test/utils/SimpleCard.test.tsx b/test/utils/SimpleCard.test.tsx new file mode 100644 index 00000000..776cff07 --- /dev/null +++ b/test/utils/SimpleCard.test.tsx @@ -0,0 +1,30 @@ +import { shallow } from 'enzyme'; +import { Card, CardBody, CardHeader } from 'reactstrap'; +import { SimpleCard } from '../../src/utils/SimpleCard'; + +describe('', () => { + it.each([ + [{}, 0 ], + [{ title: 'Cool title' }, 1 ], + ])('renders header only if title is provided', (props, expectedAmountOfHeaders) => { + const wrapper = shallow(); + + expect(wrapper.find(CardHeader)).toHaveLength(expectedAmountOfHeaders); + }); + + it('renders children inside body', () => { + const wrapper = shallow(Hello world); + 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(Hello world); + const card = wrapper.find(Card); + + expect(card.prop('className')).toEqual('foo'); + expect(card.prop('color')).toEqual('primary'); + }); +});