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  --------- Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
committed by
GitHub
parent
4e329d08b0
commit
08a9db2df6
1
.github/workflows/ci-front.yaml
vendored
1
.github/workflows/ci-front.yaml
vendored
@ -35,6 +35,7 @@ jobs:
|
|||||||
package.json
|
package.json
|
||||||
packages/twenty-front/**
|
packages/twenty-front/**
|
||||||
packages/twenty-ui/**
|
packages/twenty-ui/**
|
||||||
|
packages/twenty-shared/**
|
||||||
|
|
||||||
- name: Skip if no relevant changes
|
- name: Skip if no relevant changes
|
||||||
if: steps.changed-files.outputs.any_changed == 'false'
|
if: steps.changed-files.outputs.any_changed == 'false'
|
||||||
|
|||||||
5
.github/workflows/ci-server.yaml
vendored
5
.github/workflows/ci-server.yaml
vendored
@ -49,10 +49,14 @@ jobs:
|
|||||||
package.json
|
package.json
|
||||||
packages/twenty-server/**
|
packages/twenty-server/**
|
||||||
packages/twenty-emails/**
|
packages/twenty-emails/**
|
||||||
|
packages/twenty-shared/**
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: ./.github/workflows/actions/yarn-install
|
uses: ./.github/workflows/actions/yarn-install
|
||||||
|
- name: Build twenty-shared
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
run: npx nx build twenty-shared
|
||||||
- name: Server / Restore Task Cache
|
- name: Server / Restore Task Cache
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: ./.github/workflows/actions/task-cache
|
uses: ./.github/workflows/actions/task-cache
|
||||||
@ -120,6 +124,7 @@ jobs:
|
|||||||
package.json
|
package.json
|
||||||
packages/twenty-server/**
|
packages/twenty-server/**
|
||||||
packages/twenty-emails/**
|
packages/twenty-emails/**
|
||||||
|
packages/twenty-shared/**
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
|||||||
48
.github/workflows/ci-shared.yaml
vendored
Normal file
48
.github/workflows/ci-shared.yaml
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
name: CI Shared
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
shared-test:
|
||||||
|
timeout-minutes: 30
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
task: [lint, typecheck, test]
|
||||||
|
steps:
|
||||||
|
- name: Cancel Previous Runs
|
||||||
|
uses: styfle/cancel-workflow-action@0.11.0
|
||||||
|
with:
|
||||||
|
access_token: ${{ github.token }}
|
||||||
|
- name: Fetch custom Github Actions and base branch history
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Check for changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v11
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
packages/twenty-shared/**
|
||||||
|
- name: Skip if no relevant changes
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'false'
|
||||||
|
run: echo "No relevant changes. Skipping CI."
|
||||||
|
- name: Install dependencies
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
uses: ./.github/workflows/actions/yarn-install
|
||||||
|
- name: Run ${{ matrix.task }} task
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
uses: ./.github/workflows/actions/nx-affected
|
||||||
|
with:
|
||||||
|
tag: scope:frontend
|
||||||
|
tasks: ${{ matrix.task }}
|
||||||
12
nx.json
12
nx.json
@ -47,7 +47,8 @@
|
|||||||
"configurations": {
|
"configurations": {
|
||||||
"ci": { "cacheStrategy": "content" },
|
"ci": { "cacheStrategy": "content" },
|
||||||
"fix": { "fix": true }
|
"fix": { "fix": true }
|
||||||
}
|
},
|
||||||
|
"dependsOn": ["^build"]
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"executor": "nx:run-commands",
|
"executor": "nx:run-commands",
|
||||||
@ -63,7 +64,8 @@
|
|||||||
"configurations": {
|
"configurations": {
|
||||||
"ci": { "cacheStrategy": "content" },
|
"ci": { "cacheStrategy": "content" },
|
||||||
"fix": { "write": true }
|
"fix": { "write": true }
|
||||||
}
|
},
|
||||||
|
"dependsOn": ["^build"]
|
||||||
},
|
},
|
||||||
"typecheck": {
|
"typecheck": {
|
||||||
"executor": "nx:run-commands",
|
"executor": "nx:run-commands",
|
||||||
@ -74,7 +76,8 @@
|
|||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"watch": { "watch": true }
|
"watch": { "watch": true }
|
||||||
}
|
},
|
||||||
|
"dependsOn": ["^build"]
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"executor": "@nx/jest:jest",
|
"executor": "@nx/jest:jest",
|
||||||
@ -115,7 +118,8 @@
|
|||||||
"command": "VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true storybook build",
|
"command": "VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true storybook build",
|
||||||
"output-dir": "storybook-static",
|
"output-dir": "storybook-static",
|
||||||
"config-dir": ".storybook"
|
"config-dir": ".storybook"
|
||||||
}
|
},
|
||||||
|
"dependsOn": ["^build"]
|
||||||
},
|
},
|
||||||
"storybook:serve:dev": {
|
"storybook:serve:dev": {
|
||||||
"executor": "nx:run-commands",
|
"executor": "nx:run-commands",
|
||||||
|
|||||||
@ -249,7 +249,6 @@
|
|||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/graphql-fields": "^1.3.6",
|
"@types/graphql-fields": "^1.3.6",
|
||||||
"@types/graphql-upload": "^8.0.12",
|
"@types/graphql-upload": "^8.0.12",
|
||||||
"@types/jest": "^29.5.11",
|
|
||||||
"@types/js-cookie": "^3.0.3",
|
"@types/js-cookie": "^3.0.3",
|
||||||
"@types/js-levenshtein": "^1.1.3",
|
"@types/js-levenshtein": "^1.1.3",
|
||||||
"@types/lodash.camelcase": "^4.3.7",
|
"@types/lodash.camelcase": "^4.3.7",
|
||||||
@ -365,6 +364,7 @@
|
|||||||
"packages/twenty-zapier",
|
"packages/twenty-zapier",
|
||||||
"packages/twenty-website",
|
"packages/twenty-website",
|
||||||
"packages/twenty-e2e-testing",
|
"packages/twenty-e2e-testing",
|
||||||
|
"packages/twenty-shared",
|
||||||
"tools/eslint-rules"
|
"tools/eslint-rules"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { MainText } from 'src/components/MainText';
|
|||||||
import { Title } from 'src/components/Title';
|
import { Title } from 'src/components/Title';
|
||||||
import { WhatIsTwenty } from 'src/components/WhatIsTwenty';
|
import { WhatIsTwenty } from 'src/components/WhatIsTwenty';
|
||||||
import { capitalize } from 'src/utils/capitalize';
|
import { capitalize } from 'src/utils/capitalize';
|
||||||
import { getImageAbsoluteURI } from 'src/utils/getImageAbsoluteURI';
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
|
|
||||||
type SendInviteLinkEmailProps = {
|
type SendInviteLinkEmailProps = {
|
||||||
link: string;
|
link: string;
|
||||||
@ -30,7 +30,7 @@ export const SendInviteLinkEmail = ({
|
|||||||
serverUrl,
|
serverUrl,
|
||||||
}: SendInviteLinkEmailProps) => {
|
}: SendInviteLinkEmailProps) => {
|
||||||
const workspaceLogo = workspace.logo
|
const workspaceLogo = workspace.logo
|
||||||
? getImageAbsoluteURI(workspace.logo, serverUrl)
|
? getImageAbsoluteURI({ imageUrl: workspace.logo, baseUrl: serverUrl })
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
export const getImageAbsoluteURI = (imageUrl: string, serverUrl: string) => {
|
|
||||||
if (imageUrl.startsWith('https:') || imageUrl.startsWith('http:')) {
|
|
||||||
return imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return serverUrl.endsWith('/')
|
|
||||||
? `${serverUrl.substring(0, serverUrl.length - 1)}/files/${imageUrl}`
|
|
||||||
: `${serverUrl}/files/${imageUrl}`;
|
|
||||||
};
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"type": "module",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
@ -6,7 +7,10 @@
|
|||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"types": ["vite/client"],
|
"types": ["vite/client"],
|
||||||
"baseUrl": "."
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"twenty-shared": ["../../packages/twenty-shared/dist"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"files": [],
|
"files": [],
|
||||||
"include": [],
|
"include": [],
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
||||||
<link rel="icon" href="/icons/android/android-launchericon-48-48.png" />
|
<link rel="icon" type="image/x-icon" href="/icons/android/android-launchericon-48-48.png" data-rh="true"/>
|
||||||
<link rel="apple-touch-icon" href="/icons/ios/192.png" />
|
<link rel="apple-touch-icon" href="/icons/ios/192.png" />
|
||||||
|
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { getImageAbsoluteURI } from 'twenty-ui';
|
|
||||||
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
type LogoProps = {
|
type LogoProps = {
|
||||||
primaryLogo?: string | null;
|
primaryLogo?: string | null;
|
||||||
@ -46,16 +48,21 @@ const StyledPrimaryLogo = styled.div<{ src: string }>`
|
|||||||
export const Logo = (props: LogoProps) => {
|
export const Logo = (props: LogoProps) => {
|
||||||
const defaultPrimaryLogoUrl = `${window.location.origin}/icons/android/android-launchericon-192-192.png`;
|
const defaultPrimaryLogoUrl = `${window.location.origin}/icons/android/android-launchericon-192-192.png`;
|
||||||
|
|
||||||
const primaryLogoUrl = getImageAbsoluteURI(
|
const primaryLogoUrl = getImageAbsoluteURI({
|
||||||
props.primaryLogo ?? defaultPrimaryLogoUrl,
|
imageUrl: props.primaryLogo ?? defaultPrimaryLogoUrl,
|
||||||
);
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
});
|
||||||
|
|
||||||
const secondaryLogoUrl = isNonEmptyString(props.secondaryLogo)
|
const secondaryLogoUrl = isNonEmptyString(props.secondaryLogo)
|
||||||
? getImageAbsoluteURI(props.secondaryLogo)
|
? getImageAbsoluteURI({
|
||||||
|
imageUrl: props.secondaryLogo,
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledPrimaryLogo src={primaryLogoUrl} />
|
<StyledPrimaryLogo src={primaryLogoUrl ?? ''} />
|
||||||
{secondaryLogoUrl && (
|
{secondaryLogoUrl && (
|
||||||
<StyledSecondaryLogoContainer>
|
<StyledSecondaryLogoContainer>
|
||||||
<StyledSecondaryLogo src={secondaryLogoUrl} />
|
<StyledSecondaryLogo src={secondaryLogoUrl} />
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
|||||||
workspacePublicDataState,
|
workspacePublicDataState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { loading } = useGetPublicWorkspaceDataBySubdomainQuery({
|
const { loading, data, error } = useGetPublicWorkspaceDataBySubdomainQuery({
|
||||||
skip:
|
skip:
|
||||||
(isMultiWorkspaceEnabled && isDefaultDomain) ||
|
(isMultiWorkspaceEnabled && isDefaultDomain) ||
|
||||||
isDefined(workspacePublicData),
|
isDefined(workspacePublicData),
|
||||||
@ -38,5 +38,7 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
|
data: data?.getPublicWorkspaceDataBySubdomain,
|
||||||
|
error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,11 +8,14 @@ import {
|
|||||||
NavigationDrawerProps,
|
NavigationDrawerProps,
|
||||||
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
|
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
|
||||||
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
||||||
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
|
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
|
||||||
|
|
||||||
import { AdvancedSettingsToggle, getImageAbsoluteURI } from 'twenty-ui';
|
import { MainNavigationDrawerItems } from '@/navigation/components/MainNavigationDrawerItems';
|
||||||
import { MainNavigationDrawerItems } from './MainNavigationDrawerItems';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
import { AdvancedSettingsToggle } from 'twenty-ui';
|
||||||
|
|
||||||
export type AppNavigationDrawerProps = {
|
export type AppNavigationDrawerProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -40,10 +43,12 @@ export const AppNavigationDrawer = ({
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
logo:
|
logo: isNonEmptyString(currentWorkspace?.logo)
|
||||||
(currentWorkspace?.logo &&
|
? getImageAbsoluteURI({
|
||||||
getImageAbsoluteURI(currentWorkspace.logo)) ??
|
imageUrl: currentWorkspace.logo,
|
||||||
undefined,
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
title: currentWorkspace?.displayName ?? undefined,
|
title: currentWorkspace?.displayName ?? undefined,
|
||||||
children: <MainNavigationDrawerItems />,
|
children: <MainNavigationDrawerItems />,
|
||||||
footer: <SupportDropdown />,
|
footer: <SupportDropdown />,
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
|
import { Company } from '@/companies/types/Company';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { getLogoUrlFromDomainName } from '~/utils';
|
import { getLogoUrlFromDomainName } from '~/utils';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { Company } from '@/companies/types/Company';
|
|
||||||
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
|
|
||||||
import { getImageAbsoluteURI } from 'twenty-ui';
|
|
||||||
import { getImageIdentifierFieldValue } from './getImageIdentifierFieldValue';
|
import { getImageIdentifierFieldValue } from './getImageIdentifierFieldValue';
|
||||||
|
|
||||||
export const getAvatarUrl = (
|
export const getAvatarUrl = (
|
||||||
@ -26,7 +26,10 @@ export const getAvatarUrl = (
|
|||||||
|
|
||||||
if (objectNameSingular === CoreObjectNameSingular.Person) {
|
if (objectNameSingular === CoreObjectNameSingular.Person) {
|
||||||
return isDefined(record.avatarUrl)
|
return isDefined(record.avatarUrl)
|
||||||
? getImageAbsoluteURI(record.avatarUrl)
|
? getImageAbsoluteURI({
|
||||||
|
imageUrl: record.avatarUrl,
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
})
|
||||||
: '';
|
: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,9 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
Button,
|
import { Button, IconPhotoUp, IconTrash, IconUpload, IconX } from 'twenty-ui';
|
||||||
IconPhotoUp,
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
IconTrash,
|
|
||||||
IconUpload,
|
|
||||||
IconX,
|
|
||||||
getImageAbsoluteURI,
|
|
||||||
} from 'twenty-ui';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@ -117,7 +112,10 @@ export const ImageInput = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const pictureURI = isNonEmptyString(picture)
|
const pictureURI = isNonEmptyString(picture)
|
||||||
? getImageAbsoluteURI(picture)
|
? getImageAbsoluteURI({
|
||||||
|
imageUrl: picture,
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -14,12 +14,13 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
import {
|
import {
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
MenuItemSelectAvatar,
|
MenuItemSelectAvatar,
|
||||||
UndecoratedLink,
|
UndecoratedLink,
|
||||||
getImageAbsoluteURI,
|
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
const StyledLogo = styled.div<{ logo: string }>`
|
const StyledLogo = styled.div<{ logo: string }>`
|
||||||
background: url(${({ logo }) => logo});
|
background: url(${({ logo }) => logo});
|
||||||
@ -102,9 +103,12 @@ export const MultiWorkspaceDropdownButton = ({
|
|||||||
isNavigationDrawerExpanded={isNavigationDrawerExpanded}
|
isNavigationDrawerExpanded={isNavigationDrawerExpanded}
|
||||||
>
|
>
|
||||||
<StyledLogo
|
<StyledLogo
|
||||||
logo={getImageAbsoluteURI(
|
logo={
|
||||||
currentWorkspace?.logo ?? DEFAULT_WORKSPACE_LOGO,
|
getImageAbsoluteURI({
|
||||||
)}
|
imageUrl: currentWorkspace?.logo ?? '',
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
}) ?? ''
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<NavigationDrawerAnimatedCollapseWrapper>
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
<StyledLabel>{currentWorkspace?.displayName ?? ''}</StyledLabel>
|
<StyledLabel>{currentWorkspace?.displayName ?? ''}</StyledLabel>
|
||||||
@ -132,9 +136,12 @@ export const MultiWorkspaceDropdownButton = ({
|
|||||||
text={workspace.displayName ?? '(No name)'}
|
text={workspace.displayName ?? '(No name)'}
|
||||||
avatar={
|
avatar={
|
||||||
<StyledLogo
|
<StyledLogo
|
||||||
logo={getImageAbsoluteURI(
|
logo={
|
||||||
workspace.logo ?? DEFAULT_WORKSPACE_LOGO,
|
getImageAbsoluteURI({
|
||||||
)}
|
imageUrl: workspace.logo ?? DEFAULT_WORKSPACE_LOGO,
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
}) ?? ''
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
selected={currentWorkspace?.id === workspace.id}
|
selected={currentWorkspace?.id === workspace.id}
|
||||||
|
|||||||
@ -1,18 +1,23 @@
|
|||||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||||
import { Helmet } from 'react-helmet-async';
|
import { Helmet } from 'react-helmet-async';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { getImageAbsoluteURI } from 'twenty-ui';
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
export const PageFavicon = () => {
|
export const PageFavicon = () => {
|
||||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Helmet>
|
<Helmet>
|
||||||
{workspacePublicData?.logo && (
|
{workspacePublicData?.logo && (
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
type="image/x-icon"
|
type="image/x-icon"
|
||||||
href={getImageAbsoluteURI(workspacePublicData.logo)}
|
href={
|
||||||
|
getImageAbsoluteURI({
|
||||||
|
imageUrl: workspacePublicData.logo,
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
}) ?? ''
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
@ -9,8 +8,10 @@ import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/l
|
|||||||
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation';
|
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation';
|
||||||
|
|
||||||
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||||
|
import { useGetPublicWorkspaceDataBySubdomain } from '@/domain-manager/hooks/useGetPublicWorkspaceDataBySubdomain';
|
||||||
export const WorkspaceProviderEffect = () => {
|
export const WorkspaceProviderEffect = () => {
|
||||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
const { data: getPublicWorkspaceData } =
|
||||||
|
useGetPublicWorkspaceDataBySubdomain();
|
||||||
|
|
||||||
const lastAuthenticatedWorkspaceDomain = useRecoilValue(
|
const lastAuthenticatedWorkspaceDomain = useRecoilValue(
|
||||||
lastAuthenticatedWorkspaceDomainState,
|
lastAuthenticatedWorkspaceDomainState,
|
||||||
@ -26,16 +27,16 @@ export const WorkspaceProviderEffect = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
isMultiWorkspaceEnabled &&
|
isMultiWorkspaceEnabled &&
|
||||||
isDefined(workspacePublicData?.subdomain) &&
|
isDefined(getPublicWorkspaceData?.subdomain) &&
|
||||||
workspacePublicData.subdomain !== workspaceSubdomain
|
getPublicWorkspaceData.subdomain !== workspaceSubdomain
|
||||||
) {
|
) {
|
||||||
redirectToWorkspaceDomain(workspacePublicData.subdomain);
|
redirectToWorkspaceDomain(getPublicWorkspaceData.subdomain);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
workspaceSubdomain,
|
workspaceSubdomain,
|
||||||
isMultiWorkspaceEnabled,
|
isMultiWorkspaceEnabled,
|
||||||
redirectToWorkspaceDomain,
|
redirectToWorkspaceDomain,
|
||||||
workspacePublicData,
|
getPublicWorkspaceData,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -13,10 +13,11 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
|||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
|
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
getImageAbsoluteURI,
|
|
||||||
H1Title,
|
H1Title,
|
||||||
H1TitleFontColor,
|
H1TitleFontColor,
|
||||||
H2Title,
|
H2Title,
|
||||||
@ -25,6 +26,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Toggle,
|
Toggle,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
const StyledLinkContainer = styled.div`
|
const StyledLinkContainer = styled.div`
|
||||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||||
@ -104,9 +106,12 @@ export const SettingsAdminFeatureFlags = () => {
|
|||||||
id: workspace.id,
|
id: workspace.id,
|
||||||
title: workspace.name,
|
title: workspace.name,
|
||||||
logo:
|
logo:
|
||||||
getImageAbsoluteURI(
|
getImageAbsoluteURI({
|
||||||
isDefined(workspace.logo) ? workspace.logo : DEFAULT_WORKSPACE_LOGO,
|
imageUrl: isNonEmptyString(workspace.logo)
|
||||||
) ?? '',
|
? workspace.logo
|
||||||
|
: DEFAULT_WORKSPACE_LOGO,
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
}) ?? '',
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
|
|
||||||
const renderWorkspaceContent = () => {
|
const renderWorkspaceContent = () => {
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { within } from '@storybook/test';
|
import { within } from '@storybook/test';
|
||||||
import { HttpResponse, graphql, http } from 'msw';
|
import { HttpResponse, graphql, http } from 'msw';
|
||||||
import { getImageAbsoluteURI } from 'twenty-ui';
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { SettingsServerlessFunctionDetail } from '~/pages/settings/serverless-functions/SettingsServerlessFunctionDetail';
|
import { SettingsServerlessFunctionDetail } from '~/pages/settings/serverless-functions/SettingsServerlessFunctionDetail';
|
||||||
import {
|
import {
|
||||||
PageDecorator,
|
PageDecorator,
|
||||||
@ -43,9 +44,15 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
http.get(getImageAbsoluteURI(SOURCE_CODE_FULL_PATH) || '', () => {
|
http.get(
|
||||||
return HttpResponse.text('export const handler = () => {}');
|
getImageAbsoluteURI({
|
||||||
}),
|
imageUrl: SOURCE_CODE_FULL_PATH,
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
}) || '',
|
||||||
|
() => {
|
||||||
|
return HttpResponse.text('export const handler = () => {}');
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,7 +24,8 @@
|
|||||||
"@/*": ["packages/twenty-front/src/modules/*"],
|
"@/*": ["packages/twenty-front/src/modules/*"],
|
||||||
"~/*": ["packages/twenty-front/src/*"],
|
"~/*": ["packages/twenty-front/src/*"],
|
||||||
"twenty-ui": ["packages/twenty-ui/src/index.ts"],
|
"twenty-ui": ["packages/twenty-ui/src/index.ts"],
|
||||||
"@ui/*": ["packages/twenty-ui/src/*"]
|
"@ui/*": ["packages/twenty-ui/src/*"],
|
||||||
|
"twenty-shared": ["packages/twenty-shared/dist"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [],
|
"files": [],
|
||||||
|
|||||||
15
packages/twenty-shared/.eslintrc.cjs
Normal file
15
packages/twenty-shared/.eslintrc.cjs
Normal 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
1
packages/twenty-shared/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
dist
|
||||||
39
packages/twenty-shared/jest.config.ts
Normal file
39
packages/twenty-shared/jest.config.ts
Normal 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;
|
||||||
20
packages/twenty-shared/package.json
Normal file
20
packages/twenty-shared/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
38
packages/twenty-shared/project.json
Normal file
38
packages/twenty-shared/project.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/twenty-shared/src/index.ts
Normal file
1
packages/twenty-shared/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './utils/image/getImageAbsoluteURI';
|
||||||
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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();
|
||||||
|
};
|
||||||
24
packages/twenty-shared/tsconfig.json
Normal file
24
packages/twenty-shared/tsconfig.json
Normal 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"
|
||||||
|
}
|
||||||
22
packages/twenty-shared/tsconfig.lib.json
Normal file
22
packages/twenty-shared/tsconfig.lib.json
Normal 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"],
|
||||||
|
|
||||||
|
}
|
||||||
17
packages/twenty-shared/tsconfig.spec.json
Normal file
17
packages/twenty-shared/tsconfig.spec.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
33
packages/twenty-shared/vite.config.ts
Normal file
33
packages/twenty-shared/vite.config.ts
Normal 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'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { isNonEmptyString, isUndefined } from '@sniptt/guards';
|
import { isNonEmptyString, isUndefined } from '@sniptt/guards';
|
||||||
import { useContext, useMemo } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { invalidAvatarUrlsState } from '@ui/display/avatar/components/states/isInvalidAvatarUrlState';
|
import { invalidAvatarUrlsState } from '@ui/display/avatar/components/states/isInvalidAvatarUrlState';
|
||||||
@ -9,12 +9,9 @@ import { AvatarSize } from '@ui/display/avatar/types/AvatarSize';
|
|||||||
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
|
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
|
||||||
import { IconComponent } from '@ui/display/icon/types/IconComponent';
|
import { IconComponent } from '@ui/display/icon/types/IconComponent';
|
||||||
import { ThemeContext } from '@ui/theme';
|
import { ThemeContext } from '@ui/theme';
|
||||||
import {
|
import { Nullable, stringToHslColor } from '@ui/utilities';
|
||||||
Nullable,
|
import { REACT_APP_SERVER_BASE_URL } from '@ui/utilities/config';
|
||||||
getImageAbsoluteURI,
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
isDefined,
|
|
||||||
stringToHslColor,
|
|
||||||
} from '@ui/utilities';
|
|
||||||
|
|
||||||
const StyledAvatar = styled.div<{
|
const StyledAvatar = styled.div<{
|
||||||
size: AvatarSize;
|
size: AvatarSize;
|
||||||
@ -86,10 +83,12 @@ export const Avatar = ({
|
|||||||
invalidAvatarUrlsState,
|
invalidAvatarUrlsState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const avatarImageURI = useMemo(
|
const avatarImageURI = isNonEmptyString(avatarUrl)
|
||||||
() => (isDefined(avatarUrl) ? getImageAbsoluteURI(avatarUrl) : null),
|
? getImageAbsoluteURI({
|
||||||
[avatarUrl],
|
imageUrl: avatarUrl,
|
||||||
);
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
const noAvatarUrl = !isNonEmptyString(avatarImageURI);
|
const noAvatarUrl = !isNonEmptyString(avatarImageURI);
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
import { getImageAbsoluteURI } from '../getImageAbsoluteURI';
|
|
||||||
|
|
||||||
describe('getImageAbsoluteURI', () => {
|
|
||||||
it('should return absolute url if the imageUrl is an absolute url', () => {
|
|
||||||
const imageUrl = 'https://XXX';
|
|
||||||
const result = getImageAbsoluteURI(imageUrl);
|
|
||||||
expect(result).toBe(imageUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return absolute url if the imageUrl is an absolute unsecure url', () => {
|
|
||||||
const imageUrl = 'http://XXX';
|
|
||||||
const result = getImageAbsoluteURI(imageUrl);
|
|
||||||
expect(result).toBe(imageUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return fully formed url if imageUrl is a relative url', () => {
|
|
||||||
const imageUrl = 'XXX';
|
|
||||||
const result = getImageAbsoluteURI(imageUrl);
|
|
||||||
expect(result).toBe('http://localhost:3000/files/XXX');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { REACT_APP_SERVER_BASE_URL } from '@ui/utilities/config';
|
|
||||||
|
|
||||||
export const getImageAbsoluteURI = (imageUrl: string) => {
|
|
||||||
if (imageUrl.startsWith('https:') || imageUrl.startsWith('http:')) {
|
|
||||||
return imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverFilesUrl = REACT_APP_SERVER_BASE_URL;
|
|
||||||
|
|
||||||
return `${serverFilesUrl}/files/${imageUrl}`;
|
|
||||||
};
|
|
||||||
@ -6,7 +6,6 @@ export * from './animation/components/AnimatedTextWord';
|
|||||||
export * from './animation/components/AnimatedTranslation';
|
export * from './animation/components/AnimatedTranslation';
|
||||||
export * from './color/utils/stringToHslColor';
|
export * from './color/utils/stringToHslColor';
|
||||||
export * from './dimensions/components/ComputeNodeDimensions';
|
export * from './dimensions/components/ComputeNodeDimensions';
|
||||||
export * from './image/getImageAbsoluteURI';
|
|
||||||
export * from './isDefined';
|
export * from './isDefined';
|
||||||
export * from './responsive/hooks/useIsMobile';
|
export * from './responsive/hooks/useIsMobile';
|
||||||
export * from './screen-size/hooks/useScreenSize';
|
export * from './screen-size/hooks/useScreenSize';
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"outDir": "../../.cache/tsc",
|
"outDir": "../../.cache/tsc",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@ui/*": ["packages/twenty-ui/src/*"]
|
"@ui/*": ["packages/twenty-ui/src/*"],
|
||||||
|
"twenty-shared": ["packages/twenty-shared/dist"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [],
|
"files": [],
|
||||||
|
|||||||
@ -17,7 +17,8 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"twenty-emails": ["packages/twenty-emails/src/index.ts"],
|
"twenty-emails": ["packages/twenty-emails/src/index.ts"],
|
||||||
"twenty-ui": ["packages/twenty-ui/src/index.ts"]
|
"twenty-ui": ["packages/twenty-ui/src/index.ts"],
|
||||||
|
"twenty-shared": ["packages/twenty-shared/dist"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "tmp"]
|
"exclude": ["node_modules", "tmp"]
|
||||||
|
|||||||
21
yarn.lock
21
yarn.lock
@ -15925,16 +15925,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/jest@npm:^29.5.11":
|
|
||||||
version: 29.5.12
|
|
||||||
resolution: "@types/jest@npm:29.5.12"
|
|
||||||
dependencies:
|
|
||||||
expect: "npm:^29.0.0"
|
|
||||||
pretty-format: "npm:^29.0.0"
|
|
||||||
checksum: 10c0/25fc8e4c611fa6c4421e631432e9f0a6865a8cb07c9815ec9ac90d630271cad773b2ee5fe08066f7b95bebd18bb967f8ce05d018ee9ab0430f9dfd1d84665b6f
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@types/js-cookie@npm:^2.2.6":
|
"@types/js-cookie@npm:^2.2.6":
|
||||||
version: 2.2.7
|
version: 2.2.7
|
||||||
resolution: "@types/js-cookie@npm:2.2.7"
|
resolution: "@types/js-cookie@npm:2.2.7"
|
||||||
@ -26153,7 +26143,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"expect@npm:^29.0.0, expect@npm:^29.7.0":
|
"expect@npm:^29.7.0":
|
||||||
version: 29.7.0
|
version: 29.7.0
|
||||||
resolution: "expect@npm:29.7.0"
|
resolution: "expect@npm:29.7.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -38284,7 +38274,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"pretty-format@npm:^29.0.0, pretty-format@npm:^29.5.0, pretty-format@npm:^29.7.0":
|
"pretty-format@npm:^29.5.0, pretty-format@npm:^29.7.0":
|
||||||
version: 29.7.0
|
version: 29.7.0
|
||||||
resolution: "pretty-format@npm:29.7.0"
|
resolution: "pretty-format@npm:29.7.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -44020,6 +44010,12 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"twenty-shared@workspace:packages/twenty-shared":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "twenty-shared@workspace:packages/twenty-shared"
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"twenty-ui@workspace:packages/twenty-ui":
|
"twenty-ui@workspace:packages/twenty-ui":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "twenty-ui@workspace:packages/twenty-ui"
|
resolution: "twenty-ui@workspace:packages/twenty-ui"
|
||||||
@ -44167,7 +44163,6 @@ __metadata:
|
|||||||
"@types/facepaint": "npm:^1.2.5"
|
"@types/facepaint": "npm:^1.2.5"
|
||||||
"@types/graphql-fields": "npm:^1.3.6"
|
"@types/graphql-fields": "npm:^1.3.6"
|
||||||
"@types/graphql-upload": "npm:^8.0.12"
|
"@types/graphql-upload": "npm:^8.0.12"
|
||||||
"@types/jest": "npm:^29.5.11"
|
|
||||||
"@types/js-cookie": "npm:^3.0.3"
|
"@types/js-cookie": "npm:^3.0.3"
|
||||||
"@types/js-levenshtein": "npm:^1.1.3"
|
"@types/js-levenshtein": "npm:^1.1.3"
|
||||||
"@types/lodash.camelcase": "npm:^4.3.7"
|
"@types/lodash.camelcase": "npm:^4.3.7"
|
||||||
|
|||||||
Reference in New Issue
Block a user