fix(workspaces): add email to existing account workflow bugfix (#2703)
* fix(fe2): properly handling invitedTeam auth error * fix(fe2): disable invite dialog if not workspace admin * fix(useremails): fix for default primary:true when creating new emails * test fix
This commit is contained in:
committed by
GitHub
parent
4d82e1f575
commit
e4cc0cbc83
@@ -14,7 +14,10 @@
|
||||
v-on="on"
|
||||
/>
|
||||
</div>
|
||||
<FormButton @click="() => (isInviteDialogOpen = !isInviteDialogOpen)">
|
||||
<FormButton
|
||||
:disabled="!isWorkspaceAdmin"
|
||||
@click="() => (isInviteDialogOpen = !isInviteDialogOpen)"
|
||||
>
|
||||
Invite
|
||||
</FormButton>
|
||||
<WorkspaceInviteDialog
|
||||
@@ -29,15 +32,17 @@ import { MagnifyingGlassIcon } from '@heroicons/vue/24/outline'
|
||||
import { useDebouncedTextInput } from '@speckle/ui-components'
|
||||
import type { SettingsWorkspacesMembersTableHeader_WorkspaceFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { Roles } from '@speckle/shared'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {
|
||||
id
|
||||
role
|
||||
...WorkspaceInviteDialog_Workspace
|
||||
}
|
||||
`)
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
searchPlaceholder: string
|
||||
workspaceId: string
|
||||
workspace?: SettingsWorkspacesMembersTableHeader_WorkspaceFragment
|
||||
@@ -46,4 +51,6 @@ defineProps<{
|
||||
const search = defineModel<string>('search')
|
||||
const { on, bind } = useDebouncedTextInput({ model: search })
|
||||
const isInviteDialogOpen = ref(false)
|
||||
|
||||
const isWorkspaceAdmin = computed(() => props.workspace?.role === Roles.Workspace.Admin)
|
||||
</script>
|
||||
|
||||
@@ -64,6 +64,7 @@ import type {
|
||||
WorkspaceProjectList_ProjectCollectionFragment,
|
||||
WorkspaceProjectsQueryQueryVariables
|
||||
} from '~~/lib/common/generated/gql/graphql'
|
||||
import { skipLoggingErrorsIfOneFieldError } from '~/lib/common/helpers/graphql'
|
||||
import { workspaceRoute } from '~/lib/common/helpers/route'
|
||||
|
||||
graphql(`
|
||||
@@ -94,12 +95,23 @@ const {
|
||||
debouncedBy: 800
|
||||
})
|
||||
|
||||
const { result: initialQueryResult } = useQuery(workspacePageQuery, {
|
||||
workspaceId: props.workspaceId,
|
||||
filter: {
|
||||
search: (search.value || '').trim() || null
|
||||
}
|
||||
})
|
||||
const { result: initialQueryResult } = useQuery(
|
||||
workspacePageQuery,
|
||||
{
|
||||
workspaceId: props.workspaceId,
|
||||
filter: {
|
||||
search: (search.value || '').trim() || null
|
||||
}
|
||||
},
|
||||
() => ({
|
||||
// Custom error policy so that a failing invitedTeam resolver (due to access rights)
|
||||
// doesn't kill the entire query
|
||||
errorPolicy: 'all',
|
||||
context: {
|
||||
skipLoggingErrors: skipLoggingErrorsIfOneFieldError('invitedTeam')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const { query, identifier, onInfiniteLoad } = usePaginatedQuery<
|
||||
{ workspace: { projects: WorkspaceProjectList_ProjectCollectionFragment } },
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
:users="team.map((teamMember) => teamMember.user)"
|
||||
class="max-w-[104px]"
|
||||
/>
|
||||
<FormButton color="outline" @click="showInviteDialog = !showInviteDialog">
|
||||
<FormButton
|
||||
color="outline"
|
||||
:disabled="!isWorkspaceAdmin"
|
||||
@click="showInviteDialog = !showInviteDialog"
|
||||
>
|
||||
Invite
|
||||
</FormButton>
|
||||
</div>
|
||||
@@ -39,12 +43,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
import type { WorkspaceHeader_WorkspaceFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
|
||||
graphql(`
|
||||
fragment WorkspaceHeader_Workspace on Workspace {
|
||||
id
|
||||
role
|
||||
name
|
||||
logo
|
||||
description
|
||||
@@ -70,4 +76,7 @@ const props = defineProps<{
|
||||
const showInviteDialog = ref(false)
|
||||
|
||||
const team = computed(() => props.workspaceInfo.team || [])
|
||||
const isWorkspaceAdmin = computed(
|
||||
() => props.workspaceInfo.role === Roles.Workspace.Admin
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -111,14 +111,14 @@ const documents = {
|
||||
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n }\n }\n": types.SettingsWorkspacesMembersMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersMembersTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n": types.SettingsWorkspacesMembersMembersTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n ...WorkspaceInviteDialog_Workspace\n }\n": types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...WorkspaceInviteDialog_Workspace\n }\n": types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment ModelPageProject on Project {\n id\n createdAt\n name\n visibility\n }\n": types.ModelPageProjectFragmentDoc,
|
||||
"\n fragment ThreadCommentAttachment on Comment {\n text {\n attachments {\n id\n fileName\n fileType\n fileSize\n }\n }\n }\n": types.ThreadCommentAttachmentFragmentDoc,
|
||||
"\n fragment ViewerCommentsListItem on Comment {\n id\n rawText\n archived\n author {\n ...LimitedUserAvatar\n }\n createdAt\n viewedAt\n replies {\n totalCount\n cursor\n items {\n ...ViewerCommentsReplyItem\n }\n }\n replyAuthors(limit: 4) {\n totalCount\n items {\n ...FormUsersSelectItem\n }\n }\n resources {\n resourceId\n resourceType\n }\n }\n": types.ViewerCommentsListItemFragmentDoc,
|
||||
"\n fragment ViewerModelVersionCardItem on Version {\n id\n message\n referencedObject\n sourceApplication\n createdAt\n previewUrl\n authorUser {\n ...LimitedUserAvatar\n }\n }\n": types.ViewerModelVersionCardItemFragmentDoc,
|
||||
"\n fragment WorkspaceInviteDialog_Workspace on Workspace {\n id\n team {\n id\n user {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n title\n user {\n id\n }\n }\n }\n": types.WorkspaceInviteDialog_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...ProjectDashboardItem\n }\n cursor\n }\n": types.WorkspaceProjectList_ProjectCollectionFragmentDoc,
|
||||
"\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceInviteBanner_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBanner_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment WorkspaceInviteBanners_User on User {\n workspaceInvites {\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspaceInviteBanners_UserFragmentDoc,
|
||||
"\n fragment WorkspaceInviteBlock_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n workspaceId\n workspaceName\n token\n user {\n id\n name\n ...LimitedUserAvatar\n }\n title\n email\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBlock_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
@@ -709,7 +709,7 @@ export function graphql(source: "\n fragment SettingsWorkspacesMembersMembersTa
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n ...WorkspaceInviteDialog_Workspace\n }\n"];
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...WorkspaceInviteDialog_Workspace\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -737,7 +737,7 @@ export function graphql(source: "\n fragment WorkspaceProjectList_ProjectCollec
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"];
|
||||
export function graphql(source: "\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -294,6 +294,9 @@ function createCache(): InMemoryCache {
|
||||
invitedTeam: {
|
||||
merge: (_existing, incoming) => incoming
|
||||
},
|
||||
team: {
|
||||
merge: (_existing, incoming) => incoming
|
||||
},
|
||||
projects: {
|
||||
keyArgs: ['filter', 'limit'],
|
||||
merge: buildAbstractCollectionMergeFunction('ProjectCollection')
|
||||
|
||||
@@ -1804,6 +1804,7 @@ export type Project = {
|
||||
visibility: ProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
workspaceId?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -5268,6 +5269,7 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
|
||||
visibility?: Resolver<ResolversTypes['ProjectVisibility'], ParentType, ContextType>;
|
||||
webhooks?: Resolver<ResolversTypes['WebhookCollection'], ParentType, ContextType, Partial<ProjectWebhooksArgs>>;
|
||||
workspace?: Resolver<Maybe<ResolversTypes['Workspace']>, ParentType, ContextType>;
|
||||
workspaceId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ export const createUserEmailFactory =
|
||||
const [row] = await db<UserEmail>(UserEmails.name).insert(
|
||||
{
|
||||
id,
|
||||
primary: true,
|
||||
email: email.toLowerCase().trim(),
|
||||
...rest
|
||||
},
|
||||
|
||||
@@ -145,7 +145,8 @@ module.exports = {
|
||||
userEmail: {
|
||||
email: user.email,
|
||||
userId: user.id,
|
||||
verified: user.verified
|
||||
verified: user.verified,
|
||||
primary: true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -406,7 +406,7 @@ describe('Core @user-emails', () => {
|
||||
const newEmail = createRandomEmail()
|
||||
const createdEmail = (
|
||||
await createUserEmail({
|
||||
userEmail: { email: newEmail, userId }
|
||||
userEmail: { email: newEmail, userId, primary: false }
|
||||
})
|
||||
)?.email
|
||||
|
||||
|
||||
@@ -1793,6 +1793,7 @@ export type Project = {
|
||||
visibility: ProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
workspaceId?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1794,6 +1794,7 @@ export type Project = {
|
||||
visibility: ProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
workspaceId?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user