Compare commits

..
Author SHA1 Message Date
Sonarly Claude Code 5f316902ea fix: add array guard in getActivityTargetObjectRecords to prevent crash on non-array field values
https://sonarly.com/issue/15203?type=bug

When rendering relation-from-many fields on the People list view, `useActivityTargetObjectRecords` is called unconditionally with `fieldValue` before validating it's an array, causing `TypeError: a.map is not a function` when the value is truthy but not an array.

Fix: Added an `Array.isArray` guard on the `targets` variable in `getActivityTargetObjectRecords.ts` before the `.map()` call on line 75.

**What changed:** Renamed `targets` to `rawTargets` for the initial derivation, then added a guard that returns `[]` if `rawTargets` is not an array. The validated value is then assigned back to `targets` for the rest of the function.

**Why this fix:** The `RelationFromManyFieldDisplay` component calls `useActivityTargetObjectRecords` unconditionally for ALL relation-from-many fields (not just activity target fields), passing `fieldValue` with an unsafe type cast (`fieldValue as NoteTarget[] | TaskTarget[]`). When `fieldValue` is truthy but not an array (e.g., after a failed/partial GraphQL fetch or a newly created field with unexpected data shape), the `activityTargets` parameter passes the truthiness check on line 32 and flows directly into `.map()`, causing `TypeError: a.map is not a function`.

