mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-30 04:12:28 +00:00 
			
		
		
		
	Merge branch 'main' into o365sync
This commit is contained in:
		| @@ -41,10 +41,7 @@ jobs: | ||||
|         cp .env.example .env | ||||
|         echo "Generating secrets..." | ||||
|         echo "# === Randomly generated secrets ===" >>.env | ||||
|         echo "ACCESS_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env | ||||
|         echo "LOGIN_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env | ||||
|         echo "REFRESH_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env | ||||
|         echo "FILE_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env | ||||
|         echo "APP_SECRET=$(openssl rand -base64 32)" >>.env | ||||
|         echo "POSTGRES_ADMIN_PASSWORD=$(openssl rand -base64 32)" >>.env | ||||
|  | ||||
|         echo "Starting server..." | ||||
|   | ||||
							
								
								
									
										34
									
								
								.github/workflows/ci-tinybird.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/ci-tinybird.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| name: CI Tinybird | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|  | ||||
|   pull_request: | ||||
|  | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.ref }} | ||||
|   cancel-in-progress: true | ||||
|  | ||||
| jobs: | ||||
|   ci: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check for changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@v11 | ||||
|         with: | ||||
|           files: | | ||||
|             package.json | ||||
|             packages/twenty-tinybird/** | ||||
|  | ||||
|       - name: Skip if no relevant changes | ||||
|         if: steps.changed-files.outputs.any_changed == 'false' | ||||
|         run: echo "No relevant changes. Skipping CI." | ||||
|  | ||||
|       - name: Check twenty-tinybird package | ||||
|         uses: tinybirdco/ci/.github/workflows/ci.yml@main | ||||
|         with: | ||||
|           data_project_dir: packages/twenty-tinybird | ||||
|           tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }} | ||||
|           tb_host: https://api.eu-central-1.aws.tinybird.co | ||||
							
								
								
									
										14
									
								
								.github/workflows/ci-utils.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/ci-utils.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -23,16 +23,9 @@ jobs: | ||||
|     if: github.event.action != 'closed' | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Check for changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@v11 | ||||
|         with: | ||||
|           files: 'packages/twenty-utils/**'  | ||||
|       - name: Install dependencies | ||||
|         if: steps.changed-files.outputs.changed == 'true' | ||||
|         uses: ./.github/workflows/actions/yarn-install | ||||
|       - name: Utils / Run Danger.js | ||||
|         if: steps.changed-files.outputs.changed == 'true' | ||||
|         run: cd packages/twenty-utils && npx nx danger:ci | ||||
|         env: | ||||
|           DANGER_GITHUB_API_TOKEN: ${{ github.token }} | ||||
| @@ -42,16 +35,9 @@ jobs: | ||||
|     if: github.event.action == 'closed' && github.event.pull_request.merged == true | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Check for changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@v11 | ||||
|         with: | ||||
|           files: 'packages/twenty-utils/**' | ||||
|       - name: Install dependencies | ||||
|         if: steps.changed-files.outputs.changed == 'true'  | ||||
|         uses: ./.github/workflows/actions/yarn-install | ||||
|       - name: Run congratulate-dangerfile.js | ||||
|         if: steps.changed-files.outputs.changed == 'true' | ||||
|         run: cd packages/twenty-utils && npx nx danger:congratulate | ||||
|         env: | ||||
|           DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|   | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -9,7 +9,6 @@ | ||||
|  | ||||
| .nx/installation | ||||
| .nx/cache | ||||
| projectStructure.cache.json | ||||
|  | ||||
| .pnp.* | ||||
| .yarn/* | ||||
| @@ -30,3 +29,4 @@ storybook-static | ||||
| .nyc_output | ||||
| test-results/ | ||||
| dump.rdb | ||||
| .tinyb | ||||
|   | ||||
| @@ -1,12 +1,3 @@ | ||||
| <p align="center"> | ||||
|   <a href="https://oss.gg/"> | ||||
|     <picture> | ||||
|       <source media="(prefers-color-scheme: dark)" srcset="https://github.com/twentyhq/twenty/blob/33be2dbbe14eea00445010ecb9cd53ed603c01d5/packages/twenty-website/public/images/readme/Github%20Read-me%20banner.png"> | ||||
|       <source media="(prefers-color-scheme: light)" srcset="https://github.com/twentyhq/twenty/blob/33be2dbbe14eea00445010ecb9cd53ed603c01d5/packages/twenty-website/public/images/readme/Github%20Read-me%20banner.png"> | ||||
|       <img src="./packages/twenty-website/public/images/readme/Github%20Read-me%20banner.png" alt="Hacktoberfest" /> | ||||
|     </picture> | ||||
|   </a> | ||||
| </p> | ||||
|  | ||||
| <br> | ||||
| <p align="center"> | ||||
|   | ||||
| @@ -91,10 +91,7 @@ fi | ||||
|  | ||||
| # Generate random strings for secrets | ||||
| echo "# === Randomly generated secrets ===" >>.env | ||||
| echo "ACCESS_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env | ||||
| echo "LOGIN_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env | ||||
| echo "REFRESH_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env | ||||
| echo "FILE_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env | ||||
| echo "APP_SECRET=$(openssl rand -base64 32)" >>.env | ||||
| echo "" >>.env | ||||
| echo "POSTGRES_ADMIN_PASSWORD=$(openssl rand -base64 32)" >>.env | ||||
|  | ||||
|   | ||||
| @@ -1,23 +0,0 @@ | ||||
| **Side Quest**: Create a YouTube Video about Twenty showcasing a specific way to use Twenty effectively. | ||||
| **Points**: 750 Points | ||||
| **Proof**: Add your oss handle and YouTube video link to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » YouTube Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) YouTube Link: [YouTube](https://twenty.com/) | ||||
|  | ||||
| » 19-October-2024 by [Thefool76](https://oss.gg/thefool76) YouTube Link: [YouTube](https://youtu.be/KuAycGuW698?si=q-YxcukbbYuO8BWf) | ||||
|  | ||||
| --- | ||||
| @@ -1,27 +0,0 @@ | ||||
| **Side Quest**: Write a blog post about sharing your experience using Twenty in a detailed format on any platform. | ||||
| **Points**: 750 Points | ||||
| **Proof**: Add your oss handle and blog link to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » blog Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) blog Link: [blog](https://twenty.com/) | ||||
|  | ||||
| » 19-October-2024 by [Thefool76](https://oss.gg/thefool76) blog Link: [blog](https://k5lo7h.hashnode.dev/twenty-crm-a-fresh-start-for-modern-businesses) | ||||
|  | ||||
| » 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) blog Link: [blog](https://dev.to/sateshcharan/twenty-crm-a-fresh-start-for-modern-businesses-46kf) | ||||
|  | ||||
| » 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) blog Link: [blog](https://open.substack.com/pub/rajeevdewangan/p/comprehensive-guide-to-self-hosting?r=4lly3x&utm_campaign=post&utm_medium=web&showWelcomeOnShare=true) | ||||
|  | ||||
| » 22-October-2024 by [Khaan25](https://oss.gg/Khaan25) blog Link: [blog](https://medium.com/@ziaurzai/twenty-crm-modern-solution-for-modern-problems-a0b65fec9d6c) | ||||
| @@ -1,28 +0,0 @@ | ||||
| **Side Quest**: Write a blog post about self-hosting Twenty in a detailed format on any platform. | ||||
| **Points**: 750 Points | ||||
| **Proof**: Add your oss handle and blog link to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » blog Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) blog Link: [blog](https://twenty.com/) | ||||
|  | ||||
| » 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) blog Link: [blog](https://dev.to/sateshcharan/streamlined-self-hosting-with-twenty-crm-1-click-docker-compose-setup-188o) | ||||
|  | ||||
| » 24-October-2024 by [Shrey](https://oss.gg/shreykx) guide link : [https://github.com/shreykx/newfolder/blob/8046bc7373b8632b7fc2bfa28c360b86f8890a81/twentyguide.md] | ||||
| » 23-October-2024 by [Thefool76](https://oss.gg/thefool76) blog Link: [blog](https://k5lo7h.hashnode.dev/a-detailed-guide-to-self-host-twenty-crm-on-you-local-server) | ||||
|  | ||||
| » 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) blog Link: [blog](https://medium.com/@ziaurzai/detailed-guide-on-self-hosting-twenty-crm-on-your-server-troubleshooting-and-best-practices-1f2ca15cd6eb) | ||||
|  | ||||
| --- | ||||
| @@ -1,23 +0,0 @@ | ||||
| **Side Quest**: Create a promotional video for Twenty and share it on social media. | ||||
| **Points**: 750 Points | ||||
| **Proof**: Add your oss handle and video link to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) | ||||
|  | ||||
| » 24-October-2024 by [Thefool76](https://oss.gg/thefool76) video Link: [video](https://youtube.com/shorts/lC4oqm7UlCI?si=Md-nsfK9F6Shzjkv) | ||||
|  | ||||
| --- | ||||
| @@ -1,35 +0,0 @@ | ||||
| **Side Quest**: Design a promotional poster of Twenty and share it on social media. | ||||
| **Points**: 50 Points | ||||
| **Proof**: Add your oss handle and poster link to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » poster Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) poster Link: [poster](https://twenty.com/) | ||||
|  | ||||
| » 11-October-2024 by [thefool76](https://oss.gg/thefool76) poster Link: [poster](https://drive.google.com/file/d/1cIC1eitvY6zKVTXKq2LnVrS_2Ho9H8-P/view?usp=sharing) | ||||
|  | ||||
| » 12-October-2024 by [Ionfinisher](https://oss.gg/Ionfinisher) poster Link: [poster](https://x.com/ion_finisher/status/1845168965963628802) | ||||
|  | ||||
| » 14-October-2024 by [AliYar-Khan](https://oss.gg/AliYar-Khan) poster Link: [poster](https://x.com/Mr_Programmer14/status/1845888855183884352) | ||||
|  | ||||
| » 16-October-2024 by [Harsh BHat](https://oss.gg/harshsbhat) poster Link: [poster](https://x.com/HarshBhatX/status/1846233330435477531) | ||||
|  | ||||
| » 17-October-2024 by [Atharva Deshmukh](https://oss.gg/Atharva-3000) poster Link: [poster](https://x.com/0x_atharva/status/1846915861191577697) | ||||
|  | ||||
| » 20-October-2024 by [Naprila](https://oss.gg/Naprila) poster Link: [poster](https://x.com/mkprasad_821/status/1848037527921254625) | ||||
|  | ||||
| » 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) poster Link: [poster](https://x.com/sateshcharans/status/1848358958970396727) | ||||
|  | ||||
| » 22-October-2024 by [Khaan25](https://oss.gg/Khaan25) poster Link: [poster](https://drive.google.com/file/d/1IFtzwzKa0C_hT9cL4o3ChsKwVNRP33G_/view?usp=sharing) - [Tweet Link](https://x.com/zia_webdev/status/1848764487081619470) | ||||
| @@ -1,37 +0,0 @@ | ||||
| **Side Quest**: Design/Create new Twenty logo, tweet your design, and mention @twentycrm. | ||||
| **Points**: 50 Points | ||||
| **Proof**: Create a logo upload it on any of the platform and add your oss handle and logo link to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » Logo Link: https://link.to/content » tweet Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 08-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) Logo Link: [logo](https://drive.google.com/drive/folders/13k22xMnX2fhnWK94vas_hO1t-ImqXcHZ?usp=drive_link) » tweet Link: [tweet](https://x.com/adityadeshlahre/status/1843354963176718374) | ||||
|  | ||||
| » 11-October-2024 by [thefool76](https://oss.gg/thefool76) Logo Link: [logo](https://drive.google.com/file/d/1DxSwNY_i90kGgWzPQj5SxScBz_6r02l4/view?usp=sharing) » tweet Link: [tweet](https://x.com/thefool1135/status/1844693487067034008) | ||||
|  | ||||
| » 22-October-2024 by [Khaan25](https://oss.gg/Khaan25) Logo Link: [logo](https://drive.google.com/drive/folders/1yaegQ7Hr8YraMNs50AHZmDprvzLn6A90?usp=sharing) » tweet Link: [tweet](https://x.com/zia_webdev/status/1848754055717212388) | ||||
|  | ||||
| » 13-October-2024 by [Atharva_404](https://oss.gg/Atharva-3000) Logo Link: [logo](https://drive.google.com/drive/folders/1XB7ELR7kPA4x7Fx5RQr8wo5etdZAZgcs?usp=drive_link) » tweet Link: [tweet](https://x.com/0x_atharva/status/1845421218914095453) | ||||
|  | ||||
| » 13-October-2024 by [Ionfinisher](https://oss.gg/Ionfinisher) Logo Link: [logo](https://drive.google.com/file/d/1l9vE8CIjW9KfdioI5WKzxrdmvO8LR4j7/view?usp=drive_link) » tweet Link: [tweet](https://x.com/ion_finisher/status/1845466470429442163) | ||||
|  | ||||
| » 16-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) Logo Link: [logo](https://drive.google.com/file/d/1jmqwNvlSyWSY1-pCG63TAtDvCoVa8xg-/view?usp=sharing) » tweet Link: [tweet](https://x.com/HarshBhatX/status/1846234658712772977) | ||||
|  | ||||
| » 17-October-2024 by [shlok-py](https://oss.gg/shlok-py) Logo Link: [logo](https://drive.google.com/file/d/1BakHRLJul6DcNbLyeOXgJO9Ap4DpUxO9/view?usp=sharing) » tweet Link: [tweet](https://x.com/koirala_shlok/status/1846910669658247201) | ||||
|  | ||||
| » 20-October-2024 by [Naprila](https://oss.gg/Naprila) Logo Link: [logo](https://drive.google.com/file/d/105fWXNtOkOPkU31AV0FDZKOdrJ8XLwBb/view?usp=drivesdk) » tweet Link: [tweet](https://x.com/mkprasad_821/status/1847978789713695133) | ||||
|  | ||||
| » 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) Logo Link: [logo](https://drive.google.com/file/d/1fwvOcg8oQZC3NlTNV8EcyJxh9v_OYdpY/view?usp=sharing) » tweet Link: [tweet](https://x.com/sateshcharans/status/1848344729483690455) | ||||
|  | ||||
| --- | ||||
| @@ -1,24 +0,0 @@ | ||||
| **Side Quest**: Duplicate the Figma file from the main repo and customize the variables to create a unique interface theme for Twenty. <br/> | ||||
| **Points**: 750 Points <br/> | ||||
| **Proof**: Add your oss handle and Figma link to the list below. <br/> | ||||
| **Figma Link**: https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?t=YIFyswta6Xf6sSYK-0 | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE] Figma Link:  https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?t=YIFyswta6Xf6sSYK-0 | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/) | ||||
| » 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) Figma Link: [Figma](https://www.figma.com/design/XE21QdkFuy0IJHtmW7TURa/Twenty-(rajeevDewangan)?node-id=0-1&node-type=canvas&t=BYBulCT6hpJu6E8G-0) | ||||
| » 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) Figma Link: [Figma](https://www.figma.com/design/HqYQrzel3e2TjzujwfdCXZ/Twenty-(Copy)---Khaan25?node-id=478-19796&t=QTB8gzKTudbVNeNs-1) | ||||
|  | ||||
| --- | ||||
| @@ -1,22 +0,0 @@ | ||||
| **Side Quest**: Develop a script to facilitate the migration of data from another CRM to Twenty.  | ||||
| **Points**: 750 Points | ||||
| **Proof**: Add your oss handle and record video and share link to the list below. In video show the working proof of your created script. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) | ||||
|  | ||||
| » 22-October-2024 by [FaheemOnHub](https://oss.gg/FaheemOnHub) video Link: [video](https://drive.google.com/file/d/1bR59Q5gqoqHjzgdrF6K68U2hloexkQYM/view) | ||||
| --- | ||||
| @@ -1,21 +0,0 @@ | ||||
| **Side Quest**: Develop an integration for Raycast that enables users to create records on any object within Twenty directly from Raycast. | ||||
| **Points**: 1500 Points | ||||
| **Proof**: Add your oss handle and record video and share link to the list below. In video show the workflow of the your integration created and perform some task. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) | ||||
|  | ||||
| --- | ||||
| @@ -1,21 +0,0 @@ | ||||
| **Side Quest**: Create an n8n workflow that empowers Twenty by connecting it to another tool. | ||||
| **Points**: 750 Points | ||||
| **Proof**: Add your oss handle and template link to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » template Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) template Link: [template](https://twenty.com/) | ||||
|  | ||||
| --- | ||||
| @@ -1,21 +0,0 @@ | ||||
| **Side Quest**: Write a comprehensive guide on how to integrate Twenty with marketing automation tool (n8n, Zapier). Include a concrete use case and explain how to leverage AI to write API requests for non-developers and share it. | ||||
| **Points**: 1500 Points | ||||
| **Proof**: Add your oss handle and guide link to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR oss.gg HANDLE » guide Link: https://link.to/content | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) guide Link: [guide](https://twenty.com/) | ||||
|  | ||||
| --- | ||||
| @@ -1,62 +0,0 @@ | ||||
| **Side Quest**: Like & Re-Tweet oss.gg Launch Tweet. Quote-tweet it tagging @twentycrm to say you’ll be contributing. | ||||
| **Points**: 50 Points | ||||
| **Proof**: Add a screenshot of the retweet to the PR description. Add a link to your retweet in the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR NAME | ||||
| » Link to Tweet: https://x.com/... | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 13-October-2024 by Vanshika Dargan | ||||
| » Link to Tweet: https://x.com/VanshikaDargan/status/1845467453108949123 | ||||
|  | ||||
| » 13-October-2024 by Utsav Bhattarai | ||||
| » Link to Tweet: https://x.com/utsavdotdev/status/1845417863462649900 | ||||
|  | ||||
| » 10-October-2024 by Devansh Baghel | ||||
| » Link to Tweet: https://x.com/DevanshBaghel5/status/1844359648037748954 | ||||
|  | ||||
| » 11-October-2024 by Bhavesh Mishra | ||||
| » Link to Tweet: https://x.com/thefool1135/status/1844453425188405326 | ||||
|  | ||||
| » 11-October-2024 by Chirag Arora | ||||
| » Link to Tweet: https://x.com/Chirag8023/status/1844689900668682699 | ||||
|  | ||||
| » 11-October-2024 by Aritra Sadhukhan | ||||
| » Link to Tweet: https://x.com/AritraDevelops/status/1844670236512878646 | ||||
|  | ||||
| » 13-October-2024 by Nabhag Motivaras | ||||
| » Link to Tweet: https://x.com/NabhagMotivaras/status/1845449144695218357 | ||||
|  | ||||
| » 13-October-2024 by Ali Yar Khan | ||||
| » Link to Tweet: https://x.com/Mr_Programmer14/status/1845527862549577860 | ||||
|  | ||||
| » 13-October-2024 by Yash Parmar | ||||
| » Link to Tweet: https://x.com/yashp3020/status/1845720834716959009 | ||||
|  | ||||
| » 16-October-2024 by Harsh Bhat | ||||
| » Link to Tweet: https://x.com/HarshBhatX/status/1846252536241508392 | ||||
|  | ||||
| » 20-October-2024 by Naprila | ||||
| » Link to Tweet: https://x.com/mkprasad_821/status/1847886807314120762 | ||||
|  | ||||
| » 22-October-2024 by Zia Ur Rehman Khan | ||||
| » Link to Tweet: https://x.com/zia_webdev/status/1848659210243871165x | ||||
|  | ||||
| » 22-October-2024 by Ritansh Rajput | ||||
| » Link to Tweet: https://x.com/Ritansh_Dev/status/1848641904511975838 | ||||
|  | ||||
| » 23-October-2024 by Rajeev Dewangan | ||||
| » Link to Tweet: https://x.com/rajeevdew/status/1849109074685907374 | ||||
|  | ||||
|  | ||||
| @@ -1,39 +0,0 @@ | ||||
| **Side Quest**: Share a tweet about your favorite feature in Twenty. Tweet about your favorite feature in Twenty and mention @twentycrm. | ||||
| **Points**: 50 Points | ||||
| **Proof**: Add a screenshot of the tweet to the PR description. Add a link to your tweet in the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR NAME | ||||
| » Link to Tweet: https://x.com/... | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 10-October-2024 by Devansh Baghel | ||||
| » Link to Tweet: https://x.com/DevanshBaghel5/status/1844384722119704972 | ||||
|  | ||||
| » 11-October-2024 by Bhavesh Mishra | ||||
| » Link to Tweet: https://x.com/thefool1135/status/1844456500380696969 | ||||
|  | ||||
| » 13-October-2024 by Ali Yar Khan | ||||
| » Link to Tweet: https://x.com/Mr_Programmer14/status/1845530448245711197 | ||||
|  | ||||
| » 16-October-2024 by Harsh Bhat | ||||
| » Link to Tweet: https://x.com/HarshBhatX/status/1846075312691413066 | ||||
|  | ||||
| » 20-October-2024 by Naprila | ||||
| » Link to Tweet: https://x.com/mkprasad_821/status/1847895747707953205 | ||||
|  | ||||
| » 22-October-2024 by Zia Ur Rehman Khan | ||||
| » Link to Tweet: https://x.com/zia_webdev/status/1848660000190697633 | ||||
|  | ||||
| » 23-October-2024 by Rajeev Dewangan | ||||
| » Link to Tweet: https://x.com/rajeevdew/status/1849110473272442991 | ||||
| @@ -1,23 +0,0 @@ | ||||
| **Side Quest**: Create a bug report. Use the Twenty bug issue template to report a bug in detail, including steps to reproduce it. | ||||
| **Points**: 50-150 Points | ||||
| **Proof**: Add a link to your bug report in the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR NAME | ||||
| » Link to bug report: https://github.com/twentyhq/twenty/issues/... | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 10-October-2024 by Devansh Baghel | ||||
| » Link to bug report: https://github.com/twentyhq/twenty/issues/7560 | ||||
|  | ||||
| --- | ||||
| @@ -1,48 +0,0 @@ | ||||
| **Side Quest**: Meme Magic: Craft a meme where the number twenty plays a role. Tweet it, and tag @twentycrm. | ||||
| **Points**: 150 Points | ||||
| **Proof**: Add a screenshot of meme to the PR description. Add a link to your tweet in the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR NAME | ||||
| » Link to Tweet: https://x.com/... | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 10-October-2024 by Teddy ASSIH | ||||
| » Link to Tweet: https://x.com/ion_finisher/status/1844389252253299173 | ||||
|  | ||||
| » 11-October-2024 by Bhavesh Mishra | ||||
| » Link to Tweet: https://x.com/thefool1135/status/1844458836402503931 | ||||
|  | ||||
| » 12-October-2024 by Chirag Arora | ||||
| » Link to Tweet: https://x.com/Chirag8023/status/1845108226527994222 | ||||
|  | ||||
| » 13-October-2024 by Ali Yar Khan | ||||
| » Link to Tweet: https://x.com/Mr_Programmer14/status/1845537662587072697 | ||||
|  | ||||
| » 14-October-2024 by Yash Parmar | ||||
| » Link to Tweet: [https://x.com/yashp3020/status/1845108226527994222](https://x.com/yashp3020/status/1845720142702842093) | ||||
|  | ||||
| » 16-October-2024 by Harsh Bhat | ||||
| » Link to Tweet: https://x.com/HarshBhatX/status/1844698253104709899 | ||||
|  | ||||
| » 20-October-2024 by Poorvi Bajpai | ||||
| » Link to Tweet: https://x.com/poorvi_bajpai/status/1847881362038308992 | ||||
|  | ||||
| » 20-October-2024 by Satesh Charan | ||||
| » Link to Tweet: https://x.com/sateshcharans/status/1847760124267389357 | ||||
|  | ||||
| » 20-October-2024 by Naprila | ||||
| » Link to Tweet: https://x.com/mkprasad_821/status/1847900277510123706 | ||||
|  | ||||
| » 22-October-2024 by Zia Ur Rehman Khan | ||||
| » Link to Tweet: https://x.com/zia_webdev/status/1846954638953926675 | ||||
| @@ -1,45 +0,0 @@ | ||||
| **Side Quest**: Gif Magic: Create a gif related to Twenty. Tweet it, and tag  @twentycrm. | ||||
| **Points**: 150 Points | ||||
| **Proof**: Add a screenshot of GIF on Giphy to the PR description. Add a link to your GIPHY in the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
| » 05-April-2024 by YOUR NAME | ||||
| » Link to gif: https://giphy.com/... | ||||
|  | ||||
| --- | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 10-October-2024 by Teddy ASSIH | ||||
| » Link to gif: https://giphy.com/gifs/oss-crm-twenty-VWDHAIlGTbc6Nqdza9 | ||||
|  | ||||
| » 11-October-2024 by Bhavesh Mishra | ||||
| » Link to gif: https://shorturl.at/yln9H | ||||
|  | ||||
| » 12-October-2024 by Chirag Arora | ||||
| » Link to gif: https://giphy.com/gifs/yCJIS2MGbBdifbnuj0 | ||||
|  | ||||
| » 13-October-2024 by Nabhag Motivaras | ||||
| » Link to gif: https://giphy.com/gifs/twenty-twentycrm-opensourcecrm-wCcsmnJuzzzGrfuf9B | ||||
|  | ||||
| » 15-October-2024 by Ali Yar Khan | ||||
| » Link to gif: https://giphy.com/gifs/Q3f7T107wSsMJlT7aj | ||||
|  | ||||
| » 16-October-2024 by Harsh Bhat | ||||
| » Link to gif: https://giphy.com/gifs/oss-twentycrm-mgoYSDrjIalUL7XJzm | ||||
|  | ||||
| » 20-October-2024 by Satesh Charan | ||||
| » Link to gif: https://giphy.com/gifs/rXjvGBrTqu7vvhEsvR | ||||
|  | ||||
| » 20-October-2024 by Naprila | ||||
| » Link to gif: https://giphy.com/gifs/uiTAwFJ0BWQsQb7jbM | ||||
|  | ||||
| » 22-October-2024 by Zia Ur Rehman Khan | ||||
| » Link to gif: https://giphy.com/gifs/MG5FQSrG1mxf1N5qnA | ||||
| @@ -1,21 +0,0 @@ | ||||
| **Side Quest**: Complete all Twenty side quests | ||||
| **Points**: 300 Points | ||||
| **Proof**: Add screenshots for each side quest to the PR description. Add your name to the list below. | ||||
|  | ||||
| Please follow the following schema: | ||||
|  | ||||
| --- | ||||
|  | ||||
|  » 05-April-2024 by YOUR NAME | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| Your turn 👇 | ||||
|  | ||||
| //////////////////////////// | ||||
|  | ||||
| » 01-October-2024 by X | ||||
|  | ||||
| » 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) | ||||
|  | ||||
| » 22-October-2024 by [Khaan25](https://oss.gg/Khaan25) | ||||
| @@ -298,7 +298,7 @@ | ||||
|     "eslint-plugin-jsx-a11y": "^6.8.0", | ||||
|     "eslint-plugin-prefer-arrow": "^1.2.3", | ||||
|     "eslint-plugin-prettier": "^5.1.2", | ||||
|     "eslint-plugin-project-structure": "^3.7.2", | ||||
|     "eslint-plugin-project-structure": "^3.9.1", | ||||
|     "eslint-plugin-react": "^7.33.2", | ||||
|     "eslint-plugin-react-hooks": "^4.6.0", | ||||
|     "eslint-plugin-react-refresh": "^0.4.4", | ||||
|   | ||||
| @@ -8,10 +8,7 @@ REDIS_URL=redis://redis:6379 | ||||
| SERVER_URL=http://localhost:3000 | ||||
|  | ||||
| # Use openssl rand -base64 32 for each secret | ||||
| # ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access | ||||
| # LOGIN_TOKEN_SECRET=replace_me_with_a_random_string_login | ||||
| # REFRESH_TOKEN_SECRET=replace_me_with_a_random_string_refresh | ||||
| # FILE_TOKEN_SECRET=replace_me_with_a_random_string_refresh | ||||
| # APP_SECRET=replace_me_with_a_random_string | ||||
|  | ||||
| SIGN_IN_PREFILLED=true | ||||
|  | ||||
|   | ||||
| @@ -35,10 +35,7 @@ services: | ||||
|       STORAGE_S3_NAME: ${STORAGE_S3_NAME} | ||||
|       STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT} | ||||
|  | ||||
|       ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET} | ||||
|       LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET} | ||||
|       REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET} | ||||
|       FILE_TOKEN_SECRET: ${FILE_TOKEN_SECRET} | ||||
|       APP_SECRET: ${APP_SECRET} | ||||
|     depends_on: | ||||
|       change-vol-ownership: | ||||
|         condition: service_completed_successfully | ||||
| @@ -67,10 +64,7 @@ services: | ||||
|       STORAGE_S3_NAME: ${STORAGE_S3_NAME} | ||||
|       STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT} | ||||
|  | ||||
|       ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET} | ||||
|       LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET} | ||||
|       REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET} | ||||
|       FILE_TOKEN_SECRET: ${FILE_TOKEN_SECRET} | ||||
|       APP_SECRET: ${APP_SECRET} | ||||
|     depends_on: | ||||
|       db: | ||||
|         condition: service_healthy | ||||
|   | ||||
| @@ -55,26 +55,11 @@ spec: | ||||
|               value: "7d" | ||||
|             - name: "LOGIN_TOKEN_EXPIRES_IN" | ||||
|               value: "1h" | ||||
|             - name: ACCESS_TOKEN_SECRET | ||||
|             - name: APP_SECRET | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: tokens | ||||
|                   key: accessToken | ||||
|             - name: LOGIN_TOKEN_SECRET | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: tokens | ||||
|                   key: loginToken | ||||
|             - name: REFRESH_TOKEN_SECRET | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: tokens | ||||
|                   key: refreshToken | ||||
|             - name: FILE_TOKEN_SECRET | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: tokens | ||||
|                   key: fileToken | ||||
|           ports: | ||||
|             - containerPort: 3000 | ||||
|               name: http-tcp | ||||
|   | ||||
| @@ -42,26 +42,11 @@ spec: | ||||
|               value: "redis" | ||||
|             - name: "REDIS_URL" | ||||
|               value: "redis://twentycrm-redis.twentycrm.svc.cluster.local:6379" | ||||
|             - name: ACCESS_TOKEN_SECRET | ||||
|             - name: APP_SECRET | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: tokens | ||||
|                   key: accessToken | ||||
|             - name: LOGIN_TOKEN_SECRET | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: tokens | ||||
|                   key: loginToken | ||||
|             - name: REFRESH_TOKEN_SECRET | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: tokens | ||||
|                   key: refreshToken | ||||
|             - name: FILE_TOKEN_SECRET | ||||
|               valueFrom: | ||||
|                 secretKeyRef: | ||||
|                   name: tokens | ||||
|                   key: fileToken | ||||
|           command: | ||||
|             - yarn | ||||
|             - worker:prod | ||||
|   | ||||
| @@ -91,7 +91,7 @@ resource "kubernetes_deployment" "twentycrm_server" { | ||||
|             value = "1h" | ||||
|           } | ||||
|           env { | ||||
|             name = "ACCESS_TOKEN_SECRET" | ||||
|             name = "APP_SECRET" | ||||
|             value_from { | ||||
|               secret_key_ref { | ||||
|                 name = "tokens" | ||||
| @@ -100,36 +100,6 @@ resource "kubernetes_deployment" "twentycrm_server" { | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           env { | ||||
|             name = "LOGIN_TOKEN_SECRET" | ||||
|             value_from { | ||||
|               secret_key_ref { | ||||
|                 name = "tokens" | ||||
|                 key  = "loginToken" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           env { | ||||
|             name = "REFRESH_TOKEN_SECRET" | ||||
|             value_from { | ||||
|               secret_key_ref { | ||||
|                 name = "tokens" | ||||
|                 key  = "refreshToken" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           env { | ||||
|             name = "FILE_TOKEN_SECRET" | ||||
|             value_from { | ||||
|               secret_key_ref { | ||||
|                 name = "tokens" | ||||
|                 key  = "fileToken" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           port { | ||||
|             container_port = 3000 | ||||
|             protocol       = "TCP" | ||||
|   | ||||
| @@ -78,7 +78,7 @@ resource "kubernetes_deployment" "twentycrm_worker" { | ||||
|           } | ||||
|  | ||||
|           env { | ||||
|             name = "ACCESS_TOKEN_SECRET" | ||||
|             name = "APP_SECRET" | ||||
|             value_from { | ||||
|               secret_key_ref { | ||||
|                 name = "tokens" | ||||
| @@ -87,36 +87,6 @@ resource "kubernetes_deployment" "twentycrm_worker" { | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           env { | ||||
|             name = "LOGIN_TOKEN_SECRET" | ||||
|             value_from { | ||||
|               secret_key_ref { | ||||
|                 name = "tokens" | ||||
|                 key  = "loginToken" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           env { | ||||
|             name = "REFRESH_TOKEN_SECRET" | ||||
|             value_from { | ||||
|               secret_key_ref { | ||||
|                 name = "tokens" | ||||
|                 key  = "refreshToken" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           env { | ||||
|             name = "FILE_TOKEN_SECRET" | ||||
|             value_from { | ||||
|               secret_key_ref { | ||||
|                 name = "tokens" | ||||
|                 key  = "fileToken" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           resources { | ||||
|             requests = { | ||||
|               cpu    = "250m" | ||||
|   | ||||
							
								
								
									
										3
									
								
								packages/twenty-front/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								packages/twenty-front/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -42,3 +42,6 @@ dist-ssr | ||||
|  | ||||
| .vite/ | ||||
| .nyc_output/ | ||||
|  | ||||
| # eslint-plugin-project-structure | ||||
| projectStructure.cache.json | ||||
|   | ||||
| @@ -1,57 +1,45 @@ | ||||
| { | ||||
|   "$schema": "../../node_modules/eslint-plugin-project-structure/folderStructure.schema.json", | ||||
|   "projectRoot": "packages/twenty-front", | ||||
|   "structureRoot": "src", | ||||
|   "regexParameters": { | ||||
|     "camelCase": "^[a-z]+[A-Za-z0-9]+" | ||||
|     "camelCase": "^[a-z]+([A-Za-z0-9]+)+", | ||||
|     "kebab-case": "[a-z][a-z0-9]*(?:-[a-z0-9]+)*" | ||||
|   }, | ||||
|   "structure": [ | ||||
|     { | ||||
|       "name": "packages", | ||||
|       "children": [ | ||||
|         { | ||||
|           "name": "twenty-front", | ||||
|           "children": [ | ||||
|             { "name": "*", "children": [] }, | ||||
|     { "name": "*" }, | ||||
|             { | ||||
|               "name": "src", | ||||
|               "children": [ | ||||
|     { "name": "*", "children": [] }, | ||||
|                 { "name": "*" }, | ||||
|                 { | ||||
|                   "name": "modules", | ||||
|                   "children": [ | ||||
|                     { "ruleId": "moduleFolderRule" }, | ||||
|                     { "name": "types", "ruleId": "doNotCheckLeafFolderRule" } | ||||
|                   ] | ||||
|                 } | ||||
|               ] | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|     { "name": "modules", "ruleId": "modulesFolderRule" } | ||||
|   ], | ||||
|   "rules": { | ||||
|     "modulesFolderRule": { | ||||
|       "children": [ | ||||
|         { "ruleId": "moduleFolderRule" }, | ||||
|         { "name": "types", "children": [] } | ||||
|       ] | ||||
|     }, | ||||
|  | ||||
|     "moduleFolderRule": { | ||||
|       "name": "^(?!utils$|hooks$|states$|types$|graphql$|components$|effect-components$|constants$|validation-schemas$|contexts$|scopes$|services$|errors$)[a-z][a-z0-9]**(?:-[a-z0-9]+)**$", | ||||
|       "name": "{kebab-case}", | ||||
|       "folderRecursionLimit": 6, | ||||
|       "children": [ | ||||
|         { "ruleId": "moduleFolderRule" }, | ||||
|         { "name": "hooks", "ruleId": "hooksLeafFolderRule" }, | ||||
|         { "name": "utils", "ruleId": "utilsLeafFolderRule" }, | ||||
|         { "name": "states", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "types", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "graphql", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "components", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "effect-components", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "constants", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "validation-schemas", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "contexts", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "scopes", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "services", "ruleId": "doNotCheckLeafFolderRule" }, | ||||
|         { "name": "errors", "ruleId": "doNotCheckLeafFolderRule" } | ||||
|         { "name": "states", "children": [] }, | ||||
|         { "name": "types", "children": [] }, | ||||
|         { "name": "graphql", "children": [] }, | ||||
|         { "name": "components", "children": [] }, | ||||
|         { "name": "effect-components", "children": [] }, | ||||
|         { "name": "constants", "children": [] }, | ||||
|         { "name": "validation-schemas", "children": [] }, | ||||
|         { "name": "contexts", "children": [] }, | ||||
|         { "name": "scopes", "children": [] }, | ||||
|         { "name": "services", "children": [] }, | ||||
|         { "name": "errors", "children": [] } | ||||
|       ] | ||||
|     }, | ||||
|  | ||||
|     "hooksLeafFolderRule": { | ||||
|       "folderRecursionLimit": 2, | ||||
|       "children": [ | ||||
| @@ -63,12 +51,8 @@ | ||||
|         { "name": "internal", "ruleId": "hooksLeafFolderRule" } | ||||
|       ] | ||||
|     }, | ||||
|     "doNotCheckLeafFolderRule": { | ||||
|       "folderRecursionLimit": 1, | ||||
|       "children": [{ "name": "*" }, { "name": "*", "children": [] }] | ||||
|     }, | ||||
|  | ||||
|     "utilsLeafFolderRule": { | ||||
|       "folderRecursionLimit": 1, | ||||
|       "children": [ | ||||
|         { "name": "{camelCase}.ts" }, | ||||
|         { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ const modulesCoverage = { | ||||
|   branches: 25, | ||||
|   statements: 49, | ||||
|   lines: 50, | ||||
|   functions: 40, | ||||
|   functions: 38, | ||||
|   include: ['src/modules/**/*'], | ||||
|   exclude: ['src/**/*.ts'], | ||||
| }; | ||||
|   | ||||
| @@ -162,6 +162,11 @@ export type ClientConfig = { | ||||
|   support: Support; | ||||
| }; | ||||
|  | ||||
| export type ComputeStepOutputSchemaInput = { | ||||
|   /** Step JSON format */ | ||||
|   step: Scalars['JSON']['input']; | ||||
| }; | ||||
|  | ||||
| export type CreateAppTokenInput = { | ||||
|   expiresAt: Scalars['DateTime']['input']; | ||||
| }; | ||||
| @@ -529,6 +534,7 @@ export type Mutation = { | ||||
|   authorizeApp: AuthorizeApp; | ||||
|   challenge: LoginToken; | ||||
|   checkoutSession: SessionEntity; | ||||
|   computeStepOutputSchema: Scalars['JSON']['output']; | ||||
|   createOIDCIdentityProvider: SetupSsoOutput; | ||||
|   createOneAppToken: AppToken; | ||||
|   createOneField: Field; | ||||
| @@ -625,6 +631,11 @@ export type MutationCheckoutSessionArgs = { | ||||
| }; | ||||
|  | ||||
|  | ||||
| export type MutationComputeStepOutputSchemaArgs = { | ||||
|   input: ComputeStepOutputSchemaInput; | ||||
| }; | ||||
|  | ||||
|  | ||||
| export type MutationCreateOidcIdentityProviderArgs = { | ||||
|   input: SetupOidcSsoInput; | ||||
| }; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { gql } from '@apollo/client'; | ||||
| import * as Apollo from '@apollo/client'; | ||||
| import { gql } from '@apollo/client'; | ||||
| export type Maybe<T> = T | null; | ||||
| export type InputMaybe<T> = Maybe<T>; | ||||
| export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }; | ||||
| @@ -155,6 +155,11 @@ export type ClientConfig = { | ||||
|   support: Support; | ||||
| }; | ||||
|  | ||||
| export type ComputeStepOutputSchemaInput = { | ||||
|   /** Step JSON format */ | ||||
|   step: Scalars['JSON']; | ||||
| }; | ||||
|  | ||||
| export type CreateServerlessFunctionInput = { | ||||
|   description?: InputMaybe<Scalars['String']>; | ||||
|   name: Scalars['String']; | ||||
| @@ -424,6 +429,7 @@ export type Mutation = { | ||||
|   authorizeApp: AuthorizeApp; | ||||
|   challenge: LoginToken; | ||||
|   checkoutSession: SessionEntity; | ||||
|   computeStepOutputSchema: Scalars['JSON']; | ||||
|   createOIDCIdentityProvider: SetupSsoOutput; | ||||
|   createOneAppToken: AppToken; | ||||
|   createOneObject: Object; | ||||
| @@ -509,6 +515,11 @@ export type MutationCheckoutSessionArgs = { | ||||
| }; | ||||
|  | ||||
|  | ||||
| export type MutationComputeStepOutputSchemaArgs = { | ||||
|   input: ComputeStepOutputSchemaInput; | ||||
| }; | ||||
|  | ||||
|  | ||||
| export type MutationCreateOidcIdentityProviderArgs = { | ||||
|   input: SetupOidcSsoInput; | ||||
| }; | ||||
| @@ -1272,6 +1283,7 @@ export type Workspace = { | ||||
|   displayName?: Maybe<Scalars['String']>; | ||||
|   domainName?: Maybe<Scalars['String']>; | ||||
|   featureFlags?: Maybe<Array<FeatureFlag>>; | ||||
|   hasValidEntrepriseKey: Scalars['Boolean']; | ||||
|   id: Scalars['UUID']; | ||||
|   inviteHash?: Maybe<Scalars['String']>; | ||||
|   isPublicInviteLinkEnabled: Scalars['Boolean']; | ||||
| @@ -1677,7 +1689,7 @@ export type ImpersonateMutationVariables = Exact<{ | ||||
| }>; | ||||
|  | ||||
|  | ||||
| export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; | ||||
| export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; | ||||
|  | ||||
| export type RenewTokenMutationVariables = Exact<{ | ||||
|   appToken: Scalars['String']; | ||||
| @@ -1710,7 +1722,7 @@ export type VerifyMutationVariables = Exact<{ | ||||
| }>; | ||||
|  | ||||
|  | ||||
| export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; | ||||
| export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; | ||||
|  | ||||
| export type CheckUserExistsQueryVariables = Exact<{ | ||||
|   email: Scalars['String']; | ||||
| @@ -1797,7 +1809,7 @@ export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key: | ||||
|  | ||||
| export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdpType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> }; | ||||
|  | ||||
| export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }; | ||||
| export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }; | ||||
|  | ||||
| export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>; | ||||
|  | ||||
| @@ -1814,7 +1826,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf | ||||
| export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; | ||||
|  | ||||
|  | ||||
| export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } }; | ||||
| export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } }; | ||||
|  | ||||
| export type ActivateWorkflowVersionMutationVariables = Exact<{ | ||||
|   workflowVersionId: Scalars['String']; | ||||
| @@ -1823,6 +1835,13 @@ export type ActivateWorkflowVersionMutationVariables = Exact<{ | ||||
|  | ||||
| export type ActivateWorkflowVersionMutation = { __typename?: 'Mutation', activateWorkflowVersion: boolean }; | ||||
|  | ||||
| export type ComputeStepOutputSchemaMutationVariables = Exact<{ | ||||
|   input: ComputeStepOutputSchemaInput; | ||||
| }>; | ||||
|  | ||||
|  | ||||
| export type ComputeStepOutputSchemaMutation = { __typename?: 'Mutation', computeStepOutputSchema: any }; | ||||
|  | ||||
| export type DeactivateWorkflowVersionMutationVariables = Exact<{ | ||||
|   workflowVersionId: Scalars['String']; | ||||
| }>; | ||||
| @@ -2044,6 +2063,7 @@ export const UserQueryFragmentFragmentDoc = gql` | ||||
|     allowImpersonation | ||||
|     activationStatus | ||||
|     isPublicInviteLinkEnabled | ||||
|     hasValidEntrepriseKey | ||||
|     featureFlags { | ||||
|       id | ||||
|       key | ||||
| @@ -3443,6 +3463,37 @@ export function useActivateWorkflowVersionMutation(baseOptions?: Apollo.Mutation | ||||
| export type ActivateWorkflowVersionMutationHookResult = ReturnType<typeof useActivateWorkflowVersionMutation>; | ||||
| export type ActivateWorkflowVersionMutationResult = Apollo.MutationResult<ActivateWorkflowVersionMutation>; | ||||
| export type ActivateWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>; | ||||
| export const ComputeStepOutputSchemaDocument = gql` | ||||
|     mutation ComputeStepOutputSchema($input: ComputeStepOutputSchemaInput!) { | ||||
|   computeStepOutputSchema(input: $input) | ||||
| } | ||||
|     `; | ||||
| export type ComputeStepOutputSchemaMutationFn = Apollo.MutationFunction<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>; | ||||
|  | ||||
| /** | ||||
|  * __useComputeStepOutputSchemaMutation__ | ||||
|  * | ||||
|  * To run a mutation, you first call `useComputeStepOutputSchemaMutation` within a React component and pass it any options that fit your needs. | ||||
|  * When your component renders, `useComputeStepOutputSchemaMutation` returns a tuple that includes: | ||||
|  * - A mutate function that you can call at any time to execute the mutation | ||||
|  * - An object with fields that represent the current status of the mutation's execution | ||||
|  * | ||||
|  * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; | ||||
|  * | ||||
|  * @example | ||||
|  * const [computeStepOutputSchemaMutation, { data, loading, error }] = useComputeStepOutputSchemaMutation({ | ||||
|  *   variables: { | ||||
|  *      input: // value for 'input' | ||||
|  *   }, | ||||
|  * }); | ||||
|  */ | ||||
| export function useComputeStepOutputSchemaMutation(baseOptions?: Apollo.MutationHookOptions<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>) { | ||||
|         const options = {...defaultOptions, ...baseOptions} | ||||
|         return Apollo.useMutation<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>(ComputeStepOutputSchemaDocument, options); | ||||
|       } | ||||
| export type ComputeStepOutputSchemaMutationHookResult = ReturnType<typeof useComputeStepOutputSchemaMutation>; | ||||
| export type ComputeStepOutputSchemaMutationResult = Apollo.MutationResult<ComputeStepOutputSchemaMutation>; | ||||
| export type ComputeStepOutputSchemaMutationOptions = Apollo.BaseMutationOptions<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>; | ||||
| export const DeactivateWorkflowVersionDocument = gql` | ||||
|     mutation DeactivateWorkflowVersion($workflowVersionId: String!) { | ||||
|   deactivateWorkflowVersion(workflowVersionId: $workflowVersionId) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; | ||||
| import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; | ||||
| import { ActionMenuType } from '@/action-menu/types/ActionMenuType'; | ||||
| import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; | ||||
| import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; | ||||
| import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; | ||||
| @@ -12,17 +12,15 @@ import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTabl | ||||
| import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; | ||||
| import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; | ||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||
| import { useCallback, useEffect, useState } from 'react'; | ||||
| import { useCallback, useContext, useEffect, useState } from 'react'; | ||||
| import { IconTrash, isDefined } from 'twenty-ui'; | ||||
|  | ||||
| export const DeleteRecordsActionEffect = ({ | ||||
|   position, | ||||
|   objectMetadataItem, | ||||
|   actionMenuType, | ||||
| }: { | ||||
|   position: number; | ||||
|   objectMetadataItem: ObjectMetadataItem; | ||||
|   actionMenuType: ActionMenuType; | ||||
| }) => { | ||||
|   const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); | ||||
|  | ||||
| @@ -93,6 +91,9 @@ export const DeleteRecordsActionEffect = ({ | ||||
|     contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT && | ||||
|     contextStoreNumberOfSelectedRecords > 0; | ||||
|  | ||||
|   const { isInRightDrawer, onActionExecutedCallback } = | ||||
|     useContext(ActionMenuContext); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (canDelete) { | ||||
|       addActionMenuEntry({ | ||||
| @@ -101,6 +102,7 @@ export const DeleteRecordsActionEffect = ({ | ||||
|         position, | ||||
|         Icon: IconTrash, | ||||
|         accent: 'danger', | ||||
|         isPinned: true, | ||||
|         onClick: () => { | ||||
|           setIsDeleteRecordsModalOpen(true); | ||||
|         }, | ||||
| @@ -120,17 +122,14 @@ export const DeleteRecordsActionEffect = ({ | ||||
|             } can be recovered from the Options menu.`} | ||||
|             onConfirmClick={() => { | ||||
|               handleDeleteClick(); | ||||
|  | ||||
|               if (actionMenuType === 'recordShow') { | ||||
|               onActionExecutedCallback?.(); | ||||
|               if (isInRightDrawer) { | ||||
|                 closeRightDrawer(); | ||||
|               } | ||||
|             }} | ||||
|             deleteButtonText={`Delete ${ | ||||
|               contextStoreNumberOfSelectedRecords > 1 ? 'Records' : 'Record' | ||||
|             }`} | ||||
|             modalVariant={ | ||||
|               actionMenuType === 'recordShow' ? 'tertiary' : 'primary' | ||||
|             } | ||||
|           /> | ||||
|         ), | ||||
|       }); | ||||
| @@ -142,13 +141,14 @@ export const DeleteRecordsActionEffect = ({ | ||||
|       removeActionMenuEntry('delete'); | ||||
|     }; | ||||
|   }, [ | ||||
|     actionMenuType, | ||||
|     addActionMenuEntry, | ||||
|     canDelete, | ||||
|     closeRightDrawer, | ||||
|     contextStoreNumberOfSelectedRecords, | ||||
|     handleDeleteClick, | ||||
|     isDeleteRecordsModalOpen, | ||||
|     isInRightDrawer, | ||||
|     onActionExecutedCallback, | ||||
|     position, | ||||
|     removeActionMenuEntry, | ||||
|   ]); | ||||
|   | ||||
| @@ -1,27 +0,0 @@ | ||||
| import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect'; | ||||
| import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect'; | ||||
| import { ActionMenuType } from '@/action-menu/types/ActionMenuType'; | ||||
| import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; | ||||
|  | ||||
| const actionEffects = [ExportRecordsActionEffect, DeleteRecordsActionEffect]; | ||||
|  | ||||
| export const MultipleRecordsActionMenuEntriesSetter = ({ | ||||
|   objectMetadataItem, | ||||
|   actionMenuType, | ||||
| }: { | ||||
|   objectMetadataItem: ObjectMetadataItem; | ||||
|   actionMenuType: ActionMenuType; | ||||
| }) => { | ||||
|   return ( | ||||
|     <> | ||||
|       {actionEffects.map((ActionEffect, index) => ( | ||||
|         <ActionEffect | ||||
|           key={index} | ||||
|           position={index} | ||||
|           objectMetadataItem={objectMetadataItem} | ||||
|           actionMenuType={actionMenuType} | ||||
|         /> | ||||
|       ))} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -1,16 +1,23 @@ | ||||
| import { MultipleRecordsActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter'; | ||||
| import { SingleRecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter'; | ||||
| import { ActionMenuType } from '@/action-menu/types/ActionMenuType'; | ||||
| import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect'; | ||||
| import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect'; | ||||
| import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect'; | ||||
| import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; | ||||
| import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; | ||||
| import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; | ||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||
|  | ||||
| export const RecordActionMenuEntriesSetter = ({ | ||||
|   actionMenuType, | ||||
| }: { | ||||
|   actionMenuType: ActionMenuType; | ||||
| }) => { | ||||
| const singleRecordActionEffects = [ | ||||
|   ManageFavoritesActionEffect, | ||||
|   ExportRecordsActionEffect, | ||||
|   DeleteRecordsActionEffect, | ||||
| ]; | ||||
|  | ||||
| const multipleRecordActionEffects = [ | ||||
|   ExportRecordsActionEffect, | ||||
|   DeleteRecordsActionEffect, | ||||
| ]; | ||||
|  | ||||
| export const RecordActionMenuEntriesSetter = () => { | ||||
|   const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2( | ||||
|     contextStoreNumberOfSelectedRecordsComponentState, | ||||
|   ); | ||||
| @@ -33,19 +40,20 @@ export const RecordActionMenuEntriesSetter = ({ | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   if (contextStoreNumberOfSelectedRecords === 1) { | ||||
|     return ( | ||||
|       <SingleRecordActionMenuEntriesSetter | ||||
|         objectMetadataItem={objectMetadataItem} | ||||
|         actionMenuType={actionMenuType} | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
|   const actions = | ||||
|     contextStoreNumberOfSelectedRecords === 1 | ||||
|       ? singleRecordActionEffects | ||||
|       : multipleRecordActionEffects; | ||||
|  | ||||
|   return ( | ||||
|     <MultipleRecordsActionMenuEntriesSetter | ||||
|     <> | ||||
|       {actions.map((ActionEffect, index) => ( | ||||
|         <ActionEffect | ||||
|           key={index} | ||||
|           position={index} | ||||
|           objectMetadataItem={objectMetadataItem} | ||||
|       actionMenuType={actionMenuType} | ||||
|         /> | ||||
|       ))} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -1,31 +0,0 @@ | ||||
| import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect'; | ||||
| import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect'; | ||||
| import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect'; | ||||
| import { ActionMenuType } from '@/action-menu/types/ActionMenuType'; | ||||
| import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; | ||||
|  | ||||
| export const SingleRecordActionMenuEntriesSetter = ({ | ||||
|   objectMetadataItem, | ||||
|   actionMenuType, | ||||
| }: { | ||||
|   objectMetadataItem: ObjectMetadataItem; | ||||
|   actionMenuType: ActionMenuType; | ||||
| }) => { | ||||
|   const actionEffects = [ | ||||
|     ManageFavoritesActionEffect, | ||||
|     ExportRecordsActionEffect, | ||||
|     DeleteRecordsActionEffect, | ||||
|   ]; | ||||
|   return ( | ||||
|     <> | ||||
|       {actionEffects.map((ActionEffect, index) => ( | ||||
|         <ActionEffect | ||||
|           key={index} | ||||
|           position={index} | ||||
|           objectMetadataItem={objectMetadataItem} | ||||
|           actionMenuType={actionMenuType} | ||||
|         /> | ||||
|       ))} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -3,16 +3,12 @@ import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMen | ||||
| import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar'; | ||||
| import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown'; | ||||
| import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect'; | ||||
| import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; | ||||
|  | ||||
| import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; | ||||
| import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; | ||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||
|  | ||||
| export const RecordIndexActionMenu = ({ | ||||
|   actionMenuId, | ||||
| }: { | ||||
|   actionMenuId: string; | ||||
| }) => { | ||||
| export const RecordIndexActionMenu = () => { | ||||
|   const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( | ||||
|     contextStoreCurrentObjectMetadataIdComponentState, | ||||
|   ); | ||||
| @@ -20,15 +16,18 @@ export const RecordIndexActionMenu = ({ | ||||
|   return ( | ||||
|     <> | ||||
|       {contextStoreCurrentObjectMetadataId && ( | ||||
|         <ActionMenuComponentInstanceContext.Provider | ||||
|           value={{ instanceId: actionMenuId }} | ||||
|         <ActionMenuContext.Provider | ||||
|           value={{ | ||||
|             isInRightDrawer: false, | ||||
|             onActionExecutedCallback: () => {}, | ||||
|           }} | ||||
|         > | ||||
|           <RecordIndexActionMenuBar /> | ||||
|           <RecordIndexActionMenuDropdown /> | ||||
|           <ActionMenuConfirmationModals /> | ||||
|           <RecordIndexActionMenuEffect /> | ||||
|           <RecordActionMenuEntriesSetter actionMenuType="recordIndex" /> | ||||
|         </ActionMenuComponentInstanceContext.Provider> | ||||
|           <RecordActionMenuEntriesSetter /> | ||||
|         </ActionMenuContext.Provider> | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import styled from '@emotion/styled'; | ||||
|  | ||||
| import { RecordIndexActionMenuBarAllActionsButton } from '@/action-menu/components/RecordIndexActionMenuBarAllActionsButton'; | ||||
| import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry'; | ||||
| import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; | ||||
| import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; | ||||
| @@ -30,7 +31,9 @@ export const RecordIndexActionMenuBar = () => { | ||||
|     actionMenuEntriesComponentSelector, | ||||
|   ); | ||||
|  | ||||
|   if (actionMenuEntries.length === 0) { | ||||
|   const pinnedEntries = actionMenuEntries.filter((entry) => entry.isPinned); | ||||
|  | ||||
|   if (pinnedEntries.length === 0) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
| @@ -42,9 +45,10 @@ export const RecordIndexActionMenuBar = () => { | ||||
|       }} | ||||
|     > | ||||
|       <StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel> | ||||
|       {actionMenuEntries.map((entry, index) => ( | ||||
|       {pinnedEntries.map((entry, index) => ( | ||||
|         <RecordIndexActionMenuBarEntry key={index} entry={entry} /> | ||||
|       ))} | ||||
|       <RecordIndexActionMenuBarAllActionsButton /> | ||||
|     </BottomBar> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -0,0 +1,53 @@ | ||||
| import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; | ||||
| import { useTheme } from '@emotion/react'; | ||||
| import styled from '@emotion/styled'; | ||||
| import { IconLayoutSidebarRightExpand } from 'twenty-ui'; | ||||
|  | ||||
| const StyledButton = styled.div` | ||||
|   border-radius: ${({ theme }) => theme.border.radius.sm}; | ||||
|   color: ${({ theme }) => theme.font.color.secondary}; | ||||
|   cursor: pointer; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|  | ||||
|   padding: ${({ theme }) => theme.spacing(2)}; | ||||
|   transition: background ${({ theme }) => theme.animation.duration.fast} ease; | ||||
|   user-select: none; | ||||
|  | ||||
|   &:hover { | ||||
|     background: ${({ theme }) => theme.background.tertiary}; | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const StyledButtonLabel = styled.div` | ||||
|   font-weight: ${({ theme }) => theme.font.weight.medium}; | ||||
|   margin-left: ${({ theme }) => theme.spacing(1)}; | ||||
| `; | ||||
|  | ||||
| const StyledShortcutLabel = styled.div` | ||||
|   color: ${({ theme }) => theme.font.color.light}; | ||||
|   font-weight: ${({ theme }) => theme.font.weight.medium}; | ||||
| `; | ||||
|  | ||||
| const StyledSeparator = styled.div<{ size: 'sm' | 'md' }>` | ||||
|   background: ${({ theme }) => theme.border.color.light}; | ||||
|   height: ${({ theme, size }) => theme.spacing(size === 'sm' ? 4 : 8)}; | ||||
|   margin: 0 ${({ theme }) => theme.spacing(1)}; | ||||
|   width: 1px; | ||||
| `; | ||||
|  | ||||
| export const RecordIndexActionMenuBarAllActionsButton = () => { | ||||
|   const theme = useTheme(); | ||||
|   const { openCommandMenu } = useCommandMenu(); | ||||
|   return ( | ||||
|     <> | ||||
|       <StyledSeparator size="md" /> | ||||
|       <StyledButton onClick={() => openCommandMenu()}> | ||||
|         <IconLayoutSidebarRightExpand size={theme.icon.size.md} /> | ||||
|         <StyledButtonLabel>All Actions</StyledButtonLabel> | ||||
|         <StyledSeparator size="sm" /> | ||||
|         <StyledShortcutLabel>⌘K</StyledShortcutLabel> | ||||
|       </StyledButton> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -2,31 +2,24 @@ import { useTheme } from '@emotion/react'; | ||||
| import styled from '@emotion/styled'; | ||||
|  | ||||
| import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; | ||||
| import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent'; | ||||
|  | ||||
| type RecordIndexActionMenuBarEntryProps = { | ||||
|   entry: ActionMenuEntry; | ||||
| }; | ||||
|  | ||||
| const StyledButton = styled.div<{ accent: MenuItemAccent }>` | ||||
| const StyledButton = styled.div` | ||||
|   border-radius: ${({ theme }) => theme.border.radius.sm}; | ||||
|   color: ${(props) => | ||||
|     props.accent === 'danger' | ||||
|       ? props.theme.color.red | ||||
|       : props.theme.font.color.secondary}; | ||||
|   color: ${({ theme }) => theme.font.color.secondary}; | ||||
|   cursor: pointer; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|  | ||||
|   padding: ${({ theme }) => theme.spacing(2)}; | ||||
|   transition: background 0.1s ease; | ||||
|   transition: background ${({ theme }) => theme.animation.duration.fast} ease; | ||||
|   user-select: none; | ||||
|  | ||||
|   &:hover { | ||||
|     background: ${({ theme, accent }) => | ||||
|       accent === 'danger' | ||||
|         ? theme.background.danger | ||||
|         : theme.background.tertiary}; | ||||
|     background: ${({ theme }) => theme.background.tertiary}; | ||||
|   } | ||||
| `; | ||||
|  | ||||
| @@ -40,10 +33,7 @@ export const RecordIndexActionMenuBarEntry = ({ | ||||
| }: RecordIndexActionMenuBarEntryProps) => { | ||||
|   const theme = useTheme(); | ||||
|   return ( | ||||
|     <StyledButton | ||||
|       accent={entry.accent ?? 'default'} | ||||
|       onClick={() => entry.onClick?.()} | ||||
|     > | ||||
|     <StyledButton onClick={() => entry.onClick?.()}> | ||||
|       {entry.Icon && <entry.Icon size={theme.icon.size.md} />} | ||||
|       <StyledButtonLabel>{entry.label}</StyledButtonLabel> | ||||
|     </StyledButton> | ||||
|   | ||||
| @@ -1,9 +1,13 @@ | ||||
| import { useActionMenu } from '@/action-menu/hooks/useActionMenu'; | ||||
| import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; | ||||
| import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; | ||||
| import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; | ||||
| import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; | ||||
| import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; | ||||
| import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; | ||||
| import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; | ||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||
| import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; | ||||
| import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; | ||||
| import { useEffect } from 'react'; | ||||
| import { useRecoilValue } from 'recoil'; | ||||
| @@ -19,12 +23,22 @@ export const RecordIndexActionMenuEffect = () => { | ||||
|  | ||||
|   const { openActionBar, closeActionBar } = useActionMenu(actionMenuId); | ||||
|  | ||||
|   // Using closeActionBar here was causing a bug because it goes back to the | ||||
|   // previous hotkey scope, and we don't want that here. | ||||
|   const setIsBottomBarOpened = useSetRecoilComponentStateV2( | ||||
|     isBottomBarOpenedComponentState, | ||||
|     `action-bar-${actionMenuId}`, | ||||
|   ); | ||||
|  | ||||
|   const isDropdownOpen = useRecoilValue( | ||||
|     extractComponentState( | ||||
|       isDropdownOpenComponentState, | ||||
|       `action-menu-dropdown-${actionMenuId}`, | ||||
|     ), | ||||
|   ); | ||||
|   const { isRightDrawerOpen } = useRightDrawer(); | ||||
|  | ||||
|   const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (contextStoreNumberOfSelectedRecords > 0 && !isDropdownOpen) { | ||||
| @@ -43,5 +57,11 @@ export const RecordIndexActionMenuEffect = () => { | ||||
|     isDropdownOpen, | ||||
|   ]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (isRightDrawerOpen || isCommandMenuOpened) { | ||||
|       setIsBottomBarOpened(false); | ||||
|     } | ||||
|   }, [isRightDrawerOpen, isCommandMenuOpened, setIsBottomBarOpened]); | ||||
|  | ||||
|   return null; | ||||
| }; | ||||
|   | ||||
| @@ -1,30 +1,53 @@ | ||||
| import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter'; | ||||
| import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals'; | ||||
| import { RecordShowActionMenuBar } from '@/action-menu/components/RecordShowActionMenuBar'; | ||||
| import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; | ||||
|  | ||||
| import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; | ||||
| import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; | ||||
| import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; | ||||
| import { ObjectRecord } from '@/object-record/types/ObjectRecord'; | ||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||
| import { RecordShowPageBaseHeader } from '~/pages/object-record/RecordShowPageBaseHeader'; | ||||
|  | ||||
| export const RecordShowActionMenu = ({ | ||||
|   actionMenuId, | ||||
|   isFavorite, | ||||
|   handleFavoriteButtonClick, | ||||
|   record, | ||||
|   objectMetadataItem, | ||||
|   objectNameSingular, | ||||
| }: { | ||||
|   actionMenuId: string; | ||||
|   isFavorite: boolean; | ||||
|   handleFavoriteButtonClick: () => void; | ||||
|   record: ObjectRecord | undefined; | ||||
|   objectMetadataItem: ObjectMetadataItem; | ||||
|   objectNameSingular: string; | ||||
| }) => { | ||||
|   const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( | ||||
|     contextStoreCurrentObjectMetadataIdComponentState, | ||||
|   ); | ||||
|  | ||||
|   // TODO: refactor RecordShowPageBaseHeader to use the context store | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {contextStoreCurrentObjectMetadataId && ( | ||||
|         <ActionMenuComponentInstanceContext.Provider | ||||
|           value={{ instanceId: actionMenuId }} | ||||
|         <ActionMenuContext.Provider | ||||
|           value={{ | ||||
|             isInRightDrawer: false, | ||||
|             onActionExecutedCallback: () => {}, | ||||
|           }} | ||||
|         > | ||||
|           <RecordShowActionMenuBar /> | ||||
|           <RecordShowPageBaseHeader | ||||
|             {...{ | ||||
|               isFavorite, | ||||
|               handleFavoriteButtonClick, | ||||
|               record, | ||||
|               objectMetadataItem, | ||||
|               objectNameSingular, | ||||
|             }} | ||||
|           /> | ||||
|           <ActionMenuConfirmationModals /> | ||||
|           <RecordActionMenuEntriesSetter actionMenuType="recordShow" /> | ||||
|         </ActionMenuComponentInstanceContext.Provider> | ||||
|           <RecordActionMenuEntriesSetter /> | ||||
|         </ActionMenuContext.Provider> | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -0,0 +1,30 @@ | ||||
| import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter'; | ||||
| import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals'; | ||||
| import { RecordShowRightDrawerActionMenuBar } from '@/action-menu/components/RecordShowRightDrawerActionMenuBar'; | ||||
| import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; | ||||
|  | ||||
| import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; | ||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||
|  | ||||
| export const RecordShowRightDrawerActionMenu = () => { | ||||
|   const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( | ||||
|     contextStoreCurrentObjectMetadataIdComponentState, | ||||
|   ); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {contextStoreCurrentObjectMetadataId && ( | ||||
|         <ActionMenuContext.Provider | ||||
|           value={{ | ||||
|             isInRightDrawer: true, | ||||
|             onActionExecutedCallback: () => {}, | ||||
|           }} | ||||
|         > | ||||
|           <RecordShowRightDrawerActionMenuBar /> | ||||
|           <ActionMenuConfirmationModals /> | ||||
|           <RecordActionMenuEntriesSetter /> | ||||
|         </ActionMenuContext.Provider> | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -2,7 +2,7 @@ import { RecordShowActionMenuBarEntry } from '@/action-menu/components/RecordSho | ||||
| import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; | ||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||
| 
 | ||||
| export const RecordShowActionMenuBar = () => { | ||||
| export const RecordShowRightDrawerActionMenuBar = () => { | ||||
|   const actionMenuEntries = useRecoilComponentValueV2( | ||||
|     actionMenuEntriesComponentSelector, | ||||
|   ); | ||||
| @@ -10,15 +10,15 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto | ||||
| import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; | ||||
| import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; | ||||
| import { userEvent, waitFor, within } from '@storybook/test'; | ||||
| import { IconCheckbox, IconTrash } from 'twenty-ui'; | ||||
| import { IconTrash, RouterDecorator } from 'twenty-ui'; | ||||
|  | ||||
| const deleteMock = jest.fn(); | ||||
| const markAsDoneMock = jest.fn(); | ||||
|  | ||||
| const meta: Meta<typeof RecordIndexActionMenuBar> = { | ||||
|   title: 'Modules/ActionMenu/RecordIndexActionMenuBar', | ||||
|   component: RecordIndexActionMenuBar, | ||||
|   decorators: [ | ||||
|     RouterDecorator, | ||||
|     (Story) => ( | ||||
|       <ContextStoreComponentInstanceContext.Provider | ||||
|         value={{ instanceId: 'story-action-menu' }} | ||||
| @@ -48,6 +48,7 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = { | ||||
|                 [ | ||||
|                   'delete', | ||||
|                   { | ||||
|                     isPinned: true, | ||||
|                     key: 'delete', | ||||
|                     label: 'Delete', | ||||
|                     position: 0, | ||||
| @@ -55,16 +56,6 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = { | ||||
|                     onClick: deleteMock, | ||||
|                   }, | ||||
|                 ], | ||||
|                 [ | ||||
|                   'markAsDone', | ||||
|                   { | ||||
|                     key: 'markAsDone', | ||||
|                     label: 'Mark as done', | ||||
|                     position: 1, | ||||
|                     Icon: IconCheckbox, | ||||
|                     onClick: markAsDoneMock, | ||||
|                   }, | ||||
|                 ], | ||||
|               ]), | ||||
|             ); | ||||
|             set( | ||||
| @@ -120,12 +111,8 @@ export const WithButtonClicks: Story = { | ||||
|     const deleteButton = await canvas.findByText('Delete'); | ||||
|     await userEvent.click(deleteButton); | ||||
|  | ||||
|     const markAsDoneButton = await canvas.findByText('Mark as done'); | ||||
|     await userEvent.click(markAsDoneButton); | ||||
|  | ||||
|     await waitFor(() => { | ||||
|       expect(deleteMock).toHaveBeenCalled(); | ||||
|       expect(markAsDoneMock).toHaveBeenCalled(); | ||||
|     }); | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import { expect, jest } from '@storybook/jest'; | ||||
| import { Meta, StoryObj } from '@storybook/react'; | ||||
| import { RecoilRoot } from 'recoil'; | ||||
|  | ||||
| import { RecordShowActionMenuBar } from '@/action-menu/components/RecordShowActionMenuBar'; | ||||
| import { RecordShowRightDrawerActionMenuBar } from '@/action-menu/components/RecordShowRightDrawerActionMenuBar'; | ||||
| import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; | ||||
| import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; | ||||
| import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; | ||||
| @@ -20,9 +20,9 @@ const deleteMock = jest.fn(); | ||||
| const addToFavoritesMock = jest.fn(); | ||||
| const exportMock = jest.fn(); | ||||
|  | ||||
| const meta: Meta<typeof RecordShowActionMenuBar> = { | ||||
|   title: 'Modules/ActionMenu/RecordShowActionMenuBar', | ||||
|   component: RecordShowActionMenuBar, | ||||
| const meta: Meta<typeof RecordShowRightDrawerActionMenuBar> = { | ||||
|   title: 'Modules/ActionMenu/RecordShowRightDrawerActionMenuBar', | ||||
|   component: RecordShowRightDrawerActionMenuBar, | ||||
|   decorators: [ | ||||
|     (Story) => ( | ||||
|       <RecoilRoot | ||||
| @@ -98,7 +98,7 @@ const meta: Meta<typeof RecordShowActionMenuBar> = { | ||||
|  | ||||
| export default meta; | ||||
|  | ||||
| type Story = StoryObj<typeof RecordShowActionMenuBar>; | ||||
| type Story = StoryObj<typeof RecordShowRightDrawerActionMenuBar>; | ||||
|  | ||||
| export const Default: Story = { | ||||
|   args: { | ||||
|   | ||||
| @@ -0,0 +1,11 @@ | ||||
| import { createContext } from 'react'; | ||||
|  | ||||
| type ActionMenuContextType = { | ||||
|   isInRightDrawer: boolean; | ||||
|   onActionExecutedCallback: () => void; | ||||
| }; | ||||
|  | ||||
| export const ActionMenuContext = createContext<ActionMenuContextType>({ | ||||
|   isInRightDrawer: false, | ||||
|   onActionExecutedCallback: () => {}, | ||||
| }); | ||||
| @@ -8,6 +8,7 @@ export type ActionMenuEntry = { | ||||
|   label: string; | ||||
|   position: number; | ||||
|   Icon: IconComponent; | ||||
|   isPinned?: boolean; | ||||
|   accent?: MenuItemAccent; | ||||
|   onClick?: (event?: MouseEvent<HTMLElement>) => void; | ||||
|   ConfirmationModal?: ReactNode; | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| export type ActionMenuType = 'recordIndex' | 'recordShow'; | ||||
| @@ -8,6 +8,7 @@ import { | ||||
|   AnimatedPlaceholderEmptyTitle, | ||||
|   EMPTY_PLACEHOLDER_TRANSITION_PROPS, | ||||
|   H3Title, | ||||
|   Section, | ||||
| } from 'twenty-ui'; | ||||
|  | ||||
| import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard'; | ||||
| @@ -21,7 +22,6 @@ import { SkeletonLoader } from '@/activities/components/SkeletonLoader'; | ||||
| import { useCustomResolver } from '@/activities/hooks/useCustomResolver'; | ||||
| import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; | ||||
| import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | ||||
| import { Section } from '@/ui/layout/section/components/Section'; | ||||
| import { TimelineCalendarEventsWithTotal } from '~/generated/graphql'; | ||||
|  | ||||
| const StyledContainer = styled.div` | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { | ||||
|   EMPTY_PLACEHOLDER_TRANSITION_PROPS, | ||||
|   H1Title, | ||||
|   H1TitleFontColor, | ||||
|   Section, | ||||
| } from 'twenty-ui'; | ||||
|  | ||||
| import { ActivityList } from '@/activities/components/ActivityList'; | ||||
| @@ -20,7 +21,6 @@ import { getTimelineThreadsFromPersonId } from '@/activities/emails/graphql/quer | ||||
| import { useCustomResolver } from '@/activities/hooks/useCustomResolver'; | ||||
| import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; | ||||
| import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | ||||
| import { Section } from '@/ui/layout/section/components/Section'; | ||||
| import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql'; | ||||
|  | ||||
| const StyledContainer = styled.div` | ||||
|   | ||||
| @@ -5,7 +5,6 @@ export type Attachment = { | ||||
|   type: AttachmentType; | ||||
|   companyId: string; | ||||
|   personId: string; | ||||
|   activityId: string; | ||||
|   authorId: string; | ||||
|   createdAt: string; | ||||
|   __typename: string; | ||||
|   | ||||
| @@ -19,8 +19,6 @@ export const findActivityTargetsOperationSignatureFactory: RecordGqlOperationSig | ||||
|       __typename: true, | ||||
|       createdAt: true, | ||||
|       updatedAt: true, | ||||
|       activity: true, | ||||
|       activityId: true, | ||||
|       ...generateActivityTargetMorphFieldKeys(objectMetadataItems), | ||||
|     }, | ||||
|   }); | ||||
|   | ||||
| @@ -18,7 +18,6 @@ const mockActivityTarget = { | ||||
|   updatedAt: '2021-08-03T19:20:06.000Z', | ||||
|   createdAt: '2021-08-03T19:20:06.000Z', | ||||
|   personId: '1', | ||||
|   activityId: '234', | ||||
|   companyId: '1', | ||||
|   id: '123', | ||||
| }; | ||||
|   | ||||
| @@ -37,7 +37,6 @@ const mocks: MockedResponse[] = [ | ||||
|               edges { | ||||
|                 node { | ||||
|                   __typename | ||||
|                   activityId | ||||
|                   authorId | ||||
|                   companyId | ||||
|                   createdAt | ||||
|   | ||||
| @@ -1,11 +1,15 @@ | ||||
| import { useTheme } from '@emotion/react'; | ||||
| import styled from '@emotion/styled'; | ||||
| import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui'; | ||||
| import { | ||||
|   Checkbox, | ||||
|   CheckboxShape, | ||||
|   IconCalendar, | ||||
|   OverflowingTextWithTooltip, | ||||
| } from 'twenty-ui'; | ||||
|  | ||||
| import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; | ||||
| import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell'; | ||||
| import { getActivitySummary } from '@/activities/utils/getActivitySummary'; | ||||
| import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox'; | ||||
| import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils'; | ||||
|  | ||||
| import { ActivityRow } from '@/activities/components/ActivityRow'; | ||||
|   | ||||
| @@ -51,7 +51,6 @@ const mocks: MockedResponse[] = [ | ||||
|               edges { | ||||
|                 node { | ||||
|                   __typename | ||||
|                   activityId | ||||
|                   authorId | ||||
|                   companyId | ||||
|                   createdAt | ||||
| @@ -95,6 +94,8 @@ const mocks: MockedResponse[] = [ | ||||
|                   updatedAt | ||||
|                   viewId | ||||
|                   workflowId | ||||
|                   workflowRunId | ||||
|                   workflowVersionId | ||||
|                   workspaceMemberId | ||||
|                 } | ||||
|               } | ||||
| @@ -138,6 +139,9 @@ const mocks: MockedResponse[] = [ | ||||
|                   rocketId | ||||
|                   taskId | ||||
|                   updatedAt | ||||
|                   workflowId | ||||
|                   workflowRunId | ||||
|                   workflowVersionId | ||||
|                   workspaceMemberId | ||||
|                 } | ||||
|               } | ||||
|   | ||||
| @@ -115,7 +115,7 @@ export const PageChangeEffect = () => { | ||||
|         break; | ||||
|       } | ||||
|       case isMatchingLocation(AppPath.CreateWorkspace): { | ||||
|         setHotkeyScope(PageHotkeyScope.CreateWokspace); | ||||
|         setHotkeyScope(PageHotkeyScope.CreateWorkspace); | ||||
|         break; | ||||
|       } | ||||
|       case isMatchingLocation(AppPath.SyncEmails): { | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import { | ||||
|   IconMicrosoft, | ||||
|   Loader, | ||||
|   MainButton, | ||||
|   StyledText, | ||||
| } from 'twenty-ui'; | ||||
| import { isDefined } from '~/utils/isDefined'; | ||||
|  | ||||
| @@ -187,7 +188,9 @@ export const SignInUpForm = () => { | ||||
|           </> | ||||
|         )} | ||||
|  | ||||
|         <HorizontalSeparator visible={true} /> | ||||
|         {(authProviders.google || | ||||
|           authProviders.microsoft || | ||||
|           authProviders.sso) && <HorizontalSeparator visible />} | ||||
|  | ||||
|         {authProviders.password && | ||||
|           (signInUpStep === SignInUpStep.Password || | ||||
| @@ -267,6 +270,12 @@ export const SignInUpForm = () => { | ||||
|                           disableHotkeys | ||||
|                           onKeyDown={handleKeyDown} | ||||
|                         /> | ||||
|                         {signInUpMode === SignInUpMode.SignUp && ( | ||||
|                           <StyledText | ||||
|                             text={'At least 8 characters long.'} | ||||
|                             color={theme.font.color.secondary} | ||||
|                           /> | ||||
|                         )} | ||||
|                       </StyledInputContainer> | ||||
|                     )} | ||||
|                   /> | ||||
|   | ||||
| @@ -14,6 +14,7 @@ export type CurrentWorkspace = Pick< | ||||
|   | 'currentBillingSubscription' | ||||
|   | 'workspaceMembersCount' | ||||
|   | 'isPublicInviteLinkEnabled' | ||||
|   | 'hasValidEntrepriseKey' | ||||
|   | 'metadataVersion' | ||||
| >; | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS | ||||
| import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; | ||||
| import { Command, CommandType } from '@/command-menu/types/Command'; | ||||
| import { Company } from '@/companies/types/Company'; | ||||
| import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; | ||||
| import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu'; | ||||
| import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | ||||
| import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName'; | ||||
| @@ -287,6 +288,14 @@ export const CommandMenu = () => { | ||||
|         : true) && cmd.type === CommandType.Create, | ||||
|   ); | ||||
|  | ||||
|   const matchingActionCommands = commandMenuCommands.filter( | ||||
|     (cmd) => | ||||
|       (deferredCommandMenuSearch.length > 0 | ||||
|         ? checkInShortcuts(cmd, deferredCommandMenuSearch) || | ||||
|           checkInLabels(cmd, deferredCommandMenuSearch) | ||||
|         : true) && cmd.type === CommandType.Action, | ||||
|   ); | ||||
|  | ||||
|   useListenClickOutside({ | ||||
|     refs: [commandMenuRef], | ||||
|     callback: closeCommandMenu, | ||||
| @@ -312,6 +321,7 @@ export const CommandMenu = () => { | ||||
|  | ||||
|   const selectableItemIds = copilotCommands | ||||
|     .map((cmd) => cmd.id) | ||||
|     .concat(matchingActionCommands.map((cmd) => cmd.id)) | ||||
|     .concat(matchingCreateCommand.map((cmd) => cmd.id)) | ||||
|     .concat(matchingNavigateCommand.map((cmd) => cmd.id)) | ||||
|     .concat(people?.map((person) => person.id)) | ||||
| @@ -320,22 +330,28 @@ export const CommandMenu = () => { | ||||
|     .concat(notes?.map((note) => note.id)); | ||||
|  | ||||
|   const isNoResults = | ||||
|     !matchingActionCommands.length && | ||||
|     !matchingCreateCommand.length && | ||||
|     !matchingNavigateCommand.length && | ||||
|     !people?.length && | ||||
|     !companies?.length && | ||||
|     !notes?.length && | ||||
|     !opportunities?.length; | ||||
|  | ||||
|   const isLoading = | ||||
|     isPeopleLoading || | ||||
|     isNotesLoading || | ||||
|     isOpportunitiesLoading || | ||||
|     isCompaniesLoading; | ||||
|  | ||||
|   const mainContextStoreComponentInstanceId = useRecoilValue( | ||||
|     mainContextStoreComponentInstanceIdState, | ||||
|   ); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {isCommandMenuOpened && ( | ||||
|         <StyledCommandMenu ref={commandMenuRef}> | ||||
|         <StyledCommandMenu ref={commandMenuRef} className="command-menu"> | ||||
|           <StyledInputContainer> | ||||
|             <StyledInput | ||||
|               autoFocus | ||||
| @@ -393,6 +409,23 @@ export const CommandMenu = () => { | ||||
|                       </SelectableItem> | ||||
|                     </CommandGroup> | ||||
|                   )} | ||||
|                   {mainContextStoreComponentInstanceId && ( | ||||
|                     <CommandGroup heading="Actions"> | ||||
|                       {matchingActionCommands?.map((actionCommand) => ( | ||||
|                         <SelectableItem | ||||
|                           itemId={actionCommand.id} | ||||
|                           key={actionCommand.id} | ||||
|                         > | ||||
|                           <CommandMenuItem | ||||
|                             id={actionCommand.id} | ||||
|                             label={actionCommand.label} | ||||
|                             Icon={actionCommand.Icon} | ||||
|                             onClick={actionCommand.onCommandClick} | ||||
|                           /> | ||||
|                         </SelectableItem> | ||||
|                       ))} | ||||
|                     </CommandGroup> | ||||
|                   )} | ||||
|                   <CommandGroup heading="Create"> | ||||
|                     {matchingCreateCommand.map((cmd) => ( | ||||
|                       <SelectableItem itemId={cmd.id} key={cmd.id}> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { isNonEmptyString } from '@sniptt/guards'; | ||||
| import { useCallback } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
| import { useRecoilCallback, useSetRecoilState } from 'recoil'; | ||||
| import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; | ||||
|  | ||||
| import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; | ||||
| import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; | ||||
| @@ -9,7 +9,9 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousH | ||||
| import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; | ||||
| import { isDefined } from '~/utils/isDefined'; | ||||
|  | ||||
| import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; | ||||
| import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands'; | ||||
| import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; | ||||
| import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; | ||||
| import { ALL_ICONS } from '@ui/display/icon/providers/internal/AllIcons'; | ||||
| import { sortByProperty } from '~/utils/array/sortByProperty'; | ||||
| @@ -27,10 +29,43 @@ export const useCommandMenu = () => { | ||||
|     goBackToPreviousHotkeyScope, | ||||
|   } = usePreviousHotkeyScope(); | ||||
|  | ||||
|   const openCommandMenu = useCallback(() => { | ||||
|   const mainContextStoreComponentInstanceId = useRecoilValue( | ||||
|     mainContextStoreComponentInstanceIdState, | ||||
|   ); | ||||
|  | ||||
|   const openCommandMenu = useRecoilCallback( | ||||
|     ({ snapshot }) => | ||||
|       () => { | ||||
|         if (isDefined(mainContextStoreComponentInstanceId)) { | ||||
|           const actionMenuEntries = snapshot.getLoadable( | ||||
|             actionMenuEntriesComponentSelector.selectorFamily({ | ||||
|               instanceId: mainContextStoreComponentInstanceId, | ||||
|             }), | ||||
|           ); | ||||
|  | ||||
|           const actionCommands = actionMenuEntries | ||||
|             .getValue() | ||||
|             ?.map((actionMenuEntry) => ({ | ||||
|               id: actionMenuEntry.key, | ||||
|               label: actionMenuEntry.label, | ||||
|               Icon: actionMenuEntry.Icon, | ||||
|               onCommandClick: actionMenuEntry.onClick, | ||||
|               type: CommandType.Action, | ||||
|             })); | ||||
|  | ||||
|           setCommands(actionCommands); | ||||
|         } | ||||
|  | ||||
|         setIsCommandMenuOpened(true); | ||||
|         setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen); | ||||
|   }, [setHotkeyScopeAndMemorizePreviousScope, setIsCommandMenuOpened]); | ||||
|       }, | ||||
|     [ | ||||
|       mainContextStoreComponentInstanceId, | ||||
|       setCommands, | ||||
|       setHotkeyScopeAndMemorizePreviousScope, | ||||
|       setIsCommandMenuOpened, | ||||
|     ], | ||||
|   ); | ||||
|  | ||||
|   const closeCommandMenu = useRecoilCallback( | ||||
|     ({ snapshot }) => | ||||
|   | ||||
| @@ -3,13 +3,14 @@ import { IconComponent } from 'twenty-ui'; | ||||
| export enum CommandType { | ||||
|   Navigate = 'Navigate', | ||||
|   Create = 'Create', | ||||
|   Action = 'Action', | ||||
| } | ||||
|  | ||||
| export type Command = { | ||||
|   id: string; | ||||
|   to: string; | ||||
|   to?: string; | ||||
|   label: string; | ||||
|   type: CommandType.Navigate | CommandType.Create; | ||||
|   type: CommandType.Navigate | CommandType.Create | CommandType.Action; | ||||
|   Icon?: IconComponent; | ||||
|   firstHotKey?: string; | ||||
|   secondHotKey?: string; | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { mainContextStoreComponentInstanceIdState } from '@/context-store/states | ||||
| import { useContext, useEffect } from 'react'; | ||||
| import { useSetRecoilState } from 'recoil'; | ||||
| 
 | ||||
| export const SetMainContextStoreComponentInstanceIdEffect = () => { | ||||
| export const MainContextStoreComponentInstanceIdSetterEffect = () => { | ||||
|   const setMainContextStoreComponentInstanceId = useSetRecoilState( | ||||
|     mainContextStoreComponentInstanceIdState, | ||||
|   ); | ||||
| @@ -295,6 +295,41 @@ export const mocks = [ | ||||
|           updatedAt | ||||
|         } | ||||
|         workflowId | ||||
|         workflowRun { | ||||
|           __typename | ||||
|           createdAt | ||||
|           createdBy { | ||||
|             source | ||||
|             workspaceMemberId | ||||
|             name | ||||
|           } | ||||
|           deletedAt | ||||
|           endedAt | ||||
|           id | ||||
|           name | ||||
|           output | ||||
|           position | ||||
|           startedAt | ||||
|           status | ||||
|           updatedAt | ||||
|           workflowId | ||||
|           workflowVersionId | ||||
|         } | ||||
|         workflowRunId | ||||
|         workflowVersion { | ||||
|           __typename | ||||
|           createdAt | ||||
|           deletedAt | ||||
|           id | ||||
|           name | ||||
|           position | ||||
|           status | ||||
|           steps | ||||
|           trigger | ||||
|           updatedAt | ||||
|           workflowId | ||||
|         } | ||||
|         workflowVersionId | ||||
|         workspaceMember { | ||||
|           __typename | ||||
|           avatarUrl | ||||
| @@ -341,8 +376,8 @@ export const mocks = [ | ||||
|         mutation DeleteOneFavorite($idToDelete: ID!) { | ||||
|           deleteFavorite(id: $idToDelete) { | ||||
|             __typename | ||||
|             id | ||||
|             deletedAt | ||||
|             id | ||||
|           } | ||||
|         } | ||||
|       `, | ||||
| @@ -575,6 +610,41 @@ export const mocks = [ | ||||
|               updatedAt | ||||
|             } | ||||
|             workflowId | ||||
|             workflowRun { | ||||
|               __typename | ||||
|               createdAt | ||||
|               createdBy { | ||||
|                 source | ||||
|                 workspaceMemberId | ||||
|                 name | ||||
|               } | ||||
|               deletedAt | ||||
|               endedAt | ||||
|               id | ||||
|               name | ||||
|               output | ||||
|               position | ||||
|               startedAt | ||||
|               status | ||||
|               updatedAt | ||||
|               workflowId | ||||
|               workflowVersionId | ||||
|             } | ||||
|             workflowRunId | ||||
|             workflowVersion { | ||||
|               __typename | ||||
|               createdAt | ||||
|               deletedAt | ||||
|               id | ||||
|               name | ||||
|               position | ||||
|               status | ||||
|               steps | ||||
|               trigger | ||||
|               updatedAt | ||||
|               workflowId | ||||
|             } | ||||
|             workflowVersionId | ||||
|             workspaceMember { | ||||
|               __typename | ||||
|               avatarUrl | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { DropResult, ResponderProvided } from '@hello-pangea/dnd'; | ||||
| import { act, renderHook, waitFor } from '@testing-library/react'; | ||||
| import { renderHook, waitFor } from '@testing-library/react'; | ||||
| import { useSetRecoilState } from 'recoil'; | ||||
|  | ||||
| import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; | ||||
| @@ -7,6 +7,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites'; | ||||
| import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; | ||||
| import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | ||||
|  | ||||
| import { act } from 'react'; | ||||
| import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; | ||||
| import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; | ||||
| import { | ||||
|   | ||||
| @@ -3,13 +3,18 @@ import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableT | ||||
| describe('findAvailableTimeZoneOption', () => { | ||||
|   it('should find the matching available IANA time zone select option from a given IANA time zone', () => { | ||||
|     const ianaTimeZone = 'Europe/Paris'; | ||||
|     const expectedOption = { | ||||
|       label: '(GMT+02:00) Central European Summer Time - Paris', | ||||
|       value: 'Europe/Paris', | ||||
|     }; | ||||
|     const expectedValue = 'Europe/Paris'; | ||||
|     const expectedLabelWinter = | ||||
|       '(GMT+01:00) Central European Standard Time - Paris'; | ||||
|     const expectedLabelSummer = | ||||
|       '(GMT+02:00) Central European Summer Time - Paris'; | ||||
|  | ||||
|     const option = findAvailableTimeZoneOption(ianaTimeZone); | ||||
|  | ||||
|     expect(option).toEqual(expectedOption); | ||||
|     expect(option.value).toEqual(expectedValue); | ||||
|     expect( | ||||
|       expectedLabelWinter === option.label || | ||||
|         expectedLabelSummer === option.label, | ||||
|     ).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -3,11 +3,17 @@ import { formatTimeZoneLabel } from '@/localization/utils/formatTimeZoneLabel'; | ||||
| describe('formatTimeZoneLabel', () => { | ||||
|   it('should format the time zone label correctly when location is included in the label', () => { | ||||
|     const ianaTimeZone = 'Europe/Paris'; | ||||
|     const expectedLabel = '(GMT+02:00) Central European Summer Time - Paris'; | ||||
|     const expectedLabelSummer = | ||||
|       '(GMT+02:00) Central European Summer Time - Paris'; | ||||
|     const expectedLabelWinter = | ||||
|       '(GMT+01:00) Central European Standard Time - Paris'; | ||||
|  | ||||
|     const formattedLabel = formatTimeZoneLabel(ianaTimeZone); | ||||
|  | ||||
|     expect(formattedLabel).toEqual(expectedLabel); | ||||
|     expect( | ||||
|       expectedLabelSummer === formattedLabel || | ||||
|         expectedLabelWinter === formattedLabel, | ||||
|     ).toBeTruthy(); | ||||
|   }); | ||||
|  | ||||
|   it('should format the time zone label correctly when location is not included in the label', () => { | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; | ||||
| import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; | ||||
| import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings'; | ||||
| import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; | ||||
| import { useRecoilState } from 'recoil'; | ||||
| import { | ||||
| @@ -23,6 +24,8 @@ export const MobileNavigationBar = () => { | ||||
|   const [currentMobileNavigationDrawer, setCurrentMobileNavigationDrawer] = | ||||
|     useRecoilState(currentMobileNavigationDrawerState); | ||||
|  | ||||
|   const { openSettingsMenu } = useOpenSettingsMenu(); | ||||
|  | ||||
|   const activeItemName = isNavigationDrawerExpanded | ||||
|     ? currentMobileNavigationDrawer | ||||
|     : isCommandMenuOpened | ||||
| @@ -62,10 +65,7 @@ export const MobileNavigationBar = () => { | ||||
|       Icon: IconSettings, | ||||
|       onClick: () => { | ||||
|         closeCommandMenu(); | ||||
|         setIsNavigationDrawerExpanded( | ||||
|           (previousIsOpen) => activeItemName !== 'settings' || !previousIsOpen, | ||||
|         ); | ||||
|         setCurrentMobileNavigationDrawer('settings'); | ||||
|         openSettingsMenu(); | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
|   | ||||
| @@ -0,0 +1,19 @@ | ||||
| import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState'; | ||||
| import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; | ||||
| import { useRecoilState } from 'recoil'; | ||||
|  | ||||
| export const useOpenSettingsMenu = () => { | ||||
|   const [, setIsNavigationDrawerExpanded] = useRecoilState( | ||||
|     isNavigationDrawerExpandedState, | ||||
|   ); | ||||
|   const [, setCurrentMobileNavigationDrawer] = useRecoilState( | ||||
|     currentMobileNavigationDrawerState, | ||||
|   ); | ||||
|  | ||||
|   const openSettingsMenu = () => { | ||||
|     setIsNavigationDrawerExpanded(true); | ||||
|     setCurrentMobileNavigationDrawer('settings'); | ||||
|   }; | ||||
|  | ||||
|   return { openSettingsMenu }; | ||||
| }; | ||||
| @@ -43,8 +43,7 @@ export const NavigationDrawerItemForObjectMetadataItem = ({ | ||||
|   const shouldSubItemsBeDisplayed = isActive && objectMetadataViews.length > 1; | ||||
|  | ||||
|   const sortedObjectMetadataViews = [...objectMetadataViews].sort( | ||||
|     (viewA, viewB) => | ||||
|       viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position, | ||||
|     (viewA, viewB) => viewA.position - viewB.position, | ||||
|   ); | ||||
|  | ||||
|   const selectedSubItemIndex = sortedObjectMetadataViews.findIndex( | ||||
|   | ||||
| @@ -11,5 +11,5 @@ export const query = gql` | ||||
| export const variables = { idToDelete: 'idToDelete' }; | ||||
|  | ||||
| export const responseData = { | ||||
|   id: 'idToDelete' | ||||
|   id: 'idToDelete', | ||||
| }; | ||||
|   | ||||
| @@ -2,7 +2,8 @@ import { gql } from '@apollo/client'; | ||||
| import { FieldMetadataType } from '~/generated/graphql'; | ||||
|  | ||||
| export const FIELD_METADATA_ID = '2c43466a-fe9e-4005-8d08-c5836067aa6c'; | ||||
| export const FIELD_RELATION_METADATA_ID = '4da0302d-358a-45cd-9973-9f92723ed3c1'; | ||||
| export const FIELD_RELATION_METADATA_ID = | ||||
|   '4da0302d-358a-45cd-9973-9f92723ed3c1'; | ||||
| export const RELATION_METADATA_ID = 'f81d4fae-7dec-11d0-a765-00a0c91e6bf6'; | ||||
|  | ||||
| const baseFields = ` | ||||
| @@ -94,7 +95,7 @@ export const variables = { | ||||
|   deactivateMetadataField: { | ||||
|     idToUpdate: FIELD_METADATA_ID, | ||||
|     updatePayload: { isActive: false, label: undefined }, | ||||
|   } | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| const defaultResponseData = { | ||||
| @@ -127,4 +128,3 @@ export const responseData = { | ||||
|     options: [], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({ | ||||
|       featureFlags: [], | ||||
|       allowImpersonation: false, | ||||
|       activationStatus: WorkspaceActivationStatus.Active, | ||||
|       hasValidEntrepriseKey: false, | ||||
|       metadataVersion: 1, | ||||
|       isPublicInviteLinkEnabled: false, | ||||
|     }); | ||||
|   | ||||
| @@ -3,10 +3,13 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; | ||||
| export const mapSoftDeleteFieldsToGraphQLQuery = ( | ||||
|   objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>, | ||||
| ): string => { | ||||
|   const softDeleteFields = ['id', 'deletedAt']; | ||||
|   const softDeleteFields = ['deletedAt', 'id']; | ||||
|  | ||||
|   const fieldsThatShouldBeQueried = objectMetadataItem.fields.filter( | ||||
|     (field) => field.isActive && softDeleteFields.includes(field.name), | ||||
|   const fieldsThatShouldBeQueried = objectMetadataItem.fields | ||||
|     .filter((field) => field.isActive && softDeleteFields.includes(field.name)) | ||||
|     .sort( | ||||
|       (a, b) => | ||||
|         softDeleteFields.indexOf(a.name) - softDeleteFields.indexOf(b.name), | ||||
|     ); | ||||
|  | ||||
|   return `{ | ||||
|   | ||||
| @@ -45,31 +45,14 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = ` | ||||
|         primaryLinkLabel | ||||
|         secondaryLinks | ||||
|       } | ||||
| ` | ||||
| `; | ||||
|  | ||||
| export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = ` | ||||
|       __typename | ||||
|       activityTargets { | ||||
|         edges { | ||||
|           node { | ||||
|             __typename | ||||
|             activityId | ||||
|             companyId | ||||
|             createdAt | ||||
|             deletedAt | ||||
|             id | ||||
|             opportunityId | ||||
|             personId | ||||
|             rocketId | ||||
|             updatedAt | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       attachments { | ||||
|         edges { | ||||
|           node { | ||||
|             __typename | ||||
|             activityId | ||||
|             authorId | ||||
|             companyId | ||||
|             createdAt | ||||
| @@ -190,6 +173,8 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = ` | ||||
|             updatedAt | ||||
|             viewId | ||||
|             workflowId | ||||
|             workflowRunId | ||||
|             workflowVersionId | ||||
|             workspaceMemberId | ||||
|           } | ||||
|         } | ||||
| @@ -308,6 +293,9 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = ` | ||||
|             rocketId | ||||
|             taskId | ||||
|             updatedAt | ||||
|             workflowId | ||||
|             workflowRunId | ||||
|             workflowVersionId | ||||
|             workspaceMemberId | ||||
|           } | ||||
|         } | ||||
| @@ -324,4 +312,4 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = ` | ||||
|         primaryLinkLabel | ||||
|         secondaryLinks | ||||
|       } | ||||
| ` | ||||
| `; | ||||
|   | ||||
| @@ -15,5 +15,7 @@ export const variables = { | ||||
| }; | ||||
|  | ||||
| export const responseData = { | ||||
|   __typename: 'Person', | ||||
|   deletedAt: '2024-02-14T09:45:00Z', | ||||
|   id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9', | ||||
| }; | ||||
|   | ||||
| @@ -3,10 +3,19 @@ import { gql } from '@apollo/client'; | ||||
|  | ||||
| import { peopleQueryResult } from '~/testing/mock-data/people'; | ||||
|  | ||||
|  | ||||
| export const query = gql` | ||||
|   query FindManyPeople($filter: PersonFilterInput, $orderBy: [PersonOrderByInput], $lastCursor: String, $limit: Int) { | ||||
|     people(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){ | ||||
|   query FindManyPeople( | ||||
|     $filter: PersonFilterInput | ||||
|     $orderBy: [PersonOrderByInput] | ||||
|     $lastCursor: String | ||||
|     $limit: Int | ||||
|   ) { | ||||
|     people( | ||||
|       filter: $filter | ||||
|       orderBy: $orderBy | ||||
|       first: $limit | ||||
|       after: $lastCursor | ||||
|     ) { | ||||
|       edges { | ||||
|         node { | ||||
|           __typename | ||||
| @@ -27,38 +36,51 @@ export const query = gql` | ||||
|  | ||||
| export const mockPageSize = 2; | ||||
|  | ||||
| export const peopleMockWithIdsOnly: RecordGqlConnection = { ...peopleQueryResult.people,edges: peopleQueryResult.people.edges.map((edge) => ({ ...edge, node: { __typename: 'Person', id: edge.node.id } })) }; | ||||
| export const peopleMockWithIdsOnly: RecordGqlConnection = { | ||||
|   ...peopleQueryResult.people, | ||||
|   edges: peopleQueryResult.people.edges.map((edge) => ({ | ||||
|     ...edge, | ||||
|     node: { __typename: 'Person', id: edge.node.id }, | ||||
|   })), | ||||
| }; | ||||
|  | ||||
| export const firstRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize].cursor; | ||||
| export const secondRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor; | ||||
| export const thirdRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor; | ||||
| export const firstRequestLastCursor = | ||||
|   peopleMockWithIdsOnly.edges[mockPageSize].cursor; | ||||
| export const secondRequestLastCursor = | ||||
|   peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor; | ||||
| export const thirdRequestLastCursor = | ||||
|   peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor; | ||||
|  | ||||
| export const variablesFirstRequest = { | ||||
|   filter: undefined, | ||||
|   limit: mockPageSize, | ||||
|   orderBy: undefined | ||||
|   orderBy: undefined, | ||||
| }; | ||||
|  | ||||
| export const variablesSecondRequest = { | ||||
|   filter: undefined, | ||||
|   limit: mockPageSize, | ||||
|   orderBy: undefined, | ||||
|   lastCursor: firstRequestLastCursor | ||||
|   lastCursor: firstRequestLastCursor, | ||||
| }; | ||||
|  | ||||
| export const variablesThirdRequest = { | ||||
|   filter: undefined, | ||||
|   limit: mockPageSize, | ||||
|   orderBy: undefined, | ||||
|   lastCursor: secondRequestLastCursor | ||||
| } | ||||
|   lastCursor: secondRequestLastCursor, | ||||
| }; | ||||
|  | ||||
| const paginateRequestResponse = (response: RecordGqlConnection, start: number, end: number, hasNextPage: boolean, totalCount: number) => { | ||||
| const paginateRequestResponse = ( | ||||
|   response: RecordGqlConnection, | ||||
|   start: number, | ||||
|   end: number, | ||||
|   hasNextPage: boolean, | ||||
|   totalCount: number, | ||||
| ) => { | ||||
|   return { | ||||
|     ...response, | ||||
|     edges: [ | ||||
|       ...response.edges.slice(start, end) | ||||
|     ], | ||||
|     edges: [...response.edges.slice(start, end)], | ||||
|     pageInfo: { | ||||
|       ...response.pageInfo, | ||||
|       startCursor: response.edges[start].cursor, | ||||
| @@ -66,17 +88,35 @@ const paginateRequestResponse = (response: RecordGqlConnection, start: number, e | ||||
|       hasNextPage, | ||||
|     } satisfies RecordGqlConnection['pageInfo'], | ||||
|     totalCount, | ||||
|   } | ||||
| } | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export const responseFirstRequest = { | ||||
|   people: paginateRequestResponse(peopleMockWithIdsOnly, 0, mockPageSize, true, 6), | ||||
|   people: paginateRequestResponse( | ||||
|     peopleMockWithIdsOnly, | ||||
|     0, | ||||
|     mockPageSize, | ||||
|     true, | ||||
|     6, | ||||
|   ), | ||||
| }; | ||||
|  | ||||
| export const responseSecondRequest = { | ||||
|   people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize, mockPageSize * 2, true, 6), | ||||
|   people: paginateRequestResponse( | ||||
|     peopleMockWithIdsOnly, | ||||
|     mockPageSize, | ||||
|     mockPageSize * 2, | ||||
|     true, | ||||
|     6, | ||||
|   ), | ||||
| }; | ||||
|  | ||||
| export const responseThirdRequest = { | ||||
|   people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize * 2, mockPageSize * 3, false, 6), | ||||
|   people: paginateRequestResponse( | ||||
|     peopleMockWithIdsOnly, | ||||
|     mockPageSize * 2, | ||||
|     mockPageSize * 3, | ||||
|     false, | ||||
|     6, | ||||
|   ), | ||||
| }; | ||||
|   | ||||
| @@ -99,20 +99,6 @@ export const query = gql` | ||||
|           } | ||||
|           city | ||||
|           email | ||||
|           activityTargets { | ||||
|             edges { | ||||
|               node { | ||||
|                 __typename | ||||
|                 id | ||||
|                 updatedAt | ||||
|                 createdAt | ||||
|                 personId | ||||
|                 activityId | ||||
|                 companyId | ||||
|                 id | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           jobTitle | ||||
|           favorites { | ||||
|             edges { | ||||
| @@ -137,7 +123,6 @@ export const query = gql` | ||||
|                 createdAt | ||||
|                 name | ||||
|                 personId | ||||
|                 activityId | ||||
|                 companyId | ||||
|                 id | ||||
|                 authorId | ||||
|   | ||||
| @@ -5,15 +5,10 @@ import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter | ||||
| import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; | ||||
| import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; | ||||
| import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; | ||||
| import styled from '@emotion/styled'; | ||||
| import { useRecoilValue } from 'recoil'; | ||||
| import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect'; | ||||
| import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect'; | ||||
|  | ||||
| const StyledContainer = styled.div` | ||||
|   position: relative; | ||||
| `; | ||||
|  | ||||
| type MultipleFiltersDropdownContentProps = { | ||||
|   filterDropdownId?: string; | ||||
| }; | ||||
| @@ -46,7 +41,7 @@ export const MultipleFiltersDropdownContent = ({ | ||||
|   const shoudShowFilterInput = objectFilterDropdownFilterIsSelected; | ||||
|  | ||||
|   return ( | ||||
|     <StyledContainer> | ||||
|     <> | ||||
|       {shoudShowFilterInput ? ( | ||||
|         <ObjectFilterOperandSelectAndInput | ||||
|           filterDropdownId={filterDropdownId} | ||||
| @@ -61,6 +56,6 @@ export const MultipleFiltersDropdownContent = ({ | ||||
|           filterDefinitionUsedInDropdown?.type | ||||
|         } | ||||
|       /> | ||||
|     </StyledContainer> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -28,8 +28,13 @@ export const ObjectFilterDropdownFilterInput = ({ | ||||
|   const { | ||||
|     filterDefinitionUsedInDropdownState, | ||||
|     selectedOperandInDropdownState, | ||||
|     isObjectFilterDropdownOperandSelectUnfoldedState, | ||||
|   } = useFilterDropdown({ filterDropdownId }); | ||||
|  | ||||
|   const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue( | ||||
|     isObjectFilterDropdownOperandSelectUnfoldedState, | ||||
|   ); | ||||
|  | ||||
|   const filterDefinitionUsedInDropdown = useRecoilValue( | ||||
|     filterDefinitionUsedInDropdownState, | ||||
|   ); | ||||
| @@ -53,7 +58,9 @@ export const ObjectFilterDropdownFilterInput = ({ | ||||
|       ViewFilterOperand.IsRelative, | ||||
|     ].includes(selectedOperandInDropdown); | ||||
|  | ||||
|   if (!isDefined(filterDefinitionUsedInDropdown)) { | ||||
|   const shouldHide = isObjectFilterDropdownOperandSelectUnfolded; | ||||
|  | ||||
|   if (shouldHide || !isDefined(filterDefinitionUsedInDropdown)) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -8,9 +8,7 @@ const StyledOperandSelectContainer = styled.div` | ||||
|   background: ${({ theme }) => theme.background.secondary}; | ||||
|   box-shadow: ${({ theme }) => theme.boxShadow.light}; | ||||
|   border-radius: ${({ theme }) => theme.border.radius.md}; | ||||
|   left: 10px; | ||||
|   position: absolute; | ||||
|   top: 10px; | ||||
|  | ||||
|   width: 100%; | ||||
|   z-index: 1000; | ||||
| `; | ||||
|   | ||||
| @@ -16,9 +16,11 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM | ||||
| import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; | ||||
| import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; | ||||
| import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; | ||||
| import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; | ||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||
| import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; | ||||
| import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; | ||||
| import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; | ||||
| import { useRecoilValue } from 'recoil'; | ||||
| import { isDefined } from 'twenty-ui'; | ||||
|  | ||||
| @@ -34,7 +36,7 @@ export const StyledInput = styled.input` | ||||
|   margin: 0; | ||||
|   outline: none; | ||||
|   padding: ${({ theme }) => theme.spacing(2)}; | ||||
|   height: 19px; | ||||
|   min-height: 19px; | ||||
|   font-family: inherit; | ||||
|   font-size: ${({ theme }) => theme.font.size.sm}; | ||||
|  | ||||
| @@ -139,10 +141,15 @@ export const ObjectFilterDropdownFilterSelect = ({ | ||||
|   const { currentViewId, currentViewWithCombinedFiltersAndSorts } = | ||||
|     useGetCurrentView(); | ||||
|  | ||||
|   const isAdvancedFiltersEnabled = useIsFeatureEnabled( | ||||
|     'IS_ADVANCED_FILTERS_ENABLED', | ||||
|   ); | ||||
|  | ||||
|   const shouldShowAdvancedFilterButton = | ||||
|     isDefined(currentViewId) && | ||||
|     isDefined(currentViewWithCombinedFiltersAndSorts?.objectMetadataId) && | ||||
|     isAdvancedFilterButtonVisible; | ||||
|     isAdvancedFilterButtonVisible && | ||||
|     isAdvancedFiltersEnabled; | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
| @@ -154,6 +161,7 @@ export const ObjectFilterDropdownFilterSelect = ({ | ||||
|           setObjectFilterDropdownSearchInput(event.target.value) | ||||
|         } | ||||
|       /> | ||||
|       <ScrollWrapper contextProviderName="dropdownMenuItemsContainer"> | ||||
|         <SelectableList | ||||
|           hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton} | ||||
|           selectableItemIdArray={selectableListItemIds} | ||||
| @@ -191,6 +199,7 @@ export const ObjectFilterDropdownFilterSelect = ({ | ||||
|           </DropdownMenuItemsContainer> | ||||
|         </SelectableList> | ||||
|         {shouldShowAdvancedFilterButton && <AdvancedFilterButton />} | ||||
|       </ScrollWrapper> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -10,17 +10,28 @@ export const ObjectFilterDropdownOperandButton = () => { | ||||
|   const { | ||||
|     selectedOperandInDropdownState, | ||||
|     setIsObjectFilterDropdownOperandSelectUnfolded, | ||||
|     isObjectFilterDropdownOperandSelectUnfoldedState, | ||||
|   } = useFilterDropdown(); | ||||
|  | ||||
|   const selectedOperandInDropdown = useRecoilValue( | ||||
|     selectedOperandInDropdownState, | ||||
|   ); | ||||
|  | ||||
|   const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue( | ||||
|     isObjectFilterDropdownOperandSelectUnfoldedState, | ||||
|   ); | ||||
|  | ||||
|   const handleButtonClick = () => { | ||||
|     setIsObjectFilterDropdownOperandSelectUnfolded( | ||||
|       !isObjectFilterDropdownOperandSelectUnfolded, | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <DropdownMenuHeader | ||||
|       key={'selected-filter-operand'} | ||||
|       EndIcon={IconChevronDown} | ||||
|       onClick={() => setIsObjectFilterDropdownOperandSelectUnfolded(true)} | ||||
|       onClick={handleButtonClick} | ||||
|     > | ||||
|       {getOperandLabel(selectedOperandInDropdown)} | ||||
|     </DropdownMenuHeader> | ||||
|   | ||||
| @@ -16,6 +16,7 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab | ||||
| import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; | ||||
| import { MenuItemMultiSelect } from '@/ui/navigation/menu-item/components/MenuItemMultiSelect'; | ||||
| import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; | ||||
| import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; | ||||
| import { isDefined } from '~/utils/isDefined'; | ||||
|  | ||||
| export const EMPTY_FILTER_VALUE = ''; | ||||
| @@ -162,6 +163,7 @@ export const ObjectFilterDropdownOptionSelect = () => { | ||||
|         } | ||||
|       }} | ||||
|     > | ||||
|       <ScrollWrapper contextProviderName="dropdownMenuItemsContainer"> | ||||
|         <DropdownMenuItemsContainer hasMaxHeight> | ||||
|           {optionsInDropdown?.map((option) => ( | ||||
|             <MenuItemMultiSelect | ||||
| @@ -178,6 +180,7 @@ export const ObjectFilterDropdownOptionSelect = () => { | ||||
|           ))} | ||||
|         </DropdownMenuItemsContainer> | ||||
|         {showNoResult && <MenuItem text="No result" />} | ||||
|       </ScrollWrapper> | ||||
|     </SelectableList> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/Styl | ||||
| import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; | ||||
| import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; | ||||
| import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; | ||||
| import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; | ||||
| import { useContext } from 'react'; | ||||
| import { SORT_DIRECTIONS } from '../types/SortDirection'; | ||||
|  | ||||
| @@ -42,17 +43,13 @@ export const StyledInput = styled.input` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const StyledContainer = styled.div` | ||||
|   position: relative; | ||||
| `; | ||||
|  | ||||
| const StyledSelectedSortDirectionContainer = styled.div` | ||||
|   background: ${({ theme }) => theme.background.secondary}; | ||||
|   box-shadow: ${({ theme }) => theme.boxShadow.light}; | ||||
|   border-radius: ${({ theme }) => theme.border.radius.md}; | ||||
|   left: 10px; | ||||
|  | ||||
|   position: absolute; | ||||
|   top: 10px; | ||||
|   top: 32px; | ||||
|   width: 100%; | ||||
|   z-index: 1000; | ||||
| `; | ||||
| @@ -166,10 +163,11 @@ export const ObjectSortDropdownButton = ({ | ||||
|                 </DropdownMenuItemsContainer> | ||||
|               </StyledSelectedSortDirectionContainer> | ||||
|             )} | ||||
|             <StyledContainer> | ||||
|             <DropdownMenuHeader | ||||
|               EndIcon={IconChevronDown} | ||||
|                 onClick={() => setIsSortDirectionMenuUnfolded(true)} | ||||
|               onClick={() => | ||||
|                 setIsSortDirectionMenuUnfolded(!isSortDirectionMenuUnfolded) | ||||
|               } | ||||
|             > | ||||
|               {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} | ||||
|             </DropdownMenuHeader> | ||||
| @@ -181,6 +179,7 @@ export const ObjectSortDropdownButton = ({ | ||||
|                 setObjectSortDropdownSearchInput(event.target.value) | ||||
|               } | ||||
|             /> | ||||
|             <ScrollWrapper contextProviderName="dropdownMenuItemsContainer"> | ||||
|               <DropdownMenuItemsContainer> | ||||
|                 {visibleColumnsSortDefinitions.map( | ||||
|                   (visibleSortDefinition, index) => ( | ||||
| @@ -214,7 +213,7 @@ export const ObjectSortDropdownButton = ({ | ||||
|                   ), | ||||
|                 )} | ||||
|               </DropdownMenuItemsContainer> | ||||
|             </StyledContainer> | ||||
|             </ScrollWrapper> | ||||
|           </> | ||||
|         } | ||||
|         onClose={handleDropdownButtonClose} | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import { Key } from 'ts-key-enum'; | ||||
|  | ||||
| import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader'; | ||||
| import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect'; | ||||
| import { RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-board/constants/RecordBoardClickOutsideListenerId'; | ||||
| import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; | ||||
| import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; | ||||
| import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; | ||||
| @@ -16,7 +17,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco | ||||
| import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; | ||||
| import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; | ||||
| import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; | ||||
| import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; | ||||
| import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; | ||||
| import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; | ||||
| import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; | ||||
| import { useScrollRestoration } from '~/hooks/useScrollRestoration'; | ||||
| @@ -69,9 +70,15 @@ export const RecordBoard = () => { | ||||
|   const { resetRecordSelection, setRecordAsSelected } = | ||||
|     useRecordBoardSelection(recordBoardId); | ||||
|  | ||||
|   useListenClickOutsideByClassName({ | ||||
|     classNames: ['record-board-card'], | ||||
|     excludeClassNames: ['bottom-bar', 'action-menu-dropdown'], | ||||
|   useListenClickOutsideV2({ | ||||
|     excludeClassNames: [ | ||||
|       'bottom-bar', | ||||
|       'action-menu-dropdown', | ||||
|       'command-menu', | ||||
|       'modal-backdrop', | ||||
|     ], | ||||
|     listenerId: RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID, | ||||
|     refs: [boardRef], | ||||
|     callback: resetRecordSelection, | ||||
|   }); | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1 @@ | ||||
| export const RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID = 'record-board'; | ||||
| @@ -17,7 +17,6 @@ import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/ | ||||
| import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; | ||||
| import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; | ||||
| import { ObjectRecord } from '@/object-record/types/ObjectRecord'; | ||||
| import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox'; | ||||
| import { TextInput } from '@/ui/input/components/TextInput'; | ||||
| import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; | ||||
| import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts'; | ||||
| @@ -29,6 +28,8 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; | ||||
| import { | ||||
|   AnimatedEaseInOut, | ||||
|   AvatarChipVariant, | ||||
|   Checkbox, | ||||
|   CheckboxVariant, | ||||
|   ChipSize, | ||||
|   IconEye, | ||||
|   IconEyeOff, | ||||
|   | ||||
| @@ -7,7 +7,9 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types | ||||
| import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; | ||||
| import { useCallback, useContext } from 'react'; | ||||
| import { RecoilState, useRecoilCallback } from 'recoil'; | ||||
| import { isDefined } from 'twenty-ui'; | ||||
| import { v4 as uuidv4 } from 'uuid'; | ||||
| import { FieldMetadataType } from '~/generated-metadata/graphql'; | ||||
|  | ||||
| type SetFunction = <T>( | ||||
|   recoilVal: RecoilState<T>, | ||||
| @@ -16,7 +18,7 @@ type SetFunction = <T>( | ||||
|  | ||||
| export const useAddNewCard = () => { | ||||
|   const columnContext = useContext(RecordBoardColumnContext); | ||||
|   const { createOneRecord, selectFieldMetadataItem } = | ||||
|   const { createOneRecord, selectFieldMetadataItem, objectMetadataItem } = | ||||
|     useContext(RecordBoardContext); | ||||
|   const { resetSearchFilter } = useEntitySelectSearch({ | ||||
|     relationPickerScopeId: 'relation-picker', | ||||
| @@ -75,16 +77,47 @@ export const useAddNewCard = () => { | ||||
|         (isOpportunity && company !== null) || | ||||
|         (!isOpportunity && labelValue !== '') | ||||
|       ) { | ||||
|         // TODO: Refactor this whole section (Add new card): this should be: | ||||
|         // - simpler | ||||
|         // - piloted by metadata, | ||||
|         // - avoid drill down props, especially internal stuff | ||||
|         // - and follow record table pending record creation logic | ||||
|         let computedLabelIdentifierValue: any = labelValue; | ||||
|  | ||||
|         const labelIdentifierField = objectMetadataItem?.fields.find( | ||||
|           (field) => | ||||
|             field.id === objectMetadataItem.labelIdentifierFieldMetadataId, | ||||
|         ); | ||||
|  | ||||
|         if (!isDefined(labelIdentifierField)) { | ||||
|           throw new Error('Label identifier field not found'); | ||||
|         } | ||||
|  | ||||
|         if (labelIdentifierField.type === FieldMetadataType.FullName) { | ||||
|           computedLabelIdentifierValue = { | ||||
|             firstName: labelValue, | ||||
|             lastName: '', | ||||
|           }; | ||||
|         } | ||||
|  | ||||
|         createOneRecord({ | ||||
|           [selectFieldMetadataItem.name]: columnContext?.columnDefinition.value, | ||||
|           position, | ||||
|           ...(isOpportunity | ||||
|             ? { companyId: company?.id, name: company?.name } | ||||
|             : { [labelIdentifier.toLowerCase()]: labelValue }), | ||||
|             : { | ||||
|                 [labelIdentifier.toLowerCase()]: computedLabelIdentifierValue, | ||||
|               }), | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
|     [createOneRecord, columnContext, selectFieldMetadataItem], | ||||
|     [ | ||||
|       objectMetadataItem?.fields, | ||||
|       objectMetadataItem?.labelIdentifierFieldMetadataId, | ||||
|       createOneRecord, | ||||
|       selectFieldMetadataItem?.name, | ||||
|       columnContext?.columnDefinition?.value, | ||||
|     ], | ||||
|   ); | ||||
|  | ||||
|   const handleAddNewCardClick = useRecoilCallback( | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { | ||||
|   FieldFullNameMetadata, | ||||
|   FieldRatingMetadata, | ||||
|   FieldSelectMetadata, | ||||
|   FieldTextMetadata | ||||
|   FieldTextMetadata, | ||||
| } from '@/object-record/record-field/types/FieldMetadata'; | ||||
| import { FieldMetadataType } from '~/generated-metadata/graphql'; | ||||
| import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; | ||||
| @@ -29,7 +29,6 @@ if (!mockedPersonObjectMetadataItem) { | ||||
|   throw new Error('Person object metadata item not found'); | ||||
| } | ||||
|  | ||||
|  | ||||
| const relationFieldMetadataItem = mockedPersonObjectMetadataItem?.fields?.find( | ||||
|   ({ name }) => name === 'company', | ||||
| ); | ||||
|   | ||||
| @@ -47,22 +47,6 @@ const mocks: MockedResponse[] = [ | ||||
|               userId | ||||
|             } | ||||
|             accountOwnerId | ||||
|             activityTargets { | ||||
|               edges { | ||||
|                 node { | ||||
|                   __typename | ||||
|                   activityId | ||||
|                   companyId | ||||
|                   createdAt | ||||
|                   deletedAt | ||||
|                   id | ||||
|                   opportunityId | ||||
|                   personId | ||||
|                   rocketId | ||||
|                   updatedAt | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|             address { | ||||
|               addressStreet1 | ||||
|               addressStreet2 | ||||
| @@ -81,7 +65,6 @@ const mocks: MockedResponse[] = [ | ||||
|               edges { | ||||
|                 node { | ||||
|                   __typename | ||||
|                   activityId | ||||
|                   authorId | ||||
|                   companyId | ||||
|                   createdAt | ||||
| @@ -129,6 +112,8 @@ const mocks: MockedResponse[] = [ | ||||
|                   updatedAt | ||||
|                   viewId | ||||
|                   workflowId | ||||
|                   workflowRunId | ||||
|                   workflowVersionId | ||||
|                   workspaceMemberId | ||||
|                 } | ||||
|               } | ||||
| @@ -278,6 +263,9 @@ const mocks: MockedResponse[] = [ | ||||
|                   rocketId | ||||
|                   taskId | ||||
|                   updatedAt | ||||
|                   workflowId | ||||
|                   workflowRunId | ||||
|                   workflowVersionId | ||||
|                   workspaceMemberId | ||||
|                 } | ||||
|               } | ||||
|   | ||||
| @@ -21,7 +21,6 @@ import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmp | ||||
|  | ||||
| const StyledDropdownMenu = styled(DropdownMenu)` | ||||
|   left: -1px; | ||||
|   position: absolute; | ||||
|   top: -1px; | ||||
| `; | ||||
|  | ||||
| @@ -46,6 +45,7 @@ type MultiItemFieldInputProps<T> = { | ||||
| }; | ||||
|  | ||||
| // Todo: the API of this component does not look healthy: we have renderInput, renderItem, formatInput, ... | ||||
| // This should be refactored with a hook instead that exposes those events in a context around this component and its children. | ||||
| export const MultiItemFieldInput = <T,>({ | ||||
|   items, | ||||
|   onPersist, | ||||
| @@ -84,9 +84,9 @@ export const MultiItemFieldInput = <T,>({ | ||||
|     setInputValue(value); | ||||
|     if (!validateInput) return; | ||||
|  | ||||
|     if (errorData.isValid) { | ||||
|       setErrorData(errorData); | ||||
|     } | ||||
|     setErrorData( | ||||
|       errorData.isValid ? errorData : { isValid: true, errorMessage: '' }, | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   const handleAddButtonClick = () => { | ||||
|   | ||||
| @@ -1,14 +1,11 @@ | ||||
| import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; | ||||
| import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; | ||||
| import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; | ||||
| import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; | ||||
| import styled from '@emotion/styled'; | ||||
| import { useEffect, useState } from 'react'; | ||||
| import { MenuItemWithOptionDropdown } from '@/ui/navigation/menu-item/components/MenuItemWithOptionDropdown'; | ||||
| import { useState } from 'react'; | ||||
| import { | ||||
|   IconBookmark, | ||||
|   IconBookmarkPlus, | ||||
|   IconComponent, | ||||
|   IconDotsVertical, | ||||
|   IconPencil, | ||||
|   IconTrash, | ||||
| } from 'twenty-ui'; | ||||
| @@ -24,12 +21,6 @@ type MultiItemFieldMenuItemProps<T> = { | ||||
|   hasPrimaryButton?: boolean; | ||||
| }; | ||||
|  | ||||
| const StyledIconBookmark = styled(IconBookmark)` | ||||
|   color: ${({ theme }) => theme.font.color.light}; | ||||
|   height: ${({ theme }) => theme.icon.size.sm}px; | ||||
|   width: ${({ theme }) => theme.icon.size.sm}px; | ||||
| `; | ||||
|  | ||||
| export const MultiItemFieldMenuItem = <T,>({ | ||||
|   dropdownId, | ||||
|   isPrimary, | ||||
| @@ -47,46 +38,42 @@ export const MultiItemFieldMenuItem = <T,>({ | ||||
|   const handleMouseLeave = () => setIsHovered(false); | ||||
|  | ||||
|   const handleDeleteClick = () => { | ||||
|     closeDropdown(); | ||||
|     setIsHovered(false); | ||||
|     onDelete?.(); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (isDropdownOpen) { | ||||
|       return () => closeDropdown(); | ||||
|     } | ||||
|   }, [closeDropdown, isDropdownOpen]); | ||||
|   const handleSetAsPrimaryClick = () => { | ||||
|     closeDropdown(); | ||||
|     onSetAsPrimary?.(); | ||||
|   }; | ||||
|  | ||||
|   const handleEditClick = () => { | ||||
|     closeDropdown(); | ||||
|     onEdit?.(); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <MenuItem | ||||
|     <MenuItemWithOptionDropdown | ||||
|       onMouseEnter={handleMouseEnter} | ||||
|       onMouseLeave={handleMouseLeave} | ||||
|       text={<DisplayComponent value={value} />} | ||||
|       isIconDisplayedOnHoverOnly={!isPrimary && !isDropdownOpen} | ||||
|       iconButtons={[ | ||||
|         { | ||||
|           Wrapper: isHovered | ||||
|             ? ({ iconButton }) => ( | ||||
|                 <Dropdown | ||||
|       RightIcon={isHovered ? null : IconBookmark} | ||||
|       dropdownId={dropdownId} | ||||
|                   dropdownHotkeyScope={{ scope: dropdownId }} | ||||
|                   dropdownPlacement="right-start" | ||||
|                   dropdownStrategy="fixed" | ||||
|                   disableBlur | ||||
|                   clickableComponent={iconButton} | ||||
|                   dropdownComponents={ | ||||
|       dropdownContent={ | ||||
|         <DropdownMenuItemsContainer> | ||||
|           {hasPrimaryButton && !isPrimary && ( | ||||
|             <MenuItem | ||||
|               LeftIcon={IconBookmarkPlus} | ||||
|               text="Set as Primary" | ||||
|                           onClick={onSetAsPrimary} | ||||
|               onClick={handleSetAsPrimaryClick} | ||||
|             /> | ||||
|           )} | ||||
|           <MenuItem | ||||
|             LeftIcon={IconPencil} | ||||
|             text="Edit" | ||||
|                         onClick={onEdit} | ||||
|             onClick={handleEditClick} | ||||
|           /> | ||||
|           <MenuItem | ||||
|             accent="danger" | ||||
| @@ -97,16 +84,5 @@ export const MultiItemFieldMenuItem = <T,>({ | ||||
|         </DropdownMenuItemsContainer> | ||||
|       } | ||||
|     /> | ||||
|               ) | ||||
|             : undefined, | ||||
|           Icon: | ||||
|             isPrimary && !isHovered | ||||
|               ? (StyledIconBookmark as IconComponent) | ||||
|               : IconDotsVertical, | ||||
|           accent: 'tertiary', | ||||
|           onClick: isHovered ? () => {} : undefined, | ||||
|         }, | ||||
|       ]} | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -206,6 +206,7 @@ export const RecordIndexContainer = () => { | ||||
|             viewBarId={recordIndexId} | ||||
|           /> | ||||
|         </SpreadsheetImportProvider> | ||||
|  | ||||
|         {recordIndexViewType === ViewType.Table && ( | ||||
|           <> | ||||
|             <RecordIndexTableContainer | ||||
| @@ -232,7 +233,7 @@ export const RecordIndexContainer = () => { | ||||
|             /> | ||||
|           </StyledContainerWithPadding> | ||||
|         )} | ||||
|         <RecordIndexActionMenu actionMenuId={recordIndexId} /> | ||||
|         <RecordIndexActionMenu /> | ||||
|       </RecordFieldValueSelectorContextProvider> | ||||
|     </StyledContainer> | ||||
|   ); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata | ||||
| import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; | ||||
| import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter'; | ||||
| import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort'; | ||||
| import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; | ||||
| import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; | ||||
| import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; | ||||
| import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView'; | ||||
| @@ -81,12 +82,14 @@ export const RecordIndexTableContainerEffect = () => { | ||||
|   const selectedRowIds = useRecoilValue(selectedRowIdsSelector()); | ||||
|   const unselectedRowIds = useRecoilValue(unselectedRowIdsSelector()); | ||||
|  | ||||
|   const recordIndexFilters = useRecoilValue(recordIndexFiltersState); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (hasUserSelectedAllRows) { | ||||
|       setContextStoreTargetedRecords({ | ||||
|         mode: 'exclusion', | ||||
|         excludedRecordIds: unselectedRowIds, | ||||
|         filters: [], | ||||
|         filters: recordIndexFilters, | ||||
|       }); | ||||
|     } else { | ||||
|       setContextStoreTargetedRecords({ | ||||
| @@ -103,6 +106,7 @@ export const RecordIndexTableContainerEffect = () => { | ||||
|     }; | ||||
|   }, [ | ||||
|     hasUserSelectedAllRows, | ||||
|     recordIndexFilters, | ||||
|     selectedRowIds, | ||||
|     setContextStoreTargetedRecords, | ||||
|     unselectedRowIds, | ||||
|   | ||||
| @@ -40,6 +40,7 @@ import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemN | ||||
| import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle'; | ||||
| import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; | ||||
| import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; | ||||
| import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; | ||||
| import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection'; | ||||
| import { ViewGroupsVisibilityDropdownSection } from '@/views/components/ViewGroupsVisibilityDropdownSection'; | ||||
| import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; | ||||
| @@ -259,6 +260,7 @@ export const RecordIndexOptionsDropdownContent = ({ | ||||
|           <DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}> | ||||
|             Fields | ||||
|           </DropdownMenuHeader> | ||||
|           <ScrollWrapper contextProviderName="dropdownMenuItemsContainer"> | ||||
|             <ViewFieldsVisibilityDropdownSection | ||||
|               title="Visible" | ||||
|               fields={visibleRecordFields} | ||||
| @@ -268,6 +270,7 @@ export const RecordIndexOptionsDropdownContent = ({ | ||||
|               showSubheader={false} | ||||
|               showDragGrip={true} | ||||
|             /> | ||||
|           </ScrollWrapper> | ||||
|           <DropdownMenuSeparator /> | ||||
|           <DropdownMenuItemsContainer> | ||||
|             <MenuItemNavigate | ||||
| @@ -317,7 +320,7 @@ export const RecordIndexOptionsDropdownContent = ({ | ||||
|             Hidden Fields | ||||
|           </DropdownMenuHeader> | ||||
|           {hiddenRecordFields.length > 0 && ( | ||||
|             <> | ||||
|             <ScrollWrapper contextProviderName="dropdownMenuItemsContainer"> | ||||
|               <ViewFieldsVisibilityDropdownSection | ||||
|                 title="Hidden" | ||||
|                 fields={hiddenRecordFields} | ||||
| @@ -326,7 +329,7 @@ export const RecordIndexOptionsDropdownContent = ({ | ||||
|                 showSubheader={false} | ||||
|                 showDragGrip={false} | ||||
|               /> | ||||
|             </> | ||||
|             </ScrollWrapper> | ||||
|           )} | ||||
|           <DropdownMenuSeparator /> | ||||
|  | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 bosiraphael
					bosiraphael