Modal API Refactoring (#12062)

# Modal API Refactoring

This PR refactors the modal system to use an imperative approach for
setting hotkey scopes, addressing race conditions that occurred with the
previous effect-based implementation.

Fixes #11986
Closes #12087

## Key Changes:

- **New Modal API**: Introduced a `useModal` hook with `openModal`,
`closeModal`, and `toggleModal` functions, similar to the existing
dropdown API
- **Modal Identification**: Added a `modalId` prop to uniquely identify
modals
- **State Management**: Introduced `isModalOpenedComponentState` and
removed individual boolean state atoms (like
`isRemoveSortingModalOpenState`)
- **Modal Constants**: Added consistent modal ID constants (e.g.,
`FavoriteFolderDeleteModalId`, `RecordIndexRemoveSortingModalId`) for
better maintainability
- **Mount Effects**: Created mount effect components (like
`AuthModalMountEffect`) to handle initial modal opening where needed

## Implementation Details:

- Modified `Modal` and `ConfirmationModal` components to accept the new
`modalId` prop
- Added a component-state-based approach using
`ModalComponentInstanceContext` to track modal state
- Introduced imperative modal handlers that properly manage hotkey
scopes
- Components like `ActionModal` and `AttachmentList` now use the new
`useModal` hook for better control over modal state

## Benefits:

- **Race Condition Prevention**: Hotkey scopes are now set imperatively,
eliminating race conditions
- **Consistent API**: Modal and dropdown now share similar patterns,
improving developer experience

## Tests to do before merging:

1. Action Modals (Modal triggered by an action, for example the delete
action)

2. Auth Modal (`AuthModal.tsx` and `AuthModalMountEffect.tsx`)
   - Test that auth modal opens automatically on mount
   - Verify authentication flow works properly

3. Email Verification Sent Modal (in `SignInUp.tsx`)
   - Verify this modal displays correctly

4. Attachment Preview Modal (in `AttachmentList.tsx`)
   - Test opening preview modal by clicking on attachments
   - Verify close, download functionality works
   - Test modal navigation and interactions

5. Favorite Folder Delete Modal (`CurrentWorkspaceMemberFavorites.tsx`)
   - Test deletion confirmation flow
- Check that modal opens when attempting to delete folders with
favorites

6. Record Board Remove Sorting Modal (`RecordBoard.tsx` using
`RecordIndexRemoveSortingModalId`)
- Test that modal appears when trying to drag records with sorting
enabled
   - Verify sorting removal works correctly

7. Record Group Reorder Confirmation Modal
(`RecordGroupReorderConfirmationModal.tsx`)
   - Test group reordering with sorting enabled
   - Verify confirmation modal properly handles sorting removal

8. Confirmation Modal (base component used by several modals)
   - Test all variants with different confirmation options

For each modal, verify:
- Opening/closing behavior
- Hotkey support (Esc to close, Enter to confirm where applicable)
- Click outside behavior
- Proper z-index stacking
- Any modal-specific functionality
This commit is contained in:
Raphaël Bosi
2025-05-16 17:04:22 +02:00
committed by GitHub
parent 8334fe9528
commit 6554947671
94 changed files with 1268 additions and 563 deletions

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Is jy seker jy wil hierdie rekord uitvee? Dit kan herstel word vanaf die Command menu."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Kan nie API-name vir standaardobjekte verander nie"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Kanselleer"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Bestaande voorwerpe"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Verlaat Volskerm"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "هل أنت متأكد أنك تريد حذف هذا السجل؟ يمكن استرجاعه من قائمة الأوامر."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "لا يمكن تغيير أسماء API للكائنات القياسي
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "إلغاء"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "الكائنات الحالية"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "خروج من وضع ملء الشاشة"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Esteu segur que voleu suprimir aquest registre? Es pot recuperar des del menú d'ordres."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "No es poden canviar els noms de les API per a objectes estàndard"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Cancel·la"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Objectes existents"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Sortir de la pantalla completa"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Jste si jisti, že chcete odstranit tento záznam? Lze jej obnovit z menu Příkazy."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Nelze změnit názvy API pro standardní objekty"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Storno"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Stávající objekty"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Ukončit režim celé obrazovky"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Er du sikker på, at du vil slette denne post? Den kan gendannes fra Kommandomenuen."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Kan ikke ændre API-navne for standardobjekter"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Annuller"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Eksisterende objekter"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Afslut fuld skærm"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Sind Sie sicher, dass Sie diesen Datensatz löschen möchten? Er kann aus dem Befehlsmenü wiederhergestellt werden."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "API-Namen für Standardobjekte können nicht geändert werden"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Abbrechen"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Vorhandene Objekte"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Vollbildmodus beenden"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την εγγραφή; Μπορεί να αποκατασταθεί από το μενού Εντολών."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Δεν είναι δυνατή η αλλαγή των ονομάτων A
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Ακύρωση"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Υπάρχοντα αντικείμενα"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Έξοδος από την Πλήρη Οθόνη"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -612,7 +612,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Are you sure you want to delete this record? It can be recovered from the Command menu."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr "Are you sure? Your current information will not be saved."
@ -793,7 +793,7 @@ msgstr "Can't change API names for standard objects"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Cancel"
@ -2031,7 +2031,7 @@ msgid "Existing objects"
msgstr "Existing objects"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr "Exit"
@ -2041,7 +2041,7 @@ msgstr "Exit"
#~ msgstr "Exit Full Screen"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr "Exit import flow"

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "¿Está seguro de que desea eliminar este registro? Se puede recuperar del menú de Comandos."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "No se pueden cambiar los nombres de la API para los objetos estándar"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Cancelar"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Objetos existentes"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Salir de pantalla completa"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Haluatko varmasti poistaa tämän tietueen? Se voidaan palauttaa komento-valikosta."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "API-nimiä ei voi muuttaa vakio-objekteille"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Peruuta"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Olemassa olevat objektit"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Poistu koko näytöstä"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Êtes-vous sûr de vouloir supprimer cet enregistrementa0? Il peut être récupéré depuis le menu Commande."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Impossible de modifier les noms d'API pour les objets standard"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Annuler"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Objets existants"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Quitter le plein écran"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "האם אתה בטוח שברצונך למחוק את הרשומה הזו? ניתן לשחזר אותה מתפריט Command."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "לא ניתן לשנות שמות API עבור עצמים סטנדרטי
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "בטל"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "אובייקטים קיימים"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "צא ממסך מלא"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Biztosan törölni szeretné ezt a rekordot? Visszaállítható a Parancs menüből."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Nem változtathatók meg a szabványos objektumok API-nevei"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Mégse"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Meglévő objektumok"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Kilépés teljes képernyőből"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Sei sicuro di voler eliminare questo record? Può essere recuperato dal menu Comando."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Impossibile modificare i nomi API per gli oggetti standard"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Annulla"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Oggetti esistenti"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Esci dalla modalità a schermo intero"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "このレコードを本当に削除しますか? コマンドメニューから復元できます。"
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "標準オブジェクトのAPI名は変更できません"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "キャンセル"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "既存のオブジェクト"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "フルスクリーンを終了"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "이 기록을 삭제하시겠습니까? Command 메뉴에서 복구할 수 있습니다."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "표준 객체에 대한 API 이름을 변경할 수 없습니다."
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "취소"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "기존 개체"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "전체 화면 종료"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Weet je zeker dat je dit record wilt verwijderen? Het kan worden hersteld vanuit het Commandemenu."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Kan API-namen niet wijzigen voor standaardobjecten"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Annuleren"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Bestaande objecten"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Volledig scherm afsluiten"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Er du sikker på at du vil slette denne posten? Den kan gjenopprettes fra Kommandomenyen."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Kan ikke endre API-navn for standardobjekter"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Avbryt"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Eksisterende objekter"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Avslutt fullskjerm"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Czy jesteś pewien, że chcesz usunąć ten rekord? Można go odzyskać z menu Command."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Nie można zmienić nazw API dla standardowych obiektów"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Anuluj"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Istniejące obiekty"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Zakończ tryb pełnoekranowy"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -609,7 +609,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr ""
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -790,7 +790,7 @@ msgstr ""
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr ""
@ -2028,7 +2028,7 @@ msgid "Existing objects"
msgstr ""
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2038,7 +2038,7 @@ msgstr ""
#~ msgstr ""
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Tem certeza de que deseja excluir este registro? Ele pode ser recuperado no menu Comando."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Não é possível alterar nomes de API para objetos padrão"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Cancelar"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Objetos existentes"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Sair do modo tela cheia"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Tem certeza de que deseja excluir este registro? Ele pode ser recuperado do menu Comando."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Não é possível alterar os nomes de API para objetos padrão"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Cancelar"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Objetos existentes"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Sair do modo de tela cheia"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Sigur doriți să ștergeți această înregistrare? Ea poate fi recuperată din meniul Command."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Nu se pot schimba numele API pentru obiectele standard"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Anulare"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Obiecte existente"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Ieșiți din modul Ecran complet"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Да ли сте сигурни да желите да обришете овај запис? Може се повратити из командног менија."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Не могу се мењати API имена за стандардне
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Откажи"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Постојећи објекти"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Изађи из целог екрана"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Är du säker på att du vill ta bort denna post? Den kan återställas från kommandomenyn."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Kan inte ändra API-namn för standardobjekt"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Avbryt"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Befintliga objekt"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Avsluta helskärm"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Bu kaydı silmek istediğinizden emin misiniz? Komut menüsünden kurtarılabilir."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Standart nesneler için API adları değiştirilemez"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "İptal"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Mevcut nesneler"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Tam Ekrandan Çık"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Ви впевнені, що хочете видалити цей запис? Його можна відновити з меню Command."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Неможливо змінити API імена для стандарт
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Скасувати"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "Існуючі об'єкти"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Вийти з повноекранного режиму"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "Bạn có chắc chắn muốn xóa bản ghi này không? Nó có thể được phục hồi từ menu Command."
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "Không thể thay đổi tên API cho các đối tượng tiêu chuẩn
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "Hủy bỏ"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "\"Đối tượng hiện hữu\""
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "Thoát chế độ toàn màn hình"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "您确定要删除此记录吗?它可以从命令菜单中恢复。"
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "无法更改标准对象的 API 名称"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "取消"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "现有对象"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "退出全屏"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -617,7 +617,7 @@ msgid "Are you sure you want to delete this record? It can be recovered from the
msgstr "您確定要刪除此記錄嗎? 它可以從命令菜單中恢復。"
#. js-lingui-id: rnCqBK
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Are you sure? Your current information will not be saved."
msgstr ""
@ -798,7 +798,7 @@ msgstr "無法更改標準物件的 API 名稱"
#: src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx
#: src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
#: src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
#: src/modules/settings/components/SaveAndCancelButtons/CancelButton.tsx
msgid "Cancel"
msgstr "取消"
@ -2036,7 +2036,7 @@ msgid "Existing objects"
msgstr "現有對象"
#. js-lingui-id: ydzS9x
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit"
msgstr ""
@ -2046,7 +2046,7 @@ msgstr ""
#~ msgstr "退出全螢幕"
#. js-lingui-id: XB4onr
#: src/modules/spreadsheet-import/components/ModalCloseButton.tsx
#: src/modules/spreadsheet-import/components/SpreadSheetImportModalCloseButton.tsx
msgid "Exit import flow"
msgstr ""

View File

@ -1,10 +1,14 @@
import { ReactNode, useCallback, useContext, useState } from 'react';
import { ReactNode, useContext } from 'react';
import { createPortal } from 'react-dom';
import { ActionDisplay } from '@/action-menu/actions/display/components/ActionDisplay';
import { ActionConfigContext } from '@/action-menu/contexts/ActionConfigContext';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useCloseActionMenu } from '@/action-menu/hooks/useCloseActionMenu';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ButtonAccent } from 'twenty-ui/input';
export type ActionModalProps = {
@ -24,11 +28,7 @@ export const ActionModal = ({
confirmButtonAccent = 'danger',
isLoading = false,
}: ActionModalProps) => {
const [isOpen, setIsOpen] = useState(false);
const handleOpen = useCallback(() => {
setIsOpen(true);
}, []);
const { openModal } = useModal();
const { closeActionMenu } = useCloseActionMenu();
@ -38,19 +38,28 @@ export const ActionModal = ({
};
const actionConfig = useContext(ActionConfigContext);
const { actionMenuType } = useContext(ActionMenuContext);
const modalId = `${actionConfig?.key}-action-modal-${actionMenuType}`;
const isModalOpened = useRecoilComponentValueV2(
isModalOpenedComponentState,
modalId,
);
if (!actionConfig) {
return null;
}
const handleClick = () => openModal(modalId);
return (
<>
<ActionDisplay onClick={handleOpen} />
{isOpen &&
<ActionDisplay onClick={handleClick} />
{isModalOpened &&
createPortal(
<ConfirmationModal
isOpen={isOpen}
setIsOpen={setIsOpen}
modalId={modalId}
title={title}
subtitle={subtitle}
onConfirmClick={handleConfirmClick}

View File

@ -4,16 +4,18 @@ import { HttpResponse, graphql } from 'msw';
import { Calendar } from '@/activities/calendar/components/Calendar';
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/graphql/queries/getTimelineCalendarEventsFromCompanyId';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedTimelineCalendarEvents } from '~/testing/mock-data/timeline-calendar-events';
import { ComponentDecorator } from 'twenty-ui/testing';
const meta: Meta<typeof Calendar> = {
title: 'Modules/Activities/Calendar/Calendar',
component: Calendar,
decorators: [
I18nFrontDecorator,
ComponentDecorator,
ObjectMetadataItemsDecorator,
SnackBarDecorator,

View File

@ -11,10 +11,11 @@ import { Modal } from '@/ui/layout/modal/components/Modal';
import { useRecoilValue } from 'recoil';
import { ActivityList } from '@/activities/components/ActivityList';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { AttachmentRow } from './AttachmentRow';
import { IconButton } from 'twenty-ui/input';
import { IconDownload, IconX } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
import { AttachmentRow } from './AttachmentRow';
const DocumentViewer = lazy(() =>
import('@/activities/files/components/DocumentViewer').then((module) => ({
@ -112,6 +113,8 @@ const StyledButtonContainer = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
export const PREVIEW_MODAL_ID = 'preview-modal';
export const AttachmentList = ({
targetableObject,
title,
@ -122,10 +125,13 @@ export const AttachmentList = ({
const [isDraggingFile, setIsDraggingFile] = useState(false);
const [previewedAttachment, setPreviewedAttachment] =
useState<Attachment | null>(null);
const isAttachmentPreviewEnabled = useRecoilValue(
isAttachmentPreviewEnabledState,
);
const { openModal, closeModal } = useModal();
const onUploadFile = async (file: File) => {
await uploadAttachmentFile(file, targetableObject);
};
@ -133,9 +139,11 @@ export const AttachmentList = ({
const handlePreview = (attachment: Attachment) => {
if (!isAttachmentPreviewEnabled) return;
setPreviewedAttachment(attachment);
openModal(PREVIEW_MODAL_ID);
};
const handleClosePreview = () => {
closeModal(PREVIEW_MODAL_ID);
setPreviewedAttachment(null);
};
@ -177,7 +185,12 @@ export const AttachmentList = ({
</StyledContainer>
)}
{previewedAttachment && isAttachmentPreviewEnabled && (
<StyledModal size="large" isClosable onClose={handleClosePreview}>
<StyledModal
modalId={PREVIEW_MODAL_ID}
size="large"
isClosable
onClose={handleClosePreview}
>
<StyledModalHeader>
<StyledHeader>
<StyledModalTitle>{previewedAttachment.name}</StyledModalTitle>

View File

@ -2,14 +2,16 @@ import { Meta, StoryObj } from '@storybook/react';
import { HttpResponse, graphql } from 'msw';
import { EventCardCalendarEvent } from '@/activities/timeline-activities/rows/calendar/components/EventCardCalendarEvent';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
const meta: Meta<typeof EventCardCalendarEvent> = {
title: 'Modules/TimelineActivities/Rows/CalendarEvent/EventCardCalendarEvent',
component: EventCardCalendarEvent,
decorators: [
I18nFrontDecorator,
ComponentDecorator,
ObjectMetadataItemsDecorator,
SnackBarDecorator,

View File

@ -1,3 +1,5 @@
import { AuthModalMountEffect } from '@/auth/components/AuthModalMountEffect';
import { AUTH_MODAL_ID } from '@/auth/constants/AuthModalId';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import styled from '@emotion/styled';
@ -13,9 +15,12 @@ type AuthModalProps = {
};
export const AuthModal = ({ children }: AuthModalProps) => (
<Modal padding={'none'} modalVariant="primary">
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
<StyledContent>{children}</StyledContent>
</ScrollWrapper>
</Modal>
<>
<AuthModalMountEffect />
<Modal modalId={AUTH_MODAL_ID} padding={'none'} modalVariant="primary">
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
<StyledContent>{children}</StyledContent>
</ScrollWrapper>
</Modal>
</>
);

View File

@ -0,0 +1,14 @@
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useEffect } from 'react';
import { AUTH_MODAL_ID } from '../constants/AuthModalId';
export const AuthModalMountEffect = () => {
const { openModal } = useModal();
useEffect(() => {
openModal(AUTH_MODAL_ID);
}, [openModal]);
return null;
};

View File

@ -0,0 +1 @@
export const AUTH_MODAL_ID = 'auth-modal';

View File

@ -10,7 +10,11 @@ const RenderWithModal = (
args: React.ComponentProps<typeof EmailVerificationSent>,
) => {
return (
<Modal padding="none" modalVariant="primary">
<Modal
modalId={'email-verification-sent-modal'}
padding="none"
modalVariant="primary"
>
<Modal.Content isVerticalCentered isHorizontalCentered>
<EmailVerificationSent email={args.email} isError={args.isError} />
</Modal.Content>

View File

@ -1,6 +1,7 @@
import { FavoriteFolderNavigationDrawerItemDropdown } from '@/favorites/components/FavoriteFolderNavigationDrawerItemDropdown';
import { FavoriteIcon } from '@/favorites/components/FavoriteIcon';
import { FavoritesDroppable } from '@/favorites/components/FavoritesDroppable';
import { FAVORITE_FOLDER_DELETE_MODAL_ID } from '@/favorites/constants/FavoriteFolderDeleteModalId';
import { FavoritesDragContext } from '@/favorites/contexts/FavoritesDragContext';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useDeleteFavoriteFolder } from '@/favorites/hooks/useDeleteFavoriteFolder';
@ -11,20 +12,22 @@ import { ProcessedFavorite } from '@/favorites/utils/sortFavorites';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { NavigationDrawerInput } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerInput';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer';
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { Droppable } from '@hello-pangea/dnd';
import { useContext, useState } from 'react';
import { createPortal } from 'react-dom';
import { useLocation } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
import { IconFolder, IconFolderOpen, IconHeartOff } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
type CurrentWorkspaceMemberFavoritesProps = {
folder: {
folderId: string;
@ -46,7 +49,7 @@ export const CurrentWorkspaceMemberFavorites = ({
const [favoriteFolderName, setFavoriteFolderName] = useState(
folder.folderName,
);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { openModal } = useModal();
const [openFavoriteFolderIds, setOpenFavoriteFolderIds] = useRecoilState(
openFavoriteFolderIdsState,
@ -102,9 +105,11 @@ export const CurrentWorkspaceMemberFavorites = ({
setIsFavoriteFolderRenaming(false);
};
const modalId = `${FAVORITE_FOLDER_DELETE_MODAL_ID}-${folder.folderId}`;
const handleFavoriteFolderDelete = async () => {
if (folder.favorites.length > 0) {
setIsDeleteModalOpen(true);
openModal(modalId);
closeFavoriteFolderEditDropdown();
} else {
await deleteFavoriteFolder(folder.folderId);
@ -114,7 +119,6 @@ export const CurrentWorkspaceMemberFavorites = ({
const handleConfirmDelete = async () => {
await deleteFavoriteFolder(folder.folderId);
setIsDeleteModalOpen(false);
};
const rightOptions = (
@ -126,6 +130,11 @@ export const CurrentWorkspaceMemberFavorites = ({
/>
);
const isModalOpened = useRecoilComponentValueV2(
isModalOpenedComponentState,
modalId,
);
return (
<>
<NavigationDrawerItemsCollapsableContainer
@ -207,17 +216,17 @@ export const CurrentWorkspaceMemberFavorites = ({
</AnimatedExpandableContainer>
</NavigationDrawerItemsCollapsableContainer>
{createPortal(
<ConfirmationModal
isOpen={isDeleteModalOpen}
setIsOpen={setIsDeleteModalOpen}
title={`Remove ${folder.favorites.length} ${folder.favorites.length > 1 ? 'favorites' : 'favorite'}?`}
subtitle={`This action will delete this favorite folder ${folder.favorites.length > 1 ? `and all ${folder.favorites.length} favorites` : 'and the favorite'} inside. Do you want to continue?`}
onConfirmClick={handleConfirmDelete}
confirmButtonText="Delete Folder"
/>,
document.body,
)}
{isModalOpened &&
createPortal(
<ConfirmationModal
modalId={modalId}
title={`Remove ${folder.favorites.length} ${folder.favorites.length > 1 ? 'favorites' : 'favorite'}?`}
subtitle={`This action will delete this favorite folder ${folder.favorites.length > 1 ? `and all ${folder.favorites.length} favorites` : 'and the favorite'} inside. Do you want to continue?`}
onConfirmClick={handleConfirmDelete}
confirmButtonText="Delete Folder"
/>,
document.body,
)}
</>
);
};

View File

@ -19,13 +19,13 @@ export const FavoriteFolderNavigationDrawerItemDropdown = ({
closeDropdown,
}: FavoriteFolderNavigationDrawerItemDropdownProps) => {
const handleRename = () => {
onRename();
closeDropdown();
onRename();
};
const handleDelete = () => {
onDelete();
closeDropdown();
onDelete();
};
return (

View File

@ -0,0 +1 @@
export const FAVORITE_FOLDER_DELETE_MODAL_ID = 'favorite-folder-delete-modal';

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
import { useContext, useRef } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useRecoilCallback } from 'recoil';
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
@ -19,11 +19,12 @@ import { RecordBoardComponentInstanceContext } from '@/object-record/record-boar
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
@ -120,9 +121,7 @@ export const RecordBoard = () => {
},
});
const setIsRemoveSortingModalOpen = useSetRecoilState(
isRemoveSortingModalOpenState,
);
const { openModal } = useModal();
const handleDragEnd: OnDragEndResponder = useRecoilCallback(
({ snapshot }) =>
@ -130,7 +129,7 @@ export const RecordBoard = () => {
if (!result.destination) return;
if (currentRecordSorts.length > 0) {
setIsRemoveSortingModalOpen(true);
openModal(RECORD_INDEX_REMOVE_SORTING_MODAL_ID);
return;
}
@ -189,7 +188,7 @@ export const RecordBoard = () => {
recordIndexRecordIdsByGroupFamilyState,
selectFieldMetadataItem,
updateOneRecord,
setIsRemoveSortingModalOpen,
openModal,
currentRecordSorts,
],
);

View File

@ -1,9 +1,8 @@
import { isRecordGroupReorderConfirmationModalVisibleState } from '@/object-record/record-group/states/isRecordGroupReorderConfirmationModalVisibleState';
import { RECORD_GROUP_REORDER_CONFIRMATION_MODAL_ID } from '@/object-record/record-group/constants/RecordGroupReorderConfirmationModalId';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { createPortal } from 'react-dom';
import { useRecoilState } from 'recoil';
type RecordGroupReorderConfirmationModalProps = {
onConfirmClick: () => void;
@ -12,23 +11,13 @@ type RecordGroupReorderConfirmationModalProps = {
export const RecordGroupReorderConfirmationModal = ({
onConfirmClick,
}: RecordGroupReorderConfirmationModalProps) => {
const [
isRecordGroupReorderConfirmationModalVisible,
setIsRecordGroupReorderConfirmationModalVisible,
] = useRecoilState(isRecordGroupReorderConfirmationModalVisibleState);
const recordGroupSort = useRecoilComponentValueV2(
recordIndexRecordGroupSortComponentState,
);
if (!isRecordGroupReorderConfirmationModalVisible) {
return null;
}
return createPortal(
<ConfirmationModal
isOpen={isRecordGroupReorderConfirmationModalVisible}
setIsOpen={setIsRecordGroupReorderConfirmationModalVisible}
modalId={RECORD_GROUP_REORDER_CONFIRMATION_MODAL_ID}
title="Group sorting"
subtitle={`Would you like to remove ${recordGroupSort} group sorting?`}
onConfirmClick={onConfirmClick}

View File

@ -0,0 +1,2 @@
export const RECORD_GROUP_REORDER_CONFIRMATION_MODAL_ID =
'record-group-reorder-confirmation-modal';

View File

@ -1,16 +1,16 @@
import { RECORD_GROUP_REORDER_CONFIRMATION_MODAL_ID } from '@/object-record/record-group/constants/RecordGroupReorderConfirmationModalId';
import { useReorderRecordGroups } from '@/object-record/record-group/hooks/useReorderRecordGroups';
import { isRecordGroupReorderConfirmationModalVisibleState } from '@/object-record/record-group/states/isRecordGroupReorderConfirmationModalVisibleState';
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
import { recordIndexRecordGroupIsDraggableSortComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexRecordGroupIsDraggableSortComponentSelector';
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { ViewType } from '@/views/types/ViewType';
import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useState } from 'react';
import { useSetRecoilState } from 'recoil';
type UseRecordGroupReorderConfirmationModalParams = {
recordIndexId: string;
@ -26,9 +26,7 @@ export const useRecordGroupReorderConfirmationModal = ({
const { goBackToPreviousDropdownFocusId } =
useGoBackToPreviousDropdownFocusId();
const setIsRecordGroupReorderConfirmationModalVisible = useSetRecoilState(
isRecordGroupReorderConfirmationModalVisibleState,
);
const { openModal } = useModal();
const [pendingDragEndHandlerParams, setPendingDragEndHandlerParams] =
useState<Parameters<OnDragEndResponder> | null>(null);
@ -58,7 +56,7 @@ export const useRecordGroupReorderConfirmationModal = ({
const handleDragEndWithModal: OnDragEndResponder = (result, provided) => {
if (!isDragableSortRecordGroup) {
setIsRecordGroupReorderConfirmationModalVisible(true);
openModal(RECORD_GROUP_REORDER_CONFIRMATION_MODAL_ID);
setActiveDropdownFocusIdAndMemorizePrevious(null);
setPendingDragEndHandlerParams([result, provided]);
} else {

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const isRecordGroupReorderConfirmationModalVisibleState = atom<boolean>({
key: 'isRecordGroupReorderConfirmationModalVisibleState',
default: false,
});

View File

@ -9,7 +9,10 @@ import { RecordBoardBodyEscapeHotkeyEffect } from '@/object-record/record-board/
import { RecordBoardHotkeyEffect } from '@/object-record/record-board/components/RecordBoardHotkeyEffect';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal';
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
type RecordIndexBoardContainerProps = {
recordBoardId: string;
viewBarId: string;
@ -39,6 +42,11 @@ export const RecordIndexBoardContainer = ({
shouldMatchRootQueryFilter: true,
});
const isRecordIndexRemoveSortingModalOpened = useRecoilComponentValueV2(
isModalOpenedComponentState,
RECORD_INDEX_REMOVE_SORTING_MODAL_ID,
);
if (!selectFieldMetadataItem) {
return;
}
@ -55,7 +63,9 @@ export const RecordIndexBoardContainer = ({
}}
>
<RecordBoard />
<RecordIndexRemoveSortingModal />
{isRecordIndexRemoveSortingModalOpened && (
<RecordIndexRemoveSortingModal />
)}
<RecordBoardHotkeyEffect />
<RecordBoardBodyEscapeHotkeyEffect />
</RecordBoardContext.Provider>

View File

@ -1,8 +1,6 @@
import { useRecoilState } from 'recoil';
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
import { useRemoveRecordSort } from '@/object-record/record-sort/hooks/useRemoveRecordSort';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -14,8 +12,6 @@ export const RecordIndexRemoveSortingModal = () => {
const fieldMetadataIds = currentRecordSorts.map(
(viewSort) => viewSort.fieldMetadataId,
);
const [isRemoveSortingModalOpen, setIsRemoveSortingModalOpen] =
useRecoilState(isRemoveSortingModalOpenState);
const { removeRecordSort } = useRemoveRecordSort();
@ -26,15 +22,12 @@ export const RecordIndexRemoveSortingModal = () => {
};
return (
<>
<ConfirmationModal
isOpen={isRemoveSortingModalOpen}
setIsOpen={setIsRemoveSortingModalOpen}
title={'Remove sorting?'}
subtitle={'This is required to enable manual row reordering.'}
onConfirmClick={handleRemoveClick}
confirmButtonText={'Remove Sorting'}
/>
</>
<ConfirmationModal
modalId={RECORD_INDEX_REMOVE_SORTING_MODAL_ID}
title={'Remove sorting?'}
subtitle={'This is required to enable manual row reordering.'}
onConfirmClick={handleRemoveClick}
confirmButtonText={'Remove Sorting'}
/>
);
};

View File

@ -1,8 +1,11 @@
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext';
import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal';
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
type RecordIndexTableContainerProps = {
recordTableId: string;
@ -15,6 +18,11 @@ export const RecordIndexTableContainer = ({
}: RecordIndexTableContainerProps) => {
const { objectNameSingular } = useRecordIndexContextOrThrow();
const isRecordIndexRemoveSortingModalOpened = useRecoilComponentValueV2(
isModalOpenedComponentState,
RECORD_INDEX_REMOVE_SORTING_MODAL_ID,
);
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular,
});
@ -34,7 +42,9 @@ export const RecordIndexTableContainer = ({
viewBarId={viewBarId}
updateRecordMutation={updateEntity}
/>
<RecordIndexRemoveSortingModal />
{isRecordIndexRemoveSortingModalOpened && (
<RecordIndexRemoveSortingModal />
)}
</>
);
};

View File

@ -0,0 +1,2 @@
export const RECORD_INDEX_REMOVE_SORTING_MODAL_ID =
'record-index-remove-sorting-modal';

View File

@ -1,7 +1,7 @@
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useCallback, useContext, useState } from 'react';
import { useCallback, useContext } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
@ -34,6 +34,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { createPortal } from 'react-dom';
import {
IconChevronDown,
@ -87,6 +88,8 @@ const StyledClickableZone = styled.div`
const MotionIconChevronDown = motion.create(IconChevronDown);
const DELETE_RELATION_MODAL_ID = 'delete-relation-modal';
type RecordDetailRelationRecordsListItemProps = {
isExpanded: boolean;
onClick: (relationRecordId: string) => void;
@ -100,8 +103,7 @@ export const RecordDetailRelationRecordsListItem = ({
}: RecordDetailRelationRecordsListItemProps) => {
const { fieldDefinition } = useContext(FieldContext);
const [isDeleteRelationModalOpen, setIsDeleteRelationModalOpen] =
useState(false);
const { openModal } = useModal();
const {
relationFieldMetadataId,
@ -173,13 +175,12 @@ export const RecordDetailRelationRecordsListItem = ({
};
const handleDelete = async () => {
setIsDeleteRelationModalOpen(true);
closeDropdown();
openModal(DELETE_RELATION_MODAL_ID);
};
const handleConfirmDelete = async () => {
await deleteOneRelationRecord(relationRecord.id);
setIsDeleteRelationModalOpen(false);
};
const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
@ -306,8 +307,7 @@ export const RecordDetailRelationRecordsListItem = ({
</AnimatedEaseInOut>
{createPortal(
<ConfirmationModal
isOpen={isDeleteRelationModalOpen}
setIsOpen={setIsDeleteRelationModalOpen}
modalId={DELETE_RELATION_MODAL_ID}
title={`Delete Related ${relationObjectTypeName}`}
subtitle={
<>

View File

@ -1,14 +1,15 @@
import { DragDropContext, DropResult } from '@hello-pangea/dnd';
import { ReactNode } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useRecoilCallback } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -33,15 +34,13 @@ export const RecordTableBodyDragDropContextProvider = ({
currentRecordSortsComponentState,
);
const setIsRemoveSortingModalOpen = useSetRecoilState(
isRemoveSortingModalOpenState,
);
const { openModal } = useModal();
const handleDragEnd = useRecoilCallback(
({ snapshot }) =>
(result: DropResult) => {
if (currentRecordSorts.length > 0) {
setIsRemoveSortingModalOpen(true);
openModal(RECORD_INDEX_REMOVE_SORTING_MODAL_ID);
return;
}
@ -100,10 +99,10 @@ export const RecordTableBodyDragDropContextProvider = ({
});
},
[
currentRecordSorts.length,
recordIndexAllRecordIdsSelector,
setIsRemoveSortingModalOpen,
updateOneRow,
currentRecordSorts,
openModal,
],
);

View File

@ -1,15 +1,16 @@
import { DragDropContext, DropResult } from '@hello-pangea/dnd';
import { ReactNode } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useRecoilCallback } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { isDefined } from 'twenty-shared/utils';
@ -26,9 +27,7 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({
objectNameSingular,
});
const setIsRemoveSortingModalOpen = useSetRecoilState(
isRemoveSortingModalOpenState,
);
const { openModal } = useModal();
const recordIdsByGroupFamilyState = useRecoilComponentCallbackStateV2(
recordIndexRecordIdsByGroupComponentFamilyState,
@ -73,7 +72,7 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({
}
if (currentRecordSorts.length > 0) {
setIsRemoveSortingModalOpen(true);
openModal(RECORD_INDEX_REMOVE_SORTING_MODAL_ID);
return;
}
@ -130,11 +129,11 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({
});
},
[
currentRecordSortsCallbackState,
objectMetadataItem.fields,
recordIdsByGroupFamilyState,
updateOneRow,
setIsRemoveSortingModalOpen,
currentRecordSortsCallbackState,
openModal,
],
);

View File

@ -1,5 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const isRemoveSortingModalOpenState = createState<boolean>({
key: 'isRemoveSortingModalOpenState',
defaultValue: false,
});

View File

@ -1,5 +1,3 @@
import { useState } from 'react';
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
@ -9,6 +7,7 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { Trans, useLingui } from '@lingui/react/macro';
import {
IconCalendarEvent,
@ -25,13 +24,14 @@ type SettingsAccountsRowDropdownMenuProps = {
account: ConnectedAccount;
};
const DELETE_ACCOUNT_MODAL_ID = 'delete-account-modal';
export const SettingsAccountsRowDropdownMenu = ({
account,
}: SettingsAccountsRowDropdownMenuProps) => {
const dropdownId = `settings-account-row-${account.id}`;
const { t } = useLingui();
const [isDeleteAccountModalOpen, setIsDeleteAccountModalOpen] =
useState(false);
const { openModal } = useModal();
const navigate = useNavigateSettings();
const { closeDropdown } = useDropdown(dropdownId);
@ -43,7 +43,6 @@ export const SettingsAccountsRowDropdownMenu = ({
const deleteAccount = async () => {
await destroyOneRecord(account.id);
setIsDeleteAccountModalOpen(false);
};
return (
@ -89,16 +88,15 @@ export const SettingsAccountsRowDropdownMenu = ({
LeftIcon={IconTrash}
text={t`Remove account`}
onClick={() => {
setIsDeleteAccountModalOpen(true);
closeDropdown();
openModal(DELETE_ACCOUNT_MODAL_ID);
}}
/>
</DropdownMenuItemsContainer>
}
/>
<ConfirmationModal
isOpen={isDeleteAccountModalOpen}
setIsOpen={setIsDeleteAccountModalOpen}
modalId={DELETE_ACCOUNT_MODAL_ID}
title={t`Data deletion`}
subtitle={
<Trans>

View File

@ -1,18 +1,18 @@
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useAuth } from '@/auth/hooks/useAuth';
import { currentUserState } from '@/auth/states/currentUserState';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useLingui } from '@lingui/react/macro';
import { useDeleteUserAccountMutation } from '~/generated/graphql';
import { Button } from 'twenty-ui/input';
import { H2Title } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { useDeleteUserAccountMutation } from '~/generated/graphql';
const DELETE_ACCOUNT_MODAL_ID = 'delete-account-modal';
export const DeleteAccount = () => {
const { t } = useLingui();
const [isDeleteAccountModalOpen, setIsDeleteAccountModalOpen] =
useState(false);
const { openModal } = useModal();
const [deleteUserAccount] = useDeleteUserAccountMutation();
const currentUser = useRecoilValue(currentUserState);
@ -33,7 +33,7 @@ export const DeleteAccount = () => {
<Button
accent="danger"
onClick={() => setIsDeleteAccountModalOpen(true)}
onClick={() => openModal(DELETE_ACCOUNT_MODAL_ID)}
variant="secondary"
title={t`Delete account`}
/>
@ -41,8 +41,7 @@ export const DeleteAccount = () => {
<ConfirmationModal
confirmationValue={userEmail}
confirmationPlaceholder={userEmail ?? ''}
isOpen={isDeleteAccountModalOpen}
setIsOpen={setIsDeleteAccountModalOpen}
modalId={DELETE_ACCOUNT_MODAL_ID}
title={t`Account Deletion`}
subtitle={
<>

View File

@ -1,22 +1,22 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useAuth } from '@/auth/hooks/useAuth';
import { currentUserState } from '@/auth/states/currentUserState';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useDeleteCurrentWorkspaceMutation } from '~/generated/graphql';
import { Button } from 'twenty-ui/input';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { H2Title, IconTrash } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { useDeleteCurrentWorkspaceMutation } from '~/generated/graphql';
const DELETE_WORKSPACE_MODAL_ID = 'delete-workspace-modal';
export const DeleteWorkspace = () => {
const [isDeleteWorkSpaceModalOpen, setIsDeleteWorkSpaceModalOpen] =
useState(false);
const [deleteCurrentWorkspace] = useDeleteCurrentWorkspaceMutation();
const currentUser = useRecoilValue(currentUserState);
const userEmail = currentUser?.email;
const { t } = useLingui();
const { openModal } = useModal();
const { signOut } = useAuth();
@ -36,14 +36,13 @@ export const DeleteWorkspace = () => {
variant="secondary"
title={t`Delete workspace`}
Icon={IconTrash}
onClick={() => setIsDeleteWorkSpaceModalOpen(true)}
onClick={() => openModal(DELETE_WORKSPACE_MODAL_ID)}
/>
<ConfirmationModal
modalId={DELETE_WORKSPACE_MODAL_ID}
confirmationPlaceholder={userEmail}
confirmationValue={userEmail}
isOpen={isDeleteWorkSpaceModalOpen}
setIsOpen={setIsDeleteWorkSpaceModalOpen}
title={t`Workspace Deletion`}
subtitle={
<Trans>

View File

@ -1,5 +1,5 @@
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole';
import { SettingsRoleAssignmentConfirmationModal } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModal';
import { SettingsRoleAssignmentTableHeader } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentTableHeader';
@ -11,18 +11,14 @@ import { SettingsPath } from '@/types/SettingsPath';
import { TextInput } from '@/ui/input/components/TextInput';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import {
Role,
SearchRecord,
WorkspaceMember,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { SettingsRoleAssignmentTableRow } from './SettingsRoleAssignmentTableRow';
import {
AppTooltip,
H2Title,
@ -32,6 +28,14 @@ import {
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import {
Role,
SearchRecord,
WorkspaceMember,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID } from '../constants/RoleAssignmentConfirmationModalId';
import { SettingsRoleAssignmentTableRow } from './SettingsRoleAssignmentTableRow';
const StyledAssignToMemberContainer = styled.div`
display: flex;
@ -84,8 +88,7 @@ export const SettingsRoleAssignment = ({
updateWorkspaceMemberRoleDraftState,
} = useUpdateWorkspaceMemberRole(roleId);
const [confirmationModalIsOpen, setConfirmationModalIsOpen] =
useState<boolean>(false);
const { openModal, closeModal } = useModal();
const [selectedWorkspaceMember, setSelectedWorkspaceMember] =
useState<SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember | null>(
null,
@ -135,7 +138,6 @@ export const SettingsRoleAssignment = ({
);
const handleModalClose = () => {
setConfirmationModalIsOpen(false);
setSelectedWorkspaceMember(null);
};
@ -152,12 +154,17 @@ export const SettingsRoleAssignment = ({
role: existingRole,
avatarUrl: workspaceMemberSearchRecord.imageUrl,
});
setConfirmationModalIsOpen(true);
openModal(ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID);
closeDropdown();
};
const isModalOpened = useRecoilComponentValueV2(
isModalOpenedComponentState,
ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID,
);
const handleConfirm = async () => {
if (!selectedWorkspaceMember || !confirmationModalIsOpen) return;
if (!selectedWorkspaceMember || !isModalOpened) return;
if (!isCreateMode) {
await addWorkspaceMemberToRoleAndUpdateState({
@ -188,6 +195,7 @@ export const SettingsRoleAssignment = ({
const handleRoleClick = (roleId: string) => {
navigateSettings(SettingsPath.RoleDetail, { roleId });
handleModalClose();
closeModal(ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID);
};
const handleSearchChange = (text: string) => {
@ -267,10 +275,9 @@ export const SettingsRoleAssignment = ({
</StyledAssignToMemberContainer>
</Section>
{confirmationModalIsOpen && selectedWorkspaceMember && (
{selectedWorkspaceMember && (
<SettingsRoleAssignmentConfirmationModal
selectedWorkspaceMember={selectedWorkspaceMember}
isOpen={true}
onClose={handleModalClose}
onConfirm={handleConfirm}
onRoleClick={handleRoleClick}

View File

@ -1,11 +1,11 @@
import { SettingsRoleAssignmentConfirmationModalSubtitle } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle';
import { ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID } from '@/settings/roles/role-assignment/constants/RoleAssignmentConfirmationModalId';
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { t } from '@lingui/core/macro';
type SettingsRoleAssignmentConfirmationModalProps = {
selectedWorkspaceMember: SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember;
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
onRoleClick: (roleId: string) => void;
@ -13,7 +13,6 @@ type SettingsRoleAssignmentConfirmationModalProps = {
export const SettingsRoleAssignmentConfirmationModal = ({
selectedWorkspaceMember,
isOpen,
onClose,
onConfirm,
onRoleClick,
@ -24,8 +23,7 @@ export const SettingsRoleAssignmentConfirmationModal = ({
return (
<ConfirmationModal
isOpen={isOpen}
setIsOpen={onClose}
modalId={ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID}
title={title}
subtitle={
<SettingsRoleAssignmentConfirmationModalSubtitle
@ -33,6 +31,7 @@ export const SettingsRoleAssignmentConfirmationModal = ({
onRoleClick={onRoleClick}
/>
}
onClose={onClose}
onConfirmClick={onConfirm}
confirmButtonText={t`Confirm`}
confirmButtonAccent="danger"

View File

@ -0,0 +1,2 @@
export const ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID =
'role-assignment-confirmation-modal';

View File

@ -5,14 +5,16 @@ import { ServerlessFunctionFormValues } from '@/settings/serverless-functions/ho
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
import { SettingsPath } from '@/types/SettingsPath';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useState } from 'react';
import { Key } from 'ts-key-enum';
import { H2Title } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { Button } from 'twenty-ui/input';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
const DELETE_FUNCTION_MODAL_ID = 'delete-function-modal';
export const SettingsServerlessFunctionSettingsTab = ({
formValues,
@ -26,8 +28,7 @@ export const SettingsServerlessFunctionSettingsTab = ({
onCodeChange: (filePath: string, value: string) => void;
}) => {
const navigate = useNavigateSettings();
const [isDeleteFunctionModalOpen, setIsDeleteFunctionModalOpen] =
useState(false);
const { openModal } = useModal();
const { deleteOneServerlessFunction } = useDeleteOneServerlessFunction();
const deleteFunction = async () => {
@ -42,7 +43,7 @@ export const SettingsServerlessFunctionSettingsTab = ({
useScopedHotkeys(
[Key.Delete],
() => {
setIsDeleteFunctionModalOpen(true);
openModal(DELETE_FUNCTION_MODAL_ID);
},
SettingsServerlessFunctionHotkeyScope.ServerlessFunctionSettingsTab,
);
@ -68,7 +69,7 @@ export const SettingsServerlessFunctionSettingsTab = ({
<H2Title title="Danger zone" description="Delete this function" />
<Button
accent="danger"
onClick={() => setIsDeleteFunctionModalOpen(true)}
onClick={() => openModal(DELETE_FUNCTION_MODAL_ID)}
variant="secondary"
size="small"
title="Delete function"
@ -77,8 +78,7 @@ export const SettingsServerlessFunctionSettingsTab = ({
<ConfirmationModal
confirmationValue={formValues.name}
confirmationPlaceholder={formValues.name}
isOpen={isDeleteFunctionModalOpen}
setIsOpen={setIsDeleteFunctionModalOpen}
modalId={DELETE_FUNCTION_MODAL_ID}
title="Function Deletion"
subtitle={
<>

View File

@ -1,7 +1,7 @@
import { defaultSpreadsheetImportProps } from '@/spreadsheet-import/provider/components/SpreadsheetImport';
import {
SpreadsheetImportDialogOptions,
SpreadsheetImportFields
SpreadsheetImportDialogOptions,
SpreadsheetImportFields
} from '@/spreadsheet-import/types';
import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
import { FieldMetadataType } from 'twenty-shared/types';
@ -131,7 +131,6 @@ export const mockRsiValues = mockComponentBehaviourForTypes({
onSubmit: async () => {
return;
},
isOpen: true,
onClose: () => {
return;
},

View File

@ -19,11 +19,13 @@ const StyledCloseButtonContainer = styled.div`
top: 0;
`;
type ModalCloseButtonProps = {
onClose: () => void;
type SpreadSheetImportModalCloseButtonProps = {
onClose?: () => void;
};
export const ModalCloseButton = ({ onClose }: ModalCloseButtonProps) => {
export const SpreadSheetImportModalCloseButton = ({
onClose,
}: SpreadSheetImportModalCloseButtonProps) => {
const { initialStepState } = useSpreadsheetImportInternal();
const { initialStep } = useSpreadsheetImportInitialStep(
@ -40,7 +42,7 @@ export const ModalCloseButton = ({ onClose }: ModalCloseButtonProps) => {
const handleClose = () => {
if (activeStep === -1) {
onClose();
onClose?.();
return;
}
enqueueDialog({

View File

@ -3,8 +3,8 @@ import styled from '@emotion/styled';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ModalCloseButton } from './ModalCloseButton';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { SpreadSheetImportModalCloseButton } from './SpreadSheetImportModalCloseButton';
const StyledModal = styled(Modal)`
height: 61%;
@ -27,29 +27,30 @@ const StyledRtlLtr = styled.div`
flex-direction: column;
`;
type ModalWrapperProps = {
type SpreadSheetImportModalWrapperProps = {
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
modalId: string;
onClose?: () => void;
};
export const ModalWrapper = ({
export const SpreadSheetImportModalWrapper = ({
modalId,
children,
isOpen,
onClose,
}: ModalWrapperProps) => {
}: SpreadSheetImportModalWrapperProps) => {
const { rtl } = useSpreadsheetImportInternal();
return (
<>
{isOpen && (
<StyledModal size="large">
<StyledRtlLtr dir={rtl ? 'rtl' : 'ltr'}>
<ModalCloseButton onClose={onClose} />
{children}
</StyledRtlLtr>
</StyledModal>
)}
</>
<StyledModal
size="large"
modalId={modalId}
isClosable={true}
onClose={onClose}
>
<StyledRtlLtr dir={rtl ? 'rtl' : 'ltr'}>
<SpreadSheetImportModalCloseButton onClose={onClose} />
{children}
</StyledRtlLtr>
</StyledModal>
);
};

View File

@ -0,0 +1 @@
export const SPREADSHEET_IMPORT_MODAL_ID = 'spreadsheet-import';

View File

@ -17,7 +17,6 @@ type SpreadsheetKey = 'spreadsheet_key';
export const mockedSpreadsheetOptions: SpreadsheetImportDialogOptions<SpreadsheetKey> =
{
isOpen: true,
onClose: () => {},
fields: [],
uploadStepHook: async () => [],

View File

@ -1,14 +1,18 @@
import { useSetRecoilState } from 'recoil';
import { SPREADSHEET_IMPORT_MODAL_ID } from '@/spreadsheet-import/constants/SpreadsheetImportModalId';
import { spreadsheetImportDialogState } from '@/spreadsheet-import/states/spreadsheetImportDialogState';
import { SpreadsheetImportDialogOptions } from '@/spreadsheet-import/types';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
export const useOpenSpreadsheetImportDialog = <T extends string>() => {
const setSpreadSheetImport = useSetRecoilState(spreadsheetImportDialogState);
const { openModal } = useModal();
const openSpreadsheetImportDialog = (
options: Omit<SpreadsheetImportDialogOptions<T>, 'isOpen' | 'onClose'>,
) => {
openModal(SPREADSHEET_IMPORT_MODAL_ID);
setSpreadSheetImport({
isOpen: true,
options,

View File

@ -1,5 +1,6 @@
import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/components/ReactSpreadsheetImportContextProvider';
import { SpreadSheetImportModalWrapper } from '@/spreadsheet-import/components/SpreadSheetImportModalWrapper';
import { SPREADSHEET_IMPORT_MODAL_ID } from '@/spreadsheet-import/constants/SpreadsheetImportModalId';
import { SpreadsheetImportStepperContainer } from '@/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer';
import { SpreadsheetImportDialogOptions as SpreadsheetImportProps } from '@/spreadsheet-import/types';
@ -31,9 +32,12 @@ export const SpreadsheetImport = <T extends string>(
return (
<ReactSpreadsheetImportContextProvider values={mergedProps}>
<ModalWrapper isOpen={mergedProps.isOpen} onClose={mergedProps.onClose}>
<SpreadSheetImportModalWrapper
modalId={SPREADSHEET_IMPORT_MODAL_ID}
onClose={mergedProps.onClose}
>
<SpreadsheetImportStepperContainer />
</ModalWrapper>
</SpreadSheetImportModalWrapper>
</ReactSpreadsheetImportContextProvider>
);
};

View File

@ -3,7 +3,9 @@ import { useRecoilState, useSetRecoilState } from 'recoil';
import { spreadsheetImportDialogState } from '@/spreadsheet-import/states/spreadsheetImportDialogState';
import { SPREADSHEET_IMPORT_MODAL_ID } from '@/spreadsheet-import/constants/SpreadsheetImportModalId';
import { matchColumnsState } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/states/initialComputedColumnsState';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SpreadsheetImport } from './SpreadsheetImport';
type SpreadsheetImportProviderProps = React.PropsWithChildren;
@ -17,12 +19,16 @@ export const SpreadsheetImportProvider = (
const setMatchColumnsState = useSetRecoilState(matchColumnsState);
const { closeModal } = useModal();
const handleClose = () => {
setSpreadsheetImportDialog({
isOpen: false,
options: null,
});
closeModal(SPREADSHEET_IMPORT_MODAL_ID);
setMatchColumnsState([]);
};
@ -31,7 +37,6 @@ export const SpreadsheetImportProvider = (
{props.children}
{spreadsheetImportDialog.isOpen && spreadsheetImportDialog.options && (
<SpreadsheetImport
isOpen={true}
onClose={handleClose}
// eslint-disable-next-line react/jsx-props-no-spreading
{...spreadsheetImportDialog.options}

View File

@ -1,8 +1,8 @@
import { Meta } from '@storybook/react';
import { mockRsiValues } from '@/spreadsheet-import/__mocks__/mockRsiValues';
import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/components/ReactSpreadsheetImportContextProvider';
import { SpreadSheetImportModalWrapper } from '@/spreadsheet-import/components/SpreadSheetImportModalWrapper';
import { MatchColumnsStep } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
@ -64,7 +64,10 @@ const mockData = [
export const Default = () => (
<DialogManagerScope dialogManagerScopeId="dialog-manager">
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
<ModalWrapper isOpen={true} onClose={() => null}>
<SpreadSheetImportModalWrapper
modalId="match-columns-step"
onClose={() => null}
>
<MatchColumnsStep
headerValues={mockData[0] as string[]}
data={mockData.slice(1)}
@ -75,7 +78,7 @@ export const Default = () => (
nextStep={() => null}
onError={() => null}
/>
</ModalWrapper>
</SpreadSheetImportModalWrapper>
</ReactSpreadsheetImportContextProvider>
</DialogManagerScope>
);

View File

@ -4,11 +4,13 @@ import {
headerSelectionTableFields,
mockRsiValues,
} from '@/spreadsheet-import/__mocks__/mockRsiValues';
import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/components/ReactSpreadsheetImportContextProvider';
import { SpreadSheetImportModalWrapper } from '@/spreadsheet-import/components/SpreadSheetImportModalWrapper';
import { SelectHeaderStep } from '@/spreadsheet-import/steps/components/SelectHeaderStep/SelectHeaderStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { RecoilRoot } from 'recoil';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const meta: Meta<typeof SelectHeaderStep> = {
@ -17,15 +19,30 @@ const meta: Meta<typeof SelectHeaderStep> = {
parameters: {
layout: 'fullscreen',
},
decorators: [I18nFrontDecorator],
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(
isModalOpenedComponentState.atomFamily({
instanceId: 'select-header-step',
}),
true,
);
}}
>
<Story />
</RecoilRoot>
),
I18nFrontDecorator,
],
};
export default meta;
export const Default = () => (
<DialogManagerScope dialogManagerScopeId="dialog-manager">
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
<ModalWrapper isOpen={true} onClose={() => null}>
<SpreadSheetImportModalWrapper modalId="select-header-step">
<SelectHeaderStep
importedRows={headerSelectionTableFields}
setCurrentStepState={() => null}
@ -38,7 +55,7 @@ export const Default = () => (
data: headerSelectionTableFields,
}}
/>
</ModalWrapper>
</SpreadSheetImportModalWrapper>
</ReactSpreadsheetImportContextProvider>
</DialogManagerScope>
);

View File

@ -1,11 +1,13 @@
import { Meta } from '@storybook/react';
import { mockRsiValues } from '@/spreadsheet-import/__mocks__/mockRsiValues';
import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/components/ReactSpreadsheetImportContextProvider';
import { SpreadSheetImportModalWrapper } from '@/spreadsheet-import/components/SpreadSheetImportModalWrapper';
import { SelectSheetStep } from '@/spreadsheet-import/steps/components/SelectSheetStep/SelectSheetStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { RecoilRoot } from 'recoil';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const meta: Meta<typeof SelectSheetStep> = {
@ -14,7 +16,23 @@ const meta: Meta<typeof SelectSheetStep> = {
parameters: {
layout: 'fullscreen',
},
decorators: [I18nFrontDecorator],
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(
isModalOpenedComponentState.atomFamily({
instanceId: 'select-sheet-step',
}),
true,
);
}}
>
<Story />
</RecoilRoot>
),
I18nFrontDecorator,
],
};
export default meta;
@ -24,7 +42,10 @@ const sheetNames = ['Sheet1', 'Sheet2', 'Sheet3'];
export const Default = () => (
<DialogManagerScope dialogManagerScopeId="dialog-manager">
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
<ModalWrapper isOpen={true} onClose={() => null}>
<SpreadSheetImportModalWrapper
modalId="select-sheet-step"
onClose={() => null}
>
<SelectSheetStep
sheetNames={sheetNames}
setCurrentStepState={() => {}}
@ -55,7 +76,7 @@ export const Default = () => (
onError={() => null}
onBack={() => Promise.resolve()}
/>
</ModalWrapper>
</SpreadSheetImportModalWrapper>
</ReactSpreadsheetImportContextProvider>
</DialogManagerScope>
);

View File

@ -1,11 +1,13 @@
import { Meta } from '@storybook/react';
import { mockRsiValues } from '@/spreadsheet-import/__mocks__/mockRsiValues';
import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/components/ReactSpreadsheetImportContextProvider';
import { SpreadSheetImportModalWrapper } from '@/spreadsheet-import/components/SpreadSheetImportModalWrapper';
import { UploadStep } from '@/spreadsheet-import/steps/components/UploadStep/UploadStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { RecoilRoot } from 'recoil';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@ -15,7 +17,24 @@ const meta: Meta<typeof UploadStep> = {
parameters: {
layout: 'fullscreen',
},
decorators: [SnackBarDecorator, I18nFrontDecorator],
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(
isModalOpenedComponentState.atomFamily({
instanceId: 'upload-step',
}),
true,
);
}}
>
<Story />
</RecoilRoot>
),
SnackBarDecorator,
I18nFrontDecorator,
],
};
export default meta;
@ -23,7 +42,7 @@ export default meta;
export const Default = () => (
<DialogManagerScope dialogManagerScopeId="dialog-manager">
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
<ModalWrapper isOpen={true} onClose={() => null}>
<SpreadSheetImportModalWrapper modalId="upload-step" onClose={() => null}>
<UploadStep
setUploadedFile={() => null}
setCurrentStepState={() => null}
@ -32,7 +51,7 @@ export const Default = () => (
setPreviousStepState={() => null}
currentStepState={{ type: SpreadsheetImportStepType.upload }}
/>
</ModalWrapper>
</SpreadSheetImportModalWrapper>
</ReactSpreadsheetImportContextProvider>
</DialogManagerScope>
);

View File

@ -5,18 +5,37 @@ import {
importedColums,
mockRsiValues,
} from '@/spreadsheet-import/__mocks__/mockRsiValues';
import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
import { ReactSpreadsheetImportContextProvider } from '@/spreadsheet-import/components/ReactSpreadsheetImportContextProvider';
import { SpreadSheetImportModalWrapper } from '@/spreadsheet-import/components/SpreadSheetImportModalWrapper';
import { ValidationStep } from '@/spreadsheet-import/steps/components/ValidationStep/ValidationStep';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { RecoilRoot } from 'recoil';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const meta: Meta<typeof ValidationStep> = {
title: 'Modules/SpreadsheetImport/ValidationStep',
component: ValidationStep,
parameters: {
layout: 'fullscreen',
},
decorators: [I18nFrontDecorator],
decorators: [
(Story) => (
<RecoilRoot
initializeState={({ set }) => {
set(
isModalOpenedComponentState.atomFamily({
instanceId: 'validation-step',
}),
true,
);
}}
>
<Story />
</RecoilRoot>
),
I18nFrontDecorator,
],
};
export default meta;
@ -26,7 +45,10 @@ const file = new File([''], 'file.csv');
export const Default = () => (
<DialogManagerScope dialogManagerScopeId="dialog-manager">
<ReactSpreadsheetImportContextProvider values={mockRsiValues}>
<ModalWrapper isOpen={true} onClose={() => null}>
<SpreadSheetImportModalWrapper
modalId="validation-step"
onClose={() => null}
>
<ValidationStep
initialData={editableTableInitialData}
file={file}
@ -34,7 +56,7 @@ export const Default = () => (
onBack={() => Promise.resolve()}
setCurrentStepState={() => null}
/>
</ModalWrapper>
</SpreadSheetImportModalWrapper>
</ReactSpreadsheetImportContextProvider>
</DialogManagerScope>
);

View File

@ -9,8 +9,6 @@ import { SpreadsheetImportTableHook } from '@/spreadsheet-import/types/Spreadshe
import { SpreadsheetImportStep } from '../steps/types/SpreadsheetImportStep';
export type SpreadsheetImportDialogOptions<FieldNames extends string> = {
// Is modal visible.
isOpen: boolean;
// callback when RSI is closed before final submit
onClose: () => void;
// Field description for requested data

View File

@ -6,17 +6,18 @@ import { useDebouncedCallback } from 'use-debounce';
import { TextInput } from '@/ui/input/components/TextInput';
import { Modal, ModalVariants } from '@/ui/layout/modal/components/Modal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useLingui } from '@lingui/react/macro';
import { Button, ButtonAccent } from 'twenty-ui/input';
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
import { Button, ButtonAccent } from 'twenty-ui/input';
import { Section, SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
export type ConfirmationModalProps = {
isOpen: boolean;
modalId: string;
title: string;
loading?: boolean;
subtitle: ReactNode;
setIsOpen: (val: boolean) => void;
onClose?: () => void;
onConfirmClick: () => void;
confirmButtonText?: string;
confirmationPlaceholder?: string;
@ -58,12 +59,12 @@ export const StyledConfirmationButton = styled(StyledCenteredButton)`
`;
export const ConfirmationModal = ({
isOpen = false,
modalId,
title,
loading,
subtitle,
setIsOpen,
onConfirmClick,
onClose,
confirmButtonText = 'Confirm',
confirmationValue,
confirmationPlaceholder,
@ -88,10 +89,16 @@ export const ConfirmationModal = ({
250,
);
const handleConfirmClick = () => {
onConfirmClick();
const { closeModal } = useModal();
setIsOpen(false);
const handleConfirmClick = () => {
closeModal(modalId);
onConfirmClick();
};
const handleCancelClick = () => {
closeModal(modalId);
onClose?.();
};
const handleEnter = () => {
@ -103,63 +110,59 @@ export const ConfirmationModal = ({
return (
<AnimatePresence mode="wait">
<LayoutGroup>
{isOpen && (
<StyledConfirmationModal
onClose={() => {
if (isOpen) {
setIsOpen(false);
}
}}
onEnter={handleEnter}
isClosable={true}
padding="large"
modalVariant={modalVariant}
className="confirmation-modal"
<StyledConfirmationModal
modalId={modalId}
onClose={() => {
onClose?.();
}}
onEnter={handleEnter}
isClosable={true}
padding="large"
modalVariant={modalVariant}
className="confirmation-modal"
>
<StyledCenteredTitle>
<H1Title title={title} fontColor={H1TitleFontColor.Primary} />
</StyledCenteredTitle>
<StyledSection
alignment={SectionAlignment.Center}
fontColor={SectionFontColor.Primary}
>
<StyledCenteredTitle>
<H1Title title={title} fontColor={H1TitleFontColor.Primary} />
</StyledCenteredTitle>
<StyledSection
alignment={SectionAlignment.Center}
fontColor={SectionFontColor.Primary}
>
{subtitle}
</StyledSection>
{confirmationValue && (
<Section>
<TextInput
dataTestId="confirmation-modal-input"
value={inputConfirmationValue}
onChange={handleInputConfimrationValueChange}
placeholder={confirmationPlaceholder}
fullWidth
disableHotkeys
key={'input-' + confirmationValue}
/>
</Section>
)}
<StyledCenteredButton
onClick={() => {
setIsOpen(false);
}}
variant="secondary"
title={t`Cancel`}
fullWidth
/>
{subtitle}
</StyledSection>
{confirmationValue && (
<Section>
<TextInput
dataTestId="confirmation-modal-input"
value={inputConfirmationValue}
onChange={handleInputConfimrationValueChange}
placeholder={confirmationPlaceholder}
fullWidth
disableHotkeys
key={'input-' + confirmationValue}
/>
</Section>
)}
<StyledCenteredButton
onClick={handleCancelClick}
variant="secondary"
title={t`Cancel`}
fullWidth
dataTestId="confirmation-modal-cancel-button"
/>
{AdditionalButtons}
{AdditionalButtons}
<StyledCenteredButton
onClick={handleConfirmClick}
variant="secondary"
accent={confirmButtonAccent}
title={confirmButtonText}
disabled={!isValidValue || loading}
fullWidth
dataTestId="confirmation-modal-confirm-button"
/>
</StyledConfirmationModal>
)}
<StyledCenteredButton
onClick={handleConfirmClick}
variant="secondary"
accent={confirmButtonAccent}
title={confirmButtonText}
disabled={!isValidValue || loading}
fullWidth
dataTestId="confirmation-modal-confirm-button"
/>
</StyledConfirmationModal>
</LayoutGroup>
</AnimatePresence>
);

View File

@ -1,18 +1,16 @@
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { ModalHotkeysAndClickOutsideEffect } from '@/ui/layout/modal/components/ModalHotkeysAndClickOutsideEffect';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import {
ClickOutsideMode,
useListenClickOutside,
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { ModalComponentInstanceContext } from '@/ui/layout/modal/contexts/ModalComponentInstanceContext';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import React, { useEffect, useRef } from 'react';
import { Key } from 'ts-key-enum';
import React, { useRef } from 'react';
const StyledModalDiv = styled(motion.div)<{
size?: ModalSize;
padding?: ModalPadding;
@ -169,6 +167,7 @@ export type ModalPadding = 'none' | 'small' | 'medium' | 'large';
export type ModalVariants = 'primary' | 'secondary' | 'tertiary';
export type ModalProps = React.PropsWithChildren & {
modalId: string;
size?: ModalSize;
padding?: ModalPadding;
className?: string;
@ -176,7 +175,7 @@ export type ModalProps = React.PropsWithChildren & {
onEnter?: () => void;
modalVariant?: ModalVariants;
} & (
| { isClosable: true; onClose: () => void }
| { isClosable: true; onClose?: () => void }
| { isClosable?: false; onClose?: never }
);
@ -187,11 +186,11 @@ const modalAnimation = {
};
export const Modal = ({
modalId,
children,
size = 'medium',
padding = 'medium',
className,
hotkeyScope = ModalHotkeyScope.Default,
onEnter,
isClosable = false,
onClose,
@ -200,80 +199,65 @@ export const Modal = ({
const isMobile = useIsMobile();
const modalRef = useRef<HTMLDivElement>(null);
const {
goBackToPreviousHotkeyScope,
setHotkeyScopeAndMemorizePreviousScope,
} = usePreviousHotkeyScope();
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(hotkeyScope);
return () => {
goBackToPreviousHotkeyScope();
};
}, [
hotkeyScope,
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
]);
useScopedHotkeys(
[Key.Enter],
() => {
onEnter?.();
},
hotkeyScope,
);
useScopedHotkeys(
[Key.Escape],
() => {
if (isClosable && onClose !== undefined) {
onClose();
}
},
hotkeyScope,
);
useListenClickOutside({
refs: [modalRef],
listenerId: 'MODAL_CLICK_OUTSIDE_LISTENER_ID',
callback: () => {
if (isClosable && onClose !== undefined) {
onClose();
}
},
mode: ClickOutsideMode.comparePixels,
});
const theme = useTheme();
const stopEventPropagation = (e: React.MouseEvent) => {
e.stopPropagation();
};
const theme = useTheme();
const isModalOpened = useRecoilComponentValueV2(
isModalOpenedComponentState,
modalId,
);
const { closeModal } = useModal();
const handleClose = () => {
onClose?.();
closeModal(modalId);
};
return (
<StyledBackDrop
className="modal-backdrop"
onMouseDown={stopEventPropagation}
modalVariant={modalVariant}
>
<StyledModalDiv
ref={modalRef}
size={size}
padding={padding}
initial="hidden"
animate="visible"
exit="exit"
layout
modalVariant={modalVariant}
variants={modalAnimation}
transition={{ duration: theme.animation.duration.normal }}
className={className}
isMobile={isMobile}
>
{children}
</StyledModalDiv>
</StyledBackDrop>
<>
{isModalOpened && (
<ModalComponentInstanceContext.Provider
value={{
instanceId: modalId,
}}
>
<ModalHotkeysAndClickOutsideEffect
modalId={modalId}
modalRef={modalRef}
onEnter={onEnter}
isClosable={isClosable}
onClose={handleClose}
/>
<StyledBackDrop
data-testid="modal-backdrop"
className="modal-backdrop"
onMouseDown={stopEventPropagation}
modalVariant={modalVariant}
>
<StyledModalDiv
ref={modalRef}
size={size}
padding={padding}
initial="hidden"
animate="visible"
exit="exit"
layout
modalVariant={modalVariant}
variants={modalAnimation}
transition={{ duration: theme.animation.duration.normal }}
className={className}
isMobile={isMobile}
>
{children}
</StyledModalDiv>
</StyledBackDrop>
</ModalComponentInstanceContext.Provider>
)}
</>
);
};

View File

@ -0,0 +1,54 @@
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import {
ClickOutsideMode,
useListenClickOutside,
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { Key } from 'ts-key-enum';
type ModalHotkeysAndClickOutsideEffectProps = {
modalRef: React.RefObject<HTMLDivElement>;
onEnter?: () => void;
isClosable?: boolean;
onClose?: () => void;
modalId: string;
};
export const ModalHotkeysAndClickOutsideEffect = ({
modalRef,
onEnter,
isClosable = false,
onClose,
modalId,
}: ModalHotkeysAndClickOutsideEffectProps) => {
useScopedHotkeys(
[Key.Enter],
() => {
onEnter?.();
},
ModalHotkeyScope.ModalFocus,
);
useScopedHotkeys(
[Key.Escape],
() => {
if (isClosable && onClose !== undefined) {
onClose();
}
},
ModalHotkeyScope.ModalFocus,
);
useListenClickOutside({
refs: [modalRef],
listenerId: `MODAL_CLICK_OUTSIDE_LISTENER_ID_${modalId}`,
callback: () => {
if (isClosable && onClose !== undefined) {
onClose();
}
},
mode: ClickOutsideMode.compareHTMLRef,
});
return null;
};

View File

@ -1,26 +1,58 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { ConfirmationModal } from '../ConfirmationModal';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { RootDecorator } from '~/testing/decorators/RootDecorator';
import { isModalOpenedComponentState } from '../../states/isModalOpenedComponentState';
import { ConfirmationModal } from '../ConfirmationModal';
const initializeState = ({ set }: { set: (atom: any, value: any) => void }) => {
set(
isModalOpenedComponentState.atomFamily({
instanceId: 'confirmation-modal',
}),
true,
);
set(currentHotkeyScopeState, {
scope: ModalHotkeyScope.ModalFocus,
customScopes: {
commandMenu: true,
goto: false,
keyboardShortcutMenu: false,
},
});
set(internalHotkeysEnabledScopesState, [ModalHotkeyScope.ModalFocus]);
};
const meta: Meta<typeof ConfirmationModal> = {
title: 'UI/Layout/Modal/ConfirmationModal',
component: ConfirmationModal,
decorators: [ComponentDecorator, I18nFrontDecorator],
decorators: [RootDecorator, ComponentDecorator, I18nFrontDecorator],
parameters: {
initializeState,
disableHotkeyInitialization: true,
},
};
export default meta;
type Story = StoryObj<typeof ConfirmationModal>;
const closeMock = fn();
const confirmMock = fn();
export const Default: Story = {
args: {
isOpen: true,
modalId: 'confirmation-modal',
title: 'Pariatur labore.',
subtitle: 'Velit dolore aliquip laborum occaecat fugiat.',
confirmButtonText: 'Delete',
},
decorators: [ComponentDecorator],
};
export const InputConfirmation: Story = {
@ -29,5 +61,120 @@ export const InputConfirmation: Story = {
confirmationPlaceholder: 'email@test.dev',
...Default.args,
},
decorators: Default.decorators,
};
export const CloseOnEscape: Story = {
args: {
modalId: 'confirmation-modal',
title: 'Escape Key Test',
subtitle: 'This modal should close when pressing the Escape key.',
confirmButtonText: 'Confirm',
onClose: closeMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Escape Key Test');
closeMock.mockClear();
await userEvent.keyboard('{Escape}');
await waitFor(() => {
expect(closeMock).toHaveBeenCalledTimes(1);
});
},
};
export const CloseOnClickOutside: Story = {
args: {
modalId: 'confirmation-modal',
title: 'Click Outside Test',
subtitle: 'This modal should close when clicking outside of it.',
confirmButtonText: 'Confirm',
onClose: closeMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Click Outside Test');
const backdrop = await canvas.findByTestId('modal-backdrop');
await userEvent.click(backdrop);
await waitFor(() => {
expect(closeMock).toHaveBeenCalledTimes(1);
});
},
};
export const ConfirmWithEnterKey: Story = {
args: {
modalId: 'confirmation-modal',
title: 'Enter Key Test',
subtitle: 'This modal should confirm when pressing the Enter key.',
confirmButtonText: 'Confirm',
onConfirmClick: confirmMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Enter Key Test');
await userEvent.keyboard('{Enter}');
await waitFor(() => {
expect(confirmMock).toHaveBeenCalledTimes(1);
});
},
};
export const CancelButtonClick: Story = {
args: {
modalId: 'confirmation-modal',
title: 'Cancel Button Test',
subtitle: 'Clicking the cancel button should close the modal',
confirmButtonText: 'Confirm',
onClose: closeMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Cancel Button Test');
const cancelButton = await canvas.findByRole('button', {
name: /Cancel/,
});
await userEvent.click(cancelButton);
await waitFor(() => {
expect(closeMock).toHaveBeenCalledTimes(1);
});
},
};
export const ConfirmButtonClick: Story = {
args: {
modalId: 'confirmation-modal',
title: 'Confirm Button Test',
subtitle: 'Clicking the confirm button should trigger the confirm action',
confirmButtonText: 'Confirm',
onConfirmClick: confirmMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Confirm Button Test');
const confirmButton = await canvas.findByRole('button', {
name: /Confirm/,
});
await userEvent.click(confirmButton);
await waitFor(() => {
expect(confirmMock).toHaveBeenCalledTimes(1);
});
},
};

View File

@ -1,18 +1,53 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { Modal } from '../Modal';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { RootDecorator } from '~/testing/decorators/RootDecorator';
import { isModalOpenedComponentState } from '../../states/isModalOpenedComponentState';
import { Modal } from '../Modal';
const initializeState = ({ set }: { set: (atom: any, value: any) => void }) => {
set(
isModalOpenedComponentState.atomFamily({
instanceId: 'modal-id',
}),
true,
);
set(currentHotkeyScopeState, {
scope: ModalHotkeyScope.ModalFocus,
customScopes: {
commandMenu: true,
goto: false,
keyboardShortcutMenu: false,
},
});
set(internalHotkeysEnabledScopesState, [ModalHotkeyScope.ModalFocus]);
};
const meta: Meta<typeof Modal> = {
title: 'UI/Layout/Modal/Modal',
component: Modal,
decorators: [I18nFrontDecorator, RootDecorator, ComponentDecorator],
parameters: {
initializeState,
disableHotkeyInitialization: true,
},
};
export default meta;
type Story = StoryObj<typeof Modal>;
const closeMock = fn();
export const Default: Story = {
args: {
modalId: 'modal-id',
size: 'medium',
padding: 'medium',
children: (
@ -29,8 +64,63 @@ export const Default: Story = {
</>
),
},
decorators: [ComponentDecorator],
argTypes: {
children: { control: false },
};
export const CloseClosableModalOnClickOutside: Story = {
args: {
modalId: 'modal-id',
size: 'medium',
padding: 'medium',
isClosable: true,
onClose: closeMock,
children: (
<>
<Modal.Header>Click Outside Test</Modal.Header>
<Modal.Content>
This modal should close when clicking outside of it.
</Modal.Content>
</>
),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Click Outside Test');
const backdrop = document.querySelector('.modal-backdrop') as HTMLElement;
await userEvent.click(backdrop);
await waitFor(() => {
expect(closeMock).toHaveBeenCalledTimes(1);
});
},
};
export const CloseClosableModalOnEscape: Story = {
args: {
modalId: 'modal-id',
size: 'medium',
padding: 'medium',
isClosable: true,
onClose: closeMock,
children: (
<>
<Modal.Header>Escape Key Test</Modal.Header>
<Modal.Content>
This modal should close when pressing the Escape key.
</Modal.Content>
</>
),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Escape Key Test');
await userEvent.keyboard('{Escape}');
await waitFor(() => {
expect(closeMock).toHaveBeenCalledTimes(1);
});
},
};

View File

@ -1,3 +1,3 @@
export enum ModalHotkeyScope {
Default = 'default',
ModalFocus = 'modal-focus',
}

View File

@ -0,0 +1,3 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const ModalComponentInstanceContext = createComponentInstanceContext();

View File

@ -0,0 +1,181 @@
import { renderHook } from '@testing-library/react';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { act } from 'react';
jest.mock('@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope');
const mockSetHotkeyScopeAndMemorizePreviousScope = jest.fn();
const mockGoBackToPreviousHotkeyScope = jest.fn();
const modalId = 'test-modal-id';
const customHotkeyScope: HotkeyScope = {
scope: 'test-scope',
customScopes: {
goto: true,
commandMenu: true,
},
};
describe('useModal', () => {
beforeEach(() => {
jest.clearAllMocks();
(usePreviousHotkeyScope as jest.Mock).mockReturnValue({
setHotkeyScopeAndMemorizePreviousScope:
mockSetHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope: mockGoBackToPreviousHotkeyScope,
});
});
it('should open a modal', () => {
const { result } = renderHook(
() => {
const modal = useModal();
const isModalOpened = useRecoilValue(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
);
return { modal, isModalOpened };
},
{
wrapper: RecoilRoot,
},
);
act(() => {
result.current.modal.openModal(modalId);
});
expect(result.current.isModalOpened).toBe(true);
});
it('should open a modal with custom hotkey scope', () => {
const { result } = renderHook(
() => {
const modal = useModal();
const isModalOpened = useRecoilValue(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
);
return { modal, isModalOpened };
},
{
wrapper: RecoilRoot,
},
);
act(() => {
result.current.modal.openModal(modalId, customHotkeyScope);
});
expect(result.current.isModalOpened).toBe(true);
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith(
customHotkeyScope.scope,
customHotkeyScope.customScopes,
);
});
it('should close a modal', () => {
const { result } = renderHook(
() => {
const modal = useModal();
const isModalOpened = useRecoilValue(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
);
return { modal, isModalOpened };
},
{
wrapper: RecoilRoot,
},
);
act(() => {
result.current.modal.openModal(modalId);
});
expect(result.current.isModalOpened).toBe(true);
act(() => {
result.current.modal.closeModal(modalId);
});
expect(result.current.isModalOpened).toBe(false);
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalled();
});
it('should toggle a modal (open when closed)', () => {
const { result } = renderHook(
() => {
const modal = useModal();
const isModalOpened = useRecoilValue(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
);
return { modal, isModalOpened };
},
{
wrapper: RecoilRoot,
},
);
expect(result.current.isModalOpened).toBe(false);
act(() => {
result.current.modal.toggleModal(modalId);
});
expect(result.current.isModalOpened).toBe(true);
});
it('should toggle a modal (close when open)', () => {
const { result } = renderHook(
() => {
const modal = useModal();
const isModalOpened = useRecoilValue(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
);
return { modal, isModalOpened };
},
{
wrapper: RecoilRoot,
},
);
act(() => {
result.current.modal.openModal(modalId);
});
expect(result.current.isModalOpened).toBe(true);
act(() => {
result.current.modal.toggleModal(modalId);
});
expect(result.current.isModalOpened).toBe(false);
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalled();
});
it('should toggle a modal with custom hotkey scope', () => {
const { result } = renderHook(
() => {
const modal = useModal();
const isModalOpened = useRecoilValue(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
);
return { modal, isModalOpened };
},
{
wrapper: RecoilRoot,
},
);
act(() => {
result.current.modal.toggleModal(modalId, customHotkeyScope);
});
expect(result.current.isModalOpened).toBe(true);
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith(
customHotkeyScope.scope,
customHotkeyScope.customScopes,
);
});
});

View File

@ -0,0 +1,93 @@
import { useRecoilCallback } from 'recoil';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { isDefined } from 'twenty-shared/utils';
export const useModal = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope('modal');
const closeModal = useRecoilCallback(
({ set, snapshot }) =>
(modalId: string) => {
const isModalOpen = snapshot
.getLoadable(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
)
.getValue();
if (isModalOpen) {
goBackToPreviousHotkeyScope();
set(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
false,
);
}
},
[goBackToPreviousHotkeyScope],
);
const openModal = useRecoilCallback(
({ set, snapshot }) =>
(modalId: string, customHotkeyScope?: HotkeyScope) => {
const isModalOpened = snapshot
.getLoadable(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
)
.getValue();
if (isModalOpened) {
return;
}
set(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
true,
);
if (isDefined(customHotkeyScope)) {
setHotkeyScopeAndMemorizePreviousScope(
customHotkeyScope.scope,
customHotkeyScope.customScopes,
);
} else {
setHotkeyScopeAndMemorizePreviousScope(ModalHotkeyScope.ModalFocus, {
goto: false,
commandMenu: false,
commandMenuOpen: false,
keyboardShortcutMenu: false,
});
}
},
[setHotkeyScopeAndMemorizePreviousScope],
);
const toggleModal = useRecoilCallback(
({ snapshot }) =>
(modalId: string, customHotkeyScope?: HotkeyScope) => {
const isModalOpen = snapshot
.getLoadable(
isModalOpenedComponentState.atomFamily({ instanceId: modalId }),
)
.getValue();
if (isModalOpen) {
closeModal(modalId);
} else {
openModal(modalId, customHotkeyScope);
}
},
[closeModal, openModal],
);
return {
closeModal,
openModal,
toggleModal,
};
};

View File

@ -0,0 +1,8 @@
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
import { ModalComponentInstanceContext } from '../contexts/ModalComponentInstanceContext';
export const isModalOpenedComponentState = createComponentStateV2<boolean>({
key: 'isModalOpenedComponentState',
defaultValue: false,
componentInstanceContext: ModalComponentInstanceContext,
});

View File

@ -4,12 +4,14 @@ import {
ConfirmationModal,
StyledCenteredButton,
} from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useCreateDraftFromWorkflowVersion } from '@/workflow/hooks/useCreateDraftFromWorkflowVersion';
import { openOverrideWorkflowDraftConfirmationModalState } from '@/workflow/states/openOverrideWorkflowDraftConfirmationModalState';
import { useRecoilState } from 'recoil';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { getAppPath } from '~/utils/navigation/getAppPath';
const OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID =
'override-workflow-draft-confirmation-modal';
export const OverrideWorkflowDraftConfirmationModal = ({
workflowId,
workflowVersionIdToCopy,
@ -17,10 +19,7 @@ export const OverrideWorkflowDraftConfirmationModal = ({
workflowId: string;
workflowVersionIdToCopy: string;
}) => {
const [
openOverrideWorkflowDraftConfirmationModal,
setOpenOverrideWorkflowDraftConfirmationModal,
] = useRecoilState(openOverrideWorkflowDraftConfirmationModalState);
const { closeModal } = useModal();
const { createDraftFromWorkflowVersion } =
useCreateDraftFromWorkflowVersion();
@ -42,8 +41,7 @@ export const OverrideWorkflowDraftConfirmationModal = ({
return (
<>
<ConfirmationModal
isOpen={openOverrideWorkflowDraftConfirmationModal}
setIsOpen={setOpenOverrideWorkflowDraftConfirmationModal}
modalId={OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID}
title="A draft already exists"
subtitle="A draft already exists for this workflow. Are you sure you want to erase it?"
onConfirmClick={handleOverrideDraft}
@ -55,7 +53,7 @@ export const OverrideWorkflowDraftConfirmationModal = ({
objectRecordId: workflowId,
})}
onClick={() => {
setOpenOverrideWorkflowDraftConfirmationModal(false);
closeModal(OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID);
}}
variant="secondary"
title="Go to Draft"

View File

@ -1,5 +1,4 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
@ -10,6 +9,7 @@ import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import { isDefined } from 'twenty-shared/utils';
@ -29,6 +29,8 @@ import {
} from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const SWITCH_BILLING_INTERVAL_MODAL_ID = 'switch-billing-interval-modal';
export const SettingsBilling = () => {
const { t } = useLingui();
@ -47,8 +49,6 @@ export const SettingsBilling = () => {
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const [isSwitchingIntervalModalOpen, setIsSwitchingIntervalModalOpen] =
useState(false);
const [switchToYearlyInterval] =
useSwitchSubscriptionToYearlyIntervalMutation();
const { data, loading } = useBillingPortalSessionQuery({
@ -67,9 +67,7 @@ export const SettingsBilling = () => {
}
};
const openSwitchingIntervalModal = () => {
setIsSwitchingIntervalModalOpen(true);
};
const { openModal } = useModal();
const switchInterval = async () => {
try {
@ -133,7 +131,7 @@ export const SettingsBilling = () => {
Icon={IconCalendarEvent}
title={t`Switch to yearly`}
variant="secondary"
onClick={openSwitchingIntervalModal}
onClick={() => openModal(SWITCH_BILLING_INTERVAL_MODAL_ID)}
disabled={!hasNotCanceledCurrentSubscription}
/>
</Section>
@ -154,8 +152,7 @@ export const SettingsBilling = () => {
</Section>
</SettingsPageContainer>
<ConfirmationModal
isOpen={isSwitchingIntervalModalOpen}
setIsOpen={setIsSwitchingIntervalModalOpen}
modalId={SWITCH_BILLING_INTERVAL_MODAL_ID}
title={t`Switch billing to yearly`}
subtitle={t`Are you sure that you want to change your billing interval? You will be charged immediately for the full year.`}
onConfirmClick={switchInterval}

View File

@ -16,6 +16,7 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { Table } from '@/ui/layout/table/components/Table';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
@ -23,13 +24,6 @@ import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink';
import { WorkspaceInviteTeam } from '@/workspace/components/WorkspaceInviteTeam';
import { formatDistanceToNow } from 'date-fns';
import { useGetWorkspaceInvitationsQuery } from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { TableCell } from '../../modules/ui/layout/table/components/TableCell';
import { TableRow } from '../../modules/ui/layout/table/components/TableRow';
import { useDeleteWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useDeleteWorkspaceInvitation';
import { useResendWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useResendWorkspaceInvitation';
import { workspaceInvitationsState } from '../../modules/workspace-invitation/states/workspaceInvitationsStates';
import { isDefined } from 'twenty-shared/utils';
import {
AppTooltip,
@ -44,6 +38,16 @@ import {
} from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { useGetWorkspaceInvitationsQuery } from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { TableCell } from '../../modules/ui/layout/table/components/TableCell';
import { TableRow } from '../../modules/ui/layout/table/components/TableRow';
import { useDeleteWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useDeleteWorkspaceInvitation';
import { useResendWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useResendWorkspaceInvitation';
import { workspaceInvitationsState } from '../../modules/workspace-invitation/states/workspaceInvitationsStates';
export const WORKSPACE_MEMBER_DELETION_MODAL_ID =
'workspace-member-deletion-modal';
const StyledButtonContainer = styled.div`
align-items: center;
@ -92,7 +96,6 @@ export const SettingsWorkspaceMembers = () => {
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const theme = useTheme();
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
const [workspaceMemberToDelete, setWorkspaceMemberToDelete] = useState<
string | undefined
>();
@ -112,7 +115,6 @@ export const SettingsWorkspaceMembers = () => {
const handleRemoveWorkspaceMember = async (workspaceMemberId: string) => {
await deleteOneWorkspaceMember?.(workspaceMemberId);
setIsConfirmationModalOpen(false);
};
const workspaceInvitations = useRecoilValue(workspaceInvitationsState);
@ -177,6 +179,8 @@ export const SettingsWorkspaceMembers = () => {
);
});
const { openModal } = useModal();
return (
<SubMenuTopBarContainer
title={t`Members`}
@ -273,7 +277,7 @@ export const SettingsWorkspaceMembers = () => {
<StyledButtonContainer>
<IconButton
onClick={() => {
setIsConfirmationModalOpen(true);
openModal(WORKSPACE_MEMBER_DELETION_MODAL_ID);
setWorkspaceMemberToDelete(workspaceMember.id);
}}
variant="tertiary"
@ -371,8 +375,7 @@ export const SettingsWorkspaceMembers = () => {
</Section>
</SettingsPageContainer>
<ConfirmationModal
isOpen={isConfirmationModalOpen}
setIsOpen={setIsConfirmationModalOpen}
modalId={WORKSPACE_MEMBER_DELETION_MODAL_ID}
title={t`Account Deletion`}
subtitle={
<Trans>

View File

@ -12,6 +12,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
import { SettingsPath } from '@/types/SettingsPath';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useRecoilValue } from 'recoil';
import { ConfigVariableValue } from 'twenty-shared/types';
@ -23,7 +24,6 @@ import {
useGetDatabaseConfigVariableQuery,
} from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledForm = styled(Form)`
display: flex;
flex-direction: column;
@ -48,11 +48,13 @@ const StyledButtonContainer = styled.div`
}
`;
const RESET_VARIABLE_MODAL_ID = 'reset-variable-modal';
export const SettingsAdminConfigVariableDetails = () => {
const { variableName } = useParams();
const { t } = useLingui();
const [isEditing, setIsEditing] = useState(false);
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
const { openModal } = useModal();
const isConfigVariablesInDbEnabled = useRecoilValue(
isConfigVariablesInDbEnabledState,
);
@ -101,7 +103,7 @@ export const SettingsAdminConfigVariableDetails = () => {
}
if (isFromDatabase && !hasValueChanged) {
setIsConfirmationModalOpen(true);
openModal(RESET_VARIABLE_MODAL_ID);
return;
}
@ -193,13 +195,7 @@ export const SettingsAdminConfigVariableDetails = () => {
</SubMenuTopBarContainer>
<ConfirmationModal
isOpen={isConfirmationModalOpen}
setIsOpen={(isOpen) => {
setIsConfirmationModalOpen(isOpen);
if (!isOpen) {
setIsEditing(false);
}
}}
modalId={RESET_VARIABLE_MODAL_ID}
title={t`Reset variable`}
subtitle={t`This will revert the database value to environment/default value. The database override will be removed and the system will use the environment settings.`}
onConfirmClick={handleConfirmReset}

View File

@ -21,14 +21,15 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { Trans, useLingui } from '@lingui/react/macro';
import { H2Title, IconRepeat, IconTrash } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { useGenerateApiKeyTokenMutation } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { Button } from 'twenty-ui/input';
import { H2Title, IconRepeat, IconTrash } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
const StyledInfo = styled.span`
color: ${({ theme }) => theme.font.color.light};
@ -44,12 +45,13 @@ const StyledInputContainer = styled.div`
width: 100%;
`;
const DELETE_API_KEY_MODAL_ID = 'delete-api-key-modal';
const REGENERATE_API_KEY_MODAL_ID = 'regenerate-api-key-modal';
export const SettingsDevelopersApiKeyDetail = () => {
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const [isRegenerateKeyModalOpen, setIsRegenerateKeyModalOpen] =
useState(false);
const [isDeleteApiKeyModalOpen, setIsDeleteApiKeyModalOpen] = useState(false);
const { openModal } = useModal();
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigateSettings();
@ -194,7 +196,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
<Button
title={t`Regenerate Key`}
Icon={IconRepeat}
onClick={() => setIsRegenerateKeyModalOpen(true)}
onClick={() => openModal(REGENERATE_API_KEY_MODAL_ID)}
/>
<StyledInfo>
{formatExpiration(
@ -242,7 +244,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
variant="secondary"
title={t`Delete`}
Icon={IconTrash}
onClick={() => setIsDeleteApiKeyModalOpen(true)}
onClick={() => openModal(DELETE_API_KEY_MODAL_ID)}
/>
</Section>
</SettingsPageContainer>
@ -251,8 +253,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
<ConfirmationModal
confirmationPlaceholder={confirmationValue}
confirmationValue={confirmationValue}
isOpen={isDeleteApiKeyModalOpen}
setIsOpen={setIsDeleteApiKeyModalOpen}
modalId={DELETE_API_KEY_MODAL_ID}
title={t`Delete API key`}
subtitle={
<Trans>
@ -268,8 +269,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
<ConfirmationModal
confirmationPlaceholder={confirmationValue}
confirmationValue={confirmationValue}
isOpen={isRegenerateKeyModalOpen}
setIsOpen={setIsRegenerateKeyModalOpen}
modalId={REGENERATE_API_KEY_MODAL_ID}
title={t`Regenerate an API key`}
subtitle={
<Trans>

View File

@ -1,6 +1,6 @@
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import styled from '@emotion/styled';
import { useMemo, useState } from 'react';
import { useMemo } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
@ -11,11 +11,10 @@ import { Select } from '@/ui/input/components/Select';
import { TextArea } from '@/ui/input/components/TextArea';
import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { Trans, useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { Button, IconButton, SelectOption } from 'twenty-ui/input';
import {
H2Title,
IconBox,
@ -25,8 +24,9 @@ import {
IconTrash,
useIcons,
} from 'twenty-ui/display';
import { Button, IconButton, SelectOption } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const OBJECT_DROPDOWN_WIDTH = 340;
const ACTION_DROPDOWN_WIDTH = 140;
const OBJECT_MOBILE_WIDTH = 150;
@ -48,6 +48,8 @@ const StyledPlaceholder = styled.div`
width: ${({ theme }) => theme.spacing(8)};
`;
const DELETE_WEBHOOK_MODAL_ID = 'delete-webhook-modal';
export const SettingsDevelopersWebhooksDetail = () => {
const { t } = useLingui();
@ -77,8 +79,7 @@ export const SettingsDevelopersWebhooksDetail = () => {
isCreationMode,
});
const [isDeleteWebhookModalOpen, setIsDeleteWebhookModalOpen] =
useState(false);
const { openModal } = useModal();
const fieldTypeOptions: SelectOption<string>[] = useMemo(
() => [
@ -219,13 +220,12 @@ export const SettingsDevelopersWebhooksDetail = () => {
variant="secondary"
title={t`Delete`}
Icon={IconTrash}
onClick={() => setIsDeleteWebhookModalOpen(true)}
onClick={() => openModal(DELETE_WEBHOOK_MODAL_ID)}
/>
<ConfirmationModal
confirmationPlaceholder={confirmationText}
confirmationValue={confirmationText}
isOpen={isDeleteWebhookModalOpen}
setIsOpen={setIsDeleteWebhookModalOpen}
modalId={DELETE_WEBHOOK_MODAL_ID}
title={t`Delete webhook`}
subtitle={
<Trans>

View File

@ -9,12 +9,12 @@ import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { ApolloError } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@ -28,6 +28,9 @@ import { SettingsCustomDomain } from '~/pages/settings/workspace/SettingsCustomD
import { SettingsSubdomain } from '~/pages/settings/workspace/SettingsSubdomain';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID =
'subdomain-change-confirmation-modal';
export const SettingsDomain = () => {
const navigate = useNavigateSettings();
const { t } = useLingui();
@ -73,10 +76,7 @@ export const SettingsDomain = () => {
currentWorkspaceState,
);
const [
isSubdomainChangeConfirmationModalOpen,
setIsSubdomainChangeConfirmationModalOpen,
] = useState(false);
const { openModal } = useModal();
const form = useForm<{
subdomain: string;
@ -201,7 +201,7 @@ export const SettingsDomain = () => {
isDefined(values.subdomain) &&
values.subdomain !== currentWorkspace.subdomain
) {
setIsSubdomainChangeConfirmationModalOpen(true);
openModal(SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID);
return;
}
@ -240,10 +240,9 @@ export const SettingsDomain = () => {
</SettingsPageContainer>
</SubMenuTopBarContainer>
<ConfirmationModal
isOpen={isSubdomainChangeConfirmationModalOpen}
modalId={SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID}
title={t`Change subdomain?`}
subtitle={t`You're about to change your workspace subdomain. This action will log out all users.`}
setIsOpen={setIsSubdomainChangeConfirmationModalOpen}
onConfirmClick={() => {
const values = form.getValues();
currentWorkspace &&

View File

@ -13,7 +13,7 @@ export const RootDecorator: Decorator = (Story, context) => {
const disableHotkeyInitialization = parameters.disableHotkeyInitialization;
return (
<RecoilRoot>
<RecoilRoot initializeState={parameters.initializeState}>
<ApolloProvider client={mockedApolloClient}>
<ApolloMetadataClientMockedProvider>
{!disableHotkeyInitialization && (