The `Array.isArray` guard is placed in `getActivityTargetObjectRecords` rather than the component because:
1. It's the function that makes the assumption about the data being an array
2. The component's existing `isArray(fieldValue)` check on line 70 runs AFTER the hook has already executed and crashed
3. Hooks cannot be called conditionally in React, so the component can't skip the hook call
2026-03-16 13:43:50 +00:00
nitinandGitHub e552704201 fix: add viewFieldGroupId to ViewField fragment and connect page layout selectors to live metadata store (#18676) 2026-03-16 14:24:41 +01:00
ddedecbb36 i18n - docs translations (#18677)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-16 14:14:29 +01:00
5011e1d77b i18n - docs translations (#18674)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2026-03-16 13:02:42 +01:00
a07337fea0 fix: return method-specific MCP responses (#18671)
## Summary

Fixes #18524 

Fixes the MCP response contract for non-`initialize` methods.

Previously, `/mcp` returned initialize-style metadata for methods like
`tools/list`, which caused strict MCP clients to reject the response
shape. The endpoint also returned `201 Created` for RPC calls even
though no resource was being created.

## Changes

- return only method-specific payloads for MCP list methods
  - `tools/list` -> `{ tools: [...] }`
  - `prompts/list` -> `{ prompts: [] }`
  - `resources/list` -> `{ resources: [] }`
- keep MCP server metadata only on `initialize`
- make `/mcp` return `200 OK` instead of `201 Created`
- add regression tests for:
  - `tools/list` response shape
  - `prompts/list` response shape
  - `resources/list` response shape

## Why

Strict MCP clients expect:
- standard RPC transport semantics over HTTP
- method-specific JSON-RPC result payloads

Returning initialize metadata for non-`initialize` methods breaks that
expectation and can cause client deserialization or protocol validation
failures.

## Verification

- reproduced the issue locally against `/mcp`
- verified `tools/list` was previously returning initialize-style fields
- verified `tools/list` now returns only `result.tools`
- verified `/mcp` now returns `200 OK`
- ran targeted Jest tests:

```bash
cd /Users/apple/MyProjects/OpenSource/twenty/packages/twenty-server
npx jest --runInBand src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts src/engine/api/mcp/services/__tests__/mcp-tool-executor.service.spec.ts

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
2026-03-16 12:46:35 +01:00
Abdullah.andGitHub 6e36ad9fa2 fix: mailparser vulnerable to cross-site scripting (#18664)
Resolves [Dependabot Alert
595](https://github.com/twentyhq/twenty/security/dependabot/595) and
[Dependabot Alert
596](https://github.com/twentyhq/twenty/security/dependabot/596).
2026-03-16 09:59:36 +01:00
Charles BochetandGitHub 5c745059ad refactor: remove "core" naming from views and eliminate converter layer (#18667)
## Summary

- **Remove all "core" prefixes** from the views system — the
metadata-based storage migration is complete, so `CoreView`,
`coreViewsSelector`, `getCoreViews`, etc. are now just `View`,
`viewsSelector`, `getViews`
- **Eliminate the entire converter layer** (15 files, ~850 lines
deleted) — `convertCoreViewToView` and all sub-converters were either
no-ops or trivially adding `__typename` / mapping identical enum values.
Local enums now re-export from generated GraphQL types directly (single
source of truth)
- **Unify `View` and `ViewWithRelations`** into one type —
`ViewWithRelations` is now a type alias for `View`, selectors return
data directly without conversion

### Backend
- Rename `@ObjectType('CoreView')` → `@ObjectType('View')` (and all
sub-entities)
- Rename resolver methods: `getCoreViews` → `getViews`, `createCoreView`
→ `createView`, etc.
- Rename `FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION` →
`FIND_ALL_VIEWS_GRAPHQL_OPERATION`

### Frontend
- Delete 15 converter files (`convertGqlView*ToView*`,
`convertView*ToGql`, `convertViewWithRelationsToView`)
- Re-export `ViewType`, `ViewKey`, `ViewFilterGroupLogicalOperator` from
generated enums (no more duplicate enum definitions with different
casing)
- Replace `ViewOpenRecordInType` with `ViewOpenRecordIn` from generated
- Remove `__typename` from all local view sub-types
- Remove unused `variant` from `ViewFilter`, make `displayValue` and
`definition` optional
- Rename ~45 GraphQL query/mutation files and all selectors to drop
"core" prefix
- Delete unused `viewsWithRelationsSelector`
2026-03-16 09:57:18 +01:00
6340b9e2f7 i18n - translations (#18669)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-16 09:49:16 +01:00
Abdullah.andGitHub 2c2f66b584 fix: DOMPurify contains a cross-site scripting vulnerability (#18665)
Resolves [Dependabot Alert
597](https://github.com/twentyhq/twenty/security/dependabot/597),
[Dependabot Alert
598](https://github.com/twentyhq/twenty/security/dependabot/598),
[Dependabot Alert
599](https://github.com/twentyhq/twenty/security/dependabot/599) and
[Dependabot Alert
600](https://github.com/twentyhq/twenty/security/dependabot/600).
2026-03-16 09:49:03 +01:00
Félix MalfaitGitHubClaudeclaude[bot] <41898282+claude[bot]@users.noreply.github.com>github-actionscubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
95a35f8a1d Implement OAuth 2.0 Dynamic Client Registration (RFC 7591) (#18608)
## Summary
This PR implements OAuth 2.0 Dynamic Client Registration (RFC 7591) and
OAuth 2.0 Protected Resource Metadata (RFC 9728) support, enabling
third-party applications to dynamically register as OAuth clients
without manual configuration.

## Key Changes

### OAuth Dynamic Client Registration
- **New Controller**: `OAuthRegistrationController` at `POST
/oauth/register` endpoint
  - Validates client metadata according to RFC 7591 specifications
  - Enforces PKCE-only public client model (no client secrets)
- Supports only `authorization_code` grant type and `code` response type
  - Rate limits registrations to 10 per hour per IP address
  - Returns `client_id` and registration metadata in response

- **Input Validation**: `OAuthRegisterInput` DTO with constraints on:
  - Client name (max 256 chars)
  - Redirect URIs (max 20, validated for security)
  - Grant types, response types, scopes, and auth methods
  - Logo and client URIs (max 2048 chars)

- **Discovery Endpoint Update**: Added `registration_endpoint` to OAuth
discovery metadata

### Stale Registration Cleanup
- **Cleanup Service**: Automatically removes OAuth-only registrations
older than 30 days that have no active installations
- **Cron Job**: Runs daily at 02:30 AM UTC with batch processing (100
records per batch)
- **CLI Command**: `cron:stale-registration-cleanup` to manually trigger
cleanup

### MCP (Model Context Protocol) Authentication
- **New Guard**: `McpAuthGuard` implements RFC 9728 compliance
  - Wraps JWT authentication with proper error responses
- Returns `WWW-Authenticate` header with protected resource metadata URL
on 401
  - Enables OAuth-protected MCP endpoints

### Protected Resource Metadata
- **New Endpoint**: `GET /.well-known/oauth-protected-resource` (RFC
9728)
  - Advertises MCP resource as OAuth-protected
  - Lists supported scopes and bearer token methods
  - Enables OAuth clients to discover authorization requirements

### Application Registration Updates
- **New Source Type**: `OAUTH_ONLY` enum value for OAuth-only
registrations
- **Install Service**: Skips artifact installation for OAuth-only apps
(no code artifacts)

### Frontend Updates
- **Authorization Page**: Support both snake_case (standard OAuth) and
camelCase (legacy) query parameters
  - `client_id` / `clientId`
  - `code_challenge` / `codeChallenge`
  - `redirect_uri` / `redirectUrl`

## Implementation Details

- **Rate Limiting**: Uses token bucket algorithm with 10 registrations
per 3,600,000ms window per IP
- **Scope Validation**: Requested scopes are capped to allowed OAuth
scopes; defaults to all scopes if not specified
- **Redirect URI Validation**: Uses existing `validateRedirectUri`
utility for security
- **Cache Headers**: Registration responses include `Cache-Control:
no-store` and `Pragma: no-cache`
- **Batch Processing**: Cleanup operations process 100 records at a time
to avoid memory issues
- **Grace Period**: 30-day grace period before cleanup to allow time for
client activation

https://claude.ai/code/session_01PxcuWFFRuXMASMaMGTLYk2

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-03-16 09:42:28 +01:00
Abdullah.andGitHub 87c519b72f fix: multer vulnerable to denial of service via uncontrolled recursion (#18659)
Resolves [Dependabot Alert
608](https://github.com/twentyhq/twenty/security/dependabot/608).
2026-03-16 09:08:40 +01:00
Abdullah.andGitHub 1b20bdaf6d fix: @isaacs/brace-expansion has uncontrolled resource consumption (#18660)
Resolves [Dependabot Alert
414](https://github.com/twentyhq/twenty/security/dependabot/414).
2026-03-16 09:08:25 +01:00
Abdullah.andGitHub 67866ff59c fix: expr-eval related dependabot alerts (#18661)
Resolves [Dependabot Alert
593](https://github.com/twentyhq/twenty/security/dependabot/593) and
[Dependabot Alert
594](https://github.com/twentyhq/twenty/security/dependabot/594).

expr-eval was last published six years ago and had changes five years
ago. NPM contains a fork that published the changes from five years ago
under the same name with `-fork` suffix. This PR uses that fork as
suggested by Dependabot.
2026-03-16 09:08:19 +01:00
Abdullah.andGitHub e6f1bdd1c8 fix: yauzl contains an off-by-one error (#18662)
Resolves [Dependabot Alert
633](https://github.com/twentyhq/twenty/security/dependabot/633).
2026-03-16 09:08:12 +01:00
Charles BochetandGitHub ba9aa41bba refactor: metadata store cleanup, SSE unification, mock metadata loading & login redirect fix (#18651)
## Summary
- **SSE unification**: Replaced 11 individual SSE effect components with
a single generic `MetadataStoreSSEEffect`
- **Metadata store cleanup**: Merged `metadataCollectionHashesState`
into `metadataStoreState` (currentCollectionHash / draftCollectionHash
per entity), moved `objectMetadataItemsSelector` to `object-metadata`
domain, converted `navigationMenuItemsState` to a derived selector
- **Naming clarity**: Renamed `isAppMetadataReadyState` →
`isMinimalMetadataReadyState`, `MetadataGater` → `MinimalMetadataGater`,
`useIsLogged` → `useHasAccessTokenPair`,
`patchMetadataStoreFromSSEEvent` now takes named object params
- **Mock metadata loading**: Added `generate-navigation-menu-items.ts`
script, rewrote `useLoadMockedMinimalMetadata` to load full
objects/fields/indexes/views/navItems from generated mock data, enabling
proper sign-in background rendering (table columns, view picker,
navigation)
- **Login/logout transitions**: `MinimalMetadataLoadEffect` manages
mocked↔real metadata transitions based on auth state,
`MainContextStoreProvider` computes context on auth pages for view
picker support
- **Login redirect fix**: `handleLoadWorkspaceAfterAuthentication` now
re-enables `isAppEffectRedirectEnabled` after `loadCurrentUser()`
completes, fixing the blocked post-login navigation
- **Dead code removal**: Deleted `useRefreshPageLayouts`,
`useApplyPageLayouts`, `useStaleMetadataEntities`,
`metadataCollectionHashesState`, and all individual SSE effects

## Test plan
- [x] Login from welcome page redirects to companies page
- [x] Logout transitions cleanly to mocked metadata on welcome page
- [x] Sign-in background shows table columns, view picker, and
navigation items
- [x] SSE events still update metadata store entries correctly
- [x] Navigation menu items persist across page refreshes
- [ ] CI: lint, typecheck, tests pass
2026-03-16 00:38:11 +01:00
675 changed files with 9149 additions and 8655 deletions
+4
View File
@@ -6302,6 +6302,10 @@
"source": "/developers/extend/capabilities/apps",
"destination": "/developers/extend/apps/getting-started"
},
{
"source": "/developers/extend/mcp",
"destination": "/user-guide/ai/capabilities/mcp"
},
{
"source": "/developers/local-setup",
"destination": "/developers/contribute/capabilities/local-setup"
@@ -0,0 +1,142 @@
---
title: خادم MCP},{
description: اربط مساعدي الذكاء الاصطناعي بمساحة عمل Twenty الخاصة بك باستخدام بروتوكول سياق النموذج.
---
<Warning>
MCP حاليًا في مرحلة **ألفا** وهو متاح فقط في بعض مساحات العمل. قد لا يكون مفعّلًا لمساحة عملك بعد.
</Warning>
تعرض Twenty خادم [MCP](https://modelcontextprotocol.io/) بحيث تتمكّن مساعدات الذكاء الاصطناعي — Claude Desktop وClaude Code وCursor وChatGPT وغيرها — من قراءة وكتابة بيانات نظام إدارة علاقات العملاء (CRM) لديك باستخدام اللغة الطبيعية.
استخدم **عنوان URL لمساحة العمل** (عنوان URL الذي تستخدمه للوصول إلى Twenty) كنقطة نهاية MCP. على Twenty Cloud، قد يكون عنوان URL لمساحة العمل هو `https://{mycompany}.twenty.com` أو نطاق مخصص. الخادم متاح على:
| البيئة | نقطة نهاية MCP |
| --------------------- | ---------------------------------------------------------------------------------------- |
| **السحابة** | `https://{your-workspace-url}/mcp` (على سبيل المثال: `https://mycompany.twenty.com/mcp`) |
| **الاستضافة الذاتية** | `https://{your-domain}/mcp` |
## طرق المصادقة
لديك طريقتان لمصادقة عميل MCP: **OAuth** (مُوصى بها) أو **مفتاح API**.
### الخيار أ — OAuth (مُوصى به)
باستخدام OAuth، يفتح عميل MCP لديك نافذة متصفح لتسجيل الدخول. لا يتم تخزين أي أسرار في ملفات الإعداد، ويتم تحديث الرموز المميِّزة تلقائيًا.
<Note>
يتطلب OAuth عميل MCP يدعم [مواصفة تفويض MCP](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). تدعمه Claude Desktop وClaude Code وCursor وChatGPT.
</Note>
أضِف ما يلي إلى تهيئة عميل MCP لديك، واستبدِل `{your-workspace-url}` بمضيف مساحة العمل لديك (على سبيل المثال: `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
هذا كل شيء — لا حاجة إلى مفتاح API. عند اتصال العميل للمرة الأولى، سيفعل ما يلي:
1. اكتشاف بيانات التعريف الخاصة بـ OAuth لدى Twenty عبر `/.well-known/oauth-protected-resource` و`/.well-known/oauth-authorization-server`
2. تسجيل نفسه كعميل OAuth عبر التسجيل الديناميكي للعميل (RFC 7591)
3. فتح متصفحك لتفويض الوصول
4. استلام الرموز المميِّزة والاتصال بخادم MCP
تعيد الاتصالات اللاحقة استخدام الرموز المميِّزة المخزنة وتحدِّثها تلقائيًا.
### الخيار ب — مفتاح API
إذا كان عميل MCP لديك لا يدعم OAuth، أو كنت تفضّل بيانات اعتماد ثابتة، فمرِّر مفتاح API في ترويسة `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
يمنح مفتاح API الخاص بك حق الوصول إلى بيانات مساحة العمل. أبعِده عن أنظمة التحكم في الإصدارات وملفات dotfiles المشتركة.
</Warning>
لإنشاء مفتاح API، انتقل إلى **Settings > APIs & Webhooks > + Create key**. راجع [واجهات برمجة التطبيقات](/l/ar/developers/extend/api#create-an-api-key) للتفاصيل.
## البدء السريع
### 1. انسخ الإعداد
انتقل إلى **Settings > AI > More > MCP Server** في Twenty. اختر طريقة المصادقة (OAuth أو مفتاح API)، وانسخ مقطع JSON (سيستخدم بالفعل عنوان URL لمساحة العمل لديك)، ثم الصقه في ملف إعدادات عميل MCP لديك.
| العميل | موقع ملف الإعداد |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) أو `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (المستخدم) أو `.mcp.json` (المشروع) |
| **Cursor** | `.cursor/mcp.json` ضمن مشروعك، أو `~/.cursor/mcp.json` عالميًا |
| **ChatGPT** | فعِّل وضع المطوّر في **Settings > Apps & Connectors > Advanced settings**، ثم استخدم **Create** في **Settings > Apps & Connectors** لإضافة خادم MCP |
### ٢. الاتصال
أعِد تشغيل عميل MCP لديك (أو أعد تحميل ملف الإعداد). إذا كنت تستخدم OAuth فسيتم توجيهك إلى Twenty لتفويض الوصول. إذا كنت تستخدم مفتاح API فسيكون الاتصال فوريًا.
### ٣. ابدأ باستخدامه
اطلب من مساعد الذكاء الاصطناعي التفاعل مع نظام إدارة علاقات العملاء (CRM) لديك:
* *"أرني أحدث 5 شركات تم إنشاؤها"*
* *"أنشئ شخصًا جديدًا باسم Jane Doe في Acme Corp"*
* *"اعثر على جميع الفرص المفتوحة التي تزيد قيمتها عن 10 آلاف دولار"*
## الأدوات المتاحة
بعد الاتصال، يوفّر خادم MCP أدوات تعكس واجهة برمجة تطبيقات Twenty (API). سير العمل الموصى به هو:
1. **`get_tool_catalog`** — اكتشف جميع الأدوات المتاحة
2. **`learn_tools`** — احصل على مخطط الإدخال لأدوات محددة
3. **`execute_tool`** — شغّل أداة
لا تحتاج إلى تذكّر أسماء الأدوات. اسأل مساعد الذكاء الاصطناعي عمّا يمكنه فعله وسيستدعي `get_tool_catalog` تلقائيًا.
## الصلاحيات
ترث اتصالات MCP أذونات المستخدم المُصادَق عليه (OAuth) أو الدور المُعيَّن لمفتاح API. لتقييد ما يمكن لخادم MCP القيام به:
* **OAuth**: ينطبق دور المستخدم في مساحة العمل.
* **API Key**: عيِّن دورًا لمفتاح API ضمن **Settings > Roles**. راجع [الأذونات](/l/ar/user-guide/permissions-access/capabilities/permissions).
## التكوين ذاتي الاستضافة
في حالات الاستضافة الذاتية، استبدِل `{your-workspace-url}` بعنوان URL الخاص بالخادم لديك. تأكّد من أن قيمة `SERVER_URL` في بيئتك تطابق عنوان URL العام لمثيل Twenty لديك — إذ يُستخدَم ذلك لإنشاء بيانات تعريف اكتشاف OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
تُشتق نقطة نهاية MCP ونقاط نهاية OAuth وبيانات تعريف الاكتشاف جميعها من هذه القيمة.
## استكشاف الأخطاء وإصلاحها
**أخطاء "Unauthorized" أو 401**
* OAuth: أعد التفويض عبر مسح الرموز المميِّزة المخزنة في عميل MCP لديك ثم أعد الاتصال.
* API Key: تحقّق من أن المفتاح صالح ولم تنتهِ صلاحيته. أعِد توليده إذا لزم الأمر.
**عملية OAuth لا تفتح متصفحًا**
* تأكّد من أن عميل MCP لديك يدعم تفويض MCP. ارجع إلى طريقة مفتاح API إذا لم يكن كذلك.
**انتهاء مهلة الاتصال**
* تحقّق من إمكانية الوصول إلى عنوان URL لنقطة نهاية MCP من جهازك. بالنسبة لحالات الاستضافة الذاتية، تحقّق من أن الخادم يعمل وأن `SERVER_URL` مُعيَّن بشكل صحيح.
@@ -0,0 +1,142 @@
---
title: MCP-Server
description: Verbinden Sie KI-Assistenten mit Ihrem Twenty-Workspace über das Model Context Protocol.
---
<Warning>
MCP befindet sich derzeit in **alpha** und ist nur in einigen Workspaces verfügbar. Möglicherweise ist es für Ihren Workspace noch nicht aktiviert.
</Warning>
Twenty stellt einen [MCP](https://modelcontextprotocol.io/)-Server bereit, damit KI-Assistenten — Claude Desktop, Claude Code, Cursor, ChatGPT und andere — Ihre CRM-Daten in natürlicher Sprache lesen und schreiben können.
Verwenden Sie Ihre **Workspace-URL** (die URL, mit der Sie auf Twenty zugreifen) als MCP-Endpunkt. In Twenty Cloud kann Ihre Workspace-URL `https://{mycompany}.twenty.com` oder eine benutzerdefinierte Domain sein. Der Server ist verfügbar unter:
| Umgebung | MCP-Endpunkt |
| ----------------- | ----------------------------------------------------------------------------- |
| **Cloud** | `https://{your-workspace-url}/mcp` (z. B. `https://mycompany.twenty.com/mcp`) |
| **Selbsthosting** | `https://{your-domain}/mcp` |
## Authentifizierungsmethoden
Sie haben zwei Möglichkeiten, Ihren MCP-Client zu authentifizieren: **OAuth** (empfohlen) oder **API-Schlüssel**.
### Option A — OAuth (empfohlen)
Mit OAuth öffnet Ihr MCP-Client ein Browserfenster, damit Sie sich anmelden können. Es werden keine geheimen Informationen in Konfigurationsdateien gespeichert, und Token werden automatisch erneuert.
<Note>
OAuth erfordert einen MCP-Client, der die [MCP-Autorisierungsspezifikation](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) unterstützt. Claude Desktop, Claude Code, Cursor und ChatGPT unterstützen dies.
</Note>
Fügen Sie dies zu Ihrer MCP-Client-Konfiguration hinzu und ersetzen Sie `{your-workspace-url}` durch den Host Ihrer Workspace-URL (z. B. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
Das ist alles — kein API-Schlüssel erforderlich. Wenn der Client sich zum ersten Mal verbindet, wird er:
1. Die OAuth-Metadaten von Twenty über `/.well-known/oauth-protected-resource` und `/.well-known/oauth-authorization-server` ermitteln
2. Sich über die dynamische Client-Registrierung (RFC 7591) als OAuth-Client registrieren
3. Ihren Browser öffnen, um den Zugriff zu autorisieren
4. Token empfangen und eine Verbindung zum MCP-Server herstellen
Nachfolgende Verbindungen verwenden die gespeicherten Token erneut und erneuern sie automatisch.
### Option B — API-Schlüssel
Wenn Ihr MCP-Client OAuth nicht unterstützt oder Sie statische Anmeldeinformationen bevorzugen, übergeben Sie einen API-Schlüssel im `Authorization`-Header:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Ihr API-Schlüssel gewährt Zugriff auf Workspace-Daten. Halten Sie es von der Versionskontrolle und von gemeinsam genutzten Dotfiles fern.
</Warning>
Um einen API-Schlüssel zu erstellen, gehen Sie zu **Settings > APIs & Webhooks > + Create key**. Details finden Sie unter [APIs](/l/de/developers/extend/api#create-an-api-key).
## Schnellstart
### 1. Konfiguration kopieren
Gehen Sie in Twenty zu **Settings > AI > More > MCP Server**. Wählen Sie Ihre Authentifizierungsmethode (OAuth oder API-Schlüssel), kopieren Sie das JSON-Snippet (es verwendet bereits Ihre Workspace-URL) und fügen Sie es in die Konfigurationsdatei Ihres MCP-Clients ein.
| Client | Speicherort der Konfigurationsdatei |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) oder `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (Benutzer) oder `.mcp.json` (Projekt) |
| **Cursor** | `.cursor/mcp.json` in Ihrem Projekt oder `~/.cursor/mcp.json` global |
| **ChatGPT** | Aktivieren Sie den Entwicklermodus in **Settings > Apps & Connectors > Advanced settings** und verwenden Sie dann **Create** in **Settings > Apps & Connectors**, um den MCP-Server hinzuzufügen |
### 2. Verbinden
Starten Sie Ihren MCP-Client neu (oder laden Sie die Konfiguration neu). Bei Verwendung von OAuth werden Sie zu Twenty weitergeleitet, um den Zugriff zu autorisieren. Bei Verwendung eines API-Schlüssels wird die Verbindung sofort hergestellt.
### 3. Jetzt loslegen
Bitten Sie Ihren KI-Assistenten, mit Ihrem CRM zu interagieren:
* *"Zeige mir die 5 zuletzt erstellten Unternehmen"*
* *"Erstelle eine neue Person namens Jane Doe bei Acme Corp"*
* *"Finde alle offenen Verkaufschancen mit einem Wert von mehr als $10k"*
## Verfügbare Tools
Nach der Verbindung stellt der MCP-Server Tools bereit, die die Twenty-API widerspiegeln. Der empfohlene Workflow ist:
1. **`get_tool_catalog`** — alle verfügbaren Tools entdecken
2. **`learn_tools`** — das Eingabeschema für bestimmte Tools abrufen
3. **`execute_tool`** — ein Tool ausführen
Sie müssen sich die Tool-Namen nicht merken. Fragen Sie Ihren KI-Assistenten, was er tun kann, und er ruft `get_tool_catalog` automatisch auf.
## Berechtigungen
MCP-Verbindungen erben die Berechtigungen des authentifizierten Benutzers (OAuth) oder die dem API-Schlüssel zugewiesene Rolle. So beschränken Sie, was der MCP-Server tun darf:
* **OAuth**: Es gilt die Workspace-Rolle des Benutzers.
* **API-Schlüssel**: Weisen Sie dem API-Schlüssel unter **Settings > Roles** eine Rolle zu. Siehe [Berechtigungen](/l/de/user-guide/permissions-access/capabilities/permissions).
## Selbstgehostete Konfiguration
Für selbstgehostete Instanzen ersetzen Sie `{your-workspace-url}` durch die URL Ihres Servers. Stellen Sie sicher, dass `SERVER_URL` in Ihrer Umgebung der öffentlichen URL Ihrer Twenty-Instanz entspricht — dieser Wert wird verwendet, um die OAuth-Discovery-Metadaten zu generieren.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
Der MCP-Endpunkt, die OAuth-Endpunkte und die Discovery-Metadaten leiten sich alle von diesem Wert ab.
## Fehlerbehebung
**"Unauthorized"- oder 401-Fehler**
* OAuth: Autorisieren Sie erneut, indem Sie die gespeicherten Token in Ihrem MCP-Client löschen und die Verbindung wiederherstellen.
* API-Schlüssel: Überprüfen Sie, ob der Schlüssel gültig ist und nicht abgelaufen ist. Generieren Sie ihn bei Bedarf neu.
**Der OAuth-Flow öffnet keinen Browser**
* Stellen Sie sicher, dass Ihr MCP-Client MCP Authorization unterstützt. Wechseln Sie andernfalls zur API-Schlüssel-Methode.
**Verbindungszeitüberschreitung**
* Stellen Sie sicher, dass die MCP-Endpunkt-URL von Ihrem Rechner aus erreichbar ist. Bei selbstgehosteten Instanzen prüfen Sie, ob der Server läuft und `SERVER_URL` korrekt gesetzt ist.
@@ -0,0 +1,142 @@
---
title: Server MCP
description: Collega gli assistenti AI al tuo spazio di lavoro di Twenty utilizzando il Model Context Protocol.
---
<Warning>
MCP è attualmente in **alpha** ed è disponibile solo su alcuni spazi di lavoro. Potrebbe non essere ancora abilitato per il tuo spazio di lavoro.
</Warning>
Twenty espone un server [MCP](https://modelcontextprotocol.io/) affinché gli assistenti AI — Claude Desktop, Claude Code, Cursor, ChatGPT e altri — possano leggere e scrivere i dati del tuo CRM in linguaggio naturale.
Usa l'**URL dello spazio di lavoro** (l'URL che usi per accedere a Twenty) come endpoint MCP. Su Twenty Cloud, l'URL del tuo spazio di lavoro potrebbe essere `https://{mycompany}.twenty.com` oppure un dominio personalizzato. Il server è disponibile all'indirizzo:
| Ambiente | Endpoint MCP |
| ----------------- | ------------------------------------------------------------------------------ |
| **Cloud** | `https://{your-workspace-url}/mcp` (ad es. `https://mycompany.twenty.com/mcp`) |
| **Auto-ospitato** | `https://{your-domain}/mcp` |
## Metodi di autenticazione
Hai due modi per autenticare il tuo client MCP: **OAuth** (consigliato) o **API Key**.
### Opzione A — OAuth (Consigliato)
Con OAuth, il tuo client MCP apre una finestra del browser per effettuare l'accesso. Nessun segreto viene archiviato nei file di configurazione e i token si rinnovano automaticamente.
<Note>
OAuth richiede un client MCP che supporti la [specifica MCP Authorization](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Claude Desktop, Claude Code, Cursor e ChatGPT lo supportano.
</Note>
Aggiungi questo alla configurazione del tuo client MCP, sostituendo `{your-workspace-url}` con l'host del tuo spazio di lavoro (ad es. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
È tutto — non è necessaria alcuna chiave API. Quando il client si connette per la prima volta, eseguirà:
1. Scoprire i metadati OAuth di Twenty tramite `/.well-known/oauth-protected-resource` e `/.well-known/oauth-authorization-server`
2. Registrarsi come client OAuth tramite registrazione dinamica del client (RFC 7591)
3. Aprire il browser per autorizzare l'accesso
4. Ricevere i token e connettersi al server MCP
Le connessioni successive riutilizzano i token memorizzati e li rinnovano automaticamente.
### Opzione B — Chiave API
Se il tuo client MCP non supporta OAuth, o preferisci credenziali statiche, passa una chiave API nell'header `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
La tua chiave API concede l'accesso ai dati dello spazio di lavoro. Mantienila fuori dal controllo versione e dai dotfile condivisi.
</Warning>
Per creare una chiave API, vai su **Impostazioni > API e Webhook > + Crea chiave**. Vedi [API](/l/it/developers/extend/api#create-an-api-key) per i dettagli.
## Avvio rapido
### 1. Copia la configurazione
Vai su **Impostazioni > AI > Altro > MCP Server** in Twenty. Scegli il metodo di autenticazione (OAuth o Chiave API), copia lo snippet JSON (userà già l'URL del tuo spazio di lavoro) e incollalo nel file di configurazione del tuo client MCP.
| Client | Percorso del file di configurazione |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (utente) o `.mcp.json` (progetto) |
| **Cursor** | `.cursor/mcp.json` nel tuo progetto, oppure `~/.cursor/mcp.json` globalmente |
| **ChatGPT** | Attiva la Modalità sviluppatore in **Impostazioni > App e Connettori > Impostazioni avanzate**, quindi usa **Crea** in **Impostazioni > App e Connettori** per aggiungere il server MCP |
### 2. Connetti
Riavvia il tuo client MCP (o ricarica la configurazione). Se usi OAuth verrai reindirizzato a Twenty per autorizzare l'accesso. Se usi una chiave API la connessione è immediata.
### 3. Inizia a usarlo
Chiedi al tuo assistente AI di interagire con il tuo CRM:
* *"Mostrami le 5 aziende create più di recente"*
* *"Crea una nuova persona di nome Jane Doe presso Acme Corp"*
* *"Trova tutte le opportunità aperte di valore superiore a $10k"*
## Strumenti disponibili
Una volta connesso, il server MCP espone strumenti che rispecchiano l'API di Twenty. Il flusso di lavoro consigliato è:
1. **`get_tool_catalog`** — scoprire tutti gli strumenti disponibili
2. **`learn_tools`** — ottenere lo schema di input per strumenti specifici
3. **`execute_tool`** — eseguire uno strumento
Non è necessario ricordare i nomi degli strumenti. Chiedi al tuo assistente AI cosa può fare e chiamerà `get_tool_catalog` automaticamente.
## Permessi
Le connessioni MCP ereditano le autorizzazioni dell'utente autenticato (OAuth) o il ruolo assegnato alla chiave API. Per limitare ciò che il server MCP può fare:
* **OAuth**: si applica il ruolo dell'utente nello spazio di lavoro.
* **Chiave API**: assegna un ruolo alla chiave API in **Impostazioni > Ruoli**. Vedi [Autorizzazioni](/l/it/user-guide/permissions-access/capabilities/permissions).
## Configurazione self-hosted
Per le istanze self-hosted, sostituisci `{your-workspace-url}` con l'URL del tuo server. Assicurati che `SERVER_URL` nel tuo ambiente corrisponda all'URL pubblico della tua istanza Twenty — viene utilizzato per generare i metadati di discovery OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
L'endpoint MCP, gli endpoint OAuth e i metadati di discovery derivano tutti da questo valore.
## Risoluzione dei problemi
**Errori "Unauthorized" o 401**
* OAuth: esegui nuovamente l'autorizzazione eliminando i token memorizzati nel tuo client MCP e riconnettiti.
* Chiave API: verifica che la chiave sia valida e non sia scaduta. Rigenerala se necessario.
**Il flusso OAuth non apre il browser**
* Assicurati che il tuo client MCP supporti MCP Authorization. In caso contrario, ricorri al metodo con Chiave API.
**Timeout di connessione**
* Verifica che l'URL dell'endpoint MCP sia raggiungibile dalla tua macchina. Per le istanze self-hosted, controlla che il server sia in esecuzione e che `SERVER_URL` sia impostato correttamente.
@@ -0,0 +1,142 @@
---
title: Servidor MCP
description: Conecte assistentes de IA ao seu espaço de trabalho do Twenty usando o Model Context Protocol.
---
<Warning>
O MCP está atualmente em **alfa** e está disponível apenas em alguns espaços de trabalho. O MCP pode ainda não estar ativado no seu espaço de trabalho.
</Warning>
O Twenty expõe um servidor [MCP](https://modelcontextprotocol.io/) para que assistentes de IA — Claude Desktop, Claude Code, Cursor, ChatGPT e outros — possam ler e escrever seus dados de CRM por meio de linguagem natural.
Use o **URL do espaço de trabalho** (o URL que você usa para acessar o Twenty) como o endpoint do MCP. Na Twenty Cloud, o URL do seu espaço de trabalho pode ser `https://{mycompany}.twenty.com` ou um domínio personalizado. O servidor está disponível em:
| Ambiente | Endpoint do MCP |
| ------------------ | ------------------------------------------------------------------------------------ |
| **Nuvem** | `https://{your-workspace-url}/mcp` (por exemplo, `https://mycompany.twenty.com/mcp`) |
| **Auto-hospedado** | `https://{your-domain}/mcp` |
## Métodos de autenticação
Você tem duas maneiras de autenticar seu cliente MCP: **OAuth** (recomendado) ou **Chave de API**.
### Opção A — OAuth (recomendado)
Com o OAuth, seu cliente MCP abre uma janela do navegador para você fazer login. Nenhum segredo é armazenado em arquivos de configuração, e os tokens são atualizados automaticamente.
<Note>
O OAuth requer um cliente MCP que ofereça suporte à [especificação de Autorização MCP](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Claude Desktop, Claude Code, Cursor e ChatGPT oferecem suporte.
</Note>
Adicione isto à configuração do seu cliente MCP, substituindo `{your-workspace-url}` pelo host do seu espaço de trabalho (por exemplo, `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
É isso — nenhuma chave de API é necessária. Quando o cliente se conectar pela primeira vez, ele irá:
1. Descobrir os metadados de OAuth do Twenty por meio de `/.well-known/oauth-protected-resource` e `/.well-known/oauth-authorization-server`
2. Registrar-se como um cliente OAuth por meio de registro dinâmico de cliente (RFC 7591)
3. Abrir seu navegador para autorizar o acesso
4. Receber tokens e conectar-se ao servidor MCP
Conexões subsequentes reutilizam os tokens armazenados e os atualizam automaticamente.
### Opção B — Chave de API
Se o seu cliente MCP não oferecer suporte a OAuth, ou se você preferir credenciais estáticas, passe uma chave de API no cabeçalho `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Sua chave de API concede acesso aos dados do espaço de trabalho. Mantenha-a fora do controle de versão e de dotfiles compartilhados.
</Warning>
Para criar uma chave de API, vá em **Settings > APIs & Webhooks > + Create key**. Veja [APIs](/l/pt/developers/extend/api#create-an-api-key) para detalhes.
## Início rápido
### 1. Copie a configuração
Vá até **Settings > AI > More > MCP Server** no Twenty. Escolha seu método de autenticação (OAuth ou Chave de API), copie o trecho de JSON (ele já usará o URL do seu espaço de trabalho) e cole-o no arquivo de configuração do seu cliente MCP.
| Cliente | Local do arquivo de configuração |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) ou `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (usuário) ou `.mcp.json` (projeto) |
| **Cursor** | `.cursor/mcp.json` no seu projeto, ou `~/.cursor/mcp.json` globalmente |
| **ChatGPT** | Ative o Modo Desenvolvedor em **Settings > Apps & Connectors > Advanced settings** e, em seguida, use **Create** em **Settings > Apps & Connectors** para adicionar o servidor MCP |
### 2. Conectar
Reinicie seu cliente MCP (ou recarregue a configuração). Se estiver usando OAuth, você será redirecionado ao Twenty para autorizar o acesso. Se estiver usando uma chave de API, a conexão é imediata.
### 3. Comece a usá-lo
Peça ao seu assistente de IA para interagir com seu CRM:
* *"Mostre-me as 5 empresas criadas mais recentemente"*
* *"Crie uma nova pessoa chamada Jane Doe na Acme Corp"*
* *"Encontre todas as oportunidades em aberto com valor superior a $10k"*
## Ferramentas disponíveis
Depois de conectado, o servidor MCP expõe ferramentas que refletem a API do Twenty. O fluxo de trabalho recomendado é:
1. **`get_tool_catalog`** — descobrir todas as ferramentas disponíveis
2. **`learn_tools`** — obter o esquema de entrada para ferramentas específicas
3. **`execute_tool`** — executar uma ferramenta
Você não precisa se lembrar dos nomes das ferramentas. Pergunte ao seu assistente de IA o que ele pode fazer e ele chamará `get_tool_catalog` automaticamente.
## Permissões
As conexões MCP herdam as permissões do usuário autenticado (OAuth) ou da função atribuída à chave de API. Para restringir o que o servidor MCP pode fazer:
* **OAuth**: Aplica-se à função do espaço de trabalho do usuário.
* **Chave de API**: Atribua uma função à chave de API em **Settings > Roles**. Veja [Permissões](/l/pt/user-guide/permissions-access/capabilities/permissions).
## Configuração auto-hospedada
Para instâncias auto-hospedadas, substitua `{your-workspace-url}` pelo URL do seu servidor. Certifique-se de que `SERVER_URL` no seu ambiente corresponda ao URL público da sua instância do Twenty — isso é usado para gerar os metadados de descoberta do OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
O endpoint do MCP, os endpoints de OAuth e os metadados de descoberta derivam todos desse valor.
## Resolução de Problemas
**Erros "Unauthorized" ou 401**
* OAuth: autorize novamente limpando os tokens armazenados no seu cliente MCP e reconectando.
* Chave de API: verifique se a chave é válida e não expirou. Gere-a novamente, se necessário.
**O fluxo do OAuth não abre um navegador**
* Garanta que seu cliente MCP ofereça suporte à Autorização MCP. Se não oferecer, utilize o método de Chave de API.
**Tempo limite de conexão**
* Confirme que o URL do endpoint MCP é acessível a partir da sua máquina. Para instâncias auto-hospedadas, verifique se o servidor está em execução e se `SERVER_URL` está definido corretamente.
@@ -0,0 +1,142 @@
---
title: Server MCP
description: Conectați asistenți AI la spațiul dvs. de lucru Twenty folosind Model Context Protocol.
---
<Warning>
MCP este în prezent în **alpha** și este disponibil doar în unele spații de lucru. Este posibil să nu fie activat încă pentru spațiul dvs. de lucru.
</Warning>
Twenty expune un server [MCP](https://modelcontextprotocol.io/) astfel încât asistenții AI — Claude Desktop, Claude Code, Cursor, ChatGPT și alții — să poată citi și scrie datele tale din CRM prin limbaj natural.
Folosește **URL-ul spațiului de lucru** (URL-ul pe care îl folosești pentru a accesa Twenty) drept punct final MCP. Pe Twenty Cloud, URL-ul spațiului tău de lucru poate fi `https://{mycompany}.twenty.com` sau un domeniu personalizat. Serverul este disponibil la:
| Mediu | Punct final MCP |
| -------------------- | ------------------------------------------------------------------------------ |
| **Cloud** | `https://{your-workspace-url}/mcp` (de ex. `https://mycompany.twenty.com/mcp`) |
| **Găzduire proprie** | `https://{your-domain}/mcp` |
## Metode de autentificare
Ai două moduri de a-ți autentifica clientul MCP: **OAuth** (recomandat) sau **Cheie API**.
### Opțiunea A — OAuth (Recomandat)
Cu OAuth, clientul tău MCP deschide o fereastră de browser pentru a te autentifica. Nicio informație secretă nu este stocată în fișierele de configurare, iar tokenurile se reîmprospătează automat.
<Note>
OAuth necesită un client MCP care suportă [specificația MCP Authorization](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Claude Desktop, Claude Code, Cursor și ChatGPT o suportă.
</Note>
Adaugă asta în configurația clientului tău MCP, înlocuind `{your-workspace-url}` cu gazda spațiului tău de lucru (de ex. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
Atât — nu este necesară nicio cheie API. Când clientul se conectează pentru prima dată, acesta va:
1. Va descoperi metadatele OAuth ale Twenty prin `/.well-known/oauth-protected-resource` și `/.well-known/oauth-authorization-server`
2. Se va înregistra ca un client OAuth prin înregistrare dinamică a clientului (RFC 7591)
3. Îți va deschide browserul pentru a autoriza accesul
4. Va primi tokenurile și se va conecta la serverul MCP
Conexiunile ulterioare reutilizează tokenurile stocate și le reîmprospătează automat.
### Opțiunea B — Cheie API
Dacă clientul tău MCP nu suportă OAuth sau preferi acreditări statice, furnizează o cheie API în antetul `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Cheia ta API oferă acces la datele spațiului de lucru. Ține-o în afara controlului versiunilor și a dotfiles partajate.
</Warning>
Pentru a crea o cheie API, mergi la **Settings > APIs & Webhooks > + Create key**. Vezi [API-uri](/l/ro/developers/extend/api#create-an-api-key) pentru detalii.
## Pornire rapidă
### 1. Copiază configurația
Mergi la **Settings > AI > More > MCP Server** în Twenty. Alege metoda de autentificare (OAuth sau Cheie API), copiază fragmentul JSON (va folosi deja URL-ul spațiului tău de lucru) și lipește-l în fișierul de configurare al clientului tău MCP.
| Client | Locația fișierului de configurare |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) sau `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (utilizator) sau `.mcp.json` (proiect) |
| **Cursor** | `.cursor/mcp.json` în proiectul tău sau `~/.cursor/mcp.json` global |
| **ChatGPT** | Activează Modul pentru dezvoltatori în **Settings > Apps & Connectors > Advanced settings**, apoi folosește **Create** în **Settings > Apps & Connectors** pentru a adăuga serverul MCP |
### 2. Conectează-te
Repornește clientul tău MCP (sau reîncarcă configurația). Dacă folosești OAuth, vei fi redirecționat către Twenty pentru a autoriza accesul. Dacă folosești o cheie API, conexiunea este imediată.
### 3. Începe să-l folosești
Roagă-ți asistentul AI să interacționeze cu CRM-ul tău:
* *"Arată-mi cele 5 companii create cel mai recent"*
* *"Creează o persoană nouă numită Jane Doe la Acme Corp"*
* *"Găsește toate oportunitățile deschise cu o valoare mai mare de $10k"*
## Instrumente disponibile
După conectare, serverul MCP expune instrumente care oglindesc API-ul Twenty. Fluxul de lucru recomandat este:
1. **`get_tool_catalog`** — descoperă toate instrumentele disponibile
2. **`learn_tools`** — obține schema de intrare pentru instrumente specifice
3. **`execute_tool`** — rulează un instrument
Nu este nevoie să reții numele instrumentelor. Întreabă-ți asistentul AI ce poate face și va apela `get_tool_catalog` automat.
## Permisiuni
Conexiunile MCP moștenesc permisiunile utilizatorului autentificat (OAuth) sau rolul atribuit cheii API. Pentru a restricționa ce poate face serverul MCP:
* **OAuth**: Se aplică rolul utilizatorului din spațiul de lucru.
* **Cheie API**: Atribuie un rol cheii API în **Settings > Roles**. Vezi [Permisiuni](/l/ro/user-guide/permissions-access/capabilities/permissions).
## Configurație pentru auto-găzduire
Pentru instanțele auto-găzduite, înlocuiește `{your-workspace-url}` cu URL-ul serverului tău. Asigură-te că `SERVER_URL` din mediul tău corespunde URL-ului public al instanței tale Twenty — acesta este folosit pentru a genera metadatele de descoperire OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
Punctul final MCP, punctele finale OAuth și metadatele de descoperire derivă toate din această valoare.
## Depanare
**Erori "Unauthorized" sau 401**
* OAuth: reautorizează ștergând tokenurile stocate din clientul tău MCP și reconectează-te.
* Cheie API: verifică dacă cheia este validă și nu a expirat. Regenereaz-o dacă este necesar.
**Fluxul OAuth nu deschide un browser**
* Asigură-te că clientul tău MCP suportă MCP Authorization. Dacă nu, revino la metoda cu Cheie API.
**Timeout de conexiune**
* Confirmă că URL-ul punctului final MCP este accesibil de pe calculatorul tău. Pentru instanțele auto-găzduite, verifică faptul că serverul rulează și că `SERVER_URL` este setat corect.
@@ -0,0 +1,142 @@
---
title: Сервер MCP
description: Подключайте ИИ-ассистентов к вашему рабочему пространству Twenty с помощью протокола Model Context Protocol.
---
<Warning>
В настоящее время MCP находится на стадии **alpha** и доступен только в некоторых рабочих пространствах. В вашем рабочем пространстве он может быть ещё не включён.
</Warning>
Twenty предоставляет сервер [MCP](https://modelcontextprotocol.io/), чтобы ИИ-ассистенты — Claude Desktop, Claude Code, Cursor, ChatGPT и другие — могли читать и записывать ваши данные CRM на естественном языке.
Используйте **URL рабочей области** (URL, который вы используете для доступа к Twenty) в качестве конечной точки MCP. В Twenty Cloud URL вашей рабочей области может быть `https://{mycompany}.twenty.com` или собственный домен. Сервер доступен по адресу:
| Среда | Конечная точка MCP |
| --------------------------- | --------------------------------------------------------------------------------- |
| **Облако** | `https://{your-workspace-url}/mcp` (например, `https://mycompany.twenty.com/mcp`) |
| **Самостоятельный хостинг** | `https://{your-domain}/mcp` |
## Методы аутентификации
Есть два способа аутентифицировать ваш MCP-клиент: **OAuth** (рекомендуется) или **API Key**.
### Вариант A — OAuth (рекомендуется)
При использовании OAuth ваш MCP-клиент откроет окно браузера для входа в систему. Секреты не хранятся в файлах конфигурации, а токены обновляются автоматически.
<Note>
Для OAuth требуется MCP-клиент, поддерживающий [спецификацию авторизации MCP](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Её поддерживают Claude Desktop, Claude Code, Cursor и ChatGPT.
</Note>
Добавьте это в конфигурацию вашего MCP-клиента, заменив `{your-workspace-url}` на хост вашей рабочей области (например, `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
Вот и всё — ключ API не требуется. При первом подключении клиент выполнит:
1. Обнаружит метаданные OAuth Twenty через `/.well-known/oauth-protected-resource` и `/.well-known/oauth-authorization-server`
2. Зарегистрирует себя как OAuth-клиент через динамическую регистрацию клиентов (RFC 7591)
3. Откроет браузер для авторизации доступа
4. Получит токены и подключится к серверу MCP
При последующих подключениях используются сохранённые токены, которые автоматически обновляются.
### Вариант B — ключ API
Если ваш MCP-клиент не поддерживает OAuth или вы предпочитаете статические учётные данные, передайте ключ API в заголовке `Authorization`:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Ваш ключ API предоставляет доступ к данным рабочей области. Не храните его в системе контроля версий и общих dotfiles.
</Warning>
Чтобы создать ключ API, перейдите в **Settings > APIs & Webhooks > + Create key**. См. [API](/l/ru/developers/extend/api#create-an-api-key) для подробностей.
## Быстрый старт
### 1. Скопируйте конфигурацию
Перейдите в **Settings > AI > More > MCP Server** в Twenty. Выберите метод аутентификации (OAuth или ключ API), скопируйте фрагмент JSON (в нём уже будет использован URL вашей рабочей области) и вставьте его в файл конфигурации вашего MCP-клиента.
| Клиент | Расположение файла конфигурации |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) или `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (пользователь) или `.mcp.json` (проект) |
| **Cursor** | `.cursor/mcp.json` в вашем проекте или `~/.cursor/mcp.json` глобально |
| **ChatGPT** | Включите режим разработчика в **Settings > Apps & Connectors > Advanced settings**, затем используйте **Create** в **Settings > Apps & Connectors**, чтобы добавить сервер MCP |
### 2. Подключение
Перезапустите ваш MCP-клиент (или перезагрузите конфигурацию). Если используется OAuth, вы будете перенаправлены в Twenty для авторизации доступа. Если используется ключ API, подключение произойдёт сразу.
### 3. Начните использовать
Попросите вашего ИИ-ассистента взаимодействовать с вашей CRM:
* *"Покажи мне 5 последних созданных компаний"*
* *"Создай новый контакт по имени Jane Doe в компании Acme Corp"*
* *"Найди все открытые сделки стоимостью более $10 000"*
## Доступные инструменты
После подключения сервер MCP предоставляет инструменты, отражающие API Twenty. Рекомендуемый рабочий процесс:
1. **`get_tool_catalog`** — получить список всех доступных инструментов
2. **`learn_tools`** — получить схему входных данных для конкретных инструментов
3. **`execute_tool`** — запустить инструмент
Вам не нужно запоминать названия инструментов. Спросите у вашего ИИ-ассистента, что он умеет, и он автоматически вызовет `get_tool_catalog`.
## Разрешения
Подключения MCP наследуют разрешения аутентифицированного пользователя (OAuth) или роль, назначенную ключу API. Чтобы ограничить действия, доступные серверу MCP:
* **OAuth**: Применяется роль пользователя в рабочей области.
* **API Key**: Назначьте ключу API роль в **Settings > Roles**. См. [Разрешения](/l/ru/user-guide/permissions-access/capabilities/permissions).
## Конфигурация для самостоятельного хостинга
Для экземпляров с самостоятельным хостингом замените `{your-workspace-url}` на URL вашего сервера. Убедитесь, что значение `SERVER_URL` в вашей среде соответствует публичному URL экземпляра Twenty — оно используется для генерации метаданных обнаружения OAuth.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
Конечная точка MCP, конечные точки OAuth и метаданные обнаружения формируются на основе этого значения.
## Устранение неполадок
**Ошибки "Unauthorized" или 401**
* OAuth: повторно авторизуйтесь, очистив сохранённые токены в вашем MCP-клиенте и переподключившись.
* API Key: убедитесь, что ключ действителен и не истёк. При необходимости сгенерируйте его заново.
**Процесс OAuth не открывает браузер**
* Убедитесь, что ваш MCP-клиент поддерживает MCP Authorization. Если нет — используйте метод с ключом API.
**Тайм-аут подключения**
* Убедитесь, что URL конечной точки MCP доступен с вашего компьютера. Для экземпляров с самостоятельным хостингом проверьте, что сервер запущен и значение `SERVER_URL` установлено корректно.
@@ -0,0 +1,142 @@
---
title: MCP Sunucusu
description: Model Context Protocol kullanarak yapay zeka asistanlarını Twenty çalışma alanınıza bağlayın.
---
<Warning>
MCP şu anda **alfa** aşamasındadır ve yalnızca bazı çalışma alanlarında kullanılabilir. Çalışma alanınızda henüz etkinleştirilmemiş olabilir.
</Warning>
Twenty, yapay zeka asistanlarının — Claude Desktop, Claude Code, Cursor, ChatGPT ve diğerlerinin — doğal dil aracılığıyla CRM verilerinizi okuması ve yazması için bir [MCP](https://modelcontextprotocol.io/) sunucusu sunar.
MCP uç noktası olarak **çalışma alanı URL'nizi** (Twenty'ye erişmek için kullandığınız URL) kullanın. Twenty Cloud'da, çalışma alanı URL'niz `https://{mycompany}.twenty.com` ya da özel bir alan adı olabilir. Sunucu şu adreste kullanılabilir:
| Ortam | MCP Uç Noktası |
| ---------------------------- | ---------------------------------------------------------------------------- |
| **Bulut** | `https://{your-workspace-url}/mcp` (örn. `https://mycompany.twenty.com/mcp`) |
| **Kendi Kendine Barındırma** | `https://{your-domain}/mcp` |
## Kimlik Doğrulama Yöntemleri
MCP istemcinizi kimlik doğrulamak için iki yolunuz var: **OAuth** (önerilir) veya **API Anahtarı**.
### Seçenek A — OAuth (Önerilir)
OAuth ile, MCP istemciniz oturum açmanız için bir tarayıcı penceresi açar. Gizli bilgiler yapılandırma dosyalarında saklanmaz ve belirteçler otomatik olarak yenilenir.
<Note>
OAuth, [MCP Yetkilendirme belirtimi](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization)ni destekleyen bir MCP istemcisi gerektirir. Claude Desktop, Claude Code, Cursor ve ChatGPT bunu destekler.
</Note>
Bunu MCP istemci yapılandırmanıza ekleyin; `{your-workspace-url}` öğesini çalışma alanınızın ana makine adıyla değiştirin (örn. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
Hepsi bu — API anahtarı gerekmez. İstemci ilk kez bağlandığında şunları yapacaktır:
1. Twenty'nin OAuth üst verilerini `/.well-known/oauth-protected-resource` ve `/.well-known/oauth-authorization-server` üzerinden keşfeder
2. Dinamik istemci kaydı (RFC 7591) yoluyla kendisini bir OAuth istemcisi olarak kaydeder
3. Erişimi yetkilendirmek için tarayıcınızı açar
4. Belirteçleri alır ve MCP sunucusuna bağlanır
Sonraki bağlantılar, depolanan belirteçleri yeniden kullanır ve bunları otomatik olarak yeniler.
### Seçenek B — API Anahtarı
MCP istemciniz OAuth'u desteklemiyorsa veya statik kimlik bilgilerini tercih ediyorsanız, `Authorization` üstbilgisinde bir API anahtarı gönderin:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
API anahtarınız çalışma alanı verilerine erişim sağlar. Sürüm kontrolünün ve paylaşılan dotfile'ların dışında tutun.
</Warning>
Bir API anahtarı oluşturmak için **Settings > APIs & Webhooks > + Create key** bölümüne gidin. Ayrıntılar için [API'ler](/l/tr/developers/extend/api#create-an-api-key) bölümüne bakın.
## Hızlı Başlangıç
### 1. Yapılandırmayı kopyalayın
Twenty içinde **Settings > AI > More > MCP Server** bölümüne gidin. Kimlik doğrulama yöntemini (OAuth veya API Anahtarı) seçin, JSON parçacığını kopyalayın (çalışma alanı URL'nizi zaten kullanacaktır) ve MCP istemcinizin yapılandırma dosyasına yapıştırın.
| İstemci | Yapılandırma dosyasının konumu |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) veya `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (kullanıcı) veya `.mcp.json` (proje) |
| **Cursor** | Projenizde `.cursor/mcp.json` veya genel olarak `~/.cursor/mcp.json` |
| **ChatGPT** | Developer Mode'u **Settings > Apps & Connectors > Advanced settings** içinde açın, ardından MCP sunucusunu eklemek için **Settings > Apps & Connectors** içindeki **Create** seçeneğini kullanın |
### 2. Bağlan
MCP istemcinizi yeniden başlatın (veya yapılandırmayı yeniden yükleyin). OAuth kullanıyorsanız, erişimi yetkilendirmek için Twenty'ye yönlendirileceksiniz. API anahtarı kullanıyorsanız, bağlantı anında kurulur.
### 3. Kullanmaya başlayın
Yapay zeka asistanınızdan CRM'inizle etkileşime geçmesini isteyin:
* *"Bana en son oluşturulan 5 şirketi göster"*
* *"Acme Corp'ta Jane Doe adlı yeni bir kişi oluştur"*
* *"10.000 $'dan fazla değere sahip tüm açık fırsatları bul"*
## Kullanılabilir Araçlar
Bağlandıktan sonra, MCP sunucusu Twenty API'sini yansıtan araçlar sunar. Önerilen iş akışı:
1. **`get_tool_catalog`** — mevcut tüm araçları keşfedin
2. **`learn_tools`** — belirli araçların girdi şemasını alın
3. **`execute_tool`** — bir aracı çalıştırın
Araç adlarını hatırlamanıza gerek yok. Yapay zeka asistanınıza neler yapabileceğini sorun; `get_tool_catalog` çağrısını otomatik olarak yapacaktır.
## İzinler
MCP bağlantıları, kimliği doğrulanmış kullanıcının (OAuth) izinlerini veya API anahtarına atanan rolü devralır. MCP sunucusunun neler yapabileceğini kısıtlamak için:
* **OAuth**: Kullanıcının çalışma alanı rolü geçerlidir.
* **API Anahtarı**: **Settings > Roles** altında API anahtarına bir rol atayın. Bkz. [İzinler](/l/tr/user-guide/permissions-access/capabilities/permissions).
## Öz Barındırmalı Yapılandırma
Öz barındırmalı kurulumlarda `{your-workspace-url}` değerini sunucunuzun URL'siyle değiştirin. Ortamınızdaki `SERVER_URL` değerinin Twenty örneğinizin genel URL'siyle eşleştiğinden emin olun — bu, OAuth keşif üst verilerini oluşturmak için kullanılır.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
MCP uç noktası, OAuth uç noktaları ve keşif üst verilerinin tümü bu değerden türetilir.
## Sorun Giderme
**"Unauthorized" veya 401 hataları**
* OAuth: MCP istemcinizde depolanan belirteçleri temizleyip yeniden bağlanarak yeniden yetkilendirin.
* API Anahtarı: anahtarın geçerli olduğunu ve süresinin dolmadığını doğrulayın. Gerekirse yeniden oluşturun.
**OAuth akışı bir tarayıcı açmıyor**
* MCP istemcinizin MCP Yetkilendirmeyi desteklediğinden emin olun. Desteklemiyorsa API Anahtarı yöntemine geri dönün.
**Bağlantı zaman aşımı**
* MCP uç noktası URL'sine makinenizden erişilebildiğini doğrulayın. Öz barındırmalı kurulumlar için, sunucunun çalıştığını ve `SERVER_URL` değerinin doğru ayarlandığını kontrol edin.
@@ -0,0 +1,142 @@
---
title: MCP服务器
description: 使用 Model Context Protocol 将 AI 助手连接到你的 Twenty 工作区。
---
<Warning>
MCP 目前处于**alpha**阶段,并且仅在部分工作区可用。 它可能尚未在你的工作区启用。
</Warning>
Twenty 提供一个 [MCP](https://modelcontextprotocol.io/) 服务器,使 AI 助手——Claude Desktop、Claude Code、Cursor、ChatGPT 等——能够通过自然语言读取和写入您的 CRM 数据。
将您的**工作区 URL**(用于访问 Twenty 的 URL)用作 MCP 端点。 在 Twenty Cloud 上,您的工作区 URL 可能是 `https://{mycompany}.twenty.com`,也可能是自定义域名。 服务器可通过以下地址访问:
| 环境 | MCP 端点 |
| ------- | -------------------------------------------------------------------------- |
| **云端** | `https://{your-workspace-url}/mcp` (例如 `https://mycompany.twenty.com/mcp`) |
| **自托管** | `https://{your-domain}/mcp` |
## 身份验证方法
您可以通过两种方式对 MCP 客户端进行身份验证:**OAuth**(推荐)或 **API 密钥**。
### 选项 A — OAuth(推荐)
使用 OAuth 时,您的 MCP 客户端会打开浏览器窗口供您登录。 不会在配置文件中存储任何机密,令牌会自动刷新。
<Note>
OAuth 需要支持[MCP 授权规范](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization)的 MCP 客户端。 Claude Desktop、Claude Code、Cursor 和 ChatGPT 均支持它。
</Note>
将以下内容添加到 MCP 客户端配置中,并将 `{your-workspace-url}` 替换为您的工作区主机(例如 `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
就这样——无需 API 密钥。 客户端首次连接时将:
1. 通过 `/.well-known/oauth-protected-resource` 和 `/.well-known/oauth-authorization-server` 发现 Twenty 的 OAuth 元数据
2. 通过动态客户端注册(RFC 7591)将自身注册为 OAuth 客户端
3. 打开您的浏览器以授权访问
4. 接收令牌并连接到 MCP 服务器
后续连接会重用已存储的令牌并自动刷新。
### 选项 B — API 密钥
如果您的 MCP 客户端不支持 OAuth,或您更喜欢静态凭据,请在 `Authorization` 请求头中传递 API 密钥:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
您的 API 密钥可访问工作区数据。 请不要将其纳入版本控制,也不要放入共享的 dotfiles。
</Warning>
要创建 API 密钥,请前往 **Settings > APIs & Webhooks > + Create key**。 详见[API](/l/zh/developers/extend/api#create-an-api-key)。
## 快速开始
### 1. 复制配置
在 Twenty 中前往 **Settings > AI > More > MCP Server**。 选择您的身份验证方式(OAuth 或 API 密钥),复制 JSON 片段(其中已包含您的工作区 URL),并将其粘贴到 MCP 客户端的配置文件中。
| 客户端 | 配置文件位置 |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) 或 `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (用户) 或 `.mcp.json` (项目) |
| **Cursor** | 在您的项目中为 `.cursor/mcp.json`,或全局为 `~/.cursor/mcp.json` |
| **ChatGPT** | 在 **Settings > Apps & Connectors > Advanced settings** 中开启开发者模式,然后在 **Settings > Apps & Connectors** 中使用 **Create** 添加 MCP 服务器 |
### 2. 连接
重启您的 MCP 客户端(或重新加载配置)。 如果使用 OAuth,您将被重定向到 Twenty 以授权访问。 如果使用 API 密钥,连接会立即建立。
### 3. 开始使用
让您的 AI 助手与您的 CRM 交互:
* *"给我展示最近创建的 5 家公司"*
* *"在 Acme Corp 新建一位名为 Jane Doe 的联系人"*
* *"查找所有价值超过 $10k 的未关闭商机"*
## 可用工具
连接后,MCP 服务器会提供与 Twenty API 一一对应的工具。 推荐的工作流程是:
1. **`get_tool_catalog`** — 发现所有可用工具
2. **`learn_tools`** — 获取特定工具的输入模式
3. **`execute_tool`** — 运行工具
您无需记住工具名称。 询问您的 AI 助手它能做什么,它会自动调用 `get_tool_catalog`。
## 权限
MCP 连接将继承已通过身份验证的用户(OAuth)的权限,或继承分配给 API 密钥的角色。 要限制 MCP 服务器的操作范围:
* **OAuth**:生效的是该用户在工作区中的角色。
* **API 密钥**:在 **Settings > Roles** 下为该 API 密钥分配角色。 参见[权限](/l/zh/user-guide/permissions-access/capabilities/permissions)。
## 自托管配置
对于自托管实例,请将 `{your-workspace-url}` 替换为您的服务器 URL。 请确保您环境中的 `SERVER_URL` 与 Twenty 实例的公共 URL 一致——该值用于生成 OAuth 发现元数据。
```bash
SERVER_URL=https://twenty.yourcompany.com
```
MCP 端点、OAuth 端点以及发现元数据都基于该值生成。
## 故障排除
**"Unauthorized" 或 401 错误**
* OAuth:清除 MCP 客户端中存储的令牌并重新连接以重新授权。
* API 密钥:验证该密钥有效且未过期。 如有需要,请重新生成。
**OAuth 流程未打开浏览器**
* 确保您的 MCP 客户端支持 MCP 授权。 如果不支持,请改用 API 密钥方法。
**连接超时**
* 确认从您的机器可以访问 MCP 端点 URL。 对于自托管实例,请检查服务器是否正在运行,并确认 `SERVER_URL` 设置正确。
@@ -0,0 +1,139 @@
---
title: MCP Server
description: Connect AI assistants to your Twenty workspace using the Model Context Protocol.
---
<Warning>
MCP is currently in **alpha** and is only available on some workspaces. It may not be enabled for your workspace yet.
</Warning>
Twenty exposes an [MCP](https://modelcontextprotocol.io/) server so that AI assistants — Claude Desktop, Claude Code, Cursor, ChatGPT, and others — can read and write your CRM data through natural language.
Use your **workspace URL** (the URL you use to access Twenty) as the MCP endpoint. On Twenty Cloud, your workspace URL might be `https://{mycompany}.twenty.com` or a custom domain. The server is available at:
| Environment | MCP Endpoint |
|-------------|-------------|
| **Cloud** | `https://{your-workspace-url}/mcp` (e.g. `https://mycompany.twenty.com/mcp`) |
| **Self-Hosted** | `https://{your-domain}/mcp` |
## Authentication Methods
You have two ways to authenticate your MCP client: **OAuth** (recommended) or **API Key**.
### Option A — OAuth (Recommended)
With OAuth, your MCP client opens a browser window for you to log in. No secrets are stored in config files, and tokens refresh automatically.
<Note>
OAuth requires an MCP client that supports the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). Claude Desktop, Claude Code, Cursor, and ChatGPT support it.
</Note>
Add this to your MCP client configuration, replacing `{your-workspace-url}` with your workspace host (e.g. `mycompany.twenty.com`):
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp"
}
}
}
```
That's it — no API key needed. When the client connects for the first time it will:
1. Discover Twenty's OAuth metadata via `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`
2. Register itself as an OAuth client via dynamic client registration (RFC 7591)
3. Open your browser to authorize access
4. Receive tokens and connect to the MCP server
Subsequent connections reuse the stored tokens and refresh them automatically.
### Option B — API Key
If your MCP client does not support OAuth, or you prefer static credentials, pass an API key in the `Authorization` header:
```json
{
"mcpServers": {
"twenty": {
"type": "streamable-http",
"url": "https://{your-workspace-url}/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
<Warning>
Your API key grants access to workspace data. Keep it out of version control and shared dotfiles.
</Warning>
To create an API key, go to **Settings > APIs & Webhooks > + Create key**. See [APIs](/developers/extend/api#create-an-api-key) for details.
## Quick Start
### 1. Copy the config
Go to **Settings > AI > More > MCP Server** in Twenty. Choose your authentication method (OAuth or API Key), copy the JSON snippet (it will already use your workspace URL), and paste it into your MCP client's config file.
| Client | Config file location |
|--------|---------------------|
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Claude Code** | `~/.claude.json` (user) or `.mcp.json` (project) |
| **Cursor** | `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` globally |
| **ChatGPT** | Turn on Developer Mode in **Settings > Apps & Connectors > Advanced settings**, then use **Create** in **Settings > Apps & Connectors** to add the MCP server |
### 2. Connect
Restart your MCP client (or reload the config). If using OAuth you will be redirected to Twenty to authorize access. If using an API key the connection is immediate.
### 3. Start using it
Ask your AI assistant to interact with your CRM:
- *"Show me the 5 most recently created companies"*
- *"Create a new person named Jane Doe at Acme Corp"*
- *"Find all open opportunities worth more than $10k"*
## Available Tools
Once connected, the MCP server exposes tools that mirror the Twenty API. The recommended workflow is:
1. **`get_tool_catalog`** — discover all available tools
2. **`learn_tools`** — get the input schema for specific tools
3. **`execute_tool`** — run a tool
You don't need to remember tool names. Ask your AI assistant what it can do and it will call `get_tool_catalog` automatically.
## Permissions
MCP connections inherit the permissions of the authenticated user (OAuth) or the role assigned to the API key. To restrict what the MCP server can do:
- **OAuth**: The user's workspace role applies.
- **API Key**: Assign a role to the API key under **Settings > Roles**. See [Permissions](/user-guide/permissions-access/capabilities/permissions).
## Self-Hosted Configuration
For self-hosted instances, replace `{your-workspace-url}` with your server URL. Make sure `SERVER_URL` in your environment matches the public URL of your Twenty instance — this is used to generate the OAuth discovery metadata.
```bash
SERVER_URL=https://twenty.yourcompany.com
```
The MCP endpoint, OAuth endpoints, and discovery metadata all derive from this value.
## Troubleshooting
**"Unauthorized" or 401 errors**
- OAuth: re-authorize by clearing the stored tokens in your MCP client and reconnecting.
- API Key: verify the key is valid and hasn't expired. Regenerate it if needed.
**OAuth flow doesn't open a browser**
- Ensure your MCP client supports MCP Authorization. Fall back to the API Key method if it doesn't.
**Connection timeout**
- Confirm the MCP endpoint URL is reachable from your machine. For self-hosted instances, check that the server is running and `SERVER_URL` is set correctly.
@@ -1,19 +1,23 @@
/* oxlint-disable no-console */
import { generateApiKeys } from './mock-data/generate-api-keys.js';
import { generateBillingPlans } from './mock-data/generate-billing-plans.js';
import { generateMetadata } from './mock-data/generate-metadata.js';
import { generateObjectMetadata } from './mock-data/generate-object-metadata.js';
import { generateMinimalMetadata } from './mock-data/generate-minimal-metadata.js';
import { generateRecordData } from './mock-data/generate-record-data.js';
import { generateRoles } from './mock-data/generate-roles.js';
import { generateNavigationMenuItems } from './mock-data/generate-navigation-menu-items.js';
import { generateViews } from './mock-data/generate-views.js';
import { authenticate } from './mock-data/utils.js';
const main = async () => {
const token = await authenticate();
const metadata = await generateMetadata(token);
const metadata = await generateObjectMetadata(token);
await generateMinimalMetadata(token);
await generateRecordData(token, metadata);
await generateRoles(token);
await generateViews(token);
await generateNavigationMenuItems(token);
try {
await generateBillingPlans(token);
@@ -0,0 +1,51 @@
/* oxlint-disable no-console */
import { graphqlRequest, writeGeneratedFile } from './utils.js';
const MINIMAL_METADATA_QUERY = `
query FindMinimalMetadata {
minimalMetadata {
objectMetadataItems {
id
nameSingular
namePlural
labelSingular
labelPlural
icon
isCustom
isActive
isSystem
isRemote
}
views {
id
type
key
objectMetadataId
}
collectionHashes {
collectionName
hash
}
}
}
`;
export const generateMinimalMetadata = async (token: string) => {
console.log('Fetching minimal metadata from /metadata ...');
const data = (await graphqlRequest(
'/metadata',
MINIMAL_METADATA_QUERY,
token,
)) as {
minimalMetadata: Record<string, unknown>;
};
writeGeneratedFile(
'metadata/minimal/mock-minimal-metadata.ts',
'mockedMinimalMetadata',
'MinimalMetadata',
"import { type MinimalMetadata } from '@/metadata-store/types/MinimalMetadata';",
data.minimalMetadata,
);
};
@@ -0,0 +1,50 @@
/* oxlint-disable no-console, lingui/no-unlocalized-strings */
import { graphqlRequest, writeGeneratedFile } from './utils.js';
const FIND_MANY_NAVIGATION_MENU_ITEMS_QUERY = `
query FindManyNavigationMenuItems {
navigationMenuItems {
id
userWorkspaceId
targetRecordId
targetObjectMetadataId
viewId
folderId
name
link
icon
color
position
applicationId
createdAt
updatedAt
targetRecordIdentifier {
id
labelIdentifier
imageIdentifier
}
}
}
`;
export const generateNavigationMenuItems = async (token: string) => {
console.log('Fetching navigation menu items from /metadata ...');
const data = (await graphqlRequest(
'/metadata',
FIND_MANY_NAVIGATION_MENU_ITEMS_QUERY,
token,
)) as {
navigationMenuItems: Record<string, unknown>[];
};
console.log(` Got ${data.navigationMenuItems.length} navigation menu items.`);
writeGeneratedFile(
'metadata/navigation-menu-items/mock-navigation-menu-items-data.ts',
'mockedNavigationMenuItems',
'NavigationMenuItem[]',
"import { type NavigationMenuItem } from '~/generated-metadata/graphql';",
data.navigationMenuItems,
);
};
@@ -141,7 +141,7 @@ const METADATA_QUERY = `
}
`;
export const generateMetadata = async (token: string) => {
export const generateObjectMetadata = async (token: string) => {
console.log('Fetching object metadata from /metadata ...');
const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token);
@@ -1,9 +1,9 @@
/* oxlint-disable no-console, lingui/no-unlocalized-strings */
import { graphqlRequest, writeGeneratedFile } from './utils.js';
const FIND_ALL_CORE_VIEWS_QUERY = `
query FindAllCoreViews {
getCoreViews {
const FIND_ALL_VIEWS_QUERY = `
query FindAllViews {
getViews {
id
name
objectMetadataId
@@ -101,19 +101,19 @@ export const generateViews = async (token: string) => {
const data = (await graphqlRequest(
'/metadata',
FIND_ALL_CORE_VIEWS_QUERY,
FIND_ALL_VIEWS_QUERY,
token,
)) as {
getCoreViews: Record<string, unknown>[];
getViews: Record<string, unknown>[];
};
console.log(` Got ${data.getCoreViews.length} views.`);
console.log(` Got ${data.getViews.length} views.`);
writeGeneratedFile(
'metadata/views/mock-views-data.ts',
'mockedCoreViews',
'CoreViewWithRelations[]',
"import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';",
data.getCoreViews,
'mockedViews',
'ViewWithRelations[]',
"import { type ViewWithRelations } from '@/views/types/ViewWithRelations';",
data.getViews,
);
};
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
@@ -39,9 +39,9 @@ const setupMockIsMatchingLocation = (pathname: string) => {
);
};
jest.mock('@/auth/hooks/useIsLogged');
const setupMockIsLogged = (isLogged: boolean) => {
jest.mocked(useIsLogged).mockReturnValueOnce(isLogged);
jest.mock('@/auth/hooks/useHasAccessTokenPair');
const setupMockHasAccessTokenPair = (hasAccessTokenPair: boolean) => {
jest.mocked(useHasAccessTokenPair).mockReturnValueOnce(hasAccessTokenPair);
};
const defaultHomePagePath = '/objects/companies';
@@ -83,7 +83,7 @@ const setupMockState = (
// prettier-ignore
const testCases: {
loc: AppPath;
isLoggedIn: boolean;
hasAccessTokenPair: boolean;
isWorkspaceSuspended: boolean;
onboardingStatus: OnboardingStatus | undefined;
res: string | undefined;
@@ -93,248 +93,248 @@ const testCases: {
verifyEmailRedirectPath?: string;
returnToPath?: string;
}[] = [
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Verify, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Verify, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.SignInUp, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.SignInUp, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Invite, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Invite, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.ResetPassword, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailRedirectPath: '/nextPath?key=value', res: '/nextPath?key=value' },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailRedirectPath: '/nextPath?key=value', res: undefined },
{ loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailRedirectPath: '/nextPath?key=value', res: '/nextPath?key=value' },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailRedirectPath: '/nextPath?key=value', res: undefined },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.CreateWorkspace, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.CreateProfile, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: undefined },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: undefined },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.SyncEmails, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: undefined },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: undefined },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.InviteTeam, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: undefined },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: undefined },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.BookCallDecision, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.BookCall, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.BookCall, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.PlanRequired, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Index, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Index, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.TasksPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.TasksPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.RecordIndexPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, objectNamePluralFromParams: 'existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, objectNamePluralFromParams: 'non-existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, objectNamePluralFromParams: 'existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, objectNamePluralFromParams: 'non-existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.RecordShowPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Authorize, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.Authorize, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.NotFound, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) },
{ loc: AppPath.NotFound, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
// returnToPath: should redirect to saved path instead of defaultHomePagePath
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/authorize?clientId=abc', res: '/authorize?clientId=abc' },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/objects/tasks', res: '/objects/tasks' },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/settings/api-keys', res: '/settings/api-keys' },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/authorize?clientId=abc', res: '/authorize?clientId=abc' },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/objects/tasks', res: '/objects/tasks' },
{ loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/settings/api-keys', res: '/settings/api-keys' },
// isOnAWorkspace:false — on default domain, don't redirect to returnToPath or defaultHomePagePath from auth pages
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined },
{ loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined },
{ loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined },
];
describe('usePageChangeEffectNavigateLocation', () => {
@@ -344,7 +344,7 @@ describe('usePageChangeEffectNavigateLocation', () => {
loc,
onboardingStatus,
isWorkspaceSuspended,
isLoggedIn,
hasAccessTokenPair,
isOnAWorkspace,
objectNamePluralFromParams,
objectNamePluralFromMetadata,
@@ -355,7 +355,7 @@ describe('usePageChangeEffectNavigateLocation', () => {
setupMockIsMatchingLocation(loc);
setupMockOnboardingStatus(onboardingStatus);
setupMockIsWorkspaceActivationStatusEqualsTo(isWorkspaceSuspended);
setupMockIsLogged(isLoggedIn);
setupMockHasAccessTokenPair(hasAccessTokenPair);
setupMockIsOnAWorkspace(isOnAWorkspace ?? true);
setupMockUseParams(objectNamePluralFromParams);
setupMockState(
@@ -1,7 +1,7 @@
import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState';
import { ONBOARDING_PATHS } from '@/auth/constants/OnboardingPaths';
import { ONGOING_USER_CREATION_PATHS } from '@/auth/constants/OngoingUserCreationPaths';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { returnToPathState } from '@/auth/states/returnToPathState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
@@ -26,7 +26,7 @@ const readReturnToPathFromUrlSearchParams = (): string | null => {
};
export const usePageChangeEffectNavigateLocation = () => {
const isLoggedIn = useIsLogged();
const hasAccessTokenPair = useHasAccessTokenPair();
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
const onboardingStatus = useOnboardingStatus();
const isWorkspaceSuspended = useIsWorkspaceActivationStatusEqualsTo(
@@ -54,7 +54,7 @@ export const usePageChangeEffectNavigateLocation = () => {
: readReturnToPathFromUrlSearchParams();
if (
(!isLoggedIn || (isLoggedIn && !isOnAWorkspace)) &&
(!hasAccessTokenPair || (hasAccessTokenPair && !isOnAWorkspace)) &&
!someMatchingLocationOf([
...ONGOING_USER_CREATION_PATHS,
AppPath.ResetPassword,
@@ -140,13 +140,13 @@ export const usePageChangeEffectNavigateLocation = () => {
...ONGOING_USER_CREATION_PATHS,
]) &&
!isMatchingLocation(location, AppPath.ResetPassword) &&
isLoggedIn &&
hasAccessTokenPair &&
isOnAWorkspace
) {
return resolvedReturnToPath ?? defaultHomePagePath;
}
if (isMatchingLocation(location, AppPath.Index) && isLoggedIn) {
if (isMatchingLocation(location, AppPath.Index) && hasAccessTokenPair) {
return resolvedReturnToPath ?? defaultHomePagePath;
}
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Oor hierdie werkruimte"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Kry toegang tot jou werksruimte data vanaf jou gunsteling MCP-kliënt soos Claude Desktop, Windsurf of Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webtuistes"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-sleutel"
@@ -2131,6 +2132,11 @@ msgstr "Magtiging"
msgid "Authentication failed"
msgstr "Magtiging het misluk"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "عن مساحة العمل هذه"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "الوصول إلى بيانات مساحة العمل الخاصة بك من عميل MCP المفضل لديك مثل Claude Desktop و Windsurf أو Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "واجهة برمجة التطبيقات والويب هوك"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "مفتاح واجهة برمجة التطبيقات"
@@ -2131,6 +2132,11 @@ msgstr "المصادقة"
msgid "Authentication failed"
msgstr "فشل المصادقة"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Sobre aquest espai de treball"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Accediu a les dades del vostre espai de treball des del vostre client MCP preferit, com Claude Desktop, Windsurf o Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API i Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Clau d'API"
@@ -2131,6 +2132,11 @@ msgstr "Autenticació"
msgid "Authentication failed"
msgstr "Error en l'autenticació"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "O tomto pracovním prostoru"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Přistupujte ke svým pracovním datům z vašeho oblíbeného MCP klienta, jako je Claude Desktop, Windsurf nebo Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API klíč"
@@ -2131,6 +2132,11 @@ msgstr "Ověření"
msgid "Authentication failed"
msgstr "Ověření selhalo"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Om dette arbejdsområde"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Adgang til dine arbejdsdata fra din foretrukne MCP-klient som Claude Desktop, Windsurf eller Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-nøgle"
@@ -2131,6 +2132,11 @@ msgstr "Godkendelse"
msgid "Authentication failed"
msgstr "Godkendelse mislykkedes"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Über diesen Arbeitsbereich"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Greifen Sie auf Ihre Workspace-Daten von Ihrem bevorzugten MCP-Client wie Claude Desktop, Windsurf oder Cursor zu."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-Schlüssel"
@@ -2131,6 +2132,11 @@ msgstr "Authentifizierung"
msgid "Authentication failed"
msgstr "Authentifizierung fehlgeschlagen"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Σχετικά με αυτό το χώρο εργασίας"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Πρόσβαση στα δεδομένα του χώρου εργασίας σας από τον αγαπημένο σας πελάτη MCP, όπως το Claude Desktop, το Windsurf ή το Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API Key"
@@ -2131,6 +2132,11 @@ msgstr "Ταυτοποίηση"
msgid "Authentication failed"
msgstr "Αποτυχία ταυτοποίησης"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -658,10 +658,10 @@ msgstr "About this workspace"
msgid "Access workspace data"
msgstr "Access workspace data"
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1676,6 +1676,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API Key"
@@ -2126,6 +2127,11 @@ msgstr "Authentication"
msgid "Authentication failed"
msgstr "Authentication failed"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr "Authentication method"
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9793,6 +9799,7 @@ msgstr "Numbered list"
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr "OAuth"
@@ -9801,6 +9808,11 @@ msgstr "OAuth"
msgid "OAuth Credentials"
msgstr "OAuth Credentials"
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr "OAuth or API Key"
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Sobre este espacio de trabajo"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Accede a los datos de tu espacio de trabajo desde tu cliente MCP favorito, como Claude Desktop, Windsurf o Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API y Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Clave API"
@@ -2131,6 +2132,11 @@ msgstr "Autenticación"
msgid "Authentication failed"
msgstr "Autenticación fallida"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr "Lista numerada"
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Tietoa tästä työtilasta"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Käytä työtilan tietoja suosikki MCP-asiakkaasi, kuten Claude Desktop, Windsurf tai Cursor, kautta."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-avain"
@@ -2131,6 +2132,11 @@ msgstr "Todennus"
msgid "Authentication failed"
msgstr "Todennus epäonnistui"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "À propos de cet espace de travail"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Accédez à vos données de l'espace de travail depuis votre client MCP préféré comme Claude Desktop, Windsurf ou Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Clé API"
@@ -2131,6 +2132,11 @@ msgstr "Authentification"
msgid "Authentication failed"
msgstr "Authentification échouée"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "אודות סביבת עבודה זו"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "גש לנתוני סביבת העבודה שלך מהלקוח MCP המועדף עליך כמו Claude Desktop, Windsurf או Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API ו-Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "מפתח API"
@@ -2131,6 +2132,11 @@ msgstr "אימות"
msgid "Authentication failed"
msgstr "האימות נכשל"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Erről a munkaterületről"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Hozzáférhet munkatéradataihoz a kedvenc MCP-klienséből, például a Claude Desktop, Windsurf vagy Cursor programokból."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API kulcs"
@@ -2131,6 +2132,11 @@ msgstr "Hitelesítés"
msgid "Authentication failed"
msgstr "Hitelesítés sikertelen"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Informazioni su questo spazio di lavoro"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Accedi ai dati del tuo spazio di lavoro dal tuo client MCP preferito come Claude Desktop, Windsurf o Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API e Webhook"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Chiave API"
@@ -2131,6 +2132,11 @@ msgstr "Autenticazione"
msgid "Authentication failed"
msgstr "Autenticazione fallita"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "このワークスペースについて"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "お好みの MCP クライアント (Claude Desktop、Windsurf や Cursor など) からワークスペース データにアクセスしてください。"
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "APIキー"
@@ -2131,6 +2132,11 @@ msgstr "認証"
msgid "Authentication failed"
msgstr "認証に失敗しました"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "이 작업공간에 대해"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Claude Desktop, Windsurf 또는 Cursor와 같은 좋아하는 MCP 클라이언트에서 워크스페이스 데이터를 액세스하세요."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API 및 웹훅"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API 키"
@@ -2131,6 +2132,11 @@ msgstr "인증"
msgid "Authentication failed"
msgstr "인증 실패"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Over deze werkruimte"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Toegang tot uw werkruimtegegevens vanuit uw favoriete MCP-client zoals Claude Desktop, Windsurf of Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-sleutel"
@@ -2131,6 +2132,11 @@ msgstr "Authenticatie"
msgid "Authentication failed"
msgstr "Authenticatie mislukt"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Om dette arbeidsområdet"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Tilgangen til arbeidsområde dataene dine fra din favoritt MCP klient som Claude Desktop, Windsurf eller Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API og webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-nøkkel"
@@ -2131,6 +2132,11 @@ msgstr "Autentisering"
msgid "Authentication failed"
msgstr "Autentisering mislyktes"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "O tej przestrzeni roboczej"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Uzyskaj dostęp do danych ze swojego ulubionego klienta MCP, takiego jak Claude Desktop, Windsurf czy Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API i Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Klucz API"
@@ -2131,6 +2132,11 @@ msgstr "Uwierzytelnianie"
msgid "Authentication failed"
msgstr "Uwierzytelnianie nie powiodło się"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+14 -2
View File
@@ -658,9 +658,9 @@ msgstr ""
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
@@ -1676,6 +1676,7 @@ msgstr ""
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr ""
@@ -2126,6 +2127,11 @@ msgstr ""
msgid "Authentication failed"
msgstr ""
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9793,6 +9799,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9801,6 +9808,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Sobre este espaço de trabalho"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Acesse seus dados de trabalho a partir do seu cliente MCP favorito, como Claude Desktop, Windsurf ou Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API e Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Chave API"
@@ -2131,6 +2132,11 @@ msgstr "Autenticação"
msgid "Authentication failed"
msgstr "Falha na autenticação"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Sobre este espaço de trabalho"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Acesse os dados do seu espaço de trabalho de seu cliente MCP favorito como Claude Desktop, Windsurf ou Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API e Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Chave API"
@@ -2131,6 +2132,11 @@ msgstr "Autenticação"
msgid "Authentication failed"
msgstr "Autenticação falhou"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Despre acest spațiu de lucru"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Accesați datele dvs. din spațiul de lucru de pe clientul MCP favorit, cum ar fi Claude Desktop, Windsurf sau Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Cheie API"
@@ -2131,6 +2132,11 @@ msgstr "Autentificare"
msgid "Authentication failed"
msgstr "Autentificare eșuată"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
Binary file not shown.
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "О овом радном простору"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Приступите вашим радним подацима са омиљеног MCP клијента као што су Claude Desktop, Windsurf или Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API и Вебхукси"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API кључ"
@@ -2131,6 +2132,11 @@ msgstr "Аутентификација"
msgid "Authentication failed"
msgstr "Аутентификација није успела"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Om den här arbetsytan"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Du får åtkomst till dina arbetsytedata från din favorit MCP-klient som Claude Desktop, Windsurf eller Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-nyckel"
@@ -2131,6 +2132,11 @@ msgstr "Autentisering"
msgid "Authentication failed"
msgstr "Autentisering misslyckades"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9800,6 +9806,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9808,6 +9815,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Bu çalışma alanı hakkında"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Claude Desktop, Windsurf veya Cursor gibi en sevdiğiniz MCP istemcisinden çalışma alanı verilerinize erişin."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhook'lar"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API Anahtar"
@@ -2131,6 +2132,11 @@ msgstr "Kimlik Doğrulama"
msgid "Authentication failed"
msgstr "Kimlik doğrulama başarısız"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Про цей робочий простір"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Отримуйте доступ до даних робочого простору з улюбленого MCP клієнта, як-то Claude Desktop, Windsurf або Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API та Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API-ключ"
@@ -2131,6 +2132,11 @@ msgstr "Аутентифікація"
msgid "Authentication failed"
msgstr "Не вдалося пройти аутентифікацію"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "Giới thiệu về không gian làm việc này"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "Truy cập dữ liệu không gian làm việc của bạn từ khách hàng MCP yêu thích như Claude Desktop, Windsurf hoặc Cursor."
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API & Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "Khóa API"
@@ -2131,6 +2132,11 @@ msgstr "Xác thực"
msgid "Authentication failed"
msgstr "Xác thực thất bại"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "关于此工作区"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "从您喜欢的MCP客户端(例如Claude Desktop、Windsurf或Cursor)访问您的工作区数据。"
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API 和 Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API 密钥"
@@ -2131,6 +2132,11 @@ msgstr "身份验证"
msgid "Authentication failed"
msgstr "身份验证失败"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
+15 -3
View File
@@ -663,10 +663,10 @@ msgstr "關於此工作區"
msgid "Access workspace data"
msgstr ""
#. js-lingui-id: c2UA7k
#. js-lingui-id: W5nhmk
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Windsurf or Cursor."
msgstr "從您最喜歡的MCP客戶端訪問您的工作區數據,如Claude桌面版、Windsurf或Cursor。"
msgid "Access your workspace data from your favorite MCP client like Claude Desktop, Claude Code, Cursor, or ChatGPT. Once connected, try: \"Show me the 5 most recently created companies\" or \"Create a new person named Jane Doe\"."
msgstr ""
#. js-lingui-id: u2Cycw
#: src/modules/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection.tsx
@@ -1681,6 +1681,7 @@ msgstr "API 和 Webhooks"
#. js-lingui-id: yRnk5W
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
#: src/modules/settings/playground/components/PlaygroundSetupForm.tsx
msgid "API Key"
msgstr "API 密鑰"
@@ -2131,6 +2132,11 @@ msgstr "身份驗證"
msgid "Authentication failed"
msgstr "身份驗證失敗"
#. js-lingui-id: TxT0ER
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "Authentication method"
msgstr ""
#. js-lingui-id: H7qgYQ
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
msgid "Authenticator app"
@@ -9798,6 +9804,7 @@ msgstr ""
#. js-lingui-id: b/j06W
#: src/pages/settings/applications/SettingsApplicationRegistrationDetails.tsx
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth"
msgstr ""
@@ -9806,6 +9813,11 @@ msgstr ""
msgid "OAuth Credentials"
msgstr ""
#. js-lingui-id: LNLTJB
#: src/pages/settings/ai/components/SettingsAIMCP.tsx
msgid "OAuth or API Key"
msgstr ""
#. js-lingui-id: pNEViR
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "object"
@@ -29,7 +29,7 @@ export const getActivityTargetObjectRecords = ({
const isNote = isDefined(activityRecord) && 'noteTargets' in activityRecord;
const targets = activityTargets
const rawTargets = activityTargets
? activityTargets
: 'noteTargets' in activityRecord && isDefined(activityRecord.noteTargets)
? activityRecord.noteTargets
@@ -37,6 +37,12 @@ export const getActivityTargetObjectRecords = ({
? activityRecord.taskTargets
: [];
if (!Array.isArray(rawTargets)) {
return [];
}
const targets = rawTargets;
const activityTargetObjectNameSingular = isNote
? CoreObjectNameSingular.NoteTarget
: CoreObjectNameSingular.TaskTarget;
@@ -15,7 +15,7 @@ import { recordStoreFamilySelector } from '@/object-record/record-store/states/s
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useStore } from 'jotai';
import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector';
import { viewFromViewIdFamilySelector } from '@/views/states/selectors/viewFromViewIdFamilySelector';
export const useGetBrowsingContext = () => {
const store = useStore();
@@ -102,7 +102,7 @@ export const useGetBrowsingContext = () => {
);
const currentView = store.get(
coreViewFromViewIdFamilySelector.selectorFamily({
viewFromViewIdFamilySelector.selectorFamily({
viewId: currentViewId ?? '',
}),
);
@@ -1,12 +1,12 @@
import { AgentChatProvider } from '@/ai/components/AgentChatProvider';
import { CommandMenuConfirmationModalManager } from '@/command-menu-item/confirmation-modal/components/CommandMenuConfirmationModalManager';
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
import { MetadataGater } from '@/metadata-store/components/MetadataGater';
import { IsAppMetadataReadyEffect } from '@/metadata-store/effect-components/IsAppMetadataReadyEffect';
import { MinimalMetadataGater } from '@/metadata-store/components/MinimalMetadataGater';
import { IsMinimalMetadataReadyEffect } from '@/metadata-store/effect-components/IsMinimalMetadataReadyEffect';
import { GotoHotkeysEffectsProvider } from '@/app/effect-components/GotoHotkeysEffectsProvider';
import { MinimalMetadataLoadEffect } from '@/metadata-store/effect-components/MinimalMetadataLoadEffect';
import { MetadataProviderInitialEffects } from '@/metadata-store/effect-components/MetadataProviderInitialEffects';
import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect';
import { PageChangeEffect } from '@/app/effect-components/PageChangeEffect';
import { AuthProvider } from '@/auth/components/AuthProvider';
import { CaptchaProvider } from '@/captcha/components/CaptchaProvider';
@@ -31,7 +31,6 @@ import { BaseThemeProvider } from '@/ui/theme/components/BaseThemeProvider';
import { UserThemeProviderEffect } from '@/ui/theme/components/UserThemeProviderEffect';
import { PageFavicon } from '@/ui/utilities/page-favicon/components/PageFavicon';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
import { LazyMetadataLoadEffect } from '@/users/components/LazyMetadataLoadEffect';
import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect';
import { StrictMode } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
@@ -45,16 +44,15 @@ export const AppRouterProviders = () => {
<ApolloProvider>
<BaseThemeProvider>
<ClientConfigProviderEffect />
<UserMetadataProviderInitialEffect />
<MinimalMetadataLoadEffect />
<MetadataProviderInitialEffects />
<LazyMetadataLoadEffect />
<IsAppMetadataReadyEffect />
<IsMinimalMetadataReadyEffect />
<WorkspaceProviderEffect />
<ClientConfigProvider>
<CaptchaProvider>
<ChromeExtensionSidecarEffect />
<ChromeExtensionSidecarProvider>
<MetadataGater>
<MinimalMetadataGater>
<AuthProvider>
<ApolloCoreProvider>
<SSEProvider>
@@ -88,7 +86,7 @@ export const AppRouterProviders = () => {
</SSEProvider>
</ApolloCoreProvider>
</AuthProvider>
</MetadataGater>
</MinimalMetadataGater>
</ChromeExtensionSidecarProvider>
</CaptchaProvider>
</ClientConfigProvider>
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin';
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
@@ -13,7 +13,7 @@ export const VerifyLoginTokenEffect = () => {
const [searchParams] = useSearchParams();
const loginToken = searchParams.get('loginToken');
const isLogged = useIsLogged();
const hasAccessTokenPair = useHasAccessTokenPair();
const navigate = useNavigateApp();
const { verifyLoginToken } = useVerifyLogin();
@@ -28,7 +28,7 @@ export const VerifyLoginTokenEffect = () => {
if (isDefined(loginToken)) {
verifyLoginToken(loginToken);
} else if (!isLogged) {
} else if (!hasAccessTokenPair) {
navigate(AppPath.SignInUp);
}
// Verify only needs to run once at mount
@@ -5,11 +5,13 @@ export const AUTHORIZE_APP = gql`
$clientId: String!
$codeChallenge: String
$redirectUrl: String!
$state: String
) {
authorizeApp(
clientId: $clientId
codeChallenge: $codeChallenge
redirectUrl: $redirectUrl
state: $state
) {
redirectUrl
}
@@ -4,10 +4,10 @@ export const FIND_APPLICATION_REGISTRATION_BY_CLIENT_ID = gql`
query FindApplicationRegistrationByClientId($clientId: String!) {
findApplicationRegistrationByClientId(clientId: $clientId) {
id
logoUrl
name
oAuthScopes
websiteUrl
logoUrl
}
}
`;
@@ -30,12 +30,6 @@ jest.mock('@/domain-manager/hooks/useRedirect', () => ({
})),
}));
jest.mock('@/object-metadata/hooks/useRefreshObjectMetadataItems', () => ({
useRefreshObjectMetadataItems: jest.fn().mockImplementation(() => ({
refreshObjectMetadataItems: jest.fn(),
})),
}));
jest.mock('@/domain-manager/hooks/useOrigin', () => ({
useOrigin: jest.fn().mockImplementation(() => ({
origin: 'http://localhost',
@@ -1,27 +1,27 @@
import { act, renderHook } from '@testing-library/react';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
const renderHooks = () => {
const { result } = renderHook(() => {
const isLogged = useIsLogged();
const hasAccessTokenPair = useHasAccessTokenPair();
const setTokenPair = useSetAtomState(tokenPairState);
return {
isLogged,
hasAccessTokenPair,
setTokenPair,
};
});
return { result };
};
describe('useIsLogged', () => {
describe('useHasAccessTokenPair', () => {
it('should return correct value', async () => {
const { result } = renderHooks();
expect(result.current.isLogged).toBe(false);
expect(result.current.hasAccessTokenPair).toBe(false);
await act(async () => {
result.current.setTokenPair({
@@ -36,6 +36,6 @@ describe('useIsLogged', () => {
});
});
expect(result.current.isLogged).toBe(true);
expect(result.current.hasAccessTokenPair).toBe(true);
});
});
@@ -34,7 +34,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useSignUpInNewWorkspace } from '@/auth/sign-in-up/hooks/useSignUpInNewWorkspace';
import { useReloadWorkspaceMetadata } from '@/metadata-store/hooks/useReloadWorkspaceMetadata';
import { useLoadMockedMinimalMetadata } from '@/metadata-store/hooks/useLoadMockedMinimalMetadata';
import { lastAuthenticatedMethodState } from '@/auth/states/lastAuthenticatedMethodState';
import { loginTokenState } from '@/auth/states/loginTokenState';
import {
@@ -42,7 +42,6 @@ import {
signInUpStepState,
} from '@/auth/states/signInUpStepState';
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
import { coreViewsState } from '@/views/states/coreViewState';
import { type BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
import {
countAvailableWorkspaces,
@@ -88,8 +87,7 @@ export const useAuth = () => {
const { loadCurrentUser } = useLoadCurrentUser();
const { clearSseClient } = useClearSseClient();
const { reloadWorkspaceMetadata, resetToMockedMetadata } =
useReloadWorkspaceMetadata();
const { loadMockedMinimalMetadata } = useLoadMockedMinimalMetadata();
const { createWorkspace } = useSignUpInNewWorkspace();
const setSignInUpStep = useSetAtomState(signInUpStepState);
@@ -163,18 +161,17 @@ export const useAuth = () => {
});
store.set(loginTokenState.atom, null);
store.set(signInUpStepState.atom, SignInUpStep.Init);
store.set(coreViewsState.atom, []);
await client.clearStore();
setLastAuthenticateWorkspaceDomain(null);
await resetToMockedMetadata();
await loadMockedMinimalMetadata();
navigate(AppPath.SignInUp);
store.set(isAppEffectRedirectEnabledState.atom, true);
}, [
clearSseClient,
client,
setLastAuthenticateWorkspaceDomain,
resetToMockedMetadata,
loadMockedMinimalMetadata,
navigate,
store,
]);
@@ -305,14 +302,10 @@ export const useAuth = () => {
setIsAppEffectRedirectEnabled(false);
await loadCurrentUser();
await reloadWorkspaceMetadata();
setIsAppEffectRedirectEnabled(true);
},
[
loadCurrentUser,
handleSetAuthTokens,
reloadWorkspaceMetadata,
setIsAppEffectRedirectEnabled,
],
[loadCurrentUser, handleSetAuthTokens, setIsAppEffectRedirectEnabled],
);
const handleGetAuthTokensFromLoginToken = useCallback(
@@ -1,7 +1,7 @@
import { tokenPairState } from '@/auth/states/tokenPairState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const useIsLogged = (): boolean => {
export const useHasAccessTokenPair = (): boolean => {
const [tokenPair] = useAtomState(tokenPairState);
return !!tokenPair;
};
@@ -1,5 +1,5 @@
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector';
import { flatObjectMetadataItemsSelector } from '@/object-metadata/states/flatObjectMetadataItemsSelector';
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
export const objectPermissionsFamilySelector = createAtomFamilySelector<
@@ -14,7 +14,7 @@ export const objectPermissionsFamilySelector = createAtomFamilySelector<
({ objectNameSingular }) =>
({ get }) => {
const currentUserWorkspace = get(currentUserWorkspaceState);
const objectMetadataItems = get(objectMetadataItemsSelector);
const objectMetadataItems = get(flatObjectMetadataItemsSelector);
const objectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === objectNameSingular,
@@ -6,7 +6,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { coreViewsState } from '@/views/states/coreViewState';
import { viewsSelector } from '@/views/states/selectors/viewsSelector';
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
import { AppPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -38,6 +38,8 @@ const getViewId = (
return undefined;
};
const SIGN_IN_BACKGROUND_OBJECT_NAME_PLURAL = 'companies';
export const MainContextStoreProvider = () => {
const location = useLocation();
const isRecordIndexPage = isMatchingLocation(
@@ -46,16 +48,21 @@ export const MainContextStoreProvider = () => {
);
const isRecordShowPage = isMatchingLocation(location, AppPath.RecordShowPage);
const isSettingsPage = useIsSettingsPage();
const showAuthModal = useShowAuthModal();
const objectNamePlural = useParams().objectNamePlural ?? '';
const objectNamePluralFromParams = useParams().objectNamePlural ?? '';
const objectNameSingular = useParams().objectNameSingular ?? '';
const objectNamePlural = showAuthModal
? SIGN_IN_BACKGROUND_OBJECT_NAME_PLURAL
: objectNamePluralFromParams;
const [searchParams] = useSearchParams();
const viewIdQueryParamRaw = searchParams.get('viewId');
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
const metadataStore = useAtomFamilyStateValue(metadataStoreState, 'views');
const coreViews = useAtomStateValue(coreViewsState);
const views = useAtomStateValue(viewsSelector);
const objectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
@@ -65,7 +72,7 @@ export const MainContextStoreProvider = () => {
const { getLastVisitedViewIdFromObjectNamePlural } = useLastVisitedView();
const viewIdQueryParamView = coreViews.find(
const viewIdQueryParamView = views.find(
(view) => view.id === viewIdQueryParamRaw,
);
@@ -79,7 +86,7 @@ export const MainContextStoreProvider = () => {
objectMetadataItem?.namePlural ?? '',
);
const lastVisitedView = coreViews.find(
const lastVisitedView = views.find(
(view) => view.id === lastVisitedViewIdRaw,
);
@@ -89,13 +96,13 @@ export const MainContextStoreProvider = () => {
? lastVisitedViewIdRaw
: undefined;
const indexViewId = coreViews.find(
const indexViewId = views.find(
(view) =>
view.objectMetadataId === objectMetadataItem?.id &&
view.key === ViewKey.INDEX,
)?.id;
const firstAvailableViewId = coreViews.find(
const firstAvailableViewId = views.find(
(view) =>
view.objectMetadataId === objectMetadataItem?.id &&
view.type !== ViewType.FIELDS_WIDGET,
@@ -108,11 +115,11 @@ export const MainContextStoreProvider = () => {
firstAvailableViewId,
);
const showAuthModal = useShowAuthModal();
const shouldComputeContextStore =
(isRecordIndexPage || isRecordShowPage || isSettingsPage) &&
!showAuthModal &&
(isRecordIndexPage ||
isRecordShowPage ||
isSettingsPage ||
showAuthModal) &&
metadataStore.status === 'up-to-date';
if (!shouldComputeContextStore) {
@@ -8,7 +8,7 @@ import { useSetLastVisitedViewForObjectMetadataNamePlural } from '@/navigation/h
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector';
import { viewFromViewIdFamilySelector } from '@/views/states/selectors/viewFromViewIdFamilySelector';
import { useEffect } from 'react';
type MainContextStoreProviderEffectProps = {
@@ -52,7 +52,7 @@ export const MainContextStoreProviderEffect = ({
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const view = useAtomFamilySelectorValue(coreViewFromViewIdFamilySelector, {
const view = useAtomFamilySelectorValue(viewFromViewIdFamilySelector, {
viewId: viewId ?? '',
});
@@ -18,7 +18,7 @@ export const getViewType = ({
}
if (isRecordIndexPage) {
return view?.type === ViewType.Kanban
return view?.type === ViewType.KANBAN
? ContextStoreViewType.Kanban
: ContextStoreViewType.Table;
}
@@ -1,9 +1,7 @@
import React from 'react';
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState';
import { useDateTimeFormat } from '@/localization/hooks/useDateTimeFormat';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { UserContext } from '@/users/contexts/UserContext';
import { useLocation } from 'react-router-dom';
@@ -11,10 +9,8 @@ import { AppPath } from 'twenty-shared/types';
import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
export const MetadataGater = ({ children }: React.PropsWithChildren) => {
const isAppMetadataReady = useAtomStateValue(isAppMetadataReadyState);
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
const isLoggedIn = useIsLogged();
export const MinimalMetadataGater = ({ children }: React.PropsWithChildren) => {
const isMinimalMetadataReady = useAtomStateValue(isMinimalMetadataReadyState);
const location = useLocation();
const { dateFormat, timeFormat, timeZone } = useDateTimeFormat();
@@ -22,11 +18,12 @@ export const MetadataGater = ({ children }: React.PropsWithChildren) => {
const isOnExcludedPath =
isMatchingLocation(location, AppPath.Verify) ||
isMatchingLocation(location, AppPath.VerifyEmail) ||
isMatchingLocation(location, AppPath.SignInUp) ||
isMatchingLocation(location, AppPath.Invite) ||
isMatchingLocation(location, AppPath.ResetPassword) ||
isMatchingLocation(location, AppPath.CreateWorkspace);
const shouldShowLoader =
(!isAppMetadataReady && isLoggedIn && !isOnExcludedPath) ||
objectMetadataItems.length === 0;
const shouldShowLoader = !isMinimalMetadataReady && !isOnExcludedPath;
if (shouldShowLoader) {
return <UserOrMetadataLoader />;
@@ -1,33 +0,0 @@
import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent';
import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent';
import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery';
import { useStore } from 'jotai';
import { AllMetadataName } from '~/generated-metadata/graphql';
export const FieldMetadataSSEEffect = () => {
const queryId = 'field-metadata-sse-effect';
const store = useStore();
useListenToEventsForQuery({
queryId,
operationSignature: {
metadataName: AllMetadataName.fieldMetadata,
variables: {},
},
});
useListenToMetadataOperationBrowserEvent({
metadataName: AllMetadataName.fieldMetadata,
onMetadataOperationBrowserEvent: (eventDetail) => {
patchMetadataStoreFromSSEEvent(
store,
'fieldMetadataItems',
eventDetail.operation,
eventDetail.updatedCollectionHash,
);
},
});
return null;
};
@@ -1,7 +1,7 @@
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState';
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
@@ -10,8 +10,8 @@ import { useEffect } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
export const IsAppMetadataReadyEffect = () => {
const isLoggedIn = useIsLogged();
export const IsMinimalMetadataReadyEffect = () => {
const hasAccessTokenPair = useHasAccessTokenPair();
const currentUser = useAtomStateValue(currentUserState);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const metadataStore = useAtomFamilyStateValue(
@@ -23,7 +23,9 @@ export const IsAppMetadataReadyEffect = () => {
metadataStoreState,
'views',
);
const setIsAppMetadataReady = useSetAtomState(isAppMetadataReadyState);
const setIsMinimalMetadataReady = useSetAtomState(
isMinimalMetadataReadyState,
);
useEffect(() => {
const hasActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace);
@@ -31,23 +33,26 @@ export const IsAppMetadataReadyEffect = () => {
const areObjectsLoaded = metadataStore.status === 'up-to-date';
const areViewsLoaded = metadataStoreViews.status === 'up-to-date';
const isReady = !areObjectsLoaded
? false
: !hasAccessTokenPair ||
(isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded));
if (!areObjectsLoaded) {
setIsAppMetadataReady(false);
setIsMinimalMetadataReady(false);
return;
}
const isReady =
!isLoggedIn ||
(isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded));
setIsAppMetadataReady(isReady);
if (isReady) {
setIsMinimalMetadataReady(true);
}
}, [
isLoggedIn,
hasAccessTokenPair,
currentUser,
currentWorkspace,
metadataStore.status,
metadataStoreViews.status,
setIsAppMetadataReady,
setIsMinimalMetadataReady,
]);
return null;
@@ -1,11 +0,0 @@
import { ObjectMetadataProviderInitialEffect } from '@/metadata-store/effect-components/ObjectMetadataProviderInitialEffect';
import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect';
import { ViewMetadataProviderInitialEffect } from '@/metadata-store/effect-components/ViewMetadataProviderInitialEffect';
export const MetadataProviderInitialEffects = () => (
<>
<UserMetadataProviderInitialEffect />
<ObjectMetadataProviderInitialEffect />
<ViewMetadataProviderInitialEffect />
</>
);
@@ -0,0 +1,28 @@
import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent';
import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent';
import { mapAllMetadataNameToEntityKey } from '@/metadata-store/utils/mapAllMetadataNameToEntityKey';
import { useStore } from 'jotai';
import { isDefined } from 'twenty-shared/utils';
export const MetadataStoreSSEEffect = () => {
const store = useStore();
useListenToMetadataOperationBrowserEvent({
onMetadataOperationBrowserEvent: (eventDetail) => {
const entityKey = mapAllMetadataNameToEntityKey(eventDetail.metadataName);
if (!isDefined(entityKey)) {
return;
}
patchMetadataStoreFromSSEEvent({
store,
entityKey,
operation: eventDetail.operation,
updatedCollectionHash: eventDetail.updatedCollectionHash,
});
},
});
return null;
};
@@ -1,62 +1,73 @@
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
import { useLoadMinimalMetadata } from '@/metadata-store/hooks/useLoadMinimalMetadata';
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { useLoadMockedMinimalMetadata } from '@/metadata-store/hooks/useLoadMockedMinimalMetadata';
import { useLoadStaleMetadataEntities } from '@/metadata-store/hooks/useLoadStaleMetadataEntities';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useStore } from 'jotai';
import { useEffect, useState } from 'react';
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
type LoadedState = 'none' | 'mocked' | 'real';
const computeDesiredLoadState = (
hasAccessTokenPair: boolean,
isActiveWorkspace: boolean,
): LoadedState => {
if (hasAccessTokenPair && isActiveWorkspace) {
return 'real';
}
return 'mocked';
};
export const MinimalMetadataLoadEffect = () => {
const isLoggedIn = useIsLogged();
const hasAccessTokenPair = useHasAccessTokenPair();
const isCurrentUserLoaded = useAtomStateValue(isCurrentUserLoadedState);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const store = useStore();
const [hasLoaded, setHasLoaded] = useState(false);
const [loadedState, setLoadedState] = useState<LoadedState>('none');
const { loadMinimalMetadata } = useLoadMinimalMetadata();
const { loadMockedMinimalMetadata } = useLoadMockedMinimalMetadata();
const { loadStaleMetadataEntities } = useLoadStaleMetadataEntities();
const isActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace);
const desiredLoadState = computeDesiredLoadState(
hasAccessTokenPair,
isActiveWorkspace,
);
useEffect(() => {
if (hasLoaded) {
if (!isCurrentUserLoaded || loadedState === desiredLoadState) {
return;
}
if (!isCurrentUserLoaded || !isLoggedIn) {
return;
}
setLoadedState(desiredLoadState);
if (!isWorkspaceActiveOrSuspended(currentWorkspace)) {
return;
}
const performLoad = async () => {
if (desiredLoadState === 'mocked') {
await loadMockedMinimalMetadata();
return;
}
const objectsEntry = store.get(
metadataStoreState.atomFamily('objectMetadataItems'),
);
const viewsEntry = store.get(metadataStoreState.atomFamily('views'));
const result = await loadMinimalMetadata();
const hasObjectsFromStorage = objectsEntry.status === 'up-to-date';
const hasViewsFromStorage = viewsEntry.status === 'up-to-date';
if (hasObjectsFromStorage && hasViewsFromStorage) {
setHasLoaded(true);
return;
}
const load = async () => {
await loadMinimalMetadata();
setHasLoaded(true);
if (result?.staleEntityKeys && result.staleEntityKeys.length > 0) {
await loadStaleMetadataEntities(result.staleEntityKeys);
}
};
load();
performLoad();
}, [
hasLoaded,
isCurrentUserLoaded,
isLoggedIn,
currentWorkspace,
store,
hasAccessTokenPair,
isActiveWorkspace,
desiredLoadState,
loadedState,
loadMinimalMetadata,
loadMockedMinimalMetadata,
loadStaleMetadataEntities,
]);
return null;

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