Commit Graph

248 Commits

Author SHA1 Message Date
dde70ee3b0 Add fields for admin panel access and workspace version (#10451)
Prepare for better version upgrade system + split admin panel into two
permissions + fix GraphQL generation detection

---------

Co-authored-by: ehconitin <nitinkoche03@gmail.com>
2025-02-24 21:38:41 +01:00
cbd4d98c85 Data changes to prepare for workspaceMember page (#10439)
Workspace Member will get their own record page in the future.

This PR lays backend changes to prepare for this:
- Settings most fields on WorkspaceMember as system fields
- Renaming workspaceMember/workspaceMemberId to
forWorkspaceMember/forWorkspaceMemberId as it conflicts with the morph
relationship, if we want to be able to add a workspace member as
favorite

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-02-24 17:37:08 +01:00
9c7213a1e1 Fix hover on table cells when the command menu is opened (#10446)
This PR allows the table cell hover to be accessible even when the
command menu is opened.
I extracted the hotkeys logic from `RecordTableCellSoftFocusMode` into
`RecordTableCellSoftFocusModeHotkeysSetterEffect`. This component is
mounted conditionally if the hotkey scope is `TableSoftFocus`. By doing
so, the table cell hotkey scopes are not available when the command menu
is opened.

Before


https://github.com/user-attachments/assets/f0925565-f00a-4962-b18d-3c1617f77dd0


After


https://github.com/user-attachments/assets/49ec9195-3110-46d7-abb6-12854a8bb991

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2025-02-24 17:09:23 +01:00
730792c947 [permissions] Move SettingsPermissions from twenty-shared to twenty-server (#10430) 2025-02-24 11:16:53 +00:00
50bd91262f [permissions] Rename enum SettingsFeatures --> SettingsPermissions (#10389) 2025-02-21 16:04:30 +00:00
9f454c565b 410 open in side panel (#10363)
Closes https://github.com/twentyhq/core-team-issues/issues/410

- Added `openRecordIn` column in the `view` entity, which is set to
`SIDE_PANEL` by default
- Created a new option inside the view option dropdown to be able to set
`openRecordIn`
- Updated all record show page openings to reflect the setting behavior
- For `workflow`, `workflowVersion` and `workflowRun` (what I call
workflow objects), we want the default view `openRecordIn` to be set to
`RECORD_PAGE`. When seeding the views for the new workspaces, we set
`openRecordIn` to `RECORD_PAGE` for workflow objects. Since the workflow
objects views `openRecordIn` will be set to the default value
`SIDE_PANEL` for the existing workspaces when the sync metadata runs, I
created a script to run in the 0.43 update to update this value.
- Updated `closeCommandMenu` because of problems introduced by the
animate presence wrapper around the command menu. We now reset the
states at the end of the animation.

Note: We want to be able to open all workflow objects pages in the side
panel, but this requires some refactoring of the workflow module. For
now @Bonapara wanted to allow the possibility to change the
`openRecordIn` setting to `SIDE_PANEL` even for the workflows even if
it's buggy and not ready for the moment. Since this is an experimental
feature, it shouldn't cause too many problems.
2025-02-21 09:27:33 +00:00
2fc8eaa25b Implement record sort states and record sort context (#10257)
This PR is simple, it creates states for record sort, mirroring record
filter states.

It also implements RecordSortsComponentInstanceContext everywhere
RecordFiltersComponentInstanceContext is used.

This could be later merged into a common RecordContext concept but we
first need to decide how to handle the existing ContextStore and
RecordIndexContext and ideally end up with a unique context (or a
context provider component that wraps in all those contexts at once).

Some bugs are already present on main when trying to delete a sort, they
will be fixed in the next PRs.
2025-02-19 16:51:49 +01:00
7636def54d typo (#10327)
typo when doing the fix
2025-02-19 13:52:43 +00:00
f9763ff7ac fixing test mainly by @charlesBochet (#10325)
to enable CI to work

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-02-19 14:13:30 +01:00
2fca60436b Add settingsPermission gate on the frontend (#10179)
## Context
With the new permissions system, we now need to hide some items from the
settings navigation and gate some routes so they can't be accessed
directly.
To avoid having to set permission gates in all the component pages, I'm
introducing wrapper at the route level and in the Navigation. This is
not required and is mostly for pages that are strictly mapped to a
single permission, for the rest we still need to use the different hooks
manually but it should avoid a bit of boilerplate for most of the cases.

- currentUserWorkspaceState to access settingsPermissions
- SettingsProtectedRouteWrapper in the router that can take a
settingFeature or a featureFlag as a gate logic, if the currentUser does
not have access to the settingFeature or the featureFlag is not enabled
they will be redirected to the profile page.
- SettingsNavigationItemWrapper & SettingsNavigationSectionWrapper. The
former will check the same logic as SettingsProtectedRouteWrapper and
not display the item if needed. The later will check if all
SettingsNavigationItemWrapper are not visible and hide itself if that's
the case.
- useHasSettingsPermission to get a specific permission state for the
current user
- useSettingsPermissionMap to get a map of all permissions with their
values for the current user
- useFeatureFlagsMap same but for featureFlags
2025-02-18 15:50:23 +01:00
fb42046033 Refacto views (#10272)
In this huge (sorry!) PR:
- introducing objectMetadataItem in contextStore instead of
objectMetadataId which is more convenient
- splitting some big hooks into smaller parts to avoid re-renders
- removing Effects to avoid re-renders (especially onViewChange)
- making the view prefetch separate from favorites to avoid re-renders
- making the view prefetch load a state and add selectors on top of it
to avoir re-renders

As a result, the performance is WAY better (I suspect the favorite
implementation to trigger a lot of re-renders unfortunately).
However, we are still facing a random app freeze on view creation. I
could not investigate the root cause. As this seems to be already there
in the precedent release, we can move forward but this seems a urgent
follow up to me ==> EDIT: I've found the root cause after a few ours of
deep dive... an infinite loop in RecordTableNoRecordGroupBodyEffect...

prastoin edit: close https://github.com/twentyhq/twenty/issues/10253

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: prastoin <paul@twenty.com>
2025-02-18 13:51:07 +01:00
103dff4bd0 File previewer (#10260)
Add a file previewer for pdf, image, doc, xls

<img width="991" alt="Screenshot 2025-02-17 at 15 03 10"
src="https://github.com/user-attachments/assets/7516c13d-d6cb-4a10-b10f-b422268d223b"
/>
2025-02-18 10:18:59 +01:00
5963c0f384 [REFACTOR][BUG] Dynamically compute field to write in cache CREATE (#10130)
# Introduction
While importing records encountering missing expected fields when
writting a fragment from apollo cache

## Updates

### 1/ `createdBy` Default value
When inserting in cache in create single or many we will now make
optimistic behavior on the createdBy value

### 2/ `createRecordInCache` dynamically create `recordGrqlFields`
When creating an entry in cache, we will now dynamically generate fields
to be written in the fragment instead of expecting all of them. As by
nature record could be partial

### 3/ Strictly typed `RecordGqlFields`

# Conclusion
closes #9927
2025-02-13 17:43:54 +01:00
8a425456f2 feat(workspace): add support for custom domain status toggle (#10114)
Introduce isCustomDomainEnabled field in Workspace entity to manage
custom domain activation. Update related services, types, and logic to
validate and toggle the custom domain's status dynamically based on its
current state. This ensures accurate domain configurations are reflected
across the system.

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-02-13 15:01:33 +00:00
ba8797d220 Remove filterDefinition.type usage (#10164)
This PR essentially removes the usage of filterDefinition.type, by
replacing it with fieldMetadataItem.type derivation. Thus allowing to
completely remove filterDefinition later on.

In computeFilterRecordGqlOperationFilter, emptyOperationFilter is now
returned before going into the big switch case. This avoids repeating
the same exact call to getEmptyRecordGqlOperationFilter for each type.

Fixed some tests that need
getJestMetadataAndApolloMocksAndActionMenuWrapper to have record filters
properly working with the new implementation. We'll probably want to
refactor the record context store, record index context, etc.

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2025-02-13 00:57:28 +01:00
ed4a5b0c15 fix: many fields in an object (#10061)
Co-authored-by: Charles Bochet <charles@twenty.com>
2025-02-11 17:15:30 +01:00
47f262c970 [REFACTOR][BUG] Dynamically compute field to write in cache UPDATE & DELETE (#10079)
# Introduction
At the moment when updating any record cache occurence, we will build a
fragment that will expect all of the object metadata item fields to be
provided.
Which result in the following traces: ( in the video companies aren't
fetch with companyId and other missing fields )


https://github.com/user-attachments/assets/56eab7c1-8f01-45ff-8f5d-78737b788b92

By definition as we're using graphql we might not request every record's
fields each time we wanna consume them.
In this way we will now dynamically compute or expect depending on the
CRUD operation specific fields to be written in the cache, and not all
of them

Tested all optimistic and failure management use cases

## Covering cache
Added coverage only for the `deleteOne` and `deleteMany` hooks, it cover
only the record record cache and not its relations hydratation ( for the
moment )

## Why not closing #9927 
Unless I'm mistaken everything done here have fixed the same logs/traces
issue for updates and deletion but not creation.
Which means we still need to investigate the mass upload from import and
prefillRecord behavior

In a nutshell: went over each `updateRecordFromCache` calls, still need
to do all `createRecordInCache` calls

related to #9927 

## Conlusion
Sorry for the big PR should have ejected into a specific one for the
`MinimalRecord` refactor
Will also continue covering others hooks later in my week as for the
`deleteOne`
As always any suggestions are welcomed !
2025-02-11 16:21:44 +01:00
cc68deaab1 Translations cleaning / workflows (#10125) 2025-02-11 15:26:21 +01:00
02ced028e5 add role assignment page (#10115)
## Context
This PR introduces the "assignment" tab in the Role edit page, currently
allowing admin users to assign workspace members to specific roles.

Note: For now, a user can only have one role and a modal will warn you
if you try to re-assign a user to a new role.

## Test
<img width="648" alt="Screenshot 2025-02-10 at 17 59 21"
src="https://github.com/user-attachments/assets/dabd7a17-6aca-4d2b-95d8-46182f53e1e8"
/>
<img width="668" alt="Screenshot 2025-02-10 at 17 59 33"
src="https://github.com/user-attachments/assets/802aab7a-db67-4f83-9a44-35773df100f7"
/>
<img width="629" alt="Screenshot 2025-02-10 at 17 59 42"
src="https://github.com/user-attachments/assets/277db061-3f05-4ccd-8a83-7a96d6c1673e"
/>
2025-02-11 14:51:31 +01:00
4f06b83d7f RICH_TEXT_V2 frontend (#10083)
Adds task and note support for the new `bodyV2` field. (Field metadata
type of `bodyV2` is `RICH_TEXT_V2`.)

Related to issue https://github.com/twentyhq/twenty/issues/7613

Upgrade commands will be in separate PRs.

Fixes https://github.com/twentyhq/twenty/issues/10084

---------

Co-authored-by: ad-elias <elias@autodiligence.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2025-02-11 10:21:03 +00:00
bb24c97f80 Translations - Crowdin, Set workspace member locale on signup, and optimizations (#10091)
More progress on translations:
- Migrate from translations.io to crowdin
- Optimize performance and robustness 
- Set workspaceMember/user locale upon signup
2025-02-09 22:10:41 +01:00
ead626c2ec 360 workflow implement workflow cron triggers frontend 2 (#10051)
as title, closes https://github.com/twentyhq/core-team-issues/issues/360

## Cron Setting behavior

https://github.com/user-attachments/assets/0de3a8b9-d899-4455-a945-20c7541c3053

## Cron running behavior


https://github.com/user-attachments/assets/4c33f167-857c-4fcb-9dbe-0f9b661c9e61
2025-02-07 17:15:03 +01:00
68183b7c85 feat(): enable custom domain usage (#9911)
# Content
- Introduce the `workspaceUrls` property. It contains two
sub-properties: `customUrl, subdomainUrl`. These endpoints are used to
access the workspace. Even if the `workspaceUrls` is invalid for
multiple reasons, the `subdomainUrl` remains valid.
- Introduce `ResolveField` workspaceEndpoints to avoid unnecessary URL
computation on the frontend part.
- Add a `forceSubdomainUrl` to avoid custom URL using a query parameter
2025-02-07 14:34:26 +01:00
3cc66fe712 Remove the source handle for leaf nodes (#10057)
- Do not render a source handle for the leaf nodes
- Upgrade the `@xyflow/react` library

| Before | After |
|--------|--------|
| ![CleanShot 2025-02-06 at 16 21
08@2x](https://github.com/user-attachments/assets/42b7d11b-76bf-43b9-ba91-8d0c5c2f1792)
| ![CleanShot 2025-02-06 at 16 21
24@2x](https://github.com/user-attachments/assets/ac94aa32-45ad-4462-8db9-0078d6252ea4)
|

## Other options considered

React Flow exposes a hook to get the connections of the current node. I
tried to use this hook – which makes things way simpler – but I couldn't
find a way to make it work in Storybook. I had two options: 1. Set up
React Flow to render the nodes properly, 2. Mock the hook in Storybook.

The first option was hard to achieve as the `<Reactflow />` component
renders a whole flow, and it doesn't play well with the idea of
rendering a single node in a story.

The second option seemed overkill as mocking modules with Storybook is
not straightforward. See
https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.

I chose to keep the initial version of my code, written before I spot a
function simplifying the code. We can give it a look another time.
2025-02-07 13:17:43 +01:00
28a3f75946 [FEAT] RecordAction destroy many record (#9991)
# Introduction
Added the `RecordAction` destroy multiple record

## Repro
Select multiples `deletedRecords`, you should be able to see the
`Destroy` pinned CTA ( iso short label with the destroy one ), open
control panel and fin new CTA `Permanently delete records`


https://github.com/user-attachments/assets/31ee8738-9d61-4dec-9a1f-41bb6785e018

## TODO
- [ ] Gain granularity within tests to assert the action should be
registered only when filtering by deleted

## Conclusion
Closes https://github.com/twentyhq/core-team-issues/issues/110
2025-02-05 11:33:01 +01:00
125a0c3419 Create variants for workflow visualizer nodes (#10006)
Closes https://github.com/twentyhq/core-team-issues/issues/332

- Create the success and failed variants
- Introduce the first responsive color
- Creating stories for the new variants

These components are not yet in use in the source code. If you want to
see them, launch Storybook.

| Success | Failure |
|--------|--------|
| ![CleanShot 2025-02-04 at 16 24
43@2x](https://github.com/user-attachments/assets/0dd68a8f-3914-4b6e-b2d8-43108c2f5e8c)
| ![CleanShot 2025-02-04 at 16 24
59@2x](https://github.com/user-attachments/assets/e4e408d3-29fb-4fbc-a277-044aec9b0f4b)
|
| ![CleanShot 2025-02-04 at 16 24
54@2x](https://github.com/user-attachments/assets/d565ee47-1476-475d-adf6-dadfff9c6719)
| ![CleanShot 2025-02-04 at 16 25
05@2x](https://github.com/user-attachments/assets/9a0aabcc-84d1-41e2-a5a1-7c8cb05f963f)
|
2025-02-04 18:38:38 +01:00
2368dad9ad Enable workflow in lab (#9997)
Refresh of `objectmetadataitems` was not happening fast enough. Page was
breaking when enabling the feature flag. Instead of not storing worklow
objects in state, we will use the feature flag to block on read. This
way we avoid race conditions

<img width="1511" alt="Capture d’écran 2025-02-04 à 14 11 56"
src="https://github.com/user-attachments/assets/912cc59a-f422-48ab-84b7-7fdd7bbc35c1"
/>
2025-02-04 15:25:04 +01:00
53b51c8bba Fix-issue-370 (#9996)
Fixes the issue from introduced when alowing gmail and outlook.

fixes https://github.com/twentyhq/core-team-issues/issues/370
2025-02-04 14:20:35 +00:00
b29ff9b4e6 Removed availableFilterDefinitions as a state but kept its usage as a derived state of objectMetadataItems (#9972)
The global record filter refactor will derive everything at runtime from
objectMetadataItemsState, thus removing the need for a filter definition
concept.

Here we don't yet remove available filter definition usage but we
replace the available filter definitions states, we now derive the same
value from objectMetadataItemsState.

This will allow us to progressively remove the usage of the concept of
filter definition, at the end it will then be easy to just remove from
the codebase because nothing will use it anymore.
2025-02-03 17:29:57 +01:00
7fd89678b7 [CHORE] Avoid isDefined duplicated reference, move it to twenty-shared (#9967)
# Introduction
Avoid having multiple `isDefined` definition across our pacakges
Also avoid importing `isDefined` from `twenty-ui` which exposes a huge
barrel for a such little util function

## In a nutshell
Removed own `isDefined.ts` definition from `twenty-ui` `twenty-front`
and `twenty-server` to move it to `twenty-shared`.
Updated imports for each packages, and added explicit dependencies to
`twenty-shared` if not already in place

Related PR https://github.com/twentyhq/twenty/pull/9941
2025-02-01 12:10:10 +01:00
d946cdcba4 Disable the fields of all CRUD workflow actions on readonly mode (#9939)
Fixes
https://discord.com/channels/1130383047699738754/1333822806504247467

In this PR:

- Make the workflow step title input readonly when the visualizer is in
readonly mode
- Make all the fields of the Update Record and Delete Record readonly
when the visualizer is in readonly mode
- Create stories for the Create Record, Updated Record and Delete Record
actions; I'm checking for the default mode and several variants of the
disabled mode
- Set up mocks for the workflows and use them in msw handlers

Follow up:

- We use `readonly` and `disabled` alternatively; these are two
different states when talking about a HTML `<input />` element. I think
we should settle on a single word.
- Refactor the `<WorkflowSingleRecordPicker />` component to behave as
other selects

| Current component | Should look like |
|--------|--------|
| ![CleanShot 2025-01-30 at 17 30
29@2x](https://github.com/user-attachments/assets/104f2e7f-d758-4121-987a-f62f2e138df2)
| ![CleanShot 2025-01-30 at 17 30
49@2x](https://github.com/user-attachments/assets/e74b318e-a41a-40b9-9db8-bcc8015a1d67)
|
2025-01-31 12:31:57 +01:00
ce8c6c4bac fix: correct typo in property name from "Entreprise" to "Enterprise" (#9916)
The property name "hasValidEntrepriseKey" was corrected to
"hasValidEnterpriseKey" across multiple files for consistency and
accuracy. This ensures proper alignment with naming conventions and
avoids potential issues in usage or understanding.
2025-01-29 17:39:04 +01:00
9d32e63111 Continue Frontend localization (#9909)
Translation more content on the frontend
2025-01-29 17:36:28 +01:00
eb88f6f584 feat(custom-domain): remove domainName + add migration for custom dom… (#9872)
…ain + feature flag

Blocked by #9849
2025-01-28 15:28:18 +01:00
af8d22ee99 Fix ObjectType casing and conflict between Relation and RelationMetadata (#9849)
Fixes #9827 

Also uncovered a conflict with `@objectType('Relation')` and
`@objectType('relation)`

I don't want to address it in this PR so I will create a followup issue
when we close this but I think there's a confusion between
Relation/RelationMetadata, it's unclear what is what

---------

Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
2025-01-28 10:06:18 +01:00
bddca09451 Refactored table filters to consume new currentRecordFilters component state (#9652)
This PR implements a first real use case, now currentRecordFilters
component state acts as the global record filter reference.

It is set by the view initially and can be reset to view filters state
at any point.

This new state is also modified by two new upsertRecordFilter /
removeRecordFilter hooks that will be drop-in replacement of the actual
upsertCombinedViewFilter and removeCombinediewFilter hooks.

This PR implements the logic to manipulate record filters but only reads
it to make the table find many request, all other features are still
relying on the old view filter implementation.

Advanced filters are ignored because they are hidden and because this
effort is made precisely to allow the completion of the advanced filters
feature.
2025-01-23 11:09:44 +01:00
8ab01ebef4 [BUG] Record settings not saved (#9762)
# Introduction
By initially fixing this Fixes #9381, discovered other behavior that
have been fix.
Overall we encountered a bug that corrupts a workspace and make the
browser + api crash
This issue https://github.com/twentyhq/core-team-issues/issues/25
suggests a refactor that has final save button instead of auto-save

## `labelIdentifierFieldMetadataId` form default value
The default value resulted in being undefined, resulting in react hook
form `labelIdentifierFieldMetadataId` is required field error.

### Fix
Setting default value fallback to `null`  as field is `nullable`

## `SettingsDataModelObjectSettingsFormCard` never triggers form
Unless I'm mistaken in production touching any fields within
`SettingsDataModelObjectSettingsFormCard` would never trigger form
submission until you also modify `SettingsDataModelObjectAboutForm`
fields

### Fix
Provide and apply `onblur` that triggers the form on both
`SettingsDataModelObjectSettingsFormCard` inputs

## Wrong default `labelIdentifierFieldMetadataItem` on first page render
When landing on the page for the first time, if a custom
`labelIdentifierFieldMetadataItem` has been set it won't be computed
within the `PreviewCard`.
Occurs when `labelIdentifierFieldMetadataId` form default value is
undefined, due to `any` injection.

### Fix
In the `getLabelIdentifierFieldMetadataItem` check the
`labelIdentifierFieldMetadataIdFormValue` definition, if undefined
fallback to current `objectMetadata` identifier

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-01-22 16:32:57 +01:00
80c9ebfd4e Remove isGmailSendEmailScopeEnabled featureFlag (#9787)
as title
2025-01-22 15:53:40 +01:00
d8815d7ebf fix: prevent billingPortal creation if no active subscription (#9701)
Billing portal is created in settings/billing page even if subscription
is canceled, causing server internal error. -> Skip back end request

Bonus : display settings/billing page with disabled button even if
subscription is canceled

---------

Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2025-01-21 15:01:18 +01:00
50f36e345e Lab (#9667)
https://github.com/twentyhq/core-team-issues/issues/76
2025-01-21 14:30:59 +01:00
e1731bb31e chore: update codegen config for enum naming convention (#9751)
Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
2025-01-21 11:34:33 +01:00
056cb7c66d Translation followup (#9735)
Address PR comments and more progress on translation
2025-01-19 13:29:19 +01:00
052331685f Add more translations (#9733) 2025-01-18 17:38:05 +01:00
f38a25412e Add more translations (#9707)
As per title
2025-01-17 12:50:28 +01:00
7acb68929f Progress on translations (#9703)
Start adding a few translations on setting pages, introduce
pseudo-locale, switch to dynamic import, add eslint rule
2025-01-16 23:34:54 +01:00
f44b31573a Set up localization with feature flag control (#9649)
Refers #8128 

Changes Introduced:
- Added i18n configuration.
- Added a feature flag for localization.
- Enabled language switching based on the flag.

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
2025-01-16 21:00:56 +01:00
f621af1732 fix: date input click outside (#9676)
cc @lucasbordeau

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2025-01-16 17:35:59 +01:00
f545bd1c40 Treat suspended workspace as workspaces that need to be synced (#9669)
In this PR:
- migrate WorkspaceActivationStatus to twenty-shared (and update case to
make FE and BE consistent)
- introduce isWorkspaceActiveOrSuspended in twenty-shared
- refactor the code to use it (when we fetch data on the FE, we want to
keep SUSPENDED workspace working + when we sync workspaces we want it
too)
2025-01-16 15:01:04 +01:00
26058f3e25 Update ChooseYourPlan page with new trial period options (#9628)
### Context
- Update /plan-required page to let users get free trial without credit
card plan
- Update usePageChangeEffectNavigateLocation to redirect paused and
canceled subscription (suspended workspace) to /settings/billing page

### To do

- [x] Update usePageChangeEffectNavigateLocation test
- [x] Update ChooseYourPlan sb test



closes #9520

---------

Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
2025-01-16 11:10:36 +01:00
f722a2d619 Add Email Verification for non-Microsoft/Google Emails (#9288)
Closes twentyhq/twenty#8240 

This PR introduces email verification for non-Microsoft/Google Emails:

## Email Verification SignInUp Flow:

https://github.com/user-attachments/assets/740e9714-5413-4fd8-b02e-ace728ea47ef

The email verification link is sent as part of the
`SignInUpStep.EmailVerification`. The email verification token
validation is handled on a separate page (`AppPath.VerifyEmail`). A
verification email resend can be triggered from both pages.

## Email Verification Flow Screenshots (In Order):

![image](https://github.com/user-attachments/assets/d52237dc-fcc6-4754-a40f-b7d6294eebad)

![image](https://github.com/user-attachments/assets/263a4b6b-db49-406b-9e43-6c0f90488bb8)

![image](https://github.com/user-attachments/assets/0343ae51-32ef-48b8-8167-a96deb7db99e)

## Sent Email Details (Subject & Template):
![Screenshot 2025-01-05 at 11 56
56 PM](https://github.com/user-attachments/assets/475840d1-7d47-4792-b8c6-5c9ef5e02229)

![image](https://github.com/user-attachments/assets/a41b3b36-a36f-4a8e-b1f9-beeec7fe23e4)

### Successful Email Verification Redirect:

![image](https://github.com/user-attachments/assets/e2fad9e2-f4b1-485e-8f4a-32163c2718e7)

### Unsuccessful Email Verification (invalid token, invalid email, token
expired, user does not exist, etc.):

![image](https://github.com/user-attachments/assets/92f4b65e-2971-4f26-a9fa-7aafadd2b305)

### Force Sign In When Email Not Verified:

![image](https://github.com/user-attachments/assets/86d0f188-cded-49a6-bde9-9630fd18d71e)

# TODOs:

## Sign Up Process

- [x] Introduce server-level environment variable
IS_EMAIL_VERIFICATION_REQUIRED (defaults to false)
- [x] Ensure users joining an existing workspace through an invite are
not required to validate their email
- [x] Generate an email verification token
- [x] Store the token in appToken
- [x] Send email containing the verification link
  - [x] Create new email template for email verification
- [x] Create a frontend page to handle verification requests

## Sign In Process

- [x] After verifying user credentials, check if user's email is
verified and prompt to to verify
- [x] Show an option to resend the verification email

## Database

- [x] Rename the `emailVerified` colum on `user` to to `isEmailVerified`
for consistency

## During Deployment
- [x] Run a script/sql query to set `isEmailVerified` to `true` for all
users with a Google/Microsoft email and all users that show an
indication of a valid subscription (e.g. linked credit card)
- I have created a draft migration file below that shows one possible
approach to implementing this change:

```typescript
import { MigrationInterface, QueryRunner } from 'typeorm';

export class UpdateEmailVerifiedForActiveUsers1733318043628
  implements MigrationInterface
{
  name = 'UpdateEmailVerifiedForActiveUsers1733318043628';

  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE core."user_email_verified_backup" AS
      SELECT id, email, "isEmailVerified"
      FROM core."user"
      WHERE "deletedAt" IS NULL;
    `);

    await queryRunner.query(`
      -- Update isEmailVerified for users who have been part of workspaces with active subscriptions
      UPDATE core."user" u
      SET "isEmailVerified" = true
      WHERE EXISTS (
        -- Check if user has been part of a workspace through userWorkspace table
        SELECT 1 
        FROM core."userWorkspace" uw
        JOIN core."workspace" w ON uw."workspaceId" = w.id
        WHERE uw."userId" = u.id
        -- Check for valid subscription indicators
        AND (
          w."activationStatus" = 'ACTIVE'
          -- Add any other subscription-related conditions here
        )
      )
      AND u."deletedAt" IS NULL;
  `);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      UPDATE core."user" u
      SET "isEmailVerified" = b."isEmailVerified"
      FROM core."user_email_verified_backup" b
      WHERE u.id = b.id;
    `);

    await queryRunner.query(`DROP TABLE core."user_email_verified_backup";`);
  }
}

```

---------

Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-01-15 18:43:40 +01:00