From 2bb7802fb91da6060112e45562925168c51b99b4 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Mon, 19 Aug 2024 13:01:25 +0300 Subject: [PATCH] feat: accept & decline workspace invite as a registered member (#2675) * abstract base invite banner * WIP banner actions * WIP modify obj * minor fix * invite accept/decline cache mutations * banner accept/decline basically works * new block for accepting workspace invite * WIP wrong account flow * login/registration block changes * add email invite related changes * add new email FE * add email w/ invite works * final adjustments * minor fixes * addressing cr comments * no-FF support * extra workspace ff checks --- .../frontend-2/components/auth/LoginPanel.vue | 22 +- .../components/auth/LoginWithEmailBlock.vue | 33 ++- .../components/auth/RegisterPanel.vue | 50 ++-- .../auth/RegisterWithEmailBlock.vue | 33 ++- .../components/auth/WorkspaceInviteHeader.vue | 42 +++ .../auth/third-party/LoginBlock.vue | 7 +- .../auth/third-party/LoginButtonOIDC.vue | 19 +- .../components/error/page/Renderer.vue | 11 +- .../error/page/WorkspaceAccessErrorBlock.vue | 31 +++ .../frontend-2/components/invite/Banner.vue | 130 ++++++++++ .../project/page/settings/general/General.vue | 5 +- .../components/projects/Dashboard.vue | 88 +------ .../components/projects/DashboardHeader.vue | 101 ++++++++ .../components/projects/invite/Banner.vue | 125 ++------- .../components/projects/invite/Banners.vue | 14 +- .../workspaces/members/MembersTable.vue | 4 - .../components/workspace/invite/Banner.vue | 63 +++++ .../components/workspace/invite/Banners.vue | 27 ++ .../components/workspace/invite/Block.vue | 154 +++++++++++ .../dialog}/EmailsRow.vue | 0 .../dialog}/UserRow.vue | 0 .../frontend-2/lib/auth/composables/auth.ts | 3 +- .../frontend-2/lib/auth/graphql/queries.ts | 15 +- .../lib/common/composables/users.ts | 6 +- .../lib/common/generated/gql/gql.ts | 74 +++++- .../lib/common/generated/gql/graphql.ts | 103 ++++++-- .../frontend-2/lib/common/helpers/graphql.ts | 64 ++++- .../frontend-2/lib/common/helpers/route.ts | 2 + .../lib/dashboard/graphql/queries.ts | 1 + .../lib/projects/graphql/queries.ts | 2 +- .../lib/server/composables/invites.ts | 4 +- .../lib/workspaces/composables/management.ts | 245 +++++++++++++++++- .../lib/workspaces/graphql/mutations.ts | 10 + .../lib/workspaces/graphql/queries.ts | 9 + .../lib/workspaces/helpers/roles.ts | 4 +- .../middleware/003-acceptInvites.global.ts | 89 +++++-- packages/frontend-2/pages/dashboard.vue | 2 + .../frontend-2/pages/projects/[id]/index.vue | 1 + .../pages/workspaces/[id]/index.vue | 2 +- .../frontend-2/plugins/006-dataPreload.ts | 21 +- .../typedefs/serverInvites.graphql | 2 +- .../typedefs/workspaces.graphql | 35 ++- .../core/domain/userEmails/operations.ts | 15 ++ .../modules/core/graph/generated/graphql.ts | 29 ++- .../core/graph/resolvers/userEmails.ts | 23 +- packages/server/modules/core/helpers/types.ts | 6 - .../modules/core/repositories/userEmails.ts | 21 +- .../modules/core/services/userEmails.ts | 53 ++++ .../server/modules/core/services/users.js | 29 ++- .../integration/userEmails.graph.spec.ts | 23 +- .../core/tests/integration/userEmails.spec.ts | 41 ++- .../modules/core/tests/usersGraphql.spec.ts | 26 +- .../graph/generated/graphql.ts | 24 +- .../graph/resolvers/serverInvites.ts | 40 ++- .../serverinvites/services/operations.ts | 6 + .../serverinvites/services/processing.ts | 60 ++++- .../workspaces/graph/resolvers/workspaces.ts | 49 +++- .../modules/workspaces/services/invites.ts | 36 ++- .../graph/resolvers/workspacesCore.ts | 8 + .../workspacesCore/helpers/graphTypes.ts | 5 + .../server/test/graphql/generated/graphql.ts | 24 +- 61 files changed, 1736 insertions(+), 435 deletions(-) create mode 100644 packages/frontend-2/components/auth/WorkspaceInviteHeader.vue create mode 100644 packages/frontend-2/components/error/page/WorkspaceAccessErrorBlock.vue create mode 100644 packages/frontend-2/components/invite/Banner.vue create mode 100644 packages/frontend-2/components/projects/DashboardHeader.vue create mode 100644 packages/frontend-2/components/workspace/invite/Banner.vue create mode 100644 packages/frontend-2/components/workspace/invite/Banners.vue create mode 100644 packages/frontend-2/components/workspace/invite/Block.vue rename packages/frontend-2/components/workspace/{invite-dialog => invite/dialog}/EmailsRow.vue (100%) rename packages/frontend-2/components/workspace/{invite-dialog => invite/dialog}/UserRow.vue (100%) create mode 100644 packages/server/modules/core/services/userEmails.ts diff --git a/packages/frontend-2/components/auth/LoginPanel.vue b/packages/frontend-2/components/auth/LoginPanel.vue index 5045c8ccd..b4529d0a1 100644 --- a/packages/frontend-2/components/auth/LoginPanel.vue +++ b/packages/frontend-2/components/auth/LoginPanel.vue @@ -6,7 +6,7 @@ class="mx-auto w-full" >
-
+

{{ title }}

@@ -14,11 +14,13 @@ {{ subtitle }}
+
{{ hasThirdPartyStrategies ? 'Or login with your email' : '' }}
- -
+ +
Don't have an account? Register @@ -43,10 +49,10 @@ import { useQuery } from '@vue/apollo-composable' import { AuthStrategy } from '~~/lib/auth/helpers/strategies' import { useLoginOrRegisterUtils, useAuthManager } from '~~/lib/auth/composables/auth' -import { loginServerInfoQuery } from '~~/lib/auth/graphql/queries' import { LayoutDialog } from '@speckle/ui-components' import { ArrowRightIcon } from '@heroicons/vue/20/solid' import { registerRoute } from '~~/lib/common/helpers/route' +import { authLoginPanelQuery } from '~/lib/auth/graphql/queries' const props = withDefaults( defineProps<{ @@ -61,9 +67,13 @@ const props = withDefaults( } ) +const { appId, challenge } = useLoginOrRegisterUtils() const { isLoggedIn } = useActiveUser() const { inviteToken } = useAuthManager() const router = useRouter() +const { result } = useQuery(authLoginPanelQuery, () => ({ + token: inviteToken.value +})) const finalRegisterRoute = computed(() => { const result = router.resolve({ @@ -77,8 +87,8 @@ const concreteComponent = computed(() => { return props.dialogMode ? LayoutDialog : 'div' }) -const { result } = useQuery(loginServerInfoQuery) -const { appId, challenge } = useLoginOrRegisterUtils() +const workspaceInvite = computed(() => result.value?.workspaceInvite) +const forcedInviteEmail = computed(() => workspaceInvite.value?.email) const serverInfo = computed(() => result.value?.serverInfo) const hasLocalStrategy = computed(() => diff --git a/packages/frontend-2/components/auth/LoginWithEmailBlock.vue b/packages/frontend-2/components/auth/LoginWithEmailBlock.vue index 3f7a4d4b3..fe5bb1dbd 100644 --- a/packages/frontend-2/components/auth/LoginWithEmailBlock.vue +++ b/packages/frontend-2/components/auth/LoginWithEmailBlock.vue @@ -10,7 +10,7 @@ color="foundation" :rules="emailRules" show-label - :disabled="loading" + :disabled="!!(loading || shouldForceInviteEmail)" auto-focus /> () -const { handleSubmit } = useForm() +const { handleSubmit, setValues } = useForm() const loading = ref(false) const emailRules = [isEmail] @@ -66,6 +79,12 @@ const isMounted = useMounted() const { loginWithEmail } = useAuthManager() const { triggerNotification } = useGlobalToast() +const inviteEmail = computed(() => props.workspaceInvite?.email) +const isInviteForExistingUser = computed(() => !!props.workspaceInvite?.user) +const shouldForceInviteEmail = computed( + () => !!(inviteEmail.value && isInviteForExistingUser.value) +) + const onSubmit = handleSubmit(async ({ email, password }) => { try { loading.value = true @@ -80,4 +99,14 @@ const onSubmit = handleSubmit(async ({ email, password }) => { loading.value = false } }) + +watch( + shouldForceInviteEmail, + (shouldForce) => { + if (shouldForce) { + setValues({ email: inviteEmail.value || '' }) + } + }, + { immediate: true } +) diff --git a/packages/frontend-2/components/auth/RegisterPanel.vue b/packages/frontend-2/components/auth/RegisterPanel.vue index e3fc57915..b13451d4b 100644 --- a/packages/frontend-2/components/auth/RegisterPanel.vue +++ b/packages/frontend-2/components/auth/RegisterPanel.vue @@ -1,7 +1,7 @@