Compare commits

..
Author SHA1 Message Date
neo773andCharles Bochet 0b13c39e02 Fix participant matching failing due to updating all columns instead of only changed fields (#18105)
The updateMany in matchParticipants was spreading the entire participant
object into the SET clause, which included generated columns
(searchVector) and composite field columns (createdBySource) that can't
be written to. Narrowed the update to only set personId and
workspaceMemberId.
```
[1] query failed: UPDATE "workspace_3ixj3i1a5avy16ptijtb3lae3"."calendarEventParticipant" SET "id" = $1, "createdAt" = $2, "updatedAt" = $3, "deletedAt" = $4, "createdBySource" = $5, "createdByWorkspaceMemberId" = $6, "createdByName" = $7, "createdByContext" = $8, "updatedBySource" = $9, "updatedByWorkspaceMemberId" = $10, "updatedByName" = $11, "updatedByContext" = $12, "position" = $13, "searchVector" = $14, "handle" = $15, "displayName" = $16, "isOrganizer" = $17, "responseStatus" = $18, "calendarEventId" = $19, "personId" = $20, "workspaceMemberId" = $21 WHERE "id" = $22 RETURNING * -- PARAMETERS: ["f1526103-451a-429f-9743-3a81c3a3e3aa","2026-02-19T22:23:32.354Z","2026-02-19T22:23:32.354Z",null,"MANUAL",null,"System",null,"MANUAL",null,"System",null,0,"'[email protected]':1","[email protected]","",false,"NEEDS_ACTION","fb1892a8-6daf-435b-a43b-e8fe8693eb99","60bf9e82-f1b7-4bed-a1af-7039fb9c32f5",null,"f1526103-451a-429f-9743-3a81c3a3e3aa"]
[1] error: error: column "searchVector" can only be updated to DEFAULT
[1] Exception Captured
[1]   undefined
[1]   [
[1]     PostgresException [Error]: Data validation error.
[1]         at computeTwentyORMException (/Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/engine/twenty-orm/error-handling/compute-twenty-orm-exception.js:35:19)
[1]         at WorkspaceUpdateQueryBuilder.executeMany (/Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/engine/twenty-orm/repository/workspace-update-query-builder.js:269:82)
[1]         at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
[1]         at async WorkspaceRepository.updateMany (/Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/engine/twenty-orm/repository/workspace.repository.js:234:25)
[1]         at async MatchParticipantService.matchParticipants (/Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/modules/match-participant/match-participant.service.js:93:13)
[1]         at async /Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/modules/match-participant/match-participant.service.js:160:13
[1]         at async MatchParticipantService.matchParticipantsForPeople (/Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/modules/match-participant/match-participant.service.js:130:9)
[1]         at async CalendarEventParticipantMatchParticipantJob.handle (/Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/modules/calendar/calendar-event-participant-manager/jobs/calendar-event-participant-match-participant.job.js:45:13)
[1]         at async MessageQueueExplorer.invokeProcessMethods (/Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/engine/core-modules/message-queue/message-queue.explorer.js:113:17)
[1]         at async MessageQueueExplorer.handleProcessor (/Users/huzef/Documents/bounties/twenty/packages/twenty-server/dist/engine/core-modules/message-queue/message-queue.explorer.js:104:13) {
[1]       code: '428C9'
[1]     }
[1]   ]
```
2026-02-20 10:27:36 +01:00
Etienne 38a49650a3 FILES field - Attachment name display fix (#18073)
with new 'file' FILES field on attachment, UI should display attachment
name from file field value.
Issue with API users updating only 'file' FILES field (and not name
field anymore)
2026-02-19 16:07:18 +01:00
Etienne 35c4ec0d44 Remove non positive integer constraint on Nav Menu Item 2/2 (#18090)
Follow up https://github.com/twentyhq/twenty/pull/18081
2026-02-19 15:20:29 +01:00
WeikoandEtienne 791462bab0 Fix google compose scope not gated by feature flag (#18093)
## Context
New google api scope has been introduced in
https://github.com/twentyhq/twenty/pull/17793 without being gated behind
a feature flag

Microsoft is using pre-existing Mail.ReadWrite scope there is nothing to
gate

## Before
<img width="553" height="445" alt="Screenshot 2026-02-19 at 14 57 58"
src="https://github.com/user-attachments/assets/59a4f76b-d38d-492f-b013-b6cad4091a7f"
/>


## After
<img width="535" height="392" alt="Screenshot 2026-02-19 at 14 58 44"
src="https://github.com/user-attachments/assets/0337bf15-ec30-4549-bb9d-571a982dffd8"
/>
2026-02-19 15:20:19 +01:00
Etienne 78aad6733f Remove non positive integer constraint on Nav Menu Item (#18081)
To fit with position typed field logic + Ease favorite to nav menu item
migration (which have negative and float position)
2026-02-19 14:40:00 +01:00
4cb64c6aa5 Restore old favorite design (#18049)
Issue : With IS_NAVIGATION_MENU_ITEM_ENABLED:true +
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED:false, nav menu design is
changed after 1.18.0 release : views expansion removed, system object
displayed, position re-ordered

We prefer keeping the same "old" favorite behaviour and design state

- After 1.18.0 all workspaces have up-to-date navigation menu items
(migrated)
- IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED becomes the FF for nav menu
new design

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-02-18 21:35:17 +01:00
6b3ef404b0 i18n - docs translations (#18050)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-18 21:31:54 +01:00
6aaafb76b6 fix: show "Not shared" for junction relation fields when target or intermediate object is not readable (#18025)
When a user's role lacks read permission on the target object (e.g.,
Company) or the intermediate junction object (e.g., EmploymentHistory),
junction relation fields like "Previous Companies" displayed as blank
instead of showing "Not shared."

- In RecordFieldList, junction fields now check the junction object's
read permission and set isForbidden on the field context so FieldDisplay
renders "Not shared" instead of an empty field.
- In RelationFromManyFieldDisplay, if junction records exist but all
nested target records are null (permission-denied by the API), the
component renders "Not shared" instead of an empty list.

---------

Co-authored-by: Félix Malfait <[email protected]>
2026-02-18 21:05:45 +01:00
Thomas des FrancsandGitHub 7ec4508d5f Add currency hover tooltip in CurrencyDisplay (#18045)
https://github.com/user-attachments/assets/af768ecb-b5e9-4e8d-a9f9-fee1a08ea9a0

Fixes https://github.com/twentyhq/twenty/issues/17756
2026-02-18 20:25:34 +01:00
Thomas des FrancsandGitHub a05a9c8f79 refactor workflow action messaging with callout (#18038)
## Summary
- remove the dedicated `WorkflowMessage` component and story
- update workflow action editor components to use the shared callout
patterns
- adjust `Callout` and its stories to support the new usage in workflow
actions

## Before/After

<img width="525" height="548" alt="image"
src="https://github.com/user-attachments/assets/ce57a84f-f070-4149-85ef-a4d162b2d878"
/>


<img width="518" height="593" alt="image"
src="https://github.com/user-attachments/assets/f7249cd0-221f-496d-9deb-d9966ee43382"
/>
2026-02-18 20:23:38 +01:00
Charles BochetandGitHub ce1ffa8550 Refactor page layout types (#18042)
## Refactor page layout widget types into shared package and expose from
SDK

### Why

Widget configuration types were defined only on the server, forcing SDK
consumer apps to import from deep internal `twenty-shared/dist` paths —
fragile and breaks on structural changes. Server DTOs also had no
compile-time guarantee they matched the canonical types.

### What changed

- **`twenty-shared`**: Migrated `ChartFilter`, `GridPosition`,
`RatioAggregateConfig` and all 20 widget configuration variants into
`twenty-shared/types`. `PageLayoutWidgetConfiguration` (base, with
`SerializedRelation`) and `PageLayoutWidgetUniversalConfiguration`
(derived via `FormatRecordSerializedRelationProperties`) are now the
single source of truth.
- **`twenty-sdk`**: Re-exported `AggregateOperations`,
`ObjectRecordGroupByDateGranularity`, `PageLayoutTabLayoutMode`, and
`PageLayoutWidgetUniversalConfiguration` so consumer apps import from
`twenty-sdk` directly.
- **`twenty-server`**: All widget DTOs now `implements` their shared
type for compile-time enforcement. Added helpers to convert nested
`fieldMetadataId` ↔ `fieldMetadataUniversalIdentifier` inside chart
filters. Removed redundant local type re-exports.
2026-02-18 20:17:40 +01:00
martmullandGitHub 2cc3c75c7e Add example in create-twenty-app (#18043)
- add interactive mode to create-twenty-app
- by default create an example for each entities

<img width="1181" height="168" alt="image"
src="https://github.com/user-attachments/assets/a2490d8f-66a1-4cd5-bf41-57166cc20a1e"
/>
2026-02-18 18:04:19 +00:00
Thomas TrompetteandGitHub 9733ff1b8e Add missing objects to rich app integration tests (#18039)
Updating rich app so it also create:
- a many to many relation
- views
- navigation items

The app was built successfully.

Will still be missing front component examples

<img width="1498" height="660" alt="Capture d’écran 2026-02-18 à 17 47
25"
src="https://github.com/user-attachments/assets/acd5193f-3a36-4eb7-8276-3154e4e60f5e"
/>
2026-02-18 18:01:14 +00:00
e4075caa65 i18n - docs translations (#18048)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-18 19:42:34 +01:00
Charles BochetandGitHub c3781e87cc Sync page Layout (#18034)
## Sync page layouts, tabs, and widgets

Adds the ability for SDK applications to synchronize `pageLayout`,
`pageLayoutTab`, and `pageLayoutWidget` entities, following the same
pattern established in #18003 for views and navigation menu items.

### Changes

**`twenty-shared`**
- New `PageLayoutManifest`, `PageLayoutTabManifest`, and
`PageLayoutWidgetManifest` types with a hierarchical structure (page
layout → tabs → widgets)
- Added `pageLayouts: PageLayoutManifest[]` to the `Manifest` type

**`twenty-sdk`**
- New `definePageLayout()` SDK function with validation for
universalIdentifier, name, and nested tabs/widgets
- Wired into the manifest extraction and build pipeline
(`DefinePageLayout` target function, `PageLayouts` entity key)
- Exported from the SDK entry point

**`twenty-server`**
- Added `pageLayout`, `pageLayoutTab`, `pageLayoutWidget` to
`APPLICATION_MANIFEST_METADATA_NAMES`
- New conversion utilities: manifest → universal flat entity for all
three entity types
- Updated `computeApplicationManifestAllUniversalFlatEntity
2026-02-18 17:55:04 +01:00
e9b5cb830c i18n - docs translations (#18040)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-18 17:54:40 +01:00
EtienneandGitHub e40c758aa6 Nav Menu Item Migration command fix (#18041) 2026-02-18 17:45:21 +01:00
martmullandGitHub 53c314d0fa 2094 extensibility define postinstall orand preinstall function to run in application (#18037)
- add a new optional key `postInstallLogicFunctionUniversalIdentifier`
in applicationConfig
- seed postInstall function in create-twenty-app
- update execute:function options
- update doc
2026-02-18 15:38:22 +00:00
618df704e6 [Chore] : Generate migration for DATE_TIME to DATE for DATE_TIME + IS operand filters (#17564)
migration command in response to the fix :
https://github.com/twentyhq/twenty/pull/17529 for the issue
https://github.com/twentyhq/core-team-issues/issues/2027

---------

Co-authored-by: Arun kumar <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
2026-02-18 15:15:38 +00:00
EtienneandGitHub 058489b5cc Fixes - Workspace logo migration (#18035)
- Update migration command to handle case where workspace logo is
originated from workspace email and point to twenty-icons.com
- Update same logic for new workspaces
- Add feature-flag for all newly created workspaces
2026-02-18 16:35:48 +01:00
3bd431e95d Use proper PostgreSQL identifier/literal escaping in workspace DDL (#18024)
## Summary

- Replace the character-stripping approach (`removeSqlDDLInjection`)
with standard PostgreSQL `escapeIdentifier` and `escapeLiteral`
functions across all workspace schema manager services
- Add missing identifier escaping to `createForeignKey` (was the only
method in the FK manager without it)
- Add allowlist validation for index WHERE clauses and FK action types
- Harden tsvector expression builder with proper identifier quoting

## Context

The workspace schema managers build DDL dynamically from metadata (table
names, column names, enum values, etc.). The previous approach stripped
all non-alphanumeric characters — safe but lossy (silently corrupts
values with legitimate special characters). The new approach uses
PostgreSQL's standard escaping:

- **Identifiers**: double internal `"` and wrap → `"my""table"` (same
algorithm as `pg` driver's `escapeIdentifier`)
- **Literals**: double internal `'` and wrap → `'it''s a value'` (same
algorithm as `pg` driver's `escapeLiteral`)

`removeSqlDDLInjection` is kept only for name generation (e.g.,
`computePostgresEnumName`) where stripping to `[a-zA-Z0-9_]` is the
correct behavior.

## Files changed

| File | What |
|------|------|
| `remove-sql-injection.util.ts` | Added `escapeIdentifier` +
`escapeLiteral` |
| `validate-index-where-clause.util.ts` | New — allowlist for partial
index WHERE clauses |
| 5 schema manager services | Replaced strip+manual-quote with
`escapeIdentifier`/`escapeLiteral` |
| `build-sql-column-definition.util.ts` | `escapeIdentifier` for column
names, validated `generatedType` |
| `sanitize-default-value.util.ts` | `escapeLiteral` instead of
stripping |
| `serialize-default-value.util.ts` | `escapeLiteral` for values,
`escapeIdentifier` for enum casts |
| `get-ts-vector-column-expression.util.ts` | `escapeIdentifier` for
field names in expressions |
| `sanitize-default-value.util.spec.ts` | Updated tests for escape
behavior |

## Test plan

- [x] All 64 existing tests pass across 6 test suites
- [x] `lint:diff-with-main` passes
- [x] TypeScript typecheck — no new errors
- [ ] Verify workspace sync-metadata still works end-to-end
- [ ] Verify custom object/field creation works
- [ ] Verify enum field option changes work


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
2026-02-18 14:24:10 +00:00
f4a61f26c0 Replace generic "Unknown error" messages with descriptive error details (#18019)
## Summary

- Replace all generic `"Unknown error"` fallback messages across the
server codebase with messages that include the actual error details
- The most impactful change is in `guard-redirect.service.ts`, which
handles OAuth redirect errors — non-`AuthException` errors (e.g.,
passport state verification failures) now show `"Authentication error:
<actual message>"` instead of the opaque `"Unknown error"`
- Gmail/Google error handler services now include the error message in
the thrown exception instead of discarding it
- Other catch blocks (workflow delay resume, migration runner rollback,
code interpreter, marketplace) now use `String(error)` for non-Error
objects instead of a static fallback

Fixes the class of issues reported in
https://github.com/twentyhq/twenty/issues/17812, where a user saw
"Unknown error" during Google OAuth and had no way to diagnose the root
cause (which turned out to be a session cookie / SSL configuration
issue).

## Test plan

- [ ] Verify OAuth error flows (e.g., Google Auth with misconfigured
callback URL) now display the actual error message on the `/verify` page
instead of "Unknown error"
- [ ] Verify Gmail sync error handling still correctly classifies and
re-throws errors with descriptive messages
- [ ] Verify workflow delay resume failures include the error details in
the workflow run status


Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-18 14:12:47 +00:00
8e6b267ff3 Allow DATE_TIME IS operand to filter on a whole day (#17529)
Fixes:  https://github.com/twentyhq/core-team-issues/issues/2027

We've replaced the DATE_TIME picker with DATE picker, and changed the
logic to filter for complete day period.



https://github.com/user-attachments/assets/ba7e1078-bab3-4c62-a803-d6a851f14b7d

---------

Co-authored-by: Arun kumar <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
2026-02-18 13:44:05 +00:00
neo773andGitHub 88146c2170 Fix Gmail thread awareness for custom labels (#18031)
This fixes two edge cases for Gmail

- When policy was set to `SELECTED_FOLDERS` excluding root INBOX, it
missed label changes, so messages with newly applied labels weren't
imported until a full resync.

- Gmail thread replies by default by default do no inherit parent
message's label properties so thread context was also lost because only
individually labeled messages were returned, dropping earlier parts of
the conversation.

Fixed by subscribing to `labelAdded`/`labelRemoved` history events and
fetching full thread context when at least one message in a thread
carries a synced label. `ALL_FOLDERS` path is untouched.
2026-02-18 14:10:38 +01:00
EtienneandGitHub 3706da9bcb v1.18 - Fix command (#18032)
Same as here https://github.com/twentyhq/twenty/pull/18016
2026-02-18 13:52:13 +01:00
9bac8f15d4 i18n - translations (#18029)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-18 13:26:54 +01:00
Charles BochetandGitHub 7332379d26 Improve API Client usage and add Typescript check (#18023)
## Summary


https://github.com/user-attachments/assets/1e75cc9d-d9d2-4ef2-99f9-34450f5d8de7



Add background incremental type checking (`tsc --watch`) to the SDK dev
mode, so type regressions are caught when the generated API client
changes — without requiring a full rebuild of source files.

Previously, removing a field from the data model would regenerate the
API client, but existing front components/logic functions referencing
the removed field wouldn't surface type errors (since their source
didn't change, esbuild wouldn't rebuild them).

## What changed

- **Background `tsc --watch`**: a long-lived TypeScript watcher runs
alongside esbuild watchers, incrementally re-checking all files when the
generated client changes. Only logs on state transitions (errors appear
/ errors clear) to stay quiet.
- **Atomic client generation**: API client is now generated into a temp
directory and swapped in atomically, avoiding a race condition where
`tsc --watch` could see an empty `generated/` directory
mid-regeneration.
- **Step decoupling**: orchestrator steps no longer receive
`uploadFilesStep` directly. Instead, they use callbacks (`onFileBuilt`,
`onApiClientGenerated`), and each step manages its own `builtFileInfos`
state.
- **`apiClientChecksum` omitted from `ApplicationConfig`**: it's a
build-time computed value, same as `packageJsonChecksum`.
<img width="327" height="177" alt="image"
src="https://github.com/user-attachments/assets/02bd25bb-fa41-42b0-8d96-01c51bd4580c"
/>

<img width="529" height="452" alt="image"
src="https://github.com/user-attachments/assets/61f6e968-365b-4a5b-8f2b-a8419d6b1bd3"
/>
2026-02-18 13:26:30 +01:00
Raphaël BosiandGitHub 2455c859b4 Add SSE for metadata and plug front components (#17998)
Create the necessary tooling to listen to metadata events and plug it to
the front components. Now we have a hot reload like experience when we
edit a component in an app.

## Backend

- Split `EventWithQueryIds` into `ObjectRecordEventWithQueryIds` and
`MetadataEventWithQueryIds`
- Publish metadata event batches to active SSE streams in
`MetadataEventsToDbListener`

## Frontend

- Create a metadata event dispatching pipeline: SSE metadata events are
grouped by metadata name, transformed into
`MetadataOperationBrowserEventDetail` objects, and dispatched as browser
`CustomEvents`
- Add `useListenToMetadataOperationBrowserEvent` hook for consuming
metadata operation events filtered by metadata name and operation type
- Rename `useListenToObjectRecordEventsForQuery` to
`useListenToEventsForQuery`, now accepting both
`RecordGqlOperationSignature` and `MetadataGqlOperationSignature`
- Implement `useOnFrontComponentUpdated` which subscribes to front
component metadata events and updates the Apollo cache when the
component is modified
- Add `builtComponentChecksum` to the front component query and appends
it to the component URL for browser cache invalidation
2026-02-18 11:26:20 +00:00
martmullandGitHub e3753bf822 App feedbacks (#18028)
as title
2026-02-18 11:04:54 +00:00
WeikoandGitHub f3faa11dd2 New field creates fields widget field (#18022)
## Context
Introducing "NewFieldDefaultConfiguration" to FIELDS widget
configurations
```typescript
{
  isVisible: boolean;
  viewFieldGroupId: string | null;
}
```

This configuration will define where a new field should be added (which
section) and its default visibility inside FIELDS widget views.
The new field position should always be at the end (meaning the last
position for the view fields OR the last position of a viewFieldGroup)

See "New fields" on this screenshot
<img width="401" height="724" alt="Layout V1"
src="https://github.com/user-attachments/assets/4969bcaa-f244-4504-8947-778a02c24c47"
/>
2026-02-18 11:03:27 +00:00
nitinandGitHub 477fbc0865 fixes: loosen up front validation, add resolveEntityRelationUniversalIdentifiers to update and restore (#18015)
closes https://github.com/twentyhq/private-issues/issues/419
2026-02-18 10:35:08 +00:00
EtienneGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
08a3d983cb Date & DateTime validation fixes / improvements (#18009)
Fixes https://github.com/twentyhq/twenty/issues/17138

- Backend should have strict date/dateTime format validation
- FE in import csv is more permissive

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-02-18 10:13:18 +00:00
EtienneandGitHub ee15e034b5 Files command - fixes (#18016)
- Fixed "property entity not found" error when updating/creating a new
field and querying the same object repository just after
- Downgraded log type for unnecessary migration
2026-02-18 09:09:52 +00:00
b7274da8fa i18n - docs translations (#18020)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-18 08:12:20 +01:00
4a485aecb0 i18n - docs translations (#18017)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-18 01:06:14 +01:00
BOHEUSGitHubneo773neo773greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
5ae1d94f23 Fix connected account permissions (#17598)
Fixes #17411

---------

Co-authored-by: neo773 <[email protected]>
Co-authored-by: neo773 <[email protected]>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-02-18 01:05:56 +01:00
Abdul RahmanGitHubCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>DevessierFélix Malfait
015ccbf0a7 Navbar customization improvements (#17863)
- Add folder icon editing support
- And other navbar customization fixes/improvements.

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <[email protected]>
Co-authored-by: Félix Malfait <[email protected]>
2026-02-17 19:53:52 +00:00
3d362e6e01 i18n - docs translations (#18014)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-17 19:33:21 +01:00
Charles BochetGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
d7f025157b Migrate more to Jotai (#17968)
As per title

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-17 19:24:36 +01:00
98482f3a01 i18n - translations (#18013)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-17 19:18:53 +01:00
BOHEUSandGitHub 171efe2a19 New onboarding plan (#17776)
Fixes github.com/twentyhq/core-team-issues/issues/637
2026-02-17 19:11:47 +01:00
Baptiste DevessierandGitHub 7512b9f9bb Load view field groups (#18010) 2026-02-17 17:22:48 +00:00
Charles BochetandGitHub c0cc0689d6 Add Client Api generation (#17961)
## Add API client generation to SDK dev mode and refactor orchestrator
into step-based pipeline

### Why

The SDK dev mode lacked typed API client generation, forcing developers
to work without auto-generated GraphQL types when building applications.
Additionally, the orchestrator was a monolithic class that mixed watcher
management, token handling, and sync logic — making it difficult to
extend with new steps like client generation.

### How

- **Refactored the orchestrator** into a step-based pipeline with
dedicated classes: `CheckServer`, `EnsureValidTokens`,
`ResolveApplication`, `BuildManifest`, `UploadFiles`,
`GenerateApiClient`, `SyncApplication`, and `StartWatchers`. Each step
has typed input/output/status, managed by a new `OrchestratorState`
class.
- **Added `GenerateApiClientOrchestratorStep`** that detects
object/field schema changes and regenerates a typed GraphQL client (via
`@genql/cli`) into `node_modules/twenty-sdk/generated` for seamless
imports.
- **Replaced `checkApplicationExist`** with `findOneApplication` on both
server resolver and SDK API service, returning the entity data instead
of a boolean.
- **Added application token pair mutations**
(`generateApplicationToken`, `renewApplicationToken`) to the API
service, with the server now returning `ApplicationTokenPairDTO`
containing both access and refresh tokens.
- **Restructured the dev UI** into `dev/ui/components/` with dedicated
panel, section, and event log components.
- **Simplified `AppDevCommand`** from ~180 lines of watcher management
down to ~40 lines that delegate entirely to the orchestrator.
2026-02-17 18:45:52 +01:00
0891886aa0 i18n - translations (#18012)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-17 18:35:35 +01:00
Thomas TrompetteandGitHub f768bbe512 Replace country code by calling code in workflows (#18008)
Long overdue PR: replacing deprecated country code in workflows.
Will allow to use variables.

We keep storing the country code since it allows to display the right
flag in picker when there are multiple countries for one calling code.
But we do not store country for variables.

<img width="477" height="254" alt="Capture d’écran 2026-02-17 à 17 25
23"
src="https://github.com/user-attachments/assets/dc67c41c-33cf-4021-b7bb-490827b2aa3c"
/>
2026-02-17 17:06:09 +00:00
610c0ebc9d Move secure HTTP client IP validation to connection level (#18006)
## Summary
- Refactors SSRF protection from a request-level adapter to
connection-level agents, validating resolved IPs in `createConnection` +
socket `lookup` events
- Sets both `httpAgent` and `httpsAgent` so validation applies
regardless of protocol switches during redirects
- Caps `maxRedirects` to 10 as defense in depth

## Test plan
- [x] All 59 existing + new unit tests pass (agent util, isPrivateIp,
service)
- [x] No linter errors
- [ ] Verify webhook delivery still works with URLs that redirect
- [ ] Verify image upload from external URLs still works (relies on
redirect following)


Made with [Cursor](https://cursor.com)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes core outbound HTTP security behavior and redirect handling,
which could impact webhook/image-fetch flows and connection semantics
despite improved SSRF coverage.
> 
> **Overview**
> Refactors outbound SSRF protection from a custom axios `adapter` to
connection-level `httpAgent`/`httpsAgent` created by new
`createSsrfSafeAgent`, which blocks private IP literals up front and
validates DNS-resolved IPs via the socket `lookup` event.
> 
> When safe mode is enabled, `SecureHttpClientService.getHttpClient` now
always installs both agents and enforces a capped `maxRedirects`
(default `5`), and the old `getSecureAxiosAdapter`
implementation/tests/types are removed. `isPrivateIp` is
tightened/expanded to treat `0.0.0.0/8` as private and avoid
misclassifying bare IPv4 decimals as IPv6.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8261da4ff0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <[email protected]>
2026-02-17 16:59:38 +00:00
867 changed files with 17549 additions and 8617 deletions
+51 -13
View File
@@ -15,7 +15,7 @@
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a readytorun project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
- Zeroconfig project bootstrap
- Preconfigured scripts for auth, dev mode (watch & sync), generate, uninstall, and function management
- Preconfigured scripts for auth, dev mode (watch & sync), uninstall, and function management
- Strong TypeScript support and typed client generation
## Documentation
@@ -44,10 +44,8 @@ yarn twenty auth:login
# Add a new entity to your application (guided)
yarn twenty entity:add
# Generate a typed Twenty client and workspace entity types
yarn twenty app:generate
# Start dev mode: watches, builds, and syncs local changes to your workspace
# (also auto-generates a typed API client in node_modules/twenty-sdk/generated)
yarn twenty app:dev
# Watch your application's function logs
@@ -56,25 +54,65 @@ yarn twenty function:logs
# Execute a function with a JSON payload
yarn twenty function:execute -n my-function -p '{"key": "value"}'
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty app:uninstall
```
## Scaffolding modes
Control which example files are included when creating a new app:
| Flag | Behavior |
|------|----------|
| `-e, --exhaustive` | **(default)** Creates all example files without prompting |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
| `-i, --interactive` | Prompts you to select which examples to include |
```bash
# Default: all examples included
npx create-twenty-app@latest my-app
# Minimal: only core files
npx create-twenty-app@latest my-app -m
# Interactive: choose which examples to include
npx create-twenty-app@latest my-app -i
```
In interactive mode, you can pick from:
- **Example object** — a custom CRM object definition (`objects/example-object.ts`)
- **Example field** — a custom field on the example object (`fields/example-field.ts`)
- **Example logic function** — a server-side handler with HTTP trigger (`logic-functions/hello-world.ts`)
- **Example front component** — a React UI component (`front-components/hello-world.tsx`)
- **Example view** — a saved view for the example object (`views/example-view.ts`)
- **Example navigation menu item** — a sidebar link (`navigation-menu-items/example-navigation-menu-item.ts`)
## What gets scaffolded
- A minimal app structure ready for Twenty with example files:
- `application-config.ts` - Application metadata configuration
- `roles/default-role.ts` - Default role for logic functions
- `logic-functions/hello-world.ts` - Example logic function with HTTP trigger
- `front-components/hello-world.tsx` - Example front component
- TypeScript configuration
**Core files (always created):**
- `application-config.ts` — Application metadata configuration
- `roles/default-role.ts` — Default role for logic functions
- `logic-functions/post-install.ts` — Post-install logic function (runs after app installation)
- TypeScript configuration, ESLint, package.json, .gitignore
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
**Example files (controlled by scaffolding mode):**
- `objects/example-object.ts` — Example custom object with a text field
- `fields/example-field.ts` — Example standalone field extending the example object
- `logic-functions/hello-world.ts` — Example logic function with HTTP trigger
- `front-components/hello-world.tsx` — Example front component
- `views/example-view.ts` — Example saved view for the example object
- `navigation-menu-items/example-navigation-menu-item.ts` — Example sidebar navigation link
## Next steps
- Run `yarn twenty help` to see all available commands.
- Use `yarn twenty auth:login` to authenticate with your Twenty workspace.
- Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles).
- Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles, views, navigation menu items).
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- Keep your types uptodate using `yarn twenty app:generate`.
- Types are autogenerated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`.
## Publish your application
@@ -103,7 +141,7 @@ Our team reviews contributions for quality, security, and reusability before mer
## Troubleshooting
- Auth prompts not appearing: run `yarn twenty auth:login` again and verify the API key permissions.
- Types not generated: ensure `yarn twenty app:generate` runs without errors, then restart `yarn twenty app:dev`.
- Types not generated: ensure `yarn twenty app:dev` is running — it autogenerates the typed client.
## Contributing
- See our [GitHub](https://github.com/twentyhq/twenty)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.6.0-alpha",
"version": "0.6.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
+52 -11
View File
@@ -2,6 +2,7 @@
import chalk from 'chalk';
import { Command, CommanderError } from 'commander';
import { CreateAppCommand } from '@/create-app.command';
import { type ScaffoldingMode } from '@/types/scaffolding-options';
import packageJson from '../package.json';
const program = new Command(packageJson.name)
@@ -12,18 +13,58 @@ const program = new Command(packageJson.name)
'Output the current version of create-twenty-app.',
)
.argument('[directory]')
.option('-e, --exhaustive', 'Create all example entities (default)')
.option(
'-m, --minimal',
'Create only core entities (application-config and default-role)',
)
.option(
'-i, --interactive',
'Interactively choose which entity examples to include',
)
.helpOption('-h, --help', 'Display this help message.')
.action(async (directory?: string) => {
if (directory && !/^[a-z0-9-]+$/.test(directory)) {
console.error(
chalk.red(
`Invalid directory "${directory}". Must contain only lowercase letters, numbers, and hyphens`,
),
);
process.exit(1);
}
await new CreateAppCommand().execute(directory);
});
.action(
async (
directory?: string,
options?: {
exhaustive?: boolean;
minimal?: boolean;
interactive?: boolean;
},
) => {
const modeFlags = [
options?.exhaustive,
options?.minimal,
options?.interactive,
].filter(Boolean);
if (modeFlags.length > 1) {
console.error(
chalk.red(
'Error: --exhaustive, --minimal, and --interactive are mutually exclusive.',
),
);
process.exit(1);
}
if (directory && !/^[a-z0-9-]+$/.test(directory)) {
console.error(
chalk.red(
`Invalid directory "${directory}". Must contain only lowercase letters, numbers, and hyphens`,
),
);
process.exit(1);
}
const mode: ScaffoldingMode = options?.minimal
? 'minimal'
: options?.interactive
? 'interactive'
: 'exhaustive';
await new CreateAppCommand().execute(directory, mode);
},
);
program.exitOverride();
@@ -29,9 +29,8 @@ yarn twenty auth:switch # Switch default workspace
yarn twenty auth:list # List all configured workspaces
# Application
yarn twenty app:dev # Start dev mode (watch, build, and sync)
yarn twenty entity:add # Add a new entity (function, front-component, object, role)
yarn twenty app:generate # Generate typed Twenty client
yarn twenty app:dev # Start dev mode (watch, build, sync, and auto-generate typed client)
yarn twenty entity:add # Add a new entity (object, field, function, front-component, role, view, navigation-menu-item)
yarn twenty function:logs # Stream function logs
yarn twenty function:execute # Execute a function with JSON payload
yarn twenty app:uninstall # Uninstall app from workspace
@@ -8,14 +8,24 @@ import inquirer from 'inquirer';
import kebabCase from 'lodash.kebabcase';
import * as path from 'path';
import {
type ExampleOptions,
type ScaffoldingMode,
} from '@/types/scaffolding-options';
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
export class CreateAppCommand {
async execute(directory?: string): Promise<void> {
async execute(
directory?: string,
mode: ScaffoldingMode = 'exhaustive',
): Promise<void> {
try {
const { appName, appDisplayName, appDirectory, appDescription } =
await this.getAppInfos(directory);
const exampleOptions = await this.resolveExampleOptions(mode);
await this.validateDirectory(appDirectory);
this.logCreationInfo({ appDirectory, appName });
@@ -27,6 +37,7 @@ export class CreateAppCommand {
appDisplayName,
appDescription,
appDirectory,
exampleOptions,
});
await install(appDirectory);
@@ -92,6 +103,95 @@ export class CreateAppCommand {
return { appName, appDisplayName, appDirectory, appDescription };
}
private async resolveExampleOptions(
mode: ScaffoldingMode,
): Promise<ExampleOptions> {
if (mode === 'minimal') {
return {
includeExampleObject: false,
includeExampleField: false,
includeExampleLogicFunction: false,
includeExampleFrontComponent: false,
includeExampleView: false,
includeExampleNavigationMenuItem: false,
};
}
if (mode === 'exhaustive') {
return {
includeExampleObject: true,
includeExampleField: true,
includeExampleLogicFunction: true,
includeExampleFrontComponent: true,
includeExampleView: true,
includeExampleNavigationMenuItem: true,
};
}
const { selectedExamples } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedExamples',
message: 'Select which example files to include:',
choices: [
{
name: 'Example object (custom object definition)',
value: 'object',
checked: true,
},
{
name: 'Example field (custom field on the example object)',
value: 'field',
checked: true,
},
{
name: 'Example logic function (server-side handler)',
value: 'logicFunction',
checked: true,
},
{
name: 'Example front component (React UI component)',
value: 'frontComponent',
checked: true,
},
{
name: 'Example view (saved view for the example object)',
value: 'view',
checked: true,
},
{
name: 'Example navigation menu item (sidebar link)',
value: 'navigationMenuItem',
checked: true,
},
],
},
]);
const includeField = selectedExamples.includes('field');
const includeView = selectedExamples.includes('view');
const includeObject =
selectedExamples.includes('object') || includeField || includeView;
if ((includeField || includeView) && !selectedExamples.includes('object')) {
console.log(
chalk.yellow(
'Note: Example object auto-included because example field/view depends on it.',
),
);
}
return {
includeExampleObject: includeObject,
includeExampleField: includeField,
includeExampleLogicFunction: selectedExamples.includes('logicFunction'),
includeExampleFrontComponent: selectedExamples.includes('frontComponent'),
includeExampleView: includeView,
includeExampleNavigationMenuItem:
selectedExamples.includes('navigationMenuItem'),
};
}
private async validateDirectory(appDirectory: string): Promise<void> {
if (!(await fs.pathExists(appDirectory))) {
return;
@@ -0,0 +1,10 @@
export type ScaffoldingMode = 'exhaustive' | 'minimal' | 'interactive';
export type ExampleOptions = {
includeExampleObject: boolean;
includeExampleField: boolean;
includeExampleLogicFunction: boolean;
includeExampleFrontComponent: boolean;
includeExampleView: boolean;
includeExampleNavigationMenuItem: boolean;
};
@@ -2,6 +2,7 @@ import * as fs from 'fs-extra';
import { join } from 'path';
import { tmpdir } from 'os';
import { copyBaseApplicationProject } from '@/utils/app-template';
import { type ExampleOptions } from '@/types/scaffolding-options';
// Mock fs-extra's copy function to skip copying base template (not available during tests)
jest.mock('fs-extra', () => {
@@ -15,6 +16,24 @@ jest.mock('fs-extra', () => {
const APPLICATION_FILE_NAME = 'application-config.ts';
const DEFAULT_ROLE_FILE_NAME = 'default-role.ts';
const ALL_EXAMPLES: ExampleOptions = {
includeExampleObject: true,
includeExampleField: true,
includeExampleLogicFunction: true,
includeExampleFrontComponent: true,
includeExampleView: true,
includeExampleNavigationMenuItem: true,
};
const NO_EXAMPLES: ExampleOptions = {
includeExampleObject: false,
includeExampleField: false,
includeExampleLogicFunction: false,
includeExampleFrontComponent: false,
includeExampleView: false,
includeExampleNavigationMenuItem: false,
};
describe('copyBaseApplicationProject', () => {
let testAppDirectory: string;
@@ -41,6 +60,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
// Verify src/ folder exists
@@ -62,6 +82,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const packageJsonPath = join(testAppDirectory, 'package.json');
@@ -80,6 +101,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const gitignorePath = join(testAppDirectory, '.gitignore');
@@ -96,6 +118,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const yarnLockPath = join(testAppDirectory, 'yarn.lock');
@@ -111,6 +134,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const appConfigPath = join(testAppDirectory, 'src', APPLICATION_FILE_NAME);
@@ -148,6 +172,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const roleConfigPath = join(
@@ -192,6 +217,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
// Verify fs.copy was called with correct destination
@@ -208,6 +234,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'My Test App',
appDescription: '',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const appConfigPath = join(testAppDirectory, 'src', APPLICATION_FILE_NAME);
@@ -225,6 +252,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'App One',
appDescription: 'First app',
appDirectory: firstAppDir,
exampleOptions: ALL_EXAMPLES,
});
// Create second app
@@ -235,6 +263,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'App Two',
appDescription: 'Second app',
appDirectory: secondAppDir,
exampleOptions: ALL_EXAMPLES,
});
// Read both app configs
@@ -267,6 +296,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'App One',
appDescription: 'First app',
appDirectory: firstAppDir,
exampleOptions: ALL_EXAMPLES,
});
// Create second app
@@ -277,6 +307,7 @@ describe('copyBaseApplicationProject', () => {
appDisplayName: 'App Two',
appDescription: 'Second app',
appDirectory: secondAppDir,
exampleOptions: ALL_EXAMPLES,
});
const firstRoleConfig = await fs.readFile(
@@ -299,4 +330,345 @@ describe('copyBaseApplicationProject', () => {
expect(secondUuid).toBeDefined();
expect(firstUuid).not.toBe(secondUuid);
});
describe('scaffolding modes', () => {
describe('exhaustive mode (all examples)', () => {
it('should create all example files when all options are enabled', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const srcPath = join(testAppDirectory, 'src');
expect(
await fs.pathExists(join(srcPath, 'objects', 'example-object.ts')),
).toBe(true);
expect(
await fs.pathExists(join(srcPath, 'fields', 'example-field.ts')),
).toBe(true);
expect(
await fs.pathExists(
join(srcPath, 'logic-functions', 'hello-world.ts'),
),
).toBe(true);
expect(
await fs.pathExists(
join(srcPath, 'front-components', 'hello-world.tsx'),
),
).toBe(true);
expect(
await fs.pathExists(join(srcPath, 'views', 'example-view.ts')),
).toBe(true);
expect(
await fs.pathExists(
join(
srcPath,
'navigation-menu-items',
'example-navigation-menu-item.ts',
),
),
).toBe(true);
});
});
describe('minimal mode (no examples)', () => {
it('should create only core files when no examples are enabled', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: NO_EXAMPLES,
});
const srcPath = join(testAppDirectory, 'src');
// Core files should exist
expect(await fs.pathExists(join(srcPath, APPLICATION_FILE_NAME))).toBe(
true,
);
expect(
await fs.pathExists(join(srcPath, 'roles', DEFAULT_ROLE_FILE_NAME)),
).toBe(true);
// Example files should not exist
expect(
await fs.pathExists(join(srcPath, 'objects', 'example-object.ts')),
).toBe(false);
expect(
await fs.pathExists(join(srcPath, 'fields', 'example-field.ts')),
).toBe(false);
expect(
await fs.pathExists(
join(srcPath, 'logic-functions', 'hello-world.ts'),
),
).toBe(false);
expect(
await fs.pathExists(
join(srcPath, 'front-components', 'hello-world.tsx'),
),
).toBe(false);
expect(
await fs.pathExists(join(srcPath, 'views', 'example-view.ts')),
).toBe(false);
expect(
await fs.pathExists(
join(
srcPath,
'navigation-menu-items',
'example-navigation-menu-item.ts',
),
),
).toBe(false);
});
});
describe('selective examples', () => {
it('should create only front component when only that option is enabled', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: {
includeExampleObject: false,
includeExampleField: false,
includeExampleLogicFunction: false,
includeExampleFrontComponent: true,
includeExampleView: false,
includeExampleNavigationMenuItem: false,
},
});
const srcPath = join(testAppDirectory, 'src');
expect(
await fs.pathExists(
join(srcPath, 'front-components', 'hello-world.tsx'),
),
).toBe(true);
expect(
await fs.pathExists(join(srcPath, 'objects', 'example-object.ts')),
).toBe(false);
expect(
await fs.pathExists(join(srcPath, 'fields', 'example-field.ts')),
).toBe(false);
expect(
await fs.pathExists(
join(srcPath, 'logic-functions', 'hello-world.ts'),
),
).toBe(false);
});
it('should create only logic function when only that option is enabled', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: {
includeExampleObject: false,
includeExampleField: false,
includeExampleLogicFunction: true,
includeExampleFrontComponent: false,
includeExampleView: false,
includeExampleNavigationMenuItem: false,
},
});
const srcPath = join(testAppDirectory, 'src');
expect(
await fs.pathExists(
join(srcPath, 'logic-functions', 'hello-world.ts'),
),
).toBe(true);
expect(
await fs.pathExists(join(srcPath, 'objects', 'example-object.ts')),
).toBe(false);
});
});
});
describe('example object', () => {
it('should create example-object.ts with defineObject and correct structure', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const objectPath = join(
testAppDirectory,
'src',
'objects',
'example-object.ts',
);
expect(await fs.pathExists(objectPath)).toBe(true);
const content = await fs.readFile(objectPath, 'utf8');
expect(content).toContain(
"import { defineObject, FieldType } from 'twenty-sdk'",
);
expect(content).toContain('export default defineObject({');
expect(content).toContain(
'export const EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain('export const NAME_FIELD_UNIVERSAL_IDENTIFIER');
expect(content).toContain("nameSingular: 'exampleItem'");
expect(content).toContain("namePlural: 'exampleItems'");
expect(content).toContain('FieldType.TEXT');
expect(content).toContain(
'labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER',
);
});
it('should generate unique UUIDs for example objects across apps', async () => {
const firstAppDir = join(testAppDirectory, 'app1');
await fs.ensureDir(firstAppDir);
await copyBaseApplicationProject({
appName: 'app-one',
appDisplayName: 'App One',
appDescription: 'First app',
appDirectory: firstAppDir,
exampleOptions: ALL_EXAMPLES,
});
const secondAppDir = join(testAppDirectory, 'app2');
await fs.ensureDir(secondAppDir);
await copyBaseApplicationProject({
appName: 'app-two',
appDisplayName: 'App Two',
appDescription: 'Second app',
appDirectory: secondAppDir,
exampleOptions: ALL_EXAMPLES,
});
const firstContent = await fs.readFile(
join(firstAppDir, 'src', 'objects', 'example-object.ts'),
'utf8',
);
const secondContent = await fs.readFile(
join(secondAppDir, 'src', 'objects', 'example-object.ts'),
'utf8',
);
const uuidRegex =
/EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER =\s*'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'/;
const firstUuid = firstContent.match(uuidRegex)?.[1];
const secondUuid = secondContent.match(uuidRegex)?.[1];
expect(firstUuid).toBeDefined();
expect(secondUuid).toBeDefined();
expect(firstUuid).not.toBe(secondUuid);
});
});
describe('example field', () => {
it('should create example-field.ts with defineField referencing the object', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const fieldPath = join(
testAppDirectory,
'src',
'fields',
'example-field.ts',
);
expect(await fs.pathExists(fieldPath)).toBe(true);
const content = await fs.readFile(fieldPath, 'utf8');
expect(content).toContain(
"import { defineField, FieldType } from 'twenty-sdk'",
);
expect(content).toContain(
"import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'",
);
expect(content).toContain('export default defineField({');
expect(content).toContain(
'objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain('FieldType.NUMBER');
expect(content).toContain("name: 'priority'");
});
});
describe('example view', () => {
it('should create example-view.ts with defineView referencing the object', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const viewPath = join(
testAppDirectory,
'src',
'views',
'example-view.ts',
);
expect(await fs.pathExists(viewPath)).toBe(true);
const content = await fs.readFile(viewPath, 'utf8');
expect(content).toContain("import { defineView } from 'twenty-sdk'");
expect(content).toContain(
"import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'",
);
expect(content).toContain('export default defineView({');
expect(content).toContain(
'objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain("name: 'example-view'");
});
});
describe('example navigation menu item', () => {
it('should create example-navigation-menu-item.ts with defineNavigationMenuItem', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: ALL_EXAMPLES,
});
const navPath = join(
testAppDirectory,
'src',
'navigation-menu-items',
'example-navigation-menu-item.ts',
);
expect(await fs.pathExists(navPath)).toBe(true);
const content = await fs.readFile(navPath, 'utf8');
expect(content).toContain(
"import { defineNavigationMenuItem } from 'twenty-sdk'",
);
expect(content).toContain('export default defineNavigationMenuItem({');
expect(content).toContain("name: 'example-navigation-menu-item'");
expect(content).toContain("icon: 'IconList'");
expect(content).toContain('position: 0');
});
});
});
@@ -3,6 +3,8 @@ import { join } from 'path';
import { v4 } from 'uuid';
import { ASSETS_DIR } from 'twenty-shared/application';
import { type ExampleOptions } from '@/types/scaffolding-options';
const SRC_FOLDER = 'src';
export const copyBaseApplicationProject = async ({
@@ -10,11 +12,13 @@ export const copyBaseApplicationProject = async ({
appDisplayName,
appDescription,
appDirectory,
exampleOptions,
}: {
appName: string;
appDisplayName: string;
appDescription: string;
appDirectory: string;
exampleOptions: ExampleOptions;
}) => {
await fs.copy(join(__dirname, './constants/base-application'), appDirectory);
@@ -37,16 +41,58 @@ export const copyBaseApplicationProject = async ({
fileName: 'default-role.ts',
});
await createDefaultFrontComponent({
appDirectory: sourceFolderPath,
fileFolder: 'front-components',
fileName: 'hello-world.tsx',
});
if (exampleOptions.includeExampleObject) {
await createExampleObject({
appDirectory: sourceFolderPath,
fileFolder: 'objects',
fileName: 'example-object.ts',
});
}
await createDefaultFunction({
if (exampleOptions.includeExampleField) {
await createExampleField({
appDirectory: sourceFolderPath,
fileFolder: 'fields',
fileName: 'example-field.ts',
});
}
if (exampleOptions.includeExampleLogicFunction) {
await createDefaultFunction({
appDirectory: sourceFolderPath,
fileFolder: 'logic-functions',
fileName: 'hello-world.ts',
});
}
if (exampleOptions.includeExampleFrontComponent) {
await createDefaultFrontComponent({
appDirectory: sourceFolderPath,
fileFolder: 'front-components',
fileName: 'hello-world.tsx',
});
}
if (exampleOptions.includeExampleView) {
await createExampleView({
appDirectory: sourceFolderPath,
fileFolder: 'views',
fileName: 'example-view.ts',
});
}
if (exampleOptions.includeExampleNavigationMenuItem) {
await createExampleNavigationMenuItem({
appDirectory: sourceFolderPath,
fileFolder: 'navigation-menu-items',
fileName: 'example-navigation-menu-item.ts',
});
}
await createDefaultPostInstallFunction({
appDirectory: sourceFolderPath,
fileFolder: 'logic-functions',
fileName: 'hello-world.ts',
fileName: 'post-install.ts',
});
await createApplicationConfig({
@@ -196,7 +242,6 @@ const handler = async (): Promise<{ message: string }> => {
return { message: 'Hello, World!' };
};
// Logic function handler - rename and implement your logic
export default defineLogicFunction({
universalIdentifier: '${universalIdentifier}',
name: 'hello-world-logic-function',
@@ -215,6 +260,170 @@ export default defineLogicFunction({
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createDefaultPostInstallFunction = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const content = `import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '${universalIdentifier}';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
`;
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createExampleObject = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const objectUniversalIdentifier = v4();
const nameFieldUniversalIdentifier = v4();
const content = `import { defineObject, FieldType } from 'twenty-sdk';
export const EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER =
'${objectUniversalIdentifier}';
export const NAME_FIELD_UNIVERSAL_IDENTIFIER =
'${nameFieldUniversalIdentifier}';
export default defineObject({
universalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
nameSingular: 'exampleItem',
namePlural: 'exampleItems',
labelSingular: 'Example item',
labelPlural: 'Example items',
description: 'A sample custom object',
icon: 'IconBox',
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.TEXT,
name: 'name',
label: 'Name',
description: 'Name of the example item',
icon: 'IconAbc',
},
],
});
`;
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createExampleField = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const content = `import { defineField, FieldType } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
export default defineField({
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
universalIdentifier: '${universalIdentifier}',
type: FieldType.NUMBER,
name: 'priority',
label: 'Priority',
description: 'Priority level for the example item (1-10)',
});
`;
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createExampleView = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const content = `import { defineView } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
export default defineView({
universalIdentifier: '${universalIdentifier}',
name: 'example-view',
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
position: 0,
});
`;
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createExampleNavigationMenuItem = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const content = `import { defineNavigationMenuItem } from 'twenty-sdk';
export default defineNavigationMenuItem({
universalIdentifier: '${universalIdentifier}',
name: 'example-navigation-menu-item',
icon: 'IconList',
position: 0,
// Link to a view:
// viewUniversalIdentifier: '...',
// Or link to an object:
// targetObjectUniversalIdentifier: '...',
// Or link to an external URL:
// link: 'https://example.com',
});
`;
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createApplicationConfig = async ({
displayName,
description,
@@ -230,12 +439,14 @@ const createApplicationConfig = async ({
}) => {
const content = `import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '${v4()}',
displayName: '${displayName}',
description: '${description ?? ''}',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
`;
@@ -17,7 +17,6 @@
},
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
@@ -17,7 +17,6 @@
},
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
@@ -17,7 +17,6 @@
},
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
@@ -11,7 +11,6 @@
"scripts": {
"create-entity": "twenty app add",
"dev": "twenty app dev",
"generate": "twenty app generate",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
"auth": "twenty auth login"
@@ -17,7 +17,6 @@
"app:dev": "twenty app dev",
"app:sync": "twenty app sync",
"entity:add": "twenty entity add",
"app:generate": "twenty app generate",
"function:logs": "twenty function logs",
"function:execute": "twenty function execute",
"app:uninstall": "twenty app uninstall",
@@ -26,7 +26,7 @@ Apps let you build and manage Twenty customizations **as code**. Instead of conf
Create a new app using the official scaffolder, then authenticate and start developing:
```bash filename="Terminal"
# Scaffold a new app
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
@@ -41,21 +41,34 @@ yarn twenty auth:login
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
From here you can:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
# Generate a typed Twenty client and workspace entity types
yarn twenty app:generate
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty app:uninstall
@@ -72,9 +85,9 @@ When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
- Copies a minimal base application into `my-twenty-app/`
- Adds a local `twenty-sdk` dependency and Yarn 4 configuration
- Creates config files and scripts wired to the `twenty` CLI
- Generates a default application config and a default function role
- Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
A freshly scaffolded app looks like this:
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -93,12 +106,23 @@ my-twenty-app/
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
── hello-world.ts # Example logic function
└── front-components/
└── hello-world.tsx # Example front component
── hello-world.ts # Example logic function
└── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
└── navigation-menu-items/
└── example-navigation-menu-item.ts # Example sidebar navigation link
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
At a high level:
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus a `twenty` script that delegates to the local `twenty` CLI. Run `yarn twenty help` to list all available commands.
@@ -121,6 +145,8 @@ The SDK detects entities by parsing your TypeScript files for **`export default
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | Role definitions |
| `defineField()` | Field extensions for existing objects |
| `defineView()` | Saved view definitions |
| `defineNavigationMenuItem()` | Navigation menu item definitions |
<Note>
**File naming is flexible.** Entity detection is AST-based — the SDK scans your source files for the `export default define<Entity>({...})` pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., `logic-functions/`, `roles/`) is just a convention for code organization, not a requirement.
@@ -140,7 +166,7 @@ export default defineObject({
Later commands will add more files and folders:
- `yarn twenty app:generate` will create a `generated/` folder (typed Twenty client + workspace types).
- `yarn twenty app:dev` will auto-generate a typed API client in `node_modules/twenty-sdk/generated` (typed Twenty client + workspace types).
- `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, or roles.
## Authentication
@@ -192,6 +218,8 @@ The SDK provides helper functions for defining your app entities. As described i
| `defineFrontComponent()` | Define front components for custom UI |
| `defineRole()` | Configure role permissions and object access |
| `defineField()` | Extend existing objects with additional fields |
| `defineView()` | Define saved views for objects |
| `defineNavigationMenuItem()` | Define sidebar navigation links |
These functions validate your configuration at build time and provide IDE autocompletion and type safety.
@@ -292,6 +320,7 @@ Every app has a single `application-config.ts` file that describes:
- **Who the app is**: identifiers, display name, and description.
- **How its functions run**: which role they use for permissions.
- **(Optional) variables**: keyvalue pairs exposed to your functions as environment variables.
- **(Optional) post-install function**: a logic function that runs after the app is installed.
Use `defineApplication()` to define your application configuration:
@@ -299,6 +328,7 @@ Use `defineApplication()` to define your application configuration:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -314,6 +344,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -321,6 +352,7 @@ Notes:
- `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
- `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
- `defaultRoleUniversalIdentifier` must match the role file (see below).
- `postInstallLogicFunctionUniversalIdentifier` (optional) points to a logic function that runs automatically after the app is installed. See [Post-install functions](#post-install-functions).
#### Roles and permissions
@@ -453,6 +485,54 @@ Notes:
- The `triggers` array is optional. Functions without triggers can be used as utility functions called by other functions.
- You can mix multiple trigger types in a single function.
### Post-install functions
A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings.
When you scaffold a new app with `create-twenty-app`, a post-install function is generated for you at `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
The function is wired into your app by referencing its universal identifier in `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
You can also manually execute the post-install function at any time using the CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Key points:
- Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function.
- The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation.
- The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
- Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`.
### Route trigger payload
<Warning>
@@ -651,7 +731,7 @@ You can create new front components in two ways:
### Generated typed client
Run `yarn twenty app:generate` to create a local typed client in `generated/` based on your workspace schema. Use it in your functions:
The typed client is auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema. Use it in your functions:
```typescript
import Twenty from '~/generated';
@@ -660,7 +740,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
The client is re-generated by `yarn twenty app:generate`. Re-run after changing your objects or when onboarding to a new workspace.
The client is re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
#### Runtime credentials in logic functions
@@ -697,13 +777,13 @@ Then add a `twenty` script:
}
```
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help`, etc.
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty app:dev`, `yarn twenty help`, etc.
## Troubleshooting
- Authentication errors: run `yarn twenty auth:login` and ensure your API key has the required permissions.
- Cannot connect to server: verify the API URL and that the Twenty server is reachable.
- Types or client missing/outdated: run `yarn twenty app:generate`.
- Types or client missing/outdated: restart `yarn twenty app:dev` — it auto-generates the typed client.
- Dev mode not syncing: ensure `yarn twenty app:dev` is running and that changes are not ignored by your environment.
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,7 +27,7 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
أنشئ تطبيقًا جديدًا باستخدام المُهيئ الرسمي، ثم قم بالمصادقة وابدأ التطوير:
```bash filename="Terminal"
# إنشاء تطبيق جديد
# إنشاء تطبيق جديد (يتضمن جميع الأمثلة افتراضيًا)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
@@ -42,25 +42,38 @@ yarn twenty auth:login
yarn twenty app:dev
```
يدعم المُنشئ ثلاثة أوضاع للتحكم في ملفات الأمثلة التي سيتم تضمينها:
```bash filename="Terminal"
# الافتراضي (شامل): جميع الأمثلة (كائن، حقل، دالة منطقية، مكوّن الواجهة الأمامية، عرض، عنصر قائمة التنقل)
npx create-twenty-app@latest my-app
# الأدنى: الملفات الأساسية فقط (application-config.ts و default-role.ts)
npx create-twenty-app@latest my-app --minimal
# التفاعلي: اختر الأمثلة التي تريد تضمينها
npx create-twenty-app@latest my-app --interactive
```
من هنا يمكنك:
```bash filename="Terminal"
# أضف كيانًا جديدًا إلى تطبيقك (موجّه)
# Add a new entity to your application (guided)
yarn twenty entity:add
# ولِّد عميل Twenty مضبوط الأنواع وأنواع كيانات مساحة العمل
yarn twenty app:generate
# راقب سجلات وظائف تطبيقك
# Watch your application's function logs
yarn twenty function:logs
# نفّذ وظيفة بالاسم
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# أزل تثبيت التطبيق من مساحة العمل الحالية
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty app:uninstall
# اعرض مساعدة الأوامر
# Display commands' help
yarn twenty help
```
@@ -73,9 +86,9 @@ yarn twenty help
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
* يُولّد ضبطًا افتراضيًا للتطبيق ودورًا افتراضيًا للوظيفة
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالة ما بعد التثبيت) بالإضافة إلى ملفات الأمثلة بحسب وضع الإنشاء
يبدو التطبيق المُنشأ حديثًا بالقالب كما يلي:
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -91,15 +104,26 @@ my-twenty-app/
README.md
public/ # مجلد الأصول العامة (صور، خطوط، إلخ)
src/
├── application-config.ts # مطلوب - التكوين الرئيسي للتطبيق
├── application-config.ts # مطلوب - إعدادات التطبيق الرئيسية
├── roles/
│ └── default-role.ts # الدور الافتراضي لوظائف المنطق
│ └── default-role.ts # الدور الافتراضي للدوال المنطقية
├── objects/
│ └── example-object.ts # تعريف كائن مخصص — مثال
├── fields/
│ └── example-field.ts # تعريف حقل مستقل — مثال
├── logic-functions/
── hello-world.ts # مثال لوظيفة منطقية
└── front-components/
└── hello-world.tsx # مثال لمكوّن الواجهة الأمامية
── hello-world.ts # دالة منطقية — مثال
└── post-install.ts # دالة منطقية لما بعد التثبيت
├── front-components/
│ └── hello-world.tsx # مكوّن واجهة أمامية — مثال
├── views/
│ └── example-view.ts # تعريف عرض محفوظ — مثال
└── navigation-menu-items/
└── example-navigation-menu-item.ts # رابط تنقّل في الشريط الجانبي — مثال
```
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts` و`roles/default-role.ts` و`logic-functions/post-install.ts`). مع `--interactive`، تختار ملفات الأمثلة التي تريد تضمينها.
بشكل عام:
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` بالإضافة إلى نص برمجي `twenty` يفوِّض إلى `twenty` CLI المحلي. شغِّل `yarn twenty help` لعرض جميع الأوامر المتاحة.
@@ -115,13 +139,15 @@ my-twenty-app/
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
| دالة مساعدة | نوع الكيان |
| ------------------------ | --------------------------------- |
| `defineObject()` | تعريفات كائنات مخصصة |
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | تعريفات الأدوار |
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
| دالة مساعدة | نوع الكيان |
| ---------------------------- | --------------------------------- |
| `defineObject()` | تعريفات كائنات مخصصة |
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | تعريفات الأدوار |
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
| `defineView()` | تعريفات العروض المحفوظة |
| `defineNavigationMenuItem()` | تعريفات عناصر قائمة التنقل |
<Note>
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
@@ -142,7 +168,7 @@ export default defineObject({
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
* `yarn twenty app:generate` سيُنشئ مجلدًا `generated/` (عميل Twenty مضبوط الأنواع + أنواع مساحة العمل).
* `yarn twenty app:dev` سيولّد تلقائيًا عميل API مضبوط الأنواع في `node_modules/twenty-sdk/generated` (عميل Twenty مضبوط الأنواع + أنواع مساحة العمل).
* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات تحت `src/` لكائناتك المخصصة أو الوظائف أو المكونات الواجهية أو الأدوار.
## المصادقة
@@ -186,14 +212,16 @@ yarn twenty auth:status
يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define<Entity>({...})` كي يتم اكتشاف كياناتك:
| دالة | الغرض |
| ------------------------ | ---------------------------------------------------- |
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
| دالة | الغرض |
| ---------------------------- | ---------------------------------------------------- |
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
| `defineView()` | تعريف العروض المحفوظة للكائنات |
| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي |
تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع.
@@ -293,6 +321,7 @@ export default defineObject({
* **هوية التطبيق**: المعرفات، اسم العرض، والوصف.
* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات.
* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة.
* **(Optional) post-install function**: a logic function that runs after the app is installed.
Use `defineApplication()` to define your application configuration:
@@ -300,6 +329,7 @@ Use `defineApplication()` to define your application configuration:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ export default defineApplication({
* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه).
* `postInstallLogicFunctionUniversalIdentifier` (optional) points to a logic function that runs automatically after the app is installed. See [Post-install functions](#post-install-functions).
#### الأدوار والصلاحيات
@@ -461,6 +493,55 @@ export default defineLogicFunction({
* المصفوفة `triggers` اختيارية. يمكن استخدام الوظائف بدون مشغلات كوظائف مساعدة تُستدعى بواسطة وظائف أخرى.
* يمكنك مزج أنواع متعددة من المشغلات في وظيفة واحدة.
### Post-install functions
A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings.
عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما بعد التثبيت لك في `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
يتم ربط الدالة بتطبيقك من خلال الإشارة إلى المعرِّف العالمي الخاص بها في `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
النقاط الرئيسية:
* دوال ما بعد التثبيت هي دوال منطقية قياسية — فهي تستخدم `defineLogicFunction()` مثل أي دالة أخرى.
* حقل `postInstallLogicFunctionUniversalIdentifier` في `defineApplication()` اختياري. إذا تم تجاهله، لن يتم تشغيل أي دالة بعد التثبيت.
* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات.
* لا تحتاج دوال ما بعد التثبيت إلى مُشغِّلات — حيث يستدعيها النظام الأساسي أثناء التثبيت أو يدويًا عبر `function:execute --postInstall`.
### حمولة مشغل المسار
<Warning>
@@ -662,7 +743,7 @@ export default defineFrontComponent({
### عميل مُولَّد مضبوط الأنواع
شغّل `yarn twenty app:generate` لإنشاء عميل محلي مضبوط الأنواع في `generated/` استنادًا إلى مخطط مساحة العمل لديك. استخدمه في وظائفك:
يُولَّد العميل مضبوط الأنواع تلقائيًا بواسطة `yarn twenty app:dev` ويُخزَّن في `node_modules/twenty-sdk/generated` استنادًا إلى مخطط مساحة العمل لديك. استخدمه في وظائفك:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
يُعاد توليد العميل بواسطة `yarn twenty app:generate`. أعِد التشغيل بعد تغيير كائناتك أو عند الانضمام إلى مساحة عمل جديدة.
يُعاد توليد العميل تلقائيًا بواسطة `yarn twenty app:dev` كلما تغيّرت كائناتك أو حقولك.
#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية
@@ -708,13 +789,13 @@ yarn add -D twenty-sdk
}
```
الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty <command>`، مثلًا: `yarn twenty app:dev`، `yarn twenty app:generate`، `yarn twenty help`، إلخ.
الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty <command>`، مثلًا: `yarn twenty app:dev`، `yarn twenty help`، إلخ.
## استكشاف الأخطاء وإصلاحها
* أخطاء المصادقة: شغّل `yarn twenty auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة البرمجة وأن خادم Twenty قابل للوصول.
* الأنواع أو العميل مفقود/قديم: شغّل `yarn twenty app:generate`.
* الأنواع أو العميل مفقود/قديم: أعد تشغيل `yarn twenty app:dev` — فهو ينشئ العميل مضبوط الأنواع بشكل تلقائي.
* وضع التطوير لا يزامن: تأكد من أن `yarn twenty app:dev` قيد التشغيل وأن التغييرات ليست متجاهلة من بيئتك.
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,7 +27,7 @@ Aplikace vám umožňují vytvářet a spravovat přizpůsobení Twenty **jako k
Vytvořte novou aplikaci pomocí oficiálního scaffolderu, poté se ověřte a začněte vyvíjet:
```bash filename="Terminal"
# Vygenerujte kostru nové aplikace
# Vygenerujte kostru nové aplikace (ve výchozím nastavení zahrnuje všechny příklady)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
@@ -42,21 +42,34 @@ yarn twenty auth:login
yarn twenty app:dev
```
Nástroj pro generování kostry podporuje tři režimy pro řízení toho, které ukázkové soubory jsou zahrnuty:
```bash filename="Terminal"
# Výchozí (úplný): všechny příklady (objekt, pole, logická funkce, front-endová komponenta, zobrazení, položka navigační nabídky)
npx create-twenty-app@latest my-app
# Minimální: pouze základní soubory (application-config.ts a default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interaktivní: vyberte, které příklady zahrnout
npx create-twenty-app@latest my-app --interactive
```
Odtud můžete:
```bash filename="Terminal"
# Přidejte do vaší aplikace novou entitu (s průvodcem)
yarn twenty entity:add
# Vygenerujte typovaného klienta Twenty a typy entit pracovního prostoru
yarn twenty app:generate
# Sledujte logy funkcí vaší aplikace
yarn twenty function:logs
# Spusťte funkci podle názvu
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Spusťte postinstalační funkci
yarn twenty function:execute --postInstall
# Odinstalujte aplikaci z aktuálního pracovního prostoru
yarn twenty app:uninstall
@@ -73,9 +86,9 @@ Když spustíte `npx create-twenty-app@latest my-twenty-app`, scaffolder:
* Zkopíruje minimální základní aplikaci do `my-twenty-app/`
* Přidá lokální závislost `twenty-sdk` a konfiguraci pro Yarn 4
* Vytvoří konfigurační soubory a skripty napojené na `twenty` CLI
* Vygeneruje výchozí konfiguraci aplikace a výchozí roli funkcí
* Vygeneruje základní soubory (konfigurace aplikace, výchozí role funkcí, postinstalační funkce) a k nim ukázkové soubory podle zvoleného režimu generování kostry
Čerstvě vytvořená aplikace vypadá takto:
Čerstvě vygenerovaná aplikace s výchozím režimem `--exhaustive` vypadá takto:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -94,12 +107,23 @@ my-twenty-app/
├── application-config.ts # Povinné hlavní konfigurace aplikace
├── roles/
│ └── default-role.ts # Výchozí role pro logické funkce
├── objects/
│ └── example-object.ts # Ukázková definice vlastního objektu
├── fields/
│ └── example-field.ts # Ukázková samostatná definice pole
├── logic-functions/
── hello-world.ts # Ukázková logická funkce
└── front-components/
└── hello-world.tsx # Ukázková front-endová komponenta
── hello-world.ts # Ukázková logická funkce
└── post-install.ts # Postinstalační logická funkce
├── front-components/
│ └── hello-world.tsx # Ukázková front-endová komponenta
├── views/
│ └── example-view.ts # Ukázková definice uloženého zobrazení
└── navigation-menu-items/
└── example-navigation-menu-item.ts # Ukázkový odkaz postranní navigace
```
S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts` a `logic-functions/post-install.ts`). S volbou `--interactive` si vyberete, které ukázkové soubory chcete zahrnout.
V kostce:
* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skript `twenty`, který deleguje na lokální `twenty` CLI. Spusťte `yarn twenty help` pro výpis všech dostupných příkazů.
@@ -115,13 +139,15 @@ V kostce:
SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define<Entity>({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`:
| Pomocná funkce | Typ entity |
| ------------------------ | ------------------------------------- |
| `defineObject()` | Definice vlastních objektů |
| `defineLogicFunction()` | Definice logických funkcí |
| `defineFrontComponent()` | Definice frontendových komponent |
| `defineRole()` | Definice rolí |
| `defineField()` | Rozšíření polí u existujících objektů |
| Pomocná funkce | Typ entity |
| ---------------------------- | ------------------------------------- |
| `defineObject()` | Definice vlastních objektů |
| `defineLogicFunction()` | Definice logických funkcí |
| `defineFrontComponent()` | Definice frontendových komponent |
| `defineRole()` | Definice rolí |
| `defineField()` | Rozšíření polí u existujících objektů |
| `defineView()` | Definice uložených zobrazení |
| `defineNavigationMenuItem()` | Definice položek navigační nabídky |
<Note>
**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define<Entity>({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek.
@@ -142,7 +168,7 @@ export default defineObject({
Pozdější příkazy přidají další soubory a složky:
* `yarn twenty app:generate` vytvoří složku `generated/` (typovaný klient Twenty + typy pracovního prostoru).
* `yarn twenty app:dev` automaticky vygeneruje typovaného klienta API v `node_modules/twenty-sdk/generated` (typovaný klient Twenty + typy pracovního prostoru).
* `yarn twenty entity:add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty nebo role.
## Ověření
@@ -186,14 +212,16 @@ twenty-sdk poskytuje typované stavební bloky a pomocné funkce, které použí
SDK poskytuje pomocné funkce pro definování entit vaší aplikace. Jak je popsáno v [Detekce entit](#entity-detection), musíte použít `export default define<Entity>({...})`, aby byly vaše entity detekovány:
| Funkce | Účel |
| ------------------------ | ----------------------------------------------------------------- |
| `defineApplication()` | Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci) |
| `defineObject()` | Definice vlastních objektů s poli |
| `defineLogicFunction()` | Definice logických funkcí s obslužnými funkcemi |
| `defineFrontComponent()` | Definujte frontendové komponenty pro vlastní uživatelské rozhraní |
| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům |
| `defineField()` | Rozšiřte existující objekty o další pole |
| Funkce | Účel |
| ---------------------------- | ----------------------------------------------------------------- |
| `defineApplication()` | Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci) |
| `defineObject()` | Definice vlastních objektů s poli |
| `defineLogicFunction()` | Definice logických funkcí s obslužnými funkcemi |
| `defineFrontComponent()` | Definujte frontendové komponenty pro vlastní uživatelské rozhraní |
| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům |
| `defineField()` | Rozšiřte existující objekty o další pole |
| `defineView()` | Definujte uložená zobrazení pro objekty |
| `defineNavigationMenuItem()` | Definujte odkazy postranní navigace |
Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost.
@@ -293,6 +321,7 @@ Každá aplikace má jeden soubor `application-config.ts`, který popisuje:
* **Identitu aplikace**: identifikátory, zobrazovaný název a popis.
* **Jak běží její funkce**: kterou roli používají pro oprávnění.
* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí.
* **(Volitelná) postinstalační funkce**: logická funkce, která se spouští po instalaci aplikace.
Use `defineApplication()` to define your application configuration:
@@ -300,6 +329,7 @@ Use `defineApplication()` to define your application configuration:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ Poznámky:
* Pole `universalIdentifier` jsou deterministická ID, která vlastníte; vygenerujte je jednou a udržujte je stabilní napříč synchronizacemi.
* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` se musí shodovat se souborem role (viz níže).
* `postInstallLogicFunctionUniversalIdentifier` (volitelné) odkazuje na logickou funkci, která se automaticky spustí po instalaci aplikace. Viz [Postinstalační funkce](#post-install-functions).
#### Role a oprávnění
@@ -461,6 +493,55 @@ Poznámky:
* Pole `triggers` je volitelné. Funkce bez spouštěčů lze použít jako pomocné funkce volané jinými funkcemi.
* V jedné funkci můžete kombinovat více typů spouštěčů.
### Postinstalační funkce
Postinstalační funkce je logická funkce, která se automaticky spouští po instalaci vaší aplikace do pracovního prostoru. To je užitečné pro jednorázové úlohy nastavení, jako je naplnění výchozími daty, vytvoření počátečních záznamů nebo konfigurace nastavení pracovního prostoru.
Když vygenerujete kostru nové aplikace pomocí `create-twenty-app`, vytvoří se pro vás postinstalační funkce v `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
Funkce je připojena do vaší aplikace odkazem na její univerzální identifikátor v `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Hlavní body:
* Postinstalační funkce jsou standardní logické funkce — používají `defineLogicFunction()` stejně jako jakákoli jiná funkce.
* Pole `postInstallLogicFunctionUniversalIdentifier` v `defineApplication()` je volitelné. Pokud je vynecháno, po instalaci se nespustí žádná funkce.
* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty.
* Postinstalační funkce nepotřebují spouštěče — jsou spouštěny platformou během instalace nebo ručně pomocí `function:execute --postInstall`.
### Payload spouštěče trasy
<Warning>
@@ -662,7 +743,7 @@ Nové frontendové komponenty můžete vytvořit dvěma způsoby:
### Generovaný typovaný klient
Spusťte `yarn twenty app:generate` a vytvořte lokálního typovaného klienta v `generated/` na základě schématu vašeho pracovního prostoru. Použijte jej ve svých funkcích:
Typovaný klient je automaticky generován pomocí `yarn twenty app:dev` a ukládá se do `node_modules/twenty-sdk/generated` podle schématu vašeho pracovního prostoru. Použijte jej ve svých funkcích:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
Klient je znovu generován příkazem `yarn twenty app:generate`. Spusťte znovu po změně vašich objektů nebo při připojování k novému pracovnímu prostoru.
Klient se automaticky znovu generuje pomocí `yarn twenty app:dev` kdykoli se změní vaše objekty nebo pole.
#### Běhové přihlašovací údaje v logických funkcích
@@ -708,13 +789,13 @@ Poté přidejte skript `twenty`:
}
```
Nyní můžete spouštět všechny příkazy přes `yarn twenty <command>`, např. `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help` atd.
Nyní můžete spouštět všechny příkazy přes `yarn twenty <command>`, např. `yarn twenty app:dev`, `yarn twenty help` atd.
## Řešení potíží
* Chyby ověření: spusťte `yarn twenty auth:login` a ujistěte se, že váš klíč API má požadovaná oprávnění.
* Nelze se připojit k serveru: ověřte URL API a že je server Twenty dosažitelný.
* Typy nebo klient chybí nebo jsou zastaralé: spusťte `yarn twenty app:generate`.
* Types or client missing/outdated: restart `yarn twenty app:dev` — it auto-generates the typed client.
* Režim vývoje se nesynchronizuje: ujistěte se, že běží `yarn twenty app:dev` a že vaše prostředí změny neignoruje.
Kanál podpory na Discordu: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,7 +27,7 @@ Mit Apps können Sie Twenty-Anpassungen **als Code** erstellen und verwalten. An
Erstellen Sie mit dem offiziellen Scaffolder eine neue App, authentifizieren Sie sich und beginnen Sie mit der Entwicklung:
```bash filename="Terminal"
# Eine neue App erstellen
# Eine neue App erstellen (enthält standardmäßig alle Beispiele)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
@@ -42,21 +42,34 @@ yarn twenty auth:login
yarn twenty app:dev
```
Das Scaffolding-Tool unterstützt drei Modi, um zu steuern, welche Beispieldateien enthalten sind:
```bash filename="Terminal"
# Standard (umfassend): alle Beispiele (Objekt, Feld, Logikfunktion, Frontend-Komponente, View, Navigationsmenüeintrag)
npx create-twenty-app@latest my-app
# Minimal: nur Kerndateien (application-config.ts und default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interaktiv: wähle aus, welche Beispiele enthalten sein sollen
npx create-twenty-app@latest my-app --interactive
```
Von hier aus können Sie:
```bash filename="Terminal"
# Eine neue Entität zu deiner Anwendung hinzufügen (geführt)
# Eine neue Entität zu Ihrer Anwendung hinzufügen (geführt)
yarn twenty entity:add
# Einen typisierten Twenty-Client und Entitätstypen für den Arbeitsbereich generieren
yarn twenty app:generate
# Die Funktionsprotokolle deiner Anwendung überwachen
# Die Funktionsprotokolle Ihrer Anwendung überwachen
yarn twenty function:logs
# Eine Funktion anhand ihres Namens ausführen
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Die Post-Installationsfunktion ausführen
yarn twenty function:execute --postInstall
# Die Anwendung aus dem aktuellen Arbeitsbereich deinstallieren
yarn twenty app:uninstall
@@ -73,9 +86,9 @@ Wenn Sie `npx create-twenty-app@latest my-twenty-app` ausführen, erledigt der S
* Kopiert eine minimale Basisanwendung nach `my-twenty-app/`
* Fügt eine lokale `twenty-sdk`-Abhängigkeit und die Yarn-4-Konfiguration hinzu
* Erstellt Konfigurationsdateien und Skripte, die an die `twenty`-CLI angebunden sind
* Generiert eine Standard-Anwendungskonfiguration und eine Standard-Funktionsrolle
* Erzeugt Kerndateien (Anwendungskonfiguration, Standardrolle für Logikfunktionen, Post-Installationsfunktion) sowie Beispieldateien entsprechend dem Scaffolding-Modus
Eine frisch erzeugte App sieht so aus:
Eine frisch erstellte App mit dem Standardmodus `--exhaustive` sieht so aus:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -89,17 +102,28 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Public assets folder (images, fonts, etc.)
public/ # Ordner für öffentliche Assets (Bilder, Schriftarten usw.)
src/
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
├── logic-functions/
│ └── hello-world.ts # Example logic function
└── front-components/
└── hello-world.tsx # Example front component
application-config.ts # Erforderlich  Hauptkonfiguration der Anwendung
roles/
 default-role.ts # Standardrolle für Logikfunktionen
objects/
 example-object.ts # Beispiel für eine benutzerdefinierte Objektdefinition
fields/
 example-field.ts # Beispiel für eine eigenständige Felddefinition
logic-functions/
 hello-world.ts # Beispiel für eine Logikfunktion
 post-install.ts # Post-Installations-Logikfunktion
front-components/
 hello-world.tsx # Beispiel für eine Frontend-Komponente
views/
 example-view.ts # Beispiel für eine gespeicherte View-Definition
navigation-menu-items/
example-navigation-menu-item.ts # Beispiel für einen Navigationslink in der Seitenleiste
```
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts` und `logic-functions/post-install.ts`). Mit `--interactive` wählst du aus, welche Beispieldateien enthalten sein sollen.
Auf hoher Ebene:
* **package.json**: Deklariert den App-Namen, die Version und die Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie ein `twenty`-Skript hinzu, das an die lokale `twenty`-CLI delegiert. Führe `yarn twenty help` aus, um alle verfügbaren Befehle aufzulisten.
@@ -115,13 +139,15 @@ Auf hoher Ebene:
Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define<Entity>({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird:
| Hilfsfunktion | Entitätstyp |
| ------------------------ | ---------------------------------------- |
| `defineObject()` | Benutzerdefinierte Objektdefinitionen |
| `defineLogicFunction()` | Definitionen von Logikfunktionen |
| `defineFrontComponent()` | Definitionen von Frontend-Komponenten |
| `defineRole()` | Rollendefinitionen |
| `defineField()` | Felderweiterungen für bestehende Objekte |
| Hilfsfunktion | Entitätstyp |
| ---------------------------- | ----------------------------------------- |
| `defineObject()` | Benutzerdefinierte Objektdefinitionen |
| `defineLogicFunction()` | Definitionen von Logikfunktionen |
| `defineFrontComponent()` | Definitionen von Frontend-Komponenten |
| `defineRole()` | Rollendefinitionen |
| `defineField()` | Felderweiterungen für bestehende Objekte |
| `defineView()` | Gespeicherte View-Definitionen |
| `defineNavigationMenuItem()` | Definitionen von Navigationsmenüeinträgen |
<Note>
**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert — das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define<Entity>({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung.
@@ -142,7 +168,7 @@ export default defineObject({
Spätere Befehle fügen weitere Dateien und Ordner hinzu:
* `yarn twenty app:generate` erstellt einen `generated/`-Ordner (typisierter Twenty-Client + Workspace-Typen).
* `yarn twenty app:dev` generiert automatisch einen typisierten API-Client in `node_modules/twenty-sdk/generated` (typisierter Twenty-Client + Arbeitsbereichs-Typen).
* `yarn twenty entity:add` fügt unter `src/` Entitätsdefinitionsdateien für benutzerdefinierte Objekte, Funktionen, Frontend-Komponenten oder Rollen hinzu.
## Authentifizierung
@@ -186,14 +212,16 @@ Das twenty-sdk stellt typisierte Bausteine und Hilfsfunktionen bereit, die Sie i
Das SDK stellt Hilfsfunktionen bereit, um die Entitäten Ihrer App zu definieren. Wie in [Entitätserkennung](#entity-detection) beschrieben, müssen Sie `export default define<Entity>({...})` verwenden, damit Ihre Entitäten erkannt werden:
| Funktion | Zweck |
| ------------------------ | -------------------------------------------------------------- |
| `defineApplication()` | Anwendungsmetadaten konfigurieren (erforderlich, eine pro App) |
| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren |
| `defineLogicFunction()` | Logikfunktionen mit Handlern definieren |
| `defineFrontComponent()` | Frontend-Komponenten für benutzerdefinierte UI definieren |
| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren |
| `defineField()` | Bestehende Objekte mit zusätzlichen Feldern erweitern |
| Funktion | Zweck |
| ---------------------------- | -------------------------------------------------------------- |
| `defineApplication()` | Anwendungsmetadaten konfigurieren (erforderlich, eine pro App) |
| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren |
| `defineLogicFunction()` | Logikfunktionen mit Handlern definieren |
| `defineFrontComponent()` | Frontend-Komponenten für benutzerdefinierte UI definieren |
| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren |
| `defineField()` | Bestehende Objekte mit zusätzlichen Feldern erweitern |
| `defineView()` | Gespeicherte Views für Objekte definieren |
| `defineNavigationMenuItem()` | Seitenleisten-Navigationslinks definieren |
Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit.
@@ -293,6 +321,7 @@ Jede App hat eine einzelne Datei `application-config.ts`, die Folgendes beschrei
* **Was die App ist**: Bezeichner, Anzeigename und Beschreibung.
* **Wie ihre Funktionen ausgeführt werden**: welche Rolle sie für Berechtigungen verwenden.
* **(Optional) Variablen**: SchlüsselWert-Paare, die Ihren Funktionen als Umgebungsvariablen zur Verfügung gestellt werden.
* **(Optional) Post-Installationsfunktion**: eine Logikfunktion, die nach der Installation der App ausgeführt wird.
Verwenden Sie `defineApplication()`, um Ihre Anwendungskonfiguration zu definieren:
@@ -300,6 +329,7 @@ Verwenden Sie `defineApplication()`, um Ihre Anwendungskonfiguration zu definier
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ Notizen:
* `universalIdentifier`-Felder sind deterministische IDs, die Sie besitzen; generieren Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil.
* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen (zum Beispiel ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar).
* `defaultRoleUniversalIdentifier` muss mit der Rollendatei übereinstimmen (siehe unten).
* `postInstallLogicFunctionUniversalIdentifier` (optional) verweist auf eine Logikfunktion, die nach der Installation der App automatisch ausgeführt wird. Siehe [Post-Installationsfunktionen](#post-install-functions).
#### Rollen und Berechtigungen
@@ -461,6 +493,55 @@ Notizen:
* Das Array `triggers` ist optional. Funktionen ohne Trigger können als von anderen Funktionen aufgerufene Utility-Funktionen verwendet werden.
* Sie können mehrere Trigger-Typen in einer Funktion kombinieren.
### Post-Installationsfunktionen
Eine Post-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, nachdem Ihre App in einem Arbeitsbereich installiert wurde. Dies ist nützlich für einmalige Einrichtungsvorgänge wie das Befüllen mit Standarddaten, das Erstellen erster Datensätze oder das Konfigurieren von Arbeitsbereichseinstellungen.
Wenn du mit `create-twenty-app` eine neue App erstellst, wird für dich eine Post-Installationsfunktion unter `src/logic-functions/post-install.ts` erzeugt:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
Die Funktion wird in deine App eingebunden, indem ihr universeller Bezeichner in `application-config.ts` referenziert wird:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
Du kannst die Post-Installationsfunktion auch jederzeit manuell über die CLI ausführen:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Hauptpunkte:
* Post-Installationsfunktionen sind Standard-Logikfunktionen — sie verwenden `defineLogicFunction()` wie jede andere Funktion.
* Das Feld `postInstallLogicFunctionUniversalIdentifier` in `defineApplication()` ist optional. Wenn es weggelassen wird, wird nach der Installation keine Funktion ausgeführt.
* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen.
* Post-Installationsfunktionen benötigen keine Trigger — sie werden von der Plattform während der Installation oder manuell über `function:execute --postInstall` aufgerufen.
### Routen-Trigger-Payload
<Warning>
@@ -662,7 +743,7 @@ Sie können neue Frontend-Komponenten auf zwei Arten erstellen:
### Generierter typisierter Client
Führen Sie `yarn twenty app:generate` aus, um einen lokalen typisierten Client in `generated/` basierend auf Ihrem Arbeitsbereichs-Schema zu erstellen. Verwenden Sie ihn in Ihren Funktionen:
Der typisierte Client wird von `yarn twenty app:dev` automatisch generiert und basierend auf Ihrem Arbeitsbereichs-Schema in `node_modules/twenty-sdk/generated` gespeichert. Verwenden Sie ihn in Ihren Funktionen:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
Der Client wird durch `yarn twenty app:generate` erneut generiert. Führen Sie ihn nach Änderungen an Ihren Objekten oder beim Onboarding in einen neuen Workspace erneut aus.
Der Client wird von `yarn twenty app:dev` automatisch neu generiert, sobald sich Ihre Objekte oder Felder ändern.
#### Laufzeit-Anmeldedaten in Logikfunktionen
@@ -708,13 +789,13 @@ Fügen Sie dann ein `twenty`-Skript hinzu:
}
```
Jetzt können Sie alle Befehle über `yarn twenty <command>` ausführen, z. B. `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help` usw.
Jetzt können Sie alle Befehle über `yarn twenty <command>` ausführen, z. B. `yarn twenty app:dev`, `yarn twenty help` usw.
## Fehlerbehebung
* Authentifizierungsfehler: Führen Sie `yarn twenty auth:login` aus und stellen Sie sicher, dass Ihr API-Schlüssel die erforderlichen Berechtigungen hat.
* Verbindung zum Server nicht möglich: Überprüfen Sie die API-URL und dass der Twenty-Server erreichbar ist.
* Typen oder Client fehlen/veraltet: Führen Sie `yarn twenty app:generate` aus.
* Typen oder Client fehlen/veraltet: Starten Sie `yarn twenty app:dev` neu — der typisierte Client wird automatisch generiert.
* Dev-Modus synchronisiert nicht: Stellen Sie sicher, dass `yarn twenty app:dev` läuft und dass Änderungen von Ihrer Umgebung nicht ignoriert werden.
Discord-Hilfekanal: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -52,9 +52,6 @@ Desde aquí usted puede:
# Añade una nueva entidad a tu aplicación (guiado)
yarn entity:add
# Genera un cliente tipado de Twenty y tipos de entidad del espacio de trabajo
yarn app:generate
# Supervisa los registros de funciones de tu aplicación
yarn function:logs
@@ -157,7 +154,7 @@ src/
A grandes rasgos:
* **package.json**: Declara el nombre de la aplicación, la versión, los entornos (Node 24+, Yarn 4) y agrega `twenty-sdk` además de scripts como `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` y `auth:login` que delegan en la CLI local `twenty`.
* **package.json**: Declara el nombre de la aplicación, la versión, los entornos (Node 24+, Yarn 4) y agrega `twenty-sdk` además de scripts como `app:dev`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` y `auth:login` que delegan en la CLI local `twenty`.
* **.gitignore**: Ignora artefactos comunes como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, carpetas de cobertura, archivos de registro y archivos `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloquean y configuran la cadena de herramientas Yarn 4 utilizada por el proyecto.
* **.nvmrc**: Fija la versión de Node.js esperada por el proyecto.
@@ -173,7 +170,7 @@ A grandes rasgos:
Comandos posteriores añadirán más archivos y carpetas:
* `yarn app:generate` creará una carpeta `generated/` (cliente tipado de Twenty + tipos del espacio de trabajo).
* `yarn app:dev` genera automáticamente el cliente Twenty tipado en `node_modules/twenty-sdk/generated`.
* `yarn entity:add` añadirá archivos de definición de entidades en `src/` para tus objetos, funciones, componentes de interfaz o roles personalizados.
## Autenticación
@@ -585,7 +582,7 @@ Puedes crear funciones nuevas de dos maneras:
### Cliente tipado generado
Ejecuta yarn app:generate para crear un cliente tipado local en generated/ basado en el esquema de tu espacio de trabajo. Úsalo en tus funciones:
`yarn app:dev` genera automáticamente el cliente Twenty tipado en `node_modules/twenty-sdk/generated`. Úsalo en tus funciones:
```typescript
import Twenty from '~/generated';
@@ -594,7 +591,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
El cliente se vuelve a generar con `yarn app:generate`. Vuelve a ejecutarlo después de cambiar tus objetos o al incorporarte a un nuevo espacio de trabajo.
El cliente se regenera automáticamente durante la ejecución de `app:dev`. Reinicia `app:dev` después de cambiar tus objetos o al incorporarte a un nuevo espacio de trabajo.
#### Credenciales en tiempo de ejecución en funciones de lógica
@@ -632,7 +629,6 @@ Luego agrega scripts como estos:
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
@@ -642,13 +638,13 @@ Luego agrega scripts como estos:
}
```
Ahora puedes ejecutar los mismos comandos mediante Yarn, p. ej., `yarn app:dev`, `yarn app:generate`, etc.
Ahora puedes ejecutar los mismos comandos mediante Yarn, p. ej., `yarn app:dev`, etc.
## Solución de problemas
* Errores de autenticación: ejecuta `yarn auth:login` y asegúrate de que tu clave de API tenga los permisos necesarios.
* No se puede conectar al servidor: verifica la URL de la API y que el servidor de Twenty sea accesible.
* Tipos o cliente faltantes/obsoletos: ejecuta `yarn app:generate`.
* Tipos o cliente faltantes/obsoletos: reinicia `yarn app:dev`.
* El modo de desarrollo no sincroniza: asegúrate de que `yarn app:dev` esté ejecutándose y de que los cambios no sean ignorados por tu entorno.
Canal de ayuda en Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -52,9 +52,6 @@ yarn app:dev
# Ajouter une nouvelle entité à votre application (assisté)
yarn entity:add
# Générer un client Twenty typé et les types d'entité de l'espace de travail
yarn app:generate
# Surveiller les journaux des fonctions de votre application
yarn function:logs
@@ -157,7 +154,7 @@ src/
Dans les grandes lignes :
* **package.json** : Déclare le nom de lapplication, la version, les moteurs (Node 24+, Yarn 4), et ajoute `twenty-sdk` ainsi que des scripts comme `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` et `auth:login` qui délèguent à la CLI locale `twenty`.
* **package.json** : Déclare le nom de lapplication, la version, les moteurs (Node 24+, Yarn 4), et ajoute `twenty-sdk` ainsi que des scripts comme `app:dev`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` et `auth:login` qui délèguent à la CLI locale `twenty`.
* **.gitignore** : Ignore les artefacts courants tels que `node_modules`, `.yarn`, `generated/` (client typé), `dist/`, `build/`, les dossiers de couverture, les fichiers journaux et les fichiers `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/** : Verrouillent et configurent la chaîne doutils Yarn 4 utilisée par le projet.
* **.nvmrc** : Fige la version de Node.js attendue par le projet.
@@ -173,7 +170,7 @@ Dans les grandes lignes :
Des commandes ultérieures ajouteront dautres fichiers et dossiers :
* `yarn app:generate` créera un dossier `generated/` (client Twenty typé + types de lespace de travail).
* `yarn app:dev` génère automatiquement le client Twenty typé dans `node_modules/twenty-sdk/generated`.
* `yarn entity:add` ajoutera des fichiers de définition dentité sous `src/` pour vos objets, fonctions, composants front-end ou rôles personnalisés.
## Authentification
@@ -585,7 +582,7 @@ Vous pouvez créer de nouvelles fonctions de deux façons :
### Client typé généré
Exécutez yarn app:generate pour créer un client typé local dans generated/ basé sur le schéma de votre espace de travail. Utilisez-le dans vos fonctions :
`yarn app:dev` génère automatiquement le client Twenty typé dans `node_modules/twenty-sdk/generated`. Utilisez-le dans vos fonctions :
```typescript
import Twenty from '~/generated';
@@ -594,7 +591,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
Le client est régénéré par `yarn app:generate`. Relancez après avoir modifié vos objets ou lors de lintégration à un nouvel espace de travail.
Le client est régénéré automatiquement pendant l'exécution de `app:dev`. Redémarrez `app:dev` après avoir modifié vos objets ou lors de lintégration à un nouvel espace de travail.
#### Identifiants dexécution dans les fonctions logiques
@@ -632,7 +629,6 @@ Ajoutez ensuite des scripts comme ceux-ci :
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
@@ -642,13 +638,13 @@ Ajoutez ensuite des scripts comme ceux-ci :
}
```
Vous pouvez désormais exécuter les mêmes commandes via Yarn, par exemple `yarn app:dev`, `yarn app:generate`, etc.
Vous pouvez désormais exécuter les mêmes commandes via Yarn, par exemple `yarn app:dev`, etc.
## Résolution des problèmes
* Erreurs dauthentification : exécutez `yarn auth:login` et assurez-vous que votre clé API dispose des autorisations requises.
* Impossible de se connecter au serveur : vérifiez lURL de lAPI et que le serveur Twenty est accessible.
* Types ou client manquants/obsolètes : exécutez `yarn app:generate`.
* Types ou client manquants/obsolètes : redémarrez `yarn app:dev`.
* Le mode dev ne se synchronise pas : assurez-vous que `yarn app:dev` est en cours dexécution et que les modifications ne sont pas ignorées par votre environnement.
Canal daide Discord : https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,35 +27,48 @@ Le app ti consentono di creare e gestire le personalizzazioni di Twenty **come c
Crea una nuova app utilizzando lo scaffolder ufficiale, quindi autenticati e inizia a sviluppare:
```bash filename="Terminal"
# Crea lo scaffold di una nuova app
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# Se non usi yarn@4
# If you don't use yarn@4
corepack enable
yarn install
# Autenticati usando la tua API key (ti verrà richiesto)
# Authenticate using your API key (you'll be prompted)
yarn twenty auth:login
# Avvia la modalità di sviluppo: sincronizza automaticamente le modifiche locali con il tuo workspace
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
Da qui puoi:
```bash filename="Terminal"
# Aggiungi una nuova entità alla tua applicazione (guidata)
yarn twenty entity:add
# Genera un client Twenty tipizzato e i tipi di entità dell'area di lavoro
yarn twenty app:generate
# Monitora i log delle funzioni della tua applicazione
yarn twenty function:logs
# Esegui una funzione per nome
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Esegui la funzione post-installazione
yarn twenty function:execute --postInstall
# Disinstalla l'applicazione dallo spazio di lavoro corrente
yarn twenty app:uninstall
@@ -73,9 +86,9 @@ Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder:
* Copia un'applicazione base minimale in `my-twenty-app/`
* Aggiunge una dipendenza locale `twenty-sdk` e la configurazione di Yarn 4
* Crea file di configurazione e script collegati alla CLI `twenty`
* Genera una configurazione applicativa predefinita e un ruolo funzione predefinito
* Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
Un'app appena generata dallo scaffolder si presenta così:
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -89,17 +102,28 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Cartella delle risorse pubbliche (immagini, font, ecc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Obbligatorio - configurazione principale dell'applicazione
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Ruolo predefinito per le funzioni logiche
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
── hello-world.ts # Funzione logica di esempio
└── front-components/
└── hello-world.tsx # Componente front-end di esempio
── hello-world.ts # Example logic function
└── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
└── navigation-menu-items/
└── example-navigation-menu-item.ts # Example sidebar navigation link
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
A livello generale:
* **package.json**: Dichiara il nome dell'app, la versione, i motori (Node 24+, Yarn 4) e aggiunge `twenty-sdk` più uno script `twenty` che delega alla CLI locale `twenty`. Esegui `yarn twenty help` per elencare tutti i comandi disponibili.
@@ -115,13 +139,15 @@ A livello generale:
L'SDK rileva le entità analizzando i tuoi file TypeScript alla ricerca di chiamate **`export default define<Entity>({...})`**. Ogni tipo di entità ha una corrispondente funzione helper esportata da `twenty-sdk`:
| Funzione helper | Tipo di entità |
| ------------------------ | ----------------------------------------- |
| `defineObject()` | Definizioni di oggetti personalizzati |
| `defineLogicFunction()` | Definizioni di funzioni logiche |
| `defineFrontComponent()` | Definizioni dei componenti front-end |
| `defineRole()` | Definizioni di ruoli |
| `defineField()` | Estensioni di campo per oggetti esistenti |
| Funzione helper | Tipo di entità |
| ---------------------------- | ----------------------------------------- |
| `defineObject()` | Definizioni di oggetti personalizzati |
| `defineLogicFunction()` | Definizioni di funzioni logiche |
| `defineFrontComponent()` | Definizioni dei componenti front-end |
| `defineRole()` | Definizioni di ruoli |
| `defineField()` | Estensioni di campo per oggetti esistenti |
| `defineView()` | Saved view definitions |
| `defineNavigationMenuItem()` | Navigation menu item definitions |
<Note>
**La denominazione dei file è flessibile.** Il rilevamento delle entità è basato sull'AST — l'SDK esegue la scansione dei file sorgente alla ricerca del pattern `export default define<Entity>({...})`. Puoi organizzare file e cartelle come preferisci. Raggruppare per tipo di entità (ad es., `logic-functions/`, `roles/`) è solo una convenzione per l'organizzazione del codice, non un requisito.
@@ -142,7 +168,7 @@ export default defineObject({
Comandi successivi aggiungeranno altri file e cartelle:
* `yarn twenty app:generate` creerà una cartella `generated/` (client Twenty tipizzato + tipi dello spazio di lavoro).
* `yarn twenty app:dev` genererà automaticamente un client API tipizzato in `node_modules/twenty-sdk/generated` (client Twenty tipizzato + tipi dell'area di lavoro).
* `yarn twenty entity:add` aggiungerà file di definizione delle entità sotto `src/` per i tuoi oggetti, funzioni, componenti front-end o ruoli personalizzati.
## Autenticazione
@@ -186,14 +212,16 @@ Il pacchetto twenty-sdk fornisce blocchi tipizzati e funzioni helper da usare ne
L'SDK fornisce funzioni helper per definire le entità della tua app. Come descritto in [Rilevamento delle entità](#entity-detection), devi usare `export default define<Entity>({...})` affinché le tue entità vengano rilevate:
| Funzione | Scopo |
| ------------------------ | ----------------------------------------------------------------------- |
| `defineApplication()` | Configura i metadati dell'applicazione (obbligatorio, uno per app) |
| `defineObject()` | Definisci oggetti personalizzati con campi |
| `defineLogicFunction()` | Definisci funzioni logiche con handler |
| `defineFrontComponent()` | Definisci componenti front-end per un'interfaccia utente personalizzata |
| `defineRole()` | Configura i permessi dei ruoli e l'accesso agli oggetti |
| `defineField()` | Estendi gli oggetti esistenti con campi aggiuntivi |
| Funzione | Scopo |
| ---------------------------- | ----------------------------------------------------------------------- |
| `defineApplication()` | Configura i metadati dell'applicazione (obbligatorio, uno per app) |
| `defineObject()` | Definisci oggetti personalizzati con campi |
| `defineLogicFunction()` | Definisci funzioni logiche con handler |
| `defineFrontComponent()` | Definisci componenti front-end per un'interfaccia utente personalizzata |
| `defineRole()` | Configura i permessi dei ruoli e l'accesso agli oggetti |
| `defineField()` | Estendi gli oggetti esistenti con campi aggiuntivi |
| `defineView()` | Define saved views for objects |
| `defineNavigationMenuItem()` | Define sidebar navigation links |
Queste funzioni convalidano la configurazione in fase di build e offrono il completamento automatico nell'IDE e la sicurezza dei tipi.
@@ -293,6 +321,7 @@ Ogni app ha un singolo file `application-config.ts` che descrive:
* **Identità dell'app**: identificatori, nome visualizzato e descrizione.
* **Come vengono eseguite le sue funzioni**: quale ruolo usano per i permessi.
* **Variabili (opzionali)**: coppie chiavevalore esposte alle funzioni come variabili d'ambiente.
* **(Opzionale) funzione post-installazione**: una funzione logica che viene eseguita dopo l'installazione dell'app.
Usa `defineApplication()` per definire la configurazione della tua applicazione:
@@ -300,6 +329,7 @@ Usa `defineApplication()` per definire la configurazione della tua applicazione:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ Note:
* I campi `universalIdentifier` sono ID deterministici sotto il tuo controllo; generali una volta e mantienili stabili tra le sincronizzazioni.
* `applicationVariables` diventano variabili d'ambiente per le tue funzioni (ad esempio, `DEFAULT_RECIPIENT_NAME` è disponibile come `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` deve corrispondere al file del ruolo (vedi sotto).
* `postInstallLogicFunctionUniversalIdentifier` (opzionale) fa riferimento a una funzione logica che viene eseguita automaticamente dopo l'installazione dell'app. Vedi [Funzioni post-installazione](#post-install-functions).
#### Ruoli e permessi
@@ -461,6 +493,55 @@ Note:
* L'array `triggers` è facoltativo. Le funzioni senza trigger possono essere utilizzate come funzioni di utilità richiamate da altre funzioni.
* Puoi combinare più tipi di trigger in un'unica funzione.
### Funzioni post-installazione
Una funzione post-installazione è una funzione logica che viene eseguita automaticamente dopo che la tua app è stata installata in uno spazio di lavoro. Questo è utile per attività di configurazione una tantum come il popolamento di dati predefiniti, la creazione di record iniziali o la configurazione delle impostazioni dello spazio di lavoro.
When you scaffold a new app with `create-twenty-app`, a post-install function is generated for you at `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
The function is wired into your app by referencing its universal identifier in `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
You can also manually execute the post-install function at any time using the CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Punti chiave:
* Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function.
* The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation.
* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
* Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`.
### Payload del trigger di route
<Warning>
@@ -662,7 +743,7 @@ Puoi creare nuovi componenti front-end in due modi:
### Client tipizzato generato
Esegui `yarn twenty app:generate` per creare un client tipizzato locale in `generated/` basato sullo schema del tuo spazio di lavoro. Usalo nelle tue funzioni:
Il client tipizzato è generato automaticamente da `yarn twenty app:dev` e salvato in `node_modules/twenty-sdk/generated` in base allo schema della tua area di lavoro. Usalo nelle tue funzioni:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
Il client viene rigenerato da `yarn twenty app:generate`. Eseguilo nuovamente dopo aver modificato i tuoi oggetti oppure quando effettui l'onboarding su un nuovo spazio di lavoro.
Il client viene rigenerato automaticamente da `yarn twenty app:dev` ogni volta che i tuoi oggetti o campi cambiano.
#### Credenziali di runtime nelle funzioni logiche
@@ -708,13 +789,13 @@ Quindi aggiungi uno script `twenty`:
}
```
Ora puoi eseguire tutti i comandi tramite `yarn twenty <command>`, ad es. `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help`, ecc.
Ora puoi eseguire tutti i comandi tramite `yarn twenty <command>`, ad es. `yarn twenty app:dev`, `yarn twenty help`, ecc.
## Risoluzione dei problemi
* Errori di autenticazione: esegui `yarn twenty auth:login` e assicurati che la tua chiave API abbia i permessi richiesti.
* Impossibile connettersi al server: verifica l'URL dell'API e che il server Twenty sia raggiungibile.
* Tipi o client mancanti/obsoleti: esegui `yarn twenty app:generate`.
* Types or client missing/outdated: restart `yarn twenty app:dev` — it auto-generates the typed client.
* Modalità di sviluppo non sincronizzata: assicurati che `yarn twenty app:dev` sia in esecuzione e che le modifiche non vengano ignorate dal tuo ambiente.
Canale di supporto su Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -52,9 +52,6 @@ yarn app:dev
# アプリケーションに新しいエンティティを追加(ガイド付き)
yarn entity:add
# 型付きの Twenty クライアントとワークスペースのエンティティ型を生成
yarn app:generate
# アプリケーションの関数のログを監視
yarn function:logs
@@ -156,7 +153,7 @@ src/
概要:
* **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall`, and `auth:login` that delegate to the local `twenty` CLI.
* **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `app:dev`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall`, and `auth:login` that delegate to the local `twenty` CLI.
* **.gitignore**: `node_modules`、`.yarn`、`generated/`(型付きクライアント)、`dist/`、`build/`、カバレッジ用フォルダー、ログファイル、`.env*` ファイルなどの一般的な生成物を無視します。
* **yarn.lock**、**.yarnrc.yml**、**.yarn/**: プロジェクトで使用する Yarn 4 ツールチェーンをロックおよび構成します。
* **.nvmrc**: プロジェクトで想定する Node.js バージョンを固定します。
@@ -171,7 +168,7 @@ src/
後続のコマンドにより、さらにファイルやフォルダーが追加されます:
* `yarn app:generate` は `generated/` フォルダー(型付き Twenty クライアント + ワークスペースの型)を作成します。
* `yarn app:dev` は `node_modules/twenty-sdk/generated` 型付き Twenty クライアントを自動生成します。
* `yarn entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, or roles.
## 認証
@@ -583,7 +580,7 @@ const handler = async (event: RoutePayload) => {
### 生成された型付きクライアント
ワークスペースのスキーマに基づき、generated/ローカルの型付きクライアントを作成するには yarn app:generate を実行します。 関数内で使用します:
`yarn app:dev` は `node_modules/twenty-sdk/generated`型付き Twenty クライアントを自動生成します。 関数内で使用します:
```typescript
import Twenty from '~/generated';
@@ -592,7 +589,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
このクライアントは `yarn app:generate` によって再生成されます。 Re-run after changing your objects or when onboarding to a new workspace.
このクライアントは `app:dev` 実行中に自動的に再生成されます。 オブジェクトを変更した後、または新しいワークスペースにオンボーディングする際は、`app:dev` を再起動してください。
#### Runtime credentials in logic functions
@@ -630,7 +627,6 @@ yarn add -D twenty-sdk
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
@@ -640,13 +636,13 @@ yarn add -D twenty-sdk
}
```
Now you can run the same commands via Yarn, e.g. `yarn app:dev`, `yarn app:generate`, etc.
Now you can run the same commands via Yarn, e.g. `yarn app:dev`, etc.
## トラブルシューティング
* 認証エラー: `yarn auth:login` を実行し、API キーに必要な権限があることを確認してください。
* サーバーに接続できません: API URL と、Twenty サーバーに到達可能であることを確認してください。
* Types or client missing/outdated: run `yarn app:generate`.
* Types or client missing/outdated: restart `yarn app:dev`.
* 開発モードで同期されない: `yarn app:dev` が実行中であり、環境によって変更が無視されていないことを確認してください。
Discord ヘルプチャンネル: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -52,9 +52,6 @@ yarn app:dev
# Add a new entity to your application (guided)
yarn entity:add
# Generate a typed Twenty client and workspace entity types
yarn app:generate
# Watch your application's function logs
yarn function:logs
@@ -157,7 +154,7 @@ src/
개요:
* **package.json**: 앱 이름, 버전, 엔진(Node 24+, Yarn 4)을 선언하고, `twenty-sdk`와 함께 `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall`, `auth:login` 같은 스크립트를 추가합니다. 이 스크립트들은 로컬 `twenty` CLI에 위임됩니다.
* **package.json**: 앱 이름, 버전, 엔진(Node 24+, Yarn 4)을 선언하고, `twenty-sdk`와 함께 `app:dev`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall`, `auth:login` 같은 스크립트를 추가합니다. 이 스크립트들은 로컬 `twenty` CLI에 위임됩니다.
* **.gitignore**: `node_modules`, `.yarn`, `generated/`(타입드 클라이언트), `dist/`, `build/`, 커버리지 폴더, 로그 파일, `.env*` 파일 등의 일반 산출물을 무시합니다.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: 프로젝트에서 사용하는 Yarn 4 툴체인을 고정하고 구성합니다.
* **.nvmrc**: 프로젝트에서 예상하는 Node.js 버전을 고정합니다.
@@ -173,7 +170,7 @@ src/
이후 명령을 실행하면 더 많은 파일과 폴더가 추가됩니다:
* `yarn app:generate`는 `generated/` 폴더를 생성합니다(타입드 Twenty 클라이언트 + 워크스페이스 타입).
* `yarn app:dev`는 `node_modules/twenty-sdk/generated`에 타입드 Twenty 클라이언트를 자동으로 생성합니다.
* `yarn entity:add`는 사용자 정의 객체, 함수, 프런트 컴포넌트 또는 역할에 대한 엔티티 정의 파일을 `src/` 아래에 추가합니다.
## 인증
@@ -585,7 +582,7 @@ const handler = async (event: RoutePayload) => {
### 생성된 타입드 클라이언트
워크스페이스 스키마를 기반으로 generated/ 로컬 타입드 클라이언트를 생성하려면 yarn app:generate를 실행하세요. 함수에서 사용하세요:
`yarn app:dev`는 `node_modules/twenty-sdk/generated`에 타입드 Twenty 클라이언트를 자동으로 생성합니다. 함수에서 사용하세요:
```typescript
import Twenty from '~/generated';
@@ -594,7 +591,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
클라이언트는 `yarn app:generate`로 다시 생성됩니다. 객체를 변경한 후 또는 새 워크스페이스에 온보딩할 때 다시 실행하세요.
클라이언트는 `app:dev` 실행 중 자동으로 다시 생성됩니다. 객체를 변경한 후 또는 새 워크스페이스에 온보딩할 때 `app:dev`를 다시 시작하세요.
#### 로직 함수의 런타임 자격 증명
@@ -632,7 +629,6 @@ yarn add -D twenty-sdk
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
@@ -642,13 +638,13 @@ yarn add -D twenty-sdk
}
```
이제 Yarn을 통해 동일한 명령을 실행할 수 있습니다. 예: `yarn app:dev`, `yarn app:generate` 등.
이제 Yarn을 통해 동일한 명령을 실행할 수 있습니다. 예: `yarn app:dev` 등.
## 문제 해결
* 인증 오류: `yarn auth:login`를 실행하고 API 키에 필요한 권한이 있는지 확인하세요.
* 서버에 연결할 수 없음: API URL과 Twenty 서버에 접근 가능한지 확인하세요.
* 타입 또는 클라이언트가 없거나 오래된 경우: `yarn app:generate`를 실행하세요.
* 타입 또는 클라이언트가 없거나 오래된 경우: `yarn app:dev`를 다시 시작하세요.
* 개발 모드가 동기화되지 않음: `yarn app:dev`가 실행 중인지, 환경에서 변경 사항을 무시하지 않는지 확인하세요.
Discord 도움말 채널: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,7 +27,7 @@ Os aplicativos permitem criar e gerenciar personalizações do Twenty **como có
Crie um novo aplicativo usando o gerador oficial, depois autentique-se e comece a desenvolver:
```bash filename="Terminal"
# Scaffold a new app
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
@@ -42,20 +42,33 @@ yarn twenty auth:login
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
A partir daqui você pode:
```bash filename="Terminal"
# Adicionar uma nova entidade à sua aplicação (assistido)
yarn twenty entity:add
# Gerar um cliente Twenty tipado e tipos de entidades do espaço de trabalho
yarn twenty app:generate
# Acompanhar os logs das funções da sua aplicação
yarn twenty function:logs
# Executar uma função pelo nome
yarn twenty function:execute -n my-function -p '{"name": "test"}'
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'
# Executar a função de pós-instalação
yarn twenty function:execute --postInstall
# Desinstalar a aplicação do espaço de trabalho atual
yarn twenty app:uninstall
@@ -73,9 +86,9 @@ Ao executar `npx create-twenty-app@latest my-twenty-app`, o gerador:
* Copia um aplicativo base mínimo para `my-twenty-app/`
* Adiciona uma dependência local `twenty-sdk` e a configuração do Yarn 4
* Cria arquivos de configuração e scripts conectados à CLI `twenty`
* Gera uma configuração de aplicativo padrão e um papel padrão para as funções
* Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
Um aplicativo recém-criado pelo scaffold fica assim:
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -89,17 +102,28 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Pasta de recursos públicos (imagens, fontes, etc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Obrigatório - configuração principal da aplicação
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Papel padrão para funções de lógica
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
── hello-world.ts # Exemplo de função de lógica
└── front-components/
└── hello-world.tsx # Exemplo de componente de front-end
── hello-world.ts # Example logic function
└── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
└── navigation-menu-items/
└── example-navigation-menu-item.ts # Example sidebar navigation link
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
Em alto nível:
* **package.json**: Declara o nome do app, versão, engines (Node 24+, Yarn 4), e adiciona `twenty-sdk` além de um script `twenty` que delega para a CLI `twenty` local. Execute `yarn twenty help` para listar todos os comandos disponíveis.
@@ -115,13 +139,15 @@ Em alto nível:
O SDK detecta entidades analisando seus arquivos TypeScript em busca de chamadas **`export default define<Entity>({...})`**. Cada tipo de entidade tem uma função utilitária correspondente exportada de `twenty-sdk`:
| Função utilitária | Tipo de entidade |
| ------------------------ | ------------------------------------------- |
| `defineObject()` | Definições de objetos personalizados |
| `defineLogicFunction()` | Definições de funções de lógica |
| `defineFrontComponent()` | Definições de componentes de front-end |
| `defineRole()` | Definições de papéis |
| `defineField()` | Extensões de campos para objetos existentes |
| Função utilitária | Tipo de entidade |
| ---------------------------- | ------------------------------------------- |
| `defineObject()` | Definições de objetos personalizados |
| `defineLogicFunction()` | Definições de funções de lógica |
| `defineFrontComponent()` | Definições de componentes de front-end |
| `defineRole()` | Definições de papéis |
| `defineField()` | Extensões de campos para objetos existentes |
| `defineView()` | Saved view definitions |
| `defineNavigationMenuItem()` | Navigation menu item definitions |
<Note>
**A nomeação de arquivos é flexível.** A detecção de entidades é baseada em AST — o SDK varre seus arquivos fonte em busca do padrão `export default define<Entity>({...})`. Você pode organizar seus arquivos e pastas como quiser. Agrupar por tipo de entidade (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção para organização do código, não um requisito.
@@ -142,7 +168,7 @@ export default defineObject({
Comandos posteriores adicionarão mais arquivos e pastas:
* `yarn twenty app:generate` criará uma pasta `generated/` (cliente tipado do Twenty + tipos do espaço de trabalho).
* `yarn twenty app:dev` vai gerar automaticamente um cliente de API tipado em `node_modules/twenty-sdk/generated` (cliente Twenty tipado + tipos do espaço de trabalho).
* `yarn twenty entity:add` adicionará arquivos de definição de entidade em `src/` para seus objetos, funções, componentes de front-end ou papéis personalizados.
## Autenticação
@@ -186,14 +212,16 @@ O twenty-sdk fornece blocos de construção tipados e funções utilitárias que
O SDK fornece funções utilitárias para definir as entidades do seu app. Conforme descrito em [Detecção de entidades](#entity-detection), você deve usar `export default define<Entity>({...})` para que suas entidades sejam detectadas:
| Função | Finalidade |
| ------------------------ | ------------------------------------------------------------ |
| `defineApplication()` | Configurar metadados do aplicativo (obrigatório, um por app) |
| `defineObject()` | Define objetos personalizados com campos |
| `defineLogicFunction()` | Defina funções de lógica com handlers |
| `defineFrontComponent()` | Definir componentes de front-end para UI personalizada |
| `defineRole()` | Configura permissões de papéis e acesso a objetos |
| `defineField()` | Estender objetos existentes com campos adicionais |
| Função | Finalidade |
| ---------------------------- | ------------------------------------------------------------ |
| `defineApplication()` | Configurar metadados do aplicativo (obrigatório, um por app) |
| `defineObject()` | Define objetos personalizados com campos |
| `defineLogicFunction()` | Defina funções de lógica com handlers |
| `defineFrontComponent()` | Definir componentes de front-end para UI personalizada |
| `defineRole()` | Configura permissões de papéis e acesso a objetos |
| `defineField()` | Estender objetos existentes com campos adicionais |
| `defineView()` | Define saved views for objects |
| `defineNavigationMenuItem()` | Define sidebar navigation links |
Essas funções validam sua configuração em tempo de compilação e oferecem autocompletar na IDE e segurança de tipos.
@@ -293,6 +321,7 @@ Todo aplicativo tem um único arquivo `application-config.ts` que descreve:
* **O que é o aplicativo**: identificadores, nome de exibição e descrição.
* **Como suas funções são executadas**: qual papel usam para permissões.
* **Variáveis (opcional)**: pares chavevalor expostos às suas funções como variáveis de ambiente.
* **(Opcional) função de pós-instalação**: uma função de lógica que é executada após a instalação da aplicação.
Use `defineApplication()` to define your application configuration:
@@ -300,6 +329,7 @@ Use `defineApplication()` to define your application configuration:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ Notas:
* `universalIdentifier` são IDs determinísticos que você controla; gere-os uma vez e mantenha-os estáveis entre sincronizações.
* `applicationVariables` tornam-se variáveis de ambiente para suas funções (por exemplo, `DEFAULT_RECIPIENT_NAME` fica disponível como `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` deve corresponder ao arquivo do papel (veja abaixo).
* `postInstallLogicFunctionUniversalIdentifier` (opcional) aponta para uma função de lógica que é executada automaticamente após a instalação da aplicação. Consulte [Funções de pós-instalação](#post-install-functions).
#### Papéis e permissões
@@ -461,6 +493,55 @@ Notas:
* O array `triggers` é opcional. Funções sem gatilhos podem ser usadas como funções utilitárias chamadas por outras funções.
* Você pode misturar vários tipos de gatilho em uma única função.
### Funções de pós-instalação
Uma função de pós-instalação é uma função de lógica que é executada automaticamente após a sua aplicação ser instalada em um espaço de trabalho. Isso é útil para tarefas de configuração únicas, como preencher dados padrão, criar registros iniciais ou configurar as configurações do espaço de trabalho.
When you scaffold a new app with `create-twenty-app`, a post-install function is generated for you at `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
The function is wired into your app by referencing its universal identifier in `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
You can also manually execute the post-install function at any time using the CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Pontos-chave:
* Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function.
* The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation.
* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
* Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`.
### Payload de gatilho de rota
<Warning>
@@ -662,7 +743,7 @@ Você pode criar novos componentes de front-end de duas formas:
### Cliente tipado gerado
Execute `yarn twenty app:generate` para criar um cliente tipado local em `generated/` com base no esquema do seu espaço de trabalho. Use-o em suas funções:
O cliente tipado é gerado automaticamente pelo `yarn twenty app:dev` e armazenado em `node_modules/twenty-sdk/generated` com base no esquema do seu espaço de trabalho. Use-o em suas funções:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
O cliente é regenerado pelo `yarn twenty app:generate`. Execute novamente após alterar seus objetos ou ao ingressar em um novo workspace.
O cliente é regenerado automaticamente pelo `yarn twenty app:dev` sempre que seus objetos ou campos forem alterados.
#### Credenciais em tempo de execução em funções de lógica
@@ -708,13 +789,13 @@ Em seguida, adicione um script `twenty`:
}
```
Agora você pode executar todos os comandos via `yarn twenty <command>`, por exemplo, `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help`, etc.
Agora você pode executar todos os comandos via `yarn twenty <command>`, por exemplo, `yarn twenty app:dev`, `yarn twenty help`, etc.
## Resolução de Problemas
* Erros de autenticação: execute `yarn twenty auth:login` e certifique-se de que sua chave de API tenha as permissões necessárias.
* Não é possível conectar ao servidor: verifique a URL da API e se o servidor do Twenty está acessível.
* Tipos ou cliente ausentes/desatualizados: execute `yarn twenty app:generate`.
* Tipos ou cliente ausentes/desatualizados: reinicie `yarn twenty app:dev` — ele gera automaticamente o cliente tipado.
* Modo de desenvolvimento não sincronizando: certifique-se de que `yarn twenty app:dev` esteja em execução e de que as alterações não estejam sendo ignoradas pelo seu ambiente.
Canal de ajuda no Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,7 +27,7 @@ Aplicațiile vă permit să construiți și să gestionați personalizările Twe
Creați o aplicație nouă folosind generatorul oficial, apoi autentificați-vă și începeți să dezvoltați:
```bash filename="Terminal"
# Creează scheletul unei aplicații noi
# Creează scheletul unei aplicații noi (include toate exemplele în mod implicit)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
@@ -42,21 +42,34 @@ yarn twenty auth:login
yarn twenty app:dev
```
Generatorul de schelet acceptă trei moduri pentru a controla ce fișiere de exemplu sunt incluse:
```bash filename="Terminal"
# Implicit (exhaustiv): toate exemplele (obiect, câmp, funcție logică, componentă de interfață, vizualizare, element de meniu de navigare)
npx create-twenty-app@latest my-app
# Minimal: doar fișierele de bază (application-config.ts și default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactiv: selectezi ce exemple să incluzi
npx create-twenty-app@latest my-app --interactive
```
De aici puteți:
```bash filename="Terminal"
# Adaugă o entitate nouă în aplicația ta (ghidat)
yarn twenty entity:add
# Generează un client Twenty tipizat și tipurile de entități ale spațiului de lucru
yarn twenty app:generate
# Urmărește jurnalele funcțiilor aplicației tale
yarn twenty function:logs
# Execută o funcție după nume
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execută funcția post-instalare
yarn twenty function:execute --postInstall
# Dezinstalează aplicația din spațiul de lucru curent
yarn twenty app:uninstall
@@ -73,9 +86,9 @@ Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul:
* Copiază o aplicație de bază minimală în `my-twenty-app/`
* Adaugă o dependență locală `twenty-sdk` și configurația Yarn 4
* Creează fișiere de configurare și scripturi conectate la CLI-ul `twenty`
* Generează o configurație implicită a aplicației și un rol implicit pentru funcții
* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcția post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului
O aplicație nou generată arată astfel:
O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -89,17 +102,28 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Public assets folder (images, fonts, etc.)
public/ # Director pentru resurse publice (imagini, fonturi etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Obligatoriu - configurația principală a aplicației
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Rol implicit pentru funcțiile logice
├── objects/
│ └── example-object.ts # Exemplu de definiție a unui obiect personalizat
├── fields/
│ └── example-field.ts # Exemplu de definiție de câmp independent
├── logic-functions/
── hello-world.ts # Example logic function
└── front-components/
└── hello-world.tsx # Example front component
── hello-world.ts # Exemplu de funcție logică
└── post-install.ts # Funcție logică post-instalare
├── front-components/
│ └── hello-world.tsx # Exemplu de componentă de interfață
├── views/
│ └── example-view.ts # Exemplu de definiție a unei vizualizări salvate
└── navigation-menu-items/
└── example-navigation-menu-item.ts # Exemplu de link de navigare în bara laterală
```
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts` și `logic-functions/post-install.ts`). Cu `--interactive`, alegi ce fișiere de exemplu să incluzi.
Pe scurt:
* **package.json**: Declară numele aplicației, versiunea, motoarele (Node 24+, Yarn 4) și adaugă `twenty-sdk` plus un script `twenty` care deleagă către CLI-ul local `twenty`. Rulează `yarn twenty help` pentru a lista toate comenzile disponibile.
@@ -115,13 +139,15 @@ Pe scurt:
SDK-ul detectează entitățile analizând fișierele TypeScript pentru apeluri **`export default define<Entity>({...})`**. Fiecare tip de entitate are o funcție ajutătoare corespunzătoare, exportată din `twenty-sdk`:
| Funcție ajutătoare | Tipul entității |
| ------------------------ | ------------------------------------------- |
| `defineObject()` | Definiții de obiecte personalizate |
| `defineLogicFunction()` | Definiții de funcții de logică |
| `defineFrontComponent()` | Definiții ale componentelor de interfață |
| `defineRole()` | Definiții de rol |
| `defineField()` | Extensii de câmp pentru obiectele existente |
| Funcție ajutătoare | Tipul entității |
| ---------------------------- | ---------------------------------------------- |
| `defineObject()` | Definiții de obiecte personalizate |
| `defineLogicFunction()` | Definiții de funcții de logică |
| `defineFrontComponent()` | Definiții ale componentelor de interfață |
| `defineRole()` | Definiții de rol |
| `defineField()` | Extensii de câmp pentru obiectele existente |
| `defineView()` | Definiții pentru vizualizări salvate |
| `defineNavigationMenuItem()` | Definiții pentru elemente de meniu de navigare |
<Note>
**Denumirea fișierelor este flexibilă.** Detectarea entităților se bazează pe AST — SDK-ul scanează fișierele sursă pentru tiparul `export default define<Entity>({...})`. Puteți organiza fișierele și folderele cum doriți. Gruparea după tipul de entitate (de exemplu, `logic-functions/`, `roles/`) este doar o convenție pentru organizarea codului, nu o cerință.
@@ -142,7 +168,7 @@ export default defineObject({
Comenzile ulterioare vor adăuga mai multe fișiere și foldere:
* `yarn twenty app:generate` va crea un folder `generated/` (client Twenty tipizat + tipuri pentru spațiul de lucru).
* `yarn twenty app:dev` va genera automat un client API tipizat în `node_modules/twenty-sdk/generated` (client Twenty tipizat + tipuri ale spațiului de lucru).
* `yarn twenty entity:add` va adăuga fișiere de definire a entităților în `src/` pentru obiectele, funcțiile, componentele front-end sau rolurile personalizate.
## Autentificare
@@ -186,14 +212,16 @@ Biblioteca twenty-sdk oferă blocuri de bază tipizate și funcții ajutătoare
SDK-ul oferă funcții ajutătoare pentru definirea entităților aplicației. După cum este descris în [Detectarea entităților](#entity-detection), trebuie să folosiți `export default define<Entity>({...})` pentru ca entitățile să fie detectate:
| Funcție | Scop |
| ------------------------ | ---------------------------------------------------------------------- |
| `defineApplication()` | Configurați metadatele aplicației (obligatoriu, una per aplicație) |
| `defineObject()` | Definiți obiecte personalizate cu câmpuri |
| `defineLogicFunction()` | Definiți funcții de logică cu handleri |
| `defineFrontComponent()` | Definiți componente Front pentru interfața de utilizator personalizată |
| `defineRole()` | Configurați permisiunile rolurilor și accesul la obiecte |
| `defineField()` | Extindeți obiectele existente cu câmpuri suplimentare |
| Funcție | Scop |
| ---------------------------- | ---------------------------------------------------------------------- |
| `defineApplication()` | Configurați metadatele aplicației (obligatoriu, una per aplicație) |
| `defineObject()` | Definiți obiecte personalizate cu câmpuri |
| `defineLogicFunction()` | Definiți funcții de logică cu handleri |
| `defineFrontComponent()` | Definiți componente Front pentru interfața de utilizator personalizată |
| `defineRole()` | Configurați permisiunile rolurilor și accesul la obiecte |
| `defineField()` | Extindeți obiectele existente cu câmpuri suplimentare |
| `defineView()` | Definește vizualizări salvate pentru obiecte |
| `defineNavigationMenuItem()` | Definește linkuri de navigare în bara laterală |
Aceste funcții validează configurația în timpul build-ului și oferă completare automată în IDE și siguranța tipurilor.
@@ -293,6 +321,7 @@ Fiecare aplicație are un singur fișier `application-config.ts` care descrie:
* **Cine este aplicația**: identificatori, nume de afișare și descriere.
* **Cum rulează funcțiile**: ce rol folosesc pentru permisiuni.
* **(Opțional) variabile**: perechi cheievaloare expuse funcțiilor ca variabile de mediu.
* **(Opțional) funcție post-instalare**: o funcție logică care rulează după instalarea aplicației.
Folosiți `defineApplication()` pentru a defini configurația aplicației:
@@ -300,6 +329,7 @@ Folosiți `defineApplication()` pentru a defini configurația aplicației:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ Notițe:
* Câmpurile `universalIdentifier` sunt ID-uri deterministe pe care le dețineți; generați-le o singură dată și păstrați-le stabile între sincronizări.
* `applicationVariables` devin variabile de mediu pentru funcțiile dvs. (de exemplu, `DEFAULT_RECIPIENT_NAME` este disponibil ca `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` trebuie să corespundă fișierului de rol (vedeți mai jos).
* `postInstallLogicFunctionUniversalIdentifier` (opțional) indică o funcție logică care rulează automat după instalarea aplicației. Vezi [Funcții post-instalare](#post-install-functions).
#### Roluri și permisiuni
@@ -461,6 +493,55 @@ Notițe:
* Matricea `triggers` este opțională. Funcțiile fără declanșatoare pot fi folosite ca funcții utilitare apelate de alte funcții.
* Puteți combina mai multe tipuri de declanșatoare într-o singură funcție.
### Funcții post-instalare
O funcție post-instalare este o funcție logică care rulează automat după instalarea aplicației într-un spațiu de lucru. Aceasta este utilă pentru sarcini de configurare unice, cum ar fi popularea cu date implicite, crearea înregistrărilor inițiale sau configurarea setărilor spațiului de lucru.
Când creezi scheletul unei aplicații noi cu `create-twenty-app`, este generată o funcție post-instalare la `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
Funcția este integrată în aplicația ta prin referirea la identificatorul său universal în `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
Poți, de asemenea, să execuți manual funcția post-instalare oricând folosind CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Puncte cheie:
* Funcțiile post-instalare sunt funcții logice standard — folosesc `defineLogicFunction()` la fel ca orice altă funcție.
* Câmpul `postInstallLogicFunctionUniversalIdentifier` din `defineApplication()` este opțional. Dacă este omis, nu rulează nicio funcție după instalare.
* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de configurare mai lungi, cum ar fi popularea datelor.
* Funcțiile post-instalare nu au nevoie de declanșatoare — sunt invocate de platformă în timpul instalării sau manual prin `function:execute --postInstall`.
### Payload-ul declanșatorului de rută
<Warning>
@@ -662,7 +743,7 @@ Puteți crea componente Front noi în două moduri:
### Client tipizat generat
Rulați `yarn twenty app:generate` pentru a crea un client tipizat local în `generated/`, pe baza schemei spațiului de lucru. Folosiți-l în funcțiile dvs.:
Clientul tipizat este generat automat de `yarn twenty app:dev` și stocat în `node_modules/twenty-sdk/generated`, pe baza schemei spațiului tău de lucru. Folosiți-l în funcțiile dvs.:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
Clientul este regenerat de `yarn twenty app:generate`. Rulați din nou după ce vă modificați obiectele sau când vă integrați într-un spațiu de lucru nou.
Clientul este regenerat automat de `yarn twenty app:dev` ori de câte ori obiectele sau câmpurile tale se schimbă.
#### Acreditări la runtime în funcțiile de logică
@@ -708,13 +789,13 @@ Apoi adăugați un script `twenty`:
}
```
Acum puteți rula toate comenzile prin `yarn twenty <command>`, de ex. `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help`, etc.
Acum poți rula toate comenzile prin `yarn twenty <command>`, de ex. `yarn twenty app:dev`, `yarn twenty help`, etc.
## Depanare
* Erori de autentificare: rulați `yarn twenty auth:login` și asigurați-vă că cheia API are permisiunile necesare.
* Nu se poate conecta la server: verificați URL-ul API și că serverul Twenty este accesibil.
* Tipuri sau client lipsă/învechite: rulați `yarn twenty app:generate`.
* Types or client missing/outdated: restart `yarn twenty app:dev` — it auto-generates the typed client.
* Modul dev nu sincronizează: asigurați-vă că `yarn twenty app:dev` rulează și că modificările nu sunt ignorate de mediul dvs.
Canal de ajutor pe Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,36 +27,49 @@ description: Создавайте и управляйте настройками
Создайте новое приложение с помощью официального генератора, затем выполните аутентификацию и начните разработку:
```bash filename="Terminal"
# Создать каркас нового приложения
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# Если вы не используете yarn@4
# If you don't use yarn@4
corepack enable
yarn install
# Аутентифицироваться с помощью вашего API-ключа (вам будет предложено)
# Authenticate using your API key (you'll be prompted)
yarn twenty auth:login
# Запустить режим разработки: автоматически синхронизирует локальные изменения с вашим рабочим пространством
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
Отсюда вы можете:
```bash filename="Terminal"
# Добавить новую сущность в ваше приложение (с мастером)
yarn twenty entity:add
# Сгенерировать типизированный клиент Twenty и типы сущностей рабочего пространства
yarn twenty app:generate
# Просматривать логи функций вашего приложения
yarn twenty function:logs
# Выполнить функцию по имени
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'
# Выполнить послеустановочную функцию
yarn twenty function:execute --postInstall
# Удалить приложение из текущего рабочего пространства
yarn twenty app:uninstall
@@ -73,9 +86,9 @@ yarn twenty help
* Копирует минимальное базовое приложение в `my-twenty-app/`
* Добавляет локальную зависимость `twenty-sdk` и конфигурацию Yarn 4
* Создаёт файлы конфигурации и скрипты, подключённые к CLI `twenty`
* Генерирует конфигурацию приложения по умолчанию и роль функции по умолчанию
* Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
Свежесгенерированное приложение выглядит так:
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -89,17 +102,28 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Папка общедоступных ресурсов (изображения, шрифты и т. п.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Обязательный — основная конфигурация приложения
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Роль по умолчанию для логических функций
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
── hello-world.ts # Пример логической функции
└── front-components/
└── hello-world.tsx # Пример фронтенд-компонента
── hello-world.ts # Example logic function
└── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
└── navigation-menu-items/
└── example-navigation-menu-item.ts # Example sidebar navigation link
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
В общих чертах:
* **package.json**: Объявляет имя приложения, версию, движки (Node 24+, Yarn 4) и добавляет `twenty-sdk`, а также скрипт `twenty`, который делегирует выполнение локальному CLI `twenty`. Выполните `yarn twenty help`, чтобы вывести список всех доступных команд.
@@ -115,13 +139,15 @@ my-twenty-app/
SDK обнаруживает сущности, разбирая ваши файлы TypeScript в поисках вызовов **`export default define<Entity>({...})`**. Для каждого типа сущности существует соответствующая вспомогательная функция, экспортируемая из `twenty-sdk`:
| Вспомогательная функция | Тип сущности |
| ------------------------ | ------------------------------------------ |
| `defineObject()` | Определения пользовательских объектов |
| `defineLogicFunction()` | Определения логических функций |
| `defineFrontComponent()` | Определения компонентов фронтенда |
| `defineRole()` | Определения ролей |
| `defineField()` | Расширения полей для существующих объектов |
| Вспомогательная функция | Тип сущности |
| ---------------------------- | ------------------------------------------ |
| `defineObject()` | Определения пользовательских объектов |
| `defineLogicFunction()` | Определения логических функций |
| `defineFrontComponent()` | Определения компонентов фронтенда |
| `defineRole()` | Определения ролей |
| `defineField()` | Расширения полей для существующих объектов |
| `defineView()` | Saved view definitions |
| `defineNavigationMenuItem()` | Navigation menu item definitions |
<Note>
**Имена файлов заданы гибко.** Обнаружение сущностей основано на AST — SDK сканирует ваши исходные файлы в поисках шаблона `export default define<Entity>({...})`. Вы можете организовывать файлы и папки как угодно. Группировка по типу сущности (например, `logic-functions/`, `roles/`) — это лишь соглашение для организации кода, а не требование.
@@ -142,7 +168,7 @@ export default defineObject({
Позднее команды добавят больше файлов и папок:
* `yarn twenty app:generate` создаст папку `generated/` (типизированный клиент Twenty + типы рабочего пространства).
* `yarn twenty app:dev` автоматически сгенерирует типизированный клиент API в `node_modules/twenty-sdk/generated` (типизированный клиент Twenty + типы рабочего пространства).
* `yarn twenty entity:add` добавит файлы определений сущностей в `src/` для ваших пользовательских объектов, функций, фронтенд-компонентов или ролей.
## Аутентификация
@@ -186,14 +212,16 @@ yarn twenty auth:status
SDK предоставляет вспомогательные функции для определения сущностей вашего приложения. Как описано в [Обнаружение сущностей](#entity-detection), вы должны использовать `export default define<Entity>({...})`, чтобы ваши сущности были обнаружены:
| Функция | Назначение |
| ------------------------ | ---------------------------------------------------------------------- |
| `defineApplication()` | Настройка метаданных приложения (обязательно, по одному на приложение) |
| `defineObject()` | Определяет пользовательские объекты с полями |
| `defineLogicFunction()` | Определение логических функций с обработчиками |
| `defineFrontComponent()` | Определение фронт-компонентов для настраиваемого интерфейса |
| `defineRole()` | Настраивает права роли и доступ к объектам |
| `defineField()` | Расширение существующих объектов дополнительными полями |
| Функция | Назначение |
| ---------------------------- | ---------------------------------------------------------------------- |
| `defineApplication()` | Настройка метаданных приложения (обязательно, по одному на приложение) |
| `defineObject()` | Определяет пользовательские объекты с полями |
| `defineLogicFunction()` | Определение логических функций с обработчиками |
| `defineFrontComponent()` | Определение фронт-компонентов для настраиваемого интерфейса |
| `defineRole()` | Настраивает права роли и доступ к объектам |
| `defineField()` | Расширение существующих объектов дополнительными полями |
| `defineView()` | Define saved views for objects |
| `defineNavigationMenuItem()` | Define sidebar navigation links |
Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов.
@@ -293,6 +321,7 @@ export default defineObject({
* **Что это за приложение**: идентификаторы, отображаемое имя и описание.
* **Как запускаются его функции**: какую роль они используют для прав доступа.
* **(Необязательно) переменные**: пары ключ-значение, предоставляемые вашим функциям как переменные окружения.
* **(Необязательно) послеустановочная функция**: функция логики, которая запускается после установки приложения.
Используйте `defineApplication()` для определения конфигурации вашего приложения:
@@ -300,6 +329,7 @@ export default defineObject({
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ export default defineApplication({
* `universalIdentifier` — это детерминированные идентификаторы, которыми вы управляете; сгенерируйте их один раз и сохраняйте стабильными между синхронизациями.
* `applicationVariables` становятся переменными окружения для ваших функций (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` должен соответствовать файлу роли (см. ниже).
* `postInstallLogicFunctionUniversalIdentifier` (необязательно) указывает на логическую функцию, которая автоматически выполняется после установки приложения. См. [Послеустановочные функции](#post-install-functions).
#### Роли и разрешения
@@ -461,6 +493,55 @@ export default defineLogicFunction({
* Массив `triggers` необязателен. Функции без триггеров можно использовать как вспомогательные, вызываемые другими функциями.
* Вы можете сочетать несколько типов триггеров в одной функции.
### Послеустановочные функции
Послеустановочная функция — это функция логики, которая автоматически выполняется после установки вашего приложения в рабочем пространстве. Это полезно для одноразовых задач настройки, таких как инициализация данных по умолчанию, создание начальных записей или настройка параметров рабочего пространства.
When you scaffold a new app with `create-twenty-app`, a post-install function is generated for you at `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
The function is wired into your app by referencing its universal identifier in `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
You can also manually execute the post-install function at any time using the CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Основные моменты:
* Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function.
* The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation.
* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
* Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`.
### Полезная нагрузка триггера маршрута
<Warning>
@@ -662,7 +743,7 @@ export default defineFrontComponent({
### Сгенерированный типизированный клиент
Запустите `yarn twenty app:generate`, чтобы создать локальный типизированный клиент в `generated/` на основе схемы вашего рабочего пространства. Используйте его в своих функциях:
Типизированный клиент автоматически генерируется с помощью `yarn twenty app:dev` и сохраняется в `node_modules/twenty-sdk/generated` на основе схемы вашего рабочего пространства. Используйте его в своих функциях:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
Клиент повторно генерируется командой `yarn twenty app:generate`. Запускайте повторно после изменения ваших объектов или при подключении к новому рабочему пространству.
Клиент автоматически перегенерируется с помощью `yarn twenty app:dev` при изменении ваших объектов или полей.
#### Учётные данные времени выполнения в логических функциях
@@ -708,13 +789,13 @@ yarn add -D twenty-sdk
}
```
Теперь вы можете запускать все команды через `yarn twenty <command>`, например, `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help` и т. д.
Теперь вы можете запускать все команды через `yarn twenty <command>`, например, `yarn twenty app:dev`, `yarn twenty help` и т. д.
## Устранение неполадок
* Ошибки аутентификации: выполните `yarn twenty auth:login` и убедитесь, что у вашего ключа API есть необходимые права.
* Не удаётся подключиться к серверу: проверьте URL API и доступность сервера Twenty.
* Типы или клиент отсутствуют/устарели: выполните `yarn twenty app:generate`.
* Types or client missing/outdated: restart `yarn twenty app:dev` — it auto-generates the typed client.
* Режим разработки не синхронизируется: убедитесь, что запущен `yarn twenty app:dev`, и что ваша среда не игнорирует изменения.
Канал помощи в Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,7 +27,7 @@ Uygulamalar, Twenty özelleştirmelerini **kod olarak** oluşturup yönetmenizi
Resmi scaffolder aracını kullanarak yeni bir uygulama oluşturun, ardından kimlik doğrulaması yapıp geliştirmeye başlayın:
```bash filename="Terminal"
# Scaffold a new app
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
@@ -42,21 +42,34 @@ yarn twenty auth:login
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
Buradan şunları yapabilirsiniz:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
# Generate a typed Twenty client and workspace entity types
yarn twenty app:generate
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty app:uninstall
@@ -73,9 +86,9 @@ Ayrıca bkz.: [create-twenty-app](https://www.npmjs.com/package/create-twenty-ap
* Minimal bir temel uygulamayı `my-twenty-app/` içine kopyalar
* Yerel bir `twenty-sdk` bağımlılığı ve Yarn 4 yapılandırması ekler
* `twenty` CLI ile bağlantılı yapılandırma dosyaları ve betikler oluşturur
* Varsayılan bir uygulama yapılandırması ve varsayılan bir fonksiyon rolü üretir
* Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
Yeni şablondan oluşturulan bir uygulama şöyle görünür:
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -89,17 +102,28 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Genel varlıklar klasörü (görseller, yazı tipleri vb.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Gerekli - ana uygulama yapılandırması
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Mantık işlevleri için varsayılan rol
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
── hello-world.ts # Örnek mantık işlevi
└── front-components/
└── hello-world.tsx # Örnek ön uç bileşeni
── hello-world.ts # Example logic function
└── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
└── navigation-menu-items/
└── example-navigation-menu-item.ts # Example sidebar navigation link
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
Genel hatlarıyla:
* **package.json**: Uygulama adını, sürümünü, motorları (Node 24+, Yarn 4) bildirir ve `twenty-sdk` ile yerel `twenty` CLI'sine yetki devreden bir `twenty` betiği ekler. Tüm mevcut komutları listelemek için `yarn twenty help` komutunu çalıştırın.
@@ -115,13 +139,15 @@ Genel hatlarıyla:
SDK, TypeScript dosyalarınızı **`export default define<Entity>({...})`** çağrılarını arayarak ayrıştırıp varlıkları algılar. Her varlık türünün, `twenty-sdk` tarafından dışa aktarılan karşılık gelen bir yardımcı fonksiyonu vardır:
| Yardımcı fonksiyon | Varlık türü |
| ------------------------ | ---------------------------------------- |
| `defineObject()` | Özel nesne tanımları |
| `defineLogicFunction()` | Mantık fonksiyon tanımları |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | Rol tanımları |
| `defineField()` | Mevcut nesneler için alan genişletmeleri |
| Yardımcı fonksiyon | Varlık türü |
| ---------------------------- | ---------------------------------------- |
| `defineObject()` | Özel nesne tanımları |
| `defineLogicFunction()` | Mantık fonksiyon tanımları |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | Rol tanımları |
| `defineField()` | Mevcut nesneler için alan genişletmeleri |
| `defineView()` | Saved view definitions |
| `defineNavigationMenuItem()` | Navigation menu item definitions |
<Note>
**Dosya adlandırma esnektir.** Varlık algılama AST tabanlıdır — SDK, kaynak dosyalarınızı `export default define<Entity>({...})` desenini bulmak için tarar. Dosyalarınızı ve klasörlerinizi dilediğiniz gibi düzenleyebilirsiniz. Varlık türüne göre gruplama (örn. `logic-functions/`, `roles/`) bir gereklilik değil, yalnızca kod organizasyonu için bir gelenektir.
@@ -142,7 +168,7 @@ export default defineObject({
İlerideki komutlar daha fazla dosya ve klasör ekleyecektir:
* `yarn twenty app:generate`, `generated/` klasörünü oluşturur (türlendirilmiş Twenty istemcisi + çalışma alanı türleri).
* `yarn twenty app:dev`, `node_modules/twenty-sdk/generated` içinde tipli bir API istemcisini otomatik olarak oluşturur (tipli Twenty istemcisi + çalışma alanı türleri).
* `yarn twenty entity:add`, özel nesneleriniz, fonksiyonlarınız, ön bileşenleriniz veya rolleriniz için `src/` altında varlık tanım dosyaları ekler.
## Kimlik Doğrulama
@@ -186,14 +212,16 @@ twenty-sdk, uygulamanız içinde kullandığınız türlendirilmiş yapı taşla
SDK, uygulama varlıklarınızı tanımlamak için yardımcı fonksiyonlar sağlar. [Varlık algılama](#entity-detection) bölümünde açıklandığı gibi, varlıklarınızın algılanması için `export default define<Entity>({...})` kullanmalısınız:
| Fonksiyon | Amaç |
| ------------------------ | ------------------------------------------------------------------------- |
| `defineApplication()` | Uygulama meta verilerini yapılandırın (zorunlu, uygulama başına bir adet) |
| `defineObject()` | Alanlara sahip özel nesneler tanımlayın |
| `defineLogicFunction()` | İşleyicilerle mantık fonksiyonları tanımlayın |
| `defineFrontComponent()` | Özel kullanıcı arayüzü için ön uç bileşenlerini tanımlayın |
| `defineRole()` | Rol izinlerini ve nesne erişimini yapılandırın |
| `defineField()` | Mevcut nesneleri ek alanlarla genişletin |
| Fonksiyon | Amaç |
| ---------------------------- | ------------------------------------------------------------------------- |
| `defineApplication()` | Uygulama meta verilerini yapılandırın (zorunlu, uygulama başına bir adet) |
| `defineObject()` | Alanlara sahip özel nesneler tanımlayın |
| `defineLogicFunction()` | İşleyicilerle mantık fonksiyonları tanımlayın |
| `defineFrontComponent()` | Özel kullanıcı arayüzü için ön uç bileşenlerini tanımlayın |
| `defineRole()` | Rol izinlerini ve nesne erişimini yapılandırın |
| `defineField()` | Mevcut nesneleri ek alanlarla genişletin |
| `defineView()` | Define saved views for objects |
| `defineNavigationMenuItem()` | Define sidebar navigation links |
Bu fonksiyonlar, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar.
@@ -293,6 +321,7 @@ Her uygulamanın aşağıdakileri açıklayan tek bir `application-config.ts` do
* **Uygulamanın kim olduğu**: tanımlayıcılar, görünen ad ve açıklama.
* **Fonksiyonlarının nasıl çalıştığı**: izinler için hangi rolü kullandıkları.
* **(İsteğe bağlı) değişkenler**: fonksiyonlarınıza ortam değişkenleri olarak sunulan anahtardeğer çiftleri.
* **(İsteğe bağlı) kurulum sonrası işlev**: uygulama yüklendikten sonra çalışan bir mantık işlevi.
Uygulama yapılandırmanızı tanımlamak için `defineApplication()` kullanın:
@@ -300,6 +329,7 @@ Uygulama yapılandırmanızı tanımlamak için `defineApplication()` kullanın:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ Notlar:
* `universalIdentifier` alanları size ait belirleyici kimliklerdir; bunları bir kez oluşturun ve eşitlemeler boyunca kararlı tutun.
* `applicationVariables`, fonksiyonlarınız için ortam değişkenlerine dönüşür (örneğin, `DEFAULT_RECIPIENT_NAME` değeri `process.env.DEFAULT_RECIPIENT_NAME` olarak kullanılabilir).
* `defaultRoleUniversalIdentifier`, rol dosyasıyla eşleşmelidir (aşağıya bakın).
* `postInstallLogicFunctionUniversalIdentifier` (isteğe bağlı), uygulama yüklendikten sonra otomatik olarak çalışan bir mantık işlevine işaret eder. Bkz. [Kurulum sonrası işlevler](#post-install-functions).
#### Roller ve izinler
@@ -461,6 +493,55 @@ Notlar:
* `triggers` dizisi isteğe bağlıdır. Tetikleyicisi olmayan fonksiyonlar, diğer fonksiyonlar tarafından çağrılan yardımcı fonksiyonlar olarak kullanılabilir.
* Tek bir fonksiyonda birden çok tetikleyici türünü birleştirebilirsiniz.
### Kurulum sonrası işlevler
Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonra otomatik olarak çalışan bir mantık işlevidir. Bu, varsayılan verileri tohumlama, ilk kayıtları oluşturma veya çalışma alanı ayarlarını yapılandırma gibi tek seferlik kurulum görevleri için yararlıdır.
When you scaffold a new app with `create-twenty-app`, a post-install function is generated for you at `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
The function is wired into your app by referencing its universal identifier in `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
You can also manually execute the post-install function at any time using the CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
Önemli noktalar:
* Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function.
* The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation.
* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
* Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`.
### Rota tetikleyicisi yükü
<Warning>
@@ -662,7 +743,7 @@ Yeni ön uç bileşenlerini iki şekilde oluşturabilirsiniz:
### Oluşturulmuş türlendirilmiş istemci
Çalışma alanı şemanıza göre `generated/` içinde yerel bir türlendirilmiş istemci oluşturmak için `yarn twenty app:generate` çalıştırın. Fonksiyonlarınızda kullanın:
Tipli istemci, `yarn twenty app:dev` tarafından otomatik olarak oluşturulur ve çalışma alanı şemanıza göre `node_modules/twenty-sdk/generated` içine kaydedilir. Fonksiyonlarınızda kullanın:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
İstemci `yarn twenty app:generate` tarafından yeniden oluşturulur. Nesnelerinizi değiştirdikten sonra veya yeni bir çalışma alanına katılırken yeniden çalıştırın.
Nesneleriniz veya alanlarınız değiştiğinde, istemci `yarn twenty app:dev` tarafından otomatik olarak yeniden oluşturulur.
#### Mantık fonksiyonlarında çalışma zamanı kimlik bilgileri
@@ -708,13 +789,13 @@ Ardından bir `twenty` betiği ekleyin:
}
```
Artık tüm komutları `yarn twenty <command>` üzerinden çalıştırabilirsiniz; örn. `yarn twenty app:dev`, `yarn twenty app:generate`, `yarn twenty help` vb.
Artık tüm komutları `yarn twenty <command>` üzerinden çalıştırabilirsiniz; örn. `yarn twenty app:dev`, `yarn twenty help` vb.
## Sorun Giderme
* Kimlik doğrulama hataları: `yarn twenty auth:login` çalıştırın ve API anahtarınızın gerekli izinlere sahip olduğundan emin olun.
* Sunucuya bağlanılamıyor: API URLsini ve Twenty sunucusunun erişilebilir olduğunu doğrulayın.
* Türler veya istemci eksik/eski: `yarn twenty app:generate` çalıştırın.
* Türler veya istemci eksik/eski: `yarn twenty app:dev` komutunu yeniden çalıştırın — tip tanımlı istemciyi otomatik olarak oluşturur.
* Geliştirme modu eşitlenmiyor: `yarn twenty app:dev`'in çalıştığından ve değişikliklerin ortamınız tarafından yok sayılmadığından emin olun.
Discord Yardım Kanalı: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -27,35 +27,48 @@ description: 以代码的形式构建并管理 Twenty 自定义项。
使用官方脚手架创建一个新应用,然后进行身份验证并开始开发:
```bash filename="Terminal"
# 搭建一个新应用
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# 如果你不使用 yarn@4
# If you don't use yarn@4
corepack enable
yarn install
# 使用你的 API 密钥进行身份验证(系统会提示你)
# Authenticate using your API key (you'll be prompted)
yarn twenty auth:login
# 启动开发模式:会将本地更改自动同步到你的工作区
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
从这里您可以:
```bash filename="Terminal"
# 向你的应用添加一个新实体(引导式)
yarn twenty entity:add
# 生成类型化的 Twenty 客户端和工作区实体类型
yarn twenty app:generate
# 监听你的应用函数日志
yarn twenty function:logs
# 按名称执行一个函数
yarn twenty function:execute -n my-function -p '{"name": "test"}'
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'
# 执行安装后函数
yarn twenty function:execute --postInstall
# 从当前工作区卸载该应用
yarn twenty app:uninstall
@@ -73,9 +86,9 @@ yarn twenty help
* 将一个最小的基础应用复制到 `my-twenty-app/` 中
* 添加本地 `twenty-sdk` 依赖和 Yarn 4 配置
* 创建与 `twenty` CLI 关联的配置文件和脚本
* 生成默认的应用配置和默认的函数角色
* Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
一个新生成的脚手架应用如下所示:
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -89,17 +102,28 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # 公共资源文件夹(图像、字体等)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # 必需 - 主应用程序配置
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # 用于逻辑函数的默认角色
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
── hello-world.ts # 示例逻辑函数
└── front-components/
└── hello-world.tsx # 示例前端组件
── hello-world.ts # Example logic function
└── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
└── navigation-menu-items/
└── example-navigation-menu-item.ts # Example sidebar navigation link
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
总体来说:
* **package.json**:声明应用名称、版本、引擎(Node 24+、Yarn 4),并添加 `twenty-sdk` 以及一个 `twenty` 脚本,该脚本会委托给本地的 `twenty` CLI。 运行 `yarn twenty help` 以列出所有可用命令。
@@ -115,13 +139,15 @@ my-twenty-app/
该 SDK 通过在你的 TypeScript 文件中解析 **`export default define<Entity>({...})`** 调用来检测实体。 每种实体类型都有一个从 `twenty-sdk` 导出的对应辅助函数:
| 辅助函数 | 实体类型 |
| ------------------------ | --------- |
| `defineObject()` | 自定义对象定义 |
| `defineLogicFunction()` | 逻辑函数定义 |
| `defineFrontComponent()` | 前端组件定义 |
| `defineRole()` | 角色定义 |
| `defineField()` | 现有对象的字段扩展 |
| 辅助函数 | 实体类型 |
| ---------------------------- | -------------------------------- |
| `defineObject()` | 自定义对象定义 |
| `defineLogicFunction()` | 逻辑函数定义 |
| `defineFrontComponent()` | 前端组件定义 |
| `defineRole()` | 角色定义 |
| `defineField()` | 现有对象的字段扩展 |
| `defineView()` | Saved view definitions |
| `defineNavigationMenuItem()` | Navigation menu item definitions |
<Note>
**文件命名是灵活的。** 实体检测基于 AST — SDK 会扫描你的源文件以查找 `export default define<Entity>({...})` 模式。 你可以按照自己的喜好组织文件和文件夹。 按实体类型分组(例如 `logic-functions/`、`roles/`)只是代码组织的一种约定,并非必需。
@@ -142,7 +168,7 @@ export default defineObject({
后续命令将添加更多文件和文件夹:
* `yarn twenty app:generate` 将创建一个 `generated/` 文件夹(类型化 Twenty 客户端 + 工作空间类型)。
* `yarn twenty app:dev` 将在 `node_modules/twenty-sdk/generated` 中自动生成一个类型化的 API 客户端(类型化 Twenty 客户端 + 工作类型)。
* `yarn twenty entity:add` 会在 `src/` 下为你的自定义对象、函数、前端组件或角色添加实体定义文件。
## 身份验证
@@ -186,14 +212,16 @@ twenty-sdk 提供你在应用中使用的类型化构件和辅助函数。 以
该 SDK 提供辅助函数用于定义你的应用实体。 如 [实体检测](#entity-detection) 中所述,你必须使用 `export default define<Entity>({...})` 才能让你的实体被检测到:
| 函数 | 目的 |
| ------------------------ | ------------------ |
| `defineApplication()` | 配置应用元数据(必需,每个应用一个) |
| `defineObject()` | 定义带字段的自定义对象 |
| `defineLogicFunction()` | 定义带处理程序的逻辑函数 |
| `defineFrontComponent()` | 为自定义 UI 定义前端组件 |
| `defineRole()` | 配置角色权限和对象访问 |
| `defineField()` | 为现有对象扩展额外字段 |
| 函数 | 目的 |
| ---------------------------- | ------------------------------- |
| `defineApplication()` | 配置应用元数据(必需,每个应用一个) |
| `defineObject()` | 定义带字段的自定义对象 |
| `defineLogicFunction()` | 定义带处理程序的逻辑函数 |
| `defineFrontComponent()` | 为自定义 UI 定义前端组件 |
| `defineRole()` | 配置角色权限和对象访问 |
| `defineField()` | 为现有对象扩展额外字段 |
| `defineView()` | Define saved views for objects |
| `defineNavigationMenuItem()` | Define sidebar navigation links |
这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。
@@ -293,6 +321,7 @@ export default defineObject({
* **应用的身份**:标识符、显示名称和描述。
* **函数如何运行**:它们用于权限的角色。
* **(可选)变量**:以环境变量形式提供给函数的键值对。
* **(可选)安装后函数**:在应用安装后运行的逻辑函数。
使用 `defineApplication()` 定义你的应用配置:
@@ -300,6 +329,7 @@ export default defineObject({
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -315,6 +345,7 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -323,6 +354,7 @@ export default defineApplication({
* `universalIdentifier` 字段是你拥有的确定性 ID;生成一次并在多次同步中保持稳定。
* `applicationVariables` 会变成函数可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)。
* `defaultRoleUniversalIdentifier` 必须与角色文件一致(见下文)。
* `postInstallLogicFunctionUniversalIdentifier`(可选)指向一个在应用安装后自动运行的逻辑函数。 参见 [安装后函数](#post-install-functions)。
#### 角色和权限
@@ -461,6 +493,55 @@ export default defineLogicFunction({
* `triggers` 数组是可选的。 没有触发器的函数可作为实用函数,被其他函数调用。
* 你可以在单个函数中混用多种触发器类型。
### 安装后函数
安装后函数是在你的应用安装到工作区后自动运行的逻辑函数。 这对于一次性设置任务很有用,例如填充默认数据、创建初始记录或配置工作区设置。
When you scaffold a new app with `create-twenty-app`, a post-install function is generated for you at `src/logic-functions/post-install.ts`:
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
```
The function is wired into your app by referencing its universal identifier in `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
You can also manually execute the post-install function at any time using the CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
关键点:
* Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function.
* The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation.
* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
* Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`.
### 路由触发器负载
<Warning>
@@ -662,7 +743,7 @@ export default defineFrontComponent({
### 生成的类型化客户端
运行 `yarn twenty app:generate`,根据你的工作空间模式在 `generated/` 中创建本地类型化客户端。 在你的函数中使用它:
类型化客户端由 `yarn twenty app:dev` 自动生成,并基于你的工作区架构存放在 `node_modules/twenty-sdk/generated`。 在你的函数中使用它:
```typescript
import Twenty from '~/generated';
@@ -671,7 +752,7 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
客户端会通过 `yarn twenty app:generate` 重新生成。 在更改对象之后或接入新工作空间时,请重新运行
每当你的对象或字段发生变化时,`yarn twenty app:dev` 都会自动重新生成该客户端
#### 逻辑函数中的运行时凭据
@@ -708,13 +789,13 @@ yarn add -D twenty-sdk
}
```
现在你可以通过 `yarn twenty <command>` 运行所有命令,例如 `yarn twenty app:dev`、`yarn twenty app:generate`、`yarn twenty help` 等。
现在你可以通过 `yarn twenty <command>` 运行所有命令,例如 `yarn twenty app:dev`、`yarn twenty help` 等。
## 故障排除
* 身份验证错误:运行 `yarn twenty auth:login`,并确保你的 API 密钥具有所需权限。
* 无法连接到服务器:请验证 API URL,并确保 Twenty 服务器可达。
* 类型或客户端缺失/过期:运行 `yarn twenty app:generate`
* 类型或客户端缺失/过期:重启 `yarn twenty app:dev` — 它会自动生成类型化客户端
* 开发模式未同步:确保 `yarn twenty app:dev` 正在运行,并且你的环境不会忽略变更。
Discord 帮助频道:https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -1042,9 +1042,10 @@ export type CreateLogicFunctionFromSourceInput = {
export type CreateNavigationMenuItemInput = {
folderId?: InputMaybe<Scalars['UUID']>;
icon?: InputMaybe<Scalars['String']>;
link?: InputMaybe<Scalars['String']>;
name?: InputMaybe<Scalars['String']>;
position?: InputMaybe<Scalars['Int']>;
position?: InputMaybe<Scalars['Float']>;
targetObjectMetadataId?: InputMaybe<Scalars['UUID']>;
targetRecordId?: InputMaybe<Scalars['UUID']>;
userWorkspaceId?: InputMaybe<Scalars['UUID']>;
@@ -1447,13 +1448,8 @@ export enum EventLogTable {
export type EventSubscription = {
__typename?: 'EventSubscription';
eventStreamId: Scalars['String'];
eventWithQueryIdsList: Array<EventWithQueryIds>;
};
export type EventWithQueryIds = {
__typename?: 'EventWithQueryIds';
event: ObjectRecordEvent;
queryIds: Array<Scalars['String']>;
metadataEventsWithQueryIds: Array<MetadataEventWithQueryIds>;
objectRecordEventsWithQueryIds: Array<ObjectRecordEventWithQueryIds>;
};
export type ExecuteOneLogicFunctionInput = {
@@ -1484,6 +1480,7 @@ export enum FeatureFlagKey {
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
IS_CORE_PICTURE_MIGRATED = 'IS_CORE_PICTURE_MIGRATED',
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED = 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED',
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
IS_FILES_FIELD_MIGRATED = 'IS_FILES_FIELD_MIGRATED',
@@ -1616,6 +1613,7 @@ export type FieldRichTextConfiguration = {
export type FieldsConfiguration = {
__typename?: 'FieldsConfiguration';
configurationType: WidgetConfigurationType;
newFieldDefaultConfiguration?: Maybe<NewFieldDefaultConfiguration>;
viewId?: Maybe<Scalars['String']>;
};
@@ -2136,6 +2134,27 @@ export type MarketplaceAppRoleObjectPermission = {
objectUniversalIdentifier: Scalars['String'];
};
export type MetadataEvent = {
__typename?: 'MetadataEvent';
metadataName: Scalars['String'];
properties: ObjectRecordEventProperties;
recordId: Scalars['String'];
type: MetadataEventAction;
};
/** Metadata Event Action */
export enum MetadataEventAction {
CREATED = 'CREATED',
DELETED = 'DELETED',
UPDATED = 'UPDATED'
}
export type MetadataEventWithQueryIds = {
__typename?: 'MetadataEventWithQueryIds';
metadataEvent: MetadataEvent;
queryIds: Array<Scalars['String']>;
};
export enum ModelProvider {
ANTHROPIC = 'ANTHROPIC',
GROQ = 'GROQ',
@@ -2245,7 +2264,7 @@ export type Mutation = {
evaluateAgentTurn: AgentTurnEvaluation;
executeOneLogicFunction: LogicFunctionExecutionResult;
generateApiKeyToken: ApiKeyToken;
generateApplicationToken: AuthToken;
generateApplicationToken: ApplicationTokenPair;
generateTransientToken: TransientTokenOutput;
getAuthTokensFromLoginToken: AuthTokens;
getAuthTokensFromOTP: AuthTokens;
@@ -3229,6 +3248,7 @@ export type NavigationMenuItem = {
applicationId?: Maybe<Scalars['UUID']>;
createdAt: Scalars['DateTime'];
folderId?: Maybe<Scalars['UUID']>;
icon?: Maybe<Scalars['String']>;
id: Scalars['UUID'];
link?: Maybe<Scalars['String']>;
name?: Maybe<Scalars['String']>;
@@ -3241,6 +3261,12 @@ export type NavigationMenuItem = {
viewId?: Maybe<Scalars['UUID']>;
};
export type NewFieldDefaultConfiguration = {
__typename?: 'NewFieldDefaultConfiguration';
isVisible: Scalars['Boolean'];
viewFieldGroupId?: Maybe<Scalars['String']>;
};
export type NotesConfiguration = {
__typename?: 'NotesConfiguration';
configurationType: WidgetConfigurationType;
@@ -3370,6 +3396,12 @@ export type ObjectRecordEventProperties = {
updatedFields?: Maybe<Array<Scalars['String']>>;
};
export type ObjectRecordEventWithQueryIds = {
__typename?: 'ObjectRecordEventWithQueryIds';
objectRecordEvent: ObjectRecordEvent;
queryIds: Array<Scalars['String']>;
};
/** Date granularity options (e.g. DAY, MONTH, QUARTER, YEAR, WEEK, DAY_OF_THE_WEEK, MONTH_OF_THE_YEAR, QUARTER_OF_THE_YEAR) */
export enum ObjectRecordGroupByDateGranularity {
DAY = 'DAY',
@@ -3650,7 +3682,6 @@ export type Query = {
chatMessages: Array<AgentMessage>;
chatThread: AgentChatThread;
chatThreads: Array<AgentChatThread>;
checkApplicationExist: Scalars['Boolean'];
checkUserExists: CheckUserExistOutput;
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValidOutput;
commandMenuItem?: Maybe<CommandMenuItem>;
@@ -3761,12 +3792,6 @@ export type QueryChatThreadArgs = {
};
export type QueryCheckApplicationExistArgs = {
id?: InputMaybe<Scalars['UUID']>;
universalIdentifier?: InputMaybe<Scalars['UUID']>;
};
export type QueryCheckUserExistsArgs = {
captchaToken?: InputMaybe<Scalars['String']>;
email: Scalars['String'];
@@ -4555,9 +4580,10 @@ export type UpdateLogicFunctionFromSourceInputUpdates = {
export type UpdateNavigationMenuItemInput = {
folderId?: InputMaybe<Scalars['UUID']>;
icon?: InputMaybe<Scalars['String']>;
link?: InputMaybe<Scalars['String']>;
name?: InputMaybe<Scalars['String']>;
position?: InputMaybe<Scalars['Int']>;
position?: InputMaybe<Scalars['Float']>;
};
export type UpdateObjectPayload = {
@@ -5778,7 +5804,7 @@ export type FindOneFrontComponentQueryVariables = Exact<{
}>;
export type FindOneFrontComponentQuery = { __typename?: 'Query', frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, applicationId: string, applicationTokenPair?: { __typename?: 'ApplicationTokenPair', applicationAccessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, applicationRefreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } | null } | null };
export type FindOneFrontComponentQuery = { __typename?: 'Query', frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, applicationId: string, builtComponentChecksum: string, applicationTokenPair?: { __typename?: 'ApplicationTokenPair', applicationAccessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, applicationRefreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } | null } | null };
export type LogicFunctionFieldsFragment = { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string };
@@ -5843,42 +5869,42 @@ export type FindManyMarketplaceAppsQueryVariables = Exact<{ [key: string]: never
export type FindManyMarketplaceAppsQuery = { __typename?: 'Query', findManyMarketplaceApps: Array<{ __typename?: 'MarketplaceApp', id: string, name: string, description: string, icon: string, version: string, author: string, category: string, logo?: string | null, screenshots: Array<string>, aboutDescription: string, providers: Array<string>, websiteUrl?: string | null, termsUrl?: string | null, objects: Array<{ __typename?: 'MarketplaceAppObject', universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, fields: Array<{ __typename?: 'MarketplaceAppField', universalIdentifier?: string | null, name: string, type: string, label: string, description?: string | null, icon?: string | null }> }>, fields: Array<{ __typename?: 'MarketplaceAppField', name: string, type: string, label: string, description?: string | null, icon?: string | null, objectUniversalIdentifier?: string | null }>, logicFunctions: Array<{ __typename?: 'MarketplaceAppLogicFunction', name: string, description?: string | null, timeoutSeconds?: number | null }>, frontComponents: Array<{ __typename?: 'MarketplaceAppFrontComponent', name: string, description?: string | null }>, defaultRole?: { __typename?: 'MarketplaceAppDefaultRole', id: string, label: string, description?: string | null, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canUpdateAllSettings: boolean, canAccessAllTools: boolean, permissionFlags: Array<string>, objectPermissions: Array<{ __typename?: 'MarketplaceAppRoleObjectPermission', objectUniversalIdentifier: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }>, fieldPermissions: Array<{ __typename?: 'MarketplaceAppRoleFieldPermission', objectUniversalIdentifier: string, fieldUniversalIdentifier: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null }> } | null }> };
export type NavigationMenuItemFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string };
export type NavigationMenuItemFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string };
export type NavigationMenuItemQueryFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null };
export type NavigationMenuItemQueryFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null };
export type CreateNavigationMenuItemMutationVariables = Exact<{
input: CreateNavigationMenuItemInput;
}>;
export type CreateNavigationMenuItemMutation = { __typename?: 'Mutation', createNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type CreateNavigationMenuItemMutation = { __typename?: 'Mutation', createNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type DeleteNavigationMenuItemMutationVariables = Exact<{
id: Scalars['UUID'];
}>;
export type DeleteNavigationMenuItemMutation = { __typename?: 'Mutation', deleteNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type DeleteNavigationMenuItemMutation = { __typename?: 'Mutation', deleteNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type UpdateNavigationMenuItemMutationVariables = Exact<{
input: UpdateOneNavigationMenuItemInput;
}>;
export type UpdateNavigationMenuItemMutation = { __typename?: 'Mutation', updateNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type UpdateNavigationMenuItemMutation = { __typename?: 'Mutation', updateNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type FindManyNavigationMenuItemsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindManyNavigationMenuItemsQuery = { __typename?: 'Query', navigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> };
export type FindManyNavigationMenuItemsQuery = { __typename?: 'Query', navigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> };
export type FindOneNavigationMenuItemQueryVariables = Exact<{
id: Scalars['UUID'];
}>;
export type FindOneNavigationMenuItemQuery = { __typename?: 'Query', navigationMenuItem?: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null } | null };
export type FindOneNavigationMenuItemQuery = { __typename?: 'Query', navigationMenuItem?: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null } | null };
export type ObjectMetadataFieldsFragment = { __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> };
@@ -7368,6 +7394,7 @@ export const NavigationMenuItemFieldsFragmentDoc = gql`
folderId
name
link
icon
position
applicationId
createdAt
@@ -10565,6 +10592,7 @@ export const FindOneFrontComponentDocument = gql`
id
name
applicationId
builtComponentChecksum
applicationTokenPair {
applicationAccessToken {
token
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Kalender aansig"
msgid "Calendars"
msgstr "Kalenders"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Verander nodustipe"
msgid "Change Password"
msgstr "Verander Wagwoord"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Verander Plan"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Tel unieke waardes"
msgid "Country"
msgstr "Land"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Landkode"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Geen beskikbare velde om te kies nie"
msgid "No body"
msgstr "Geen inhoud"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Geen konteks is vir hierdie versoek voorsien nie"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Geen land nie"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organisasie"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Privaatheidsbeleid"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "عرض التقويم"
msgid "Calendars"
msgstr "التقاويم"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "تغيير نوع العقدة"
msgid "Change Password"
msgstr "تغيير كلمة السر"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "تغيير الخطة"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "عدّ القيم الفريدة"
msgid "Country"
msgstr "البلد"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "رمز البلد"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "لا توجد حقول متاحة للاختيار"
msgid "No body"
msgstr "لا يوجد متن"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "لم يتم توفير سياق لهذا الطلب"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "لا دولة"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "المؤسسة"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "\\\\"
msgid "Pro"
msgstr "محترف"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Vista de calendari"
msgid "Calendars"
msgstr "Calendaris"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Canvia el tipus de node"
msgid "Change Password"
msgstr "Canvia la contrasenya"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Canviar Pla"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Compta valors únics"
msgid "Country"
msgstr "País"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Codi de país"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "No hi ha camps disponibles per seleccionar"
msgid "No body"
msgstr "Sense cos"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "No s'ha proporcionat cap context per a aquesta sol·licitud"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Sense país"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organització"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Política de Privacitat"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Kalendářní zobrazení"
msgid "Calendars"
msgstr "Kalendáře"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Změnit typ uzlu"
msgid "Change Password"
msgstr "Změnit heslo"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Změnit plán"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Počet unikátních hodnot"
msgid "Country"
msgstr "Země"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Kód země"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Žádná dostupná pole k výběru"
msgid "No body"
msgstr "Žádné tělo"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Pro tento požadavek nebyl poskytnut žádný kontext"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Žádná země"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organizace"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Zásady ochrany osobních údajů"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Kalendervisning"
msgid "Calendars"
msgstr "Kalendere"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Skift nodetype"
msgid "Change Password"
msgstr "Skift kodeord"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Ændre Pakke"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Tæl unikke værdier"
msgid "Country"
msgstr "Land"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Landekode"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Ingen tilgængelige felter for at vælge"
msgid "No body"
msgstr "Ingen body"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Der blev ikke angivet nogen kontekst for denne anmodning"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Intet land"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organisation"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Privatlivspolitik"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Kalenderansicht"
msgid "Calendars"
msgstr "Kalender"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Knotentyp ändern"
msgid "Change Password"
msgstr "Passwort ändern"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Plan ändern"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Eindeutige Werte zählen"
msgid "Country"
msgstr "Land"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Ländercode"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Keine verfügbaren Felder zur Auswahl"
msgid "No body"
msgstr "Kein Body"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Für diese Anfrage wurde kein Kontext bereitgestellt"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Kein Land"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organisation"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Datenschutzrichtlinie"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Προβολή Ημερολογίου"
msgid "Calendars"
msgstr "Ημερολόγια"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Αλλαγή τύπου κόμβου"
msgid "Change Password"
msgstr "Αλλαγή Κωδικού"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Αλλαγή σχεδίου"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Καταμέτρηση μοναδικών τιμών"
msgid "Country"
msgstr "Χώρα"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Κωδικός χώρας"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Δεν υπάρχουν διαθέσιμα πεδία για επιλο
msgid "No body"
msgstr "Χωρίς σώμα"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Δεν παρέχεται πλαίσιο για αυτό το αίτημ
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Χωρίς χώρα"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Οργανωτικός"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Πολιτική Απορρήτου"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2372,6 +2372,11 @@ msgstr "Calendar View"
msgid "Calendars"
msgstr "Calendars"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr "Calling Code"
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2508,11 +2513,6 @@ msgstr "Change node type"
msgid "Change Password"
msgstr "Change Password"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Change Plan"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3315,11 +3315,6 @@ msgstr "Count unique values"
msgid "Country"
msgstr "Country"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Country Code"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8696,6 +8691,11 @@ msgstr "No available fields to select"
msgid "No body"
msgstr "No body"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr "No calling code"
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8726,7 +8726,6 @@ msgstr "No context was provided for this request"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "No country"
@@ -9548,6 +9547,11 @@ msgstr "Ordered List"
msgid "Organization"
msgstr "Organization"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr "Organization plan"
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10108,6 +10112,11 @@ msgstr "Privacy Policy"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr "Pro plan"
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Vista del calendario"
msgid "Calendars"
msgstr "Calendarios"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Cambiar tipo de nodo"
msgid "Change Password"
msgstr "Cambiar contraseña"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Cambiar plan"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Contar valores únicos"
msgid "Country"
msgstr "País"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Código de país"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "No hay campos disponibles para seleccionar"
msgid "No body"
msgstr "Sin cuerpo"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "No se proporcionó contexto para esta solicitud"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Sin país"
@@ -9553,6 +9552,11 @@ msgstr "Lista ordenada"
msgid "Organization"
msgstr "Organización"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Política de privacidad"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Kalenterinäkymä"
msgid "Calendars"
msgstr "Kalenterit"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Vaihda solmun tyyppi"
msgid "Change Password"
msgstr "Vaihda salasana"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Muuta suunnitelmaa"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Laske yksilölliset arvot"
msgid "Country"
msgstr "Maa"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Maakoodi"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Ei valittavissa olevia kenttiä"
msgid "No body"
msgstr "Ei viestirunkoa"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Tälle pyynnölle ei annettu kontekstia"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Ei maata"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organisaatio"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Tietosuojakäytäntö"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Vue Calendrier"
msgid "Calendars"
msgstr "Calendriers"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Changer le type de nœud"
msgid "Change Password"
msgstr "Changer le mot de passe"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Changer de plan"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Compter les valeurs uniques"
msgid "Country"
msgstr "Pays"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Code du pays"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Aucun champ disponible à sélectionner"
msgid "No body"
msgstr "Aucun corps"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Aucun contexte n'a été fourni pour cette requête"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Aucun pays"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organisation"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Politique de confidentialité"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
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
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "תצוגת לוח שנה"
msgid "Calendars"
msgstr "לוחות שנה"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "שנה סוג צומת"
msgid "Change Password"
msgstr "שנה סיסמה"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "שנה תוכנית"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "ספור ערכים ייחודיים"
msgid "Country"
msgstr "מדינה"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "קוד מדינה"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "אין שדות זמינים לבחירה"
msgid "No body"
msgstr "ללא גוף"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "לא סופק הקשר לבקשה זו"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "אין מדינה"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "ארגון"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "מדיניות הפרטיות"
msgid "Pro"
msgstr "מקצועי"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Naptár nézet"
msgid "Calendars"
msgstr "Naptárak"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Csere csomópont típus"
msgid "Change Password"
msgstr "Jelszó megváltoztatása"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Terv módosítása"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Egyedi értékek számlálása"
msgid "Country"
msgstr "Ország"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Országkód"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Nincsenek kiválasztható mezők"
msgid "No body"
msgstr "Nincs törzs"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Ehhez a kéréshez nem lett kontextus megadva"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Nincs ország"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Szervezet"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Adatvédelmi irányelvek"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Vista Calendario"
msgid "Calendars"
msgstr "Calendari"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Cambia il tipo di nodo"
msgid "Change Password"
msgstr "Cambia password"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Cambia piano"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Conta valori unici"
msgid "Country"
msgstr "Paese"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Prefisso internazionale"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Nessun campo disponibile da selezionare"
msgid "No body"
msgstr "Nessun corpo"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Nessun contesto è stato fornito per questa richiesta"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Nessun paese"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organizzazione"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Informativa sulla privacy"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "カレンダー表示"
msgid "Calendars"
msgstr "カレンダー"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "ノードの種類を変更"
msgid "Change Password"
msgstr "パスワードを変更"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "プランを変更"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "一意の値をカウント"
msgid "Country"
msgstr "国"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "国コード"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "選択可能なフィールドがありません"
msgid "No body"
msgstr "本文なし"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "このリクエストにはコンテキストが提供されていませ
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "国なし"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "組織"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "プライバシーポリシー"
msgid "Pro"
msgstr "プロ"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "캘린더 보기"
msgid "Calendars"
msgstr "캘린더"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "노드 유형 변경"
msgid "Change Password"
msgstr "비밀번호 변경"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "요금제 변경"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "고유 값 개수"
msgid "Country"
msgstr "국가"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "국가 코드"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "선택할 수 있는 필드가 없습니다."
msgid "No body"
msgstr "본문 없음"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "이 요청에 대한 컨텍스트가 제공되지 않았습니다"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "국가 없음"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "조직"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "개인정보 보호정책"
msgid "Pro"
msgstr "프로"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Kalenderweergave"
msgid "Calendars"
msgstr "Kalenders"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Wijzig node-type"
msgid "Change Password"
msgstr "Wachtwoord wijzigen"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Plan wijzigen"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Unieke waarden tellen"
msgid "Country"
msgstr "Land"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Landcode"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Geen beschikbare velden om te selecteren"
msgid "No body"
msgstr "Geen body"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Er is geen context opgegeven voor dit verzoek"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Geen land"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organisatie"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Privacybeleid"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Kalendervisning"
msgid "Calendars"
msgstr "Kalendere"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Endre nodetype"
msgid "Change Password"
msgstr "Endre passord"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Endre plan"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Tell unike verdier"
msgid "Country"
msgstr "Land"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Landskode"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Ingen tilgjengelige felter å velge"
msgid "No body"
msgstr "Ingen body"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Ingen kontekst ble oppgitt for denne forespørselen"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Intet land"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organisasjon"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Personvernpolicy"
msgid "Pro"
msgstr "Proff"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Widok Kalendarza"
msgid "Calendars"
msgstr "Kalendarze"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Zmień typ węzła"
msgid "Change Password"
msgstr "Zmień hasło"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Zmień plan"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Policz unikalne wartości"
msgid "Country"
msgstr "Kraj"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Kod kraju"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Brak dostępnych pól do wyboru"
msgid "No body"
msgstr "Brak treści"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Nie podano kontekstu dla tego żądania"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Brak kraju"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organizacja"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Polityka prywatności"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2372,6 +2372,11 @@ msgstr ""
msgid "Calendars"
msgstr ""
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2508,11 +2513,6 @@ msgstr ""
msgid "Change Password"
msgstr ""
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr ""
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3315,11 +3315,6 @@ msgstr ""
msgid "Country"
msgstr ""
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr ""
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8696,6 +8691,11 @@ msgstr ""
msgid "No body"
msgstr ""
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8726,7 +8726,6 @@ msgstr ""
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr ""
@@ -9548,6 +9547,11 @@ msgstr ""
msgid "Organization"
msgstr ""
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10108,6 +10112,11 @@ msgstr ""
msgid "Pro"
msgstr ""
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Visão de calendário"
msgid "Calendars"
msgstr "Calendários"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Alterar tipo de nó"
msgid "Change Password"
msgstr "Alterar Senha"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Alterar Plano"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Contar valores únicos"
msgid "Country"
msgstr "País"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Código do país"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Nenhum campo disponível para selecionar"
msgid "No body"
msgstr "Sem corpo"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Nenhum contexto foi fornecido para esta solicitação"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Sem país"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organização"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Política de privacidade"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Visão do Calendário"
msgid "Calendars"
msgstr "Calendários"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Alterar tipo de nó"
msgid "Change Password"
msgstr "Alterar palavra-passe"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Mudar Plano"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Contar valores únicos"
msgid "Country"
msgstr "País"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Código do País"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Sem campos disponíveis para selecionar"
msgid "No body"
msgstr "Sem corpo"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Não foi fornecido contexto para este pedido"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Sem país"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organização"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Política de Privacidade"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Vizualizare calendar"
msgid "Calendars"
msgstr "Calendare"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Schimbă tipul nodului"
msgid "Change Password"
msgstr "Schimbă parola"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Schimbă planul"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Numără valorile unice"
msgid "Country"
msgstr "Țară"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Codul țării"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Nu există câmpuri disponibile de selectat"
msgid "No body"
msgstr "Fără Body"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Nu a fost furnizat niciun context pentru această cerere"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Fără țară"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Organizație"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Politica de confidențialitate"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
Binary file not shown.
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Преглед календара"
msgid "Calendars"
msgstr "Календари"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Промените тип чвора"
msgid "Change Password"
msgstr "Промени лозинку"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Промени план"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Број јединствених вредности"
msgid "Country"
msgstr "Земља"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Код земље"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Нема доступних поља за избор"
msgid "No body"
msgstr "Нема тела"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Контекст није обезбеђен за овај захтев"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Нема земље"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Организација"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Политика приватности"
msgid "Pro"
msgstr "Про"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Kalendervy"
msgid "Calendars"
msgstr "Kalendrar"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Ändra nodtyp"
msgid "Change Password"
msgstr "Byt Lösenord"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Ändra plan"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Räkna unika värden"
msgid "Country"
msgstr "Land"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Landskod"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8703,6 +8698,11 @@ msgstr "Inga tillgängliga fält att välja"
msgid "No body"
msgstr "Ingen body"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8733,7 +8733,6 @@ msgstr "Ingen kontext angavs för denna begäran"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Inget land"
@@ -9555,6 +9554,11 @@ msgstr ""
msgid "Organization"
msgstr "Organisation"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10115,6 +10119,11 @@ msgstr "Integritetspolicy"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Takvim Görünümü"
msgid "Calendars"
msgstr "Takvimler"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Düğüm türünü değiştir"
msgid "Change Password"
msgstr "Şifre Değiştir"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Planı Değiştir"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Benzersiz değerleri say"
msgid "Country"
msgstr "Ülke"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Ülke Kodu"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Seçilecek uygun alan yok"
msgid "No body"
msgstr "Gövde yok"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Bu istek için bağlam sağlanmadı"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Ülke yok"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Kuruluş"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Gizlilik Politikası"
msgid "Pro"
msgstr "Pro"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Перегляд календаря"
msgid "Calendars"
msgstr "Календарі"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Змінити тип вузла"
msgid "Change Password"
msgstr "Змінити пароль"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Змінити план"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Підрахунок унікальних значень"
msgid "Country"
msgstr "Країна"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Код країни"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Немає доступних полів для вибору"
msgid "No body"
msgstr "Без тіла"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Для цього запиту не надано контексту"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Немає країни"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Організація"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Політика конфіденційності"
msgid "Pro"
msgstr "Професіонал"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "Xem lịch"
msgid "Calendars"
msgstr "Lịch công tác"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "Thay đổi loại nút"
msgid "Change Password"
msgstr "Đổi Mật khẩu"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "Thay đổi kế hoạch"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "Số lượng giá trị duy nhất"
msgid "Country"
msgstr "Quốc gia"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "Mã quốc gia"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "Không có trường nào khả dụng để chọn"
msgid "No body"
msgstr "Không có phần thân"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "Không có ngữ cảnh nào được cung cấp cho yêu cầu này"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "Không có quốc gia"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "Tổ chức"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "Chính sách Bảo mật"
msgid "Pro"
msgstr "Chuyên nghiệp"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "日历视图"
msgid "Calendars"
msgstr "日历"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "更改节点类型"
msgid "Change Password"
msgstr "更改密码"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "更改计划"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "计算唯一值"
msgid "Country"
msgstr "国家"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "国家代码"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "没有可选的字段"
msgid "No body"
msgstr "无正文"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "此请求未提供上下文"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "无国家"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "组织"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "隐私政策"
msgid "Pro"
msgstr "专业版"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+20 -11
View File
@@ -2377,6 +2377,11 @@ msgstr "日曆檢視"
msgid "Calendars"
msgstr "日曆"
#. js-lingui-id: qIlodj
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Calling Code"
msgstr ""
#. js-lingui-id: msssZq
#: src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx
msgid "Can't change API names for standard objects"
@@ -2513,11 +2518,6 @@ msgstr "更改節點類型"
msgid "Change Password"
msgstr "更改密碼"
#. js-lingui-id: wRtBJP
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Change Plan"
msgstr "更改計劃"
#. js-lingui-id: EYSFEW
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "Change subdomain?"
@@ -3320,11 +3320,6 @@ msgstr "計數唯一值"
msgid "Country"
msgstr "國家"
#. js-lingui-id: j2OqfX
#: src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx
msgid "Country Code"
msgstr "國碼"
#. js-lingui-id: gJdfqX
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "create"
@@ -8701,6 +8696,11 @@ msgstr "沒有可選擇的欄位"
msgid "No body"
msgstr "沒有主體"
#. js-lingui-id: c3+z7H
#: src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx
msgid "No calling code"
msgstr ""
#. js-lingui-id: OTe3RI
#: src/pages/settings/domains/SettingsDomain.tsx
msgid "No change detected"
@@ -8731,7 +8731,6 @@ msgstr "此請求未提供任何上下文"
#: src/modules/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm.tsx
#: src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountrySelectInput.tsx
#: src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx
msgid "No country"
msgstr "無國家"
@@ -9553,6 +9552,11 @@ msgstr ""
msgid "Organization"
msgstr "組織"
#. js-lingui-id: zi/p7n
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Organization plan"
msgstr ""
#. js-lingui-id: nV6twc
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions.tsx
msgid "Organize"
@@ -10113,6 +10117,11 @@ msgstr "隱私政策"
msgid "Pro"
msgstr "專業版"
#. js-lingui-id: r5je1s
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
msgid "Pro plan"
msgstr ""
#. js-lingui-id: k1ifdL
#: src/modules/workflow/components/WorkflowStepExecutionResult.tsx
#: src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
@@ -2,24 +2,16 @@ import { Action } from '@/action-menu/actions/components/Action';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
import { useCreateNavigationMenuItem } from '@/navigation-menu-item/hooks/useCreateNavigationMenuItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const AddToFavoritesSingleRecordAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const recordId = useSelectedRecordIdOrThrow();
const isNavigationMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED,
);
const { createFavorite } = useCreateFavorite();
const { createNavigationMenuItem } = useCreateNavigationMenuItem();
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
@@ -28,11 +20,7 @@ export const AddToFavoritesSingleRecordAction = () => {
return;
}
if (isNavigationMenuItemEnabled) {
createNavigationMenuItem(selectedRecord, objectMetadataItem.nameSingular);
} else {
createFavorite(selectedRecord, objectMetadataItem.nameSingular);
}
createFavorite(selectedRecord, objectMetadataItem.nameSingular);
};
return <Action onClick={handleClick} />;
@@ -8,9 +8,7 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const DeleteSingleRecordAction = () => {
const { recordIndexId, objectMetadataItem } =
@@ -29,9 +27,7 @@ export const DeleteSingleRecordAction = () => {
const { sortedFavorites: favorites } = useFavorites();
const { deleteFavorite } = useDeleteFavorite();
const isNavigationMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED,
);
const { navigationMenuItems, workspaceNavigationMenuItems } =
usePrefetchedNavigationMenuItemsData();
const { removeNavigationMenuItemsByTargetRecordIds } =
@@ -50,15 +46,13 @@ export const DeleteSingleRecordAction = () => {
deleteFavorite(foundFavorite.id);
}
if (isNavigationMenuItemEnabled) {
const foundNavigationMenuItem = [
...navigationMenuItems,
...workspaceNavigationMenuItems,
].find((item) => item.targetRecordId === recordId);
const foundNavigationMenuItem = [
...navigationMenuItems,
...workspaceNavigationMenuItems,
].find((item) => item.targetRecordId === recordId);
if (isDefined(foundNavigationMenuItem)) {
removeNavigationMenuItemsByTargetRecordIds([recordId]);
}
if (isDefined(foundNavigationMenuItem)) {
removeNavigationMenuItemsByTargetRecordIds([recordId]);
}
await deleteOneRecord(recordId);
@@ -5,9 +5,7 @@ import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItem';
import { usePrefetchedNavigationMenuItemsData } from '@/navigation-menu-item/hooks/usePrefetchedNavigationMenuItemsData';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const RemoveFromFavoritesSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
@@ -16,9 +14,6 @@ export const RemoveFromFavoritesSingleRecordAction = () => {
const { sortedFavorites: favorites } = useFavorites();
const { navigationMenuItems, workspaceNavigationMenuItems } =
usePrefetchedNavigationMenuItemsData();
const isNavigationMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED,
);
const { deleteFavorite } = useDeleteFavorite();
const { deleteNavigationMenuItem } = useDeleteNavigationMenuItem();
@@ -27,28 +22,21 @@ export const RemoveFromFavoritesSingleRecordAction = () => {
(favorite) => favorite.recordId === recordId,
);
const foundNavigationMenuItem = isNavigationMenuItemEnabled
? [...navigationMenuItems, ...workspaceNavigationMenuItems].find(
(item) =>
item.targetRecordId === recordId &&
item.targetObjectMetadataId === objectMetadataItem.id,
)
: undefined;
const foundNavigationMenuItem = [
...navigationMenuItems,
...workspaceNavigationMenuItems,
].find(
(item) =>
item.targetRecordId === recordId &&
item.targetObjectMetadataId === objectMetadataItem.id,
);
const handleClick = () => {
if (isNavigationMenuItemEnabled) {
if (!isDefined(foundNavigationMenuItem)) {
return;
}
deleteNavigationMenuItem(foundNavigationMenuItem.id);
return;
}
if (!isDefined(foundFavorite)) {
if (!isDefined(foundNavigationMenuItem) || !isDefined(foundFavorite)) {
return;
}
deleteNavigationMenuItem(foundNavigationMenuItem.id);
deleteFavorite(foundFavorite.id);
};
@@ -6,7 +6,6 @@ import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
import { isRecordReadOnly } from '@/object-record/read-only/utils/isRecordReadOnly';
import { msg } from '@lingui/core/macro';
import React from 'react';
@@ -36,7 +35,7 @@ export const useRelatedRecordActions = ({
(field) =>
field.type === 'RELATION' &&
field.relation?.type === 'ONE_TO_MANY' &&
!isHiddenSystemField(field),
!field.isSystem,
);
let currentPosition = startPosition;
@@ -29,8 +29,8 @@ export const useShouldActionBeRegisteredParams = ({
}): ShouldBeRegisteredFunctionParams => {
const { sortedFavorites: favorites } = useFavorites();
const { navigationMenuItems } = usePrefetchedNavigationMenuItemsData();
const isNavigationMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED,
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
);
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
@@ -47,7 +47,7 @@ export const useShouldActionBeRegisteredParams = ({
return false;
}
if (isNavigationMenuItemEnabled && isDefined(objectMetadataItem)) {
if (isNavigationMenuItemEditingEnabled && isDefined(objectMetadataItem)) {
const foundNavigationMenuItem = navigationMenuItems?.find(
(item) =>
item.targetRecordId === recordId &&
@@ -62,7 +62,7 @@ export const useShouldActionBeRegisteredParams = ({
return !!foundFavorite;
}, [
recordId,
isNavigationMenuItemEnabled,
isNavigationMenuItemEditingEnabled,
objectMetadataItem,
navigationMenuItems,
favorites,
@@ -22,8 +22,8 @@ import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFla
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { isNavigationModifierPressed } from 'twenty-ui/utilities';
import {
PermissionFlagType,
FeatureFlagKey,
PermissionFlagType,
} from '~/generated-metadata/graphql';
import { formatToHumanReadableDate } from '~/utils/date-utils';
import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension';
@@ -96,7 +96,11 @@ export const AttachmentRow = ({
);
const { name: originalFileName, extension: attachmentFileExtension } =
getFileNameAndExtension(attachment.name);
getFileNameAndExtension(
isFilesFieldMigrated
? (attachment.file?.[0]?.label as string)
: attachment.name,
);
const [attachmentFileName, setAttachmentFileName] =
useState(originalFileName);
@@ -206,7 +210,9 @@ export const AttachmentRow = ({
target="_blank"
rel="noopener noreferrer"
>
<OverflowingTextWithTooltip text={attachment.name} />
<OverflowingTextWithTooltip
text={`${attachmentFileName}${attachmentFileExtension}`}
/>
</StyledLink>
</StyledLinkContainer>
)}
@@ -1,8 +1,7 @@
import { useRecoilValue } from 'recoil';
import { usePrepareFindManyActivitiesQuery } from '@/activities/hooks/usePrepareFindManyActivitiesQuery';
import { objectShowPageTargetableObjectState } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectIdState';
import { objectShowPageTargetableObjectStateV2 } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectStateV2';
import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { isDefined } from 'twenty-shared/utils';
// This hook should only be executed if the normalized cache is up-to-date
@@ -13,8 +12,8 @@ export const useRefreshShowPageFindManyActivitiesQueries = ({
}: {
activityObjectNameSingular: CoreObjectNameSingular;
}) => {
const objectShowPageTargetableObject = useRecoilValue(
objectShowPageTargetableObjectState,
const objectShowPageTargetableObject = useRecoilValueV2(
objectShowPageTargetableObjectStateV2,
);
const { prepareFindManyActivitiesQuery } = usePrepareFindManyActivitiesQuery({
@@ -1,16 +1,17 @@
import { useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilState } from 'recoil';
import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB';
import { useRefreshShowPageFindManyActivitiesQueries } from '@/activities/hooks/useRefreshShowPageFindManyActivitiesQueries';
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
import { objectShowPageTargetableObjectState } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectIdState';
import { objectShowPageTargetableObjectStateV2 } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectStateV2';
import { type Note } from '@/activities/types/Note';
import { type Task } from '@/activities/types/Task';
import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { isDefined } from 'twenty-shared/utils';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
export const useUpsertActivity = ({
activityObjectNameSingular,
@@ -33,8 +34,8 @@ export const useUpsertActivity = ({
isUpsertingActivityInDBState,
);
const objectShowPageTargetableObject = useRecoilValue(
objectShowPageTargetableObjectState,
const objectShowPageTargetableObject = useRecoilValueV2(
objectShowPageTargetableObjectStateV2,
);
const { refreshShowPageFindManyActivitiesQueries } =
@@ -1,8 +1,7 @@
import { useEffect, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useActivities } from '@/activities/hooks/useActivities';
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
import { currentNotesQueryVariablesStateV2 } from '@/activities/notes/states/currentNotesQueryVariablesStateV2';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline-activities/constants/FindManyTimelineActivitiesOrderBy';
import { type Note } from '@/activities/types/Note';
import { type RecordGqlOperationVariables } from 'twenty-shared/types';
@@ -10,6 +9,7 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
export const useNotes = (targetableObject: ActivityTargetableObject) => {
const notesQueryVariables = useMemo(
@@ -34,7 +34,7 @@ export const useNotes = (targetableObject: ActivityTargetableObject) => {
});
const [currentNotesQueryVariables, setCurrentNotesQueryVariables] =
useRecoilState(currentNotesQueryVariablesState);
useRecoilStateV2(currentNotesQueryVariablesStateV2);
// TODO: fix useEffect, remove with better pattern
useEffect(() => {
@@ -1,9 +0,0 @@
import { atom } from 'recoil';
import { type RecordGqlOperationVariables } from 'twenty-shared/types';
export const currentNotesQueryVariablesState =
atom<RecordGqlOperationVariables | null>({
default: null,
key: 'currentNotesQueryVariablesState',
});

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