Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,72 @@
|
||||
import {
|
||||
canBeCastAsIntegerOrNull,
|
||||
castAsIntegerOrNull,
|
||||
} from '../cast-as-integer-or-null';
|
||||
|
||||
describe('canBeCastAsIntegerOrNull', () => {
|
||||
it(`should return true if null`, () => {
|
||||
expect(canBeCastAsIntegerOrNull(null)).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if number`, () => {
|
||||
expect(canBeCastAsIntegerOrNull(9)).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if empty string`, () => {
|
||||
expect(canBeCastAsIntegerOrNull('')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if integer string`, () => {
|
||||
expect(canBeCastAsIntegerOrNull('9')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return false if undefined`, () => {
|
||||
expect(canBeCastAsIntegerOrNull(undefined)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if non numeric string`, () => {
|
||||
expect(canBeCastAsIntegerOrNull('9a')).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if non numeric string #2`, () => {
|
||||
expect(canBeCastAsIntegerOrNull('a9a')).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if float`, () => {
|
||||
expect(canBeCastAsIntegerOrNull(0.9)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if float string`, () => {
|
||||
expect(canBeCastAsIntegerOrNull('0.9')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('castAsIntegerOrNull', () => {
|
||||
it(`should cast null to null`, () => {
|
||||
expect(castAsIntegerOrNull(null)).toBe(null);
|
||||
});
|
||||
|
||||
it(`should cast empty string to null`, () => {
|
||||
expect(castAsIntegerOrNull('')).toBe(null);
|
||||
});
|
||||
|
||||
it(`should cast an integer to an integer`, () => {
|
||||
expect(castAsIntegerOrNull(9)).toBe(9);
|
||||
});
|
||||
|
||||
it(`should cast an integer string to an integer`, () => {
|
||||
expect(castAsIntegerOrNull('9')).toBe(9);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast a float string to an integer`, () => {
|
||||
expect(() => castAsIntegerOrNull('9.9')).toThrow(Error);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast a non numeric string to an integer`, () => {
|
||||
expect(() => castAsIntegerOrNull('9.9a')).toThrow(Error);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast an undefined to an integer`, () => {
|
||||
expect(() => castAsIntegerOrNull(undefined)).toThrow(Error);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,116 @@
|
||||
import {
|
||||
canBeCastAsPositiveIntegerOrNull,
|
||||
castAsPositiveIntegerOrNull,
|
||||
} from '~/utils/cast-as-positive-integer-or-null';
|
||||
|
||||
describe('canBeCastAsPositiveIntegerOrNull', () => {
|
||||
it(`should return true if null`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull(null)).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if positive number`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull(9)).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return false if negative number`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull(-9)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return true if zero`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull(0)).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string 0`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull('0')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return false if negative float`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull(-1.22)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if positive float`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull(1.22)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if positive float string`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull('0.9')).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if negative float string`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull('-0.9')).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if less than 1`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull(0.22)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return true if empty string`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull('')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if integer string`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull('9')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return false if undefined`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull(undefined)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if non numeric string`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull('9a')).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if non numeric string #2`, () => {
|
||||
expect(canBeCastAsPositiveIntegerOrNull('a9a')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('castAsPositiveIntegerOrNull', () => {
|
||||
it(`should cast null to null`, () => {
|
||||
expect(castAsPositiveIntegerOrNull(null)).toBe(null);
|
||||
});
|
||||
|
||||
it(`should cast empty string to null`, () => {
|
||||
expect(castAsPositiveIntegerOrNull('')).toBe(null);
|
||||
});
|
||||
|
||||
it(`should cast an integer to positive integer`, () => {
|
||||
expect(castAsPositiveIntegerOrNull(9)).toBe(9);
|
||||
});
|
||||
|
||||
it(`should cast an integer string to positive integer`, () => {
|
||||
expect(castAsPositiveIntegerOrNull('9')).toBe(9);
|
||||
});
|
||||
|
||||
it(`should cast an integer to zero integer`, () => {
|
||||
expect(castAsPositiveIntegerOrNull(0)).toBe(0);
|
||||
});
|
||||
|
||||
it(`should cast an integer string to zero integer`, () => {
|
||||
expect(castAsPositiveIntegerOrNull('0')).toBe(0);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast a positive float string to positive integer`, () => {
|
||||
expect(() => castAsPositiveIntegerOrNull('9.9')).toThrow(Error);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast a negative float string to positive integer`, () => {
|
||||
expect(() => castAsPositiveIntegerOrNull('-9.9')).toThrow(Error);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast a positive float to positive integer`, () => {
|
||||
expect(() => castAsPositiveIntegerOrNull(9.9)).toThrow(Error);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast a negative float to positive integer`, () => {
|
||||
expect(() => castAsPositiveIntegerOrNull(-9.9)).toThrow(Error);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast a non numeric string to positive integer`, () => {
|
||||
expect(() => castAsPositiveIntegerOrNull('9.9a')).toThrow(Error);
|
||||
});
|
||||
|
||||
it(`should throw if trying to cast an undefined to positive integer`, () => {
|
||||
expect(() => castAsPositiveIntegerOrNull(undefined)).toThrow(Error);
|
||||
});
|
||||
});
|
||||
297
packages/twenty-front/src/utils/__tests__/date-utils.test.ts
Normal file
297
packages/twenty-front/src/utils/__tests__/date-utils.test.ts
Normal file
@ -0,0 +1,297 @@
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import {
|
||||
beautifyDateDiff,
|
||||
beautifyExactDate,
|
||||
beautifyExactDateTime,
|
||||
beautifyPastDateAbsolute,
|
||||
beautifyPastDateRelativeToNow,
|
||||
DEFAULT_DATE_LOCALE,
|
||||
hasDatePassed,
|
||||
parseDate,
|
||||
} from '../date-utils';
|
||||
import { logError } from '../logError';
|
||||
|
||||
jest.mock('~/utils/logError');
|
||||
jest.useFakeTimers().setSystemTime(new Date('2024-01-01T00:00:00.000Z'));
|
||||
|
||||
describe('beautifyExactDateTime', () => {
|
||||
it('should return the date in the correct format with time', () => {
|
||||
const mockDate = '2023-01-01T12:13:24';
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(DEFAULT_DATE_LOCALE)
|
||||
.toFormat('DD · T');
|
||||
|
||||
const result = beautifyExactDateTime(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
it('should return the time in the correct format for a datetime that is today', () => {
|
||||
const todayString = DateTime.local().toISODate();
|
||||
const mockDate = `${todayString}T12:13:24`;
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(DEFAULT_DATE_LOCALE)
|
||||
.toFormat('T');
|
||||
|
||||
const result = beautifyExactDateTime(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('beautifyExactDate', () => {
|
||||
it('should return the past date in the correct format without time', () => {
|
||||
const mockDate = '2023-01-01T12:13:24';
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(DEFAULT_DATE_LOCALE)
|
||||
.toFormat('DD');
|
||||
|
||||
const result = beautifyExactDate(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
it('should return "Today" if the date is today', () => {
|
||||
const todayString = DateTime.local().toISODate();
|
||||
const mockDate = `${todayString}T12:13:24`;
|
||||
const expected = 'Today';
|
||||
|
||||
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, { addSuffix: true });
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasDatePassed', () => {
|
||||
it('should log an error and return false when passed an invalid date string', () => {
|
||||
const result = hasDatePassed('invalid-date-string');
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "invalid-date-string"'),
|
||||
);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should log an error and return false when passed NaN', () => {
|
||||
const result = hasDatePassed(NaN);
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "NaN"'),
|
||||
);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should log an error and return false when passed invalid Date object', () => {
|
||||
const result = hasDatePassed(new Date(NaN));
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(
|
||||
Error('Invalid date passed to formatPastDate: "Invalid Date"'),
|
||||
);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true when passed past date', () => {
|
||||
const now = DateTime.local();
|
||||
const pastDate = now.minus({ day: 1 });
|
||||
|
||||
const result = hasDatePassed(pastDate.toJSDate());
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when passed future date', () => {
|
||||
const now = DateTime.local();
|
||||
const futureDate = now.plus({ days: 1 });
|
||||
|
||||
const result = hasDatePassed(futureDate.toJSDate());
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when passed current date', () => {
|
||||
const now = DateTime.local();
|
||||
|
||||
const result = hasDatePassed(now.toJSDate());
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('beautifyDateDiff', () => {
|
||||
it('should return the correct date diff', () => {
|
||||
const date = '2023-11-05T00:00:00.000Z';
|
||||
const dateToCompareWith = '2023-11-01T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date, dateToCompareWith);
|
||||
expect(result).toEqual('4 days');
|
||||
});
|
||||
it('should return the correct date diff for large diff', () => {
|
||||
const date = '2033-11-05T00:00:00.000Z';
|
||||
const dateToCompareWith = '2023-11-01T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date, dateToCompareWith);
|
||||
expect(result).toEqual('10 years and 4 days');
|
||||
});
|
||||
it('should return the correct date for negative diff', () => {
|
||||
const date = '2013-11-05T00:00:00.000Z';
|
||||
const dateToCompareWith = '2023-11-01T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date, dateToCompareWith);
|
||||
expect(result).toEqual('-9 years and -361 days');
|
||||
});
|
||||
it('should return the correct date diff for large diff', () => {
|
||||
const date = '2033-11-01T00:00:00.000Z';
|
||||
const dateToCompareWith = '2023-11-01T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date, dateToCompareWith);
|
||||
expect(result).toEqual('10 years');
|
||||
});
|
||||
it('should return the proper english date diff', () => {
|
||||
const date = '2024-11-02T00:00:00.000Z';
|
||||
const dateToCompareWith = '2023-11-01T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date, dateToCompareWith);
|
||||
expect(result).toEqual('1 year and 1 day');
|
||||
});
|
||||
it('should round date diff', () => {
|
||||
const date = '2024-11-03T14:04:43.421Z';
|
||||
const dateToCompareWith = '2023-11-01T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date, dateToCompareWith);
|
||||
expect(result).toEqual('1 year and 2 days');
|
||||
});
|
||||
it('should compare to now', () => {
|
||||
const date = '2027-01-10T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date);
|
||||
expect(result).toEqual('3 years and 9 days');
|
||||
});
|
||||
it('should return short version', () => {
|
||||
const date = '2033-11-05T00:00:00.000Z';
|
||||
const dateToCompareWith = '2023-11-01T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date, dateToCompareWith, true);
|
||||
expect(result).toEqual('10 years');
|
||||
});
|
||||
it('should return short version for short differences', () => {
|
||||
const date = '2023-11-05T00:00:00.000Z';
|
||||
const dateToCompareWith = '2023-11-01T00:00:00.000Z';
|
||||
const result = beautifyDateDiff(date, dateToCompareWith, true);
|
||||
expect(result).toEqual('4 days');
|
||||
});
|
||||
});
|
||||
35
packages/twenty-front/src/utils/__tests__/is-domain.test.ts
Normal file
35
packages/twenty-front/src/utils/__tests__/is-domain.test.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { isDomain } from '~/utils/is-domain';
|
||||
|
||||
describe('isDomain', () => {
|
||||
it(`should return false if null`, () => {
|
||||
expect(isDomain(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if undefined`, () => {
|
||||
expect(isDomain(undefined)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return true if string google`, () => {
|
||||
expect(isDomain('google')).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return true if string google.com`, () => {
|
||||
expect(isDomain('google.com')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string bbc.co.uk`, () => {
|
||||
expect(isDomain('bbc.co.uk')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string web.io`, () => {
|
||||
expect(isDomain('web.io')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string x.com`, () => {
|
||||
expect(isDomain('x.com')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string 2.com`, () => {
|
||||
expect(isDomain('2.com')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
62
packages/twenty-front/src/utils/__tests__/is-url.test.ts
Normal file
62
packages/twenty-front/src/utils/__tests__/is-url.test.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { isURL } from '~/utils/is-url';
|
||||
|
||||
describe('isURL', () => {
|
||||
it(`should return false if null`, () => {
|
||||
expect(isURL(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if undefined`, () => {
|
||||
expect(isURL(undefined)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return true if string google`, () => {
|
||||
expect(isURL('google')).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return true if string google.com`, () => {
|
||||
expect(isURL('google.com')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string bbc.co.uk`, () => {
|
||||
expect(isURL('bbc.co.uk')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string web.io`, () => {
|
||||
expect(isURL('web.io')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string x.com`, () => {
|
||||
expect(isURL('x.com')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string 2.com`, () => {
|
||||
expect(isURL('2.com')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string https://2.com/test/`, () => {
|
||||
expect(isURL('https://2.com/test/')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string is https://www.linkedin.com/company/b%C3%B6ke-&-partner-sdft-partmbb/`, () => {
|
||||
expect(
|
||||
isURL(
|
||||
'https://www.linkedin.com/company/b%C3%B6ke-&-partner-sdft-partmbb/',
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return true if the TLD is long', () => {
|
||||
expect(isURL('https://example.travelinsurance')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return true if the TLD is internationalized', () => {
|
||||
// The longest TLD as of now
|
||||
// https://stackoverflow.com/questions/9238640/how-long-can-a-tld-possibly-be
|
||||
// curl -s http://data.iana.org/TLD/tlds-alpha-by-domain.txt \
|
||||
// | tail -n+2 \
|
||||
// | awk '{ print length, $0 }' \
|
||||
// | sort --numeric-sort --reverse \
|
||||
// | head -n 5
|
||||
expect(isURL('https://example.xn--vermgensberatung-pwb')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
49
packages/twenty-front/src/utils/__tests__/utils.test.ts
Normal file
49
packages/twenty-front/src/utils/__tests__/utils.test.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { getLogoUrlFromDomainName, sanitizeURL } from '..';
|
||||
|
||||
describe('sanitizeURL', () => {
|
||||
test('should sanitize the URL correctly', () => {
|
||||
expect(sanitizeURL('http://example.com/')).toBe('example.com');
|
||||
expect(sanitizeURL('https://www.example.com/')).toBe('example.com');
|
||||
expect(sanitizeURL('www.example.com')).toBe('example.com');
|
||||
expect(sanitizeURL('example.com')).toBe('example.com');
|
||||
expect(sanitizeURL('example.com/')).toBe('example.com');
|
||||
});
|
||||
|
||||
test('should handle undefined input', () => {
|
||||
expect(sanitizeURL(undefined)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLogoUrlFromDomainName', () => {
|
||||
test('should return the correct logo URL for a given domain', () => {
|
||||
expect(getLogoUrlFromDomainName('example.com')).toBe(
|
||||
'https://favicon.twenty.com/example.com',
|
||||
);
|
||||
|
||||
expect(getLogoUrlFromDomainName('http://example.com/')).toBe(
|
||||
'https://favicon.twenty.com/example.com',
|
||||
);
|
||||
|
||||
expect(getLogoUrlFromDomainName('https://www.example.com/')).toBe(
|
||||
'https://favicon.twenty.com/example.com',
|
||||
);
|
||||
|
||||
expect(getLogoUrlFromDomainName('www.example.com')).toBe(
|
||||
'https://favicon.twenty.com/example.com',
|
||||
);
|
||||
|
||||
expect(getLogoUrlFromDomainName('example.com/')).toBe(
|
||||
'https://favicon.twenty.com/example.com',
|
||||
);
|
||||
|
||||
expect(getLogoUrlFromDomainName('apple.com')).toBe(
|
||||
'https://favicon.twenty.com/apple.com',
|
||||
);
|
||||
});
|
||||
|
||||
test('should handle undefined input', () => {
|
||||
expect(getLogoUrlFromDomainName(undefined)).toBe(
|
||||
'https://favicon.twenty.com/',
|
||||
);
|
||||
});
|
||||
});
|
||||
11
packages/twenty-front/src/utils/array/array-to-chunks.ts
Normal file
11
packages/twenty-front/src/utils/array/array-to-chunks.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// split an array into subarrays of a given size
|
||||
export const arrayToChunks = <T>(array: T[], size: number) => {
|
||||
const arrayCopy = [...array];
|
||||
const results = [];
|
||||
|
||||
while (arrayCopy.length) {
|
||||
results.push(arrayCopy.splice(0, size));
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
2
packages/twenty-front/src/utils/assert.ts
Normal file
2
packages/twenty-front/src/utils/assert.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const assertNotNull = <T>(item: T): item is NonNullable<T> =>
|
||||
item !== null && item !== undefined;
|
||||
75
packages/twenty-front/src/utils/cast-as-integer-or-null.ts
Normal file
75
packages/twenty-front/src/utils/cast-as-integer-or-null.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { isNull, isNumber, isString } from '@sniptt/guards';
|
||||
|
||||
import { logError } from './logError';
|
||||
|
||||
const DEBUG_MODE = false;
|
||||
|
||||
export const canBeCastAsIntegerOrNull = (
|
||||
probableNumberOrNull: string | undefined | number | null,
|
||||
): probableNumberOrNull is number | null => {
|
||||
if (probableNumberOrNull === undefined) {
|
||||
if (DEBUG_MODE) logError('probableNumberOrNull === undefined');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNumber(probableNumberOrNull)) {
|
||||
if (DEBUG_MODE) logError('typeof probableNumberOrNull === "number"');
|
||||
|
||||
return Number.isInteger(probableNumberOrNull);
|
||||
}
|
||||
|
||||
if (isNull(probableNumberOrNull)) {
|
||||
if (DEBUG_MODE) logError('probableNumberOrNull === null');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (probableNumberOrNull === '') {
|
||||
if (DEBUG_MODE) logError('probableNumberOrNull === ""');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isString(probableNumberOrNull)) {
|
||||
const stringAsNumber = +probableNumberOrNull;
|
||||
|
||||
if (isNaN(stringAsNumber)) {
|
||||
if (DEBUG_MODE) logError('isNaN(stringAsNumber)');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (Number.isInteger(stringAsNumber)) {
|
||||
if (DEBUG_MODE) logError('Number.isInteger(stringAsNumber)');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const castAsIntegerOrNull = (
|
||||
probableNumberOrNull: string | undefined | number | null,
|
||||
): number | null => {
|
||||
if (canBeCastAsIntegerOrNull(probableNumberOrNull) === false) {
|
||||
throw new Error('Cannot cast to number or null');
|
||||
}
|
||||
|
||||
if (isNull(probableNumberOrNull)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isString(probableNumberOrNull)) {
|
||||
if (probableNumberOrNull === '') {
|
||||
return null;
|
||||
}
|
||||
return +probableNumberOrNull;
|
||||
}
|
||||
|
||||
if (isNumber(probableNumberOrNull)) {
|
||||
return probableNumberOrNull;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -0,0 +1,65 @@
|
||||
import { isInteger, isNull, isNumber, isString } from '@sniptt/guards';
|
||||
|
||||
export const canBeCastAsPositiveIntegerOrNull = (
|
||||
probablePositiveNumberOrNull: string | undefined | number | null,
|
||||
): probablePositiveNumberOrNull is number | null => {
|
||||
if (probablePositiveNumberOrNull === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNumber(probablePositiveNumberOrNull)) {
|
||||
return (
|
||||
Number.isInteger(probablePositiveNumberOrNull) &&
|
||||
Math.sign(probablePositiveNumberOrNull) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
if (isNull(probablePositiveNumberOrNull)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (probablePositiveNumberOrNull === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isString(probablePositiveNumberOrNull)) {
|
||||
const stringAsNumber = +probablePositiveNumberOrNull;
|
||||
|
||||
if (isNaN(stringAsNumber)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isInteger(stringAsNumber) && Math.sign(stringAsNumber) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const castAsPositiveIntegerOrNull = (
|
||||
probablePositiveNumberOrNull: string | undefined | number | null,
|
||||
): number | null => {
|
||||
if (
|
||||
canBeCastAsPositiveIntegerOrNull(probablePositiveNumberOrNull) === false
|
||||
) {
|
||||
throw new Error('Cannot cast to positive number or null');
|
||||
}
|
||||
|
||||
if (probablePositiveNumberOrNull === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isString(probablePositiveNumberOrNull)) {
|
||||
if (probablePositiveNumberOrNull === '') {
|
||||
return null;
|
||||
}
|
||||
return +probablePositiveNumberOrNull;
|
||||
}
|
||||
|
||||
if (isNumber(probablePositiveNumberOrNull)) {
|
||||
return probablePositiveNumberOrNull;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
29
packages/twenty-front/src/utils/convert-currency-amount.ts
Normal file
29
packages/twenty-front/src/utils/convert-currency-amount.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export const convertCurrencyToCurrencyMicros = (
|
||||
currencyAmount: number | null | undefined,
|
||||
) => {
|
||||
if (currencyAmount == null) {
|
||||
return null;
|
||||
}
|
||||
const currencyAmountAsNumber = +currencyAmount;
|
||||
if (isNaN(currencyAmountAsNumber)) {
|
||||
throw new Error(`Cannot convert ${currencyAmount} to micros`);
|
||||
}
|
||||
const currencyAmountAsMicros = currencyAmountAsNumber * 1000000;
|
||||
if (currencyAmountAsMicros % 1 !== 0) {
|
||||
throw new Error(`Cannot convert ${currencyAmount} to micros`);
|
||||
}
|
||||
return currencyAmountAsMicros;
|
||||
};
|
||||
|
||||
export const convertCurrencyMicrosToCurrency = (
|
||||
currencyAmountMicros: number | null | undefined,
|
||||
) => {
|
||||
if (currencyAmountMicros == null) {
|
||||
return null;
|
||||
}
|
||||
const currencyAmountMicrosAsNumber = +currencyAmountMicros;
|
||||
if (isNaN(currencyAmountMicrosAsNumber)) {
|
||||
throw new Error(`Cannot convert ${currencyAmountMicros} to currency`);
|
||||
}
|
||||
return currencyAmountMicrosAsNumber / 1000000;
|
||||
};
|
||||
29
packages/twenty-front/src/utils/cookie-storage.ts
Normal file
29
packages/twenty-front/src/utils/cookie-storage.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import Cookies 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?: Cookies.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();
|
||||
129
packages/twenty-front/src/utils/date-utils.ts
Normal file
129
packages/twenty-front/src/utils/date-utils.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { isDate, isNumber, isString } from '@sniptt/guards';
|
||||
import { differenceInCalendarDays, formatDistanceToNow } from 'date-fns';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { logError } from './logError';
|
||||
|
||||
export const DEFAULT_DATE_LOCALE = 'en-EN';
|
||||
|
||||
export const parseDate = (dateToParse: Date | string | number) => {
|
||||
let formattedDate: DateTime | null = null;
|
||||
|
||||
if (!dateToParse) {
|
||||
throw new Error(`Invalid date passed to formatPastDate: "${dateToParse}"`);
|
||||
} else if (isString(dateToParse)) {
|
||||
formattedDate = DateTime.fromISO(dateToParse);
|
||||
} else if (isDate(dateToParse)) {
|
||||
formattedDate = DateTime.fromJSDate(dateToParse);
|
||||
} else if (isNumber(dateToParse)) {
|
||||
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);
|
||||
};
|
||||
|
||||
const isSameDay = (a: DateTime, b: DateTime): boolean =>
|
||||
a.hasSame(b, 'day') && a.hasSame(b, 'month') && a.hasSame(b, 'year');
|
||||
|
||||
const formatDate = (dateToFormat: Date | string | number, format: string) => {
|
||||
try {
|
||||
const parsedDate = parseDate(dateToFormat);
|
||||
return parsedDate.toFormat(format);
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const beautifyExactDateTime = (
|
||||
dateToBeautify: Date | string | number,
|
||||
) => {
|
||||
const isToday = isSameDay(parseDate(dateToBeautify), DateTime.local());
|
||||
const dateFormat = isToday ? 'T' : 'DD · T';
|
||||
return formatDate(dateToBeautify, dateFormat);
|
||||
};
|
||||
|
||||
export const beautifyExactDate = (dateToBeautify: Date | string | number) => {
|
||||
const isToday = isSameDay(parseDate(dateToBeautify), DateTime.local());
|
||||
const dateFormat = isToday ? "'Today'" : 'DD';
|
||||
return formatDate(dateToBeautify, dateFormat);
|
||||
};
|
||||
|
||||
export const beautifyPastDateRelativeToNow = (
|
||||
pastDate: Date | string | number,
|
||||
) => {
|
||||
try {
|
||||
const parsedDate = parseDate(pastDate);
|
||||
|
||||
return formatDistanceToNow(parsedDate.toJSDate(), {
|
||||
addSuffix: true,
|
||||
}).replace('less than a minute ago', 'now');
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const 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 '';
|
||||
}
|
||||
};
|
||||
|
||||
export const hasDatePassed = (date: Date | string | number) => {
|
||||
try {
|
||||
const parsedDate = parseDate(date);
|
||||
|
||||
return (
|
||||
differenceInCalendarDays(
|
||||
DateTime.local().toJSDate(),
|
||||
parsedDate.toJSDate(),
|
||||
) >= 1
|
||||
);
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const beautifyDateDiff = (
|
||||
date: string,
|
||||
dateToCompareWith?: string,
|
||||
short: boolean = false,
|
||||
) => {
|
||||
const dateDiff = DateTime.fromISO(date).diff(
|
||||
dateToCompareWith ? DateTime.fromISO(dateToCompareWith) : DateTime.now(),
|
||||
['years', 'days'],
|
||||
);
|
||||
let result = '';
|
||||
if (dateDiff.years) result = result + `${dateDiff.years} year`;
|
||||
if (![0, 1].includes(dateDiff.years)) result = result + 's';
|
||||
if (short && dateDiff.years) return result;
|
||||
if (dateDiff.years && dateDiff.days) result = result + ' and ';
|
||||
if (dateDiff.days) result = result + `${Math.floor(dateDiff.days)} day`;
|
||||
if (![0, 1].includes(dateDiff.days)) result = result + 's';
|
||||
return result;
|
||||
};
|
||||
12
packages/twenty-front/src/utils/debounce.ts
Normal file
12
packages/twenty-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);
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
import { formatNumber } from '../number';
|
||||
|
||||
// This tests the en-US locale by default
|
||||
describe('formatNumber', () => {
|
||||
it(`Should format 123 correctly`, () => {
|
||||
expect(formatNumber(123)).toEqual('123');
|
||||
});
|
||||
it(`Should format decimal numbers correctly`, () => {
|
||||
expect(formatNumber(123.92)).toEqual('123.92');
|
||||
});
|
||||
it(`Should format large numbers correctly`, () => {
|
||||
expect(formatNumber(1234567)).toEqual('1,234,567');
|
||||
});
|
||||
it(`Should format large numbers with a decimal point correctly`, () => {
|
||||
expect(formatNumber(7654321.89)).toEqual('7,654,321.89');
|
||||
});
|
||||
});
|
||||
1
packages/twenty-front/src/utils/format/number.ts
Normal file
1
packages/twenty-front/src/utils/format/number.ts
Normal file
@ -0,0 +1 @@
|
||||
export const formatNumber = (value: number): string => value.toLocaleString();
|
||||
24
packages/twenty-front/src/utils/index.ts
Normal file
24
packages/twenty-front/src/utils/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { parseDate } from './date-utils';
|
||||
|
||||
export const formatToHumanReadableDate = (date: Date | string) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(parsedJSDate);
|
||||
};
|
||||
|
||||
export const sanitizeURL = (link: string | null | undefined) => {
|
||||
return link
|
||||
? link.replace(/(https?:\/\/)|(www\.)/g, '').replace(/\/$/, '')
|
||||
: '';
|
||||
};
|
||||
|
||||
export const getLogoUrlFromDomainName = (
|
||||
domainName?: string,
|
||||
): string | undefined => {
|
||||
const sanitizedDomain = sanitizeURL(domainName);
|
||||
return `https://favicon.twenty.com/${sanitizedDomain}`;
|
||||
};
|
||||
7
packages/twenty-front/src/utils/is-domain.ts
Normal file
7
packages/twenty-front/src/utils/is-domain.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { isDefined } from './isDefined';
|
||||
|
||||
export const isDomain = (url: string | undefined | null) =>
|
||||
isDefined(url) &&
|
||||
/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/.test(
|
||||
url,
|
||||
);
|
||||
7
packages/twenty-front/src/utils/is-url.ts
Normal file
7
packages/twenty-front/src/utils/is-url.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { isDefined } from './isDefined';
|
||||
|
||||
export const isURL = (url: string | undefined | null) =>
|
||||
isDefined(url) &&
|
||||
url.match(
|
||||
/^(https?:\/\/)?(www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/i,
|
||||
);
|
||||
3
packages/twenty-front/src/utils/isDeeplyEqual.ts
Normal file
3
packages/twenty-front/src/utils/isDeeplyEqual.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import deepEqual from 'deep-equal';
|
||||
|
||||
export const isDeeplyEqual = <T>(a: T, b: T) => deepEqual(a, b);
|
||||
3
packages/twenty-front/src/utils/isDefined.ts
Normal file
3
packages/twenty-front/src/utils/isDefined.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const isDefined = <T>(
|
||||
value: T | undefined | null,
|
||||
): value is NonNullable<T> => value !== undefined && value !== null;
|
||||
13
packages/twenty-front/src/utils/isNonEmptyArray.ts
Normal file
13
packages/twenty-front/src/utils/isNonEmptyArray.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export const isNonEmptyArray = <T>(
|
||||
probableArray: T[] | readonly T[] | undefined | null,
|
||||
): probableArray is NonNullable<T[]> => {
|
||||
if (
|
||||
Array.isArray(probableArray) &&
|
||||
probableArray.length &&
|
||||
probableArray.length > 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
4
packages/twenty-front/src/utils/logDebug.ts
Normal file
4
packages/twenty-front/src/utils/logDebug.ts
Normal file
@ -0,0 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
export const logDebug = (message: any, ...optionalParams: any[]) => {
|
||||
console.debug(message, optionalParams);
|
||||
};
|
||||
4
packages/twenty-front/src/utils/logError.ts
Normal file
4
packages/twenty-front/src/utils/logError.ts
Normal file
@ -0,0 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
export const logError = (message: any) => {
|
||||
console.error(message);
|
||||
};
|
||||
16
packages/twenty-front/src/utils/promise-to-observable.ts
Normal file
16
packages/twenty-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),
|
||||
);
|
||||
});
|
||||
39
packages/twenty-front/src/utils/recoil-effects.ts
Normal file
39
packages/twenty-front/src/utils/recoil-effects.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { AtomEffect } from 'recoil';
|
||||
|
||||
import { cookieStorage } from '~/utils/cookie-storage';
|
||||
|
||||
export const localStorageEffect =
|
||||
<T>(key: string): AtomEffect<T> =>
|
||||
({ setSelf, onSet }) => {
|
||||
const savedValue = localStorage.getItem(key);
|
||||
if (savedValue != null) {
|
||||
setSelf(JSON.parse(savedValue));
|
||||
}
|
||||
|
||||
onSet((newValue, _, isReset) => {
|
||||
isReset
|
||||
? localStorage.removeItem(key)
|
||||
: localStorage.setItem(key, JSON.stringify(newValue));
|
||||
});
|
||||
};
|
||||
|
||||
export const cookieStorageEffect =
|
||||
<T>(key: string): AtomEffect<T | null> =>
|
||||
({ setSelf, onSet }) => {
|
||||
const savedValue = cookieStorage.getItem(key);
|
||||
if (savedValue != null && JSON.parse(savedValue)['accessToken']) {
|
||||
setSelf(JSON.parse(savedValue));
|
||||
}
|
||||
|
||||
onSet((newValue, _, isReset) => {
|
||||
if (!newValue) {
|
||||
cookieStorage.removeItem(key);
|
||||
return;
|
||||
}
|
||||
isReset
|
||||
? cookieStorage.removeItem(key)
|
||||
: cookieStorage.setItem(key, JSON.stringify(newValue), {
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7),
|
||||
});
|
||||
});
|
||||
};
|
||||
13
packages/twenty-front/src/utils/string-to-hsl.ts
Normal file
13
packages/twenty-front/src/utils/string-to-hsl.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export const 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 + '%)';
|
||||
};
|
||||
7
packages/twenty-front/src/utils/string/capitalize.ts
Normal file
7
packages/twenty-front/src/utils/string/capitalize.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
export const capitalize = (stringToCapitalize: string) => {
|
||||
if (!isNonEmptyString(stringToCapitalize)) return '';
|
||||
|
||||
return stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1);
|
||||
};
|
||||
34
packages/twenty-front/src/utils/title-utils.ts
Normal file
34
packages/twenty-front/src/utils/title-utils.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { AppBasePath } from '@/types/AppBasePath';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
|
||||
export const getPageTitleFromPath = (pathname: string): string => {
|
||||
switch (pathname) {
|
||||
case AppPath.Verify:
|
||||
return 'Verify';
|
||||
case AppPath.SignIn:
|
||||
return 'Sign In';
|
||||
case AppPath.SignUp:
|
||||
return 'Sign Up';
|
||||
case AppPath.Invite:
|
||||
return 'Invite';
|
||||
case AppPath.CreateWorkspace:
|
||||
return 'Create Workspace';
|
||||
case AppPath.CreateProfile:
|
||||
return 'Create Profile';
|
||||
case AppPath.TasksPage:
|
||||
return 'Tasks';
|
||||
case AppPath.OpportunitiesPage:
|
||||
return 'Opportunities';
|
||||
case `${AppBasePath.Settings}/${SettingsPath.ProfilePage}`:
|
||||
return 'Profile';
|
||||
case `${AppBasePath.Settings}/${SettingsPath.Appearance}`:
|
||||
return 'Appearance';
|
||||
case `${AppBasePath.Settings}/${SettingsPath.WorkspaceMembersPage}`:
|
||||
return 'Workspace Members';
|
||||
case `${AppBasePath.Settings}/${SettingsPath.Workspace}`:
|
||||
return 'Workspace';
|
||||
default:
|
||||
return 'Twenty';
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user