From a28a4846bca597090a02aa350d8d13dffcddab18 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 20 Aug 2021 17:30:07 +0200 Subject: [PATCH] Created base structure to manage domains --- src/api/types/index.ts | 7 ++ src/common/AsideMenu.tsx | 27 +++++--- src/common/MenuLayout.tsx | 5 +- src/common/services/provideServices.ts | 1 + src/domains/ManageDomains.tsx | 92 +++++++++++++++++++++++++ src/domains/services/provideServices.ts | 4 ++ src/mercure/helpers/Topics.ts | 6 +- src/servers/Overview.tsx | 2 +- src/short-urls/ShortUrlsList.tsx | 2 +- src/tags/TagsList.tsx | 7 +- src/utils/helpers/features.ts | 2 + src/visits/OrphanVisits.tsx | 2 +- src/visits/TagVisits.tsx | 2 +- 13 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 src/domains/ManageDomains.tsx diff --git a/src/api/types/index.ts b/src/api/types/index.ts index acd0d4f7..9d75eaf5 100644 --- a/src/api/types/index.ts +++ b/src/api/types/index.ts @@ -65,9 +65,16 @@ export interface ShlinkShortUrlData extends ShortUrlMeta { tags?: string[]; } +interface ShlinkDomainRedirects { + baseUrlRedirect: string, + regular404Redirect: string, + invalidShortUrlRedirect: string +} + export interface ShlinkDomain { domain: string; isDefault: boolean; + redirects?: ShlinkDomainRedirects; // Optional only for Shlink older than 2.8 } export interface ShlinkDomainsResponse { diff --git a/src/common/AsideMenu.tsx b/src/common/AsideMenu.tsx index 3d9fd4b8..4f7ec14a 100644 --- a/src/common/AsideMenu.tsx +++ b/src/common/AsideMenu.tsx @@ -4,6 +4,7 @@ import { faTags as tagsIcon, faPen as editIcon, faHome as overviewIcon, + faGlobe as domainsIcon, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC } from 'react'; @@ -11,11 +12,12 @@ import { NavLink, NavLinkProps } from 'react-router-dom'; import classNames from 'classnames'; import { Location } from 'history'; import { DeleteServerButtonProps } from '../servers/DeleteServerButton'; -import { ServerWithId } from '../servers/data'; +import { isServerWithId, SelectedServer } from '../servers/data'; +import { supportsDomainRedirects } from '../utils/helpers/features'; import './AsideMenu.scss'; export interface AsideMenuProps { - selectedServer: ServerWithId; + selectedServer: SelectedServer; className?: string; showOnMobile?: boolean; } @@ -38,7 +40,8 @@ const AsideMenuItem: FC = ({ children, to, className, ...res const AsideMenu = (DeleteServerButton: FC) => ( { selectedServer, showOnMobile = false }: AsideMenuProps, ) => { - const serverId = selectedServer ? selectedServer.id : ''; + const serverId = isServerWithId(selectedServer) ? selectedServer.id : ''; + const addManageDomainsLink = supportsDomainRedirects(selectedServer); const asideClass = classNames('aside-menu', { 'aside-menu--hidden': !showOnMobile, }); @@ -64,15 +67,23 @@ const AsideMenu = (DeleteServerButton: FC) => ( Manage tags + {addManageDomainsLink && ( + + + Manage domains + + )} Edit this server - + {isServerWithId(selectedServer) && ( + + )} ); diff --git a/src/common/MenuLayout.tsx b/src/common/MenuLayout.tsx index 669b2197..871a0f97 100644 --- a/src/common/MenuLayout.tsx +++ b/src/common/MenuLayout.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { useSwipeable, useToggle } from '../utils/helpers/hooks'; -import { supportsOrphanVisits, supportsTagVisits } from '../utils/helpers/features'; +import { supportsDomainRedirects, supportsOrphanVisits, supportsTagVisits } from '../utils/helpers/features'; import { isReachableServer } from '../servers/data'; import NotFound from './NotFound'; import { AsideMenuProps } from './AsideMenu'; @@ -22,6 +22,7 @@ const MenuLayout = ( ServerError: FC, Overview: FC, EditShortUrl: FC, + ManageDomains: FC, ) => withSelectedServer(({ location, selectedServer }) => { const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle(); @@ -33,6 +34,7 @@ const MenuLayout = ( const addTagsVisitsRoute = supportsTagVisits(selectedServer); const addOrphanVisitsRoute = supportsOrphanVisits(selectedServer); + const addManageDomainsRoute = supportsDomainRedirects(selectedServer); const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible }); const swipeableProps = useSwipeable(showSidebar, hideSidebar); @@ -55,6 +57,7 @@ const MenuLayout = ( {addTagsVisitsRoute && } {addOrphanVisitsRoute && } + {addManageDomainsRoute && } List short URLs} /> diff --git a/src/common/services/provideServices.ts b/src/common/services/provideServices.ts index a7a71139..2abbe688 100644 --- a/src/common/services/provideServices.ts +++ b/src/common/services/provideServices.ts @@ -43,6 +43,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: 'ServerError', 'Overview', 'EditShortUrl', + 'ManageDomains', ); bottle.decorator('MenuLayout', connect([ 'selectedServer', 'shortUrlsListParams' ], [ 'selectServer' ])); bottle.decorator('MenuLayout', withRouter); diff --git a/src/domains/ManageDomains.tsx b/src/domains/ManageDomains.tsx new file mode 100644 index 00000000..5d392cc5 --- /dev/null +++ b/src/domains/ManageDomains.tsx @@ -0,0 +1,92 @@ +import { FC, useEffect } from 'react'; +import { faCheck as defaultDomainIcon, faEdit as editIcon, faBan as forbiddenIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Button, UncontrolledTooltip } from 'reactstrap'; +import Message from '../utils/Message'; +import { Result } from '../utils/Result'; +import { ShlinkApiError } from '../api/ShlinkApiError'; +import { SimpleCard } from '../utils/SimpleCard'; +import { DomainsList } from './reducers/domainsList'; +import SearchField from '../utils/SearchField'; + +interface ManageDomainsProps { + listDomains: Function; + domainsList: DomainsList; +} + +const Na: FC = () => N/A; +const DefaultDomain: FC = () => ( + <> + + Default domain + +); + +export const ManageDomains: FC = ({ listDomains, domainsList }) => { + const { domains, loading, error } = domainsList; + + useEffect(() => { + listDomains(); + }, []); + + const renderContent = () => { + if (loading) { + return ; + } + + if (error) { + return ( + + + + ); + } + + return ( + + + + + + + + + + + + {domains.map((domain) => ( + + + + + + + + + ))} + +
+ DomainBase path redirectRegular 404 redirectInvalid short URL redirect +
{domain.isDefault ? : ''}{domain.domain}{domain.redirects?.baseUrlRedirect ?? }{domain.redirects?.regular404Redirect ?? }{domain.redirects?.invalidShortUrlRedirect ?? } + + + + {domain.isDefault && ( + + Redirects for default domain cannot be edited here. + + )} +
+
+ ); + }; + + return ( + <> + {}} /> + {renderContent()} + + ); +}; diff --git a/src/domains/services/provideServices.ts b/src/domains/services/provideServices.ts index bd56d8a2..bf90cc7a 100644 --- a/src/domains/services/provideServices.ts +++ b/src/domains/services/provideServices.ts @@ -2,12 +2,16 @@ import Bottle from 'bottlejs'; import { ConnectDecorator } from '../../container/types'; import { listDomains } from '../reducers/domainsList'; import { DomainSelector } from '../DomainSelector'; +import { ManageDomains } from '../ManageDomains'; const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('DomainSelector', () => DomainSelector); bottle.decorator('DomainSelector', connect([ 'domainsList' ], [ 'listDomains' ])); + bottle.serviceFactory('ManageDomains', () => ManageDomains); + bottle.decorator('ManageDomains', connect([ 'domainsList' ], [ 'listDomains' ])); + // Actions bottle.serviceFactory('listDomains', listDomains, 'buildShlinkApiClient'); }; diff --git a/src/mercure/helpers/Topics.ts b/src/mercure/helpers/Topics.ts index 42e08d4f..663cc371 100644 --- a/src/mercure/helpers/Topics.ts +++ b/src/mercure/helpers/Topics.ts @@ -1,7 +1,7 @@ export class Topics { - public static visits = () => 'https://shlink.io/new-visit'; + public static readonly visits = 'https://shlink.io/new-visit'; - public static shortUrlVisits = (shortCode: string) => `https://shlink.io/new-visit/${shortCode}`; + public static readonly orphanVisits = 'https://shlink.io/new-orphan-visit'; - public static orphanVisits = () => 'https://shlink.io/new-orphan-visit'; + public static readonly shortUrlVisits = (shortCode: string) => `https://shlink.io/new-visit/${shortCode}`; } diff --git a/src/servers/Overview.tsx b/src/servers/Overview.tsx index ea156041..84614b2e 100644 --- a/src/servers/Overview.tsx +++ b/src/servers/Overview.tsx @@ -120,4 +120,4 @@ export const Overview = ( ); -}, () => [ Topics.visits(), Topics.orphanVisits() ]); +}, () => [ Topics.visits, Topics.orphanVisits ]); diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx index 4736bc8c..1cac40b5 100644 --- a/src/short-urls/ShortUrlsList.tsx +++ b/src/short-urls/ShortUrlsList.tsx @@ -99,6 +99,6 @@ const ShortUrlsList = (ShortUrlsTable: FC) => boundToMercur ); -}, () => [ Topics.visits() ]); +}, () => [ Topics.visits ]); export default ShortUrlsList; diff --git a/src/tags/TagsList.tsx b/src/tags/TagsList.tsx index fde6cc5e..a29345b0 100644 --- a/src/tags/TagsList.tsx +++ b/src/tags/TagsList.tsx @@ -1,5 +1,6 @@ import { FC, useEffect, useState } from 'react'; import { splitEvery } from 'ramda'; +import { Row } from 'reactstrap'; import Message from '../utils/Message'; import SearchField from '../utils/SearchField'; import { SelectedServer } from '../servers/data'; @@ -51,7 +52,7 @@ const TagsList = (TagCard: FC) => boundToMercureHub(( const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags); return ( -
+ {tagsGroups.map((group, index) => (
{group.map((tag) => ( @@ -66,7 +67,7 @@ const TagsList = (TagCard: FC) => boundToMercureHub(( ))}
))} -
+ ); }; @@ -76,6 +77,6 @@ const TagsList = (TagCard: FC) => boundToMercureHub(( {renderContent()} ); -}, () => [ Topics.visits() ]); +}, () => [ Topics.visits ]); export default TagsList; diff --git a/src/utils/helpers/features.ts b/src/utils/helpers/features.ts index 609c9e95..68651fcc 100644 --- a/src/utils/helpers/features.ts +++ b/src/utils/helpers/features.ts @@ -29,3 +29,5 @@ export const supportsBotVisits = serverMatchesVersions({ minVersion: '2.7.0' }); export const supportsCrawlableVisits = supportsBotVisits; export const supportsQrErrorCorrection = serverMatchesVersions({ minVersion: '2.8.0' }); + +export const supportsDomainRedirects = supportsQrErrorCorrection; diff --git a/src/visits/OrphanVisits.tsx b/src/visits/OrphanVisits.tsx index 8184e687..e87a93fe 100644 --- a/src/visits/OrphanVisits.tsx +++ b/src/visits/OrphanVisits.tsx @@ -41,4 +41,4 @@ export const OrphanVisits = ({ exportVisits }: VisitsExporter) => boundToMercure ); -}, () => [ Topics.orphanVisits() ]); +}, () => [ Topics.orphanVisits ]); diff --git a/src/visits/TagVisits.tsx b/src/visits/TagVisits.tsx index 4a80519f..d7619a0a 100644 --- a/src/visits/TagVisits.tsx +++ b/src/visits/TagVisits.tsx @@ -43,6 +43,6 @@ const TagVisits = (colorGenerator: ColorGenerator, { exportVisits }: VisitsExpor ); -}, () => [ Topics.visits() ]); +}, () => [ Topics.visits ]); export default TagVisits;