Uniformize folder structure (#693)

* Uniformize folder structure

* Fix icons

* Fix icons

* Fix tests

* Fix tests
This commit is contained in:
Charles Bochet
2023-07-16 14:29:28 -07:00
committed by GitHub
parent 900ec5572f
commit 6ced8434bd
462 changed files with 931 additions and 960 deletions

View 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);
});
});

View 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',
);
});
});

View File

@ -0,0 +1,3 @@
export function assertNotNull<T>(item: T): item is NonNullable<T> {
return item !== null && item !== undefined;
}

View 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();

View 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 '';
}
}

View 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
View 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;
};

View File

@ -0,0 +1,5 @@
export function isDefined<T>(
value: T | undefined | null,
): value is NonNullable<T> {
return value !== undefined && value !== null;
}

View 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;
}

View 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;
}

View File

@ -0,0 +1,3 @@
export function logError(message: any) {
console.error(message);
}

View 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),
);
});

View 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 + '%)';
}