Add Twenty Shared & Fix profile image rendering (#8841)

PR Summary: 

1. Added `Twenty Shared` Package to centralize utilitiies as mentioned
in #8942
2. Optimization of `getImageAbsoluteURI.ts` to handle edge cases


![image](https://github.com/user-attachments/assets/c72a3061-6eba-46b8-85ac-869f06bf23c0)

---------

Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Mohammed Abdul Razak Wahab
2024-12-17 13:54:21 +05:30
committed by GitHub
parent 4e329d08b0
commit 08a9db2df6
39 changed files with 453 additions and 129 deletions

View File

@ -0,0 +1,15 @@
module.exports = {
extends: ['../../.eslintrc.cjs'],
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: ['packages/twenty-shared/tsconfig.{json,*.json}'],
},
rules: {
'@nx/dependency-checks': 'error',
},
},
],
};

1
packages/twenty-shared/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

View File

@ -0,0 +1,39 @@
import { JestConfigWithTsJest, pathsToModuleNameMapper } from 'ts-jest';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const tsConfig = require('./tsconfig.json');
const jestConfig: JestConfigWithTsJest = {
displayName: 'twenty-ui',
preset: '../../jest.preset.js',
testEnvironment: 'jsdom',
transformIgnorePatterns: ['../../node_modules/'],
transform: {
'^.+\\.[tj]sx?$': [
'@swc/jest',
{
jsc: {
parser: { syntax: 'typescript', tsx: true },
transform: { react: { runtime: 'automatic' } },
},
},
],
},
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|webp|svg|svg\\?react)$':
'<rootDir>/__mocks__/imageMock.js',
...pathsToModuleNameMapper(tsConfig.compilerOptions.paths),
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
extensionsToTreatAsEsm: ['.ts', '.tsx'],
coverageDirectory: './coverage',
coverageThreshold: {
global: {
statements: 100,
lines: 100,
functions: 100,
},
},
};
export default jestConfig;

View File

@ -0,0 +1,20 @@
{
"name": "twenty-shared",
"version": "0.40.0-canary",
"license": "AGPL-3.0",
"main": "./dist/index.js",
"scripts": {
"build": "npx vite build"
},
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
}
},
"engines": {
"node": "^18.17.1",
"npm": "please-use-yarn",
"yarn": "^4.0.2"
}
}

View File

@ -0,0 +1,38 @@
{
"name": "twenty-shared",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/twenty-shared/src",
"projectType": "library",
"tags": ["scope:shared"],
"targets": {
"build": {
"dependsOn": ["^build"],
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "{projectRoot}/dist"
}
},
"typecheck": {},
"test": {},
"lint": {
"options": {
"lintFilePatterns": [
"{projectRoot}/src/**/*.{ts,tsx,json}",
"{projectRoot}/package.json"
],
"reportUnusedDisableDirectives": "error"
},
"configurations": {
"fix": {}
}
},
"fmt": {
"options": {
"files": "src"
},
"configurations": {
"fix": {}
}
}
}
}

View File

@ -0,0 +1 @@
export * from './utils/image/getImageAbsoluteURI';

View File

@ -0,0 +1,38 @@
import { getImageAbsoluteURI } from '../getImageAbsoluteURI';
describe('getImageAbsoluteURI', () => {
it('should return baseUrl if imageUrl is empty string', () => {
const imageUrl = '';
const baseUrl = 'http://localhost:3000';
const result = getImageAbsoluteURI({ imageUrl, baseUrl });
expect(result).toBe('http://localhost:3000/files/');
});
it('should return absolute url if the imageUrl is an absolute url', () => {
const imageUrl = 'https://XXX';
const baseUrl = 'http://localhost:3000';
const result = getImageAbsoluteURI({ imageUrl, baseUrl });
expect(result).toBe(imageUrl);
});
it('should return fully formed url if imageUrl is a relative url starting with /', () => {
const imageUrl = '/path/pic.png';
const baseUrl = 'http://localhost:3000';
const result = getImageAbsoluteURI({ imageUrl, baseUrl });
expect(result).toBe('http://localhost:3000/files/path/pic.png');
});
it('should return fully formed url if imageUrl is a relative url nost starting with slash', () => {
const imageUrl = 'pic.png';
const baseUrl = 'http://localhost:3000';
const result = getImageAbsoluteURI({ imageUrl, baseUrl });
expect(result).toBe('http://localhost:3000/files/pic.png');
});
it('should handle queryParameters in the imageUrl', () => {
const imageUrl = '/pic.png?token=XXX';
const baseUrl = 'http://localhost:3000';
const result = getImageAbsoluteURI({ imageUrl, baseUrl });
expect(result).toBe('http://localhost:3000/files/pic.png?token=XXX');
});
});

View File

@ -0,0 +1,19 @@
type getImageAbsoluteURIProps = {
imageUrl: string;
baseUrl: string;
};
export const getImageAbsoluteURI = ({
imageUrl,
baseUrl,
}: getImageAbsoluteURIProps): string => {
if (imageUrl.startsWith('https:') || imageUrl.startsWith('http:')) {
return imageUrl;
}
if (imageUrl.startsWith('/')) {
return new URL(`/files${imageUrl}`, baseUrl).toString();
}
return new URL(`/files/${imageUrl}`, baseUrl).toString();
};

View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"types": ["vite/client"],
"baseUrl": ".",
"paths": {
"twenty-shared": ["packages/twenty-shared/dist"]
}
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
}

View File

@ -0,0 +1,22 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../.cache/tsc",
"types": [
"node",
"vite/client"
]
},
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"],
}

View File

@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/__mocks__/**/*",
"jest.config.ts",
"src/**/*.d.ts",
"src/**/*.spec.ts",
"src/**/*.spec.tsx",
"src/**/*.test.ts",
"src/**/*.test.tsx",
"vite.config.ts"
]
}

View File

@ -0,0 +1,33 @@
import * as path from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/packages/twenty-shared',
plugins: [
tsconfigPaths(),
dts({
entryRoot: 'src',
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
entry: 'src/index.ts',
name: 'twenty-shared',
fileName: 'index',
formats: ['es', 'cjs'],
},
},
});