diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index e9ee0298..1ca78d21 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,5 +1,11 @@ # @baseapp-frontend/components +## 0.0.34 + +### Patch Changes + +- Implement change role functionality on members section + ## 0.0.33 ### Patch Changes diff --git a/packages/components/__generated__/ChangeUserRoleMutation.graphql.ts b/packages/components/__generated__/ChangeUserRoleMutation.graphql.ts new file mode 100644 index 00000000..76003eb7 --- /dev/null +++ b/packages/components/__generated__/ChangeUserRoleMutation.graphql.ts @@ -0,0 +1,160 @@ +/** + * @generated SignedSource<<05311bb05e2c36ab1ed76caf2054ef2b>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ + +/* eslint-disable */ +// @ts-nocheck +import { ConcreteRequest, Mutation } from 'relay-runtime' + +export type ProfileRoles = 'ADMIN' | 'MANAGER' | '%future added value' +export type RoleUpdateInput = { + clientMutationId?: string | null | undefined + profileId: string + roleType?: ProfileRoles | null | undefined + userId: string +} +export type ChangeUserRoleMutation$variables = { + input: RoleUpdateInput +} +export type ChangeUserRoleMutation$data = { + readonly profileRoleUpdate: + | { + readonly errors: + | ReadonlyArray< + | { + readonly field: string + readonly messages: ReadonlyArray + } + | null + | undefined + > + | null + | undefined + readonly profileUserRole: + | { + readonly id: string + readonly role: ProfileRoles | null | undefined + } + | null + | undefined + } + | null + | undefined +} +export type ChangeUserRoleMutation = { + response: ChangeUserRoleMutation$data + variables: ChangeUserRoleMutation$variables +} + +const node: ConcreteRequest = (function () { + var v0 = [ + { + defaultValue: null, + kind: 'LocalArgument', + name: 'input', + }, + ], + v1 = [ + { + alias: null, + args: [ + { + kind: 'Variable', + name: 'input', + variableName: 'input', + }, + ], + concreteType: 'RoleUpdatePayload', + kind: 'LinkedField', + name: 'profileRoleUpdate', + plural: false, + selections: [ + { + alias: null, + args: null, + concreteType: 'ProfileUserRole', + kind: 'LinkedField', + name: 'profileUserRole', + plural: false, + selections: [ + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'id', + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'role', + storageKey: null, + }, + ], + storageKey: null, + }, + { + alias: null, + args: null, + concreteType: 'ErrorType', + kind: 'LinkedField', + name: 'errors', + plural: true, + selections: [ + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'field', + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'messages', + storageKey: null, + }, + ], + storageKey: null, + }, + ], + storageKey: null, + }, + ] + return { + fragment: { + argumentDefinitions: v0 /*: any*/, + kind: 'Fragment', + metadata: null, + name: 'ChangeUserRoleMutation', + selections: v1 /*: any*/, + type: 'Mutation', + abstractKey: null, + }, + kind: 'Request', + operation: { + argumentDefinitions: v0 /*: any*/, + kind: 'Operation', + name: 'ChangeUserRoleMutation', + selections: v1 /*: any*/, + }, + params: { + cacheID: 'b8717d03953ac3aeaf61e4a3b826c7d1', + id: null, + metadata: {}, + name: 'ChangeUserRoleMutation', + operationKind: 'mutation', + text: 'mutation ChangeUserRoleMutation(\n $input: RoleUpdateInput!\n) {\n profileRoleUpdate(input: $input) {\n profileUserRole {\n id\n role\n }\n errors {\n field\n messages\n }\n }\n}\n', + }, + } +})() + +;(node as any).hash = 'e4f28862dc2b186db3b3fb452cf749bb' + +export default node diff --git a/packages/components/__generated__/MemberItemFragment.graphql.ts b/packages/components/__generated__/MemberItemFragment.graphql.ts index e82bc5cc..649d5581 100644 --- a/packages/components/__generated__/MemberItemFragment.graphql.ts +++ b/packages/components/__generated__/MemberItemFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<15dc9b53215c47ddbaac982d5c81d426>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -19,6 +19,7 @@ export type MemberItemFragment$data = { readonly role: ProfileRoles | null | undefined readonly status: ProfileRoleStatus | null | undefined readonly user: { + readonly id: string readonly profile: | { readonly ' $fragmentSpreads': FragmentRefs<'ProfileItemFragment'> @@ -33,65 +34,69 @@ export type MemberItemFragment$key = { readonly ' $fragmentSpreads': FragmentRefs<'MemberItemFragment'> } -const node: ReaderFragment = { - argumentDefinitions: [], - kind: 'Fragment', - metadata: null, - name: 'MemberItemFragment', - selections: [ - { - alias: null, - args: null, - kind: 'ScalarField', - name: 'id', - storageKey: null, - }, - { - alias: null, - args: null, - concreteType: 'User', - kind: 'LinkedField', - name: 'user', - plural: false, - selections: [ - { - alias: null, - args: null, - concreteType: 'Profile', - kind: 'LinkedField', - name: 'profile', - plural: false, - selections: [ - { - args: null, - kind: 'FragmentSpread', - name: 'ProfileItemFragment', - }, - ], - storageKey: null, - }, - ], - storageKey: null, - }, - { - alias: null, - args: null, - kind: 'ScalarField', - name: 'role', - storageKey: null, - }, - { - alias: null, - args: null, - kind: 'ScalarField', - name: 'status', - storageKey: null, - }, - ], - type: 'ProfileUserRole', - abstractKey: null, -} +const node: ReaderFragment = (function () { + var v0 = { + alias: null, + args: null, + kind: 'ScalarField', + name: 'id', + storageKey: null, + } + return { + argumentDefinitions: [], + kind: 'Fragment', + metadata: null, + name: 'MemberItemFragment', + selections: [ + v0 /*: any*/, + { + alias: null, + args: null, + concreteType: 'User', + kind: 'LinkedField', + name: 'user', + plural: false, + selections: [ + { + alias: null, + args: null, + concreteType: 'Profile', + kind: 'LinkedField', + name: 'profile', + plural: false, + selections: [ + { + args: null, + kind: 'FragmentSpread', + name: 'ProfileItemFragment', + }, + ], + storageKey: null, + }, + v0 /*: any*/, + ], + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'role', + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'status', + storageKey: null, + }, + ], + type: 'ProfileUserRole', + abstractKey: null, + } +})() -;(node as any).hash = 'bd85958690e77e1ccd3a6cc89ce44335' +;(node as any).hash = '18aa448d266fed6b34c6377032e7a213' export default node diff --git a/packages/components/__generated__/UserMembersListFragment.graphql.ts b/packages/components/__generated__/UserMembersListFragment.graphql.ts index 325d383b..06c7adc2 100644 --- a/packages/components/__generated__/UserMembersListFragment.graphql.ts +++ b/packages/components/__generated__/UserMembersListFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<7b5d9ac9387460075ed09089a27cd807>> + * @generated SignedSource<<1ccb4ef3183869c662dc4a70b535479d>> * @lightSyntaxTransform * @nogrep */ @@ -12,6 +12,7 @@ import { ReaderFragment, RefetchableFragment } from 'relay-runtime' import { FragmentRefs } from 'relay-runtime' export type UserMembersListFragment$data = { + readonly canChangeRole: boolean | null | undefined readonly id: string readonly members: | { @@ -92,6 +93,19 @@ const node: ReaderFragment = (function () { }, name: 'UserMembersListFragment', selections: [ + { + alias: 'canChangeRole', + args: [ + { + kind: 'Literal', + name: 'perm', + value: 'baseapp_profiles.change_profileuserrole', + }, + ], + kind: 'ScalarField', + name: 'hasPerm', + storageKey: 'hasPerm(perm:"baseapp_profiles.change_profileuserrole")', + }, { args: null, kind: 'FragmentSpread', @@ -200,6 +214,6 @@ const node: ReaderFragment = (function () { } })() -;(node as any).hash = 'b14044279dce0f71bd144a09351f0df2' +;(node as any).hash = '8337f3f7c24d4eeb8d4c2495429a0d71' export default node diff --git a/packages/components/__generated__/UserMembersListPaginationQuery.graphql.ts b/packages/components/__generated__/UserMembersListPaginationQuery.graphql.ts index 1e98f54f..fe1b29ac 100644 --- a/packages/components/__generated__/UserMembersListPaginationQuery.graphql.ts +++ b/packages/components/__generated__/UserMembersListPaginationQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<5217aecc6cf81c52b03e9d986eff97c4>> + * @generated SignedSource<<0460ee653a305690604a858c7e8b8e59>> * @lightSyntaxTransform * @nogrep */ @@ -203,6 +203,19 @@ const node: ConcreteRequest = (function () { plural: false, selections: [ v2 /*: any*/, + { + alias: 'canChangeRole', + args: [ + { + kind: 'Literal', + name: 'perm', + value: 'baseapp_profiles.change_profileuserrole', + }, + ], + kind: 'ScalarField', + name: 'hasPerm', + storageKey: 'hasPerm(perm:"baseapp_profiles.change_profileuserrole")', + }, v4 /*: any*/, v5 /*: any*/, v6 /*: any*/, @@ -338,12 +351,12 @@ const node: ConcreteRequest = (function () { ], }, params: { - cacheID: 'e889baf91ade9a8612108dfa2583a2c8', + cacheID: 'fc604a63cecf6efc1ec2d8381adf066b', id: null, metadata: {}, name: 'UserMembersListPaginationQuery', operationKind: 'query', - text: 'query UserMembersListPaginationQuery(\n $count: Int = 10\n $cursor: String\n $orderBy: String\n $profileId: ID!\n) {\n profile(id: $profileId) {\n pk\n ...UserMembersListFragment_32czeo\n id\n }\n}\n\nfragment MemberItemFragment on ProfileUserRole {\n id\n user {\n profile {\n ...ProfileItemFragment\n id\n }\n id\n }\n role\n status\n}\n\nfragment ProfileItemFragment on Profile {\n id\n name\n image(width: 100, height: 100) {\n url\n }\n urlPath {\n path\n id\n }\n}\n\nfragment UserMembersListFragment_32czeo on Profile {\n ...ProfileItemFragment\n members(first: $count, after: $cursor, orderBy: $orderBy) {\n totalCount\n edges {\n node {\n ...MemberItemFragment\n id\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n', + text: 'query UserMembersListPaginationQuery(\n $count: Int = 10\n $cursor: String\n $orderBy: String\n $profileId: ID!\n) {\n profile(id: $profileId) {\n pk\n ...UserMembersListFragment_32czeo\n id\n }\n}\n\nfragment MemberItemFragment on ProfileUserRole {\n id\n user {\n profile {\n ...ProfileItemFragment\n id\n }\n id\n }\n role\n status\n}\n\nfragment ProfileItemFragment on Profile {\n id\n name\n image(width: 100, height: 100) {\n url\n }\n urlPath {\n path\n id\n }\n}\n\nfragment UserMembersListFragment_32czeo on Profile {\n canChangeRole: hasPerm(perm: "baseapp_profiles.change_profileuserrole")\n ...ProfileItemFragment\n members(first: $count, after: $cursor, orderBy: $orderBy) {\n totalCount\n edges {\n node {\n ...MemberItemFragment\n id\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n', }, } })() diff --git a/packages/components/__generated__/userMembersListPaginationRefetchable.graphql.ts b/packages/components/__generated__/userMembersListPaginationRefetchable.graphql.ts index fc831d2f..36424844 100644 --- a/packages/components/__generated__/userMembersListPaginationRefetchable.graphql.ts +++ b/packages/components/__generated__/userMembersListPaginationRefetchable.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<7fae0b75bc4bce4bae590e668afd8d37>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -203,6 +203,19 @@ const node: ConcreteRequest = (function () { { kind: 'InlineFragment', selections: [ + { + alias: 'canChangeRole', + args: [ + { + kind: 'Literal', + name: 'perm', + value: 'baseapp_profiles.change_profileuserrole', + }, + ], + kind: 'ScalarField', + name: 'hasPerm', + storageKey: 'hasPerm(perm:"baseapp_profiles.change_profileuserrole")', + }, v8 /*: any*/, v9 /*: any*/, v10 /*: any*/, @@ -340,16 +353,16 @@ const node: ConcreteRequest = (function () { ], }, params: { - cacheID: '3f7d21cee41fbf8a15113dc8de609c61', + cacheID: '433292e795ecdcf8b2954be5d812f489', id: null, metadata: {}, name: 'userMembersListPaginationRefetchable', operationKind: 'query', - text: 'query userMembersListPaginationRefetchable(\n $count: Int = 10\n $cursor: String\n $orderBy: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...UserMembersListFragment_32czeo\n id\n }\n}\n\nfragment MemberItemFragment on ProfileUserRole {\n id\n user {\n profile {\n ...ProfileItemFragment\n id\n }\n id\n }\n role\n status\n}\n\nfragment ProfileItemFragment on Profile {\n id\n name\n image(width: 100, height: 100) {\n url\n }\n urlPath {\n path\n id\n }\n}\n\nfragment UserMembersListFragment_32czeo on Profile {\n ...ProfileItemFragment\n members(first: $count, after: $cursor, orderBy: $orderBy) {\n totalCount\n edges {\n node {\n ...MemberItemFragment\n id\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n', + text: 'query userMembersListPaginationRefetchable(\n $count: Int = 10\n $cursor: String\n $orderBy: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...UserMembersListFragment_32czeo\n id\n }\n}\n\nfragment MemberItemFragment on ProfileUserRole {\n id\n user {\n profile {\n ...ProfileItemFragment\n id\n }\n id\n }\n role\n status\n}\n\nfragment ProfileItemFragment on Profile {\n id\n name\n image(width: 100, height: 100) {\n url\n }\n urlPath {\n path\n id\n }\n}\n\nfragment UserMembersListFragment_32czeo on Profile {\n canChangeRole: hasPerm(perm: "baseapp_profiles.change_profileuserrole")\n ...ProfileItemFragment\n members(first: $count, after: $cursor, orderBy: $orderBy) {\n totalCount\n edges {\n node {\n ...MemberItemFragment\n id\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n', }, } })() -;(node as any).hash = 'b14044279dce0f71bd144a09351f0df2' +;(node as any).hash = '8337f3f7c24d4eeb8d4c2495429a0d71' export default node diff --git a/packages/components/modules/profiles/Members/MemberItem/index.tsx b/packages/components/modules/profiles/Members/MemberItem/index.tsx index e3a07553..72e6e576 100644 --- a/packages/components/modules/profiles/Members/MemberItem/index.tsx +++ b/packages/components/modules/profiles/Members/MemberItem/index.tsx @@ -1,15 +1,17 @@ -import { FC } from 'react' +import { FC, useState } from 'react' -import { AvatarWithPlaceholder } from '@baseapp-frontend/design-system' +import { useCurrentProfile } from '@baseapp-frontend/authentication' +import { AvatarWithPlaceholder, ConfirmDialog } from '@baseapp-frontend/design-system' -import { Box, Button, Typography } from '@mui/material' +import { Box, Button, MenuItem, SelectChangeEvent, Typography } from '@mui/material' import { useFragment } from 'react-relay' import { ProfileItemFragment$key } from '../../../../__generated__/ProfileItemFragment.graphql' +import { useChangeUserRoleMutation } from '../../graphql/mutations/ChangeUserRole' import { ProfileItemFragment } from '../../graphql/queries/ProfileItem' -import { MemberStatuses } from '../constants' +import { MemberRoles, MemberStatuses, roleOptions } from '../constants' import { capitalizeFirstLetter } from '../utils' -import { MemberItemContainer, MemberPersonalInformation } from './styled' +import { MemberItemContainer, MemberPersonalInformation, Select } from './styled' import { MemberItemProps } from './types' const MemberItem: FC = ({ @@ -19,12 +21,110 @@ const MemberItem: FC = ({ avatarProps = {}, avatarWidth = 40, avatarHeight = 40, + canChangeMember = false, + userId, }) => { const memberProfile = useFragment(ProfileItemFragment, member) - const hasStatusAndRole = status && memberRole + + const { currentProfile } = useCurrentProfile() + + const [changeUserRole, isChangingUserRole] = useChangeUserRoleMutation() + const [openConfirmChangeMember, setOpenConfirmChangeMember] = useState(false) + if (!memberProfile) return null + + const shouldRenderChangeRoleSelect = + status === MemberStatuses.active && memberRole !== 'owner' && canChangeMember + + const haveMemberRoleAndStatus = memberRole && status + + const changeRole = (roleType: MemberRoles) => { + if (currentProfile?.id && userId) { + changeUserRole({ + variables: { + input: { profileId: currentProfile.id, userId, roleType }, + }, + }) + } + } + + const handleRoleChange = (event: SelectChangeEvent<{ value: MemberRoles }>) => { + if (event.target.value === MemberRoles.admin) { + setOpenConfirmChangeMember(true) + return + } + if (currentProfile?.id && userId) { + changeRole(event?.target?.value as MemberRoles) + } + } + + const cancelChangeRole = () => { + setOpenConfirmChangeMember(false) + } + + const confirmChangeRole = () => { + if (currentProfile?.id && userId) { + changeRole(MemberRoles.admin) + } + setOpenConfirmChangeMember(false) + } + + const renderRoleButton = () => { + if (shouldRenderChangeRoleSelect) { + return ( + + + + ) + } + if (haveMemberRoleAndStatus) { + return ( + + + + ) + } + return null + } + return ( + + Confirm + + } + onClose={cancelChangeRole} + content={ + + Are you sure you want to promote this member to an admin? They will have full + administrative rights, including the ability to manage members and settings. + + } + cancelText="Back" + /> = ({ {memberProfile?.urlPath?.path} - - {hasStatusAndRole && ( - - - - )} + {renderRoleButton()} ) } diff --git a/packages/components/modules/profiles/Members/MemberItem/styled.tsx b/packages/components/modules/profiles/Members/MemberItem/styled.tsx index 17f424c0..15ef3e6d 100644 --- a/packages/components/modules/profiles/Members/MemberItem/styled.tsx +++ b/packages/components/modules/profiles/Members/MemberItem/styled.tsx @@ -1,4 +1,4 @@ -import { Box, styled } from '@mui/material' +import { Box, Select as MUISelect, alpha, styled } from '@mui/material' import { MemberPersonalInformationProps } from './types' @@ -19,3 +19,24 @@ export const MemberPersonalInformation = styled(Box, { alignItems: 'center', justifyContent: 'space-between', })) + +export const Select = styled(MUISelect)(({ theme }) => ({ + backgroundColor: alpha(theme.palette.grey[500], 0.08), + borderRadius: 8, + padding: 0, + '.MuiSelect-select': { + ...theme.typography.button, + background: 'transparent', + padding: theme.spacing(0.75, 1.5), + }, + + '&:hover': { + backgroundColor: alpha(theme.palette.grey[500], 0.24), + }, + '&.Mui-disabled': { + opacity: 0.5, + }, + '& .MuiSelect-icon': { + color: theme.palette.text.primary, + }, +})) diff --git a/packages/components/modules/profiles/Members/MemberItem/types.ts b/packages/components/modules/profiles/Members/MemberItem/types.ts index 072a903e..aa51ad6d 100644 --- a/packages/components/modules/profiles/Members/MemberItem/types.ts +++ b/packages/components/modules/profiles/Members/MemberItem/types.ts @@ -14,4 +14,6 @@ export interface MemberItemProps { avatarProps?: AvatarProps avatarWidth?: number avatarHeight?: number + canChangeMember?: boolean + userId?: string } diff --git a/packages/components/modules/profiles/Members/MemberListItem/index.tsx b/packages/components/modules/profiles/Members/MemberListItem/index.tsx index 4c9373e7..a34d785a 100644 --- a/packages/components/modules/profiles/Members/MemberListItem/index.tsx +++ b/packages/components/modules/profiles/Members/MemberListItem/index.tsx @@ -43,6 +43,8 @@ const MemberListItem: FC = ({ member={memberFragment?.user?.profile} memberRole={memberFragment?.role} status={memberFragment?.status} + userId={memberFragment?.user?.id} + canChangeMember={data?.canChangeRole || false} {...memberItemComponentProps} /> @@ -56,6 +58,8 @@ const MemberListItem: FC = ({ member={memberFragment?.user?.profile} memberRole={memberFragment?.role} status={memberFragment?.status} + userId={memberFragment?.user?.id} + canChangeMember={data?.canChangeRole || false} {...memberItemComponentProps} /> @@ -74,6 +78,8 @@ const MemberListItem: FC = ({ member={memberFragment?.user?.profile} memberRole={memberFragment?.role} status={memberFragment?.status} + userId={memberFragment?.user?.id} + canChangeMember={data?.canChangeRole || false} {...memberItemComponentProps} /> ) diff --git a/packages/components/modules/profiles/Members/constants.ts b/packages/components/modules/profiles/Members/constants.ts index 20dc5b35..fdee91d0 100644 --- a/packages/components/modules/profiles/Members/constants.ts +++ b/packages/components/modules/profiles/Members/constants.ts @@ -1,3 +1,5 @@ +import { capitalizeFirstLetter } from './utils' + export const NUMBER_OF_MEMBERS_TO_LOAD_NEXT = 5 export const NUMBER_OF_MEMBERS_ON_FIRST_LOAD = 10 export enum MemberStatuses { @@ -5,3 +7,19 @@ export enum MemberStatuses { pending = 'PENDING', inactive = 'INACTIVE', } + +export enum MemberRoles { + admin = 'ADMIN', + manager = 'MANAGER', +} + +export const roleOptions = [ + { + value: MemberRoles.admin, + label: capitalizeFirstLetter(MemberRoles.admin.toLowerCase()), + }, + { + value: MemberRoles.manager, + label: capitalizeFirstLetter(MemberRoles.manager.toLowerCase()), + }, +] diff --git a/packages/components/modules/profiles/Members/styled.tsx b/packages/components/modules/profiles/Members/styled.tsx new file mode 100644 index 00000000..eddb6b63 --- /dev/null +++ b/packages/components/modules/profiles/Members/styled.tsx @@ -0,0 +1,7 @@ +import { Skeleton, styled } from '@mui/material' + +export const MemberItemSkeleton = styled(Skeleton)(({ theme }) => ({ + width: '100%', + height: 52, + borderRadius: theme.spacing(0.75), +})) diff --git a/packages/components/modules/profiles/graphql/mutations/ChangeUserRole.ts b/packages/components/modules/profiles/graphql/mutations/ChangeUserRole.ts new file mode 100644 index 00000000..decd8915 --- /dev/null +++ b/packages/components/modules/profiles/graphql/mutations/ChangeUserRole.ts @@ -0,0 +1,48 @@ +import { useNotification } from '@baseapp-frontend/utils' + +import { Disposable, UseMutationConfig, graphql, useMutation } from 'react-relay' + +import { ChangeUserRoleMutation } from '../../../../__generated__/ChangeUserRoleMutation.graphql' + +export const ChangeUserRoleMutationQuery = graphql` + mutation ChangeUserRoleMutation($input: RoleUpdateInput!) { + profileRoleUpdate(input: $input) { + profileUserRole { + id + role + } + errors { + field + messages + } + } + } +` + +export const useChangeUserRoleMutation = (): [ + (config: UseMutationConfig) => Disposable, + boolean, +] => { + const { sendToast } = useNotification() + const [commitMutation, isMutationInFlight] = useMutation( + ChangeUserRoleMutationQuery, + ) + + const commit = (config: UseMutationConfig) => + commitMutation({ + ...config, + onCompleted: (response, errors) => { + errors?.forEach((error) => { + sendToast(error.message, { type: 'error' }) + }) + + config?.onCompleted?.(response, errors) + }, + onError: (error) => { + sendToast(error.message, { type: 'error' }) + config?.onError?.(error) + }, + }) + + return [commit, isMutationInFlight] +} diff --git a/packages/components/modules/profiles/graphql/queries/MemberItem.ts b/packages/components/modules/profiles/graphql/queries/MemberItem.ts index 9851ef27..bebd2a20 100644 --- a/packages/components/modules/profiles/graphql/queries/MemberItem.ts +++ b/packages/components/modules/profiles/graphql/queries/MemberItem.ts @@ -7,6 +7,7 @@ export const MemberItemFragment = graphql` profile { ...ProfileItemFragment } + id } role status diff --git a/packages/components/modules/profiles/graphql/queries/UserMembersList.ts b/packages/components/modules/profiles/graphql/queries/UserMembersList.ts index e50bbda4..146c2319 100644 --- a/packages/components/modules/profiles/graphql/queries/UserMembersList.ts +++ b/packages/components/modules/profiles/graphql/queries/UserMembersList.ts @@ -22,6 +22,7 @@ export const UserMembersListFragment = graphql` cursor: { type: "String" } orderBy: { type: "String" } ) { + canChangeRole: hasPerm(perm: "baseapp_profiles.change_profileuserrole") ...ProfileItemFragment members(first: $count, after: $cursor, orderBy: $orderBy) @connection(key: "UserMembersFragment_members", filters: ["orderBy"]) { diff --git a/packages/components/package.json b/packages/components/package.json index 9be357ff..793afd00 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,7 +1,7 @@ { "name": "@baseapp-frontend/components", "description": "BaseApp components modules such as comments, notifications, messages, and more.", - "version": "0.0.33", + "version": "0.0.34", "main": "./index.ts", "types": "dist/index.d.ts", "sideEffects": false, diff --git a/packages/components/schema.graphql b/packages/components/schema.graphql index a0fe1b70..3b5e3de4 100644 --- a/packages/components/schema.graphql +++ b/packages/components/schema.graphql @@ -624,6 +624,7 @@ type Metadata { } type Mutation { + rateCreate(input: RateCreateInput!): RateCreatePayload organizationCreate(input: OrganizationCreateInput!): OrganizationCreatePayload chatRoomCreate(input: ChatRoomCreateInput!): ChatRoomCreatePayload chatRoomSendMessage(input: ChatRoomSendMessageInput!): ChatRoomSendMessagePayload @@ -645,6 +646,7 @@ type Mutation { profileCreate(input: ProfileCreateInput!): ProfileCreatePayload profileUpdate(input: ProfileUpdateInput!): ProfileUpdatePayload profileDelete(input: ProfileDeleteInput!): ProfileDeletePayload + profileRoleUpdate(input: RoleUpdateInput!): RoleUpdatePayload } """An object with an ID""" @@ -1367,6 +1369,22 @@ type RateConnection { edgeCount: Int } +input RateCreateInput { + targetObjectId: ID! + profileId: ID + value: Int! + clientMutationId: String +} + +type RateCreatePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + rate: RateEdge + target: RatingsInterface + clientMutationId: String +} + """A Relay edge containing a `Rate` and its cursor.""" type RateEdge { """The item at the end of the edge""" @@ -1536,6 +1554,21 @@ enum ReportTypes { OTHER } +input RoleUpdateInput { + profileId: ID! + userId: ID! + roleType: ProfileRoles = null + clientMutationId: String +} + +type RoleUpdatePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + profileUserRole: ProfileUserRole + clientMutationId: String +} + type Subscription { chatRoomOnNewMessage(roomId: ID!): ChatRoomOnNewMessage chatRoomOnRoomUpdate(profileId: ID!): ChatRoomOnRoomUpdate diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a58ac3a9..6158b6d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9308,9 +9308,11 @@ packages: sudo-prompt@8.2.5: resolution: {integrity: sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. sudo-prompt@9.1.1: resolution: {integrity: sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} @@ -10208,7 +10210,7 @@ snapshots: '@babel/generator@7.17.7': dependencies: - '@babel/types': 7.17.0 + '@babel/types': 7.26.3 jsesc: 2.5.2 source-map: 0.5.7