Uniformize folder structure (#693)
* Uniformize folder structure * Fix icons * Fix icons * Fix tests * Fix tests
This commit is contained in:
153
front/src/utils/__tests__/date-utils.test.ts
Normal file
153
front/src/utils/__tests__/date-utils.test.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import {
|
||||
beautifyExactDate,
|
||||
beautifyPastDateAbsolute,
|
||||
beautifyPastDateRelativeToNow,
|
||||
DEFAULT_DATE_LOCALE,
|
||||
parseDate,
|
||||
} from '../date-utils';
|
||||
import { logError } from '../logError';
|
||||
|
||||
jest.mock('~/utils/logError');
|
||||
|
||||
describe('beautifyExactDate', () => {
|
||||
it('should return the correct relative date', () => {
|
||||
const mockDate = '2023-01-01T12:13:24';
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(DEFAULT_DATE_LOCALE)
|
||||
.toFormat('DD · TT');
|
||||
|
||||
const result = beautifyExactDate(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseDate', () => {
|
||||
it('should log an error and return empty string when passed an invalid date string', () => {
|
||||
expect(() => {
|
||||
parseDate('invalid-date-string');
|
||||
}).toThrow(
|
||||
Error('Invalid date passed to formatPastDate: "invalid-date-string"'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log an error and return empty string when passed NaN', () => {
|
||||
expect(() => {
|
||||
parseDate(NaN);
|
||||
}).toThrow(Error('Invalid date passed to formatPastDate: "NaN"'));
|
||||
});
|
||||
|
||||
it('should log an error and return empty string when passed invalid Date object', () => {
|
||||
expect(() => {
|
||||
parseDate(new Date(NaN));
|
||||
}).toThrow(Error('Invalid date passed to formatPastDate: "Invalid Date"'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('beautifyPastDateRelativeToNow', () => {
|
||||
it('should return the correct relative date', () => {
|
||||
const mockDate = '2023-01-01';
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = formatDistanceToNow(actualDate);
|
||||
|
||||
const result = beautifyPastDateRelativeToNow(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should log an error and return empty string when passed an invalid date string', () => {
|
||||
const result = beautifyPastDateRelativeToNow('invalid-date-string');
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "invalid-date-string"'),
|
||||
);
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
it('should log an error and return empty string when passed NaN', () => {
|
||||
const result = beautifyPastDateRelativeToNow(NaN);
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "NaN"'),
|
||||
);
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
it('should log an error and return empty string when passed invalid Date object', () => {
|
||||
const result = beautifyPastDateRelativeToNow(
|
||||
new Date('invalid-date-asdasd'),
|
||||
);
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "Invalid Date"'),
|
||||
);
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('beautifyPastDateAbsolute', () => {
|
||||
it('should log an error and return empty string when passed an invalid date string', () => {
|
||||
const result = beautifyPastDateAbsolute('invalid-date-string');
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "invalid-date-string"'),
|
||||
);
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
it('should log an error and return empty string when passed NaN', () => {
|
||||
const result = beautifyPastDateAbsolute(NaN);
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "NaN"'),
|
||||
);
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
it('should log an error and return empty string when passed invalid Date object', () => {
|
||||
const result = beautifyPastDateAbsolute(new Date(NaN));
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "Invalid Date"'),
|
||||
);
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
it('should return the correct format when the date difference is less than 24 hours', () => {
|
||||
const now = DateTime.local();
|
||||
const pastDate = now.minus({ hours: 23 });
|
||||
const expected = pastDate.toFormat('HH:mm');
|
||||
|
||||
const result = beautifyPastDateAbsolute(pastDate.toJSDate());
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return the correct format when the date difference is less than 7 days', () => {
|
||||
const now = DateTime.local();
|
||||
const pastDate = now.minus({ days: 6 });
|
||||
const expected = pastDate.toFormat('cccc - HH:mm');
|
||||
|
||||
const result = beautifyPastDateAbsolute(pastDate.toJSDate());
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return the correct format when the date difference is less than 365 days', () => {
|
||||
const now = DateTime.local();
|
||||
const pastDate = now.minus({ days: 364 });
|
||||
const expected = pastDate.toFormat('MMMM d - HH:mm');
|
||||
|
||||
const result = beautifyPastDateAbsolute(pastDate.toJSDate());
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return the correct format when the date difference is more than 365 days', () => {
|
||||
const now = DateTime.local();
|
||||
const pastDate = now.minus({ days: 366 });
|
||||
const expected = pastDate.toFormat('dd/MM/yyyy - HH:mm');
|
||||
|
||||
const result = beautifyPastDateAbsolute(pastDate.toJSDate());
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
21
front/src/utils/__tests__/utils.test.ts
Normal file
21
front/src/utils/__tests__/utils.test.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getLogoUrlFromDomainName } from '..';
|
||||
|
||||
describe('getLogoUrlFromDomainName', () => {
|
||||
it(`should generate logo url if undefined `, () => {
|
||||
expect(getLogoUrlFromDomainName(undefined)).toBe(
|
||||
'https://api.faviconkit.com/undefined/144',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should generate logo url if defined `, () => {
|
||||
expect(getLogoUrlFromDomainName('test.com')).toBe(
|
||||
'https://api.faviconkit.com/test.com/144',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should generate logo url if empty `, () => {
|
||||
expect(getLogoUrlFromDomainName('')).toBe(
|
||||
'https://api.faviconkit.com//144',
|
||||
);
|
||||
});
|
||||
});
|
||||
3
front/src/utils/assert.ts
Normal file
3
front/src/utils/assert.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function assertNotNull<T>(item: T): item is NonNullable<T> {
|
||||
return item !== null && item !== undefined;
|
||||
}
|
||||
25
front/src/utils/cookie-storage.ts
Normal file
25
front/src/utils/cookie-storage.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import Cookies, { CookieAttributes } from 'js-cookie';
|
||||
|
||||
class CookieStorage {
|
||||
private keys: Set<string> = new Set();
|
||||
|
||||
getItem(key: string): string | undefined {
|
||||
return Cookies.get(key);
|
||||
}
|
||||
|
||||
setItem(key: string, value: string, attributes?: CookieAttributes): void {
|
||||
this.keys.add(key);
|
||||
Cookies.set(key, value, attributes);
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
this.keys.delete(key);
|
||||
Cookies.remove(key);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.keys.forEach((key) => this.removeItem(key));
|
||||
}
|
||||
}
|
||||
|
||||
export const cookieStorage = new CookieStorage();
|
||||
75
front/src/utils/date-utils.ts
Normal file
75
front/src/utils/date-utils.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { logError } from './logError';
|
||||
|
||||
export const DEFAULT_DATE_LOCALE = 'en-EN';
|
||||
|
||||
export function parseDate(dateToParse: Date | string | number) {
|
||||
let formattedDate: DateTime | null = null;
|
||||
|
||||
if (!dateToParse) {
|
||||
throw new Error(`Invalid date passed to formatPastDate: "${dateToParse}"`);
|
||||
} else if (typeof dateToParse === 'string') {
|
||||
formattedDate = DateTime.fromISO(dateToParse);
|
||||
} else if (dateToParse instanceof Date) {
|
||||
formattedDate = DateTime.fromJSDate(dateToParse);
|
||||
} else if (typeof dateToParse === 'number') {
|
||||
formattedDate = DateTime.fromMillis(dateToParse);
|
||||
}
|
||||
|
||||
if (!formattedDate) {
|
||||
throw new Error(`Invalid date passed to formatPastDate: "${dateToParse}"`);
|
||||
}
|
||||
|
||||
if (!formattedDate.isValid) {
|
||||
throw new Error(`Invalid date passed to formatPastDate: "${dateToParse}"`);
|
||||
}
|
||||
|
||||
return formattedDate.setLocale(DEFAULT_DATE_LOCALE);
|
||||
}
|
||||
|
||||
export function beautifyExactDate(dateToBeautify: Date | string | number) {
|
||||
try {
|
||||
const parsedDate = parseDate(dateToBeautify);
|
||||
|
||||
return parsedDate.toFormat('DD · TT');
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function beautifyPastDateRelativeToNow(
|
||||
pastDate: Date | string | number,
|
||||
) {
|
||||
try {
|
||||
const parsedDate = parseDate(pastDate);
|
||||
|
||||
return formatDistanceToNow(parsedDate.toJSDate());
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function beautifyPastDateAbsolute(pastDate: Date | string | number) {
|
||||
try {
|
||||
const parsedPastDate = parseDate(pastDate);
|
||||
|
||||
const hoursDiff = parsedPastDate.diffNow('hours').negate().hours;
|
||||
|
||||
if (hoursDiff <= 24) {
|
||||
return parsedPastDate.toFormat('HH:mm');
|
||||
} else if (hoursDiff <= 7 * 24) {
|
||||
return parsedPastDate.toFormat('cccc - HH:mm');
|
||||
} else if (hoursDiff <= 365 * 24) {
|
||||
return parsedPastDate.toFormat('MMMM d - HH:mm');
|
||||
} else {
|
||||
return parsedPastDate.toFormat('dd/MM/yyyy - HH:mm');
|
||||
}
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
12
front/src/utils/debounce.ts
Normal file
12
front/src/utils/debounce.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export const debounce = <FuncArgs extends any[]>(
|
||||
func: (...args: FuncArgs) => void,
|
||||
delay: number,
|
||||
) => {
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
return (...args: FuncArgs) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
func(...args);
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
19
front/src/utils/index.ts
Normal file
19
front/src/utils/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { parseDate } from './date-utils';
|
||||
|
||||
export function formatToHumanReadableDate(date: Date | string) {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(parsedJSDate);
|
||||
}
|
||||
|
||||
export const getLogoUrlFromDomainName = (domainName?: string): string => {
|
||||
return `https://api.faviconkit.com/${domainName}/144`;
|
||||
};
|
||||
|
||||
export const browserPrefersDarkMode = (): boolean => {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
};
|
||||
5
front/src/utils/isDefined.ts
Normal file
5
front/src/utils/isDefined.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export function isDefined<T>(
|
||||
value: T | undefined | null,
|
||||
): value is NonNullable<T> {
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
13
front/src/utils/isNonEmptyArray.ts
Normal file
13
front/src/utils/isNonEmptyArray.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export function isNonEmptyArray<T>(
|
||||
probableArray: T[] | undefined | null,
|
||||
): probableArray is NonNullable<T[]> {
|
||||
if (
|
||||
Array.isArray(probableArray) &&
|
||||
probableArray.length &&
|
||||
probableArray.length > 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
15
front/src/utils/isNonEmptyString.ts
Normal file
15
front/src/utils/isNonEmptyString.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { isDefined } from './isDefined';
|
||||
|
||||
export function isNonEmptyString(
|
||||
probableNonEmptyString: string | undefined | null,
|
||||
): probableNonEmptyString is string {
|
||||
if (
|
||||
isDefined(probableNonEmptyString) &&
|
||||
typeof probableNonEmptyString === 'string' &&
|
||||
probableNonEmptyString !== ''
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
3
front/src/utils/logError.ts
Normal file
3
front/src/utils/logError.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function logError(message: any) {
|
||||
console.error(message);
|
||||
}
|
||||
16
front/src/utils/promise-to-observable.ts
Normal file
16
front/src/utils/promise-to-observable.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Observable } from '@apollo/client';
|
||||
|
||||
export const promiseToObservable = <T>(promise: Promise<T>) =>
|
||||
new Observable<T>((subscriber) => {
|
||||
promise.then(
|
||||
(value) => {
|
||||
if (subscriber.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
subscriber.next(value);
|
||||
subscriber.complete();
|
||||
},
|
||||
(err) => subscriber.error(err),
|
||||
);
|
||||
});
|
||||
13
front/src/utils/string-to-hsl.ts
Normal file
13
front/src/utils/string-to-hsl.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export function stringToHslColor(
|
||||
str: string,
|
||||
saturation: number,
|
||||
lightness: number,
|
||||
) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
|
||||
const h = hash % 360;
|
||||
return 'hsl(' + h + ', ' + saturation + '%, ' + lightness + '%)';
|
||||
}
|
||||
Reference in New Issue
Block a user