mirror of
https://github.com/lingble/twenty.git
synced 2025-10-29 20:02:29 +00:00
Merge branch 'main' into o365sync
This commit is contained in:
2
.github/workflows/ci-front.yaml
vendored
2
.github/workflows/ci-front.yaml
vendored
@@ -223,4 +223,4 @@ jobs:
|
|||||||
uses: ./.github/workflows/actions/nx-affected
|
uses: ./.github/workflows/actions/nx-affected
|
||||||
with:
|
with:
|
||||||
tag: scope:frontend
|
tag: scope:frontend
|
||||||
tasks: ${{ matrix.task }}
|
tasks: ${{ matrix.task }}
|
||||||
|
|||||||
@@ -41,10 +41,7 @@ jobs:
|
|||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
echo "Generating secrets..."
|
echo "Generating secrets..."
|
||||||
echo "# === Randomly generated secrets ===" >>.env
|
echo "# === Randomly generated secrets ===" >>.env
|
||||||
echo "ACCESS_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
|
echo "APP_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 "POSTGRES_ADMIN_PASSWORD=$(openssl rand -base64 32)" >>.env
|
echo "POSTGRES_ADMIN_PASSWORD=$(openssl rand -base64 32)" >>.env
|
||||||
|
|
||||||
echo "Starting server..."
|
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'
|
if: github.event.action != 'closed'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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
|
- name: Install dependencies
|
||||||
if: steps.changed-files.outputs.changed == 'true'
|
|
||||||
uses: ./.github/workflows/actions/yarn-install
|
uses: ./.github/workflows/actions/yarn-install
|
||||||
- name: Utils / Run Danger.js
|
- name: Utils / Run Danger.js
|
||||||
if: steps.changed-files.outputs.changed == 'true'
|
|
||||||
run: cd packages/twenty-utils && npx nx danger:ci
|
run: cd packages/twenty-utils && npx nx danger:ci
|
||||||
env:
|
env:
|
||||||
DANGER_GITHUB_API_TOKEN: ${{ github.token }}
|
DANGER_GITHUB_API_TOKEN: ${{ github.token }}
|
||||||
@@ -42,16 +35,9 @@ jobs:
|
|||||||
if: github.event.action == 'closed' && github.event.pull_request.merged == true
|
if: github.event.action == 'closed' && github.event.pull_request.merged == true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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
|
- name: Install dependencies
|
||||||
if: steps.changed-files.outputs.changed == 'true'
|
|
||||||
uses: ./.github/workflows/actions/yarn-install
|
uses: ./.github/workflows/actions/yarn-install
|
||||||
- name: Run congratulate-dangerfile.js
|
- name: Run congratulate-dangerfile.js
|
||||||
if: steps.changed-files.outputs.changed == 'true'
|
|
||||||
run: cd packages/twenty-utils && npx nx danger:congratulate
|
run: cd packages/twenty-utils && npx nx danger:congratulate
|
||||||
env:
|
env:
|
||||||
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
.nx/installation
|
.nx/installation
|
||||||
.nx/cache
|
.nx/cache
|
||||||
projectStructure.cache.json
|
|
||||||
|
|
||||||
.pnp.*
|
.pnp.*
|
||||||
.yarn/*
|
.yarn/*
|
||||||
@@ -30,3 +29,4 @@ storybook-static
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
test-results/
|
test-results/
|
||||||
dump.rdb
|
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>
|
<br>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|||||||
@@ -91,10 +91,7 @@ fi
|
|||||||
|
|
||||||
# Generate random strings for secrets
|
# Generate random strings for secrets
|
||||||
echo "# === Randomly generated secrets ===" >>.env
|
echo "# === Randomly generated secrets ===" >>.env
|
||||||
echo "ACCESS_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
|
echo "APP_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 "" >>.env
|
echo "" >>.env
|
||||||
echo "POSTGRES_ADMIN_PASSWORD=$(openssl rand -base64 32)" >>.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-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||||
"eslint-plugin-prettier": "^5.1.2",
|
"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": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.4",
|
"eslint-plugin-react-refresh": "^0.4.4",
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ REDIS_URL=redis://redis:6379
|
|||||||
SERVER_URL=http://localhost:3000
|
SERVER_URL=http://localhost:3000
|
||||||
|
|
||||||
# Use openssl rand -base64 32 for each secret
|
# Use openssl rand -base64 32 for each secret
|
||||||
# ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access
|
# APP_SECRET=replace_me_with_a_random_string
|
||||||
# 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
|
|
||||||
|
|
||||||
SIGN_IN_PREFILLED=true
|
SIGN_IN_PREFILLED=true
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ services:
|
|||||||
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
|
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
|
||||||
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
|
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
|
||||||
|
|
||||||
ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET}
|
APP_SECRET: ${APP_SECRET}
|
||||||
LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET}
|
|
||||||
REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET}
|
|
||||||
FILE_TOKEN_SECRET: ${FILE_TOKEN_SECRET}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
change-vol-ownership:
|
change-vol-ownership:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -67,10 +64,7 @@ services:
|
|||||||
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
|
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
|
||||||
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
|
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
|
||||||
|
|
||||||
ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET}
|
APP_SECRET: ${APP_SECRET}
|
||||||
LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET}
|
|
||||||
REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET}
|
|
||||||
FILE_TOKEN_SECRET: ${FILE_TOKEN_SECRET}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -55,26 +55,11 @@ spec:
|
|||||||
value: "7d"
|
value: "7d"
|
||||||
- name: "LOGIN_TOKEN_EXPIRES_IN"
|
- name: "LOGIN_TOKEN_EXPIRES_IN"
|
||||||
value: "1h"
|
value: "1h"
|
||||||
- name: ACCESS_TOKEN_SECRET
|
- name: APP_SECRET
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: tokens
|
name: tokens
|
||||||
key: accessToken
|
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:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
name: http-tcp
|
name: http-tcp
|
||||||
|
|||||||
@@ -42,26 +42,11 @@ spec:
|
|||||||
value: "redis"
|
value: "redis"
|
||||||
- name: "REDIS_URL"
|
- name: "REDIS_URL"
|
||||||
value: "redis://twentycrm-redis.twentycrm.svc.cluster.local:6379"
|
value: "redis://twentycrm-redis.twentycrm.svc.cluster.local:6379"
|
||||||
- name: ACCESS_TOKEN_SECRET
|
- name: APP_SECRET
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: tokens
|
name: tokens
|
||||||
key: accessToken
|
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:
|
command:
|
||||||
- yarn
|
- yarn
|
||||||
- worker:prod
|
- worker:prod
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ resource "kubernetes_deployment" "twentycrm_server" {
|
|||||||
value = "1h"
|
value = "1h"
|
||||||
}
|
}
|
||||||
env {
|
env {
|
||||||
name = "ACCESS_TOKEN_SECRET"
|
name = "APP_SECRET"
|
||||||
value_from {
|
value_from {
|
||||||
secret_key_ref {
|
secret_key_ref {
|
||||||
name = "tokens"
|
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 {
|
port {
|
||||||
container_port = 3000
|
container_port = 3000
|
||||||
protocol = "TCP"
|
protocol = "TCP"
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ resource "kubernetes_deployment" "twentycrm_worker" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
env {
|
env {
|
||||||
name = "ACCESS_TOKEN_SECRET"
|
name = "APP_SECRET"
|
||||||
value_from {
|
value_from {
|
||||||
secret_key_ref {
|
secret_key_ref {
|
||||||
name = "tokens"
|
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 {
|
resources {
|
||||||
requests = {
|
requests = {
|
||||||
cpu = "250m"
|
cpu = "250m"
|
||||||
|
|||||||
5
packages/twenty-front/.gitignore
vendored
5
packages/twenty-front/.gitignore
vendored
@@ -41,4 +41,7 @@ dist-ssr
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
.vite/
|
.vite/
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
|
|
||||||
|
# eslint-plugin-project-structure
|
||||||
|
projectStructure.cache.json
|
||||||
|
|||||||
@@ -1,57 +1,45 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../node_modules/eslint-plugin-project-structure/folderStructure.schema.json",
|
"$schema": "../../node_modules/eslint-plugin-project-structure/folderStructure.schema.json",
|
||||||
|
"projectRoot": "packages/twenty-front",
|
||||||
|
"structureRoot": "src",
|
||||||
"regexParameters": {
|
"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": [
|
"structure": [
|
||||||
{
|
{ "name": "*" },
|
||||||
"name": "packages",
|
{ "name": "*", "children": [] },
|
||||||
"children": [
|
{ "name": "modules", "ruleId": "modulesFolderRule" }
|
||||||
{
|
|
||||||
"name": "twenty-front",
|
|
||||||
"children": [
|
|
||||||
{ "name": "*", "children": [] },
|
|
||||||
{ "name": "*" },
|
|
||||||
{
|
|
||||||
"name": "src",
|
|
||||||
"children": [
|
|
||||||
{ "name": "*", "children": [] },
|
|
||||||
{ "name": "*" },
|
|
||||||
{
|
|
||||||
"name": "modules",
|
|
||||||
"children": [
|
|
||||||
{ "ruleId": "moduleFolderRule" },
|
|
||||||
{ "name": "types", "ruleId": "doNotCheckLeafFolderRule" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"modulesFolderRule": {
|
||||||
|
"children": [
|
||||||
|
{ "ruleId": "moduleFolderRule" },
|
||||||
|
{ "name": "types", "children": [] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
"moduleFolderRule": {
|
"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,
|
"folderRecursionLimit": 6,
|
||||||
"children": [
|
"children": [
|
||||||
{ "ruleId": "moduleFolderRule" },
|
{ "ruleId": "moduleFolderRule" },
|
||||||
{ "name": "hooks", "ruleId": "hooksLeafFolderRule" },
|
{ "name": "hooks", "ruleId": "hooksLeafFolderRule" },
|
||||||
{ "name": "utils", "ruleId": "utilsLeafFolderRule" },
|
{ "name": "utils", "ruleId": "utilsLeafFolderRule" },
|
||||||
{ "name": "states", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "states", "children": [] },
|
||||||
{ "name": "types", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "types", "children": [] },
|
||||||
{ "name": "graphql", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "graphql", "children": [] },
|
||||||
{ "name": "components", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "components", "children": [] },
|
||||||
{ "name": "effect-components", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "effect-components", "children": [] },
|
||||||
{ "name": "constants", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "constants", "children": [] },
|
||||||
{ "name": "validation-schemas", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "validation-schemas", "children": [] },
|
||||||
{ "name": "contexts", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "contexts", "children": [] },
|
||||||
{ "name": "scopes", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "scopes", "children": [] },
|
||||||
{ "name": "services", "ruleId": "doNotCheckLeafFolderRule" },
|
{ "name": "services", "children": [] },
|
||||||
{ "name": "errors", "ruleId": "doNotCheckLeafFolderRule" }
|
{ "name": "errors", "children": [] }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"hooksLeafFolderRule": {
|
"hooksLeafFolderRule": {
|
||||||
"folderRecursionLimit": 2,
|
"folderRecursionLimit": 2,
|
||||||
"children": [
|
"children": [
|
||||||
@@ -63,12 +51,8 @@
|
|||||||
{ "name": "internal", "ruleId": "hooksLeafFolderRule" }
|
{ "name": "internal", "ruleId": "hooksLeafFolderRule" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doNotCheckLeafFolderRule": {
|
|
||||||
"folderRecursionLimit": 1,
|
|
||||||
"children": [{ "name": "*" }, { "name": "*", "children": [] }]
|
|
||||||
},
|
|
||||||
"utilsLeafFolderRule": {
|
"utilsLeafFolderRule": {
|
||||||
"folderRecursionLimit": 1,
|
|
||||||
"children": [
|
"children": [
|
||||||
{ "name": "{camelCase}.ts" },
|
{ "name": "{camelCase}.ts" },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const modulesCoverage = {
|
|||||||
branches: 25,
|
branches: 25,
|
||||||
statements: 49,
|
statements: 49,
|
||||||
lines: 50,
|
lines: 50,
|
||||||
functions: 40,
|
functions: 38,
|
||||||
include: ['src/modules/**/*'],
|
include: ['src/modules/**/*'],
|
||||||
exclude: ['src/**/*.ts'],
|
exclude: ['src/**/*.ts'],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -162,6 +162,11 @@ export type ClientConfig = {
|
|||||||
support: Support;
|
support: Support;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ComputeStepOutputSchemaInput = {
|
||||||
|
/** Step JSON format */
|
||||||
|
step: Scalars['JSON']['input'];
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateAppTokenInput = {
|
export type CreateAppTokenInput = {
|
||||||
expiresAt: Scalars['DateTime']['input'];
|
expiresAt: Scalars['DateTime']['input'];
|
||||||
};
|
};
|
||||||
@@ -529,6 +534,7 @@ export type Mutation = {
|
|||||||
authorizeApp: AuthorizeApp;
|
authorizeApp: AuthorizeApp;
|
||||||
challenge: LoginToken;
|
challenge: LoginToken;
|
||||||
checkoutSession: SessionEntity;
|
checkoutSession: SessionEntity;
|
||||||
|
computeStepOutputSchema: Scalars['JSON']['output'];
|
||||||
createOIDCIdentityProvider: SetupSsoOutput;
|
createOIDCIdentityProvider: SetupSsoOutput;
|
||||||
createOneAppToken: AppToken;
|
createOneAppToken: AppToken;
|
||||||
createOneField: Field;
|
createOneField: Field;
|
||||||
@@ -625,6 +631,11 @@ export type MutationCheckoutSessionArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationComputeStepOutputSchemaArgs = {
|
||||||
|
input: ComputeStepOutputSchemaInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationCreateOidcIdentityProviderArgs = {
|
export type MutationCreateOidcIdentityProviderArgs = {
|
||||||
input: SetupOidcSsoInput;
|
input: SetupOidcSsoInput;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
export type Maybe<T> = T | null;
|
export type Maybe<T> = T | null;
|
||||||
export type InputMaybe<T> = Maybe<T>;
|
export type InputMaybe<T> = Maybe<T>;
|
||||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||||
@@ -155,6 +155,11 @@ export type ClientConfig = {
|
|||||||
support: Support;
|
support: Support;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ComputeStepOutputSchemaInput = {
|
||||||
|
/** Step JSON format */
|
||||||
|
step: Scalars['JSON'];
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateServerlessFunctionInput = {
|
export type CreateServerlessFunctionInput = {
|
||||||
description?: InputMaybe<Scalars['String']>;
|
description?: InputMaybe<Scalars['String']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
@@ -424,6 +429,7 @@ export type Mutation = {
|
|||||||
authorizeApp: AuthorizeApp;
|
authorizeApp: AuthorizeApp;
|
||||||
challenge: LoginToken;
|
challenge: LoginToken;
|
||||||
checkoutSession: SessionEntity;
|
checkoutSession: SessionEntity;
|
||||||
|
computeStepOutputSchema: Scalars['JSON'];
|
||||||
createOIDCIdentityProvider: SetupSsoOutput;
|
createOIDCIdentityProvider: SetupSsoOutput;
|
||||||
createOneAppToken: AppToken;
|
createOneAppToken: AppToken;
|
||||||
createOneObject: Object;
|
createOneObject: Object;
|
||||||
@@ -509,6 +515,11 @@ export type MutationCheckoutSessionArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationComputeStepOutputSchemaArgs = {
|
||||||
|
input: ComputeStepOutputSchemaInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationCreateOidcIdentityProviderArgs = {
|
export type MutationCreateOidcIdentityProviderArgs = {
|
||||||
input: SetupOidcSsoInput;
|
input: SetupOidcSsoInput;
|
||||||
};
|
};
|
||||||
@@ -1272,6 +1283,7 @@ export type Workspace = {
|
|||||||
displayName?: Maybe<Scalars['String']>;
|
displayName?: Maybe<Scalars['String']>;
|
||||||
domainName?: Maybe<Scalars['String']>;
|
domainName?: Maybe<Scalars['String']>;
|
||||||
featureFlags?: Maybe<Array<FeatureFlag>>;
|
featureFlags?: Maybe<Array<FeatureFlag>>;
|
||||||
|
hasValidEntrepriseKey: Scalars['Boolean'];
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
inviteHash?: Maybe<Scalars['String']>;
|
inviteHash?: Maybe<Scalars['String']>;
|
||||||
isPublicInviteLinkEnabled: Scalars['Boolean'];
|
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<{
|
export type RenewTokenMutationVariables = Exact<{
|
||||||
appToken: Scalars['String'];
|
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<{
|
export type CheckUserExistsQueryVariables = Exact<{
|
||||||
email: Scalars['String'];
|
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 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; }>;
|
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 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<{
|
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
workflowVersionId: Scalars['String'];
|
||||||
@@ -1823,6 +1835,13 @@ export type ActivateWorkflowVersionMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type ActivateWorkflowVersionMutation = { __typename?: 'Mutation', activateWorkflowVersion: boolean };
|
export type ActivateWorkflowVersionMutation = { __typename?: 'Mutation', activateWorkflowVersion: boolean };
|
||||||
|
|
||||||
|
export type ComputeStepOutputSchemaMutationVariables = Exact<{
|
||||||
|
input: ComputeStepOutputSchemaInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ComputeStepOutputSchemaMutation = { __typename?: 'Mutation', computeStepOutputSchema: any };
|
||||||
|
|
||||||
export type DeactivateWorkflowVersionMutationVariables = Exact<{
|
export type DeactivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
workflowVersionId: Scalars['String'];
|
||||||
}>;
|
}>;
|
||||||
@@ -2044,6 +2063,7 @@ export const UserQueryFragmentFragmentDoc = gql`
|
|||||||
allowImpersonation
|
allowImpersonation
|
||||||
activationStatus
|
activationStatus
|
||||||
isPublicInviteLinkEnabled
|
isPublicInviteLinkEnabled
|
||||||
|
hasValidEntrepriseKey
|
||||||
featureFlags {
|
featureFlags {
|
||||||
id
|
id
|
||||||
key
|
key
|
||||||
@@ -3443,6 +3463,37 @@ export function useActivateWorkflowVersionMutation(baseOptions?: Apollo.Mutation
|
|||||||
export type ActivateWorkflowVersionMutationHookResult = ReturnType<typeof useActivateWorkflowVersionMutation>;
|
export type ActivateWorkflowVersionMutationHookResult = ReturnType<typeof useActivateWorkflowVersionMutation>;
|
||||||
export type ActivateWorkflowVersionMutationResult = Apollo.MutationResult<ActivateWorkflowVersionMutation>;
|
export type ActivateWorkflowVersionMutationResult = Apollo.MutationResult<ActivateWorkflowVersionMutation>;
|
||||||
export type ActivateWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>;
|
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`
|
export const DeactivateWorkflowVersionDocument = gql`
|
||||||
mutation DeactivateWorkflowVersion($workflowVersionId: String!) {
|
mutation DeactivateWorkflowVersion($workflowVersionId: String!) {
|
||||||
deactivateWorkflowVersion(workflowVersionId: $workflowVersionId)
|
deactivateWorkflowVersion(workflowVersionId: $workflowVersionId)
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge */
|
/* https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge */
|
||||||
.grecaptcha-badge {
|
.grecaptcha-badge {
|
||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||||
import { ActionMenuType } from '@/action-menu/types/ActionMenuType';
|
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
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 { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
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';
|
import { IconTrash, isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
export const DeleteRecordsActionEffect = ({
|
export const DeleteRecordsActionEffect = ({
|
||||||
position,
|
position,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
actionMenuType,
|
|
||||||
}: {
|
}: {
|
||||||
position: number;
|
position: number;
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
actionMenuType: ActionMenuType;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||||
|
|
||||||
@@ -93,6 +91,9 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT &&
|
contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT &&
|
||||||
contextStoreNumberOfSelectedRecords > 0;
|
contextStoreNumberOfSelectedRecords > 0;
|
||||||
|
|
||||||
|
const { isInRightDrawer, onActionExecutedCallback } =
|
||||||
|
useContext(ActionMenuContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
addActionMenuEntry({
|
addActionMenuEntry({
|
||||||
@@ -101,6 +102,7 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
position,
|
position,
|
||||||
Icon: IconTrash,
|
Icon: IconTrash,
|
||||||
accent: 'danger',
|
accent: 'danger',
|
||||||
|
isPinned: true,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setIsDeleteRecordsModalOpen(true);
|
setIsDeleteRecordsModalOpen(true);
|
||||||
},
|
},
|
||||||
@@ -120,17 +122,14 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
} can be recovered from the Options menu.`}
|
} can be recovered from the Options menu.`}
|
||||||
onConfirmClick={() => {
|
onConfirmClick={() => {
|
||||||
handleDeleteClick();
|
handleDeleteClick();
|
||||||
|
onActionExecutedCallback?.();
|
||||||
if (actionMenuType === 'recordShow') {
|
if (isInRightDrawer) {
|
||||||
closeRightDrawer();
|
closeRightDrawer();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
deleteButtonText={`Delete ${
|
deleteButtonText={`Delete ${
|
||||||
contextStoreNumberOfSelectedRecords > 1 ? 'Records' : 'Record'
|
contextStoreNumberOfSelectedRecords > 1 ? 'Records' : 'Record'
|
||||||
}`}
|
}`}
|
||||||
modalVariant={
|
|
||||||
actionMenuType === 'recordShow' ? 'tertiary' : 'primary'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -142,13 +141,14 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
removeActionMenuEntry('delete');
|
removeActionMenuEntry('delete');
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
actionMenuType,
|
|
||||||
addActionMenuEntry,
|
addActionMenuEntry,
|
||||||
canDelete,
|
canDelete,
|
||||||
closeRightDrawer,
|
closeRightDrawer,
|
||||||
contextStoreNumberOfSelectedRecords,
|
contextStoreNumberOfSelectedRecords,
|
||||||
handleDeleteClick,
|
handleDeleteClick,
|
||||||
isDeleteRecordsModalOpen,
|
isDeleteRecordsModalOpen,
|
||||||
|
isInRightDrawer,
|
||||||
|
onActionExecutedCallback,
|
||||||
position,
|
position,
|
||||||
removeActionMenuEntry,
|
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 { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
|
||||||
import { SingleRecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter';
|
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
|
||||||
import { ActionMenuType } from '@/action-menu/types/ActionMenuType';
|
import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect';
|
||||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordActionMenuEntriesSetter = ({
|
const singleRecordActionEffects = [
|
||||||
actionMenuType,
|
ManageFavoritesActionEffect,
|
||||||
}: {
|
ExportRecordsActionEffect,
|
||||||
actionMenuType: ActionMenuType;
|
DeleteRecordsActionEffect,
|
||||||
}) => {
|
];
|
||||||
|
|
||||||
|
const multipleRecordActionEffects = [
|
||||||
|
ExportRecordsActionEffect,
|
||||||
|
DeleteRecordsActionEffect,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const RecordActionMenuEntriesSetter = () => {
|
||||||
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
|
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
|
||||||
contextStoreNumberOfSelectedRecordsComponentState,
|
contextStoreNumberOfSelectedRecordsComponentState,
|
||||||
);
|
);
|
||||||
@@ -33,19 +40,20 @@ export const RecordActionMenuEntriesSetter = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contextStoreNumberOfSelectedRecords === 1) {
|
const actions =
|
||||||
return (
|
contextStoreNumberOfSelectedRecords === 1
|
||||||
<SingleRecordActionMenuEntriesSetter
|
? singleRecordActionEffects
|
||||||
objectMetadataItem={objectMetadataItem}
|
: multipleRecordActionEffects;
|
||||||
actionMenuType={actionMenuType}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MultipleRecordsActionMenuEntriesSetter
|
<>
|
||||||
objectMetadataItem={objectMetadataItem}
|
{actions.map((ActionEffect, index) => (
|
||||||
actionMenuType={actionMenuType}
|
<ActionEffect
|
||||||
/>
|
key={index}
|
||||||
|
position={index}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
|
||||||
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
|
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
|
||||||
import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect';
|
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 { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordIndexActionMenu = ({
|
export const RecordIndexActionMenu = () => {
|
||||||
actionMenuId,
|
|
||||||
}: {
|
|
||||||
actionMenuId: string;
|
|
||||||
}) => {
|
|
||||||
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||||
contextStoreCurrentObjectMetadataIdComponentState,
|
contextStoreCurrentObjectMetadataIdComponentState,
|
||||||
);
|
);
|
||||||
@@ -20,15 +16,18 @@ export const RecordIndexActionMenu = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{contextStoreCurrentObjectMetadataId && (
|
{contextStoreCurrentObjectMetadataId && (
|
||||||
<ActionMenuComponentInstanceContext.Provider
|
<ActionMenuContext.Provider
|
||||||
value={{ instanceId: actionMenuId }}
|
value={{
|
||||||
|
isInRightDrawer: false,
|
||||||
|
onActionExecutedCallback: () => {},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<RecordIndexActionMenuBar />
|
<RecordIndexActionMenuBar />
|
||||||
<RecordIndexActionMenuDropdown />
|
<RecordIndexActionMenuDropdown />
|
||||||
<ActionMenuConfirmationModals />
|
<ActionMenuConfirmationModals />
|
||||||
<RecordIndexActionMenuEffect />
|
<RecordIndexActionMenuEffect />
|
||||||
<RecordActionMenuEntriesSetter actionMenuType="recordIndex" />
|
<RecordActionMenuEntriesSetter />
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
</ActionMenuContext.Provider>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { RecordIndexActionMenuBarAllActionsButton } from '@/action-menu/components/RecordIndexActionMenuBarAllActionsButton';
|
||||||
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
|
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
|
||||||
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
@@ -30,7 +31,9 @@ export const RecordIndexActionMenuBar = () => {
|
|||||||
actionMenuEntriesComponentSelector,
|
actionMenuEntriesComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (actionMenuEntries.length === 0) {
|
const pinnedEntries = actionMenuEntries.filter((entry) => entry.isPinned);
|
||||||
|
|
||||||
|
if (pinnedEntries.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +45,10 @@ export const RecordIndexActionMenuBar = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
|
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
|
||||||
{actionMenuEntries.map((entry, index) => (
|
{pinnedEntries.map((entry, index) => (
|
||||||
<RecordIndexActionMenuBarEntry key={index} entry={entry} />
|
<RecordIndexActionMenuBarEntry key={index} entry={entry} />
|
||||||
))}
|
))}
|
||||||
|
<RecordIndexActionMenuBarAllActionsButton />
|
||||||
</BottomBar>
|
</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 styled from '@emotion/styled';
|
||||||
|
|
||||||
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
|
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
|
||||||
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
|
|
||||||
|
|
||||||
type RecordIndexActionMenuBarEntryProps = {
|
type RecordIndexActionMenuBarEntryProps = {
|
||||||
entry: ActionMenuEntry;
|
entry: ActionMenuEntry;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledButton = styled.div<{ accent: MenuItemAccent }>`
|
const StyledButton = styled.div`
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
color: ${(props) =>
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
props.accent === 'danger'
|
|
||||||
? props.theme.color.red
|
|
||||||
: props.theme.font.color.secondary};
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
transition: background 0.1s ease;
|
transition: background ${({ theme }) => theme.animation.duration.fast} ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${({ theme, accent }) =>
|
background: ${({ theme }) => theme.background.tertiary};
|
||||||
accent === 'danger'
|
|
||||||
? theme.background.danger
|
|
||||||
: theme.background.tertiary};
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -40,10 +33,7 @@ export const RecordIndexActionMenuBarEntry = ({
|
|||||||
}: RecordIndexActionMenuBarEntryProps) => {
|
}: RecordIndexActionMenuBarEntryProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<StyledButton
|
<StyledButton onClick={() => entry.onClick?.()}>
|
||||||
accent={entry.accent ?? 'default'}
|
|
||||||
onClick={() => entry.onClick?.()}
|
|
||||||
>
|
|
||||||
{entry.Icon && <entry.Icon size={theme.icon.size.md} />}
|
{entry.Icon && <entry.Icon size={theme.icon.size.md} />}
|
||||||
<StyledButtonLabel>{entry.label}</StyledButtonLabel>
|
<StyledButtonLabel>{entry.label}</StyledButtonLabel>
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
|
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
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 { 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 { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
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 { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
@@ -19,12 +23,22 @@ export const RecordIndexActionMenuEffect = () => {
|
|||||||
|
|
||||||
const { openActionBar, closeActionBar } = useActionMenu(actionMenuId);
|
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(
|
const isDropdownOpen = useRecoilValue(
|
||||||
extractComponentState(
|
extractComponentState(
|
||||||
isDropdownOpenComponentState,
|
isDropdownOpenComponentState,
|
||||||
`action-menu-dropdown-${actionMenuId}`,
|
`action-menu-dropdown-${actionMenuId}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const { isRightDrawerOpen } = useRightDrawer();
|
||||||
|
|
||||||
|
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contextStoreNumberOfSelectedRecords > 0 && !isDropdownOpen) {
|
if (contextStoreNumberOfSelectedRecords > 0 && !isDropdownOpen) {
|
||||||
@@ -43,5 +57,11 @@ export const RecordIndexActionMenuEffect = () => {
|
|||||||
isDropdownOpen,
|
isDropdownOpen,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isRightDrawerOpen || isCommandMenuOpened) {
|
||||||
|
setIsBottomBarOpened(false);
|
||||||
|
}
|
||||||
|
}, [isRightDrawerOpen, isCommandMenuOpened, setIsBottomBarOpened]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,30 +1,53 @@
|
|||||||
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
|
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
|
||||||
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
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 { 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { RecordShowPageBaseHeader } from '~/pages/object-record/RecordShowPageBaseHeader';
|
||||||
|
|
||||||
export const RecordShowActionMenu = ({
|
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(
|
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||||
contextStoreCurrentObjectMetadataIdComponentState,
|
contextStoreCurrentObjectMetadataIdComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: refactor RecordShowPageBaseHeader to use the context store
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{contextStoreCurrentObjectMetadataId && (
|
{contextStoreCurrentObjectMetadataId && (
|
||||||
<ActionMenuComponentInstanceContext.Provider
|
<ActionMenuContext.Provider
|
||||||
value={{ instanceId: actionMenuId }}
|
value={{
|
||||||
|
isInRightDrawer: false,
|
||||||
|
onActionExecutedCallback: () => {},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<RecordShowActionMenuBar />
|
<RecordShowPageBaseHeader
|
||||||
|
{...{
|
||||||
|
isFavorite,
|
||||||
|
handleFavoriteButtonClick,
|
||||||
|
record,
|
||||||
|
objectMetadataItem,
|
||||||
|
objectNameSingular,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<ActionMenuConfirmationModals />
|
<ActionMenuConfirmationModals />
|
||||||
<RecordActionMenuEntriesSetter actionMenuType="recordShow" />
|
<RecordActionMenuEntriesSetter />
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
</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 { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordShowActionMenuBar = () => {
|
export const RecordShowRightDrawerActionMenuBar = () => {
|
||||||
const actionMenuEntries = useRecoilComponentValueV2(
|
const actionMenuEntries = useRecoilComponentValueV2(
|
||||||
actionMenuEntriesComponentSelector,
|
actionMenuEntriesComponentSelector,
|
||||||
);
|
);
|
||||||
@@ -10,15 +10,15 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto
|
|||||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||||
import { userEvent, waitFor, within } from '@storybook/test';
|
import { userEvent, waitFor, within } from '@storybook/test';
|
||||||
import { IconCheckbox, IconTrash } from 'twenty-ui';
|
import { IconTrash, RouterDecorator } from 'twenty-ui';
|
||||||
|
|
||||||
const deleteMock = jest.fn();
|
const deleteMock = jest.fn();
|
||||||
const markAsDoneMock = jest.fn();
|
|
||||||
|
|
||||||
const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
||||||
title: 'Modules/ActionMenu/RecordIndexActionMenuBar',
|
title: 'Modules/ActionMenu/RecordIndexActionMenuBar',
|
||||||
component: RecordIndexActionMenuBar,
|
component: RecordIndexActionMenuBar,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
RouterDecorator,
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<ContextStoreComponentInstanceContext.Provider
|
<ContextStoreComponentInstanceContext.Provider
|
||||||
value={{ instanceId: 'story-action-menu' }}
|
value={{ instanceId: 'story-action-menu' }}
|
||||||
@@ -48,6 +48,7 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
|||||||
[
|
[
|
||||||
'delete',
|
'delete',
|
||||||
{
|
{
|
||||||
|
isPinned: true,
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
position: 0,
|
position: 0,
|
||||||
@@ -55,16 +56,6 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
|||||||
onClick: deleteMock,
|
onClick: deleteMock,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
|
||||||
'markAsDone',
|
|
||||||
{
|
|
||||||
key: 'markAsDone',
|
|
||||||
label: 'Mark as done',
|
|
||||||
position: 1,
|
|
||||||
Icon: IconCheckbox,
|
|
||||||
onClick: markAsDoneMock,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
set(
|
set(
|
||||||
@@ -120,12 +111,8 @@ export const WithButtonClicks: Story = {
|
|||||||
const deleteButton = await canvas.findByText('Delete');
|
const deleteButton = await canvas.findByText('Delete');
|
||||||
await userEvent.click(deleteButton);
|
await userEvent.click(deleteButton);
|
||||||
|
|
||||||
const markAsDoneButton = await canvas.findByText('Mark as done');
|
|
||||||
await userEvent.click(markAsDoneButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(deleteMock).toHaveBeenCalled();
|
expect(deleteMock).toHaveBeenCalled();
|
||||||
expect(markAsDoneMock).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { expect, jest } from '@storybook/jest';
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { RecoilRoot } from 'recoil';
|
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 { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||||
@@ -20,9 +20,9 @@ const deleteMock = jest.fn();
|
|||||||
const addToFavoritesMock = jest.fn();
|
const addToFavoritesMock = jest.fn();
|
||||||
const exportMock = jest.fn();
|
const exportMock = jest.fn();
|
||||||
|
|
||||||
const meta: Meta<typeof RecordShowActionMenuBar> = {
|
const meta: Meta<typeof RecordShowRightDrawerActionMenuBar> = {
|
||||||
title: 'Modules/ActionMenu/RecordShowActionMenuBar',
|
title: 'Modules/ActionMenu/RecordShowRightDrawerActionMenuBar',
|
||||||
component: RecordShowActionMenuBar,
|
component: RecordShowRightDrawerActionMenuBar,
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<RecoilRoot
|
<RecoilRoot
|
||||||
@@ -98,7 +98,7 @@ const meta: Meta<typeof RecordShowActionMenuBar> = {
|
|||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof RecordShowActionMenuBar>;
|
type Story = StoryObj<typeof RecordShowRightDrawerActionMenuBar>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
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;
|
label: string;
|
||||||
position: number;
|
position: number;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
|
isPinned?: boolean;
|
||||||
accent?: MenuItemAccent;
|
accent?: MenuItemAccent;
|
||||||
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
||||||
ConfirmationModal?: ReactNode;
|
ConfirmationModal?: ReactNode;
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export type ActionMenuType = 'recordIndex' | 'recordShow';
|
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
H3Title,
|
H3Title,
|
||||||
|
Section,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
|
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
|
||||||
@@ -21,7 +22,6 @@ import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
|||||||
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
|
||||||
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
|
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
H1Title,
|
H1Title,
|
||||||
H1TitleFontColor,
|
H1TitleFontColor,
|
||||||
|
Section,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { ActivityList } from '@/activities/components/ActivityList';
|
import { ActivityList } from '@/activities/components/ActivityList';
|
||||||
@@ -20,7 +21,6 @@ import { getTimelineThreadsFromPersonId } from '@/activities/emails/graphql/quer
|
|||||||
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
|
||||||
import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql';
|
import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export type Attachment = {
|
|||||||
type: AttachmentType;
|
type: AttachmentType;
|
||||||
companyId: string;
|
companyId: string;
|
||||||
personId: string;
|
personId: string;
|
||||||
activityId: string;
|
|
||||||
authorId: string;
|
authorId: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
__typename: string;
|
__typename: string;
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ export const findActivityTargetsOperationSignatureFactory: RecordGqlOperationSig
|
|||||||
__typename: true,
|
__typename: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
activity: true,
|
|
||||||
activityId: true,
|
|
||||||
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
|
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ const mockActivityTarget = {
|
|||||||
updatedAt: '2021-08-03T19:20:06.000Z',
|
updatedAt: '2021-08-03T19:20:06.000Z',
|
||||||
createdAt: '2021-08-03T19:20:06.000Z',
|
createdAt: '2021-08-03T19:20:06.000Z',
|
||||||
personId: '1',
|
personId: '1',
|
||||||
activityId: '234',
|
|
||||||
companyId: '1',
|
companyId: '1',
|
||||||
id: '123',
|
id: '123',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ const mocks: MockedResponse[] = [
|
|||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
activityId
|
|
||||||
authorId
|
authorId
|
||||||
companyId
|
companyId
|
||||||
createdAt
|
createdAt
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
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 { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||||
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
|
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
|
||||||
import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox';
|
|
||||||
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
|
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
|
||||||
|
|
||||||
import { ActivityRow } from '@/activities/components/ActivityRow';
|
import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const mocks: MockedResponse[] = [
|
|||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
activityId
|
|
||||||
authorId
|
authorId
|
||||||
companyId
|
companyId
|
||||||
createdAt
|
createdAt
|
||||||
@@ -95,6 +94,8 @@ const mocks: MockedResponse[] = [
|
|||||||
updatedAt
|
updatedAt
|
||||||
viewId
|
viewId
|
||||||
workflowId
|
workflowId
|
||||||
|
workflowRunId
|
||||||
|
workflowVersionId
|
||||||
workspaceMemberId
|
workspaceMemberId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,6 +139,9 @@ const mocks: MockedResponse[] = [
|
|||||||
rocketId
|
rocketId
|
||||||
taskId
|
taskId
|
||||||
updatedAt
|
updatedAt
|
||||||
|
workflowId
|
||||||
|
workflowRunId
|
||||||
|
workflowVersionId
|
||||||
workspaceMemberId
|
workspaceMemberId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export const PageChangeEffect = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(AppPath.CreateWorkspace): {
|
case isMatchingLocation(AppPath.CreateWorkspace): {
|
||||||
setHotkeyScope(PageHotkeyScope.CreateWokspace);
|
setHotkeyScope(PageHotkeyScope.CreateWorkspace);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case isMatchingLocation(AppPath.SyncEmails): {
|
case isMatchingLocation(AppPath.SyncEmails): {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
IconMicrosoft,
|
IconMicrosoft,
|
||||||
Loader,
|
Loader,
|
||||||
MainButton,
|
MainButton,
|
||||||
|
StyledText,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
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 &&
|
{authProviders.password &&
|
||||||
(signInUpStep === SignInUpStep.Password ||
|
(signInUpStep === SignInUpStep.Password ||
|
||||||
@@ -267,6 +270,12 @@ export const SignInUpForm = () => {
|
|||||||
disableHotkeys
|
disableHotkeys
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
|
{signInUpMode === SignInUpMode.SignUp && (
|
||||||
|
<StyledText
|
||||||
|
text={'At least 8 characters long.'}
|
||||||
|
color={theme.font.color.secondary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</StyledInputContainer>
|
</StyledInputContainer>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type CurrentWorkspace = Pick<
|
|||||||
| 'currentBillingSubscription'
|
| 'currentBillingSubscription'
|
||||||
| 'workspaceMembersCount'
|
| 'workspaceMembersCount'
|
||||||
| 'isPublicInviteLinkEnabled'
|
| 'isPublicInviteLinkEnabled'
|
||||||
|
| 'hasValidEntrepriseKey'
|
||||||
| 'metadataVersion'
|
| 'metadataVersion'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS
|
|||||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||||
import { Command, CommandType } from '@/command-menu/types/Command';
|
import { Command, CommandType } from '@/command-menu/types/Command';
|
||||||
import { Company } from '@/companies/types/Company';
|
import { Company } from '@/companies/types/Company';
|
||||||
|
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
|
||||||
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
|
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
|
||||||
@@ -287,6 +288,14 @@ export const CommandMenu = () => {
|
|||||||
: true) && cmd.type === CommandType.Create,
|
: 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({
|
useListenClickOutside({
|
||||||
refs: [commandMenuRef],
|
refs: [commandMenuRef],
|
||||||
callback: closeCommandMenu,
|
callback: closeCommandMenu,
|
||||||
@@ -312,6 +321,7 @@ export const CommandMenu = () => {
|
|||||||
|
|
||||||
const selectableItemIds = copilotCommands
|
const selectableItemIds = copilotCommands
|
||||||
.map((cmd) => cmd.id)
|
.map((cmd) => cmd.id)
|
||||||
|
.concat(matchingActionCommands.map((cmd) => cmd.id))
|
||||||
.concat(matchingCreateCommand.map((cmd) => cmd.id))
|
.concat(matchingCreateCommand.map((cmd) => cmd.id))
|
||||||
.concat(matchingNavigateCommand.map((cmd) => cmd.id))
|
.concat(matchingNavigateCommand.map((cmd) => cmd.id))
|
||||||
.concat(people?.map((person) => person.id))
|
.concat(people?.map((person) => person.id))
|
||||||
@@ -320,22 +330,28 @@ export const CommandMenu = () => {
|
|||||||
.concat(notes?.map((note) => note.id));
|
.concat(notes?.map((note) => note.id));
|
||||||
|
|
||||||
const isNoResults =
|
const isNoResults =
|
||||||
|
!matchingActionCommands.length &&
|
||||||
!matchingCreateCommand.length &&
|
!matchingCreateCommand.length &&
|
||||||
!matchingNavigateCommand.length &&
|
!matchingNavigateCommand.length &&
|
||||||
!people?.length &&
|
!people?.length &&
|
||||||
!companies?.length &&
|
!companies?.length &&
|
||||||
!notes?.length &&
|
!notes?.length &&
|
||||||
!opportunities?.length;
|
!opportunities?.length;
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isPeopleLoading ||
|
isPeopleLoading ||
|
||||||
isNotesLoading ||
|
isNotesLoading ||
|
||||||
isOpportunitiesLoading ||
|
isOpportunitiesLoading ||
|
||||||
isCompaniesLoading;
|
isCompaniesLoading;
|
||||||
|
|
||||||
|
const mainContextStoreComponentInstanceId = useRecoilValue(
|
||||||
|
mainContextStoreComponentInstanceIdState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCommandMenuOpened && (
|
{isCommandMenuOpened && (
|
||||||
<StyledCommandMenu ref={commandMenuRef}>
|
<StyledCommandMenu ref={commandMenuRef} className="command-menu">
|
||||||
<StyledInputContainer>
|
<StyledInputContainer>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -393,6 +409,23 @@ export const CommandMenu = () => {
|
|||||||
</SelectableItem>
|
</SelectableItem>
|
||||||
</CommandGroup>
|
</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">
|
<CommandGroup heading="Create">
|
||||||
{matchingCreateCommand.map((cmd) => (
|
{matchingCreateCommand.map((cmd) => (
|
||||||
<SelectableItem itemId={cmd.id} key={cmd.id}>
|
<SelectableItem itemId={cmd.id} key={cmd.id}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
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 { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||||
import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands';
|
import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands';
|
||||||
|
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { ALL_ICONS } from '@ui/display/icon/providers/internal/AllIcons';
|
import { ALL_ICONS } from '@ui/display/icon/providers/internal/AllIcons';
|
||||||
import { sortByProperty } from '~/utils/array/sortByProperty';
|
import { sortByProperty } from '~/utils/array/sortByProperty';
|
||||||
@@ -27,10 +29,43 @@ export const useCommandMenu = () => {
|
|||||||
goBackToPreviousHotkeyScope,
|
goBackToPreviousHotkeyScope,
|
||||||
} = usePreviousHotkeyScope();
|
} = usePreviousHotkeyScope();
|
||||||
|
|
||||||
const openCommandMenu = useCallback(() => {
|
const mainContextStoreComponentInstanceId = useRecoilValue(
|
||||||
setIsCommandMenuOpened(true);
|
mainContextStoreComponentInstanceIdState,
|
||||||
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
|
);
|
||||||
}, [setHotkeyScopeAndMemorizePreviousScope, setIsCommandMenuOpened]);
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
mainContextStoreComponentInstanceId,
|
||||||
|
setCommands,
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
|
setIsCommandMenuOpened,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const closeCommandMenu = useRecoilCallback(
|
const closeCommandMenu = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import { IconComponent } from 'twenty-ui';
|
|||||||
export enum CommandType {
|
export enum CommandType {
|
||||||
Navigate = 'Navigate',
|
Navigate = 'Navigate',
|
||||||
Create = 'Create',
|
Create = 'Create',
|
||||||
|
Action = 'Action',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Command = {
|
export type Command = {
|
||||||
id: string;
|
id: string;
|
||||||
to: string;
|
to?: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: CommandType.Navigate | CommandType.Create;
|
type: CommandType.Navigate | CommandType.Create | CommandType.Action;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
firstHotKey?: string;
|
firstHotKey?: string;
|
||||||
secondHotKey?: string;
|
secondHotKey?: string;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { mainContextStoreComponentInstanceIdState } from '@/context-store/states
|
|||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
export const SetMainContextStoreComponentInstanceIdEffect = () => {
|
export const MainContextStoreComponentInstanceIdSetterEffect = () => {
|
||||||
const setMainContextStoreComponentInstanceId = useSetRecoilState(
|
const setMainContextStoreComponentInstanceId = useSetRecoilState(
|
||||||
mainContextStoreComponentInstanceIdState,
|
mainContextStoreComponentInstanceIdState,
|
||||||
);
|
);
|
||||||
@@ -84,7 +84,7 @@ export const mocks = [
|
|||||||
query: gql`
|
query: gql`
|
||||||
mutation CreateOneFavorite($input: FavoriteCreateInput!) {
|
mutation CreateOneFavorite($input: FavoriteCreateInput!) {
|
||||||
createFavorite(data: $input) {
|
createFavorite(data: $input) {
|
||||||
__typename
|
__typename
|
||||||
company {
|
company {
|
||||||
__typename
|
__typename
|
||||||
accountOwnerId
|
accountOwnerId
|
||||||
@@ -295,6 +295,41 @@ export const mocks = [
|
|||||||
updatedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
workflowId
|
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 {
|
workspaceMember {
|
||||||
__typename
|
__typename
|
||||||
avatarUrl
|
avatarUrl
|
||||||
@@ -341,8 +376,8 @@ export const mocks = [
|
|||||||
mutation DeleteOneFavorite($idToDelete: ID!) {
|
mutation DeleteOneFavorite($idToDelete: ID!) {
|
||||||
deleteFavorite(id: $idToDelete) {
|
deleteFavorite(id: $idToDelete) {
|
||||||
__typename
|
__typename
|
||||||
id
|
|
||||||
deletedAt
|
deletedAt
|
||||||
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -575,6 +610,41 @@ export const mocks = [
|
|||||||
updatedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
workflowId
|
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 {
|
workspaceMember {
|
||||||
__typename
|
__typename
|
||||||
avatarUrl
|
avatarUrl
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DropResult, ResponderProvided } from '@hello-pangea/dnd';
|
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 { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
@@ -7,6 +7,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
|
|||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
|
||||||
|
import { act } from 'react';
|
||||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -3,13 +3,18 @@ import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableT
|
|||||||
describe('findAvailableTimeZoneOption', () => {
|
describe('findAvailableTimeZoneOption', () => {
|
||||||
it('should find the matching available IANA time zone select option from a given IANA time zone', () => {
|
it('should find the matching available IANA time zone select option from a given IANA time zone', () => {
|
||||||
const ianaTimeZone = 'Europe/Paris';
|
const ianaTimeZone = 'Europe/Paris';
|
||||||
const expectedOption = {
|
const expectedValue = 'Europe/Paris';
|
||||||
label: '(GMT+02:00) Central European Summer Time - Paris',
|
const expectedLabelWinter =
|
||||||
value: 'Europe/Paris',
|
'(GMT+01:00) Central European Standard Time - Paris';
|
||||||
};
|
const expectedLabelSummer =
|
||||||
|
'(GMT+02:00) Central European Summer Time - Paris';
|
||||||
|
|
||||||
const option = findAvailableTimeZoneOption(ianaTimeZone);
|
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', () => {
|
describe('formatTimeZoneLabel', () => {
|
||||||
it('should format the time zone label correctly when location is included in the label', () => {
|
it('should format the time zone label correctly when location is included in the label', () => {
|
||||||
const ianaTimeZone = 'Europe/Paris';
|
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);
|
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', () => {
|
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 { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||||
|
import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings';
|
||||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +24,8 @@ export const MobileNavigationBar = () => {
|
|||||||
const [currentMobileNavigationDrawer, setCurrentMobileNavigationDrawer] =
|
const [currentMobileNavigationDrawer, setCurrentMobileNavigationDrawer] =
|
||||||
useRecoilState(currentMobileNavigationDrawerState);
|
useRecoilState(currentMobileNavigationDrawerState);
|
||||||
|
|
||||||
|
const { openSettingsMenu } = useOpenSettingsMenu();
|
||||||
|
|
||||||
const activeItemName = isNavigationDrawerExpanded
|
const activeItemName = isNavigationDrawerExpanded
|
||||||
? currentMobileNavigationDrawer
|
? currentMobileNavigationDrawer
|
||||||
: isCommandMenuOpened
|
: isCommandMenuOpened
|
||||||
@@ -62,10 +65,7 @@ export const MobileNavigationBar = () => {
|
|||||||
Icon: IconSettings,
|
Icon: IconSettings,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
closeCommandMenu();
|
closeCommandMenu();
|
||||||
setIsNavigationDrawerExpanded(
|
openSettingsMenu();
|
||||||
(previousIsOpen) => activeItemName !== 'settings' || !previousIsOpen,
|
|
||||||
);
|
|
||||||
setCurrentMobileNavigationDrawer('settings');
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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 shouldSubItemsBeDisplayed = isActive && objectMetadataViews.length > 1;
|
||||||
|
|
||||||
const sortedObjectMetadataViews = [...objectMetadataViews].sort(
|
const sortedObjectMetadataViews = [...objectMetadataViews].sort(
|
||||||
(viewA, viewB) =>
|
(viewA, viewB) => viewA.position - viewB.position,
|
||||||
viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedSubItemIndex = sortedObjectMetadataViews.findIndex(
|
const selectedSubItemIndex = sortedObjectMetadataViews.findIndex(
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ export const query = gql`
|
|||||||
export const variables = { idToDelete: 'idToDelete' };
|
export const variables = { idToDelete: 'idToDelete' };
|
||||||
|
|
||||||
export const responseData = {
|
export const responseData = {
|
||||||
id: 'idToDelete'
|
id: 'idToDelete',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { gql } from '@apollo/client';
|
|||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
export const FIELD_METADATA_ID = '2c43466a-fe9e-4005-8d08-c5836067aa6c';
|
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';
|
export const RELATION_METADATA_ID = 'f81d4fae-7dec-11d0-a765-00a0c91e6bf6';
|
||||||
|
|
||||||
const baseFields = `
|
const baseFields = `
|
||||||
@@ -29,12 +30,12 @@ export const queries = {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
deleteMetadataFieldRelation: gql`
|
deleteMetadataFieldRelation: gql`
|
||||||
mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {
|
mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {
|
||||||
deleteOneRelation(input: { id: $idToDelete }) {
|
deleteOneRelation(input: { id: $idToDelete }) {
|
||||||
id
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`,
|
||||||
`,
|
|
||||||
activateMetadataField: gql`
|
activateMetadataField: gql`
|
||||||
mutation UpdateOneFieldMetadataItem(
|
mutation UpdateOneFieldMetadataItem(
|
||||||
$idToUpdate: UUID!
|
$idToUpdate: UUID!
|
||||||
@@ -94,7 +95,7 @@ export const variables = {
|
|||||||
deactivateMetadataField: {
|
deactivateMetadataField: {
|
||||||
idToUpdate: FIELD_METADATA_ID,
|
idToUpdate: FIELD_METADATA_ID,
|
||||||
updatePayload: { isActive: false, label: undefined },
|
updatePayload: { isActive: false, label: undefined },
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultResponseData = {
|
const defaultResponseData = {
|
||||||
@@ -127,4 +128,3 @@ export const responseData = {
|
|||||||
options: [],
|
options: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
|||||||
featureFlags: [],
|
featureFlags: [],
|
||||||
allowImpersonation: false,
|
allowImpersonation: false,
|
||||||
activationStatus: WorkspaceActivationStatus.Active,
|
activationStatus: WorkspaceActivationStatus.Active,
|
||||||
|
hasValidEntrepriseKey: false,
|
||||||
metadataVersion: 1,
|
metadataVersion: 1,
|
||||||
isPublicInviteLinkEnabled: false,
|
isPublicInviteLinkEnabled: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|||||||
export const mapSoftDeleteFieldsToGraphQLQuery = (
|
export const mapSoftDeleteFieldsToGraphQLQuery = (
|
||||||
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>,
|
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>,
|
||||||
): string => {
|
): string => {
|
||||||
const softDeleteFields = ['id', 'deletedAt'];
|
const softDeleteFields = ['deletedAt', 'id'];
|
||||||
|
|
||||||
const fieldsThatShouldBeQueried = objectMetadataItem.fields.filter(
|
const fieldsThatShouldBeQueried = objectMetadataItem.fields
|
||||||
(field) => field.isActive && softDeleteFields.includes(field.name),
|
.filter((field) => field.isActive && softDeleteFields.includes(field.name))
|
||||||
);
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
softDeleteFields.indexOf(a.name) - softDeleteFields.indexOf(b.name),
|
||||||
|
);
|
||||||
|
|
||||||
return `{
|
return `{
|
||||||
__typename
|
__typename
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = `
|
|||||||
id
|
id
|
||||||
intro
|
intro
|
||||||
jobTitle
|
jobTitle
|
||||||
linkedinLink{
|
linkedinLink {
|
||||||
primaryLinkUrl
|
primaryLinkUrl
|
||||||
primaryLinkLabel
|
primaryLinkLabel
|
||||||
secondaryLinks
|
secondaryLinks
|
||||||
@@ -45,31 +45,14 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = `
|
|||||||
primaryLinkLabel
|
primaryLinkLabel
|
||||||
secondaryLinks
|
secondaryLinks
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
|
||||||
export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
||||||
__typename
|
__typename
|
||||||
activityTargets {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
__typename
|
|
||||||
activityId
|
|
||||||
companyId
|
|
||||||
createdAt
|
|
||||||
deletedAt
|
|
||||||
id
|
|
||||||
opportunityId
|
|
||||||
personId
|
|
||||||
rocketId
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attachments {
|
attachments {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
activityId
|
|
||||||
authorId
|
authorId
|
||||||
companyId
|
companyId
|
||||||
createdAt
|
createdAt
|
||||||
@@ -190,6 +173,8 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
|||||||
updatedAt
|
updatedAt
|
||||||
viewId
|
viewId
|
||||||
workflowId
|
workflowId
|
||||||
|
workflowRunId
|
||||||
|
workflowVersionId
|
||||||
workspaceMemberId
|
workspaceMemberId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,6 +293,9 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
|||||||
rocketId
|
rocketId
|
||||||
taskId
|
taskId
|
||||||
updatedAt
|
updatedAt
|
||||||
|
workflowId
|
||||||
|
workflowRunId
|
||||||
|
workflowVersionId
|
||||||
workspaceMemberId
|
workspaceMemberId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,4 +312,4 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
|||||||
primaryLinkLabel
|
primaryLinkLabel
|
||||||
secondaryLinks
|
secondaryLinks
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
|||||||
@@ -15,5 +15,7 @@ export const variables = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const responseData = {
|
export const responseData = {
|
||||||
|
__typename: 'Person',
|
||||||
|
deletedAt: '2024-02-14T09:45:00Z',
|
||||||
id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9',
|
id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,19 @@ import { gql } from '@apollo/client';
|
|||||||
|
|
||||||
import { peopleQueryResult } from '~/testing/mock-data/people';
|
import { peopleQueryResult } from '~/testing/mock-data/people';
|
||||||
|
|
||||||
|
|
||||||
export const query = gql`
|
export const query = gql`
|
||||||
query FindManyPeople($filter: PersonFilterInput, $orderBy: [PersonOrderByInput], $lastCursor: String, $limit: Int) {
|
query FindManyPeople(
|
||||||
people(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
$filter: PersonFilterInput
|
||||||
|
$orderBy: [PersonOrderByInput]
|
||||||
|
$lastCursor: String
|
||||||
|
$limit: Int
|
||||||
|
) {
|
||||||
|
people(
|
||||||
|
filter: $filter
|
||||||
|
orderBy: $orderBy
|
||||||
|
first: $limit
|
||||||
|
after: $lastCursor
|
||||||
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
@@ -27,38 +36,51 @@ export const query = gql`
|
|||||||
|
|
||||||
export const mockPageSize = 2;
|
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 firstRequestLastCursor =
|
||||||
export const secondRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor;
|
peopleMockWithIdsOnly.edges[mockPageSize].cursor;
|
||||||
export const thirdRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor;
|
export const secondRequestLastCursor =
|
||||||
|
peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor;
|
||||||
|
export const thirdRequestLastCursor =
|
||||||
|
peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor;
|
||||||
|
|
||||||
export const variablesFirstRequest = {
|
export const variablesFirstRequest = {
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
limit: mockPageSize,
|
limit: mockPageSize,
|
||||||
orderBy: undefined
|
orderBy: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const variablesSecondRequest = {
|
export const variablesSecondRequest = {
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
limit: mockPageSize,
|
limit: mockPageSize,
|
||||||
orderBy: undefined,
|
orderBy: undefined,
|
||||||
lastCursor: firstRequestLastCursor
|
lastCursor: firstRequestLastCursor,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const variablesThirdRequest = {
|
export const variablesThirdRequest = {
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
limit: mockPageSize,
|
limit: mockPageSize,
|
||||||
orderBy: undefined,
|
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 {
|
return {
|
||||||
...response,
|
...response,
|
||||||
edges: [
|
edges: [...response.edges.slice(start, end)],
|
||||||
...response.edges.slice(start, end)
|
|
||||||
],
|
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
...response.pageInfo,
|
...response.pageInfo,
|
||||||
startCursor: response.edges[start].cursor,
|
startCursor: response.edges[start].cursor,
|
||||||
@@ -66,17 +88,35 @@ const paginateRequestResponse = (response: RecordGqlConnection, start: number, e
|
|||||||
hasNextPage,
|
hasNextPage,
|
||||||
} satisfies RecordGqlConnection['pageInfo'],
|
} satisfies RecordGqlConnection['pageInfo'],
|
||||||
totalCount,
|
totalCount,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const responseFirstRequest = {
|
export const responseFirstRequest = {
|
||||||
people: paginateRequestResponse(peopleMockWithIdsOnly, 0, mockPageSize, true, 6),
|
people: paginateRequestResponse(
|
||||||
|
peopleMockWithIdsOnly,
|
||||||
|
0,
|
||||||
|
mockPageSize,
|
||||||
|
true,
|
||||||
|
6,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const responseSecondRequest = {
|
export const responseSecondRequest = {
|
||||||
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize, mockPageSize * 2, true, 6),
|
people: paginateRequestResponse(
|
||||||
|
peopleMockWithIdsOnly,
|
||||||
|
mockPageSize,
|
||||||
|
mockPageSize * 2,
|
||||||
|
true,
|
||||||
|
6,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const responseThirdRequest = {
|
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
|
city
|
||||||
email
|
email
|
||||||
activityTargets {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
__typename
|
|
||||||
id
|
|
||||||
updatedAt
|
|
||||||
createdAt
|
|
||||||
personId
|
|
||||||
activityId
|
|
||||||
companyId
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jobTitle
|
jobTitle
|
||||||
favorites {
|
favorites {
|
||||||
edges {
|
edges {
|
||||||
@@ -137,7 +123,6 @@ export const query = gql`
|
|||||||
createdAt
|
createdAt
|
||||||
name
|
name
|
||||||
personId
|
personId
|
||||||
activityId
|
|
||||||
companyId
|
companyId
|
||||||
id
|
id
|
||||||
authorId
|
authorId
|
||||||
|
|||||||
@@ -5,15 +5,10 @@ import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter
|
|||||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
||||||
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type MultipleFiltersDropdownContentProps = {
|
type MultipleFiltersDropdownContentProps = {
|
||||||
filterDropdownId?: string;
|
filterDropdownId?: string;
|
||||||
};
|
};
|
||||||
@@ -46,7 +41,7 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
const shoudShowFilterInput = objectFilterDropdownFilterIsSelected;
|
const shoudShowFilterInput = objectFilterDropdownFilterIsSelected;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<>
|
||||||
{shoudShowFilterInput ? (
|
{shoudShowFilterInput ? (
|
||||||
<ObjectFilterOperandSelectAndInput
|
<ObjectFilterOperandSelectAndInput
|
||||||
filterDropdownId={filterDropdownId}
|
filterDropdownId={filterDropdownId}
|
||||||
@@ -61,6 +56,6 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
filterDefinitionUsedInDropdown?.type
|
filterDefinitionUsedInDropdown?.type
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,8 +28,13 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
const {
|
const {
|
||||||
filterDefinitionUsedInDropdownState,
|
filterDefinitionUsedInDropdownState,
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
} = useFilterDropdown({ filterDropdownId });
|
} = useFilterDropdown({ filterDropdownId });
|
||||||
|
|
||||||
|
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
|
);
|
||||||
|
|
||||||
const filterDefinitionUsedInDropdown = useRecoilValue(
|
const filterDefinitionUsedInDropdown = useRecoilValue(
|
||||||
filterDefinitionUsedInDropdownState,
|
filterDefinitionUsedInDropdownState,
|
||||||
);
|
);
|
||||||
@@ -53,7 +58,9 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
ViewFilterOperand.IsRelative,
|
ViewFilterOperand.IsRelative,
|
||||||
].includes(selectedOperandInDropdown);
|
].includes(selectedOperandInDropdown);
|
||||||
|
|
||||||
if (!isDefined(filterDefinitionUsedInDropdown)) {
|
const shouldHide = isObjectFilterDropdownOperandSelectUnfolded;
|
||||||
|
|
||||||
|
if (shouldHide || !isDefined(filterDefinitionUsedInDropdown)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ const StyledOperandSelectContainer = styled.div`
|
|||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
left: 10px;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1000;
|
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 { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-ui';
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ export const StyledInput = styled.input`
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
height: 19px;
|
min-height: 19px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
|
||||||
@@ -139,10 +141,15 @@ export const ObjectFilterDropdownFilterSelect = ({
|
|||||||
const { currentViewId, currentViewWithCombinedFiltersAndSorts } =
|
const { currentViewId, currentViewWithCombinedFiltersAndSorts } =
|
||||||
useGetCurrentView();
|
useGetCurrentView();
|
||||||
|
|
||||||
|
const isAdvancedFiltersEnabled = useIsFeatureEnabled(
|
||||||
|
'IS_ADVANCED_FILTERS_ENABLED',
|
||||||
|
);
|
||||||
|
|
||||||
const shouldShowAdvancedFilterButton =
|
const shouldShowAdvancedFilterButton =
|
||||||
isDefined(currentViewId) &&
|
isDefined(currentViewId) &&
|
||||||
isDefined(currentViewWithCombinedFiltersAndSorts?.objectMetadataId) &&
|
isDefined(currentViewWithCombinedFiltersAndSorts?.objectMetadataId) &&
|
||||||
isAdvancedFilterButtonVisible;
|
isAdvancedFilterButtonVisible &&
|
||||||
|
isAdvancedFiltersEnabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -154,43 +161,45 @@ export const ObjectFilterDropdownFilterSelect = ({
|
|||||||
setObjectFilterDropdownSearchInput(event.target.value)
|
setObjectFilterDropdownSearchInput(event.target.value)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<SelectableList
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
<SelectableList
|
||||||
selectableItemIdArray={selectableListItemIds}
|
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||||
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
|
selectableItemIdArray={selectableListItemIds}
|
||||||
onEnter={handleEnter}
|
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
|
||||||
>
|
onEnter={handleEnter}
|
||||||
<DropdownMenuItemsContainer>
|
>
|
||||||
{visibleColumnsFilterDefinitions.map(
|
<DropdownMenuItemsContainer>
|
||||||
(visibleFilterDefinition, index) => (
|
{visibleColumnsFilterDefinitions.map(
|
||||||
<SelectableItem
|
(visibleFilterDefinition, index) => (
|
||||||
itemId={visibleFilterDefinition.fieldMetadataId}
|
<SelectableItem
|
||||||
key={`visible-select-filter-${index}`}
|
itemId={visibleFilterDefinition.fieldMetadataId}
|
||||||
>
|
key={`visible-select-filter-${index}`}
|
||||||
<ObjectFilterDropdownFilterSelectMenuItem
|
>
|
||||||
filterDefinition={visibleFilterDefinition}
|
<ObjectFilterDropdownFilterSelectMenuItem
|
||||||
/>
|
filterDefinition={visibleFilterDefinition}
|
||||||
</SelectableItem>
|
/>
|
||||||
),
|
</SelectableItem>
|
||||||
)}
|
),
|
||||||
</DropdownMenuItemsContainer>
|
)}
|
||||||
{shoudShowSeparator && <DropdownMenuSeparator />}
|
</DropdownMenuItemsContainer>
|
||||||
<DropdownMenuItemsContainer>
|
{shoudShowSeparator && <DropdownMenuSeparator />}
|
||||||
{hiddenColumnsFilterDefinitions.map(
|
<DropdownMenuItemsContainer>
|
||||||
(hiddenFilterDefinition, index) => (
|
{hiddenColumnsFilterDefinitions.map(
|
||||||
<SelectableItem
|
(hiddenFilterDefinition, index) => (
|
||||||
itemId={hiddenFilterDefinition.fieldMetadataId}
|
<SelectableItem
|
||||||
key={`hidden-select-filter-${index}`}
|
itemId={hiddenFilterDefinition.fieldMetadataId}
|
||||||
>
|
key={`hidden-select-filter-${index}`}
|
||||||
<ObjectFilterDropdownFilterSelectMenuItem
|
>
|
||||||
filterDefinition={hiddenFilterDefinition}
|
<ObjectFilterDropdownFilterSelectMenuItem
|
||||||
/>
|
filterDefinition={hiddenFilterDefinition}
|
||||||
</SelectableItem>
|
/>
|
||||||
),
|
</SelectableItem>
|
||||||
)}
|
),
|
||||||
</DropdownMenuItemsContainer>
|
)}
|
||||||
</SelectableList>
|
</DropdownMenuItemsContainer>
|
||||||
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
</SelectableList>
|
||||||
|
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
||||||
|
</ScrollWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,17 +10,28 @@ export const ObjectFilterDropdownOperandButton = () => {
|
|||||||
const {
|
const {
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
} = useFilterDropdown();
|
} = useFilterDropdown();
|
||||||
|
|
||||||
const selectedOperandInDropdown = useRecoilValue(
|
const selectedOperandInDropdown = useRecoilValue(
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
setIsObjectFilterDropdownOperandSelectUnfolded(
|
||||||
|
!isObjectFilterDropdownOperandSelectUnfolded,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
key={'selected-filter-operand'}
|
key={'selected-filter-operand'}
|
||||||
EndIcon={IconChevronDown}
|
EndIcon={IconChevronDown}
|
||||||
onClick={() => setIsObjectFilterDropdownOperandSelectUnfolded(true)}
|
onClick={handleButtonClick}
|
||||||
>
|
>
|
||||||
{getOperandLabel(selectedOperandInDropdown)}
|
{getOperandLabel(selectedOperandInDropdown)}
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
|
|||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
import { MenuItemMultiSelect } from '@/ui/navigation/menu-item/components/MenuItemMultiSelect';
|
import { MenuItemMultiSelect } from '@/ui/navigation/menu-item/components/MenuItemMultiSelect';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const EMPTY_FILTER_VALUE = '';
|
export const EMPTY_FILTER_VALUE = '';
|
||||||
@@ -162,22 +163,24 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
{optionsInDropdown?.map((option) => (
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
<MenuItemMultiSelect
|
{optionsInDropdown?.map((option) => (
|
||||||
key={option.id}
|
<MenuItemMultiSelect
|
||||||
selected={option.isSelected}
|
key={option.id}
|
||||||
isKeySelected={option.id === selectedItemId}
|
selected={option.isSelected}
|
||||||
onSelectChange={(selected) =>
|
isKeySelected={option.id === selectedItemId}
|
||||||
handleMultipleOptionSelectChange(option, selected)
|
onSelectChange={(selected) =>
|
||||||
}
|
handleMultipleOptionSelectChange(option, selected)
|
||||||
text={option.label}
|
}
|
||||||
color={option.color}
|
text={option.label}
|
||||||
className=""
|
color={option.color}
|
||||||
/>
|
className=""
|
||||||
))}
|
/>
|
||||||
</DropdownMenuItemsContainer>
|
))}
|
||||||
{showNoResult && <MenuItem text="No result" />}
|
</DropdownMenuItemsContainer>
|
||||||
|
{showNoResult && <MenuItem text="No result" />}
|
||||||
|
</ScrollWrapper>
|
||||||
</SelectableList>
|
</SelectableList>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/Styl
|
|||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { SORT_DIRECTIONS } from '../types/SortDirection';
|
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`
|
const StyledSelectedSortDirectionContainer = styled.div`
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
left: 10px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 32px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
`;
|
`;
|
||||||
@@ -166,21 +163,23 @@ export const ObjectSortDropdownButton = ({
|
|||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</StyledSelectedSortDirectionContainer>
|
</StyledSelectedSortDirectionContainer>
|
||||||
)}
|
)}
|
||||||
<StyledContainer>
|
<DropdownMenuHeader
|
||||||
<DropdownMenuHeader
|
EndIcon={IconChevronDown}
|
||||||
EndIcon={IconChevronDown}
|
onClick={() =>
|
||||||
onClick={() => setIsSortDirectionMenuUnfolded(true)}
|
setIsSortDirectionMenuUnfolded(!isSortDirectionMenuUnfolded)
|
||||||
>
|
}
|
||||||
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
>
|
||||||
</DropdownMenuHeader>
|
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
<StyledInput
|
</DropdownMenuHeader>
|
||||||
autoFocus
|
<StyledInput
|
||||||
value={objectSortDropdownSearchInput}
|
autoFocus
|
||||||
placeholder="Search fields"
|
value={objectSortDropdownSearchInput}
|
||||||
onChange={(event) =>
|
placeholder="Search fields"
|
||||||
setObjectSortDropdownSearchInput(event.target.value)
|
onChange={(event) =>
|
||||||
}
|
setObjectSortDropdownSearchInput(event.target.value)
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{visibleColumnsSortDefinitions.map(
|
{visibleColumnsSortDefinitions.map(
|
||||||
(visibleSortDefinition, index) => (
|
(visibleSortDefinition, index) => (
|
||||||
@@ -214,7 +213,7 @@ export const ObjectSortDropdownButton = ({
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</StyledContainer>
|
</ScrollWrapper>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
onClose={handleDropdownButtonClose}
|
onClose={handleDropdownButtonClose}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Key } from 'ts-key-enum';
|
|||||||
|
|
||||||
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
|
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
|
||||||
import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect';
|
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 { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
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 { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
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 { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
||||||
@@ -69,9 +70,15 @@ export const RecordBoard = () => {
|
|||||||
const { resetRecordSelection, setRecordAsSelected } =
|
const { resetRecordSelection, setRecordAsSelected } =
|
||||||
useRecordBoardSelection(recordBoardId);
|
useRecordBoardSelection(recordBoardId);
|
||||||
|
|
||||||
useListenClickOutsideByClassName({
|
useListenClickOutsideV2({
|
||||||
classNames: ['record-board-card'],
|
excludeClassNames: [
|
||||||
excludeClassNames: ['bottom-bar', 'action-menu-dropdown'],
|
'bottom-bar',
|
||||||
|
'action-menu-dropdown',
|
||||||
|
'command-menu',
|
||||||
|
'modal-backdrop',
|
||||||
|
],
|
||||||
|
listenerId: RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID,
|
||||||
|
refs: [boardRef],
|
||||||
callback: resetRecordSelection,
|
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 { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
|
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||||
@@ -29,6 +28,8 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
|||||||
import {
|
import {
|
||||||
AnimatedEaseInOut,
|
AnimatedEaseInOut,
|
||||||
AvatarChipVariant,
|
AvatarChipVariant,
|
||||||
|
Checkbox,
|
||||||
|
CheckboxVariant,
|
||||||
ChipSize,
|
ChipSize,
|
||||||
IconEye,
|
IconEye,
|
||||||
IconEyeOff,
|
IconEyeOff,
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types
|
|||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { useCallback, useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { RecoilState, useRecoilCallback } from 'recoil';
|
import { RecoilState, useRecoilCallback } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
type SetFunction = <T>(
|
type SetFunction = <T>(
|
||||||
recoilVal: RecoilState<T>,
|
recoilVal: RecoilState<T>,
|
||||||
@@ -16,7 +18,7 @@ type SetFunction = <T>(
|
|||||||
|
|
||||||
export const useAddNewCard = () => {
|
export const useAddNewCard = () => {
|
||||||
const columnContext = useContext(RecordBoardColumnContext);
|
const columnContext = useContext(RecordBoardColumnContext);
|
||||||
const { createOneRecord, selectFieldMetadataItem } =
|
const { createOneRecord, selectFieldMetadataItem, objectMetadataItem } =
|
||||||
useContext(RecordBoardContext);
|
useContext(RecordBoardContext);
|
||||||
const { resetSearchFilter } = useEntitySelectSearch({
|
const { resetSearchFilter } = useEntitySelectSearch({
|
||||||
relationPickerScopeId: 'relation-picker',
|
relationPickerScopeId: 'relation-picker',
|
||||||
@@ -75,16 +77,47 @@ export const useAddNewCard = () => {
|
|||||||
(isOpportunity && company !== null) ||
|
(isOpportunity && company !== null) ||
|
||||||
(!isOpportunity && labelValue !== '')
|
(!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({
|
createOneRecord({
|
||||||
[selectFieldMetadataItem.name]: columnContext?.columnDefinition.value,
|
[selectFieldMetadataItem.name]: columnContext?.columnDefinition.value,
|
||||||
position,
|
position,
|
||||||
...(isOpportunity
|
...(isOpportunity
|
||||||
? { companyId: company?.id, name: company?.name }
|
? { 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(
|
const handleAddNewCardClick = useRecoilCallback(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
FieldFullNameMetadata,
|
FieldFullNameMetadata,
|
||||||
FieldRatingMetadata,
|
FieldRatingMetadata,
|
||||||
FieldSelectMetadata,
|
FieldSelectMetadata,
|
||||||
FieldTextMetadata
|
FieldTextMetadata,
|
||||||
} from '@/object-record/record-field/types/FieldMetadata';
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
@@ -29,7 +29,6 @@ if (!mockedPersonObjectMetadataItem) {
|
|||||||
throw new Error('Person object metadata item not found');
|
throw new Error('Person object metadata item not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const relationFieldMetadataItem = mockedPersonObjectMetadataItem?.fields?.find(
|
const relationFieldMetadataItem = mockedPersonObjectMetadataItem?.fields?.find(
|
||||||
({ name }) => name === 'company',
|
({ name }) => name === 'company',
|
||||||
);
|
);
|
||||||
@@ -110,4 +109,4 @@ export const actorFieldDefinition: FieldDefinition<FieldActorMetadata> = {
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'actor',
|
fieldName: 'actor',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,22 +47,6 @@ const mocks: MockedResponse[] = [
|
|||||||
userId
|
userId
|
||||||
}
|
}
|
||||||
accountOwnerId
|
accountOwnerId
|
||||||
activityTargets {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
__typename
|
|
||||||
activityId
|
|
||||||
companyId
|
|
||||||
createdAt
|
|
||||||
deletedAt
|
|
||||||
id
|
|
||||||
opportunityId
|
|
||||||
personId
|
|
||||||
rocketId
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
address {
|
address {
|
||||||
addressStreet1
|
addressStreet1
|
||||||
addressStreet2
|
addressStreet2
|
||||||
@@ -81,7 +65,6 @@ const mocks: MockedResponse[] = [
|
|||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
activityId
|
|
||||||
authorId
|
authorId
|
||||||
companyId
|
companyId
|
||||||
createdAt
|
createdAt
|
||||||
@@ -129,6 +112,8 @@ const mocks: MockedResponse[] = [
|
|||||||
updatedAt
|
updatedAt
|
||||||
viewId
|
viewId
|
||||||
workflowId
|
workflowId
|
||||||
|
workflowRunId
|
||||||
|
workflowVersionId
|
||||||
workspaceMemberId
|
workspaceMemberId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,6 +263,9 @@ const mocks: MockedResponse[] = [
|
|||||||
rocketId
|
rocketId
|
||||||
taskId
|
taskId
|
||||||
updatedAt
|
updatedAt
|
||||||
|
workflowId
|
||||||
|
workflowRunId
|
||||||
|
workflowVersionId
|
||||||
workspaceMemberId
|
workspaceMemberId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmp
|
|||||||
|
|
||||||
const StyledDropdownMenu = styled(DropdownMenu)`
|
const StyledDropdownMenu = styled(DropdownMenu)`
|
||||||
left: -1px;
|
left: -1px;
|
||||||
position: absolute;
|
|
||||||
top: -1px;
|
top: -1px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -46,6 +45,7 @@ type MultiItemFieldInputProps<T> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Todo: the API of this component does not look healthy: we have renderInput, renderItem, formatInput, ...
|
// 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,>({
|
export const MultiItemFieldInput = <T,>({
|
||||||
items,
|
items,
|
||||||
onPersist,
|
onPersist,
|
||||||
@@ -84,9 +84,9 @@ export const MultiItemFieldInput = <T,>({
|
|||||||
setInputValue(value);
|
setInputValue(value);
|
||||||
if (!validateInput) return;
|
if (!validateInput) return;
|
||||||
|
|
||||||
if (errorData.isValid) {
|
setErrorData(
|
||||||
setErrorData(errorData);
|
errorData.isValid ? errorData : { isValid: true, errorMessage: '' },
|
||||||
}
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddButtonClick = () => {
|
const handleAddButtonClick = () => {
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
import styled from '@emotion/styled';
|
import { MenuItemWithOptionDropdown } from '@/ui/navigation/menu-item/components/MenuItemWithOptionDropdown';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
IconBookmark,
|
IconBookmark,
|
||||||
IconBookmarkPlus,
|
IconBookmarkPlus,
|
||||||
IconComponent,
|
|
||||||
IconDotsVertical,
|
|
||||||
IconPencil,
|
IconPencil,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
@@ -24,12 +21,6 @@ type MultiItemFieldMenuItemProps<T> = {
|
|||||||
hasPrimaryButton?: boolean;
|
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,>({
|
export const MultiItemFieldMenuItem = <T,>({
|
||||||
dropdownId,
|
dropdownId,
|
||||||
isPrimary,
|
isPrimary,
|
||||||
@@ -47,66 +38,51 @@ export const MultiItemFieldMenuItem = <T,>({
|
|||||||
const handleMouseLeave = () => setIsHovered(false);
|
const handleMouseLeave = () => setIsHovered(false);
|
||||||
|
|
||||||
const handleDeleteClick = () => {
|
const handleDeleteClick = () => {
|
||||||
|
closeDropdown();
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
onDelete?.();
|
onDelete?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleSetAsPrimaryClick = () => {
|
||||||
if (isDropdownOpen) {
|
closeDropdown();
|
||||||
return () => closeDropdown();
|
onSetAsPrimary?.();
|
||||||
}
|
};
|
||||||
}, [closeDropdown, isDropdownOpen]);
|
|
||||||
|
const handleEditClick = () => {
|
||||||
|
closeDropdown();
|
||||||
|
onEdit?.();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItemWithOptionDropdown
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
text={<DisplayComponent value={value} />}
|
text={<DisplayComponent value={value} />}
|
||||||
isIconDisplayedOnHoverOnly={!isPrimary && !isDropdownOpen}
|
isIconDisplayedOnHoverOnly={!isPrimary && !isDropdownOpen}
|
||||||
iconButtons={[
|
RightIcon={isHovered ? null : IconBookmark}
|
||||||
{
|
dropdownId={dropdownId}
|
||||||
Wrapper: isHovered
|
dropdownContent={
|
||||||
? ({ iconButton }) => (
|
<DropdownMenuItemsContainer>
|
||||||
<Dropdown
|
{hasPrimaryButton && !isPrimary && (
|
||||||
dropdownId={dropdownId}
|
<MenuItem
|
||||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
LeftIcon={IconBookmarkPlus}
|
||||||
dropdownPlacement="right-start"
|
text="Set as Primary"
|
||||||
dropdownStrategy="fixed"
|
onClick={handleSetAsPrimaryClick}
|
||||||
disableBlur
|
/>
|
||||||
clickableComponent={iconButton}
|
)}
|
||||||
dropdownComponents={
|
<MenuItem
|
||||||
<DropdownMenuItemsContainer>
|
LeftIcon={IconPencil}
|
||||||
{hasPrimaryButton && !isPrimary && (
|
text="Edit"
|
||||||
<MenuItem
|
onClick={handleEditClick}
|
||||||
LeftIcon={IconBookmarkPlus}
|
/>
|
||||||
text="Set as Primary"
|
<MenuItem
|
||||||
onClick={onSetAsPrimary}
|
accent="danger"
|
||||||
/>
|
LeftIcon={IconTrash}
|
||||||
)}
|
text="Delete"
|
||||||
<MenuItem
|
onClick={handleDeleteClick}
|
||||||
LeftIcon={IconPencil}
|
/>
|
||||||
text="Edit"
|
</DropdownMenuItemsContainer>
|
||||||
onClick={onEdit}
|
}
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
accent="danger"
|
|
||||||
LeftIcon={IconTrash}
|
|
||||||
text="Delete"
|
|
||||||
onClick={handleDeleteClick}
|
|
||||||
/>
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
Icon:
|
|
||||||
isPrimary && !isHovered
|
|
||||||
? (StyledIconBookmark as IconComponent)
|
|
||||||
: IconDotsVertical,
|
|
||||||
accent: 'tertiary',
|
|
||||||
onClick: isHovered ? () => {} : undefined,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ export const RecordIndexContainer = () => {
|
|||||||
viewBarId={recordIndexId}
|
viewBarId={recordIndexId}
|
||||||
/>
|
/>
|
||||||
</SpreadsheetImportProvider>
|
</SpreadsheetImportProvider>
|
||||||
|
|
||||||
{recordIndexViewType === ViewType.Table && (
|
{recordIndexViewType === ViewType.Table && (
|
||||||
<>
|
<>
|
||||||
<RecordIndexTableContainer
|
<RecordIndexTableContainer
|
||||||
@@ -232,7 +233,7 @@ export const RecordIndexContainer = () => {
|
|||||||
/>
|
/>
|
||||||
</StyledContainerWithPadding>
|
</StyledContainerWithPadding>
|
||||||
)}
|
)}
|
||||||
<RecordIndexActionMenu actionMenuId={recordIndexId} />
|
<RecordIndexActionMenu />
|
||||||
</RecordFieldValueSelectorContextProvider>
|
</RecordFieldValueSelectorContextProvider>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user