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 = (
|
+
+
+ |
@@ -54,11 +55,6 @@ export const ShortUrlsRow = (
className="responsive-table__cell short-urls-row__cell short-urls-row__cell--break"
data-th={`${shortUrl.title ? 'Title' : 'Long URL'}`}
>
- {isDisabled && (
-
-
-
- )}
{shortUrl.title ?? shortUrl.longUrl}
|
{shortUrl.title && (
diff --git a/src/short-urls/helpers/index.ts b/src/short-urls/helpers/index.ts
index b2348f52..e4dd1f09 100644
--- a/src/short-urls/helpers/index.ts
+++ b/src/short-urls/helpers/index.ts
@@ -3,8 +3,6 @@ import { ShortUrl, ShortUrlData } from '../data';
import { OptionalString } from '../../utils/utils';
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
import { ShortUrlCreationSettings } from '../../settings/reducers/settings';
-import { isBefore, parseISO } from 'date-fns';
-import { now } from '../../utils/helpers/date';
export const shortUrlMatches = (shortUrl: ShortUrl, shortCode: string, domain: OptionalString): boolean => {
if (isNil(domain)) {
@@ -22,14 +20,6 @@ export const domainMatches = (shortUrl: ShortUrl, domain: string): boolean => {
return shortUrl.domain === domain;
};
-export const shortUrlIsDisabled = (shortUrl: ShortUrl): boolean => (
- !!shortUrl.meta.maxVisits && shortUrl.visitsCount >= shortUrl.meta.maxVisits
-) || (
- !!shortUrl.meta.validSince && isBefore(now(), parseISO(shortUrl.meta.validSince))
-) || (
- !!shortUrl.meta.validUntil && isBefore(parseISO(shortUrl.meta.validUntil), now())
-);
-
export const shortUrlDataFromShortUrl = (shortUrl?: ShortUrl, settings?: ShortUrlCreationSettings): ShortUrlData => {
const validateUrl = settings?.validateUrls ?? false;