Compare commits

...
Author SHA1 Message Date
Devessier 2696cbd1f3 test: wip 2026-04-16 18:51:58 +02:00
Abdul RahmanandGitHub a49d8386aa Fix calendar event "Not shared" content alignment and background (#19743)
Issue link:
https://discord.com/channels/1130383047699738754/1491712714848866424

<img width="1287" height="419" alt="Screenshot 2026-04-16 at 9 50 34 AM"
src="https://github.com/user-attachments/assets/ba2143df-7957-4aee-8994-80c0392b6086"
/>
2026-04-16 08:38:09 +00:00
Charles BochetandGitHub e420ee8746 Release v1.22.0 for twenty-sdk, twenty-client-sdk, and create-twenty-app (#19751)
## Summary
- Bump `twenty-sdk` from `1.22.0-canary.6` to `1.22.0`
- Bump `twenty-client-sdk` from `1.22.0-canary.6` to `1.22.0`
- Bump `create-twenty-app` from `1.22.0-canary.6` to `1.22.0`
2026-04-16 08:29:50 +00:00
Thomas TrompetteandGitHub 7adab8884b Add isUnique update in query + invalidate cache on rollback (#19746)
Fix isUnique not sent in field update mutation: Added isUnique and
settings to the Pick type in useUpdateOneFieldMetadataItem, which
previously silently dropped these properties from the GraphQL payload.

Invalidate cache on failed migration rollback: Added invalidateCache()
call in the migration runner's catch block so that a failed migration
(e.g., unique index creation failing due to duplicate data) regenerates
the Redis hash, preventing stale metadata from persisting in frontend
localStorage indefinitely. Was
2026-04-16 07:44:59 +00:00
6101a4f113 Update website hero to use shared local avatars and logos (#19736)
## Summary
- import shared company logos and people avatars into
`packages/twenty-website-new/public/images/shared`
- expand the shared asset registry with the new local logo and avatar
paths
- update the home hero data to use local shared assets for matched
people and company icons
- replace synthetic or mismatched hero avatars with better-fitting named
or anonymous local assets
- prefer local shared company logos in hero visual components before
falling back to remote icons

## Testing
- Not run (not requested)

---------

Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com>
2026-04-16 06:58:48 +00:00
Thomas des FrancsandGitHub da8fe7f6f7 Homepage 3 cards finished (#19732)
& various fixes
2026-04-16 06:42:25 +00:00
cddc47b61f i18n - translations (#19731)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-15 19:08:24 +02:00
Charles BochetandGitHub 446c39d1c0 Fix silent failures in logic function route trigger execution (#19698)
## Summary

Route-triggered logic functions were returning empty 500 responses with
zero server-side logging when the Lambda build chain failed. This PR
makes those failures observable and returns meaningful HTTP responses to
API clients.

- **Observability** — Log errors (with stack traces) at each layer of
the execution chain: `LambdaDriver` (deps-layer fetch, SDK-layer fetch,
invocation), `LogicFunctionExecutorService`, and `RouteTriggerService`.
- **Typed exceptions** — Replace raw `throw error` sites with
`LogicFunctionException` carrying an appropriate code and
`userFriendlyMessage` (new codes: `LOGIC_FUNCTION_EXECUTION_FAILED`,
`LOGIC_FUNCTION_LAYER_BUILD_FAILED`).
- **Correct HTTP semantics** — `RouteTriggerService` maps inner
exception codes to the right `RouteTriggerExceptionCode` so
`LOGIC_FUNCTION_NOT_FOUND` returns 404 and `RATE_LIMIT_EXCEEDED` returns
429 (new code + filter case) instead of a generic 500.
- **User-facing messages** — Forward the inner
`CustomException.userFriendlyMessage` when wrapping into
`RouteTriggerException`, without leaking raw internal error text into
the public exception message.
- **Infra** — Bump Lambda ephemeral storage from 2048 to 4096 MB to
prevent `ENOSPC` errors during yarn install layer builds (root cause of
the original silent failures).
2026-04-15 16:49:43 +00:00
5eda10760c Fix navbar folder opening lag by deferring navigation until expand animation completes (#19686)
### Before


https://github.com/user-attachments/assets/7d57aa55-8d4c-43e1-8835-f40206e5e453



### After


https://github.com/user-attachments/assets/2457c8ee-fcb9-41ae-a8f7-08f8c7b4a233

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-04-15 16:31:33 +00:00
b9605a3003 Refactor SnackBar duration handling and progress bar visibility logic (#19712)
## Summary

Fixes error snackbars disappearing too quickly by **disabling
auto-dismiss for error variants by default**. Error snackbars now remain
visible until the user closes them.

Closes #19694

## What changed

- Error snackbars no longer default to a 6s timeout (they only
auto-dismiss if an explicit `duration` is provided).
- Non-error snackbars keep the existing default auto-dismiss behavior
(6s).
- Progress bar animation/visibility is tied to auto-dismiss (no progress
bar when there’s no duration).

**Files**
-
packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx

## How to test

1. Trigger a long error snackbar (example: spreadsheet/CSV import error
with a long message).
2. Confirm the snackbar **stays visible** until clicking **Close**.
3. Trigger a success/info snackbar and confirm it **still
auto-dismisses** after ~6s.

## Notes

- Call sites that explicitly pass `options.duration` for error snackbars
will continue to auto-dismiss (intentional).

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-04-15 16:22:28 +00:00
WeikoandGitHub 56d6e13b5d Fix fields widget flash during reset (#19726)
Summary

- Remove the evictViewMetadataForViewIds step from the page-layout reset
flow. It synchronously cleared viewFields/viewFieldGroups rows from the
metadata store, leaving a window where
useFieldsWidgetGroups saw a view with no fields and fell back to
buildDefaultFieldsWidgetGroups, briefly rendering a synthetic "General"
+ "Other" layout before the real reset defaults
arrived.
- invalidateMetadataStore() alone is sufficient: it marks the
collections stale and triggers MinimalMetadataLoadEffect to refetch,
which replaces current atomically. The UI now
transitions old-layout → new-default with no synthetic flash.
- Simplified refreshPageLayoutAfterReset to no longer take a
collectAffectedViewIds callback, and updated both tab/widget reset call
sites plus ObjectLayout.tsx accordingly.
- Deleted the now-unused evictViewMetadataForViewIds and
collectViewIdsFromWidgets utils.
2026-04-15 16:08:24 +00:00
725171bfd3 i18n - translations (#19729)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-15 18:11:26 +02:00
Raphaël BosiandGitHub 3b85747d3f Go back to the original command K button (#19727)
## Before


https://github.com/user-attachments/assets/868e5293-8843-44c8-8011-b7130e97fa95


## After


https://github.com/user-attachments/assets/89cf46cc-1fb1-4e78-bfef-53e1d04e8ebf
2026-04-15 15:53:26 +00:00
Paul RastoinandGitHub a4cc7fb9c5 [Upgrade] Fix workspace creation cursor (#19701)
## Summary

### Problem

The upgrade migration system required new workspaces to always start
from a workspace command, which was too rigid. When the system was
mid-upgrade within an instance command (IC) segment, workspace creation
would fail or produce inconsistent state.

### Solution

#### Workspace-scoped instance command rows

Instance commands now write upgrade migration rows for **all
active/suspended workspaces** alongside the global row. This means every
workspace has a complete migration history, including instance command
records.

- `InstanceCommandRunnerService` reloads `activeOrSuspendedWorkspaceIds`
immediately before writing records (both success and failure paths) to
mitigate race conditions with concurrent workspace creation.
- `recordUpgradeMigration` in `UpgradeMigrationService` accepts a
discriminated union over `status`, handles `error: unknown` formatting
internally, and writes global + workspace rows in batch.

#### Flexible initial cursor for new workspaces

`getInitialCursorForNewWorkspace` now accepts the last **attempted**
(not just completed) instance command with its status:

- If the IC is `completed` and the next step is a workspace segment →
cursor is set to the last WC of that segment (existing behavior).
- If the IC is `failed` or not the last of its segment → cursor is set
to that IC itself, preserving its status.

This allows workspaces to be created at any point during the upgrade
lifecycle, including mid-IC-segment and after IC failure.

#### Relaxed workspace segment validation

`validateWorkspaceCursorsAreInWorkspaceSegment` accepts workspaces whose
cursor is:
1. Within the current workspace segment, OR
2. At the immediately preceding instance command with `completed` status
(handles the `-w` single-workspace upgrade scenario).

Workspaces with cursors in a previous segment, ahead of the current
segment, or at a preceding IC with `failed` status are rejected.

### Test plan
created empty workspaces to allow testing upgrade with several active
workspaces
2026-04-15 15:41:10 +00:00
Raphaël BosiandGitHub 0f8152f536 Fix command menu item edit record selection dropdown icons (#19725)
## Before
<img width="816" height="126" alt="CleanShot 2026-04-15 at 17 20 11@2x"
src="https://github.com/user-attachments/assets/647d6aa6-1000-41d9-9351-c1489bc095c6"
/>


## After
<img width="808" height="120" alt="CleanShot 2026-04-15 at 17 19 05@2x"
src="https://github.com/user-attachments/assets/e7685370-b6eb-4720-a5dd-401dfae8dc6b"
/>
2026-04-15 15:31:59 +00:00
256 changed files with 8102 additions and 1841 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "1.22.0-canary.6",
"version": "1.22.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-client-sdk",
"version": "1.22.0-canary.6",
"version": "1.22.0",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
@@ -3189,7 +3189,6 @@ msgstr "Knipbord vereis 'n veilige verbinding (HTTPS). Gebruik asseblief hierdie
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Maak toe"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Sluit banier"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Maak sypaneel toe"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Meer"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Maak Outlook oop"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Maak sypaneel oop"
@@ -3189,7 +3189,6 @@ msgstr "تتطلب الحافظة اتصالاً آمناً (HTTPS). يُرجى
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "إغلاق"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "إغلاق اللافتة"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "إغلاق اللوحة الجانبية"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "المزيد"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "فتح Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "فتح اللوحة الجانبية"
@@ -3189,7 +3189,6 @@ msgstr "El porta-retalls requereix una connexió segura (HTTPS). Accedeix a aque
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Tanca"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Tanca el bàner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Tanca el panell lateral"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Més"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Obrir en Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Obre el panell lateral"
@@ -3189,7 +3189,6 @@ msgstr "Schránka vyžaduje zabezpečené připojení (HTTPS). Pro povolení kop
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Zavřít"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Zavřít banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Zavřít postranní panel"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Více"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Otevřít Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Otevřít postranní panel"
@@ -3189,7 +3189,6 @@ msgstr "Udklipsholderen kræver en sikker forbindelse (HTTPS). Tilgå venligst d
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Luk"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Luk banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Luk sidepanelet"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Mere"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Åbn Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Åbn sidepanelet"
@@ -3189,7 +3189,6 @@ msgstr "Die Zwischenablage erfordert eine sichere Verbindung (HTTPS). Bitte grei
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Schließen"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Banner schließen"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Seitenpanel schließen"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Mehr"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Outlook öffnen"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Seitenpanel öffnen"
@@ -3189,7 +3189,6 @@ msgstr "Το Πρόχειρο απαιτεί ασφαλή σύνδεση (HTTPS)
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Κλείσιμο"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Κλείσιμο banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Κλείσιμο πλευρικού πάνελ"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Περισσότερα"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Άνοιγμα με Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Άνοιγμα πλευρικού πάνελ"
-4
View File
@@ -3184,7 +3184,6 @@ msgstr "Clipboard requires a secure connection (HTTPS). Please access this app o
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Close"
@@ -3195,7 +3194,6 @@ msgid "Close banner"
msgstr "Close banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Close side panel"
@@ -9276,7 +9274,6 @@ msgstr "months"
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "More"
@@ -10719,7 +10716,6 @@ msgid "Open Outlook"
msgstr "Open Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Open side panel"
@@ -3189,7 +3189,6 @@ msgstr "El portapapeles requiere una conexión segura (HTTPS). Accede a esta apl
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Cerrar"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Cerrar banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Cerrar panel lateral"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Más"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Abrir Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Abrir panel lateral"
@@ -3189,7 +3189,6 @@ msgstr "Leikepöytä edellyttää suojattua yhteyttä (HTTPS). Avaa tämä sovel
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Sulje"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Sulje banneri"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Sulje sivupaneeli"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Lisää"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Avaa Outlookissa"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Avaa sivupaneeli"
@@ -3189,7 +3189,6 @@ msgstr "Le presse-papiers nécessite une connexion sécurisée (HTTPS). Veuillez
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Fermer"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Fermer la bannière"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Fermer le panneau latéral"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Plus"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Ouvrir Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Ouvrir le panneau latéral"
@@ -3189,7 +3189,6 @@ msgstr "לוח הגזירים דורש חיבור מאובטח (HTTPS). יש ל
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "סגור"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "סגור באנר"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "סגור את חלונית הצד"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "עוד"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "פתח ב-Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "פתח את חלונית הצד"
@@ -3189,7 +3189,6 @@ msgstr ""
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Bezár"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Banner bezárása"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Oldalsó panel bezárása"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Több"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Outlook megnyitása"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Oldalsó panel megnyitása"
@@ -3189,7 +3189,6 @@ msgstr "Gli appunti richiedono una connessione sicura (HTTPS). Accedi a questa a
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Chiudi"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Chiudi banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Chiudi il pannello laterale"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Altro"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Aprire Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Apri il pannello laterale"
@@ -3189,7 +3189,6 @@ msgstr "クリップボードの使用にはセキュアな接続 (HTTPS) が必
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "閉じる"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "バナーを閉じる"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "サイドパネルを閉じる"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "もっと"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Outlookで開く"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "サイドパネルを開く"
@@ -3189,7 +3189,6 @@ msgstr "클립보드는 보안 연결(HTTPS)이 필요합니다. 복사 기능
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "닫기"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "배너 닫기"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "사이드 패널 닫기"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "더보기"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Outlook 열기"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "사이드 패널 열기"
@@ -3189,7 +3189,6 @@ msgstr ""
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Sluiten"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Banner sluiten"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Zijpaneel sluiten"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Meer"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Open Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Zijpaneel openen"
@@ -3189,7 +3189,6 @@ msgstr "Utklippstavlen krever en sikker tilkobling (HTTPS). Åpne denne appen ov
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Lukk"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Lukk banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Lukk sidepanelet"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Mer"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Åpne Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Åpne sidepanelet"
@@ -3189,7 +3189,6 @@ msgstr "Schowek wymaga bezpiecznego połączenia (HTTPS). Otwórz tę aplikację
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Zamknij"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Zamknij baner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Zamknij panel boczny"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Więcej"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Otwórz Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Otwórz panel boczny"
@@ -3184,7 +3184,6 @@ msgstr ""
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr ""
@@ -3195,7 +3194,6 @@ msgid "Close banner"
msgstr ""
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr ""
@@ -9276,7 +9274,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr ""
@@ -10719,7 +10716,6 @@ msgid "Open Outlook"
msgstr ""
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr ""
@@ -3189,7 +3189,6 @@ msgstr "A área de transferência requer uma conexão segura (HTTPS). Acesse est
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Fechar"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Fechar banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr ""
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Mais"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Abrir Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr ""
@@ -3189,7 +3189,6 @@ msgstr "A área de transferência requer uma conexão segura (HTTPS). Acesse est
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Fechar"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Fechar banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Fechar painel lateral"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Mais"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Abrir Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Abrir painel lateral"
@@ -3189,7 +3189,6 @@ msgstr "Clipboardul necesită o conexiune securizată (HTTPS). Accesați aceast
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Închide"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Închide bannerul"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Închide panoul lateral"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Mai mult"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Deschide Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Deschide panoul lateral"
Binary file not shown.
@@ -3189,7 +3189,6 @@ msgstr "Клипборд захтева безбедну везу (HTTPS). Пр
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Затвори"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Затвори банер"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Затвори бочни панел"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Још"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Отвори Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Отвори бочни панел"
@@ -3189,7 +3189,6 @@ msgstr "Urklipp kräver en säker anslutning (HTTPS). Öppna den här appen via
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Stäng"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Stäng banner"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Stäng sidopanelen"
@@ -9283,7 +9281,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Mer"
@@ -10726,7 +10723,6 @@ msgid "Open Outlook"
msgstr "Öppna Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Öppna sidopanelen"
@@ -3189,7 +3189,6 @@ msgstr "Pano güvenli bir bağlantı (HTTPS) gerektirir. Kopyalamayı etkinleşt
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Kapat"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Afişi kapat"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Yan paneli kapat"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Daha Fazla"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Outlook'u Aç"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Yan paneli aç"
@@ -3189,7 +3189,6 @@ msgstr "Буфер обміну вимагає захищеного з'єдна
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Закрити"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Закрити банер"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Закрити бічну панель"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Більше"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Відкрити Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Відкрити бічну панель"
@@ -3189,7 +3189,6 @@ msgstr "Bộ nhớ tạm yêu cầu kết nối bảo mật (HTTPS). Vui lòng t
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "Đóng"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "Đóng biểu ngữ"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "Đóng ngăn bên"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "Thêm nữa"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "Mở Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "Mở ngăn bên"
@@ -3189,7 +3189,6 @@ msgstr "剪贴板功能需要安全连接 (HTTPS)。请通过 HTTPS 访问此应
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "关闭"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "关闭横幅"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "关闭侧边栏"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "更多"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "打开Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "打开侧边栏"
@@ -3189,7 +3189,6 @@ msgstr "剪貼簿需要安全連線 (HTTPS)。請透過 HTTPS 存取此應用程
#. js-lingui-id: yz7wBu
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close"
msgstr "關閉"
@@ -3200,7 +3199,6 @@ msgid "Close banner"
msgstr "關閉橫幅"
#. js-lingui-id: saip/v
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Close side panel"
msgstr "關閉側邊面板"
@@ -9281,7 +9279,6 @@ msgstr ""
#. js-lingui-id: 2FYpfJ
#: src/pages/settings/ai/SettingsAI.tsx
#: src/modules/ui/layout/tab-list/components/TabMoreButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "More"
msgstr "更多"
@@ -10724,7 +10721,6 @@ msgid "Open Outlook"
msgstr "打開Outlook"
#. js-lingui-id: bY2Xkv
#: src/modules/ui/layout/page-header/components/PageHeaderToggleSidePanelButton.tsx
#: src/modules/side-panel/components/SidePanelToggleButton.tsx
msgid "Open side panel"
msgstr "開啟側邊面板"
@@ -2,45 +2,31 @@ import { styled } from '@linaria/react';
import { Trans } from '@lingui/react/macro';
import { useContext } from 'react';
import { IconLock } from 'twenty-ui/display';
import { Card, CardContent } from 'twenty-ui/layout';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledVisibilityCardContainer = styled.div`
color: ${themeCssVariables.font.color.light};
flex: 1;
transition: color ${themeCssVariables.animation.duration.normal} ease;
width: 100%;
> * {
border-color: ${themeCssVariables.border.color.light};
}
`;
const StyledVisibilityCardContentContainer = styled.div`
const StyledContainer = styled.div`
align-items: center;
background-color: ${themeCssVariables.background.transparent.lighter};
background: ${themeCssVariables.background.transparent.lighter};
border: 1px solid ${themeCssVariables.border.color.light};
border-radius: ${themeCssVariables.border.radius.sm};
box-sizing: border-box;
color: ${themeCssVariables.font.color.light};
display: flex;
font-size: ${themeCssVariables.font.size.sm};
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[1]};
height: ${themeCssVariables.spacing[6]};
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[1]};
width: 100%;
`;
export const CalendarEventNotSharedContent = () => {
const { theme } = useContext(ThemeContext);
return (
<StyledVisibilityCardContainer>
<Card>
<StyledVisibilityCardContentContainer>
<CardContent>
<IconLock size={theme.icon.size.sm} />
<Trans>Not shared</Trans>
</CardContent>
</StyledVisibilityCardContentContainer>
</Card>
</StyledVisibilityCardContainer>
<StyledContainer>
<IconLock size={theme.icon.size.sm} />
<Trans>Not shared</Trans>
</StyledContainer>
);
};
@@ -1,10 +1,10 @@
import { type ObjectPermissions } from 'twenty-shared/types';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { type ObjectPermissions } from 'twenty-shared/types';
import { type UserWorkspace } from '~/generated-metadata/graphql';
export type CurrentUserWorkspace = Pick<
UserWorkspace,
'permissionFlags' | 'twoFactorAuthenticationMethodSummary'
'id' | 'permissionFlags' | 'twoFactorAuthenticationMethodSummary'
> & {
objectsPermissions: Array<ObjectPermissions & { objectMetadataId: string }>;
};
@@ -11,13 +11,14 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import {
IconChevronDown,
IconSquareCheck,
IconSquareX,
} from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const DROPDOWN_ID = 'command-menu-edit-record-selection-dropdown';
@@ -55,6 +56,7 @@ export const CommandMenuItemEditRecordSelectionDropdown = ({
isRecordPage = false,
}: CommandMenuItemEditRecordSelectionDropdownProps) => {
const { t } = useLingui();
const { theme } = useContext(ThemeContext);
const { closeDropdown } = useCloseDropdown();
const mainContextStoreHasSelectedRecords = useAtomStateValue(
@@ -92,9 +94,17 @@ export const CommandMenuItemEditRecordSelectionDropdown = ({
disabled={isRecordPage}
data-click-outside-id={COMMAND_MENU_DROPDOWN_CLICK_OUTSIDE_ID}
>
<TriggerIcon size={16} />
<TriggerIcon
size={16}
color={theme.font.color.primary}
stroke={theme.icon.stroke.sm}
/>
<StyledLabel>{triggerLabel}</StyledLabel>
<IconChevronDown size={16} />
<IconChevronDown
size={16}
color={theme.font.color.primary}
stroke={theme.icon.stroke.sm}
/>
</StyledClickableArea>
}
dropdownPlacement="bottom-start"
@@ -1,5 +1,5 @@
import { styled } from '@linaria/react';
import { useState, type ReactNode } from 'react';
import { useDeferredValue, useState, type ReactNode } from 'react';
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer';
@@ -25,12 +25,15 @@ export const NavigationMenuItemFolderLayout = ({
}: NavigationMenuItemFolderLayoutProps) => {
const [skipInitialExpandAnimation] = useState(() => isOpen);
const deferredIsOpen = useDeferredValue(isOpen);
const isExpandedForAnimation = isOpen ? deferredIsOpen : false;
return (
<NavigationDrawerItemsCollapsableContainer isGroup={isGroup}>
{header}
<StyledFolderExpandableWrapper>
<AnimatedExpandableContainer
isExpanded={isOpen}
isExpanded={isExpandedForAnimation}
dimension="height"
mode="fit-content"
containAnimation
@@ -43,10 +43,12 @@ export const useUpdateOneFieldMetadataItem = () => {
| 'description'
| 'icon'
| 'isActive'
| 'isUnique'
| 'label'
| 'name'
| 'defaultValue'
| 'options'
| 'settings'
| 'isLabelSyncedWithName'
>;
}): Promise<
@@ -8,7 +8,7 @@ export const getLabelIdentifierFieldValue = (
labelIdentifierFieldMetadataItem: FieldMetadataItem | undefined,
): string => {
if (!isDefined(labelIdentifierFieldMetadataItem)) {
return record.id;
return record.id ?? '';
}
const recordIdentifierValue = record[labelIdentifierFieldMetadataItem.name];
@@ -27,7 +27,6 @@ import { isFieldSelect } from '@/object-record/record-field/ui/types/guards/isFi
import { isFieldSelectValue } from '@/object-record/record-field/ui/types/guards/isFieldSelectValue';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
@@ -63,10 +62,6 @@ export const usePersistField = ({
}: {
objectMetadataItemId: string;
}) => {
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: objectMetadataItemId,
});
const { objectMetadataItems } = useObjectMetadataItems();
const { updateOneRecord } = useUpdateOneRecord();
@@ -159,6 +154,16 @@ export const usePersistField = ({
return;
}
const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === objectMetadataItemId,
);
if (!isDefined(objectMetadataItem)) {
throw new Error(
`Object metadata item not found for id ${objectMetadataItemId}`,
);
}
const isValuePersistable =
fieldIsMorphRelationManyToOne ||
fieldIsRelationManyToOne ||
@@ -290,7 +295,7 @@ export const usePersistField = ({
}
},
[
objectMetadataItem?.nameSingular,
objectMetadataItemId,
objectMetadataItems,
store,
updateOneRecord,
@@ -387,7 +387,10 @@ export const PageLayoutTabList = ({
onDragEnd={handleDragEnd}
onDragUpdate={handleDragUpdate}
>
<StyledContainer className={className}>
<StyledContainer
data-testid="page-layout-tab-list"
className={className}
>
<PageLayoutTabListVisibleTabs
visibleTabs={tabsWithIcons}
visibleTabCount={visibleTabCount}
@@ -452,7 +455,10 @@ export const PageLayoutTabList = ({
</StyledContainer>
</DragDropContext>
) : (
<StyledContainer className={className}>
<StyledContainer
data-testid="page-layout-tab-list"
className={className}
>
<PageLayoutTabListVisibleTabs
visibleTabs={tabsWithIcons}
visibleTabCount={visibleTabCount}
@@ -0,0 +1,348 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { graphql, HttpResponse } from 'msw';
import { expect, userEvent, waitFor, within } from 'storybook/test';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
import {
PageDecorator,
type PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { RecordStoreDecorator } from '~/testing/decorators/RecordStoreDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedCompanyRecords } from '~/testing/mock-data/generated/data/companies/mock-companies-data';
import { mockedUserData } from '~/testing/mock-data/users';
import { getOperationName } from '~/utils/getOperationName';
const companyRecord = mockedCompanyRecords[0];
const mockedUserDataWithFeatureFlags = {
...mockedUserData,
currentWorkspace: {
...mockedUserData.currentWorkspace,
featureFlags: [
{
__typename: 'FeatureFlag' as const,
key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
value: true,
},
{
__typename: 'FeatureFlag' as const,
key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED,
value: true,
},
],
},
};
const enterEditMode = async (canvasElement: HTMLElement) => {
const body = canvasElement.ownerDocument.body;
const sidePanelButton = await within(body).findByTestId(
'page-header-side-panel-button',
);
await userEvent.click(sidePanelButton);
const searchInput = await within(body).findByTestId('side-panel-focus');
await userEvent.type(searchInput, 'Edit Layout');
const editLayoutItem = await within(body).findByText('Edit Layout', {
exact: true,
});
await userEvent.click(editLayoutItem);
await waitFor(
() => {
expect(within(body).getByText('Layout customization')).toBeVisible();
},
{ timeout: 5000 },
);
};
const meta: Meta<PageDecoratorArgs> = {
title: 'Modules/PageLayout/RecordPageLayoutEditing',
component: RecordShowPage,
decorators: [PageDecorator, ContextStoreDecorator, RecordStoreDecorator],
args: {
routePath: '/object/:objectNameSingular/:objectRecordId',
routeParams: {
':objectNameSingular': 'company',
':objectRecordId': companyRecord.id,
},
},
parameters: {
records: [companyRecord],
layout: 'fullscreen',
msw: {
handlers: [
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: mockedUserDataWithFeatureFlags,
},
});
}),
graphql.query('FindOneCompany', () => {
return HttpResponse.json({
data: {
company: companyRecord,
},
});
}),
...graphqlMocks.handlers,
],
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const DefaultLayout: Story = {
play: async ({ canvasElement }) => {
const body = canvasElement.ownerDocument.body;
await expect(await within(body).findByText('Fields')).toBeVisible();
await expect(
await within(
await within(body).findByTestId('page-layout-tab-list'),
).findByText('Timeline'),
).toBeVisible();
},
};
export const CreateTab: Story = {
play: async ({ canvasElement }) => {
const body = canvasElement.ownerDocument.body;
await enterEditMode(canvasElement);
const newTabButton = await within(body).findByRole('button', {
name: 'New Tab',
});
await userEvent.click(newTabButton);
const emptyTabInput = await within(body).findByPlaceholderText('Tab');
await expect(emptyTabInput).toHaveValue('Untitled');
await userEvent.keyboard('{Escape}');
const moreButtons = await within(
within(body).getByTestId('page-layout-tab-list'),
).findAllByRole('button', {
name: /\+\d{1} More/,
});
await expect(moreButtons[1]).toBeVisible();
await userEvent.click(moreButtons[1]);
await waitFor(() => {
expect(
within(within(body).getAllByRole('listbox')[0]).getByText('Untitled', {
exact: true,
}),
).toBeVisible();
});
},
};
export const RenameTab: Story = {
play: async ({ canvasElement }) => {
const body = canvasElement.ownerDocument.body;
await enterEditMode(canvasElement);
const timelineTab = await within(
within(body).getByTestId('page-layout-tab-list'),
).findByText('Timeline', {
exact: true,
});
await userEvent.click(timelineTab);
const sidePanel = await waitFor(() => {
return canvasElement.querySelector('[data-side-panel]') as HTMLElement;
});
await userEvent.click(await within(sidePanel!).findByText('Timeline'));
const titleInput =
await within(body).findByPlaceholderText('Full tab widget');
await userEvent.clear(titleInput);
await userEvent.type(titleInput, 'My Custom Timeline{Enter}');
await waitFor(() => {
expect(
within(within(body).getByTestId('page-layout-tab-list')).getByText(
'My Custom Timeline',
{ exact: true },
),
).toBeVisible();
});
},
};
// TODO
export const DeleteTab: Story = {
play: async ({ canvasElement }) => {
const body = canvasElement.ownerDocument.body;
await enterEditMode(canvasElement);
const defaultMoreTabsCount = 5;
await waitFor(
() => {
expect(
within(body).getByText(`+${defaultMoreTabsCount} More`, {
exact: true,
}),
).toBeVisible();
},
{ timeout: 5000 },
);
const newTabButton = await within(body).findByRole('button', {
name: 'New Tab',
});
await userEvent.click(newTabButton);
const emptyTabInput = await within(body).findByRole('textbox', {
name: 'Tab',
});
await expect(emptyTabInput).toHaveValue('Untitled');
await userEvent.keyboard('{Escape}');
await waitFor(() => {
expect(
within(body).getByText(`+${defaultMoreTabsCount + 1} More`, {
exact: true,
}),
).toBeVisible();
});
const deleteButton = await within(body).findByText('Delete', {
exact: true,
});
await userEvent.click(deleteButton);
await waitFor(() => {
expect(
within(body).getByText(`+${defaultMoreTabsCount} More`, {
exact: true,
}),
).toBeVisible();
});
},
};
// TODO
export const DuplicateTab: Story = {
play: async ({ canvasElement }) => {
const body = canvasElement.ownerDocument.body;
await enterEditMode(canvasElement);
const timelineTab = await within(body).findByText('Timeline', {
exact: true,
});
await userEvent.click(timelineTab);
await userEvent.click(timelineTab);
const duplicateButton = await within(body).findByText('Duplicate', {
exact: true,
});
await userEvent.click(duplicateButton);
await waitFor(() => {
expect(
within(body).getByText('Timeline - Copy', { exact: true }),
).toBeVisible();
});
},
};
// TODO
export const ReorderTabMoveLeft: Story = {
play: async ({ canvasElement }) => {
const body = canvasElement.ownerDocument.body;
await enterEditMode(canvasElement);
const tasksTab = await within(body).findByText('Tasks', { exact: true });
await userEvent.click(tasksTab);
await userEvent.click(tasksTab);
const moveLeftButton = await within(body).findByText('Move left', {
exact: true,
});
await userEvent.click(moveLeftButton);
await waitFor(() => {
expect(within(body).getByText('Tasks', { exact: true })).toBeVisible();
expect(within(body).getByText('Timeline', { exact: true })).toBeVisible();
});
},
};
// TODO
export const TabSwitching: Story = {
play: async ({ canvasElement }) => {
const body = canvasElement.ownerDocument.body;
const emailsTab = await within(body).findByText('Emails', {
exact: true,
});
await userEvent.click(emailsTab);
const tasksTab = await within(body).findByText('Tasks', { exact: true });
await userEvent.click(tasksTab);
const timelineTab = await within(body).findByText('Timeline', {
exact: true,
});
await userEvent.click(timelineTab);
},
};
// TODO
export const CancelDiscardsChanges: Story = {
play: async ({ canvasElement }) => {
const body = canvasElement.ownerDocument.body;
await enterEditMode(canvasElement);
const newTabButton = await within(body).findByRole('button', {
name: 'New Tab',
});
await userEvent.click(newTabButton);
const emptyTabInput = await within(body).findByRole('textbox', {
name: 'Tab',
});
await expect(emptyTabInput).toHaveValue('Untitled');
await userEvent.keyboard('{Escape}');
await waitFor(() => {
expect(within(body).getByText('Untitled', { exact: true })).toBeVisible();
});
const cancelButton = await within(body).findByRole('button', {
name: 'Cancel',
});
await userEvent.click(cancelButton);
await waitFor(
() => {
expect(
within(body).queryByText('Untitled', { exact: true }),
).not.toBeInTheDocument();
},
{ timeout: 5000 },
);
},
};
@@ -15,9 +15,7 @@ import { hasInitializedFieldsWidgetGroupsDraftComponentState } from '@/page-layo
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
import { type PageLayout } from '@/page-layout/types/PageLayout';
import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts';
import { evictViewMetadataForViewIds } from '@/page-layout/utils/evictViewMetadataForViewIds';
import { toDraftPageLayout } from '@/page-layout/utils/toDraftPageLayout';
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
@@ -76,55 +74,47 @@ export const useRefreshPageLayoutAfterReset = (
pageLayoutId,
);
const refreshPageLayoutAfterReset = useCallback(
async (collectAffectedViewIds: (layout: PageLayout) => Set<string>) => {
const { data } = await client.query({
query: FindOnePageLayoutDocument,
variables: { id: pageLayoutId },
fetchPolicy: 'network-only',
});
const refreshPageLayoutAfterReset = useCallback(async () => {
const { data } = await client.query({
query: FindOnePageLayoutDocument,
variables: { id: pageLayoutId },
fetchPolicy: 'network-only',
});
let affectedViewIds = new Set<string>();
if (isDefined(data?.getPageLayout)) {
const freshLayout = transformPageLayout(data.getPageLayout);
if (isDefined(data?.getPageLayout)) {
const freshLayout = transformPageLayout(data.getPageLayout);
store.set(pageLayoutPersistedState, freshLayout);
store.set(pageLayoutDraftState, toDraftPageLayout(freshLayout));
store.set(
pageLayoutCurrentLayoutsState,
convertPageLayoutToTabLayouts(freshLayout),
);
}
affectedViewIds = collectAffectedViewIds(freshLayout);
store.set(fieldsWidgetGroupsDraftState, {});
store.set(fieldsWidgetUngroupedFieldsDraftState, {});
store.set(fieldsWidgetEditorModeDraftState, {});
store.set(hasInitializedFieldsWidgetGroupsDraftState, {});
store.set(pageLayoutPersistedState, freshLayout);
store.set(pageLayoutDraftState, toDraftPageLayout(freshLayout));
store.set(
pageLayoutCurrentLayoutsState,
convertPageLayoutToTabLayouts(freshLayout),
);
}
store.set(fieldsWidgetGroupsDraftState, {});
store.set(fieldsWidgetUngroupedFieldsDraftState, {});
store.set(fieldsWidgetEditorModeDraftState, {});
store.set(hasInitializedFieldsWidgetGroupsDraftState, {});
setIsPageLayoutInEditMode(false);
exitLayoutCustomizationMode();
evictViewMetadataForViewIds(store, affectedViewIds);
invalidateMetadataStore();
},
[
client,
pageLayoutId,
store,
pageLayoutPersistedState,
pageLayoutDraftState,
pageLayoutCurrentLayoutsState,
fieldsWidgetGroupsDraftState,
fieldsWidgetUngroupedFieldsDraftState,
fieldsWidgetEditorModeDraftState,
hasInitializedFieldsWidgetGroupsDraftState,
setIsPageLayoutInEditMode,
exitLayoutCustomizationMode,
invalidateMetadataStore,
],
);
setIsPageLayoutInEditMode(false);
exitLayoutCustomizationMode();
invalidateMetadataStore();
}, [
client,
pageLayoutId,
store,
pageLayoutPersistedState,
pageLayoutDraftState,
pageLayoutCurrentLayoutsState,
fieldsWidgetGroupsDraftState,
fieldsWidgetUngroupedFieldsDraftState,
fieldsWidgetEditorModeDraftState,
hasInitializedFieldsWidgetGroupsDraftState,
setIsPageLayoutInEditMode,
exitLayoutCustomizationMode,
invalidateMetadataStore,
]);
return { refreshPageLayoutAfterReset };
};
@@ -7,7 +7,6 @@ import { CrudOperationType } from 'twenty-shared/types';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { RESET_PAGE_LAYOUT_TAB_TO_DEFAULT } from '@/page-layout/graphql/mutations/resetPageLayoutTabToDefault';
import { useRefreshPageLayoutAfterReset } from '@/page-layout/hooks/useRefreshPageLayoutAfterReset';
import { collectViewIdsFromWidgets } from '@/page-layout/utils/collectViewIdsFromWidgets';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
export const useResetPageLayoutTabToDefault = (
@@ -24,11 +23,7 @@ export const useResetPageLayoutTabToDefault = (
async (tabId: string) => {
try {
await resetMutation({ variables: { id: tabId } });
await refreshPageLayoutAfterReset((layout) =>
collectViewIdsFromWidgets(
layout.tabs.find((tab) => tab.id === tabId)?.widgets ?? [],
),
);
await refreshPageLayoutAfterReset();
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
@@ -10,9 +10,6 @@ import { useInvalidateMetadataStore } from '@/metadata-store/hooks/useInvalidate
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { RESET_PAGE_LAYOUT_TO_DEFAULT } from '@/page-layout/graphql/mutations/resetPageLayoutToDefault';
import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/pageLayoutIsInitializedComponentState';
import { type PageLayout } from '@/page-layout/types/PageLayout';
import { collectViewIdsFromWidgets } from '@/page-layout/utils/collectViewIdsFromWidgets';
import { evictViewMetadataForViewIds } from '@/page-layout/utils/evictViewMetadataForViewIds';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
export const useResetPageLayoutToDefault = () => {
@@ -23,22 +20,10 @@ export const useResetPageLayoutToDefault = () => {
const store = useStore();
const resetPageLayoutToDefault = useCallback(
async ({
pageLayoutId,
pageLayout,
}: {
pageLayoutId: string;
pageLayout: PageLayout;
}) => {
const preResetViewIds = collectViewIdsFromWidgets(
pageLayout.tabs.flatMap((tab) => tab.widgets),
);
async ({ pageLayoutId }: { pageLayoutId: string }) => {
try {
await resetMutation({ variables: { id: pageLayoutId } });
evictViewMetadataForViewIds(store, preResetViewIds);
if (isDefined(pageLayoutId)) {
store.set(
pageLayoutIsInitializedComponentState.atomFamily({
@@ -7,7 +7,6 @@ import { ResetPageLayoutWidgetToDefaultDocument } from '~/generated-metadata/gra
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useRefreshPageLayoutAfterReset } from '@/page-layout/hooks/useRefreshPageLayoutAfterReset';
import { collectViewIdsFromWidgets } from '@/page-layout/utils/collectViewIdsFromWidgets';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
export const useResetPageLayoutWidgetToDefault = (
@@ -24,13 +23,7 @@ export const useResetPageLayoutWidgetToDefault = (
async (widgetId: string) => {
try {
await resetMutation({ variables: { id: widgetId } });
await refreshPageLayoutAfterReset((layout) =>
collectViewIdsFromWidgets(
layout.tabs
.flatMap((tab) => tab.widgets)
.filter((widget) => widget.id === widgetId),
),
);
await refreshPageLayoutAfterReset();
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
@@ -1,20 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { getWidgetConfigurationViewId } from '@/page-layout/utils/getWidgetConfigurationViewId';
export const collectViewIdsFromWidgets = (
widgets: PageLayoutWidget[],
): Set<string> => {
const viewIds = new Set<string>();
for (const widget of widgets) {
const viewId = getWidgetConfigurationViewId(widget.configuration);
if (isDefined(viewId)) {
viewIds.add(viewId);
}
}
return viewIds;
};
@@ -1,31 +0,0 @@
import type { useStore } from 'jotai';
import { isDefined } from 'twenty-shared/utils';
import {
type MetadataEntityKey,
metadataStoreState,
} from '@/metadata-store/states/metadataStoreState';
const VIEW_RELATED_METADATA_KEYS: MetadataEntityKey[] = [
'viewFields',
'viewFieldGroups',
];
export const evictViewMetadataForViewIds = (
store: ReturnType<typeof useStore>,
viewIds: Set<string>,
) => {
if (viewIds.size === 0) {
return;
}
for (const key of VIEW_RELATED_METADATA_KEYS) {
store.set(metadataStoreState.atomFamily(key), (prev) => ({
...prev,
current: (prev.current as { viewId?: string }[]).filter(
(item) => !isDefined(item.viewId) || !viewIds.has(item.viewId),
),
currentCollectionHash: undefined,
}));
}
};
@@ -77,7 +77,6 @@ export const ObjectLayout = ({ objectMetadataItem }: ObjectLayoutProps) => {
await resetPageLayoutToDefault({
pageLayoutId: pageLayout.id,
pageLayout,
});
};
@@ -1,53 +1,165 @@
import { SIDE_PANEL_TOP_BAR_HEIGHT_MOBILE } from '@/side-panel/constants/SidePanelTopBarHeightMobile';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { PAGE_HEADER_SIDE_PANEL_BUTTON_CLICK_OUTSIDE_ID } from '@/ui/layout/page-header/constants/PageHeaderSidePanelButtonClickOutsideId';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import {
AppTooltip,
IconLayoutSidebarRightExpand,
IconX,
TooltipDelay,
TooltipPosition,
} from 'twenty-ui/display';
import { motion } from 'framer-motion';
import { useContext } from 'react';
import { AppTooltip, TooltipDelay, TooltipPosition } from 'twenty-ui/display';
import { AnimatedButton } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { AnimatedIconCrossfade, useIsMobile } from 'twenty-ui/utilities';
import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledButtonWrapper = styled.div<{ alignToTop: boolean }>`
align-items: ${({ alignToTop }) => (alignToTop ? 'center' : 'initial')};
display: ${({ alignToTop }) => (alignToTop ? 'flex' : 'block')};
height: ${({ alignToTop }) =>
alignToTop ? `${SIDE_PANEL_TOP_BAR_HEIGHT_MOBILE}px` : 'auto'};
position: ${({ alignToTop }) => (alignToTop ? 'fixed' : 'static')};
right: ${({ alignToTop }) =>
alignToTop ? themeCssVariables.spacing[3] : 'auto'};
top: ${({ alignToTop }) => (alignToTop ? '0' : 'auto')};
z-index: ${RootStackingContextZIndices.SidePanelButton};
`;
const StyledTooltipWrapper = styled.div`
font-size: ${themeCssVariables.font.size.md};
`;
const xPaths = {
topLeft: `M12 12 L6 6`,
topRight: `M12 12 L18 6`,
bottomLeft: `M12 12 L6 18`,
bottomRight: `M12 12 L18 18`,
};
const AnimatedIcon = ({
isSidePanelOpened,
}: {
isSidePanelOpened: boolean;
}) => {
const { theme } = useContext(ThemeContext);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={theme.icon.size.sm}
height={theme.icon.size.sm}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={theme.icon.stroke.md}
strokeLinecap="round"
strokeLinejoin="round"
style={{ display: 'block' }}
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<motion.circle
cx={12}
cy={12}
r="1"
initial={{ opacity: 0 }}
animate={{
scale: isSidePanelOpened ? 0 : 1,
opacity: isSidePanelOpened ? 0 : 1,
}}
transition={{
duration: theme.animation.duration.fast,
}}
/>
{Object.values(xPaths).map((path, index) => (
<motion.path
key={index}
d={path}
initial={{ pathLength: 0, opacity: 0 }}
animate={{
pathLength: isSidePanelOpened ? 1 : 0,
opacity: isSidePanelOpened ? 1 : 0,
}}
transition={{
duration: theme.animation.duration.fast,
ease: 'easeInOut',
delay: isSidePanelOpened ? 0.1 : 0,
}}
/>
))}
<motion.circle
cx="12"
cy="5"
r="1"
initial={{ opacity: 0 }}
animate={{
scale: isSidePanelOpened ? 0 : 1,
opacity: isSidePanelOpened ? 0 : 1,
}}
transition={{
duration: theme.animation.duration.fast,
}}
/>
<motion.circle
cx="12"
cy="19"
r="1"
initial={{ opacity: 0 }}
animate={{
scale: isSidePanelOpened ? 0 : 1,
opacity: isSidePanelOpened ? 0 : 1,
}}
transition={{
duration: theme.animation.duration.fast,
}}
/>
</svg>
);
};
export const SidePanelToggleButton = () => {
const { toggleSidePanelMenu } = useSidePanelMenu();
const isSidePanelOpened = useAtomStateValue(isSidePanelOpenedState);
const isLayoutCustomizationModeEnabled = useAtomStateValue(
isLayoutCustomizationModeEnabledState,
);
const isMobile = useIsMobile();
const alignWithSidePanelTopBar =
isMobile && isLayoutCustomizationModeEnabled && isSidePanelOpened;
const ariaLabel = isSidePanelOpened
? t`Close side panel`
: t`Open side panel`;
const { theme } = useContext(ThemeContext);
return (
<div id="toggle-side-panel-button">
<AnimatedButton
animatedSvg={
<AnimatedIconCrossfade
isActive={isSidePanelOpened}
ActiveIcon={IconX}
InactiveIcon={IconLayoutSidebarRightExpand}
/>
}
dataClickOutsideId={PAGE_HEADER_SIDE_PANEL_BUTTON_CLICK_OUTSIDE_ID}
dataTestId="page-header-side-panel-button"
size={isMobile ? 'medium' : 'small'}
variant="secondary"
accent="default"
title={isSidePanelOpened ? t`Close` : t`More`}
ariaLabel={ariaLabel}
onClick={toggleSidePanelMenu}
/>
<StyledButtonWrapper alignToTop={alignWithSidePanelTopBar}>
<div id="toggle-side-panel-button">
<AnimatedButton
animatedSvg={<AnimatedIcon isSidePanelOpened={isSidePanelOpened} />}
dataClickOutsideId={PAGE_HEADER_SIDE_PANEL_BUTTON_CLICK_OUTSIDE_ID}
dataTestId="page-header-side-panel-button"
size={isMobile ? 'medium' : 'small'}
variant="secondary"
accent="default"
hotkeys={[getOsControlSymbol(), 'K']}
ariaLabel={ariaLabel}
onClick={toggleSidePanelMenu}
animate={{
rotate: isSidePanelOpened ? 90 : 0,
}}
transition={{
duration: theme.animation.duration.normal,
ease: 'easeInOut',
}}
/>
</div>
<StyledTooltipWrapper>
<AppTooltip
anchorSelect="#toggle-side-panel-button"
@@ -58,6 +170,6 @@ export const SidePanelToggleButton = () => {
noArrow
/>
</StyledTooltipWrapper>
</div>
</StyledButtonWrapper>
);
};
@@ -9,9 +9,9 @@ import {
useContext,
useMemo,
} from 'react';
import { Link } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import {
HorizontalSeparator,
IconAlertTriangle,
IconInfoCircle,
IconSquareRoundedCheck,
@@ -19,6 +19,7 @@ import {
} from 'twenty-ui/display';
import { ProgressBar, useProgressAnimation } from 'twenty-ui/feedback';
import { LightButton, LightIconButton } from 'twenty-ui/input';
import { UndecoratedLink } from 'twenty-ui/navigation';
import {
MOBILE_VIEWPORT,
ThemeContext,
@@ -39,9 +40,9 @@ export type SnackBarProps = Pick<ComponentPropsWithoutRef<'div'>, 'id'> & {
duration?: number;
icon?: ReactNode;
message: string;
actionText?: string;
actionOnClick?: () => void;
actionTo?: string;
buttonLabel?: string;
buttonOnClick?: () => void;
buttonTo?: string;
detailedMessage?: string;
onCancel?: () => void;
onClose?: () => void;
@@ -56,9 +57,9 @@ const StyledContainer = styled.div`
border-radius: ${themeCssVariables.border.radius.md};
box-shadow: ${themeCssVariables.boxShadow.strong};
box-sizing: border-box;
cursor: pointer;
margin-top: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]}
${themeCssVariables.spacing[1]};
position: relative;
width: 296px;
@@ -70,12 +71,15 @@ const StyledContainer = styled.div`
const StyledProgressBarContainer = styled.div`
bottom: 0;
height: auto;
left: 0;
pointer-events: none;
position: absolute;
right: 0;
top: 0;
& > [role='progressbar'] {
height: 100%;
}
`;
const StyledHeader = styled.div`
@@ -112,24 +116,15 @@ const StyledDescription = styled.div`
width: 200px;
`;
const StyledLinkContainer = styled.div`
> a {
color: ${themeCssVariables.font.color.tertiary};
display: block;
font-size: ${themeCssVariables.font.size.sm};
max-width: 200px;
overflow: hidden;
padding-left: ${themeCssVariables.spacing[6]};
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: ${themeCssVariables.font.color.secondary};
}
}
const StyledBottomActionContainer = styled.div`
margin-top: ${themeCssVariables.spacing[2]};
`;
const StyledActionButton = styled.div`
padding-left: ${themeCssVariables.spacing[6]};
const StyledBottomAction = styled.div`
align-items: center;
display: flex;
justify-content: flex-end;
padding-top: ${themeCssVariables.spacing[1]};
`;
const defaultAriaLabelByVariant: Record<
@@ -151,9 +146,9 @@ export const SnackBar = ({
id,
message,
detailedMessage,
actionText,
actionOnClick,
actionTo,
buttonLabel,
buttonOnClick,
buttonTo,
onCancel,
onClose,
role = 'status',
@@ -205,15 +200,11 @@ export const SnackBar = ({
}, [iconComponent, variant, i18n, theme.icon.size.md, theme.snackBar]);
const handleMouseEnter = () => {
if (progressAnimation?.state === 'running') {
progressAnimation.pause();
}
progressAnimation?.pause();
};
const handleMouseLeave = () => {
if (progressAnimation?.state === 'paused') {
progressAnimation.play();
}
progressAnimation?.play();
};
const sanitizedMessage = sanitizeMessageToRenderInSnackbar(message);
@@ -251,16 +242,21 @@ export const SnackBar = ({
{isDefined(sanitizedDetailedMessage) && (
<StyledDescription>{sanitizedDetailedMessage}</StyledDescription>
)}
{actionText && actionTo && (
<StyledLinkContainer>
<Link to={actionTo}>{actionText}</Link>
</StyledLinkContainer>
)}
{actionText && actionOnClick && !actionTo && (
<StyledActionButton>
<LightButton title={actionText} onClick={actionOnClick} />
</StyledActionButton>
)}
{isDefined(buttonLabel) &&
(isDefined(buttonOnClick) || isDefined(buttonTo)) && (
<StyledBottomActionContainer>
<HorizontalSeparator noMargin />
<StyledBottomAction>
{isDefined(buttonTo) ? (
<UndecoratedLink to={buttonTo}>
<LightButton title={buttonLabel} />
</UndecoratedLink>
) : (
<LightButton title={buttonLabel} onClick={buttonOnClick} />
)}
</StyledBottomAction>
</StyledBottomActionContainer>
)}
</StyledContainer>
);
};
@@ -58,9 +58,9 @@ export const SnackBarProvider = ({ children }: React.PropsWithChildren) => {
message,
detailedMessage,
variant,
actionText,
actionOnClick,
actionTo,
buttonLabel,
buttonOnClick,
buttonTo,
}) => (
<motion.div
key={id}
@@ -78,9 +78,9 @@ export const SnackBarProvider = ({ children }: React.PropsWithChildren) => {
message,
detailedMessage,
variant,
actionText,
actionOnClick,
actionTo,
buttonLabel,
buttonOnClick,
buttonTo,
}}
onClose={() => handleSnackBarClose(id)}
/>
@@ -41,6 +41,33 @@ export const Default: Story = {
},
};
export const WithBottomButton: Story = {
args: {
variant: SnackBarVariant.Error,
message: 'An error has occurred',
detailedMessage: 'Error during useFindManyRecord...',
buttonLabel: 'Open Record',
buttonOnClick: fn(),
},
decorators: [ComponentDecorator],
parameters: {
chromatic: { disableSnapshot: true },
},
};
export const SuccessWithButton: Story = {
args: {
variant: SnackBarVariant.Success,
message: 'Record created successfully',
buttonLabel: 'View Record',
buttonOnClick: fn(),
},
decorators: [ComponentDecorator],
parameters: {
chromatic: { disableSnapshot: true },
},
};
export const Catalog: CatalogStory<Story, typeof SnackBar> = {
args: {
onCancel: fn(),
@@ -63,3 +90,22 @@ export const Catalog: CatalogStory<Story, typeof SnackBar> = {
},
},
};
export const CatalogWithButton: CatalogStory<Story, typeof SnackBar> = {
args: {
buttonLabel: 'Open Record',
buttonOnClick: fn(),
},
decorators: [CatalogDecorator],
parameters: {
catalog: {
dimensions: [
{
name: 'variants',
values: Object.values(SnackBarVariant),
props: (variant: SnackBarVariant) => ({ variant }),
},
],
},
},
};
@@ -7,7 +7,7 @@ import { type SnackBarOptions } from '@/ui/feedback/snack-bar-manager/states/sna
export const buildErrorAction = (
apolloError?: ErrorLike,
): Pick<SnackBarOptions, 'actionText' | 'actionTo'> | null => {
): Pick<SnackBarOptions, 'buttonLabel' | 'buttonTo'> | null => {
if (!apolloError) {
return null;
}
@@ -16,8 +16,8 @@ export const buildErrorAction = (
if (isDefined(conflictingRecord)) {
return {
actionText: t`View existing record`,
actionTo: getAppPath(AppPath.RecordShowPage, {
buttonLabel: t`View existing record`,
buttonTo: getAppPath(AppPath.RecordShowPage, {
objectNameSingular: conflictingRecord.conflictingObjectNameSingular,
objectRecordId: conflictingRecord.conflictingRecordId,
}),
@@ -1,178 +0,0 @@
import { SIDE_PANEL_TOP_BAR_HEIGHT_MOBILE } from '@/side-panel/constants/SidePanelTopBarHeightMobile';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { PAGE_HEADER_SIDE_PANEL_BUTTON_CLICK_OUTSIDE_ID } from '@/ui/layout/page-header/constants/PageHeaderSidePanelButtonClickOutsideId';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { motion } from 'framer-motion';
import { useContext } from 'react';
import { AppTooltip, TooltipDelay, TooltipPosition } from 'twenty-ui/display';
import { AnimatedButton } from 'twenty-ui/input';
import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledButtonWrapper = styled.div<{ alignToTop: boolean }>`
align-items: ${({ alignToTop }) => (alignToTop ? 'center' : 'initial')};
display: ${({ alignToTop }) => (alignToTop ? 'flex' : 'block')};
height: ${({ alignToTop }) =>
alignToTop ? `${SIDE_PANEL_TOP_BAR_HEIGHT_MOBILE}px` : 'auto'};
position: ${({ alignToTop }) => (alignToTop ? 'fixed' : 'static')};
right: ${({ alignToTop }) =>
alignToTop ? themeCssVariables.spacing[3] : 'auto'};
top: ${({ alignToTop }) => (alignToTop ? '0' : 'auto')};
z-index: ${RootStackingContextZIndices.SidePanelButton};
`;
const StyledTooltipWrapper = styled.div`
font-size: ${themeCssVariables.font.size.md};
`;
const xPaths = {
topLeft: `M12 12 L6 6`,
topRight: `M12 12 L18 6`,
bottomLeft: `M12 12 L6 18`,
bottomRight: `M12 12 L18 18`,
};
const AnimatedIcon = ({
isSidePanelOpened,
}: {
isSidePanelOpened: boolean;
}) => {
const { theme } = useContext(ThemeContext);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={theme.icon.size.sm}
height={theme.icon.size.sm}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={theme.icon.stroke.md}
strokeLinecap="round"
strokeLinejoin="round"
style={{ display: 'block' }}
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
{/* Center dot */}
<motion.circle
cx={12}
cy={12}
r="1"
initial={{ opacity: 0 }}
animate={{
scale: isSidePanelOpened ? 0 : 1,
opacity: isSidePanelOpened ? 0 : 1,
}}
transition={{
duration: theme.animation.duration.fast,
}}
/>
{/* X lines expanding from center */}
{Object.values(xPaths).map((path, index) => (
<motion.path
key={index}
d={path}
initial={{ pathLength: 0, opacity: 0 }}
animate={{
pathLength: isSidePanelOpened ? 1 : 0,
opacity: isSidePanelOpened ? 1 : 0,
}}
transition={{
duration: theme.animation.duration.fast,
ease: 'easeInOut',
delay: isSidePanelOpened ? 0.1 : 0,
}}
/>
))}
{/* Top dot */}
<motion.circle
cx="12"
cy="5"
r="1"
initial={{ opacity: 0 }}
animate={{
scale: isSidePanelOpened ? 0 : 1,
opacity: isSidePanelOpened ? 0 : 1,
}}
transition={{
duration: theme.animation.duration.fast,
}}
/>
{/* Bottom dot */}
<motion.circle
cx="12"
cy="19"
r="1"
initial={{ opacity: 0 }}
animate={{
scale: isSidePanelOpened ? 0 : 1,
opacity: isSidePanelOpened ? 0 : 1,
}}
transition={{
duration: theme.animation.duration.fast,
}}
/>
</svg>
);
};
export const PageHeaderToggleSidePanelButton = () => {
const { toggleSidePanelMenu } = useSidePanelMenu();
const isSidePanelOpened = useAtomStateValue(isSidePanelOpenedState);
const isLayoutCustomizationModeEnabled = useAtomStateValue(
isLayoutCustomizationModeEnabledState,
);
const isMobile = useIsMobile();
const alignWithSidePanelTopBar =
isMobile && isLayoutCustomizationModeEnabled && isSidePanelOpened;
const ariaLabel = isSidePanelOpened
? t`Close side panel`
: t`Open side panel`;
const { theme } = useContext(ThemeContext);
return (
<StyledButtonWrapper alignToTop={alignWithSidePanelTopBar}>
<div id="toggle-side-panel-button">
<AnimatedButton
animatedSvg={<AnimatedIcon isSidePanelOpened={isSidePanelOpened} />}
dataClickOutsideId={PAGE_HEADER_SIDE_PANEL_BUTTON_CLICK_OUTSIDE_ID}
dataTestId="page-header-side-panel-button"
size={isMobile ? 'medium' : 'small'}
variant="secondary"
accent="default"
hotkeys={[getOsControlSymbol(), 'K']}
ariaLabel={ariaLabel}
onClick={toggleSidePanelMenu}
animate={{
rotate: isSidePanelOpened ? 90 : 0,
}}
transition={{
duration: theme.animation.duration.normal,
ease: 'easeInOut',
}}
/>
</div>
<StyledTooltipWrapper>
<AppTooltip
anchorSelect="#toggle-side-panel-button"
content={ariaLabel}
delay={TooltipDelay.longDelay}
place={TooltipPosition.Bottom}
offset={5}
noArrow
/>
</StyledTooltipWrapper>
</StyledButtonWrapper>
);
};
@@ -30,7 +30,7 @@ export const RecordShowPageTitle = ({
)
: '';
const pageTitle = pageName.trim()
const pageTitle = (pageName ?? '').trim()
? `${pageName} - ${objectMetadataItem.labelSingular}`
: objectMetadataItem.labelSingular;
@@ -1,5 +1,7 @@
import { ApolloProvider } from '@apollo/client/react';
import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect';
import { ApolloCoreClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloCoreClientMockedProvider';
import { loadDevMessages } from '@apollo/client/dev';
import { ApolloProvider } from '@apollo/client/react';
import { type Decorator } from '@storybook/react-vite';
import { Provider as JotaiProvider } from 'jotai';
import { HelmetProvider } from 'react-helmet-async';
@@ -10,19 +12,18 @@ import {
Route,
RouterProvider,
} from 'react-router-dom';
import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect';
import { ApolloCoreClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloCoreClientMockedProvider';
import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout';
import { MinimalMetadataGater } from '@/metadata-store/components/MinimalMetadataGater';
import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect';
import { IsMinimalMetadataReadyEffect } from '@/metadata-store/effect-components/IsMinimalMetadataReadyEffect';
import { MinimalMetadataLoadEffect } from '@/metadata-store/effect-components/MinimalMetadataLoadEffect';
import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect';
import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useState } from 'react';
import { ClientConfigProvider } from '~/modules/client-config/components/ClientConfigProvider';
import { mockedApolloClient } from '~/testing/mockedApolloClient';
import { CommandRunner } from '@/command-menu-item/engine-command/components/CommandRunner';
import { MainContextStoreProvider } from '@/context-store/components/MainContextStoreProvider';
import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider';
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
@@ -104,6 +105,7 @@ const Providers = () => {
</FullHeightStorybookLayout>
</PreComputedChipGeneratorsProvider>
<MainContextStoreProvider />
<CommandRunner />
</ApolloCoreClientMockedProvider>
</MinimalMetadataGater>
</ClientConfigProvider>
@@ -18,21 +18,21 @@ import { GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN } from '@/auth/graphql/queries/getP
import { LIST_PLANS } from '@/settings/billing/graphql/queries/listPlans';
import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery';
import { mockBillingPlans } from '~/testing/mock-data/billing-plans';
import { mockedBackendCommandMenuItems } from '~/testing/mock-data/command-menu-items';
import { mockedCompanyRecords } from '~/testing/mock-data/generated/data/companies/mock-companies-data';
import { mockedTaskRecords } from '~/testing/mock-data/generated/data/tasks/mock-tasks-data';
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata';
import { mockedRoles } from '~/testing/mock-data/generated/metadata/roles/mock-roles-data';
import { mockedBackendCommandMenuItems } from '~/testing/mock-data/command-menu-items';
import { type Task } from '@/activities/types/Task';
import { FIND_MINIMAL_METADATA } from '@/metadata-store/graphql/queries/findMinimalMetadata';
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import {
getConnectionTypename,
getEdgeTypename,
isDefined,
} from 'twenty-shared/utils';
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { mockedApiKeys } from '~/testing/mock-data/generated/metadata/api-keys/mock-api-keys-data';
import { mockedMinimalMetadata } from '~/testing/mock-data/generated/metadata/minimal/mock-minimal-metadata';
import { mockedNavigationMenuItems } from '~/testing/mock-data/generated/metadata/navigation-menu-items/mock-navigation-menu-items-data';
@@ -648,6 +648,13 @@ export const graphqlMocks = {
},
});
}),
metadataGraphql.query('FindManyFrontComponents', () => {
return HttpResponse.json({
data: {
frontComponents: [],
},
});
}),
metadataGraphql.query('GetWebhook', ({ variables }) => {
const webhookId = variables.input?.id;
@@ -21,6 +21,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
conditionalAvailabilityExpression: 'targetObjectReadPermissions.person',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
availabilityObjectMetadataId: null,
payload: null,
},
{
__typename: 'CommandMenuItem',
@@ -39,6 +40,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
'targetObjectReadPermissions.opportunity',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
availabilityObjectMetadataId: null,
payload: null,
},
{
__typename: 'CommandMenuItem',
@@ -56,6 +58,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
conditionalAvailabilityExpression: null,
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
availabilityObjectMetadataId: null,
payload: null,
},
{
__typename: 'CommandMenuItem',
@@ -73,6 +76,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
conditionalAvailabilityExpression: 'targetObjectReadPermissions.task',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
availabilityObjectMetadataId: null,
payload: null,
},
{
__typename: 'CommandMenuItem',
@@ -90,6 +94,26 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
conditionalAvailabilityExpression: 'targetObjectReadPermissions.note',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
availabilityObjectMetadataId: null,
payload: null,
},
{
__typename: 'CommandMenuItem',
id: 'mock-edit-record-page-layout',
workflowVersionId: null,
frontComponentId: null,
frontComponent: null,
engineComponentKey: EngineComponentKey.EDIT_RECORD_PAGE_LAYOUT,
label: 'Edit Layout',
icon: 'IconPencil',
shortLabel: 'Edit Layout',
position: 18,
isPinned: false,
hotKeys: null,
conditionalAvailabilityExpression:
'pageType == "RECORD_PAGE" and featureFlags.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED and noneDefined(selectedRecords, "deletedAt") and objectPermissions.canUpdateObjectRecords and objectMetadataItem.nameSingular != "dashboard"',
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
availabilityObjectMetadataId: null,
payload: null,
},
{
__typename: 'CommandMenuItem',
@@ -107,6 +131,7 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
conditionalAvailabilityExpression: null,
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
availabilityObjectMetadataId: null,
payload: null,
},
{
__typename: 'CommandMenuItem',
@@ -124,5 +149,6 @@ export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
conditionalAvailabilityExpression: null,
availabilityType: CommandMenuItemAvailabilityType.FALLBACK,
availabilityObjectMetadataId: null,
payload: null,
},
];
@@ -1,10 +1,10 @@
import { type CurrentUserWorkspace } from '@/auth/states/currentUserWorkspaceState';
import { CUSTOM_WORKSPACE_APPLICATION_MOCK } from '@/object-metadata/hooks/__tests__/constants/CustomWorkspaceApplicationMock.test.constant';
import { type WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import {
AUTO_SELECT_FAST_MODEL_ID,
AUTO_SELECT_SMART_MODEL_ID,
} from 'twenty-shared/constants';
import { type CurrentUserWorkspace } from '@/auth/states/currentUserWorkspaceState';
import { CUSTOM_WORKSPACE_APPLICATION_MOCK } from '@/object-metadata/hooks/__tests__/constants/CustomWorkspaceApplicationMock.test.constant';
import { type WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import {
OnboardingStatus,
PermissionFlagType,
@@ -14,6 +14,7 @@ import {
type Workspace,
WorkspaceActivationStatus,
WorkspaceMemberDateFormatEnum,
WorkspaceMemberNumberFormatEnum,
WorkspaceMemberTimeFormatEnum,
} from '~/generated-metadata/graphql';
import { mockBillingPlans } from '~/testing/mock-data/billing-plans';
@@ -33,6 +34,7 @@ type MockedUser = Pick<
| 'userVars'
| 'availableWorkspaces'
| 'hasPassword'
| 'deletedWorkspaceMembers'
> & {
workspaceMember: WorkspaceMember | null;
locale: string;
@@ -161,6 +163,26 @@ export const mockCurrentWorkspace = {
databaseSchema: null,
isTwoFactorAuthenticationEnforced: false,
eventLogRetentionDays: 90,
customDomain: null,
defaultRole: {
__typename: 'Role',
id: '5722c41f-6ed1-42e7-a61c-525a377d4a83',
label: 'Admin',
description: 'Admin role',
icon: 'IconUserCog',
canUpdateAllSettings: true,
canAccessAllTools: true,
isEditable: false,
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: true,
canBeAssignedToUsers: true,
canBeAssignedToAgents: false,
canBeAssignedToApiKeys: true,
},
aiAdditionalInstructions: null,
editableProfileFields: [],
__typename: 'Workspace',
} as const satisfies Workspace;
@@ -181,6 +203,9 @@ export const mockedWorkspaceMemberData: WorkspaceMember = {
dateFormat: WorkspaceMemberDateFormatEnum.DAY_FIRST,
timeFormat: WorkspaceMemberTimeFormatEnum.HOUR_24,
timeZone: 'America/New_York',
userWorkspaceId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6e',
calendarStartDay: 0,
numberFormat: WorkspaceMemberNumberFormatEnum.SYSTEM,
};
export const mockedUserData: MockedUser = {
@@ -196,7 +221,9 @@ export const mockedUserData: MockedUser = {
'a95afad9ff6f0b364e2a3fd3e246a1a852c22b6e55a3ca33745a86c201f9c10d',
workspaceMember: mockedWorkspaceMemberData,
currentWorkspace: mockCurrentWorkspace,
deletedWorkspaceMembers: [],
currentUserWorkspace: {
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6e',
permissionFlags: [
PermissionFlagType.WORKSPACE_MEMBERS,
PermissionFlagType.CONNECTED_ACCOUNTS,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-sdk",
"version": "1.22.0-canary.6",
"version": "1.22.0",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/sdk/index.d.ts",
@@ -4,7 +4,10 @@ import { Command, CommandRunner, Option } from 'nest-commander';
import {
SEED_APPLE_WORKSPACE_ID,
SEED_EMPTY_WORKSPACE_3_ID,
SEED_EMPTY_WORKSPACE_4_ID,
SEED_YCOMBINATOR_WORKSPACE_ID,
SeededEmptyWorkspacesIds,
SeededWorkspacesIds,
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
import { DevSeederService } from 'src/engine/workspace-manager/dev-seeder/services/dev-seeder.service';
@@ -42,12 +45,21 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
? [SEED_APPLE_WORKSPACE_ID]
: [SEED_APPLE_WORKSPACE_ID, SEED_YCOMBINATOR_WORKSPACE_ID];
const emptyWorkspaceIds: SeededEmptyWorkspacesIds[] = [
SEED_EMPTY_WORKSPACE_3_ID,
SEED_EMPTY_WORKSPACE_4_ID,
];
try {
for (const workspaceId of workspaceIds) {
await this.devSeederService.seedDev(workspaceId, {
light: options.light,
});
}
for (const workspaceId of emptyWorkspaceIds) {
await this.devSeederService.seedEmptyWorkspace(workspaceId);
}
} catch (error) {
this.logger.error(error);
this.logger.error(error.stack);
@@ -62,6 +62,9 @@ export class RunInstanceCommandsCommand extends CommandRunner {
await this.checkWorkspaceVersionSafety(options);
await this.runLegacyPendingTypeOrmMigrations();
const activeOrSuspendedWorkspaceIds =
await this.workspaceVersionService.getActiveOrSuspendedWorkspaceIds();
for (const {
command,
name,
@@ -79,9 +82,6 @@ export class RunInstanceCommandsCommand extends CommandRunner {
}
if (options.includeSlow) {
const hasWorkspaces =
await this.workspaceVersionService.hasActiveOrSuspendedWorkspaces();
for (const {
command,
name,
@@ -90,7 +90,7 @@ export class RunInstanceCommandsCommand extends CommandRunner {
await this.instanceUpgradeService.runSlowInstanceCommand({
command,
name,
skipDataMigration: !hasWorkspaces,
skipDataMigration: activeOrSuspendedWorkspaceIds.length === 0,
});
if (result.status === 'failed') {
@@ -119,10 +119,10 @@ export class RunInstanceCommandsCommand extends CommandRunner {
return;
}
const activeWorkspaceIds =
const activeOrSuspendedWorkspaceIds =
await this.workspaceVersionService.getActiveOrSuspendedWorkspaceIds();
if (activeWorkspaceIds.length === 0) {
if (activeOrSuspendedWorkspaceIds.length === 0) {
return;
}
@@ -141,7 +141,7 @@ export class RunInstanceCommandsCommand extends CommandRunner {
const allAtPreviousVersion =
await this.upgradeMigrationService.areAllWorkspacesAtCommand({
commandName: lastWorkspaceCommand.name,
workspaceIds: activeWorkspaceIds,
workspaceIds: activeOrSuspendedWorkspaceIds,
});
if (!allAtPreviousVersion) {
@@ -2202,6 +2202,11 @@ msgstr "Misluk"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "Funksiekode is onveranderd."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "Te veel rekords om gelyktydig op te dateer."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Te veel versoeke. Probeer asseblief later weer."
@@ -2202,6 +2202,11 @@ msgstr "فشل"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "لم تتغير شيفرة الدالة."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "عدد السجلات المراد تحديثها دفعة واحدة كبير جداً."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "عدد كبير جداً من الطلبات. يُرجى المحاولة لاحقاً."
@@ -2202,6 +2202,11 @@ msgstr "Error"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "El codi de la funció no ha canviat."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "Massa registres per actualitzar d'una vegada."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Massa sol·licituds. Torna-ho a provar més tard."
@@ -2202,6 +2202,11 @@ msgstr "Selhalo"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "Kód funkce se nezměnil."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "Příliš mnoho záznamů k aktualizaci najednou."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Příliš mnoho požadavků. Zkuste to prosím později."
@@ -2202,6 +2202,11 @@ msgstr "Mislykkedes"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "Funktionskoden er uændret."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "For mange poster at opdatere på én gang."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "For mange forespørgsler. Prøv igen senere."
@@ -2202,6 +2202,11 @@ msgstr "Fehlgeschlagen"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "Funktionscode ist unverändert."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "Zu viele Datensätze, um sie auf einmal zu aktualisieren."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut."
@@ -2202,6 +2202,11 @@ msgstr "Απέτυχε"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "Ο κώδικας της συνάρτησης δεν έχει αλλάξει."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "Πάρα πολλές εγγραφές για ενημέρωση ταυτόχρονα."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Πάρα πολλά αιτήματα. Δοκιμάστε ξανά αργότερα."
@@ -2197,6 +2197,11 @@ msgstr "Failed"
msgid "Failed Insufficient Permissions"
msgstr "Failed Insufficient Permissions"
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr "Failed to build function dependencies."
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2746,6 +2751,11 @@ msgstr "Function code failed to compile."
msgid "Function code is unchanged."
msgstr "Function code is unchanged."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr "Function execution failed."
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7220,6 +7230,7 @@ msgid "Too many records to update at once."
msgstr "Too many records to update at once."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Too many requests. Please try again later."
@@ -2202,6 +2202,11 @@ msgstr "Fallido"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "El código de la función no ha cambiado."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "Demasiados registros para actualizar de una sola vez."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Demasiadas solicitudes. Por favor, inténtelo de nuevo más tarde."
@@ -2202,6 +2202,11 @@ msgstr "Epäonnistui"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "Funktion koodia ei ole muutettu."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "Liian monta tietuetta päivitettäväksi kerralla."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Liian monta pyyntöä. Yritä myöhemmin uudelleen."
@@ -2202,6 +2202,11 @@ msgstr "Échec"
msgid "Failed Insufficient Permissions"
msgstr ""
#. js-lingui-id: rl11Jd
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Failed to build function dependencies."
msgstr ""
#. js-lingui-id: E7Dsiq
#: src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts
msgid "Failed to create evaluation. Please try again."
@@ -2751,6 +2756,11 @@ msgstr ""
msgid "Function code is unchanged."
msgstr "Le code de la fonction est inchangé."
#. js-lingui-id: rqDhd1
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution failed."
msgstr ""
#. js-lingui-id: Ho3UAo
#: src/engine/metadata-modules/logic-function/logic-function.exception.ts
msgid "Function execution limit reached."
@@ -7225,6 +7235,7 @@ msgid "Too many records to update at once."
msgstr "Trop d'enregistrements à mettre à jour en une seule fois."
#. js-lingui-id: M68C8m
#: src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger.exception.ts
#: src/engine/core-modules/email-verification/email-verification.exception.ts
msgid "Too many requests. Please try again later."
msgstr "Trop de requêtes. Veuillez réessayer plus tard."
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

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