diff --git a/src/short-urls/ShortUrlsTable.tsx b/src/short-urls/ShortUrlsTable.tsx index f0416bfe..a1018e95 100644 --- a/src/short-urls/ShortUrlsTable.tsx +++ b/src/short-urls/ShortUrlsTable.tsx @@ -65,6 +65,7 @@ export const ShortUrlsTable = (ShortUrlsRow: ShortUrlsRowType) => ({ Created at {renderOrderIcon?.('dateCreated')} + Short URL {renderOrderIcon?.('shortCode')} diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts index 88f5b22b..8c22bdec 100644 --- a/src/short-urls/data/index.ts +++ b/src/short-urls/data/index.ts @@ -31,7 +31,9 @@ export interface ShortUrl { shortUrl: string; longUrl: string; dateCreated: string; - visitsCount: number; + /** @deprecated */ + visitsCount: number; // Deprecated since Shlink 3.4.0 + visitsSummary?: ShortUrlVisitsSummary; // Optional only before Shlink 3.4.0 meta: Required>; tags: string[]; domain: string | null; @@ -46,6 +48,12 @@ export interface ShortUrlMeta { maxVisits?: number; } +export interface ShortUrlVisitsSummary { + total: number; + nonBots: number; + bots: number; +} + export interface ShortUrlModalProps { shortUrl: ShortUrl; isOpen: boolean; diff --git a/src/short-urls/helpers/DisabledLabel.tsx b/src/short-urls/helpers/DisabledLabel.tsx deleted file mode 100644 index 90f42597..00000000 --- a/src/short-urls/helpers/DisabledLabel.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { FC, useRef } from 'react'; -import { UncontrolledTooltip } from 'reactstrap'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faLinkSlash } from '@fortawesome/free-solid-svg-icons'; -import { mutableRefToElementRef } from '../../utils/helpers/components'; - -export const DisabledLabel: FC = () => { - const tooltipRef = useRef(); - - return ( - <> - - - Disabled - - tooltipRef.current) as any} placement="left"> - This short URL cannot be currently visited because of some of its limits. - - - ); -}; diff --git a/src/short-urls/helpers/ShortUrlStatus.tsx b/src/short-urls/helpers/ShortUrlStatus.tsx new file mode 100644 index 00000000..82e61b65 --- /dev/null +++ b/src/short-urls/helpers/ShortUrlStatus.tsx @@ -0,0 +1,91 @@ +import { FC, ReactNode, useRef } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { IconDefinition } from '@fortawesome/fontawesome-common-types'; +import { faLinkSlash, faCalendarXmark, faCheck } from '@fortawesome/free-solid-svg-icons'; +import { UncontrolledTooltip } from 'reactstrap'; +import { isBefore } from 'date-fns'; +import { mutableRefToElementRef } from '../../utils/helpers/components'; +import { ShortUrl } from '../data'; +import { formatHumanFriendly, now, parseISO } from '../../utils/helpers/date'; + +interface ShortUrlStatusProps { + shortUrl: ShortUrl; +} + +interface StatusResult { + icon: IconDefinition; + className: string; + description?: ReactNode; +} + +const resolveShortUrlStatus = (shortUrl: ShortUrl): StatusResult => { + const { meta, visitsCount, visitsSummary } = shortUrl; + const { maxVisits, validSince, validUntil } = meta; + const totalVisits = visitsSummary?.total ?? visitsCount; + + if (maxVisits && totalVisits >= maxVisits) { + return { + icon: faLinkSlash, + className: 'text-danger', + description: ( + <> + This short URL cannot be currently visited because it has reached the maximum + amount of {maxVisits} visit{maxVisits > 1 ? 's' : ''}. + + ), + }; + } + + if (validUntil && isBefore(parseISO(validUntil), now())) { + return { + icon: faCalendarXmark, + className: 'text-danger', + description: ( + <> + This short URL cannot be visited + since {formatHumanFriendly(parseISO(validUntil))}. + + ), + }; + } + + if (validSince && isBefore(now(), parseISO(validSince))) { + return { + icon: faCalendarXmark, + className: 'text-warning', + description: ( + <> + This short URL will start working + on {formatHumanFriendly(parseISO(validSince))}. + + ), + }; + } + + return { + icon: faCheck, + className: 'text-primary', + }; +}; + +export const ShortUrlStatus: FC = ({ shortUrl }) => { + const tooltipRef = useRef(); + const { icon, className, description } = resolveShortUrlStatus(shortUrl); + + return ( + <> + + + + {description && ( + tooltipRef.current) as any} placement="right"> + {description} + + )} + + ); +}; diff --git a/src/short-urls/helpers/ShortUrlsRow.tsx b/src/short-urls/helpers/ShortUrlsRow.tsx index adc7569d..b97b3c57 100644 --- a/src/short-urls/helpers/ShortUrlsRow.tsx +++ b/src/short-urls/helpers/ShortUrlsRow.tsx @@ -9,8 +9,7 @@ import { Time } from '../../utils/dates/Time'; import { ShortUrlVisitsCount } from './ShortUrlVisitsCount'; import { ShortUrlsRowMenuType } from './ShortUrlsRowMenu'; import { Tags } from './Tags'; -import { shortUrlIsDisabled } from './index'; -import { DisabledLabel } from './DisabledLabel'; +import { ShortUrlStatus } from './ShortUrlStatus'; import './ShortUrlsRow.scss'; interface ShortUrlsRowProps { @@ -27,7 +26,6 @@ export const ShortUrlsRow = ( const [copiedToClipboard, setCopiedToClipboard] = useTimeoutToggle(); const [active, setActive] = useTimeoutToggle(false, 500); const isFirstRun = useRef(true); - const isDisabled = shortUrlIsDisabled(shortUrl); useEffect(() => { !isFirstRun.current && setActive(); @@ -39,6 +37,9 @@ export const ShortUrlsRow = (