Merge branch 'main' into c--build-twenty-query-builder-for-event-emitter

This commit is contained in:
Weiko 2024-10-24 15:18:10 +02:00
commit 22f829b311
1040 changed files with 25564 additions and 4240 deletions

View File

@ -3,13 +3,9 @@ on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-chrome-extension/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-chrome-extension/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -26,7 +22,25 @@ jobs:
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
package.json
packages/twenty-chrome-extension/**
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Chrome Extension / Run build
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx build twenty-chrome-extension
- name: Mark as Valid if No Changes
if: steps.changed-files.outputs.changed != 'true'
run: |
echo "No relevant changes detected. Marking as valid."

View File

@ -3,15 +3,9 @@ on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-front/**'
- 'packages/twenty-ui/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-front/**'
- 'packages/twenty-ui/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -29,21 +23,43 @@ jobs:
access_token: ${{ github.token }}
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
package.json
packages/twenty-front/**
packages/twenty-ui/**
- name: Skip if no relevant changes
if: steps.changed-files.outputs.any_changed == 'false'
run: echo "No relevant changes. Skipping CI."
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Diagnostic disk space issue
if: steps.changed-files.outputs.any_changed == 'true'
run: df -h
- name: Front / Restore Storybook Task Cache
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/task-cache
with:
tag: scope:frontend
tasks: storybook:build
- name: Front / Write .env
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx reset:env twenty-front
- name: Front / Build storybook
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx storybook:build twenty-front
front-sb-test:
runs-on: shipfox-8vcpu-ubuntu-2204
timeout-minutes: 60
needs: front-sb-build
strategy:
matrix:
@ -54,35 +70,70 @@ jobs:
steps:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
packages/twenty-front/**
- name: Skip if no relevant changes
if: steps.changed-files.outputs.any_changed == 'false'
run: echo "No relevant changes. Skipping CI."
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Install Playwright
if: steps.changed-files.outputs.any_changed == 'true'
run: cd packages/twenty-front && npx playwright install
- name: Front / Restore Storybook Task Cache
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/task-cache
with:
tag: scope:frontend
tasks: storybook:build
- name: Front / Write .env
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx reset:env twenty-front
- name: Run storybook tests
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx storybook:serve-and-test:static twenty-front --configuration=${{ matrix.storybook_scope }}
front-sb-test-performance:
runs-on: shipfox-8vcpu-ubuntu-2204
timeout-minutes: 60
env:
REACT_APP_SERVER_BASE_URL: http://localhost:3000
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
steps:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
packages/twenty-front/**
- name: Skip if no relevant changes
if: steps.changed-files.outputs.any_changed == 'false'
run: echo "No relevant changes. Skipping CI."
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Install Playwright
if: steps.changed-files.outputs.any_changed == 'true'
run: cd packages/twenty-front && npx playwright install
- name: Front / Write .env
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx reset:env twenty-front
- name: Run storybook tests
run: npx nx storybook:serve-and-test:static:performance twenty-front
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx run twenty-front:storybook:serve-and-test:static:performance
front-chromatic-deployment:
if: contains(github.event.pull_request.labels.*.name, 'run-chromatic') || github.event_name == 'push'
needs: front-sb-build
@ -95,19 +146,35 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
packages/twenty-front/**
- name: Skip if no relevant changes
if: steps.changed-files.outputs.any_changed == 'false'
run: echo "No relevant changes. Skipping CI."
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Front / Restore Storybook Task Cache
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/task-cache
with:
tag: scope:frontend
tasks: storybook:build
- name: Front / Write .env
if: steps.changed-files.outputs.any_changed == 'true'
run: |
cd packages/twenty-front
touch .env
echo "REACT_APP_SERVER_BASE_URL: $REACT_APP_SERVER_BASE_URL" >> .env
- name: Publish to Chromatic
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx run twenty-front:chromatic:ci
front-task:
runs-on: ubuntu-latest
@ -125,19 +192,34 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
packages/twenty-front/**
- name: Skip if no relevant changes
if: steps.changed-files.outputs.any_changed == 'false'
run: echo "No relevant changes. Skipping CI."
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Front / Restore ${{ matrix.task }} task cache
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/task-cache
with:
tag: scope:frontend
tasks: ${{ matrix.task }}
- name: Reset .env
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:frontend
tasks: reset:env
- name: Run ${{ matrix.task }} task
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:frontend

View File

@ -3,15 +3,9 @@ on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-server/**'
- 'packages/twenty-emails/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-server/**'
- 'packages/twenty-emails/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -38,22 +32,38 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
package.json
packages/twenty-server/**
packages/twenty-emails/**
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Server / Restore Task Cache
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/task-cache
with:
tag: scope:backend
- name: Server / Run lint & typecheck
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:backend
tasks: lint,typecheck
- name: Server / Build
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx build twenty-server
- name: Server / Write .env
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx reset:env twenty-server
- name: Worker / Run
if: steps.changed-files.outputs.any_changed == 'true'
run: npx nx run twenty-server:worker:ci
server-test:
@ -66,13 +76,26 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
package.json
packages/twenty-server/**
packages/twenty-emails/**
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Server / Restore Task Cache
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/task-cache
with:
tag: scope:backend
- name: Server / Run Tests
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:backend
@ -100,13 +123,26 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
package.json
packages/twenty-server/**
packages/twenty-emails/**
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Server / Restore Task Cache
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/task-cache
with:
tag: scope:backend
- name: Server / Run Integration Tests
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:backend

View File

@ -1,8 +1,7 @@
name: 'Test Docker Compose'
on:
pull_request:
paths:
- 'packages/twenty-docker/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -13,8 +12,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
packages/twenty-docker/**
docker-compose.yml
- name: Skip if no relevant changes
if: steps.changed-files.outputs.any_changed != 'true'
run: echo "No relevant changes detected. Marking as valid."
- name: Run compose
if: steps.changed-files.outputs.any_changed == 'true'
run: |
echo "Patching docker-compose.yml..."
# change image to localbuild using yq

View File

@ -23,9 +23,16 @@ jobs:
if: github.event.action != 'closed'
steps:
- uses: actions/checkout@v4
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: 'packages/twenty-utils/**'
- name: Install dependencies
if: steps.changed-files.outputs.changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Utils / Run Danger.js
if: steps.changed-files.outputs.changed == 'true'
run: cd packages/twenty-utils && npx nx danger:ci
env:
DANGER_GITHUB_API_TOKEN: ${{ github.token }}
@ -35,9 +42,16 @@ jobs:
if: github.event.action == 'closed' && github.event.pull_request.merged == true
steps:
- uses: actions/checkout@v4
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: 'packages/twenty-utils/**'
- name: Install dependencies
if: steps.changed-files.outputs.changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Run congratulate-dangerfile.js
if: steps.changed-files.outputs.changed == 'true'
run: cd packages/twenty-utils && npx nx danger:congratulate
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -3,13 +3,10 @@ on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-website/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-website/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -27,13 +24,29 @@ jobs:
- 5432:5432
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: 'package.json, packages/twenty-website/**'
- name: Install dependencies
if: steps.changed-files.outputs.changed == 'true'
uses: ./.github/workflows/actions/yarn-install
- name: Website / Run migrations
if: steps.changed-files.outputs.changed == 'true'
run: npx nx database:migrate twenty-website
env:
DATABASE_PG_URL: postgres://twenty:twenty@localhost:5432/default
- name: Website / Build Website
if: steps.changed-files.outputs.changed == 'true'
run: npx nx build twenty-website
env:
DATABASE_PG_URL: postgres://twenty:twenty@localhost:5432/default
DATABASE_PG_URL: postgres://twenty:twenty@localhost:5432/default
- name: Mark as VALID
if: steps.changed-files.outputs.changed != 'true' # If no changes, mark as valid
run: echo "No relevant changes detected. CI is valid."

View File

@ -13,11 +13,27 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v11
with:
files: |
packages/** # Adjust this to your relevant directories
playwright.config.ts # Include any relevant config files
- name: Skip if no relevant changes
if: steps.changed-files.outputs.any_changed != 'true'
run: echo "No relevant changes detected. Marking as valid."
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
run: npm install -g yarn && yarn
- name: Install Playwright Browsers
if: steps.changed-files.outputs.any_changed == 'true'
run: yarn playwright install --with-deps
- name: Run Playwright tests
if: steps.changed-files.outputs.any_changed == 'true'
run: yarn test:e2e companies
- uses: actions/upload-artifact@v4
if: always()

1
.gitignore vendored
View File

@ -9,6 +9,7 @@
.nx/installation
.nx/cache
projectStructure.cache.json
.pnp.*
.yarn/*

View File

@ -45,5 +45,5 @@
"search.exclude": {
"**/.yarn": true,
},
"eslint.debug": true
"eslint.debug": true,
}

49
LICENSE
View File

@ -1,3 +1,8 @@
This project is mostly licensed under the GNU General Public License (GPL) as described below. However, certain files within this project are licensed under a different commercial license. These files are clearly marked with the following comment at the top of the file: /* @license Enterprise */
Files with this comment are not licensed under the aGPL v3, but instead are subject to the commercial license terms defined later in this file.
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
@ -659,3 +664,47 @@ specific requirements.
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
------------------------------------
The Twenty.com Commercial License (the “Commercial License”)
Copyright (c) 2023-present Twenty.com, PBC
With regard to Twenty's Software:
This part of the software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Terms available
at https://twenty.com/legal/terms, or other agreements governing
the use of the Software, as mutually agreed by you and Twenty.com, PBC ("Twenty"),
and otherwise have a valid Twenty Enterprise Edition subscription
for the correct number of hosts and seats as defined in the Commercial Terms.
Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Twenty and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Commercial Subscription for the correct number of hosts and seats.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that Twenty.Com and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Twenty Software, those
components are licensed under the original license provided by the owner of the
applicable component.

View File

@ -45,7 +45,7 @@ trap on_exit EXIT
# Use environment variables VERSION and BRANCH, with defaults if not set
version=${VERSION:-$(curl -s https://api.github.com/repos/twentyhq/twenty/releases/latest | grep '"tag_name":' | cut -d '"' -f 4)}
branch=${BRANCH:-main}
branch=${BRANCH:-$version}
echo "🚀 Using version $version and branch $branch"
@ -72,7 +72,7 @@ done
echo "📁 Creating directory '$dir_name'"
mkdir -p "$dir_name" && cd "$dir_name" || { echo "❌ Failed to create/access directory '$dir_name'"; exit 1; }
# Copy the twenty/packages/twenty-docker/docker-compose.yml file in it
# Copy twenty/packages/twenty-docker/docker-compose.yml in it
echo -e "\t• Copying docker-compose.yml"
curl -sLo docker-compose.yml https://raw.githubusercontent.com/twentyhq/twenty/$branch/packages/twenty-docker/docker-compose.yml

14
nx.json
View File

@ -108,12 +108,11 @@
"storybook:build": {
"executor": "nx:run-commands",
"cache": true,
"dependsOn": ["^build"],
"inputs": ["^default", "excludeTests"],
"outputs": ["{projectRoot}/{options.output-dir}"],
"options": {
"cwd": "{projectRoot}",
"command": "storybook build",
"command": "VITE_DISABLE_ESLINT_CHECKER=true storybook build",
"output-dir": "storybook-static",
"config-dir": ".storybook"
}
@ -192,16 +191,7 @@
"executor": "nx:run-commands",
"options": {
"commands": [
"npx concurrently --kill-others --success=first -n SB,TEST 'nx storybook:serve:static {projectName} --port={args.port}' 'npx wait-on tcp:{args.port} && nx storybook:test {projectName} --port={args.port}'"
],
"port": 6006
}
},
"storybook:serve-and-test:static:performance": {
"executor": "nx:run-commands",
"options": {
"commands": [
"npx concurrently --kill-others --success=first -n SB,TEST 'nx storybook:serve:dev {projectName} --configuration=performance --port={args.port}' 'npx wait-on tcp:{args.port} && nx storybook:test:no-coverage {projectName} --port={args.port} --configuration=performance'"
"npx concurrently --kill-others --success=first -n SB,TEST 'nx storybook:serve:static {projectName} --port={args.port} --configuration={args.performance}' 'npx wait-on tcp:{args.port} && nx storybook:test {projectName} --port={args.port} --configuration={args.scope}'"
],
"port": 6006
}

View File

@ -18,4 +18,6 @@ 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)
---

View File

@ -18,4 +18,10 @@ 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)

View File

@ -18,4 +18,10 @@ 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)
» 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)
---

