mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Replaced most of the usages of moment with date-fns
This commit is contained in:
parent
ee65c0c050
commit
4be1a295d8
21 changed files with 124 additions and 119 deletions
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -12082,9 +12082,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"date-fns": {
|
"date-fns": {
|
||||||
"version": "2.16.1",
|
"version": "2.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.22.1.tgz",
|
||||||
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ=="
|
"integrity": "sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg=="
|
||||||
},
|
},
|
||||||
"date-format": {
|
"date-format": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"compare-versions": "^3.6.0",
|
"compare-versions": "^3.6.0",
|
||||||
"csvjson": "^5.1.0",
|
"csvjson": "^5.1.0",
|
||||||
|
"date-fns": "^2.22.1",
|
||||||
"event-source-polyfill": "^1.0.22",
|
"event-source-polyfill": "^1.0.22",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { isEmpty, pipe } from 'ramda';
|
import { isEmpty, pipe } from 'ramda';
|
||||||
import moment from 'moment';
|
import { parseISO } from 'date-fns';
|
||||||
import SearchField from '../utils/SearchField';
|
import SearchField from '../utils/SearchField';
|
||||||
import Tag from '../tags/helpers/Tag';
|
import Tag from '../tags/helpers/Tag';
|
||||||
import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
||||||
|
@ -16,7 +16,7 @@ interface SearchBarProps {
|
||||||
shortUrlsListParams: ShortUrlsListParams;
|
shortUrlsListParams: ShortUrlsListParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateOrNull = (date?: string) => date ? moment(date) : null;
|
const dateOrNull = (date?: string) => date ? parseISO(date) : null;
|
||||||
|
|
||||||
const SearchBar = (colorGenerator: ColorGenerator) => ({ listShortUrls, shortUrlsListParams }: SearchBarProps) => {
|
const SearchBar = (colorGenerator: ColorGenerator) => ({ listShortUrls, shortUrlsListParams }: SearchBarProps) => {
|
||||||
const selectedTags = shortUrlsListParams.tags ?? [];
|
const selectedTags = shortUrlsListParams.tags ?? [];
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { FC, useEffect, useState } from 'react';
|
||||||
import { InputType } from 'reactstrap/lib/Input';
|
import { InputType } from 'reactstrap/lib/Input';
|
||||||
import { Button, FormGroup, Input, Row } from 'reactstrap';
|
import { Button, FormGroup, Input, Row } from 'reactstrap';
|
||||||
import { isEmpty, pipe, replace, trim } from 'ramda';
|
import { isEmpty, pipe, replace, trim } from 'ramda';
|
||||||
import m from 'moment';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { parseISO } from 'date-fns';
|
||||||
import DateInput, { DateInputProps } from '../utils/DateInput';
|
import DateInput, { DateInputProps } from '../utils/DateInput';
|
||||||
import {
|
import {
|
||||||
supportsCrawlableVisits,
|
supportsCrawlableVisits,
|
||||||
|
@ -38,6 +38,7 @@ export interface ShortUrlFormProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
||||||
|
const toDate = (date?: string | Date): Date | undefined => typeof date === 'string' ? parseISO(date) : date;
|
||||||
|
|
||||||
export const ShortUrlForm = (
|
export const ShortUrlForm = (
|
||||||
TagsSelector: FC<TagsSelectorProps>,
|
TagsSelector: FC<TagsSelectorProps>,
|
||||||
|
@ -74,7 +75,7 @@ export const ShortUrlForm = (
|
||||||
const renderDateInput = (id: DateFields, placeholder: string, props: Partial<DateInputProps> = {}) => (
|
const renderDateInput = (id: DateFields, placeholder: string, props: Partial<DateInputProps> = {}) => (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<DateInput
|
<DateInput
|
||||||
selected={shortUrlData[id] ? m(shortUrlData[id]) : null}
|
selected={shortUrlData[id] ? toDate(shortUrlData[id] as string | Date) : null}
|
||||||
placeholderText={placeholder}
|
placeholderText={placeholder}
|
||||||
isClearable
|
isClearable
|
||||||
onChange={(date) => setShortUrlData({ ...shortUrlData, [id]: date })}
|
onChange={(date) => setShortUrlData({ ...shortUrlData, [id]: date })}
|
||||||
|
@ -163,8 +164,8 @@ export const ShortUrlForm = (
|
||||||
<div className={limitAccessCardClasses}>
|
<div className={limitAccessCardClasses}>
|
||||||
<SimpleCard title="Limit access to the short URL">
|
<SimpleCard title="Limit access to the short URL">
|
||||||
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
|
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
|
||||||
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlData.validUntil ? m(shortUrlData.validUntil) : undefined })}
|
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlData.validUntil ? toDate(shortUrlData.validUntil) : undefined })}
|
||||||
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlData.validSince ? m(shortUrlData.validSince) : undefined })}
|
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlData.validSince ? toDate(shortUrlData.validSince) : undefined })}
|
||||||
</SimpleCard>
|
</SimpleCard>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import * as m from 'moment';
|
|
||||||
import { Nullable, OptionalString } from '../../utils/utils';
|
import { Nullable, OptionalString } from '../../utils/utils';
|
||||||
|
|
||||||
export interface EditShortUrlData {
|
export interface EditShortUrlData {
|
||||||
longUrl?: string;
|
longUrl?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
title?: string;
|
title?: string;
|
||||||
validSince?: m.Moment | string | null;
|
validSince?: Date | string | null;
|
||||||
validUntil?: m.Moment | string | null;
|
validUntil?: Date | string | null;
|
||||||
maxVisits?: number | null;
|
maxVisits?: number | null;
|
||||||
validateUrl?: boolean;
|
validateUrl?: boolean;
|
||||||
crawlable?: boolean;
|
crawlable?: boolean;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { isEmpty } from 'ramda';
|
|
||||||
import { FC, useEffect, useRef } from 'react';
|
import { FC, useEffect, useRef } from 'react';
|
||||||
import Moment from 'react-moment';
|
import { isEmpty } from 'ramda';
|
||||||
import { ExternalLink } from 'react-external-link';
|
import { ExternalLink } from 'react-external-link';
|
||||||
import ColorGenerator from '../../utils/services/ColorGenerator';
|
import ColorGenerator from '../../utils/services/ColorGenerator';
|
||||||
import { StateFlagTimeout } from '../../utils/helpers/hooks';
|
import { StateFlagTimeout } from '../../utils/helpers/hooks';
|
||||||
|
@ -8,6 +7,7 @@ import Tag from '../../tags/helpers/Tag';
|
||||||
import { SelectedServer } from '../../servers/data';
|
import { SelectedServer } from '../../servers/data';
|
||||||
import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon';
|
import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon';
|
||||||
import { ShortUrl } from '../data';
|
import { ShortUrl } from '../data';
|
||||||
|
import { Time } from '../../utils/Time';
|
||||||
import ShortUrlVisitsCount from './ShortUrlVisitsCount';
|
import ShortUrlVisitsCount from './ShortUrlVisitsCount';
|
||||||
import { ShortUrlsRowMenuProps } from './ShortUrlsRowMenu';
|
import { ShortUrlsRowMenuProps } from './ShortUrlsRowMenu';
|
||||||
import './ShortUrlsRow.scss';
|
import './ShortUrlsRow.scss';
|
||||||
|
@ -53,7 +53,7 @@ const ShortUrlsRow = (
|
||||||
return (
|
return (
|
||||||
<tr className="short-urls-row">
|
<tr className="short-urls-row">
|
||||||
<td className="indivisible short-urls-row__cell" data-th="Created at: ">
|
<td className="indivisible short-urls-row__cell" data-th="Created at: ">
|
||||||
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
|
<Time date={shortUrl.dateCreated} />
|
||||||
</td>
|
</td>
|
||||||
<td className="short-urls-row__cell" data-th="Short URL: ">
|
<td className="short-urls-row__cell" data-th="Short URL: ">
|
||||||
<span className="indivisible short-urls-row__cell--relative">
|
<span className="indivisible short-urls-row__cell--relative">
|
||||||
|
|
|
@ -1,33 +1,12 @@
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { isNil, dissoc } from 'ramda';
|
import { isNil } from 'ramda';
|
||||||
import DatePicker, { ReactDatePickerProps } from 'react-datepicker';
|
import DatePicker, { ReactDatePickerProps } from 'react-datepicker';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faCalendarAlt as calendarIcon } from '@fortawesome/free-regular-svg-icons';
|
import { faCalendarAlt as calendarIcon } from '@fortawesome/free-regular-svg-icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import moment from 'moment';
|
|
||||||
import './DateInput.scss';
|
import './DateInput.scss';
|
||||||
|
|
||||||
interface DatePropsInterface {
|
export type DateInputProps = ReactDatePickerProps;
|
||||||
endDate?: moment.Moment | null;
|
|
||||||
maxDate?: moment.Moment | null;
|
|
||||||
minDate?: moment.Moment | null;
|
|
||||||
selected?: moment.Moment | null;
|
|
||||||
startDate?: moment.Moment | null;
|
|
||||||
onChange?: (date: moment.Moment | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DateInputProps = DatePropsInterface & Omit<ReactDatePickerProps, keyof DatePropsInterface>;
|
|
||||||
|
|
||||||
const transformProps = (props: DateInputProps): ReactDatePickerProps => ({
|
|
||||||
// @ts-expect-error The DatePicker type definition is wrong. It has a ref prop
|
|
||||||
...dissoc('ref', props),
|
|
||||||
endDate: props.endDate?.toDate(),
|
|
||||||
maxDate: props.maxDate?.toDate(),
|
|
||||||
minDate: props.minDate?.toDate(),
|
|
||||||
selected: props.selected?.toDate(),
|
|
||||||
startDate: props.startDate?.toDate(),
|
|
||||||
onChange: (date: Date | null) => props.onChange?.(date && moment(date)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const DateInput = (props: DateInputProps) => {
|
const DateInput = (props: DateInputProps) => {
|
||||||
const { className, isClearable, selected } = props;
|
const { className, isClearable, selected } = props;
|
||||||
|
@ -37,7 +16,7 @@ const DateInput = (props: DateInputProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="date-input-container">
|
<div className="date-input-container">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...transformProps(props)}
|
{...props}
|
||||||
dateFormat="yyyy-MM-dd"
|
dateFormat="yyyy-MM-dd"
|
||||||
className={classNames('date-input-container__input form-control', className)}
|
className={classNames('date-input-container__input form-control', className)}
|
||||||
// @ts-expect-error The DatePicker type definition is wrong. It has a ref prop
|
// @ts-expect-error The DatePicker type definition is wrong. It has a ref prop
|
||||||
|
|
18
src/utils/Time.tsx
Normal file
18
src/utils/Time.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { parseISO, format as formatDate, getUnixTime, formatDistance } from 'date-fns';
|
||||||
|
import { isDateObject } from './helpers/date';
|
||||||
|
|
||||||
|
interface DateProps {
|
||||||
|
date: Date | string;
|
||||||
|
format?: string;
|
||||||
|
relative?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Time = ({ date, format = 'yyyy-MM-dd HH:mm', relative = false }: DateProps) => {
|
||||||
|
const dateObject = isDateObject(date) ? date : parseISO(date);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<time dateTime={`${getUnixTime(dateObject)}000`}>
|
||||||
|
{relative ? `${formatDistance(new Date(), dateObject)} ago` : formatDate(dateObject, format)}
|
||||||
|
</time>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,10 +1,9 @@
|
||||||
import moment from 'moment';
|
|
||||||
import DateInput from '../DateInput';
|
import DateInput from '../DateInput';
|
||||||
import { DateRange } from './types';
|
import { DateRange } from './types';
|
||||||
|
|
||||||
interface DateRangeRowProps extends DateRange {
|
interface DateRangeRowProps extends DateRange {
|
||||||
onStartDateChange: (date: moment.Moment | null) => void;
|
onStartDateChange: (date: Date | null) => void;
|
||||||
onEndDateChange: (date: moment.Moment | null) => void;
|
onEndDateChange: (date: Date | null) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import moment from 'moment';
|
import { subDays, startOfDay, endOfDay } from 'date-fns';
|
||||||
import { filter, isEmpty } from 'ramda';
|
import { filter, isEmpty } from 'ramda';
|
||||||
import { formatInternational } from '../../helpers/date';
|
import { formatInternational } from '../../helpers/date';
|
||||||
|
|
||||||
export interface DateRange {
|
export interface DateRange {
|
||||||
startDate?: moment.Moment | null;
|
startDate?: Date | null;
|
||||||
endDate?: moment.Moment | null;
|
endDate?: Date | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DateInterval = 'today' | 'yesterday' | 'last7Days' | 'last30Days' | 'last90Days' | 'last180days' | 'last365Days';
|
export type DateInterval = 'today' | 'yesterday' | 'last7Days' | 'last30Days' | 'last90Days' | 'last180days' | 'last365Days';
|
||||||
|
@ -54,6 +54,8 @@ export const rangeOrIntervalToString = (range?: DateRange | DateInterval): strin
|
||||||
return INTERVAL_TO_STRING_MAP[range];
|
return INTERVAL_TO_STRING_MAP[range];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const startOfDaysAgo = (daysAgo: number) => startOfDay(subDays(new Date(), daysAgo));
|
||||||
|
|
||||||
export const intervalToDateRange = (dateInterval?: DateInterval): DateRange => {
|
export const intervalToDateRange = (dateInterval?: DateInterval): DateRange => {
|
||||||
if (!dateInterval) {
|
if (!dateInterval) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -61,21 +63,19 @@ export const intervalToDateRange = (dateInterval?: DateInterval): DateRange => {
|
||||||
|
|
||||||
switch (dateInterval) {
|
switch (dateInterval) {
|
||||||
case 'today':
|
case 'today':
|
||||||
return { startDate: moment().startOf('day'), endDate: moment() };
|
return { startDate: startOfDay(new Date()), endDate: new Date() };
|
||||||
case 'yesterday':
|
case 'yesterday':
|
||||||
const yesterday = moment().subtract(1, 'day'); // eslint-disable-line no-case-declarations
|
return { startDate: startOfDaysAgo(1), endDate: endOfDay(subDays(new Date(), 1)) };
|
||||||
|
|
||||||
return { startDate: yesterday.startOf('day'), endDate: yesterday.endOf('day') };
|
|
||||||
case 'last7Days':
|
case 'last7Days':
|
||||||
return { startDate: moment().subtract(7, 'days').startOf('day'), endDate: moment() };
|
return { startDate: startOfDaysAgo(7), endDate: new Date() };
|
||||||
case 'last30Days':
|
case 'last30Days':
|
||||||
return { startDate: moment().subtract(30, 'days').startOf('day'), endDate: moment() };
|
return { startDate: startOfDaysAgo(30), endDate: new Date() };
|
||||||
case 'last90Days':
|
case 'last90Days':
|
||||||
return { startDate: moment().subtract(90, 'days').startOf('day'), endDate: moment() };
|
return { startDate: startOfDaysAgo(90), endDate: new Date() };
|
||||||
case 'last180days':
|
case 'last180days':
|
||||||
return { startDate: moment().subtract(180, 'days').startOf('day'), endDate: moment() };
|
return { startDate: startOfDaysAgo(180), endDate: new Date() };
|
||||||
case 'last365Days':
|
case 'last365Days':
|
||||||
return { startDate: moment().subtract(365, 'days').startOf('day'), endDate: moment() };
|
return { startDate: startOfDaysAgo(365), endDate: new Date() };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
import * as moment from 'moment';
|
import { format, formatISO } from 'date-fns';
|
||||||
import { OptionalString } from '../utils';
|
import { OptionalString } from '../utils';
|
||||||
|
|
||||||
type MomentOrString = moment.Moment | string;
|
type DateOrString = Date | string;
|
||||||
type NullableDate = MomentOrString | null;
|
type NullableDate = DateOrString | null;
|
||||||
|
|
||||||
const isMomentObject = (date: MomentOrString): date is moment.Moment => typeof (date as moment.Moment).format === 'function';
|
export const isDateObject = (date: DateOrString): date is Date => typeof date !== 'string';
|
||||||
|
|
||||||
const formatDateFromFormat = (date?: NullableDate, format?: string): OptionalString =>
|
const formatDateFromFormat = (date?: NullableDate, theFormat?: string): OptionalString => {
|
||||||
!date || !isMomentObject(date) ? date : date.format(format);
|
if (!date || !isDateObject(date)) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
export const formatDate = (format = 'YYYY-MM-DD') => (date?: NullableDate) => formatDateFromFormat(date, format);
|
return theFormat ? format(date, theFormat) : formatISO(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDate = (format = 'yyyy-MM-dd') => (date?: NullableDate) => formatDateFromFormat(date, format);
|
||||||
|
|
||||||
export const formatIsoDate = (date?: NullableDate) => formatDateFromFormat(date, undefined);
|
export const formatIsoDate = (date?: NullableDate) => formatDateFromFormat(date, undefined);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import Moment from 'react-moment';
|
|
||||||
import { ExternalLink } from 'react-external-link';
|
import { ExternalLink } from 'react-external-link';
|
||||||
import { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail';
|
import { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail';
|
||||||
|
import { Time } from '../utils/Time';
|
||||||
import { ShortUrlVisits } from './reducers/shortUrlVisits';
|
import { ShortUrlVisits } from './reducers/shortUrlVisits';
|
||||||
import VisitsHeader from './VisitsHeader';
|
import VisitsHeader from './VisitsHeader';
|
||||||
import './ShortUrlVisitsHeader.scss';
|
import './ShortUrlVisitsHeader.scss';
|
||||||
|
@ -22,18 +22,14 @@ const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }: ShortU
|
||||||
const renderDate = () => !shortUrl ? <small>Loading...</small> : (
|
const renderDate = () => !shortUrl ? <small>Loading...</small> : (
|
||||||
<span>
|
<span>
|
||||||
<b id="created" className="short-url-visits-header__created-at">
|
<b id="created" className="short-url-visits-header__created-at">
|
||||||
<Moment fromNow>{shortUrl.dateCreated}</Moment>
|
<Time date={shortUrl.dateCreated} relative />
|
||||||
</b>
|
</b>
|
||||||
<UncontrolledTooltip placement="bottom" target="created">
|
<UncontrolledTooltip placement="bottom" target="created">
|
||||||
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
|
<Time date={shortUrl.dateCreated} />
|
||||||
</UncontrolledTooltip>
|
</UncontrolledTooltip>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
const visitsStatsTitle = (
|
const visitsStatsTitle = <>Visits for <ExternalLink href={shortLink} /></>;
|
||||||
<>
|
|
||||||
Visits for <ExternalLink href={shortLink} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VisitsHeader title={visitsStatsTitle} goBack={goBack} visits={visits} shortUrl={shortUrl}>
|
<VisitsHeader title={visitsStatsTitle} goBack={goBack} visits={visits} shortUrl={shortUrl}>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { useEffect, useMemo, useState, useRef } from 'react';
|
import { useEffect, useMemo, useState, useRef } from 'react';
|
||||||
import Moment from 'react-moment';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { min, splitEvery } from 'ramda';
|
import { min, splitEvery } from 'ramda';
|
||||||
import {
|
import {
|
||||||
|
@ -16,6 +15,7 @@ import { determineOrderDir, OrderDir } from '../utils/utils';
|
||||||
import { prettify } from '../utils/helpers/numbers';
|
import { prettify } from '../utils/helpers/numbers';
|
||||||
import { supportsBotVisits } from '../utils/helpers/features';
|
import { supportsBotVisits } from '../utils/helpers/features';
|
||||||
import { SelectedServer } from '../servers/data';
|
import { SelectedServer } from '../servers/data';
|
||||||
|
import { Time } from '../utils/Time';
|
||||||
import { NormalizedOrphanVisit, NormalizedVisit } from './types';
|
import { NormalizedOrphanVisit, NormalizedVisit } from './types';
|
||||||
import './VisitsTable.scss';
|
import './VisitsTable.scss';
|
||||||
|
|
||||||
|
@ -194,9 +194,7 @@ const VisitsTable = ({
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
<td>
|
<td><Time date={visit.date} /></td>
|
||||||
<Moment format="YYYY-MM-DD HH:mm">{visit.date}</Moment>
|
|
||||||
</td>
|
|
||||||
<td>{visit.country}</td>
|
<td>{visit.country}</td>
|
||||||
<td>{visit.city}</td>
|
<td>{visit.city}</td>
|
||||||
<td>{visit.browser}</td>
|
<td>{visit.browser}</td>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import moment from 'moment';
|
import { formatISO, parse } from 'date-fns';
|
||||||
import { identity } from 'ramda';
|
import { identity } from 'ramda';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { Input } from 'reactstrap';
|
import { Input } from 'reactstrap';
|
||||||
|
@ -34,8 +34,8 @@ describe('<ShortUrlForm />', () => {
|
||||||
|
|
||||||
it('saves short URL with data set in form controls', () => {
|
it('saves short URL with data set in form controls', () => {
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
const validSince = moment('2017-01-01');
|
const validSince = parse('2017-01-01', 'yyyy-MM-dd', new Date());
|
||||||
const validUntil = moment('2017-01-06');
|
const validUntil = parse('2017-01-06', 'yyyy-MM-dd', new Date());
|
||||||
|
|
||||||
wrapper.find(Input).first().simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } });
|
wrapper.find(Input).first().simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } });
|
||||||
wrapper.find('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]);
|
wrapper.find('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]);
|
||||||
|
@ -53,8 +53,8 @@ describe('<ShortUrlForm />', () => {
|
||||||
tags: [ 'tag_foo', 'tag_bar' ],
|
tags: [ 'tag_foo', 'tag_bar' ],
|
||||||
customSlug: 'my-slug',
|
customSlug: 'my-slug',
|
||||||
domain: 'example.com',
|
domain: 'example.com',
|
||||||
validSince: validSince.format(),
|
validSince: formatISO(validSince),
|
||||||
validUntil: validUntil.format(),
|
validUntil: formatISO(validUntil),
|
||||||
maxVisits: 20,
|
maxVisits: 20,
|
||||||
findIfExists: false,
|
findIfExists: false,
|
||||||
shortCodeLength: 15,
|
shortCodeLength: 15,
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import moment from 'moment';
|
|
||||||
import Moment from 'react-moment';
|
|
||||||
import { assoc, toString } from 'ramda';
|
import { assoc, toString } from 'ramda';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { ExternalLink } from 'react-external-link';
|
import { ExternalLink } from 'react-external-link';
|
||||||
|
import { formatISO, parse } from 'date-fns';
|
||||||
import createShortUrlsRow from '../../../src/short-urls/helpers/ShortUrlsRow';
|
import createShortUrlsRow from '../../../src/short-urls/helpers/ShortUrlsRow';
|
||||||
import Tag from '../../../src/tags/helpers/Tag';
|
import Tag from '../../../src/tags/helpers/Tag';
|
||||||
import ColorGenerator from '../../../src/utils/services/ColorGenerator';
|
import ColorGenerator from '../../../src/utils/services/ColorGenerator';
|
||||||
|
@ -11,6 +10,7 @@ import { StateFlagTimeout } from '../../../src/utils/helpers/hooks';
|
||||||
import { ShortUrl } from '../../../src/short-urls/data';
|
import { ShortUrl } from '../../../src/short-urls/data';
|
||||||
import { ReachableServer } from '../../../src/servers/data';
|
import { ReachableServer } from '../../../src/servers/data';
|
||||||
import { CopyToClipboardIcon } from '../../../src/utils/CopyToClipboardIcon';
|
import { CopyToClipboardIcon } from '../../../src/utils/CopyToClipboardIcon';
|
||||||
|
import { Time } from '../../../src/utils/Time';
|
||||||
|
|
||||||
describe('<ShortUrlsRow />', () => {
|
describe('<ShortUrlsRow />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
|
@ -27,7 +27,7 @@ describe('<ShortUrlsRow />', () => {
|
||||||
shortCode: 'abc123',
|
shortCode: 'abc123',
|
||||||
shortUrl: 'http://doma.in/abc123',
|
shortUrl: 'http://doma.in/abc123',
|
||||||
longUrl: 'http://foo.com/bar',
|
longUrl: 'http://foo.com/bar',
|
||||||
dateCreated: moment('2018-05-23 18:30:41').format(),
|
dateCreated: formatISO(parse('2018-05-23 18:30:41', 'yyyy-MM-dd HH:mm:ss', new Date())),
|
||||||
tags: [ 'nodejs', 'reactjs' ],
|
tags: [ 'nodejs', 'reactjs' ],
|
||||||
visitsCount: 45,
|
visitsCount: 45,
|
||||||
domain: null,
|
domain: null,
|
||||||
|
@ -62,9 +62,9 @@ describe('<ShortUrlsRow />', () => {
|
||||||
|
|
||||||
it('renders date in first column', () => {
|
it('renders date in first column', () => {
|
||||||
const col = wrapper.find('td').first();
|
const col = wrapper.find('td').first();
|
||||||
const moment = col.find(Moment);
|
const date = col.find(Time);
|
||||||
|
|
||||||
expect(moment.html()).toContain('>2018-05-23 18:30</time>');
|
expect(date.html()).toContain('>2018-05-23 18:30</time>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders short URL in second row', () => {
|
it('renders short URL in second row', () => {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import moment from 'moment';
|
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import DateInput, { DateInputProps } from '../../src/utils/DateInput';
|
import DateInput, { DateInputProps } from '../../src/utils/DateInput';
|
||||||
|
|
||||||
|
@ -30,7 +29,7 @@ describe('<DateInput />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not show calendar icon when input is clearable', () => {
|
it('does not show calendar icon when input is clearable', () => {
|
||||||
wrapped = createComponent({ isClearable: true, selected: moment() });
|
wrapped = createComponent({ isClearable: true, selected: new Date() });
|
||||||
expect(wrapped.find(FontAwesomeIcon)).toHaveLength(0);
|
expect(wrapped.find(FontAwesomeIcon)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { DropdownItem } from 'reactstrap';
|
import { DropdownItem } from 'reactstrap';
|
||||||
import moment from 'moment';
|
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { DateRangeSelector, DateRangeSelectorProps } from '../../../src/utils/dates/DateRangeSelector';
|
import { DateRangeSelector, DateRangeSelectorProps } from '../../../src/utils/dates/DateRangeSelector';
|
||||||
import { DateInterval } from '../../../src/utils/dates/types';
|
import { DateInterval } from '../../../src/utils/dates/types';
|
||||||
|
@ -40,7 +39,7 @@ describe('<DateRangeSelector />', () => {
|
||||||
[ 'last90Days' as DateInterval, 0, 1 ],
|
[ 'last90Days' as DateInterval, 0, 1 ],
|
||||||
[ 'last180days' as DateInterval, 0, 1 ],
|
[ 'last180days' as DateInterval, 0, 1 ],
|
||||||
[ 'last365Days' as DateInterval, 0, 1 ],
|
[ 'last365Days' as DateInterval, 0, 1 ],
|
||||||
[{ startDate: moment() }, 0, 0 ],
|
[{ startDate: new Date() }, 0, 0 ],
|
||||||
])('sets proper element as active based on provided date range', (
|
])('sets proper element as active based on provided date range', (
|
||||||
initialDateRange,
|
initialDateRange,
|
||||||
expectedActiveItems,
|
expectedActiveItems,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import moment from 'moment';
|
import { format, parse, subDays } from 'date-fns';
|
||||||
import {
|
import {
|
||||||
DateInterval,
|
DateInterval,
|
||||||
dateRangeIsEmpty,
|
dateRangeIsEmpty,
|
||||||
|
@ -20,9 +20,9 @@ describe('date-types', () => {
|
||||||
[{ startDate: undefined, endDate: undefined }, true ],
|
[{ startDate: undefined, endDate: undefined }, true ],
|
||||||
[{ startDate: undefined, endDate: null }, true ],
|
[{ startDate: undefined, endDate: null }, true ],
|
||||||
[{ startDate: null, endDate: undefined }, true ],
|
[{ startDate: null, endDate: undefined }, true ],
|
||||||
[{ startDate: moment() }, false ],
|
[{ startDate: new Date() }, false ],
|
||||||
[{ endDate: moment() }, false ],
|
[{ endDate: new Date() }, false ],
|
||||||
[{ startDate: moment(), endDate: moment() }, false ],
|
[{ startDate: new Date(), endDate: new Date() }, false ],
|
||||||
])('proper result is returned', (dateRange, expectedResult) => {
|
])('proper result is returned', (dateRange, expectedResult) => {
|
||||||
expect(dateRangeIsEmpty(dateRange)).toEqual(expectedResult);
|
expect(dateRangeIsEmpty(dateRange)).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
@ -58,31 +58,39 @@ describe('date-types', () => {
|
||||||
[{ startDate: undefined, endDate: undefined }, undefined ],
|
[{ startDate: undefined, endDate: undefined }, undefined ],
|
||||||
[{ startDate: undefined, endDate: null }, undefined ],
|
[{ startDate: undefined, endDate: null }, undefined ],
|
||||||
[{ startDate: null, endDate: undefined }, undefined ],
|
[{ startDate: null, endDate: undefined }, undefined ],
|
||||||
[{ startDate: moment('2020-01-01') }, 'Since 2020-01-01' ],
|
[{ startDate: parse('2020-01-01', 'yyyy-MM-dd', new Date()) }, 'Since 2020-01-01' ],
|
||||||
[{ endDate: moment('2020-01-01') }, 'Until 2020-01-01' ],
|
[{ endDate: parse('2020-01-01', 'yyyy-MM-dd', new Date()) }, 'Until 2020-01-01' ],
|
||||||
[{ startDate: moment('2020-01-01'), endDate: moment('2021-02-02') }, '2020-01-01 - 2021-02-02' ],
|
[
|
||||||
|
{
|
||||||
|
startDate: parse('2020-01-01', 'yyyy-MM-dd', new Date()),
|
||||||
|
endDate: parse('2021-02-02', 'yyyy-MM-dd', new Date()),
|
||||||
|
},
|
||||||
|
'2020-01-01 - 2021-02-02',
|
||||||
|
],
|
||||||
])('proper result is returned', (range, expectedValue) => {
|
])('proper result is returned', (range, expectedValue) => {
|
||||||
expect(rangeOrIntervalToString(range)).toEqual(expectedValue);
|
expect(rangeOrIntervalToString(range)).toEqual(expectedValue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('intervalToDateRange', () => {
|
describe('intervalToDateRange', () => {
|
||||||
const now = () => moment();
|
const now = () => new Date();
|
||||||
|
const daysBack = (days: number) => subDays(new Date(), days);
|
||||||
|
const formatted = (date?: Date | null): string | undefined => !date ? undefined : format(date, 'yyyy-MM-dd');
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
[ undefined, undefined, undefined ],
|
[ undefined, undefined, undefined ],
|
||||||
[ 'today' as DateInterval, now(), now() ],
|
[ 'today' as DateInterval, now(), now() ],
|
||||||
[ 'yesterday' as DateInterval, now().subtract(1, 'day'), now().subtract(1, 'day') ],
|
[ 'yesterday' as DateInterval, daysBack(1), daysBack(1) ],
|
||||||
[ 'last7Days' as DateInterval, now().subtract(7, 'day'), now() ],
|
[ 'last7Days' as DateInterval, daysBack(7), now() ],
|
||||||
[ 'last30Days' as DateInterval, now().subtract(30, 'day'), now() ],
|
[ 'last30Days' as DateInterval, daysBack(30), now() ],
|
||||||
[ 'last90Days' as DateInterval, now().subtract(90, 'day'), now() ],
|
[ 'last90Days' as DateInterval, daysBack(90), now() ],
|
||||||
[ 'last180days' as DateInterval, now().subtract(180, 'day'), now() ],
|
[ 'last180days' as DateInterval, daysBack(180), now() ],
|
||||||
[ 'last365Days' as DateInterval, now().subtract(365, 'day'), now() ],
|
[ 'last365Days' as DateInterval, daysBack(365), now() ],
|
||||||
])('proper result is returned', (interval, expectedStartDate, expectedEndDate) => {
|
])('proper result is returned', (interval, expectedStartDate, expectedEndDate) => {
|
||||||
const { startDate, endDate } = intervalToDateRange(interval);
|
const { startDate, endDate } = intervalToDateRange(interval);
|
||||||
|
|
||||||
expect(expectedStartDate?.format('YYYY-MM-DD')).toEqual(startDate?.format('YYYY-MM-DD'));
|
expect(formatted(expectedStartDate)).toEqual(formatted(startDate));
|
||||||
expect(expectedEndDate?.format('YYYY-MM-DD')).toEqual(endDate?.format('YYYY-MM-DD'));
|
expect(formatted(expectedEndDate)).toEqual(formatted(endDate));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import moment from 'moment';
|
import { formatISO, parse } from 'date-fns';
|
||||||
import { formatDate, formatIsoDate } from '../../../src/utils/helpers/date';
|
import { formatDate, formatIsoDate } from '../../../src/utils/helpers/date';
|
||||||
|
|
||||||
describe('date', () => {
|
describe('date', () => {
|
||||||
describe('formatDate', () => {
|
describe('formatDate', () => {
|
||||||
it.each([
|
it.each([
|
||||||
[ moment('2020-03-05 10:00:10'), 'DD/MM/YYYY', '05/03/2020' ],
|
[ parse('2020-03-05 10:00:10', 'yyyy-MM-dd HH:mm:ss', new Date()), 'dd/MM/yyyy', '05/03/2020' ],
|
||||||
[ moment('2020-03-05 10:00:10'), 'YYYY-MM', '2020-03' ],
|
[ parse('2020-03-05 10:00:10', 'yyyy-MM-dd HH:mm:ss', new Date()), 'yyyy-MM', '2020-03' ],
|
||||||
[ moment('2020-03-05 10:00:10'), undefined, '2020-03-05' ],
|
[ parse('2020-03-05 10:00:10', 'yyyy-MM-dd HH:mm:ss', new Date()), undefined, '2020-03-05' ],
|
||||||
[ '2020-03-05 10:00:10', 'DD-MM-YYYY', '2020-03-05 10:00:10' ],
|
[ '2020-03-05 10:00:10', 'dd-MM-yyyy', '2020-03-05 10:00:10' ],
|
||||||
[ '2020-03-05 10:00:10', undefined, '2020-03-05 10:00:10' ],
|
[ '2020-03-05 10:00:10', undefined, '2020-03-05 10:00:10' ],
|
||||||
[ undefined, undefined, undefined ],
|
[ undefined, undefined, undefined ],
|
||||||
[ null, undefined, null ],
|
[ null, undefined, null ],
|
||||||
|
@ -18,7 +18,10 @@ describe('date', () => {
|
||||||
|
|
||||||
describe('formatIsoDate', () => {
|
describe('formatIsoDate', () => {
|
||||||
it.each([
|
it.each([
|
||||||
[ moment('2020-03-05 10:00:10'), moment('2020-03-05 10:00:10').format() ],
|
[
|
||||||
|
parse('2020-03-05 10:00:10', 'yyyy-MM-dd HH:mm:ss', new Date()),
|
||||||
|
formatISO(parse('2020-03-05 10:00:10', 'yyyy-MM-dd HH:mm:ss', new Date())),
|
||||||
|
],
|
||||||
[ '2020-03-05 10:00:10', '2020-03-05 10:00:10' ],
|
[ '2020-03-05 10:00:10', '2020-03-05 10:00:10' ],
|
||||||
[ 'foo', 'foo' ],
|
[ 'foo', 'foo' ],
|
||||||
[ undefined, undefined ],
|
[ undefined, undefined ],
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import Moment from 'react-moment';
|
|
||||||
import { ExternalLink } from 'react-external-link';
|
import { ExternalLink } from 'react-external-link';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import ShortUrlVisitsHeader from '../../src/visits/ShortUrlVisitsHeader';
|
import ShortUrlVisitsHeader from '../../src/visits/ShortUrlVisitsHeader';
|
||||||
import { ShortUrlDetail } from '../../src/short-urls/reducers/shortUrlDetail';
|
import { ShortUrlDetail } from '../../src/short-urls/reducers/shortUrlDetail';
|
||||||
import { ShortUrlVisits } from '../../src/visits/reducers/shortUrlVisits';
|
import { ShortUrlVisits } from '../../src/visits/reducers/shortUrlVisits';
|
||||||
|
import { Time } from '../../src/utils/Time';
|
||||||
|
|
||||||
describe('<ShortUrlVisitsHeader />', () => {
|
describe('<ShortUrlVisitsHeader />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
|
@ -36,9 +36,9 @@ describe('<ShortUrlVisitsHeader />', () => {
|
||||||
afterEach(() => wrapper.unmount());
|
afterEach(() => wrapper.unmount());
|
||||||
|
|
||||||
it('shows when the URL was created', () => {
|
it('shows when the URL was created', () => {
|
||||||
const moment = wrapper.find(Moment).first();
|
const time = wrapper.find(Time).first();
|
||||||
|
|
||||||
expect(moment.prop('children')).toEqual(dateCreated);
|
expect(time.prop('date')).toEqual(dateCreated);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { CardHeader, DropdownItem } from 'reactstrap';
|
import { CardHeader, DropdownItem } from 'reactstrap';
|
||||||
import { Line } from 'react-chartjs-2';
|
import { Line } from 'react-chartjs-2';
|
||||||
import moment from 'moment';
|
import { formatISO, subDays, subMonths, subYears } from 'date-fns';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import LineChartCard from '../../../src/visits/helpers/LineChartCard';
|
import LineChartCard from '../../../src/visits/helpers/LineChartCard';
|
||||||
import ToggleSwitch from '../../../src/utils/ToggleSwitch';
|
import ToggleSwitch from '../../../src/utils/ToggleSwitch';
|
||||||
|
@ -27,12 +27,12 @@ describe('<LineChartCard />', () => {
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[[], 'monthly' ],
|
[[], 'monthly' ],
|
||||||
[[{ date: moment().subtract(1, 'day').format() }], 'hourly' ],
|
[[{ date: formatISO(subDays(new Date(), 1)) }], 'hourly' ],
|
||||||
[[{ date: moment().subtract(3, 'day').format() }], 'daily' ],
|
[[{ date: formatISO(subDays(new Date(), 3)) }], 'daily' ],
|
||||||
[[{ date: moment().subtract(2, 'month').format() }], 'weekly' ],
|
[[{ date: formatISO(subMonths(new Date(), 2)) }], 'weekly' ],
|
||||||
[[{ date: moment().subtract(6, 'month').format() }], 'weekly' ],
|
[[{ date: formatISO(subMonths(new Date(), 6)) }], 'weekly' ],
|
||||||
[[{ date: moment().subtract(7, 'month').format() }], 'monthly' ],
|
[[{ date: formatISO(subMonths(new Date(), 7)) }], 'monthly' ],
|
||||||
[[{ date: moment().subtract(1, 'year').format() }], 'monthly' ],
|
[[{ date: formatISO(subYears(new Date(), 1)) }], 'monthly' ],
|
||||||
])('renders group menu and selects proper grouping item based on visits dates', (visits, expectedActiveItem) => {
|
])('renders group menu and selects proper grouping item based on visits dates', (visits, expectedActiveItem) => {
|
||||||
const wrapper = createWrapper(visits.map((visit) => Mock.of<NormalizedVisit>(visit)));
|
const wrapper = createWrapper(visits.map((visit) => Mock.of<NormalizedVisit>(visit)));
|
||||||
const items = wrapper.find(DropdownItem);
|
const items = wrapper.find(DropdownItem);
|
||||||
|
|
Loading…
Reference in a new issue