View File

@ -1,5 +1,5 @@
**Side Quest**: Design a promotional poster of Twenty and share it on social media.
**Points**: 300 Points
**Points**: 50 Points
**Proof**: Add your oss handle and poster link to the list below.
Please follow the following schema:
@ -25,4 +25,11 @@ Your turn 👇
» 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)

View File

@ -1,5 +1,5 @@
**Side Quest**: Design/Create new Twenty logo, tweet your design, and mention @twentycrm.
**Points**: 300 Points
**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:
@ -20,10 +20,18 @@ Your turn 👇
» 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)
---

View File

@ -1,12 +1,13 @@
**Side Quest**: Duplicate the Figma file from the main repo and customize the variables to create a unique interface theme for Twenty.
**Points**: 750 Points
**Proof**: Add your oss handle and Figma link to the list below.
**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://link.to/content
» 05-April-2024 by YOUR oss.gg HANDLE] Figma Link: https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?t=YIFyswta6Xf6sSYK-0
---
@ -18,4 +19,6 @@ Your turn 👇
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/)
---
» 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)
---

View File

@ -18,4 +18,5 @@ 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)
---

View File

@ -46,3 +46,17 @@ Your turn 👇
» 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

View File

@ -28,4 +28,12 @@ Your turn 👇
» 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

View File

@ -34,4 +34,15 @@ Your turn 👇
» 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

View File

@ -34,4 +34,12 @@ Your turn 👇
» 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

View File

@ -16,4 +16,6 @@ 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)

View File

@ -176,6 +176,7 @@
"scroll-into-view": "^1.16.2",
"semver": "^7.5.4",
"sharp": "^0.32.1",
"slash": "^5.1.0",
"stripe": "^14.17.0",
"ts-key-enum": "^2.0.12",
"tslib": "^2.3.0",
@ -294,6 +295,7 @@
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-project-structure": "^3.7.2",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
@ -347,7 +349,7 @@
"version": "0.2.1",
"nx": {},
"scripts": {
"start": "npx nx run-many -t start -p twenty-server twenty-front"
"start": "npx nx run-many -t start worker -p twenty-server twenty-front"
},
"workspaces": {
"packages": [

View File

@ -21,7 +21,14 @@ module.exports = {
parserOptions: {
project: ['packages/twenty-front/tsconfig.{json,*.json}'],
},
rules: {},
plugins: ['project-structure'],
settings: {
'project-structure/folder-structure-config-path':
'packages/twenty-front/folderStructure.json',
},
rules: {
'project-structure/folder-structure': 'error',
},
},
],
};

View File

@ -50,7 +50,11 @@ const config: StorybookConfig = {
const { mergeConfig } = await import('vite');
return mergeConfig(config, {
// Add dependencies to pre-optimization
resolve: {
alias: {
'react-dom/client': 'react-dom/profiling',
},
},
});
},
};

View File

@ -0,0 +1,81 @@
{
"$schema": "../../node_modules/eslint-plugin-project-structure/folderStructure.schema.json",
"regexParameters": {
"camelCase": "^[a-z]+[A-Za-z0-9]+"
},
"structure": [
{
"name": "packages",
"children": [
{
"name": "twenty-front",
"children": [
{ "name": "*", "children": [] },
{ "name": "*" },
{
"name": "src",
"children": [
{ "name": "*", "children": [] },
{ "name": "*" },
{
"name": "modules",
"children": [
{ "ruleId": "moduleFolderRule" },
{ "name": "types", "ruleId": "doNotCheckLeafFolderRule" }
]
}
]
}
]
}
]
}
],
"rules": {
"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]+)**$",
"folderRecursionLimit": 6,
"children": [
{ "ruleId": "moduleFolderRule" },
{ "name": "hooks", "ruleId": "hooksLeafFolderRule" },
{ "name": "utils", "ruleId": "utilsLeafFolderRule" },
{ "name": "states", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "types", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "graphql", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "components", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "effect-components", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "constants", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "validation-schemas", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "contexts", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "scopes", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "services", "ruleId": "doNotCheckLeafFolderRule" },
{ "name": "errors", "ruleId": "doNotCheckLeafFolderRule" }
]
},
"hooksLeafFolderRule": {
"folderRecursionLimit": 2,
"children": [
{ "name": "use{PascalCase}.(ts|tsx)" },
{
"name": "__tests__",
"children": [{ "name": "use{PascalCase}.test.(ts|tsx)" }]
},
{ "name": "internal", "ruleId": "hooksLeafFolderRule" }
]
},
"doNotCheckLeafFolderRule": {
"folderRecursionLimit": 1,
"children": [{ "name": "*" }, { "name": "*", "children": [] }]
},
"utilsLeafFolderRule": {
"folderRecursionLimit": 1,
"children": [
{ "name": "{camelCase}.ts" },
{
"name": "__tests__",
"children": [{ "name": "{camelCase}.test.ts" }]
}
]
}
}
}

View File

@ -25,9 +25,9 @@ const jestConfig: JestConfigWithTsJest = {
extensionsToTreatAsEsm: ['.ts', '.tsx'],
coverageThreshold: {
global: {
statements: 60,
statements: 59,
lines: 55,
functions: 50,
functions: 49,
},
},
collectCoverageFrom: ['<rootDir>/src/**/*.ts'],

View File

@ -33,6 +33,12 @@
"@nivo/calendar": "^0.87.0",
"@nivo/core": "^0.87.0",
"@nivo/line": "^0.87.0",
"@tiptap/extension-document": "^2.9.0",
"@tiptap/extension-paragraph": "^2.9.0",
"@tiptap/extension-placeholder": "^2.9.0",
"@tiptap/extension-text": "^2.9.0",
"@tiptap/extension-text-style": "^2.8.0",
"@tiptap/react": "^2.8.0",
"@xyflow/react": "^12.0.4",
"transliteration": "^2.3.5"
}

View File

@ -52,7 +52,9 @@
"reportUnusedDisableDirectives": "error"
},
"configurations": {
"ci": { "eslintConfig": "{projectRoot}/.eslintrc-ci.cjs" },
"ci": {
"eslintConfig": "{projectRoot}/.eslintrc-ci.cjs"
},
"fix": {}
}
},
@ -68,6 +70,12 @@
"storybook:build": {
"options": {
"env": { "NODE_OPTIONS": "--max_old_space_size=6500" }
},
"configurations": {
"docs": { "env": { "STORYBOOK_SCOPE": "ui-docs" } },
"modules": { "env": { "STORYBOOK_SCOPE": "modules" } },
"pages": { "env": { "STORYBOOK_SCOPE": "pages" } },
"performance": { "env": { "STORYBOOK_SCOPE": "performance" } }
}
},
"storybook:serve:dev": {
@ -80,7 +88,13 @@
}
},
"storybook:serve:static": {
"options": { "port": 6006 }
"options": { "port": 6006 },
"configurations": {
"docs": { "env": { "STORYBOOK_SCOPE": "ui-docs" } },
"modules": { "env": { "STORYBOOK_SCOPE": "modules" } },
"pages": { "env": { "STORYBOOK_SCOPE": "pages" } },
"performance": { "env": { "STORYBOOK_SCOPE": "performance" } }
}
},
"storybook:coverage": {
"configurations": {
@ -102,9 +116,6 @@
},
"storybook:serve-and-test:static": {
"options": {
"commands": [
"npx concurrently --kill-others --success=first -n SB,TEST 'nx storybook:serve:static {projectName} --port={args.port}' 'npx wait-on tcp:{args.port} && nx storybook:test {projectName} --port={args.port} --configuration={args.scope}'"
],
"port": 6006
},
"configurations": {
@ -114,15 +125,6 @@
"performance": { "scope": "performance" }
}
},
"storybook:serve-and-test:static:performance": {},
"storybook:test:no-coverage": {
"configurations": {
"docs": { "env": { "STORYBOOK_SCOPE": "ui-docs" } },
"modules": { "env": { "STORYBOOK_SCOPE": "modules" } },
"pages": { "env": { "STORYBOOK_SCOPE": "pages" } },
"performance": { "env": { "STORYBOOK_SCOPE": "performance" } }
}
},
"graphql:generate": {
"executor": "nx:run-commands",
"defaultConfiguration": "data",

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -64,6 +64,7 @@ export type AuthProviders = {
magicLink: Scalars['Boolean'];
microsoft: Scalars['Boolean'];
password: Scalars['Boolean'];
sso: Scalars['Boolean'];
};
export type AuthToken = {
@ -141,6 +142,7 @@ export enum CaptchaDriverType {
export type ClientConfig = {
__typename?: 'ClientConfig';
analyticsEnabled: Scalars['Boolean'];
api: ApiConfig;
authProviders: AuthProviders;
billing: Billing;
@ -174,9 +176,13 @@ export type DeleteOneObjectInput = {
id: Scalars['UUID'];
};
export type DeleteServerlessFunctionInput = {
/** The id of the function. */
id: Scalars['ID'];
export type DeleteSsoInput = {
identityProviderId: Scalars['String'];
};
export type DeleteSsoOutput = {
__typename?: 'DeleteSsoOutput';
identityProviderId: Scalars['String'];
};
/** Schema update on a table */
@ -187,6 +193,20 @@ export enum DistantTableUpdate {
TableDeleted = 'TABLE_DELETED'
}
export type EditSsoInput = {
id: Scalars['String'];
status: SsoIdentityProviderStatus;
};
export type EditSsoOutput = {
__typename?: 'EditSsoOutput';
id: Scalars['String'];
issuer: Scalars['String'];
name: Scalars['String'];
status: SsoIdentityProviderStatus;
type: IdpType;
};
export type EmailPasswordResetLink = {
__typename?: 'EmailPasswordResetLink';
/** Boolean that confirms query was dispatched */
@ -276,12 +296,53 @@ export enum FileFolder {
WorkspaceLogo = 'WorkspaceLogo'
}
export type FindAvailableSsoidpInput = {
email: Scalars['String'];
};
export type FindAvailableSsoidpOutput = {
__typename?: 'FindAvailableSSOIDPOutput';
id: Scalars['String'];
issuer: Scalars['String'];
name: Scalars['String'];
status: SsoIdentityProviderStatus;
type: IdpType;
workspace: WorkspaceNameAndId;
};
export type FullName = {
__typename?: 'FullName';
firstName: Scalars['String'];
lastName: Scalars['String'];
};
export type GenerateJwt = GenerateJwtOutputWithAuthTokens | GenerateJwtOutputWithSsoauth;
export type GenerateJwtOutputWithAuthTokens = {
__typename?: 'GenerateJWTOutputWithAuthTokens';
authTokens: AuthTokens;
reason: Scalars['String'];
success: Scalars['Boolean'];
};
export type GenerateJwtOutputWithSsoauth = {
__typename?: 'GenerateJWTOutputWithSSOAUTH';
availableSSOIDPs: Array<FindAvailableSsoidpOutput>;
reason: Scalars['String'];
success: Scalars['Boolean'];
};
export type GetAuthorizationUrlInput = {
identityProviderId: Scalars['String'];
};
export type GetAuthorizationUrlOutput = {
__typename?: 'GetAuthorizationUrlOutput';
authorizationURL: Scalars['String'];
id: Scalars['String'];
type: Scalars['String'];
};
export type GetServerlessFunctionSourceCodeInput = {
/** The id of the function. */
id: Scalars['ID'];
@ -289,6 +350,11 @@ export type GetServerlessFunctionSourceCodeInput = {
version?: Scalars['String'];
};
export enum IdpType {
Oidc = 'OIDC',
Saml = 'SAML'
}
export type IndexConnection = {
__typename?: 'IndexConnection';
/** Array of edges. */
@ -358,23 +424,29 @@ export type Mutation = {
authorizeApp: AuthorizeApp;
challenge: LoginToken;
checkoutSession: SessionEntity;
createOIDCIdentityProvider: SetupSsoOutput;
createOneAppToken: AppToken;
createOneObject: Object;
createOneServerlessFunction: ServerlessFunction;
createSAMLIdentityProvider: SetupSsoOutput;
deactivateWorkflowVersion: Scalars['Boolean'];
deleteCurrentWorkspace: Workspace;
deleteOneObject: Object;
deleteOneServerlessFunction: ServerlessFunction;
deleteSSOIdentityProvider: DeleteSsoOutput;
deleteUser: User;
deleteWorkspaceInvitation: Scalars['String'];
disablePostgresProxy: PostgresCredentials;
editSSOIdentityProvider: EditSsoOutput;
emailPasswordResetLink: EmailPasswordResetLink;
enablePostgresProxy: PostgresCredentials;
exchangeAuthorizationCode: ExchangeAuthCode;
executeOneServerlessFunction: ServerlessFunctionExecutionResult;
findAvailableSSOIdentityProviders: Array<FindAvailableSsoidpOutput>;
generateApiKeyToken: ApiKeyToken;
generateJWT: AuthTokens;
generateJWT: GenerateJwt;
generateTransientToken: TransientToken;
getAuthorizationUrl: GetAuthorizationUrlOutput;
impersonate: Verify;
publishServerlessFunction: ServerlessFunction;
renewToken: AuthTokens;
@ -437,11 +509,21 @@ export type MutationCheckoutSessionArgs = {
};
export type MutationCreateOidcIdentityProviderArgs = {
input: SetupOidcSsoInput;
};
export type MutationCreateOneServerlessFunctionArgs = {
input: CreateServerlessFunctionInput;
};
export type MutationCreateSamlIdentityProviderArgs = {
input: SetupSamlSsoInput;
};
export type MutationDeactivateWorkflowVersionArgs = {
workflowVersionId: Scalars['String'];
};
@ -453,7 +535,12 @@ export type MutationDeleteOneObjectArgs = {
export type MutationDeleteOneServerlessFunctionArgs = {
input: DeleteServerlessFunctionInput;
input: ServerlessFunctionIdInput;
};
export type MutationDeleteSsoIdentityProviderArgs = {
input: DeleteSsoInput;
};
@ -462,6 +549,11 @@ export type MutationDeleteWorkspaceInvitationArgs = {
};
export type MutationEditSsoIdentityProviderArgs = {
input: EditSsoInput;
};
export type MutationEmailPasswordResetLinkArgs = {
email: Scalars['String'];
};
@ -479,6 +571,11 @@ export type MutationExecuteOneServerlessFunctionArgs = {
};
export type MutationFindAvailableSsoIdentityProvidersArgs = {
input: FindAvailableSsoidpInput;
};
export type MutationGenerateApiKeyTokenArgs = {
apiKeyId: Scalars['String'];
expiresAt: Scalars['String'];
@ -490,6 +587,11 @@ export type MutationGenerateJwtArgs = {
};
export type MutationGetAuthorizationUrlArgs = {
input: GetAuthorizationUrlInput;
};
export type MutationImpersonateArgs = {
userId: Scalars['String'];
};
@ -669,6 +771,8 @@ export type Query = {
clientConfig: ClientConfig;
currentUser: User;
currentWorkspace: Workspace;
findManyServerlessFunctions: Array<ServerlessFunction>;
findOneServerlessFunction: ServerlessFunction;
findWorkspaceFromInviteHash: Workspace;
findWorkspaceInvitations: Array<WorkspaceInvitation>;
getAvailablePackages: Scalars['JSON'];
@ -681,10 +785,9 @@ export type Query = {
getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
index: Index;
indexMetadatas: IndexConnection;
listSSOIdentityProvidersByWorkspaceId: Array<FindAvailableSsoidpOutput>;
object: Object;
objects: ObjectConnection;
serverlessFunction: ServerlessFunction;
serverlessFunctions: ServerlessFunctionConnection;
validatePasswordResetToken: ValidatePasswordResetToken;
};
@ -705,6 +808,11 @@ export type QueryCheckWorkspaceInviteHashIsValidArgs = {
};
export type QueryFindOneServerlessFunctionArgs = {
input: ServerlessFunctionIdInput;
};
export type QueryFindWorkspaceFromInviteHashArgs = {
inviteHash: Scalars['String'];
};
@ -821,6 +929,12 @@ export type RunWorkflowVersionInput = {
workflowVersionId: Scalars['String'];
};
export enum SsoIdentityProviderStatus {
Active = 'Active',
Error = 'Error',
Inactive = 'Inactive'
}
export type SendInvitationsOutput = {
__typename?: 'SendInvitationsOutput';
errors: Array<Scalars['String']>;
@ -843,27 +957,12 @@ export type ServerlessFunction = {
id: Scalars['UUID'];
latestVersion?: Maybe<Scalars['String']>;
name: Scalars['String'];
publishedVersions: Array<Scalars['String']>;
runtime: Scalars['String'];
syncStatus: ServerlessFunctionSyncStatus;
updatedAt: Scalars['DateTime'];
};
export type ServerlessFunctionConnection = {
__typename?: 'ServerlessFunctionConnection';
/** Array of edges. */
edges: Array<ServerlessFunctionEdge>;
/** Paging information */
pageInfo: PageInfo;
};
export type ServerlessFunctionEdge = {
__typename?: 'ServerlessFunctionEdge';
/** Cursor for this node. */
cursor: Scalars['ConnectionCursor'];
/** The node containing the ServerlessFunction */
node: ServerlessFunction;
};
export type ServerlessFunctionExecutionResult = {
__typename?: 'ServerlessFunctionExecutionResult';
/** Execution result in JSON format */
@ -882,6 +981,11 @@ export enum ServerlessFunctionExecutionStatus {
Success = 'SUCCESS'
}
export type ServerlessFunctionIdInput = {
/** The id of the function. */
id: Scalars['ID'];
};
/** SyncStatus of the serverlessFunction */
export enum ServerlessFunctionSyncStatus {
NotReady = 'NOT_READY',
@ -893,6 +997,31 @@ export type SessionEntity = {
url?: Maybe<Scalars['String']>;
};
export type SetupOidcSsoInput = {
clientID: Scalars['String'];
clientSecret: Scalars['String'];
issuer: Scalars['String'];
name: Scalars['String'];
};
export type SetupSamlSsoInput = {
certificate: Scalars['String'];
fingerprint?: InputMaybe<Scalars['String']>;
id: Scalars['String'];
issuer: Scalars['String'];
name: Scalars['String'];
ssoURL: Scalars['String'];
};
export type SetupSsoOutput = {
__typename?: 'SetupSsoOutput';
id: Scalars['String'];
issuer: Scalars['String'];
name: Scalars['String'];
status: SsoIdentityProviderStatus;
type: IdpType;
};
/** Sort Directions */
export enum SortDirection {
Asc = 'ASC',
@ -1031,6 +1160,7 @@ export type UpdateObjectPayload = {
labelSingular?: InputMaybe<Scalars['String']>;
namePlural?: InputMaybe<Scalars['String']>;
nameSingular?: InputMaybe<Scalars['String']>;
shouldSyncLabelAndName?: InputMaybe<Scalars['Boolean']>;
};
export type UpdateOneObjectInput = {
@ -1052,11 +1182,13 @@ export type UpdateWorkspaceInput = {
displayName?: InputMaybe<Scalars['String']>;
domainName?: InputMaybe<Scalars['String']>;
inviteHash?: InputMaybe<Scalars['String']>;
isPublicInviteLinkEnabled?: InputMaybe<Scalars['Boolean']>;
logo?: InputMaybe<Scalars['String']>;
};
export type User = {
__typename?: 'User';
analyticsTinybirdJwt?: Maybe<Scalars['String']>;
canImpersonate: Scalars['Boolean'];
createdAt: Scalars['DateTime'];
defaultAvatarUrl?: Maybe<Scalars['String']>;
@ -1141,6 +1273,7 @@ export type Workspace = {
featureFlags?: Maybe<Array<FeatureFlag>>;
id: Scalars['UUID'];
inviteHash?: Maybe<Scalars['String']>;
isPublicInviteLinkEnabled: Scalars['Boolean'];
logo?: Maybe<Scalars['String']>;
metadataVersion: Scalars['Float'];
updatedAt: Scalars['DateTime'];
@ -1213,6 +1346,12 @@ export enum WorkspaceMemberTimeFormatEnum {
System = 'SYSTEM'
}
export type WorkspaceNameAndId = {
__typename?: 'WorkspaceNameAndId';
displayName?: Maybe<Scalars['String']>;
id: Scalars['String'];
};
export type Field = {
__typename?: 'field';
createdAt: Scalars['DateTime'];
@ -1338,6 +1477,7 @@ export type Object = {
labelSingular: Scalars['String'];
namePlural: Scalars['String'];
nameSingular: Scalars['String'];
shouldSyncLabelAndName: Scalars['Boolean'];
updatedAt: Scalars['DateTime'];
};
@ -1469,6 +1609,8 @@ export type AuthTokenFragmentFragment = { __typename?: 'AuthToken', token: strin
export type AuthTokensFragmentFragment = { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } };
export type AvailableSsoIdentityProvidersFragmentFragment = { __typename?: 'FindAvailableSSOIDPOutput', id: string, issuer: string, name: string, status: SsoIdentityProviderStatus, workspace: { __typename?: 'WorkspaceNameAndId', id: string, displayName?: string | null } };
export type AuthorizeAppMutationVariables = Exact<{
clientId: Scalars['String'];
codeChallenge: Scalars['String'];
@ -1494,6 +1636,13 @@ export type EmailPasswordResetLinkMutationVariables = Exact<{
export type EmailPasswordResetLinkMutation = { __typename?: 'Mutation', emailPasswordResetLink: { __typename?: 'EmailPasswordResetLink', success: boolean } };
export type FindAvailableSsoIdentityProvidersMutationVariables = Exact<{
input: FindAvailableSsoidpInput;
}>;
export type FindAvailableSsoIdentityProvidersMutation = { __typename?: 'Mutation', findAvailableSSOIdentityProviders: Array<{ __typename?: 'FindAvailableSSOIDPOutput', id: string, issuer: string, name: string, status: SsoIdentityProviderStatus, workspace: { __typename?: 'WorkspaceNameAndId', id: string, displayName?: string | null } }> };
export type GenerateApiKeyTokenMutationVariables = Exact<{
apiKeyId: Scalars['String'];
expiresAt: Scalars['String'];
@ -1507,19 +1656,26 @@ export type GenerateJwtMutationVariables = Exact<{
}>;
export type GenerateJwtMutation = { __typename?: 'Mutation', generateJWT: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type GenerateJwtMutation = { __typename?: 'Mutation', generateJWT: { __typename?: 'GenerateJWTOutputWithAuthTokens', success: boolean, reason: string, authTokens: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } } | { __typename?: 'GenerateJWTOutputWithSSOAUTH', success: boolean, reason: string, availableSSOIDPs: Array<{ __typename?: 'FindAvailableSSOIDPOutput', id: string, issuer: string, name: string, status: SsoIdentityProviderStatus, workspace: { __typename?: 'WorkspaceNameAndId', id: string, displayName?: string | null } }> } };
export type GenerateTransientTokenMutationVariables = Exact<{ [key: string]: never; }>;
export type GenerateTransientTokenMutation = { __typename?: 'Mutation', generateTransientToken: { __typename?: 'TransientToken', transientToken: { __typename?: 'AuthToken', token: string } } };
export type GetAuthorizationUrlMutationVariables = Exact<{
input: GetAuthorizationUrlInput;
}>;
export type GetAuthorizationUrlMutation = { __typename?: 'Mutation', getAuthorizationUrl: { __typename?: 'GetAuthorizationUrlOutput', id: string, type: string, authorizationURL: string } };
export type ImpersonateMutationVariables = Exact<{
userId: Scalars['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, 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, 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, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type RenewTokenMutationVariables = Exact<{
appToken: Scalars['String'];
@ -1552,7 +1708,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, 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, 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, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type CheckUserExistsQueryVariables = Exact<{
email: Scalars['String'];
@ -1599,14 +1755,47 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } };
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, analyticsEnabled: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } };
export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;
export type SkipSyncEmailOnboardingStepMutation = { __typename?: 'Mutation', skipSyncEmailOnboardingStep: { __typename?: 'OnboardingStepSuccess', success: boolean } };
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: 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, 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 CreateOidcIdentityProviderMutationVariables = Exact<{
input: SetupOidcSsoInput;
}>;
export type CreateOidcIdentityProviderMutation = { __typename?: 'Mutation', createOIDCIdentityProvider: { __typename?: 'SetupSsoOutput', id: string, type: IdpType, issuer: string, name: string, status: SsoIdentityProviderStatus } };
export type CreateSamlIdentityProviderMutationVariables = Exact<{
input: SetupSamlSsoInput;
}>;
export type CreateSamlIdentityProviderMutation = { __typename?: 'Mutation', createSAMLIdentityProvider: { __typename?: 'SetupSsoOutput', id: string, type: IdpType, issuer: string, name: string, status: SsoIdentityProviderStatus } };
export type DeleteSsoIdentityProviderMutationVariables = Exact<{
input: DeleteSsoInput;
}>;
export type DeleteSsoIdentityProviderMutation = { __typename?: 'Mutation', deleteSSOIdentityProvider: { __typename?: 'DeleteSsoOutput', identityProviderId: string } };
export type EditSsoIdentityProviderMutationVariables = Exact<{
input: EditSsoInput;
}>;
export type EditSsoIdentityProviderMutation = { __typename?: 'Mutation', editSSOIdentityProvider: { __typename?: 'EditSsoOutput', id: string, type: IdpType, issuer: string, name: string, status: SsoIdentityProviderStatus } };
export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key: string]: never; }>;
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 DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
@ -1623,7 +1812,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, 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, 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, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } };
export type ActivateWorkflowVersionMutationVariables = Exact<{
workflowVersionId: Scalars['String'];
@ -1801,6 +1990,18 @@ export const AuthTokensFragmentFragmentDoc = gql`
}
}
${AuthTokenFragmentFragmentDoc}`;
export const AvailableSsoIdentityProvidersFragmentFragmentDoc = gql`
fragment AvailableSSOIdentityProvidersFragment on FindAvailableSSOIDPOutput {
id
issuer
name
status
workspace {
id
displayName
}
}
`;
export const WorkspaceMemberQueryFragmentFragmentDoc = gql`
fragment WorkspaceMemberQueryFragment on WorkspaceMember {
id
@ -1824,6 +2025,7 @@ export const UserQueryFragmentFragmentDoc = gql`
email
canImpersonate
supportUserHash
analyticsTinybirdJwt
onboardingStatus
workspaceMember {
...WorkspaceMemberQueryFragment
@ -1839,6 +2041,7 @@ export const UserQueryFragmentFragmentDoc = gql`
inviteHash
allowImpersonation
activationStatus
isPublicInviteLinkEnabled
featureFlags {
id
key
@ -2235,6 +2438,39 @@ export function useEmailPasswordResetLinkMutation(baseOptions?: Apollo.MutationH
export type EmailPasswordResetLinkMutationHookResult = ReturnType<typeof useEmailPasswordResetLinkMutation>;
export type EmailPasswordResetLinkMutationResult = Apollo.MutationResult<EmailPasswordResetLinkMutation>;
export type EmailPasswordResetLinkMutationOptions = Apollo.BaseMutationOptions<EmailPasswordResetLinkMutation, EmailPasswordResetLinkMutationVariables>;
export const FindAvailableSsoIdentityProvidersDocument = gql`
mutation FindAvailableSSOIdentityProviders($input: FindAvailableSSOIDPInput!) {
findAvailableSSOIdentityProviders(input: $input) {
...AvailableSSOIdentityProvidersFragment
}
}
${AvailableSsoIdentityProvidersFragmentFragmentDoc}`;
export type FindAvailableSsoIdentityProvidersMutationFn = Apollo.MutationFunction<FindAvailableSsoIdentityProvidersMutation, FindAvailableSsoIdentityProvidersMutationVariables>;
/**
* __useFindAvailableSsoIdentityProvidersMutation__
*
* To run a mutation, you first call `useFindAvailableSsoIdentityProvidersMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useFindAvailableSsoIdentityProvidersMutation` 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 [findAvailableSsoIdentityProvidersMutation, { data, loading, error }] = useFindAvailableSsoIdentityProvidersMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useFindAvailableSsoIdentityProvidersMutation(baseOptions?: Apollo.MutationHookOptions<FindAvailableSsoIdentityProvidersMutation, FindAvailableSsoIdentityProvidersMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<FindAvailableSsoIdentityProvidersMutation, FindAvailableSsoIdentityProvidersMutationVariables>(FindAvailableSsoIdentityProvidersDocument, options);
}
export type FindAvailableSsoIdentityProvidersMutationHookResult = ReturnType<typeof useFindAvailableSsoIdentityProvidersMutation>;
export type FindAvailableSsoIdentityProvidersMutationResult = Apollo.MutationResult<FindAvailableSsoIdentityProvidersMutation>;
export type FindAvailableSsoIdentityProvidersMutationOptions = Apollo.BaseMutationOptions<FindAvailableSsoIdentityProvidersMutation, FindAvailableSsoIdentityProvidersMutationVariables>;
export const GenerateApiKeyTokenDocument = gql`
mutation GenerateApiKeyToken($apiKeyId: String!, $expiresAt: String!) {
generateApiKeyToken(apiKeyId: $apiKeyId, expiresAt: $expiresAt) {
@ -2272,12 +2508,26 @@ export type GenerateApiKeyTokenMutationOptions = Apollo.BaseMutationOptions<Gene
export const GenerateJwtDocument = gql`
mutation GenerateJWT($workspaceId: String!) {
generateJWT(workspaceId: $workspaceId) {
tokens {
...AuthTokensFragment
... on GenerateJWTOutputWithAuthTokens {
success
reason
authTokens {
tokens {
...AuthTokensFragment
}
}
}
... on GenerateJWTOutputWithSSOAUTH {
success
reason
availableSSOIDPs {
...AvailableSSOIdentityProvidersFragment
}
}
}
}
${AuthTokensFragmentFragmentDoc}`;
${AuthTokensFragmentFragmentDoc}
${AvailableSsoIdentityProvidersFragmentFragmentDoc}`;
export type GenerateJwtMutationFn = Apollo.MutationFunction<GenerateJwtMutation, GenerateJwtMutationVariables>;
/**
@ -2338,6 +2588,41 @@ export function useGenerateTransientTokenMutation(baseOptions?: Apollo.MutationH
export type GenerateTransientTokenMutationHookResult = ReturnType<typeof useGenerateTransientTokenMutation>;
export type GenerateTransientTokenMutationResult = Apollo.MutationResult<GenerateTransientTokenMutation>;
export type GenerateTransientTokenMutationOptions = Apollo.BaseMutationOptions<GenerateTransientTokenMutation, GenerateTransientTokenMutationVariables>;
export const GetAuthorizationUrlDocument = gql`
mutation GetAuthorizationUrl($input: GetAuthorizationUrlInput!) {
getAuthorizationUrl(input: $input) {
id
type
authorizationURL
}
}
`;
export type GetAuthorizationUrlMutationFn = Apollo.MutationFunction<GetAuthorizationUrlMutation, GetAuthorizationUrlMutationVariables>;
/**
* __useGetAuthorizationUrlMutation__
*
* To run a mutation, you first call `useGetAuthorizationUrlMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useGetAuthorizationUrlMutation` 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 [getAuthorizationUrlMutation, { data, loading, error }] = useGetAuthorizationUrlMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useGetAuthorizationUrlMutation(baseOptions?: Apollo.MutationHookOptions<GetAuthorizationUrlMutation, GetAuthorizationUrlMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<GetAuthorizationUrlMutation, GetAuthorizationUrlMutationVariables>(GetAuthorizationUrlDocument, options);
}
export type GetAuthorizationUrlMutationHookResult = ReturnType<typeof useGetAuthorizationUrlMutation>;
export type GetAuthorizationUrlMutationResult = Apollo.MutationResult<GetAuthorizationUrlMutation>;
export type GetAuthorizationUrlMutationOptions = Apollo.BaseMutationOptions<GetAuthorizationUrlMutation, GetAuthorizationUrlMutationVariables>;
export const ImpersonateDocument = gql`
mutation Impersonate($userId: String!) {
impersonate(userId: $userId) {
@ -2756,6 +3041,7 @@ export const GetClientConfigDocument = gql`
google
password
microsoft
sso
}
billing {
isBillingEnabled
@ -2765,6 +3051,7 @@ export const GetClientConfigDocument = gql`
signInPrefilled
signUpDisabled
debugMode
analyticsEnabled
support {
supportDriver
supportFrontChatId
@ -2844,6 +3131,188 @@ export function useSkipSyncEmailOnboardingStepMutation(baseOptions?: Apollo.Muta
export type SkipSyncEmailOnboardingStepMutationHookResult = ReturnType<typeof useSkipSyncEmailOnboardingStepMutation>;
export type SkipSyncEmailOnboardingStepMutationResult = Apollo.MutationResult<SkipSyncEmailOnboardingStepMutation>;
export type SkipSyncEmailOnboardingStepMutationOptions = Apollo.BaseMutationOptions<SkipSyncEmailOnboardingStepMutation, SkipSyncEmailOnboardingStepMutationVariables>;
export const CreateOidcIdentityProviderDocument = gql`
mutation CreateOIDCIdentityProvider($input: SetupOIDCSsoInput!) {
createOIDCIdentityProvider(input: $input) {
id
type
issuer
name
status
}
}
`;
export type CreateOidcIdentityProviderMutationFn = Apollo.MutationFunction<CreateOidcIdentityProviderMutation, CreateOidcIdentityProviderMutationVariables>;
/**
* __useCreateOidcIdentityProviderMutation__
*
* To run a mutation, you first call `useCreateOidcIdentityProviderMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateOidcIdentityProviderMutation` 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 [createOidcIdentityProviderMutation, { data, loading, error }] = useCreateOidcIdentityProviderMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useCreateOidcIdentityProviderMutation(baseOptions?: Apollo.MutationHookOptions<CreateOidcIdentityProviderMutation, CreateOidcIdentityProviderMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateOidcIdentityProviderMutation, CreateOidcIdentityProviderMutationVariables>(CreateOidcIdentityProviderDocument, options);
}
export type CreateOidcIdentityProviderMutationHookResult = ReturnType<typeof useCreateOidcIdentityProviderMutation>;
export type CreateOidcIdentityProviderMutationResult = Apollo.MutationResult<CreateOidcIdentityProviderMutation>;
export type CreateOidcIdentityProviderMutationOptions = Apollo.BaseMutationOptions<CreateOidcIdentityProviderMutation, CreateOidcIdentityProviderMutationVariables>;
export const CreateSamlIdentityProviderDocument = gql`
mutation CreateSAMLIdentityProvider($input: SetupSAMLSsoInput!) {
createSAMLIdentityProvider(input: $input) {
id
type
issuer
name
status
}
}
`;
export type CreateSamlIdentityProviderMutationFn = Apollo.MutationFunction<CreateSamlIdentityProviderMutation, CreateSamlIdentityProviderMutationVariables>;
/**
* __useCreateSamlIdentityProviderMutation__
*
* To run a mutation, you first call `useCreateSamlIdentityProviderMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateSamlIdentityProviderMutation` 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 [createSamlIdentityProviderMutation, { data, loading, error }] = useCreateSamlIdentityProviderMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useCreateSamlIdentityProviderMutation(baseOptions?: Apollo.MutationHookOptions<CreateSamlIdentityProviderMutation, CreateSamlIdentityProviderMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateSamlIdentityProviderMutation, CreateSamlIdentityProviderMutationVariables>(CreateSamlIdentityProviderDocument, options);
}
export type CreateSamlIdentityProviderMutationHookResult = ReturnType<typeof useCreateSamlIdentityProviderMutation>;
export type CreateSamlIdentityProviderMutationResult = Apollo.MutationResult<CreateSamlIdentityProviderMutation>;
export type CreateSamlIdentityProviderMutationOptions = Apollo.BaseMutationOptions<CreateSamlIdentityProviderMutation, CreateSamlIdentityProviderMutationVariables>;
export const DeleteSsoIdentityProviderDocument = gql`
mutation DeleteSSOIdentityProvider($input: DeleteSsoInput!) {
deleteSSOIdentityProvider(input: $input) {
identityProviderId
}
}
`;
export type DeleteSsoIdentityProviderMutationFn = Apollo.MutationFunction<DeleteSsoIdentityProviderMutation, DeleteSsoIdentityProviderMutationVariables>;
/**
* __useDeleteSsoIdentityProviderMutation__
*
* To run a mutation, you first call `useDeleteSsoIdentityProviderMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteSsoIdentityProviderMutation` 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 [deleteSsoIdentityProviderMutation, { data, loading, error }] = useDeleteSsoIdentityProviderMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useDeleteSsoIdentityProviderMutation(baseOptions?: Apollo.MutationHookOptions<DeleteSsoIdentityProviderMutation, DeleteSsoIdentityProviderMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DeleteSsoIdentityProviderMutation, DeleteSsoIdentityProviderMutationVariables>(DeleteSsoIdentityProviderDocument, options);
}
export type DeleteSsoIdentityProviderMutationHookResult = ReturnType<typeof useDeleteSsoIdentityProviderMutation>;
export type DeleteSsoIdentityProviderMutationResult = Apollo.MutationResult<DeleteSsoIdentityProviderMutation>;
export type DeleteSsoIdentityProviderMutationOptions = Apollo.BaseMutationOptions<DeleteSsoIdentityProviderMutation, DeleteSsoIdentityProviderMutationVariables>;
export const EditSsoIdentityProviderDocument = gql`
mutation EditSSOIdentityProvider($input: EditSsoInput!) {
editSSOIdentityProvider(input: $input) {
id
type
issuer
name
status
}
}
`;
export type EditSsoIdentityProviderMutationFn = Apollo.MutationFunction<EditSsoIdentityProviderMutation, EditSsoIdentityProviderMutationVariables>;
/**
* __useEditSsoIdentityProviderMutation__
*
* To run a mutation, you first call `useEditSsoIdentityProviderMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useEditSsoIdentityProviderMutation` 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 [editSsoIdentityProviderMutation, { data, loading, error }] = useEditSsoIdentityProviderMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useEditSsoIdentityProviderMutation(baseOptions?: Apollo.MutationHookOptions<EditSsoIdentityProviderMutation, EditSsoIdentityProviderMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<EditSsoIdentityProviderMutation, EditSsoIdentityProviderMutationVariables>(EditSsoIdentityProviderDocument, options);
}
export type EditSsoIdentityProviderMutationHookResult = ReturnType<typeof useEditSsoIdentityProviderMutation>;
export type EditSsoIdentityProviderMutationResult = Apollo.MutationResult<EditSsoIdentityProviderMutation>;
export type EditSsoIdentityProviderMutationOptions = Apollo.BaseMutationOptions<EditSsoIdentityProviderMutation, EditSsoIdentityProviderMutationVariables>;
export const ListSsoIdentityProvidersByWorkspaceIdDocument = gql`
query ListSSOIdentityProvidersByWorkspaceId {
listSSOIdentityProvidersByWorkspaceId {
type
id
name
issuer
status
}
}
`;
/**
* __useListSsoIdentityProvidersByWorkspaceIdQuery__
*
* To run a query within a React component, call `useListSsoIdentityProvidersByWorkspaceIdQuery` and pass it any options that fit your needs.
* When your component renders, `useListSsoIdentityProvidersByWorkspaceIdQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useListSsoIdentityProvidersByWorkspaceIdQuery({
* variables: {
* },
* });
*/
export function useListSsoIdentityProvidersByWorkspaceIdQuery(baseOptions?: Apollo.QueryHookOptions<ListSsoIdentityProvidersByWorkspaceIdQuery, ListSsoIdentityProvidersByWorkspaceIdQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ListSsoIdentityProvidersByWorkspaceIdQuery, ListSsoIdentityProvidersByWorkspaceIdQueryVariables>(ListSsoIdentityProvidersByWorkspaceIdDocument, options);
}
export function useListSsoIdentityProvidersByWorkspaceIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ListSsoIdentityProvidersByWorkspaceIdQuery, ListSsoIdentityProvidersByWorkspaceIdQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ListSsoIdentityProvidersByWorkspaceIdQuery, ListSsoIdentityProvidersByWorkspaceIdQueryVariables>(ListSsoIdentityProvidersByWorkspaceIdDocument, options);
}
export type ListSsoIdentityProvidersByWorkspaceIdQueryHookResult = ReturnType<typeof useListSsoIdentityProvidersByWorkspaceIdQuery>;
export type ListSsoIdentityProvidersByWorkspaceIdLazyQueryHookResult = ReturnType<typeof useListSsoIdentityProvidersByWorkspaceIdLazyQuery>;
export type ListSsoIdentityProvidersByWorkspaceIdQueryResult = Apollo.QueryResult<ListSsoIdentityProvidersByWorkspaceIdQuery, ListSsoIdentityProvidersByWorkspaceIdQueryVariables>;
export const DeleteUserAccountDocument = gql`
mutation DeleteUserAccount {
deleteUser {

View File

@ -1,51 +1,97 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { ActionMenuType } from '@/action-menu/types/ActionMenuType';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount';
import { useDeleteTableData } from '@/object-record/record-index/options/hooks/useDeleteTableData';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { IconTrash } from 'twenty-ui';
import { IconTrash, isDefined } from 'twenty-ui';
export const DeleteRecordsActionEffect = ({
position,
objectMetadataItem,
actionMenuType,
}: {
position: number;
objectMetadataItem: ObjectMetadataItem;
actionMenuType: ActionMenuType;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
const contextStoreTargetedRecordIds = useRecoilValue(
contextStoreTargetedRecordIdsState,
);
const contextStoreCurrentObjectMetadataId = useRecoilValue(
contextStoreCurrentObjectMetadataIdState,
);
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: contextStoreCurrentObjectMetadataId,
});
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
useState(false);
const { deleteTableData } = useDeleteTableData({
objectNameSingular: objectMetadataItem?.nameSingular ?? '',
recordIndexId: objectMetadataItem?.namePlural ?? '',
const { resetTableRowSelection } = useRecordTable({
recordTableId: objectMetadataItem.namePlural,
});
const handleDeleteClick = useCallback(() => {
deleteTableData(contextStoreTargetedRecordIds);
}, [deleteTableData, contextStoreTargetedRecordIds]);
const { deleteManyRecords } = useDeleteManyRecords({
objectNameSingular: objectMetadataItem.nameSingular,
});
const isRemoteObject = objectMetadataItem?.isRemote ?? false;
const { favorites, deleteFavorite } = useFavorites();
const numberOfSelectedRecords = contextStoreTargetedRecordIds.length;
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
);
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
const graphqlFilter = computeContextStoreFilters(
contextStoreTargetedRecordsRule,
objectMetadataItem,
);
const { fetchAllRecordIds } = useFetchAllRecordIds({
objectNameSingular: objectMetadataItem.nameSingular,
filter: graphqlFilter,
});
const { closeRightDrawer } = useRightDrawer();
const handleDeleteClick = useCallback(async () => {
const recordIdsToDelete = await fetchAllRecordIds();
resetTableRowSelection();
for (const recordIdToDelete of recordIdsToDelete) {
const foundFavorite = favorites?.find(
(favorite) => favorite.recordId === recordIdToDelete,
);
if (foundFavorite !== undefined) {
deleteFavorite(foundFavorite.id);
}
}
await deleteManyRecords(recordIdsToDelete, {
delayInMsBetweenRequests: 50,
});
}, [
deleteFavorite,
deleteManyRecords,
favorites,
fetchAllRecordIds,
resetTableRowSelection,
]);
const isRemoteObject = objectMetadataItem.isRemote;
const canDelete =
!isRemoteObject && numberOfSelectedRecords < DELETE_MAX_COUNT;
!isRemoteObject &&
isDefined(contextStoreNumberOfSelectedRecords) &&
contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT &&
contextStoreNumberOfSelectedRecords > 0;
useEffect(() => {
if (canDelete) {
@ -62,32 +108,49 @@ export const DeleteRecordsActionEffect = ({
<ConfirmationModal
isOpen={isDeleteRecordsModalOpen}
setIsOpen={setIsDeleteRecordsModalOpen}
title={`Delete ${numberOfSelectedRecords} ${
numberOfSelectedRecords === 1 ? `record` : 'records'
title={`Delete ${contextStoreNumberOfSelectedRecords} ${
contextStoreNumberOfSelectedRecords === 1 ? `record` : 'records'
}`}
subtitle={`Are you sure you want to delete ${
numberOfSelectedRecords === 1 ? 'this record' : 'these records'
contextStoreNumberOfSelectedRecords === 1
? 'this record'
: 'these records'
}? ${
numberOfSelectedRecords === 1 ? 'It' : 'They'
contextStoreNumberOfSelectedRecords === 1 ? 'It' : 'They'
} can be recovered from the Options menu.`}
onConfirmClick={() => handleDeleteClick()}
onConfirmClick={() => {
handleDeleteClick();
if (actionMenuType === 'recordShow') {
closeRightDrawer();
}
}}
deleteButtonText={`Delete ${
numberOfSelectedRecords > 1 ? 'Records' : 'Record'
contextStoreNumberOfSelectedRecords > 1 ? 'Records' : 'Record'
}`}
modalVariant={
actionMenuType === 'recordShow' ? 'tertiary' : 'primary'
}
/>
),
});
} else {
removeActionMenuEntry('delete');
}
return () => {
removeActionMenuEntry('delete');
};
}, [
canDelete,
actionMenuType,
addActionMenuEntry,
removeActionMenuEntry,
isDeleteRecordsModalOpen,
numberOfSelectedRecords,
canDelete,
closeRightDrawer,
contextStoreNumberOfSelectedRecords,
handleDeleteClick,
isDeleteRecordsModalOpen,
position,
removeActionMenuEntry,
]);
return null;

View File

@ -1,38 +1,27 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import {
displayedExportProgress,
useExportTableData,
} from '@/object-record/record-index/options/hooks/useExportTableData';
useExportRecordData,
} from '@/action-menu/hooks/useExportRecordData';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { IconFileExport } from 'twenty-ui';
export const ExportRecordsActionEffect = ({
position,
objectMetadataItem,
}: {
position: number;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
const contextStoreCurrentObjectMetadataId = useRecoilValue(
contextStoreCurrentObjectMetadataIdState,
);
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: contextStoreCurrentObjectMetadataId,
});
const baseTableDataParams = {
const { progress, download } = useExportRecordData({
delayMs: 100,
objectNameSingular: objectMetadataItem?.nameSingular ?? '',
recordIndexId: objectMetadataItem?.namePlural ?? '',
};
const { progress, download } = useExportTableData({
...baseTableDataParams,
filename: `${objectMetadataItem?.nameSingular}.csv`,
objectMetadataItem,
recordIndexId: objectMetadataItem.namePlural,
filename: `${objectMetadataItem.nameSingular}.csv`,
});
useEffect(() => {

View File

@ -1,39 +1,37 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { IconHeart, IconHeartOff, isDefined } from 'twenty-ui';
export const ManageFavoritesActionEffect = ({
position,
objectMetadataItem,
}: {
position: number;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
const contextStoreTargetedRecordIds = useRecoilValue(
contextStoreTargetedRecordIdsState,
);
const contextStoreCurrentObjectMetadataId = useRecoilValue(
contextStoreCurrentObjectMetadataIdState,
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
const { favorites, createFavorite, deleteFavorite } = useFavorites();
const selectedRecordId = contextStoreTargetedRecordIds[0];
const selectedRecordId =
contextStoreTargetedRecordsRule.mode === 'selection'
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
: undefined;
const selectedRecord = useRecoilValue(
recordStoreFamilyState(selectedRecordId),
recordStoreFamilyState(selectedRecordId ?? ''),
);
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: contextStoreCurrentObjectMetadataId,
});
const foundFavorite = favorites?.find(
(favorite) => favorite.recordId === selectedRecordId,
);

View File

@ -1,13 +1,26 @@
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 = () => {
export const MultipleRecordsActionMenuEntriesSetter = ({
objectMetadataItem,
actionMenuType,
}: {
objectMetadataItem: ObjectMetadataItem;
actionMenuType: ActionMenuType;
}) => {
return (
<>
{actionEffects.map((ActionEffect, index) => (
<ActionEffect key={index} position={index} />
<ActionEffect
key={index}
position={index}
objectMetadataItem={objectMetadataItem}
actionMenuType={actionMenuType}
/>
))}
</>
);

View File

@ -1,20 +1,51 @@
import { MultipleRecordsActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter';
import { SingleRecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { useRecoilValue } from 'recoil';
import { ActionMenuType } from '@/action-menu/types/ActionMenuType';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordActionMenuEntriesSetter = () => {
const contextStoreTargetedRecordIds = useRecoilValue(
contextStoreTargetedRecordIdsState,
export const RecordActionMenuEntriesSetter = ({
actionMenuType,
}: {
actionMenuType: ActionMenuType;
}) => {
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
);
if (contextStoreTargetedRecordIds.length === 0) {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState,
);
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: contextStoreCurrentObjectMetadataId ?? '',
});
if (!objectMetadataItem) {
throw new Error(
`Object metadata item not found for id ${contextStoreCurrentObjectMetadataId}`,
);
}
if (!contextStoreNumberOfSelectedRecords) {
return null;
}
if (contextStoreTargetedRecordIds.length === 1) {
return <SingleRecordActionMenuEntriesSetter />;
if (contextStoreNumberOfSelectedRecords === 1) {
return (
<SingleRecordActionMenuEntriesSetter
objectMetadataItem={objectMetadataItem}
actionMenuType={actionMenuType}
/>
);
}
return <MultipleRecordsActionMenuEntriesSetter />;
return (
<MultipleRecordsActionMenuEntriesSetter
objectMetadataItem={objectMetadataItem}
actionMenuType={actionMenuType}
/>
);
};

View File

@ -1,17 +1,30 @@
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 = () => {
export const SingleRecordActionMenuEntriesSetter = ({
objectMetadataItem,
actionMenuType,
}: {
objectMetadataItem: ObjectMetadataItem;
actionMenuType: ActionMenuType;
}) => {
const actionEffects = [
ManageFavoritesActionEffect,
ExportRecordsActionEffect,
DeleteRecordsActionEffect,
ManageFavoritesActionEffect,
];
return (
<>
{actionEffects.map((ActionEffect, index) => (
<ActionEffect key={index} position={index} />
<ActionEffect
key={index}
position={index}
objectMetadataItem={objectMetadataItem}
actionMenuType={actionMenuType}
/>
))}
</>
);

View File

@ -0,0 +1,35 @@
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordIndexActionMenu = ({
actionMenuId,
}: {
actionMenuId: string;
}) => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState,
);
return (
<>
{contextStoreCurrentObjectMetadataId && (
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: actionMenuId }}
>
<RecordIndexActionMenuBar />
<RecordIndexActionMenuDropdown />
<ActionMenuConfirmationModals />
<RecordIndexActionMenuEffect />
<RecordActionMenuEntriesSetter actionMenuType="recordIndex" />
</ActionMenuComponentInstanceContext.Provider>
)}
</>
);
};

View File

@ -1,14 +1,13 @@
import styled from '@emotion/styled';
import { ActionMenuBarEntry } from '@/action-menu/components/ActionMenuBarEntry';
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useRecoilValue } from 'recoil';
const StyledLabel = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
@ -18,9 +17,9 @@ const StyledLabel = styled.div`
padding-right: ${({ theme }) => theme.spacing(2)};
`;
export const ActionMenuBar = () => {
const contextStoreTargetedRecordIds = useRecoilValue(
contextStoreTargetedRecordIdsState,
export const RecordIndexActionMenuBar = () => {
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
);
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
@ -42,11 +41,9 @@ export const ActionMenuBar = () => {
scope: ActionBarHotkeyScope.ActionBar,
}}
>
<StyledLabel>
{contextStoreTargetedRecordIds.length} selected:
</StyledLabel>
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
{actionMenuEntries.map((entry, index) => (
<ActionMenuBarEntry key={index} entry={entry} />
<RecordIndexActionMenuBarEntry key={index} entry={entry} />
))}
</BottomBar>
);

View File

@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
type ActionMenuBarEntryProps = {
type RecordIndexActionMenuBarEntryProps = {
entry: ActionMenuEntry;
};
@ -35,7 +35,9 @@ const StyledButtonLabel = styled.div`
margin-left: ${({ theme }) => theme.spacing(1)};
`;
export const ActionMenuBarEntry = ({ entry }: ActionMenuBarEntryProps) => {
export const RecordIndexActionMenuBarEntry = ({
entry,
}: RecordIndexActionMenuBarEntryProps) => {
const theme = useTheme();
return (
<StyledButton

View File

@ -3,11 +3,12 @@ import { useRecoilValue } from 'recoil';
import { PositionType } from '../types/PositionType';
import { actionMenuDropdownPositionComponentState } from '@/action-menu/states/actionMenuDropdownPositionComponentState';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -34,7 +35,7 @@ const StyledContainerActionMenuDropdown = styled.div<StyledContainerProps>`
width: auto;
`;
export const ActionMenuDropdown = () => {
export const RecordIndexActionMenuDropdown = () => {
const actionMenuEntries = useRecoilComponentValueV2(
actionMenuEntriesComponentSelector,
);
@ -45,7 +46,7 @@ export const ActionMenuDropdown = () => {
const actionMenuDropdownPosition = useRecoilValue(
extractComponentState(
actionMenuDropdownPositionComponentState,
recordIndexActionMenuDropdownPositionComponentState,
`action-menu-dropdown-${actionMenuId}`,
),
);
@ -64,7 +65,7 @@ export const ActionMenuDropdown = () => {
return (
<StyledContainerActionMenuDropdown
position={actionMenuDropdownPosition}
className="context-menu"
className="action-menu-dropdown"
>
<Dropdown
dropdownId={`action-menu-dropdown-${actionMenuId}`}
@ -73,15 +74,19 @@ export const ActionMenuDropdown = () => {
}}
data-select-disable
dropdownMenuWidth={width}
dropdownComponents={actionMenuEntries.map((item, index) => (
<MenuItem
key={index}
LeftIcon={item.Icon}
onClick={item.onClick}
accent={item.accent}
text={item.label}
/>
))}
dropdownComponents={
<DropdownMenuItemsContainer>
{actionMenuEntries.map((item, index) => (
<MenuItem
key={index}
LeftIcon={item.Icon}
onClick={item.onClick}
accent={item.accent}
text={item.label}
/>
))}
</DropdownMenuItemsContainer>
}
/>
</StyledContainerActionMenuDropdown>
);

View File

@ -1,15 +1,16 @@
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
export const ActionMenuEffect = () => {
const contextStoreTargetedRecordIds = useRecoilValue(
contextStoreTargetedRecordIdsState,
export const RecordIndexActionMenuEffect = () => {
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
);
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
@ -26,17 +27,17 @@ export const ActionMenuEffect = () => {
);
useEffect(() => {
if (contextStoreTargetedRecordIds.length > 0 && !isDropdownOpen) {
if (contextStoreNumberOfSelectedRecords > 0 && !isDropdownOpen) {
// We only handle opening the ActionMenuBar here, not the Dropdown.
// The Dropdown is already managed by sync handlers for events like
// right-click to open and click outside to close.
openActionBar();
}
if (contextStoreTargetedRecordIds.length === 0) {
if (contextStoreNumberOfSelectedRecords === 0 && isDropdownOpen) {
closeActionBar();
}
}, [
contextStoreTargetedRecordIds,
contextStoreNumberOfSelectedRecords,
openActionBar,
closeActionBar,
isDropdownOpen,

View File

@ -0,0 +1,31 @@
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
import { RecordShowActionMenuBar } from '@/action-menu/components/RecordShowActionMenuBar';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordShowActionMenu = ({
actionMenuId,
}: {
actionMenuId: string;
}) => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState,
);
return (
<>
{contextStoreCurrentObjectMetadataId && (
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: actionMenuId }}
>
<RecordShowActionMenuBar />
<ActionMenuConfirmationModals />
<RecordActionMenuEntriesSetter actionMenuType="recordShow" />
</ActionMenuComponentInstanceContext.Provider>
)}
</>
);
};

View File

@ -0,0 +1,16 @@
import { RecordShowActionMenuBarEntry } from '@/action-menu/components/RecordShowActionMenuBarEntry';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordShowActionMenuBar = () => {
const actionMenuEntries = useRecoilComponentValueV2(
actionMenuEntriesComponentSelector,
);
return (
<>
{actionMenuEntries.map((actionMenuEntry) => (
<RecordShowActionMenuBarEntry entry={actionMenuEntry} />
))}
</>
);
};

View File

@ -0,0 +1,53 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
type RecordShowActionMenuBarEntryProps = {
entry: ActionMenuEntry;
};
const StyledButton = styled.div<{ accent: MenuItemAccent }>`
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${(props) =>
props.accent === 'danger'
? props.theme.color.red
: props.theme.font.color.secondary};
cursor: pointer;
display: flex;
justify-content: center;
padding: ${({ theme }) => theme.spacing(2)};
transition: background 0.1s ease;
user-select: none;
&:hover {
background: ${({ theme, accent }) =>
accent === 'danger'
? theme.background.danger
: theme.background.tertiary};
}
`;
const StyledButtonLabel = styled.div`
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-left: ${({ theme }) => theme.spacing(1)};
`;
// For now, this component is the same as RecordIndexActionMenuBarEntry but they
// will probably diverge in the future
export const RecordShowActionMenuBarEntry = ({
entry,
}: RecordShowActionMenuBarEntryProps) => {
const theme = useTheme();
return (
<StyledButton
accent={entry.accent ?? 'default'}
onClick={() => entry.onClick?.()}
>
{entry.Icon && <entry.Icon size={theme.icon.size.md} />}
<StyledButtonLabel>{entry.label}</StyledButtonLabel>
</StyledButton>
);
};

View File

@ -1,111 +0,0 @@
import { expect, jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { RecoilRoot } from 'recoil';
import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
import { userEvent, waitFor, within } from '@storybook/test';
import { IconCheckbox, IconTrash } from 'twenty-ui';
const deleteMock = jest.fn();
const markAsDoneMock = jest.fn();
const meta: Meta<typeof ActionMenuBar> = {
title: 'Modules/ActionMenu/ActionMenuBar',
component: ActionMenuBar,
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(contextStoreTargetedRecordIdsState, ['1', '2', '3']);
set(
actionMenuEntriesComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
new Map([
[
'delete',
{
key: 'delete',
label: 'Delete',
position: 0,
Icon: IconTrash,
onClick: deleteMock,
},
],
[
'markAsDone',
{
key: 'markAsDone',
label: 'Mark as done',
position: 1,
Icon: IconCheckbox,
onClick: markAsDoneMock,
},
],
]),
);
set(
isBottomBarOpenedComponentState.atomFamily({
instanceId: 'action-bar-story-action-menu',
}),
true,
);
}}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<Story />
</ActionMenuComponentInstanceContext.Provider>
</RecoilRoot>
),
],
args: {
actionMenuId: 'story-action-menu',
},
};
export default meta;
type Story = StoryObj<typeof ActionMenuBar>;
export const Default: Story = {
args: {
actionMenuId: 'story-action-menu',
},
};
export const WithCustomSelection: Story = {
args: {
actionMenuId: 'story-action-menu',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const selectionText = await canvas.findByText('3 selected:');
expect(selectionText).toBeInTheDocument();
},
};
export const WithButtonClicks: Story = {
args: {
actionMenuId: 'story-action-menu',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const deleteButton = await canvas.findByText('Delete');
await userEvent.click(deleteButton);
const markAsDoneButton = await canvas.findByText('Mark as done');
await userEvent.click(markAsDoneButton);
await waitFor(() => {
expect(deleteMock).toHaveBeenCalled();
expect(markAsDoneMock).toHaveBeenCalled();
});
},
};

View File

@ -0,0 +1,131 @@
import { expect, jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { RecoilRoot } from 'recoil';
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
import { userEvent, waitFor, within } from '@storybook/test';
import { IconCheckbox, IconTrash } from 'twenty-ui';
const deleteMock = jest.fn();
const markAsDoneMock = jest.fn();
const meta: Meta<typeof RecordIndexActionMenuBar> = {
title: 'Modules/ActionMenu/RecordIndexActionMenuBar',
component: RecordIndexActionMenuBar,
decorators: [
(Story) => (
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<RecoilRoot
initializeState={({ set }) => {
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
{
mode: 'selection',
selectedRecordIds: ['1', '2', '3'],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
3,
);
set(
actionMenuEntriesComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
new Map([
[
'delete',
{
key: 'delete',
label: 'Delete',
position: 0,
Icon: IconTrash,
onClick: deleteMock,
},
],
[
'markAsDone',
{
key: 'markAsDone',
label: 'Mark as done',
position: 1,
Icon: IconCheckbox,
onClick: markAsDoneMock,
},
],
]),
);
set(
isBottomBarOpenedComponentState.atomFamily({
instanceId: 'action-bar-story-action-menu',
}),
true,
);
}}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<Story />
</ActionMenuComponentInstanceContext.Provider>
</RecoilRoot>
</ContextStoreComponentInstanceContext.Provider>
),
],
args: {
actionMenuId: 'story-action-menu',
},
};
export default meta;
type Story = StoryObj<typeof RecordIndexActionMenuBar>;
export const Default: Story = {
args: {
actionMenuId: 'story-action-menu',
},
};
export const WithCustomSelection: Story = {
args: {
actionMenuId: 'story-action-menu',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const selectionText = await canvas.findByText('3 selected:');
expect(selectionText).toBeInTheDocument();
},
};
export const WithButtonClicks: Story = {
args: {
actionMenuId: 'story-action-menu',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const deleteButton = await canvas.findByText('Delete');
await userEvent.click(deleteButton);
const markAsDoneButton = await canvas.findByText('Mark as done');
await userEvent.click(markAsDoneButton);
await waitFor(() => {
expect(deleteMock).toHaveBeenCalled();
expect(markAsDoneMock).toHaveBeenCalled();
});
},
};

View File

@ -1,19 +1,19 @@
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
import { expect, jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { ComponentDecorator, IconCheckbox, IconTrash } from 'twenty-ui';
import { ActionMenuBarEntry } from '../ActionMenuBarEntry';
const meta: Meta<typeof ActionMenuBarEntry> = {
title: 'Modules/ActionMenu/ActionMenuBarEntry',
component: ActionMenuBarEntry,
const meta: Meta<typeof RecordIndexActionMenuBarEntry> = {
title: 'Modules/ActionMenu/RecordIndexActionMenuBarEntry',
component: RecordIndexActionMenuBarEntry,
decorators: [ComponentDecorator],
};
export default meta;
type Story = StoryObj<typeof ActionMenuBarEntry>;
type Story = StoryObj<typeof RecordIndexActionMenuBarEntry>;
const deleteMock = jest.fn();
const markAsDoneMock = jest.fn();

View File

@ -3,10 +3,10 @@ import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { RecoilRoot } from 'recoil';
import { ActionMenuDropdown } from '@/action-menu/components/ActionMenuDropdown';
import { actionMenuDropdownPositionComponentState } from '@/action-menu/states/actionMenuDropdownPositionComponentState';
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { IconCheckbox, IconHeart, IconTrash } from 'twenty-ui';
@ -15,16 +15,16 @@ const deleteMock = jest.fn();
const markAsDoneMock = jest.fn();
const addToFavoritesMock = jest.fn();
const meta: Meta<typeof ActionMenuDropdown> = {
title: 'Modules/ActionMenu/ActionMenuDropdown',
component: ActionMenuDropdown,
const meta: Meta<typeof RecordIndexActionMenuDropdown> = {
title: 'Modules/ActionMenu/RecordIndexActionMenuDropdown',
component: RecordIndexActionMenuDropdown,
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(
extractComponentState(
actionMenuDropdownPositionComponentState,
recordIndexActionMenuDropdownPositionComponentState,
'action-menu-dropdown-story',
),
{ x: 10, y: 10 },
@ -87,7 +87,7 @@ const meta: Meta<typeof ActionMenuDropdown> = {
export default meta;
type Story = StoryObj<typeof ActionMenuDropdown>;
type Story = StoryObj<typeof RecordIndexActionMenuDropdown>;
export const Default: Story = {
args: {

View File

@ -0,0 +1,131 @@
import { expect, jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { RecoilRoot } from 'recoil';
import { RecordShowActionMenuBar } from '@/action-menu/components/RecordShowActionMenuBar';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
import { userEvent, waitFor, within } from '@storybook/test';
import {
ComponentDecorator,
IconFileExport,
IconHeart,
IconTrash,
} from 'twenty-ui';
const deleteMock = jest.fn();
const addToFavoritesMock = jest.fn();
const exportMock = jest.fn();
const meta: Meta<typeof RecordShowActionMenuBar> = {
title: 'Modules/ActionMenu/RecordShowActionMenuBar',
component: RecordShowActionMenuBar,
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
{
mode: 'selection',
selectedRecordIds: ['1'],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
1,
);
set(
actionMenuEntriesComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
new Map([
[
'addToFavorites',
{
key: 'addToFavorites',
label: 'Add to favorites',
position: 0,
Icon: IconHeart,
onClick: addToFavoritesMock,
},
],
[
'export',
{
key: 'export',
label: 'Export',
position: 1,
Icon: IconFileExport,
onClick: exportMock,
},
],
[
'delete',
{
key: 'delete',
label: 'Delete',
position: 2,
Icon: IconTrash,
onClick: deleteMock,
accent: 'danger' as MenuItemAccent,
},
],
]),
);
}}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<Story />
</ActionMenuComponentInstanceContext.Provider>
</RecoilRoot>
),
ComponentDecorator,
],
args: {
actionMenuId: 'story-action-menu',
},
};
export default meta;
type Story = StoryObj<typeof RecordShowActionMenuBar>;
export const Default: Story = {
args: {
actionMenuId: 'story-action-menu',
},
};
export const WithButtonClicks: Story = {
args: {
actionMenuId: 'story-action-menu',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const deleteButton = await canvas.findByText('Delete');
await userEvent.click(deleteButton);
const addToFavoritesButton = await canvas.findByText('Add to favorites');
await userEvent.click(addToFavoritesButton);
const exportButton = await canvas.findByText('Export');
await userEvent.click(exportButton);
await waitFor(() => {
expect(deleteMock).toHaveBeenCalled();
expect(addToFavoritesMock).toHaveBeenCalled();
expect(exportMock).toHaveBeenCalled();
});
},
};

View File

@ -7,7 +7,7 @@ import {
displayedExportProgress,
download,
generateCsv,
} from '../useExportTableData';
} from '../useExportRecordData';
jest.useFakeTimers();

View File

@ -4,10 +4,11 @@ import { useMemo } from 'react';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/record-index/options/constants/ExportTableDataDefaultPageSize';
import { useProcessRecordsForCSVExport } from '@/object-record/record-index/options/hooks/useProcessRecordsForCSVExport';
import {
useTableData,
UseTableDataOptions,
} from '@/object-record/record-index/options/hooks/useTableData';
UseRecordDataOptions,
useRecordData,
} from '@/object-record/record-index/options/hooks/useRecordData';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { RelationDefinitionType } from '~/generated-metadata/graphql';
@ -134,21 +135,22 @@ const downloader = (mimeType: string, generator: GenerateExport) => {
export const csvDownloader = downloader('text/csv', generateCsv);
type UseExportTableDataOptions = Omit<UseTableDataOptions, 'callback'> & {
type UseExportTableDataOptions = Omit<UseRecordDataOptions, 'callback'> & {
filename: string;
};
export const useExportTableData = ({
export const useExportRecordData = ({
delayMs,
filename,
maximumRequests = 100,
objectNameSingular,
objectMetadataItem,
pageSize = EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE,
recordIndexId,
viewType,
}: UseExportTableDataOptions) => {
const { processRecordsForCSVExport } =
useProcessRecordsForCSVExport(objectNameSingular);
const { processRecordsForCSVExport } = useProcessRecordsForCSVExport(
objectMetadataItem.nameSingular,
);
const downloadCsv = useMemo(
() =>
@ -160,10 +162,10 @@ export const useExportTableData = ({
[filename, processRecordsForCSVExport],
);
const { getTableData: download, progress } = useTableData({
const { getTableData: download, progress } = useRecordData({
delayMs,
maximumRequests,
objectNameSingular,
objectMetadataItem,
pageSize,
recordIndexId,
callback: downloadCsv,

View File

@ -1,9 +1,9 @@
import { PositionType } from '@/action-menu/types/PositionType';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const actionMenuDropdownPositionComponentState =
export const recordIndexActionMenuDropdownPositionComponentState =
createComponentState<PositionType>({
key: 'actionMenuDropdownPositionComponentState',
key: 'recordIndexActionMenuDropdownPositionComponentState',
defaultValue: {
x: null,
y: null,

View File

@ -0,0 +1 @@
export type ActionMenuType = 'recordIndex' | 'recordShow';

View File

@ -3,14 +3,14 @@ import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { ChangeEvent, useRef } from 'react';
import { Button } from '@/ui/input/button/components/Button';
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
import { Button } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { AttachmentIcon } from '../files/components/AttachmentIcon';
import { AttachmentType } from '../files/types/Attachment';
import { getFileType } from '../files/utils/getFileType';
import { AttachmentIcon } from '../../files/components/AttachmentIcon';
import { AttachmentType } from '../../files/types/Attachment';
import { getFileType } from '../../files/utils/getFileType';
const StyledFileInput = styled.input`
display: none;

View File

@ -1,8 +1,8 @@
import { BlockNoteSchema, defaultBlockSpecs } from '@blocknote/core';
import { FileBlock } from './FileBlock';
import { FileBlock } from '../components/FileBlock';
export const blockSchema = BlockNoteSchema.create({
export const BLOCK_SCHEMA = BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
file: FileBlock,

View File

@ -18,7 +18,7 @@ import {
import { SuggestionItem } from '@/ui/input/editor/components/CustomSlashMenu';
import { blockSchema } from './schema';
import { BLOCK_SCHEMA } from '../constants/Schema';
const Icons: Record<string, IconComponent> = {
'Heading 1': IconH1,
@ -35,7 +35,7 @@ const Icons: Record<string, IconComponent> = {
Emoji: IconMoodSmile,
};
export const getSlashMenu = (editor: typeof blockSchema.BlockNoteEditor) => {
export const getSlashMenu = (editor: typeof BLOCK_SCHEMA.BlockNoteEditor) => {
const items: SuggestionItem[] = [
...getDefaultReactSlashMenuItems(editor).map((x) => ({
...x,

View File

@ -1,26 +1,26 @@
import styled from '@emotion/styled';
import { format, getYear } from 'date-fns';
import { H3Title } from 'twenty-ui';
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from '@/activities/calendar/constants/Calendar';
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents';
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId';
import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromPersonId';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
H3Title,
} from 'twenty-ui';
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from '@/activities/calendar/constants/Calendar';
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromCompanyId';
import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromPersonId';
import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { Section } from '@/ui/layout/section/components/Section';
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';

View File

@ -1,10 +1,10 @@
import { getOperationName } from '@apollo/client/utilities';
import { Meta, StoryObj } from '@storybook/react';
import { graphql, HttpResponse } from 'msw';
import { HttpResponse, graphql } from 'msw';
import { ComponentDecorator } from 'twenty-ui';
import { Calendar } from '@/activities/calendar/components/Calendar';
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId';
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromCompanyId';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';

View File

@ -1,7 +1,6 @@
import { timelineCalendarEventParticipantFragment } from '@/activities/calendar/graphql/queries/fragments/timelineCalendarEventParticipantFragment';
import { gql } from '@apollo/client';
import { timelineCalendarEventParticipantFragment } from '@/activities/calendar/queries/fragments/timelineCalendarEventParticipantFragment';
export const timelineCalendarEventFragment = gql`
fragment TimelineCalendarEventFragment on TimelineCalendarEvent {
id

View File

@ -1,7 +1,6 @@
import { timelineCalendarEventFragment } from '@/activities/calendar/graphql/queries/fragments/timelineCalendarEventFragment';
import { gql } from '@apollo/client';
import { timelineCalendarEventFragment } from '@/activities/calendar/queries/fragments/timelineCalendarEventFragment';
export const timelineCalendarEventWithTotalFragment = gql`
fragment TimelineCalendarEventsWithTotalFragment on TimelineCalendarEventsWithTotal {
totalNumberOfCalendarEvents

View File

@ -1,7 +1,6 @@
import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/graphql/queries/fragments/timelineCalendarEventWithTotalFragment';
import { gql } from '@apollo/client';
import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/queries/fragments/timelineCalendarEventWithTotalFragment';
export const getTimelineCalendarEventsFromCompanyId = gql`
query GetTimelineCalendarEventsFromCompanyId(
$companyId: UUID!

View File

@ -1,7 +1,6 @@
import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/graphql/queries/fragments/timelineCalendarEventWithTotalFragment';
import { gql } from '@apollo/client';
import { timelineCalendarEventWithTotalFragment } from '@/activities/calendar/queries/fragments/timelineCalendarEventWithTotalFragment';
export const getTimelineCalendarEventsFromPersonId = gql`
query GetTimelineCalendarEventsFromPersonId(
$personId: UUID!

View File

@ -7,7 +7,6 @@ import { Key } from 'ts-key-enum';
import { useDebouncedCallback } from 'use-debounce';
import { v4 } from 'uuid';
import { blockSchema } from '@/activities/blocks/schema';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { activityBodyFamilyState } from '@/activities/states/activityBodyFamilyState';
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
@ -27,6 +26,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { getFileType } from '../files/utils/getFileType';
import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
import { Note } from '@/activities/types/Note';
import { Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@ -287,7 +287,7 @@ export const RichTextEditor = ({
const editor = useCreateBlockNote({
initialContent: initialBody,
domAttributes: { editor: { class: 'editor' } },
schema: blockSchema,
schema: BLOCK_SCHEMA,
uploadFile: handleUploadAttachment,
});

View File

@ -1,10 +1,10 @@
import { Loader } from '@/ui/feedback/loader/components/Loader';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
} from 'twenty-ui';
export const EmailLoader = ({ loadingText }: { loadingText?: string }) => (
<AnimatedPlaceholderEmptyContainer>

View File

@ -1,7 +1,5 @@
import styled from '@emotion/styled';
import { IconArrowBackUp, IconUserCircle } from 'twenty-ui';
import { Button } from '@/ui/input/button/components/Button';
import { Button, IconArrowBackUp, IconUserCircle } from 'twenty-ui';
const StyledThreadBottomBar = styled.div`
align-items: center;

View File

@ -1,8 +1,6 @@
import React from 'react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
import { AnimatedEaseInOut } from 'twenty-ui';
const StyledThreadMessageBody = styled(motion.div)`
color: ${({ theme }) => theme.font.color.primary};

View File

@ -1,24 +1,25 @@
import styled from '@emotion/styled';
import { H1Title, H1TitleFontColor } from 'twenty-ui';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
H1Title,
H1TitleFontColor,
} from 'twenty-ui';
import { ActivityList } from '@/activities/components/ActivityList';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging';
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
import { getTimelineThreadsFromPersonId } from '@/activities/emails/queries/getTimelineThreadsFromPersonId';
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromCompanyId';
import { getTimelineThreadsFromPersonId } from '@/activities/emails/graphql/queries/getTimelineThreadsFromPersonId';
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { Section } from '@/ui/layout/section/components/Section';
import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql';

View File

@ -1,6 +1,6 @@
import { gql } from '@apollo/client';
import { participantFragment } from '@/activities/emails/queries/fragments/participantFragment';
import { participantFragment } from '@/activities/emails/graphql/queries/fragments/participantFragment';
export const timelineThreadFragment = gql`
fragment TimelineThreadFragment on TimelineThread {

View File

@ -1,7 +1,6 @@
import { timelineThreadFragment } from '@/activities/emails/graphql/queries/fragments/timelineThreadFragment';
import { gql } from '@apollo/client';
import { timelineThreadFragment } from '@/activities/emails/queries/fragments/timelineThreadFragment';
export const timelineThreadWithTotalFragment = gql`
fragment TimelineThreadsWithTotalFragment on TimelineThreadsWithTotal {
totalNumberOfThreads

View File

@ -1,7 +1,6 @@
import { timelineThreadWithTotalFragment } from '@/activities/emails/graphql/queries/fragments/timelineThreadWithTotalFragment';
import { gql } from '@apollo/client';
import { timelineThreadWithTotalFragment } from '@/activities/emails/queries/fragments/timelineThreadWithTotalFragment';
export const getTimelineThreadsFromCompanyId = gql`
query GetTimelineThreadsFromCompanyId(
$companyId: UUID!

View File

@ -1,6 +1,6 @@
import { gql } from '@apollo/client';
import { timelineThreadWithTotalFragment } from '@/activities/emails/queries/fragments/timelineThreadWithTotalFragment';
import { timelineThreadWithTotalFragment } from '@/activities/emails/graphql/queries/fragments/timelineThreadWithTotalFragment';
export const getTimelineThreadsFromPersonId = gql`
query GetTimelineThreadsFromPersonId(

View File

@ -1,10 +1,9 @@
import styled from '@emotion/styled';
import { useState } from 'react';
import { IconArrowsVertical } from 'twenty-ui';
import { Button, IconArrowsVertical } from 'twenty-ui';
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
import { EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender';
import { Button } from '@/ui/input/button/components/Button';
const StyledButtonContainer = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};

View File

@ -9,12 +9,11 @@ import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMe
import { IntermediaryMessages } from '@/activities/emails/right-drawer/components/IntermediaryMessages';
import { useRightDrawerEmailThread } from '@/activities/emails/right-drawer/hooks/useRightDrawerEmailThread';
import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState';
import { Button } from '@/ui/input/button/components/Button';
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
import { messageThreadState } from '@/ui/layout/right-drawer/states/messageThreadState';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { IconArrowBackUp } from 'twenty-ui';
import { Button, IconArrowBackUp } from 'twenty-ui';
const StyledWrapper = styled.div`
display: flex;

View File

@ -3,9 +3,9 @@ import {
IconDownload,
IconPencil,
IconTrash,
LightIconButton,
} from 'twenty-ui';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';

View File

@ -1,6 +1,15 @@
import styled from '@emotion/styled';
import { ChangeEvent, useRef, useState } from 'react';
import { IconPlus } from 'twenty-ui';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
Button,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
IconPlus,
} from 'twenty-ui';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { AttachmentList } from '@/activities/files/components/AttachmentList';
@ -8,15 +17,6 @@ import { DropZone } from '@/activities/files/components/DropZone';
import { useAttachments } from '@/activities/files/hooks/useAttachments';
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { Button } from '@/ui/input/button/components/Button';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { isDefined } from '~/utils/isDefined';
const StyledAttachmentsContainer = styled.div`

View File

@ -1,7 +1,7 @@
import { useRecoilValue } from 'recoil';
import { usePrepareFindManyActivitiesQuery } from '@/activities/hooks/usePrepareFindManyActivitiesQuery';
import { objectShowPageTargetableObjectState } from '@/activities/timelineActivities/states/objectShowPageTargetableObjectIdState';
import { objectShowPageTargetableObjectState } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectIdState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { isDefined } from '~/utils/isDefined';

View File

@ -4,7 +4,7 @@ import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB'
import { useRefreshShowPageFindManyActivitiesQueries } from '@/activities/hooks/useRefreshShowPageFindManyActivitiesQueries';
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
import { objectShowPageTargetableObjectState } from '@/activities/timelineActivities/states/objectShowPageTargetableObjectIdState';
import { objectShowPageTargetableObjectState } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectIdState';
import { Note } from '@/activities/types/Note';
import { Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';

View File

@ -1,21 +1,20 @@
import styled from '@emotion/styled';
import { IconPlus } from 'twenty-ui';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { NoteList } from '@/activities/notes/components/NoteList';
import { useNotes } from '@/activities/notes/hooks/useNotes';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { Button } from '@/ui/input/button/components/Button';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import styled from '@emotion/styled';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
Button,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
IconPlus,
} from 'twenty-ui';
const StyledNotesContainer = styled.div`
display: flex;

View File

@ -3,7 +3,7 @@ import { useRecoilState } from 'recoil';
import { useActivities } from '@/activities/hooks/useActivities';
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timelineActivities/constants/FindManyTimelineActivitiesOrderBy';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline-activities/constants/FindManyTimelineActivitiesOrderBy';
import { Note } from '@/activities/types/Note';
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';

View File

@ -1,10 +1,9 @@
import { isNonEmptyArray } from '@sniptt/guards';
import { IconPlus } from 'twenty-ui';
import { Button, IconPlus } from 'twenty-ui';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { Button } from '@/ui/input/button/components/Button';
export const AddTaskButton = ({
activityTargetableObjects,

View File

@ -1,6 +1,6 @@
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
export const PageAddTaskButton = () => {
const openCreateActivity = useOpenCreateActivityDrawer({

View File

@ -1,25 +1,24 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { IconPlus } from 'twenty-ui';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
Button,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
IconPlus,
} from 'twenty-ui';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { TASKS_TAB_LIST_COMPONENT_ID } from '@/activities/tasks/constants/TasksTabListComponentId';
import { useTasks } from '@/activities/tasks/hooks/useTasks';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { Button } from '@/ui/input/button/components/Button';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import {
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import groupBy from 'lodash.groupby';
import { AddTaskButton } from './AddTaskButton';
import { TaskList } from './TaskList';

View File

@ -1,5 +1,5 @@
import { useActivities } from '@/activities/hooks/useActivities';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timelineActivities/constants/FindManyTimelineActivitiesOrderBy';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline-activities/constants/FindManyTimelineActivitiesOrderBy';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';

View File

@ -1,10 +1,10 @@
import styled from '@emotion/styled';
import { ReactElement } from 'react';
import { EventsGroup } from '@/activities/timelineActivities/components/EventsGroup';
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
import { filterOutInvalidTimelineActivities } from '@/activities/timelineActivities/utils/filterOutInvalidTimelineActivities';
import { groupEventsByMonth } from '@/activities/timelineActivities/utils/groupEventsByMonth';
import { EventsGroup } from '@/activities/timeline-activities/components/EventsGroup';
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { filterOutInvalidTimelineActivities } from '@/activities/timeline-activities/utils/filterOutInvalidTimelineActivities';
import { groupEventsByMonth } from '@/activities/timeline-activities/utils/groupEventsByMonth';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';

View File

@ -2,13 +2,13 @@ import styled from '@emotion/styled';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext';
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
import { useLinkedObjectObjectMetadataItem } from '@/activities/timelineActivities/hooks/useLinkedObjectObjectMetadataItem';
import { EventIconDynamicComponent } from '@/activities/timelineActivities/rows/components/EventIconDynamicComponent';
import { EventRowDynamicComponent } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
import { getTimelineActivityAuthorFullName } from '@/activities/timelineActivities/utils/getTimelineActivityAuthorFullName';
import { useLinkedObjectObjectMetadataItem } from '@/activities/timeline-activities/hooks/useLinkedObjectObjectMetadataItem';
import { EventIconDynamicComponent } from '@/activities/timeline-activities/rows/components/EventIconDynamicComponent';
import { EventRowDynamicComponent } from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { getTimelineActivityAuthorFullName } from '@/activities/timeline-activities/utils/getTimelineActivityAuthorFullName';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { EventRow } from '@/activities/timelineActivities/components/EventRow';
import { EventGroup } from '@/activities/timelineActivities/utils/groupEventsByMonth';
import { EventRow } from '@/activities/timeline-activities/components/EventRow';
import { EventGroup } from '@/activities/timeline-activities/utils/groupEventsByMonth';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
type EventsGroupProps = {
@ -20,8 +20,8 @@ const StyledActivityGroup = styled.div`
`;
const StyledActivityGroupContainer = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(2)};
padding-top: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(3)};
margin-top: ${({ theme }) => theme.spacing(3)};
position: relative;
`;
@ -29,7 +29,7 @@ const StyledActivityGroupBar = styled.div`
align-items: center;
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.xl};
border-radius: ${({ theme }) => theme.border.radius.md};
display: flex;
flex-direction: column;
height: 100%;

Some files were not shown because too many files have changed in this diff Show More