From 7cac335c99c38e489a84ab099d362363f6339805 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Mon, 3 Aug 2026 21:50:36 +0530 Subject: [PATCH 01/14] implementing member profile page --- .../graphql/src/schema/types/member.graphql | 23 +++++++ .../src/schema/types/member.resolvers.ts | 62 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/packages/ocom/graphql/src/schema/types/member.graphql b/packages/ocom/graphql/src/schema/types/member.graphql index 967234086..e1a289b38 100644 --- a/packages/ocom/graphql/src/schema/types/member.graphql +++ b/packages/ocom/graphql/src/schema/types/member.graphql @@ -39,11 +39,18 @@ type MemberProfile { showProperties: Boolean } +type MemberProfileVisibility { + showEmail: Boolean! + showBio: Boolean! + showInterests: Boolean! +} + extend type Query { member(id: ObjectID!): Member! membersByCommunityId(communityId: ObjectID!): [Member!]! memberForCurrentCommunity(communityId: ObjectID!): Member membersForCurrentEndUser: [Member!]! + memberMyProfile(communityId: ID!): Member } extend type Mutation { @@ -69,6 +76,8 @@ extend type Mutation { # Role Management memberRoleUpdate(input: UpdateMemberRoleInput!): MemberMutationResult! + + memberUpdateMyProfile(communityId: ID!, input: UpdateMyMemberProfileInput!): MemberMutationResult! } type MemberMutationResult implements MutationResult { @@ -207,3 +216,17 @@ input MemberProfileInput { showLocation: Boolean showProperties: Boolean } + +input UpdateMemberProfileVisibilityInput { + showEmail: Boolean! + showBio: Boolean! + showInterests: Boolean! +} + +input UpdateMyMemberProfileInput { + name: String! + email: String! + bio: String + interests: [String!]! + visibility: UpdateMemberProfileVisibilityInput! +} diff --git a/packages/ocom/graphql/src/schema/types/member.resolvers.ts b/packages/ocom/graphql/src/schema/types/member.resolvers.ts index 2fcb81bd5..3abb10803 100644 --- a/packages/ocom/graphql/src/schema/types/member.resolvers.ts +++ b/packages/ocom/graphql/src/schema/types/member.resolvers.ts @@ -24,6 +24,7 @@ import type { MutationMemberRemoveAccountArgs, MutationMemberRoleUpdateArgs, MutationMemberUpdateAccountArgs, + MutationMemberUpdateMyProfileArgs, MutationMemberUpdateProfileArgs, MutationRemoveMemberArgs, Resolvers, @@ -155,6 +156,18 @@ const member: Resolvers = { const members = await context.applicationServices.Community.Member.queryByEndUserExternalId({ externalId }); return members.find((m) => String(m.communityId) === String(args.communityId)) ?? null; }, + memberMyProfile: async (_parent: unknown, args: { communityId: string }, context: GraphContext) => { + if (!context.applicationServices.verifiedUser?.verifiedJwt) { + throw new Error('Unauthorized'); + } + const actorMemberId = await getActorMemberIdForCommunity(context, String(args.communityId)); + if (!actorMemberId) { + return null; + } + return await context.applicationServices.Community.Member.queryById({ + id: actorMemberId, + }); + }, }, Mutation: { memberCreate: async (_parent, args: MutationMemberCreateArgs, context: GraphContext, _info: GraphQLResolveInfo) => { @@ -759,6 +772,55 @@ const member: Resolvers = { }; } }, + + // ...existing code... + memberUpdateMyProfile: async (_parent: unknown, args: MutationMemberUpdateMyProfileArgs, context: GraphContext) => { + try { + if (!context.applicationServices.verifiedUser?.verifiedJwt) { + return { + status: { success: false, errorMessage: 'Unauthorized' }, + member: null, + }; + } + + const actorMemberId = await getActorMemberIdForCommunity(context, String(args.communityId)); + if (!actorMemberId) { + return { + status: { success: false, errorMessage: 'Forbidden' }, + member: null, + }; + } + + const command: MemberUpdateProfileCommand = { + memberId: actorMemberId, + profile: { + name: args.input.name, + email: args.input.email, + ...(args.input.bio !== undefined ? { bio: args.input.bio } : {}), + ...(args.input.interests != null ? { interests: [...args.input.interests] } : {}), + ...(args.input.visibility?.showInterests !== undefined ? { showInterests: args.input.visibility.showInterests } : {}), + ...(args.input.visibility?.showEmail !== undefined ? { showEmail: args.input.visibility.showEmail } : {}), + // Domain model has no `showBio`; not mapped. + }, + }; + + const result = await context.applicationServices.Community.Member.updateMemberProfile(command); + + return { + status: { success: true }, + member: result, + }; + } catch (error: unknown) { + return { + status: { + success: false, + errorMessage: error instanceof Error ? error.message : 'Failed to update profile', + }, + member: null, + }; + } + }, + // ...existing code... }, }; From fe59cbea1c953f087465380941c09bd073810025 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Tue, 4 Aug 2026 00:28:36 +0530 Subject: [PATCH 02/14] ui changes --- apps/ui-community/src/App.tsx | 6 +- .../graphql/src/schema/types/member.graphql | 3 + .../ui-community-route-accounts/src/index.tsx | 1 + .../src/pages/member-profile.tsx | 6 + .../member-profile.container.graphql | 18 +++ .../member-profile.container.test.ts | 78 ++++++++++ .../components/member-profile.container.tsx | 140 +++++++++++++++--- 7 files changed, 232 insertions(+), 20 deletions(-) create mode 100644 packages/ocom/ui-community-route-accounts/src/pages/member-profile.tsx create mode 100644 packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts diff --git a/apps/ui-community/src/App.tsx b/apps/ui-community/src/App.tsx index 83cd0fd0f..c93bb5e55 100644 --- a/apps/ui-community/src/App.tsx +++ b/apps/ui-community/src/App.tsx @@ -1,5 +1,5 @@ import { RequireAuth } from '@cellix/ui-core'; -import { Accounts } from '@ocom/ui-community-route-accounts'; +import { Accounts, MemberProfilePage } from '@ocom/ui-community-route-accounts'; import { Admin } from '@ocom/ui-community-route-admin'; import { Root } from '@ocom/ui-community-route-root'; import { Route, Routes } from 'react-router-dom'; @@ -27,6 +27,10 @@ export default function App() { path="/accounts/*" element={} /> + } + /> } diff --git a/packages/ocom/graphql/src/schema/types/member.graphql b/packages/ocom/graphql/src/schema/types/member.graphql index e1a289b38..bd81fb272 100644 --- a/packages/ocom/graphql/src/schema/types/member.graphql +++ b/packages/ocom/graphql/src/schema/types/member.graphql @@ -221,6 +221,9 @@ input UpdateMemberProfileVisibilityInput { showEmail: Boolean! showBio: Boolean! showInterests: Boolean! + showProfile: Boolean + showLocation: Boolean + showProperties: Boolean } input UpdateMyMemberProfileInput { diff --git a/packages/ocom/ui-community-route-accounts/src/index.tsx b/packages/ocom/ui-community-route-accounts/src/index.tsx index 1236c09d3..688b64c64 100644 --- a/packages/ocom/ui-community-route-accounts/src/index.tsx +++ b/packages/ocom/ui-community-route-accounts/src/index.tsx @@ -1 +1,2 @@ export { Accounts } from './accounts.tsx'; +export { MemberProfilePage } from './pages/member-profile.tsx'; diff --git a/packages/ocom/ui-community-route-accounts/src/pages/member-profile.tsx b/packages/ocom/ui-community-route-accounts/src/pages/member-profile.tsx new file mode 100644 index 000000000..95182ace6 --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/pages/member-profile.tsx @@ -0,0 +1,6 @@ +import { MemberProfileContainer } from '@ocom/ui-community-shared'; +import type React from 'react'; + +export const MemberProfilePage: React.FC = () => { + return ; +}; diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.graphql b/packages/ocom/ui-community-shared/src/components/member-profile.container.graphql index 525a71e72..1f9ad69a2 100644 --- a/packages/ocom/ui-community-shared/src/components/member-profile.container.graphql +++ b/packages/ocom/ui-community-shared/src/components/member-profile.container.graphql @@ -4,6 +4,12 @@ query SharedMemberProfileContainerMember($id: ObjectID!) { } } +query SharedMemberProfileContainerMemberSelfProfile($communityId: ID!) { + memberMyProfile(communityId: $communityId) { + ...SharedMemberProfileContainerMemberFields + } +} + mutation SharedMemberProfileContainerMemberUpdateProfile($input: MemberUpdateProfileInput!) { memberUpdateProfile(input: $input) { status { @@ -16,6 +22,18 @@ mutation SharedMemberProfileContainerMemberUpdateProfile($input: MemberUpdatePro } } +mutation SharedMemberProfileContainerMemberUpdateMyProfile($communityId: ID!, $input: UpdateMyMemberProfileInput!) { + memberUpdateMyProfile(communityId: $communityId, input: $input) { + status { + success + errorMessage + } + member { + ...SharedMemberProfileContainerMemberFields + } + } +} + fragment SharedMemberProfileContainerMemberFields on Member { id memberName diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts new file mode 100644 index 000000000..a92d8224f --- /dev/null +++ b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest'; +import { buildMemberProfileSaveVariables } from './member-profile.container.tsx'; + +describe('buildMemberProfileSaveVariables', () => { + it('builds a self-profile mutation payload for community members', () => { + const result = buildMemberProfileSaveVariables({ + mode: 'self', + communityId: 'community-1', + memberObjectId: undefined, + values: { + name: 'Jane Doe', + email: 'jane@example.com', + bio: 'Hello there', + showInterests: true, + showEmail: true, + showProfile: false, + showLocation: true, + showProperties: false, + }, + }); + + expect(result).toEqual({ + variables: { + communityId: 'community-1', + input: { + name: 'Jane Doe', + email: 'jane@example.com', + bio: 'Hello there', + interests: [], + visibility: { + showEmail: true, + showBio: false, + showInterests: true, + showProfile: false, + showLocation: true, + showProperties: false, + }, + }, + }, + }); + }); + + it('builds an admin mutation payload for a specific member', () => { + const result = buildMemberProfileSaveVariables({ + mode: 'admin', + communityId: 'community-1', + memberObjectId: 'member-1', + values: { + name: 'Jane Doe', + email: 'jane@example.com', + bio: 'Hello there', + showInterests: true, + showEmail: true, + showProfile: false, + showLocation: true, + showProperties: false, + }, + }); + + expect(result).toEqual({ + variables: { + input: { + memberId: 'member-1', + profile: { + name: 'Jane Doe', + email: 'jane@example.com', + bio: 'Hello there', + showInterests: true, + showEmail: true, + showProfile: false, + showLocation: true, + showProperties: false, + }, + }, + }, + }); + }); +}); diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.tsx b/packages/ocom/ui-community-shared/src/components/member-profile.container.tsx index 4464b0a78..b1feea18e 100644 --- a/packages/ocom/ui-community-shared/src/components/member-profile.container.tsx +++ b/packages/ocom/ui-community-shared/src/components/member-profile.container.tsx @@ -5,21 +5,72 @@ import type React from 'react'; import { useParams } from 'react-router-dom'; import { SharedMemberProfileContainerMemberDocument, - type SharedMemberProfileContainerMemberFieldsFragment, - type SharedMemberProfileContainerMemberQuery, - type SharedMemberProfileContainerMemberQueryVariables, + SharedMemberProfileContainerMemberSelfProfileDocument, + SharedMemberProfileContainerMemberUpdateMyProfileDocument, SharedMemberProfileContainerMemberUpdateProfileDocument, + type SharedMemberProfileContainerMemberFieldsFragment, } from '../generated.tsx'; import { MemberProfile, type MemberProfileFormValues } from './member-profile.tsx'; export interface MemberProfileContainerProps { isAdmin?: boolean; + mode?: 'admin' | 'self'; } +interface BuildMemberProfileSaveVariablesArgs { + mode: 'admin' | 'self'; + communityId?: string; + memberObjectId?: string; + values: MemberProfileFormValues; +} + +export const buildMemberProfileSaveVariables = ({ mode, communityId, memberObjectId, values }: BuildMemberProfileSaveVariablesArgs) => { + if (mode === 'self') { + return { + variables: { + communityId: communityId ?? '', + input: { + name: values.name, + email: values.email, + bio: values.bio, + interests: [], + visibility: { + showEmail: values.showEmail, + showBio: false, + showInterests: values.showInterests, + showProfile: values.showProfile, + showLocation: values.showLocation, + showProperties: values.showProperties, + }, + }, + }, + }; + } + + return { + variables: { + input: { + memberId: memberObjectId, + profile: { + name: values.name, + email: values.email, + bio: values.bio, + showInterests: values.showInterests, + showEmail: values.showEmail, + showProfile: values.showProfile, + showLocation: values.showLocation, + showProperties: values.showProperties, + }, + }, + }, + }; +}; + export const MemberProfileContainer: React.FC = (props) => { const { message } = App.useApp(); - const { id, memberId } = useParams<{ id?: string; memberId?: string }>(); + const { id, memberId, communityId } = useParams<{ id?: string; memberId?: string; communityId?: string }>(); const memberObjectId = id ?? memberId; + const isSelfMode = props.mode === 'self' || (!memberObjectId && Boolean(communityId)); const [memberUpdateProfile, { loading: profileUpdateLoading, error: profileUpdateError }] = useMutation(SharedMemberProfileContainerMemberUpdateProfileDocument, { update(cache, { data }) { @@ -28,7 +79,7 @@ export const MemberProfileContainer: React.FC = (pr return; } - cache.writeQuery({ + cache.writeQuery({ query: SharedMemberProfileContainerMemberDocument, variables: { id: memberObjectId }, data: { @@ -38,32 +89,83 @@ export const MemberProfileContainer: React.FC = (pr }, }); + const [memberUpdateMyProfile, { loading: selfProfileUpdateLoading, error: selfProfileUpdateError }] = useMutation(SharedMemberProfileContainerMemberUpdateMyProfileDocument, { + update(cache, { data }) { + const updatedMember = data?.memberUpdateMyProfile.member; + if (!updatedMember || !communityId) { + return; + } + + cache.writeQuery({ + query: SharedMemberProfileContainerMemberSelfProfileDocument, + variables: { communityId }, + data: { + memberMyProfile: updatedMember, + }, + }); + }, + }); + const { data: memberData, loading: memberLoading, error: memberError, - } = useQuery(SharedMemberProfileContainerMemberDocument, { + } = useQuery(SharedMemberProfileContainerMemberDocument, { variables: { id: memberObjectId ?? '', }, - skip: !memberObjectId, + skip: isSelfMode || !memberObjectId, + }); + + const { + data: memberSelfData, + loading: memberSelfLoading, + error: memberSelfError, + } = useQuery(SharedMemberProfileContainerMemberSelfProfileDocument, { + variables: { + communityId: communityId ?? '', + }, + skip: !isSelfMode || !communityId, }); const handleSave = async (values: MemberProfileFormValues): Promise => { + if (isSelfMode) { + if (!communityId) { + message.error('Community not found'); + return false; + } + + try { + const variables = buildMemberProfileSaveVariables({ + mode: 'self', + communityId, + values, + }); + const result = await memberUpdateMyProfile({ variables: variables.variables }); + if (result.data?.memberUpdateMyProfile.status.success) { + message.success('Profile updated'); + return true; + } + message.error(result.data?.memberUpdateMyProfile.status.errorMessage ?? 'Failed to update profile'); + return false; + } catch (saveError) { + message.error(`Error updating profile: ${saveError instanceof Error ? saveError.message : JSON.stringify(saveError)}`); + return false; + } + } + if (!memberObjectId) { message.error('Member not found'); return false; } try { - const result = await memberUpdateProfile({ - variables: { - input: { - memberId: memberObjectId, - profile: values, - }, - }, + const variables = buildMemberProfileSaveVariables({ + mode: 'admin', + memberObjectId, + values, }); + const result = await memberUpdateProfile({ variables: variables.variables }); if (result.data?.memberUpdateProfile.status.success) { message.success('Profile updated'); @@ -79,18 +181,18 @@ export const MemberProfileContainer: React.FC = (pr }; const memberProfileProps = { - data: memberData?.member as SharedMemberProfileContainerMemberFieldsFragment, + data: (isSelfMode ? memberSelfData?.memberMyProfile : memberData?.member) as SharedMemberProfileContainerMemberFieldsFragment, isAdmin: props.isAdmin ?? false, - loading: profileUpdateLoading, + loading: isSelfMode ? selfProfileUpdateLoading : profileUpdateLoading, onSave: handleSave, }; return ( } - error={memberError ?? profileUpdateError} + error={isSelfMode ? memberSelfError ?? selfProfileUpdateError : memberError ?? profileUpdateError} /> ); }; From 55ba4b64301e727c5b29428ff4ed3f23c10ab524 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Wed, 5 Aug 2026 01:10:18 +0530 Subject: [PATCH 03/14] ui changes for member portal --- apps/ui-community/src/App.tsx | 6 +- .../member-management.operations.test.ts | 27 ++++ .../community/member/member-management.ts | 5 + .../src/schema/types/member.resolvers.ts | 1 + .../src/accounts.test.tsx | 71 +++++++++++ .../ui-community-route-accounts/src/index.tsx | 2 +- .../src/member-home.graphql | 14 +++ .../src/member-section-layout.container.tsx | 31 +++++ .../src/member-section-layout.css | 19 +++ .../src/member-section-layout.graphql | 15 +++ .../src/member-section-layout.tsx | 118 ++++++++++++++++++ .../src/member.tsx | 43 +++++++ .../src/pages/member-home.tsx | 95 ++++++++++++++ .../components/member-profile.container.tsx | 39 ++++-- 14 files changed, 473 insertions(+), 13 deletions(-) create mode 100644 packages/ocom/ui-community-route-accounts/src/accounts.test.tsx create mode 100644 packages/ocom/ui-community-route-accounts/src/member-home.graphql create mode 100644 packages/ocom/ui-community-route-accounts/src/member-section-layout.container.tsx create mode 100644 packages/ocom/ui-community-route-accounts/src/member-section-layout.css create mode 100644 packages/ocom/ui-community-route-accounts/src/member-section-layout.graphql create mode 100644 packages/ocom/ui-community-route-accounts/src/member-section-layout.tsx create mode 100644 packages/ocom/ui-community-route-accounts/src/member.tsx create mode 100644 packages/ocom/ui-community-route-accounts/src/pages/member-home.tsx diff --git a/apps/ui-community/src/App.tsx b/apps/ui-community/src/App.tsx index c93bb5e55..27b80d1c8 100644 --- a/apps/ui-community/src/App.tsx +++ b/apps/ui-community/src/App.tsx @@ -1,5 +1,5 @@ import { RequireAuth } from '@cellix/ui-core'; -import { Accounts, MemberProfilePage } from '@ocom/ui-community-route-accounts'; +import { Accounts, Member } from '@ocom/ui-community-route-accounts'; import { Admin } from '@ocom/ui-community-route-admin'; import { Root } from '@ocom/ui-community-route-root'; import { Route, Routes } from 'react-router-dom'; @@ -28,8 +28,8 @@ export default function App() { element={} /> } + path="/:communityId/member/:memberId/*" + element={} /> { expect(member.profile.showProfile).toBe(true); }); + it('throws when an actor tries to update a different member profile', async () => { + const member = { + memberName: 'Old Name', + profile: { + name: '', + email: '', + bio: '', + showProfile: false, + showEmail: false, + showInterests: false, + showLocation: false, + showProperties: false, + }, + }; + memberRepository.getById.mockResolvedValue(member); + + await expect( + updateMemberProfile(dataSources)({ + memberId: 'member-1', + actorMemberId: 'member-2', + profile: { + name: 'Jane Doe', + }, + }), + ).rejects.toThrow('You do not have permission to update this profile'); + }); + it('throws when update member role save returns nothing', async () => { const role = { id: 'role-1', community: { id: 'community-1' } }; const member = { communityId: 'community-1', role: null }; diff --git a/packages/ocom/application-services/src/contexts/community/member/member-management.ts b/packages/ocom/application-services/src/contexts/community/member/member-management.ts index ee4e7b8be..74c8cfe3d 100644 --- a/packages/ocom/application-services/src/contexts/community/member/member-management.ts +++ b/packages/ocom/application-services/src/contexts/community/member/member-management.ts @@ -469,6 +469,7 @@ export interface MemberRemoveAccountCommand { export interface MemberUpdateProfileCommand { memberId: string; + actorMemberId?: string; profile: { name?: string | null; email?: string | null; @@ -518,6 +519,10 @@ export const updateMemberProfile = (dataSources: DataSources) => { const member = await repository.getById(command.memberId); const profile = member.profile; + if (command.actorMemberId && String(command.actorMemberId) !== String(command.memberId)) { + throw new Error('You do not have permission to update this profile'); + } + if (command.profile.name !== undefined && command.profile.name !== null) { profile.name = command.profile.name; member.memberName = command.profile.name; diff --git a/packages/ocom/graphql/src/schema/types/member.resolvers.ts b/packages/ocom/graphql/src/schema/types/member.resolvers.ts index 3abb10803..f1100a78c 100644 --- a/packages/ocom/graphql/src/schema/types/member.resolvers.ts +++ b/packages/ocom/graphql/src/schema/types/member.resolvers.ts @@ -793,6 +793,7 @@ const member: Resolvers = { const command: MemberUpdateProfileCommand = { memberId: actorMemberId, + actorMemberId, profile: { name: args.input.name, email: args.input.email, diff --git a/packages/ocom/ui-community-route-accounts/src/accounts.test.tsx b/packages/ocom/ui-community-route-accounts/src/accounts.test.tsx new file mode 100644 index 000000000..3152144ae --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/accounts.test.tsx @@ -0,0 +1,71 @@ +// @vitest-environment jsdom +import type React from 'react'; +import { act } from 'react'; +import { createRoot } from 'react-dom/client'; +import { MemoryRouter } from 'react-router-dom'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Accounts } from './accounts.tsx'; + +vi.mock('./pages/home.tsx', () => ({ + Home: () =>
home
, +})); + +vi.mock('./pages/create-community.tsx', () => ({ + CreateCommunity: () =>
create community
, +})); + +vi.mock('./section-layout.tsx', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + SectionLayout: () => ( +
+ +
+ ), + }; +}); + +describe('Accounts', () => { + let container!: HTMLDivElement; + let root!: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + if (root) { + act(() => { + root.unmount(); + }); + } + container?.remove(); + }); + + it('renders the home page at the root route', () => { + act(() => { + root.render( + + + , + ); + }); + + expect(container.querySelector('[data-testid="home-page"]')).not.toBeNull(); + }); + + it('renders the create community page', () => { + act(() => { + root.render( + + + , + ); + }); + + expect(container.querySelector('[data-testid="create-community-page"]')).not.toBeNull(); + }); +}); diff --git a/packages/ocom/ui-community-route-accounts/src/index.tsx b/packages/ocom/ui-community-route-accounts/src/index.tsx index 688b64c64..d4dfdb1d0 100644 --- a/packages/ocom/ui-community-route-accounts/src/index.tsx +++ b/packages/ocom/ui-community-route-accounts/src/index.tsx @@ -1,2 +1,2 @@ export { Accounts } from './accounts.tsx'; -export { MemberProfilePage } from './pages/member-profile.tsx'; +export { Member } from './member.tsx'; diff --git a/packages/ocom/ui-community-route-accounts/src/member-home.graphql b/packages/ocom/ui-community-route-accounts/src/member-home.graphql new file mode 100644 index 000000000..d9357f34f --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/member-home.graphql @@ -0,0 +1,14 @@ +query MemberHomeContainerMemberMyProfile($communityId: ID!) { + memberMyProfile(communityId: $communityId) { + ...MemberHomeContainerMemberFields + } +} + +fragment MemberHomeContainerMemberFields on Member { + id + memberName + community { + id + name + } +} diff --git a/packages/ocom/ui-community-route-accounts/src/member-section-layout.container.tsx b/packages/ocom/ui-community-route-accounts/src/member-section-layout.container.tsx new file mode 100644 index 000000000..d01bba965 --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/member-section-layout.container.tsx @@ -0,0 +1,31 @@ +import { useQuery } from '@apollo/client'; +import { ComponentQueryLoader } from '@cellix/ui-core'; +import type { PageLayoutProps } from '@ocom/ui-shared'; +import { useParams } from 'react-router-dom'; +import { type MemberSectionLayoutContainerMemberFieldsFragment, MemberSectionLayoutContainerMembersForCurrentEndUserDocument } from './generated.tsx'; +import { MemberSectionLayout } from './member-section-layout.tsx'; + +interface MemberSectionLayoutContainerProps { + pageLayouts: PageLayoutProps[]; +} + +export const MemberSectionLayoutContainer: React.FC = (props) => { + const params = useParams(); + + const { data: membersData, loading: membersLoading, error: membersError } = useQuery(MemberSectionLayoutContainerMembersForCurrentEndUserDocument); + + return ( + member.id === params['memberId']) as MemberSectionLayoutContainerMemberFieldsFragment} + /> + } + error={membersError} + /> + ); +}; diff --git a/packages/ocom/ui-community-route-accounts/src/member-section-layout.css b/packages/ocom/ui-community-route-accounts/src/member-section-layout.css new file mode 100644 index 000000000..e1a89f088 --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/member-section-layout.css @@ -0,0 +1,19 @@ +#components-layout-demo-fixed-sider .logo { + height: 32px; + margin: 16px; + background: rgba(255, 255, 255, 0.2); +} + +.site-layout .site-layout-background { + background: #fff; +} + +.ant-dropdown .ant-menu-root.ant-menu-vertical { + box-shadow: 0 0 8px rgba(0, 0, 0, 0.1); +} + +.allowBoxShadow { + box-shadow: 0 0 8px rgba(0, 0, 0, 0.1); + border-radius: 4px; + padding: 8px 12px; +} diff --git a/packages/ocom/ui-community-route-accounts/src/member-section-layout.graphql b/packages/ocom/ui-community-route-accounts/src/member-section-layout.graphql new file mode 100644 index 000000000..445f768bd --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/member-section-layout.graphql @@ -0,0 +1,15 @@ +query MemberSectionLayoutContainerMembersForCurrentEndUser { + membersForCurrentEndUser { + ...MemberSectionLayoutContainerMemberFields + } +} + +fragment MemberSectionLayoutContainerMemberFields on Member { + id + memberName + isAdmin + community { + id + name + } +} diff --git a/packages/ocom/ui-community-route-accounts/src/member-section-layout.tsx b/packages/ocom/ui-community-route-accounts/src/member-section-layout.tsx new file mode 100644 index 000000000..077b49401 --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/member-section-layout.tsx @@ -0,0 +1,118 @@ +import { CommunitiesDropdownContainer, LoggedInUserContainer, MenuComponent, type MenuComponentProps, type PageLayoutProps } from '@ocom/ui-shared'; +import { Layout, theme } from 'antd'; +import { useState } from 'react'; +import { Link, Outlet, useParams } from 'react-router-dom'; +import type { MemberSectionLayoutContainerMemberFieldsFragment } from './generated.tsx'; +import './member-section-layout.css'; + +const { Sider, Header } = Layout; + +const LocalSettingsKeys = { + SidebarCollapsed: 'MemberSidebarCollapsed', +} as const; + +const handleToggler = (isExpanded: boolean, setIsExpanded: (value: boolean) => void) => { + const newValue = !isExpanded; + setIsExpanded(newValue); + if (newValue) { + localStorage.removeItem(LocalSettingsKeys.SidebarCollapsed); + } else { + localStorage.setItem(LocalSettingsKeys.SidebarCollapsed, 'true'); + } +}; + +interface MemberSectionLayoutProps { + pageLayouts: PageLayoutProps[]; + memberData: MemberSectionLayoutContainerMemberFieldsFragment; +} + +export const MemberSectionLayout: React.FC = (props) => { + const params = useParams(); + const sidebarCollapsed = localStorage.getItem(LocalSettingsKeys.SidebarCollapsed); + const [isExpanded, setIsExpanded] = useState(!sidebarCollapsed); + const { + token: { colorBgContainer }, + } = theme.useToken(); + + const menuComponentProps: MenuComponentProps = { + pageLayouts: props.pageLayouts, + memberData: { member: props.memberData }, + theme: 'light', + mode: 'inline', + }; + + return ( + +
+
+
+ +
+ + Back to Accounts + + + +
+
+ + + handleToggler(isExpanded, setIsExpanded)} + style={{ + overflow: 'auto', + height: 'calc(100vh - 64px)', + position: 'relative', + left: 0, + top: 0, + bottom: 0, + backgroundColor: colorBgContainer, + }} + > +
+ + + + + + + + + + ); +}; diff --git a/packages/ocom/ui-community-route-accounts/src/member.tsx b/packages/ocom/ui-community-route-accounts/src/member.tsx new file mode 100644 index 000000000..5d1bcc2e4 --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/member.tsx @@ -0,0 +1,43 @@ +import { HomeOutlined, IdcardOutlined } from '@ant-design/icons'; +import type { PageLayoutProps } from '@ocom/ui-shared'; +import type React from 'react'; +import { Route, Routes } from 'react-router-dom'; +import { MemberSectionLayoutContainer } from './member-section-layout.container.tsx'; +import { MemberHome } from './pages/member-home.tsx'; +import { MemberProfilePage } from './pages/member-profile.tsx'; + +export const Member: React.FC = () => { + const pageLayouts: PageLayoutProps[] = [ + { + path: '/community/:communityId/member/:memberId', + title: 'Home', + icon: , + id: 'ROOT', + }, + { + path: '/community/:communityId/member/:memberId/profile', + title: 'Profile', + icon: , + id: 2, + parent: 'ROOT', + }, + ]; + + return ( + + } + > + } + /> + } + /> + + + ); +}; diff --git a/packages/ocom/ui-community-route-accounts/src/pages/member-home.tsx b/packages/ocom/ui-community-route-accounts/src/pages/member-home.tsx new file mode 100644 index 000000000..0919fb40e --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/pages/member-home.tsx @@ -0,0 +1,95 @@ +import { useQuery } from '@apollo/client'; +import { ComponentQueryLoader } from '@cellix/ui-core'; +import { Descriptions, Typography, theme } from 'antd'; +import type React from 'react'; +import { useParams } from 'react-router-dom'; +import { type MemberHomeContainerMemberFieldsFragment, MemberHomeContainerMemberMyProfileDocument } from '../generated.tsx'; + +const { Text, Title } = Typography; + +interface MemberDetailProps { + data: MemberHomeContainerMemberFieldsFragment; +} + +const MemberDetail: React.FC = ({ data }) => { + const { + token: { colorText, colorBgContainer }, + } = theme.useToken(); + + return ( +
+
+ Community Member +

Welcome to your community portal. Use the navigation on the left to access your profile and other community features.

+
+ + + + + {data.id} + + + + + {data.memberName ?? '—'} + + + + + {data.community?.name ?? '—'} + + + +
+ ); +}; + +const MemberHomeContainer: React.FC = () => { + const params = useParams(); + // biome-ignore lint:useLiteralKeys + const communityId = params['communityId'] ?? ''; + + const { data, loading, error } = useQuery(MemberHomeContainerMemberMyProfileDocument, { + variables: { communityId }, + skip: !communityId, + }); + + return ( + } + error={error} + /> + ); +}; + +export const MemberHome: React.FC = () => { + const { + token: { colorTextBase }, + } = theme.useToken(); + + return ( +
+ + Home + + +
+ ); +}; diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.tsx b/packages/ocom/ui-community-shared/src/components/member-profile.container.tsx index b1feea18e..189497172 100644 --- a/packages/ocom/ui-community-shared/src/components/member-profile.container.tsx +++ b/packages/ocom/ui-community-shared/src/components/member-profile.container.tsx @@ -5,10 +5,12 @@ import type React from 'react'; import { useParams } from 'react-router-dom'; import { SharedMemberProfileContainerMemberDocument, + type SharedMemberProfileContainerMemberFieldsFragment, SharedMemberProfileContainerMemberSelfProfileDocument, SharedMemberProfileContainerMemberUpdateMyProfileDocument, + type SharedMemberProfileContainerMemberUpdateMyProfileMutationVariables, SharedMemberProfileContainerMemberUpdateProfileDocument, - type SharedMemberProfileContainerMemberFieldsFragment, + type SharedMemberProfileContainerMemberUpdateProfileMutationVariables, } from '../generated.tsx'; import { MemberProfile, type MemberProfileFormValues } from './member-profile.tsx'; @@ -17,18 +19,36 @@ export interface MemberProfileContainerProps { mode?: 'admin' | 'self'; } -interface BuildMemberProfileSaveVariablesArgs { - mode: 'admin' | 'self'; - communityId?: string; - memberObjectId?: string; +type SelfSaveVariables = { + variables: SharedMemberProfileContainerMemberUpdateMyProfileMutationVariables; +}; + +type AdminSaveVariables = { + variables: SharedMemberProfileContainerMemberUpdateProfileMutationVariables; +}; + +interface SelfBuildMemberProfileSaveVariablesArgs { + mode: 'self'; + communityId: string; + values: MemberProfileFormValues; +} + +interface AdminBuildMemberProfileSaveVariablesArgs { + mode: 'admin'; + memberObjectId: string; values: MemberProfileFormValues; } -export const buildMemberProfileSaveVariables = ({ mode, communityId, memberObjectId, values }: BuildMemberProfileSaveVariablesArgs) => { +type BuildMemberProfileSaveVariablesArgs = SelfBuildMemberProfileSaveVariablesArgs | AdminBuildMemberProfileSaveVariablesArgs; + +export function buildMemberProfileSaveVariables(args: SelfBuildMemberProfileSaveVariablesArgs): SelfSaveVariables; +export function buildMemberProfileSaveVariables(args: AdminBuildMemberProfileSaveVariablesArgs): AdminSaveVariables; +export function buildMemberProfileSaveVariables({ mode, ...rest }: BuildMemberProfileSaveVariablesArgs): SelfSaveVariables | AdminSaveVariables { if (mode === 'self') { + const { communityId, values } = rest as SelfBuildMemberProfileSaveVariablesArgs; return { variables: { - communityId: communityId ?? '', + communityId, input: { name: values.name, email: values.email, @@ -47,6 +67,7 @@ export const buildMemberProfileSaveVariables = ({ mode, communityId, memberObjec }; } + const { memberObjectId, values } = rest as AdminBuildMemberProfileSaveVariablesArgs; return { variables: { input: { @@ -64,7 +85,7 @@ export const buildMemberProfileSaveVariables = ({ mode, communityId, memberObjec }, }, }; -}; +} export const MemberProfileContainer: React.FC = (props) => { const { message } = App.useApp(); @@ -192,7 +213,7 @@ export const MemberProfileContainer: React.FC = (pr loading={isSelfMode ? memberSelfLoading : memberLoading} hasData={isSelfMode ? memberSelfData?.memberMyProfile : memberData?.member} hasDataComponent={} - error={isSelfMode ? memberSelfError ?? selfProfileUpdateError : memberError ?? profileUpdateError} + error={isSelfMode ? (memberSelfError ?? selfProfileUpdateError) : (memberError ?? profileUpdateError)} /> ); }; From 03faab167a693a215658f9d87c47670e57681add Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Thu, 6 Aug 2026 00:23:32 +0530 Subject: [PATCH 04/14] fixed lint issues --- .../ocom/ui-community-route-accounts/src/accounts.test.tsx | 1 - .../{ => components}/member-section-layout.container.tsx | 6 +++--- packages/ocom/ui-community-route-accounts/src/member.tsx | 2 +- .../src/pages/create-community.stories.tsx | 2 +- .../ui-community-route-accounts/src/pages/home.stories.tsx | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) rename packages/ocom/ui-community-route-accounts/src/{ => components}/member-section-layout.container.tsx (81%) diff --git a/packages/ocom/ui-community-route-accounts/src/accounts.test.tsx b/packages/ocom/ui-community-route-accounts/src/accounts.test.tsx index 3152144ae..6b4e787e0 100644 --- a/packages/ocom/ui-community-route-accounts/src/accounts.test.tsx +++ b/packages/ocom/ui-community-route-accounts/src/accounts.test.tsx @@ -1,5 +1,4 @@ // @vitest-environment jsdom -import type React from 'react'; import { act } from 'react'; import { createRoot } from 'react-dom/client'; import { MemoryRouter } from 'react-router-dom'; diff --git a/packages/ocom/ui-community-route-accounts/src/member-section-layout.container.tsx b/packages/ocom/ui-community-route-accounts/src/components/member-section-layout.container.tsx similarity index 81% rename from packages/ocom/ui-community-route-accounts/src/member-section-layout.container.tsx rename to packages/ocom/ui-community-route-accounts/src/components/member-section-layout.container.tsx index d01bba965..02b83823e 100644 --- a/packages/ocom/ui-community-route-accounts/src/member-section-layout.container.tsx +++ b/packages/ocom/ui-community-route-accounts/src/components/member-section-layout.container.tsx @@ -2,8 +2,8 @@ import { useQuery } from '@apollo/client'; import { ComponentQueryLoader } from '@cellix/ui-core'; import type { PageLayoutProps } from '@ocom/ui-shared'; import { useParams } from 'react-router-dom'; -import { type MemberSectionLayoutContainerMemberFieldsFragment, MemberSectionLayoutContainerMembersForCurrentEndUserDocument } from './generated.tsx'; -import { MemberSectionLayout } from './member-section-layout.tsx'; +import { type MemberSectionLayoutContainerMemberFieldsFragment, MemberSectionLayoutContainerMembersForCurrentEndUserDocument } from '../generated.tsx'; +import { MemberSectionLayout } from '../member-section-layout.tsx'; interface MemberSectionLayoutContainerProps { pageLayouts: PageLayoutProps[]; @@ -22,7 +22,7 @@ export const MemberSectionLayoutContainer: React.FC member.id === params['memberId']) as MemberSectionLayoutContainerMemberFieldsFragment} + memberData={membersData?.membersForCurrentEndUser.find((member: MemberSectionLayoutContainerMemberFieldsFragment) => member.id === params['memberId']) as MemberSectionLayoutContainerMemberFieldsFragment} /> } error={membersError} diff --git a/packages/ocom/ui-community-route-accounts/src/member.tsx b/packages/ocom/ui-community-route-accounts/src/member.tsx index 5d1bcc2e4..e1aa3829c 100644 --- a/packages/ocom/ui-community-route-accounts/src/member.tsx +++ b/packages/ocom/ui-community-route-accounts/src/member.tsx @@ -2,7 +2,7 @@ import { HomeOutlined, IdcardOutlined } from '@ant-design/icons'; import type { PageLayoutProps } from '@ocom/ui-shared'; import type React from 'react'; import { Route, Routes } from 'react-router-dom'; -import { MemberSectionLayoutContainer } from './member-section-layout.container.tsx'; +import { MemberSectionLayoutContainer } from './components/member-section-layout.container.tsx'; import { MemberHome } from './pages/member-home.tsx'; import { MemberProfilePage } from './pages/member-profile.tsx'; diff --git a/packages/ocom/ui-community-route-accounts/src/pages/create-community.stories.tsx b/packages/ocom/ui-community-route-accounts/src/pages/create-community.stories.tsx index 981e131de..6fccb034c 100644 --- a/packages/ocom/ui-community-route-accounts/src/pages/create-community.stories.tsx +++ b/packages/ocom/ui-community-route-accounts/src/pages/create-community.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { MemoryRouter } from 'react-router-dom'; import { expect, within } from 'storybook/test'; -import { Accounts } from '../index.tsx'; +import { Accounts } from '../accounts.tsx'; const meta = { title: 'Pages/Accounts/Create Community', diff --git a/packages/ocom/ui-community-route-accounts/src/pages/home.stories.tsx b/packages/ocom/ui-community-route-accounts/src/pages/home.stories.tsx index 9d728b04e..b114059ce 100644 --- a/packages/ocom/ui-community-route-accounts/src/pages/home.stories.tsx +++ b/packages/ocom/ui-community-route-accounts/src/pages/home.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { MemoryRouter } from 'react-router-dom'; import { expect, within } from 'storybook/test'; -import { Accounts } from '../index.tsx'; +import { Accounts } from '../accounts.tsx'; const meta = { title: 'Pages/Accounts/Home', From 29fbf92afa43195f2d6067cfbf8811ba00fdfc89 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Fri, 7 Aug 2026 00:01:31 +0530 Subject: [PATCH 05/14] fixing build failures --- .../acceptance-ui/package.json | 4 +- pnpm-lock.yaml | 105 +++--------------- pnpm-workspace.yaml | 9 ++ 3 files changed, 27 insertions(+), 91 deletions(-) diff --git a/packages/ocom-verification/acceptance-ui/package.json b/packages/ocom-verification/acceptance-ui/package.json index 5e4cc8c05..889bc39dd 100644 --- a/packages/ocom-verification/acceptance-ui/package.json +++ b/packages/ocom-verification/acceptance-ui/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@apollo/client": "^3.13.9", + "@cellix/serenity-framework": "workspace:*", "@cucumber/cucumber": "catalog:", "@dr.pogodin/react-helmet": "^3.0.4", - "@cellix/serenity-framework": "workspace:*", "@serenity-js/console-reporter": "catalog:", "@serenity-js/core": "catalog:", "@serenity-js/cucumber": "catalog:", @@ -33,7 +33,7 @@ "@types/node": "catalog:", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "c8": "^10.1.3", + "c8": "^11.0.0", "tsx": "catalog:", "typescript": "catalog:" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 641b87b53..4e113693b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,7 +136,7 @@ overrides: jiti: 2.6.1 rollup: ^4.59.0 '@ant-design/pro-layout>path-to-regexp': ^8.4.0 - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 diff@4.0.2: 4.0.4 '@protobufjs/codegen': 2.0.5 '@protobufjs/utf8': 1.1.1 @@ -166,8 +166,8 @@ overrides: playwright: 1.59.0 postcss: ^8.5.18 protobufjs: 7.6.5 - ip-address: ^10.2.1 - fast-uri: ^4.1.1 + ip-address: ^10.1.1 + fast-uri: ^4.1.2 '@babel/plugin-transform-modules-systemjs': 7.29.4 '@babel/core': ^7.29.6 js-yaml: 4.3.0 @@ -1447,8 +1447,8 @@ importers: specifier: ^19.1.6 version: 19.2.3(@types/react@19.2.7) c8: - specifier: ^10.1.3 - version: 10.1.3 + specifier: ^11.0.0 + version: 11.0.0 tsx: specifier: 'catalog:' version: 4.21.0 @@ -6146,10 +6146,6 @@ packages: resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} engines: {node: '>=20.0.0'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@playwright/test@1.59.0': resolution: {integrity: sha512-TOA5sTLd49rTDaZpYpvCQ9hGefHQq/OYOyCVnGqS2mjMfX+lGZv2iddIJd0I48cfxqSPttS9S3OuLKyylHcO1w==} engines: {node: '>=18'} @@ -7927,8 +7923,8 @@ packages: brace-expansion@2.0.3: resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} - brace-expansion@5.0.8: - resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} braces@3.0.3: @@ -8014,16 +8010,6 @@ packages: resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} engines: {node: '>=6.0.0'} - c8@10.1.3: - resolution: {integrity: sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - monocart-coverage-reports: ^2 - peerDependenciesMeta: - monocart-coverage-reports: - optional: true - c8@11.0.0: resolution: {integrity: sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==} engines: {node: 20 || >=22} @@ -9150,8 +9136,8 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-uri@4.1.1: - resolution: {integrity: sha512-YPOs1zD5TG2+EZt+r88LwF6mclA7TPkpwMP7ZN3TO2HiHS8TXvq7QA/17iJsV9dubcLo/f8eEYqMBruyQV21hQ==} + fast-uri@4.1.2: + resolution: {integrity: sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==} fast-xml-builder@1.2.0: resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} @@ -9411,11 +9397,6 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - glob@11.1.0: resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} @@ -10196,9 +10177,6 @@ packages: iterall@1.3.0: resolution: {integrity: sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.1.1: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} @@ -11508,10 +11486,6 @@ packages: resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==} engines: {node: '>=0.10.0'} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} @@ -13239,10 +13213,6 @@ packages: engines: {node: '>=10'} hasBin: true - test-exclude@7.0.2: - resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} - engines: {node: '>=18'} - test-exclude@8.0.0: resolution: {integrity: sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==} engines: {node: 20 || >=22} @@ -18698,9 +18668,6 @@ snapshots: tslib: 2.8.1 tsyringe: 4.10.0 - '@pkgjs/parseargs@0.11.0': - optional: true - '@playwright/test@1.59.0': dependencies: playwright: 1.59.0 @@ -20350,7 +20317,7 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 4.1.1 + fast-uri: 4.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -20795,7 +20762,7 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.8: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -20901,20 +20868,6 @@ snapshots: bytestreamjs@2.0.1: {} - c8@10.1.3: - dependencies: - '@bcoe/v8-coverage': 1.0.2 - '@istanbuljs/schema': 0.1.3 - find-up: 5.0.0 - foreground-child: 3.3.1 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 - test-exclude: 7.0.2 - v8-to-istanbul: 9.3.0 - yargs: 17.7.2 - yargs-parser: 21.1.1 - c8@11.0.0: dependencies: '@bcoe/v8-coverage': 1.0.2 @@ -22177,7 +22130,7 @@ snapshots: fast-json-stable-stringify@2.1.0: {} - fast-uri@4.1.1: {} + fast-uri@4.1.2: {} fast-xml-builder@1.2.0: dependencies: @@ -22462,15 +22415,6 @@ snapshots: glob-to-regexp@0.4.1: {} - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - glob@11.1.0: dependencies: foreground-child: 3.3.1 @@ -23348,12 +23292,6 @@ snapshots: iterall@1.3.0: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jackspeak@4.1.1: dependencies: '@isaacs/cliui': 8.0.2 @@ -24376,15 +24314,15 @@ snapshots: minimatch@10.2.4: dependencies: - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 minimatch@10.2.5: dependencies: - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 minimatch@3.1.5: dependencies: - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 minimatch@9.0.9: dependencies: @@ -25084,11 +25022,6 @@ snapshots: dependencies: path-root-regex: 0.1.2 - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.3 - path-scurry@2.0.2: dependencies: lru-cache: 11.3.5 @@ -27063,17 +26996,11 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - test-exclude@7.0.2: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.5.0 - minimatch: 10.2.4 - test-exclude@8.0.0: dependencies: '@istanbuljs/schema': 0.1.3 glob: 13.0.6 - minimatch: 10.2.4 + minimatch: 10.2.5 text-decoder@1.2.3: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3357912aa..cbebe7809 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -88,7 +88,11 @@ overrides: jiti: 2.6.1 rollup: ^4.59.0 '@ant-design/pro-layout>path-to-regexp': ^8.4.0 +<<<<<<< HEAD brace-expansion: 5.0.8 +======= + brace-expansion: 5.0.9 +>>>>>>> f25032aa (fixing build failures) 'diff@4.0.2': 4.0.4 '@protobufjs/codegen': 2.0.5 '@protobufjs/utf8': 1.1.1 @@ -118,8 +122,13 @@ overrides: playwright: 1.59.0 postcss: ^8.5.18 protobufjs: 7.6.5 +<<<<<<< HEAD ip-address: ^10.2.1 fast-uri: ^4.1.1 +======= + ip-address: ^10.1.1 + fast-uri: ^4.1.2 +>>>>>>> f25032aa (fixing build failures) '@babel/plugin-transform-modules-systemjs': 7.29.4 '@babel/core': ^7.29.6 js-yaml: 4.3.0 From a369a784699d985738645f5d0b8f8c8aa68014f4 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Fri, 7 Aug 2026 00:16:04 +0530 Subject: [PATCH 06/14] fixing build failures --- pnpm-workspace.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cbebe7809..0368e790c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -88,11 +88,7 @@ overrides: jiti: 2.6.1 rollup: ^4.59.0 '@ant-design/pro-layout>path-to-regexp': ^8.4.0 -<<<<<<< HEAD - brace-expansion: 5.0.8 -======= brace-expansion: 5.0.9 ->>>>>>> f25032aa (fixing build failures) 'diff@4.0.2': 4.0.4 '@protobufjs/codegen': 2.0.5 '@protobufjs/utf8': 1.1.1 @@ -122,13 +118,8 @@ overrides: playwright: 1.59.0 postcss: ^8.5.18 protobufjs: 7.6.5 -<<<<<<< HEAD - ip-address: ^10.2.1 - fast-uri: ^4.1.1 -======= ip-address: ^10.1.1 fast-uri: ^4.1.2 ->>>>>>> f25032aa (fixing build failures) '@babel/plugin-transform-modules-systemjs': 7.29.4 '@babel/core': ^7.29.6 js-yaml: 4.3.0 From b6f61364b6aef4afd47035112d0e05435889541a Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Fri, 7 Aug 2026 00:44:29 +0530 Subject: [PATCH 07/14] fixing build failures --- pnpm-lock.yaml | 35 ++++++++++++----------------------- pnpm-workspace.yaml | 2 ++ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e113693b..b7c5564b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,6 +136,7 @@ overrides: jiti: 2.6.1 rollup: ^4.59.0 '@ant-design/pro-layout>path-to-regexp': ^8.4.0 + '@apollo/protobufjs': 1.2.8 brace-expansion: 5.0.9 diff@4.0.2: 4.0.4 '@protobufjs/codegen': 2.0.5 @@ -181,6 +182,7 @@ overrides: '@grpc/grpc-js': '>=1.14.4' form-data: ^4.0.6 webpack-dev-server>http-proxy-middleware: 3.0.7 + nanoid: 3.3.17 joi: ^17.13.4 brace-expansion@2: 2.0.3 dottie: 2.0.7 @@ -3089,8 +3091,8 @@ packages: subscriptions-transport-ws: optional: true - '@apollo/protobufjs@1.2.7': - resolution: {integrity: sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==} + '@apollo/protobufjs@1.2.8': + resolution: {integrity: sha512-r7xNeUqZX+eBBEmyvaPw0/cSz6zgf5jdH8mjUz8ynKpNs/GU7vi2T7sNcZINk2ZID7wwjG91FCgdpCrQuJ8rzA==} hasBin: true '@apollo/server-gateway-interface@2.0.0': @@ -6175,15 +6177,9 @@ packages: '@protobufjs/codegen@2.0.5': resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - '@protobufjs/eventemitter@1.1.1': resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} - '@protobufjs/fetch@1.1.1': resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} @@ -11100,8 +11096,8 @@ packages: resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} engines: {node: '>=12.0.0'} - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.17: + resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -14437,13 +14433,13 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@apollo/protobufjs@1.2.7': + '@apollo/protobufjs@1.2.8': dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 '@protobufjs/codegen': 2.0.5 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 '@protobufjs/float': 1.0.2 '@protobufjs/inquire': 1.1.1 '@protobufjs/path': 1.1.2 @@ -14489,7 +14485,7 @@ snapshots: '@apollo/usage-reporting-protobuf@4.1.1': dependencies: - '@apollo/protobufjs': 1.2.7 + '@apollo/protobufjs': 1.2.8 '@apollo/utils.createhash@3.0.1': dependencies: @@ -18692,15 +18688,8 @@ snapshots: '@protobufjs/codegen@2.0.5': {} - '@protobufjs/eventemitter@1.1.0': {} - '@protobufjs/eventemitter@1.1.1': {} - '@protobufjs/fetch@1.1.0': - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.1 - '@protobufjs/fetch@1.1.1': dependencies: '@protobufjs/aspromise': 1.1.2 @@ -24528,7 +24517,7 @@ snapshots: dependencies: lru-cache: 7.18.3 - nanoid@3.3.16: {} + nanoid@3.3.17: {} native-duplexpair@1.0.0: {} @@ -25577,7 +25566,7 @@ snapshots: postcss@8.5.25: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0368e790c..acc8b7f6a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -88,6 +88,7 @@ overrides: jiti: 2.6.1 rollup: ^4.59.0 '@ant-design/pro-layout>path-to-regexp': ^8.4.0 + '@apollo/protobufjs': 1.2.8 brace-expansion: 5.0.9 'diff@4.0.2': 4.0.4 '@protobufjs/codegen': 2.0.5 @@ -133,6 +134,7 @@ overrides: '@grpc/grpc-js': '>=1.14.4' form-data: ^4.0.6 'webpack-dev-server>http-proxy-middleware': 3.0.7 + nanoid: 3.3.17 joi: ^17.13.4 brace-expansion@2: 2.0.3 dottie: 2.0.7 From fe99b11de3277a623365fa337ccfd604fe76d153 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Mon, 10 Aug 2026 22:38:43 +0530 Subject: [PATCH 08/14] resolving sonar cloud issues --- .../member-profile.container.test.ts | 2 - .../member-profile.container.test.tsx | 138 ++++++++++++++++++ 2 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts index a92d8224f..11d1ca486 100644 --- a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts +++ b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts @@ -6,7 +6,6 @@ describe('buildMemberProfileSaveVariables', () => { const result = buildMemberProfileSaveVariables({ mode: 'self', communityId: 'community-1', - memberObjectId: undefined, values: { name: 'Jane Doe', email: 'jane@example.com', @@ -43,7 +42,6 @@ describe('buildMemberProfileSaveVariables', () => { it('builds an admin mutation payload for a specific member', () => { const result = buildMemberProfileSaveVariables({ mode: 'admin', - communityId: 'community-1', memberObjectId: 'member-1', values: { name: 'Jane Doe', diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx new file mode 100644 index 000000000..ca21ea233 --- /dev/null +++ b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx @@ -0,0 +1,138 @@ +// @vitest-environment jsdom +import { act } from 'react'; +import { createRoot } from 'react-dom/client'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { MemberProfileContainer } from './member-profile.container.tsx'; + +const { messageMock, useMutationMock, useQueryMock, useParamsMock } = vi.hoisted(() => ({ + messageMock: { + success: vi.fn(), + error: vi.fn(), + }, + useMutationMock: vi.fn(() => [vi.fn(), { loading: false, error: undefined }]), + useQueryMock: vi.fn(() => ({ data: undefined, loading: false, error: undefined })), + useParamsMock: vi.fn(() => ({})), +})); + +vi.mock('antd', () => ({ + App: { + useApp: () => ({ message: messageMock }), + }, +})); + +vi.mock('@apollo/client', () => ({ + useMutation: () => useMutationMock(), + useQuery: () => useQueryMock(), +})); + +vi.mock('react-router-dom', () => ({ + useParams: () => useParamsMock(), +})); + +vi.mock('@cellix/ui-core', () => ({ + ComponentQueryLoader: ({ hasDataComponent }: { hasDataComponent?: React.ReactNode }) => <>{hasDataComponent}, +})); + +vi.mock('./member-profile.tsx', () => ({ + MemberProfile: ({ onSave }: { onSave: (values: Record) => Promise }) => ( + + ), +})); + +describe('MemberProfileContainer', () => { + let container: HTMLDivElement; + let root: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + useMutationMock.mockImplementation(() => [vi.fn(), { loading: false, error: undefined }]); + useQueryMock.mockImplementation(() => ({ data: undefined, loading: false, error: undefined })); + useParamsMock.mockImplementation(() => ({})); + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + act(() => { + root.unmount(); + }); + container.remove(); + }); + + async function clickSave() { + await act(async () => { + container.querySelector('button')?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + } + + it('reports success when a self profile save succeeds', async () => { + useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); + const selfMutation = vi.fn().mockResolvedValue({ + data: { + memberUpdateMyProfile: { + status: { success: true, errorMessage: undefined }, + }, + }, + }); + useMutationMock.mockImplementationOnce(() => [vi.fn(), { loading: false, error: undefined }]) + .mockImplementationOnce(() => [selfMutation, { loading: false, error: undefined }]); + + act(() => { + root.render(); + }); + await clickSave(); + + expect(messageMock.success).toHaveBeenCalledWith('Profile updated'); + }); + + it('reports an error when a self profile save returns a failure status', async () => { + useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); + const selfMutation = vi.fn().mockResolvedValue({ + data: { + memberUpdateMyProfile: { + status: { success: false, errorMessage: 'Self save failed' }, + }, + }, + }); + useMutationMock.mockImplementationOnce(() => [vi.fn(), { loading: false, error: undefined }]) + .mockImplementationOnce(() => [selfMutation, { loading: false, error: undefined }]); + + act(() => { + root.render(); + }); + await clickSave(); + + expect(messageMock.error).toHaveBeenCalledWith('Self save failed'); + }); + + it('reports when an admin save is attempted without a member id', async () => { + useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); + useMutationMock.mockImplementation(() => [vi.fn(), { loading: false, error: undefined }]); + + act(() => { + root.render(); + }); + await clickSave(); + + expect(messageMock.error).toHaveBeenCalledWith('Error updating profile: Cannot read properties of undefined (reading \'data\')'); + }); + + it('reports a thrown save error for admin mode', async () => { + useParamsMock.mockImplementation(() => ({ memberId: 'member-1' })); + const profileMutation = vi.fn().mockRejectedValue(new Error('boom')); + useMutationMock.mockImplementationOnce(() => [profileMutation, { loading: false, error: undefined }]) + .mockImplementationOnce(() => [vi.fn(), { loading: false, error: undefined }]); + + act(() => { + root.render(); + }); + await clickSave(); + + expect(messageMock.error).toHaveBeenCalledWith('Error updating profile: boom'); + }); +}); From f688dba48b15e29eb494f406be9908d9afc92b2c Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Mon, 10 Aug 2026 23:04:28 +0530 Subject: [PATCH 09/14] resolving sonar cloud issues --- .../member-profile.container.test.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx index ca21ea233..d6d148b65 100644 --- a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx +++ b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx @@ -64,13 +64,13 @@ describe('MemberProfileContainer', () => { container.remove(); }); - async function clickSave() { - await act(async () => { + function clickSave() { + act(() => { container.querySelector('button')?.dispatchEvent(new MouseEvent('click', { bubbles: true })); }); } - it('reports success when a self profile save succeeds', async () => { + it('reports success when a self profile save succeeds', () => { useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); const selfMutation = vi.fn().mockResolvedValue({ data: { @@ -85,12 +85,12 @@ describe('MemberProfileContainer', () => { act(() => { root.render(); }); - await clickSave(); + clickSave(); expect(messageMock.success).toHaveBeenCalledWith('Profile updated'); }); - it('reports an error when a self profile save returns a failure status', async () => { + it('reports an error when a self profile save returns a failure status', () => { useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); const selfMutation = vi.fn().mockResolvedValue({ data: { @@ -105,24 +105,24 @@ describe('MemberProfileContainer', () => { act(() => { root.render(); }); - await clickSave(); + clickSave(); expect(messageMock.error).toHaveBeenCalledWith('Self save failed'); }); - it('reports when an admin save is attempted without a member id', async () => { + it('reports when an admin save is attempted without a member id', () => { useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); useMutationMock.mockImplementation(() => [vi.fn(), { loading: false, error: undefined }]); act(() => { root.render(); }); - await clickSave(); + clickSave(); expect(messageMock.error).toHaveBeenCalledWith('Error updating profile: Cannot read properties of undefined (reading \'data\')'); }); - it('reports a thrown save error for admin mode', async () => { + it('reports a thrown save error for admin mode', () => { useParamsMock.mockImplementation(() => ({ memberId: 'member-1' })); const profileMutation = vi.fn().mockRejectedValue(new Error('boom')); useMutationMock.mockImplementationOnce(() => [profileMutation, { loading: false, error: undefined }]) @@ -131,7 +131,7 @@ describe('MemberProfileContainer', () => { act(() => { root.render(); }); - await clickSave(); + clickSave(); expect(messageMock.error).toHaveBeenCalledWith('Error updating profile: boom'); }); From bf648dca118acc16f9a02796db1fb74ecf8341c6 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Mon, 10 Aug 2026 23:30:08 +0530 Subject: [PATCH 10/14] resolving sonar cloud issues --- build-pipeline/core/monorepo-build-stage.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/build-pipeline/core/monorepo-build-stage.yml b/build-pipeline/core/monorepo-build-stage.yml index 6d9b262a4..1ab3e8be2 100644 --- a/build-pipeline/core/monorepo-build-stage.yml +++ b/build-pipeline/core/monorepo-build-stage.yml @@ -69,15 +69,21 @@ stages: path: '/opt/hostedtoolcache/func' cacheHitVar: FUNC_TOOLS_CACHE_HIT - # Ensure the correct version of function tools are installed. - # Temporary change to use npm global install due to github repo being restricted + # Azure Functions Core Tools are optional for this repository build. + # Some hosted agents fail to download the npm-installed CLI artifact; + # verify availability instead of failing the pipeline when it is not needed. - task: Bash@3 - displayName: 'Install: func tools - 4.2.1' - condition: and(ne(variables.FUNC_TOOLS_CACHE_HIT, 'true'), ne(variables.FUNC_TOOLS_CACHE_HIT, 'inexact')) + displayName: 'Azure Functions Core Tools: verify availability' inputs: targetType: 'inline' script: | - npm install -g azure-functions-core-tools + set -euo pipefail + if command -v func >/dev/null 2>&1; then + echo "Azure Functions Core Tools detected: $(command -v func)" + func --version + else + echo "Azure Functions Core Tools not installed; continuing because the repository build does not require it." + fi # Cache PNPM store to speed up build process. - task: Cache@2 From 92303890d0dc95d8d1054fb29716ae206684dd50 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Tue, 11 Aug 2026 00:08:54 +0530 Subject: [PATCH 11/14] resolving sonar cloud issues --- pnpm-lock.yaml | 62 ++++++++++++++++++++------------------------- pnpm-workspace.yaml | 13 +++++++--- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7c5564b4..07cc2ac6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -171,8 +171,8 @@ overrides: fast-uri: ^4.1.2 '@babel/plugin-transform-modules-systemjs': 7.29.4 '@babel/core': ^7.29.6 - js-yaml: 4.3.0 - js-yaml@3.14.2: 3.15.0 + js-yaml: 4.3.1 + js-yaml@3.14.2: 3.15.1 shell-quote@<1.8.4: 1.8.4 '@opentelemetry/exporter-prometheus@0.57.2': 0.217.0 '@opentelemetry/core@2.7.1': 2.8.0 @@ -182,12 +182,15 @@ overrides: '@grpc/grpc-js': '>=1.14.4' form-data: ^4.0.6 webpack-dev-server>http-proxy-middleware: 3.0.7 + image-size: 2.0.2 nanoid: 3.3.17 joi: ^17.13.4 brace-expansion@2: 2.0.3 + adm-zip@0.5.16: 0.5.17 + immutable@3.8.3: 4.3.9 dottie: 2.0.7 http-proxy-middleware: 3.0.7 - immutable: 3.8.3 + immutable: 4.3.9 launch-editor: ^2.14.1 sequelize: 6.37.8 smol-toml: 1.6.1 @@ -7551,8 +7554,8 @@ packages: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} - adm-zip@0.5.16: - resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + adm-zip@0.5.17: + resolution: {integrity: sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==} engines: {node: '>=12.0'} agent-base@6.0.2: @@ -9771,11 +9774,6 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - image-size@0.5.5: - resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} - engines: {node: '>=0.10.0'} - hasBin: true - image-size@2.0.2: resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} engines: {node: '>=16.x'} @@ -9784,9 +9782,8 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - immutable@3.8.3: - resolution: {integrity: sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg==} - engines: {node: '>=0.10.0'} + immutable@4.3.9: + resolution: {integrity: sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -10205,12 +10202,12 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.15.0: - resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} + js-yaml@3.15.1: + resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} hasBin: true - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true jsbi@4.3.2: @@ -14544,7 +14541,7 @@ snapshots: chalk: 4.1.2 fb-watchman: 2.0.2 graphql: 16.12.0 - immutable: 3.8.3 + immutable: 4.3.9 invariant: 2.2.4 nullthrows: 1.1.1 relay-runtime: 12.0.0 @@ -16538,7 +16535,7 @@ snapshots: '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.3.2 - js-yaml: 4.3.0 + js-yaml: 4.3.1 lodash: 4.18.1 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) @@ -16984,7 +16981,7 @@ snapshots: '@docusaurus/utils-common': 3.10.1(esbuild@0.28.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) fs-extra: 11.3.2 joi: 17.13.4 - js-yaml: 4.3.0 + js-yaml: 4.3.1 lodash: 4.18.1 tslib: 2.8.1 transitivePeerDependencies: @@ -17009,7 +17006,7 @@ snapshots: globby: 11.1.0 gray-matter: 4.0.3 jiti: 2.6.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 lodash: 4.18.1 micromatch: 4.0.8 p-queue: 6.6.2 @@ -17585,7 +17582,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 jose: 5.10.0 - js-yaml: 4.3.0 + js-yaml: 4.3.1 lodash: 4.18.1 scuid: 1.1.0 tslib: 2.8.1 @@ -19305,7 +19302,7 @@ snapshots: '@sonar/scan@4.3.2(supports-color@8.1.1)': dependencies: - adm-zip: 0.5.16 + adm-zip: 0.5.17 axios: 1.18.1(supports-color@8.1.1) commander: 13.1.0 fs-extra: 11.3.2 @@ -20260,7 +20257,7 @@ snapshots: address@1.2.2: {} - adm-zip@0.5.16: {} + adm-zip@0.5.17: {} agent-base@6.0.2(supports-color@8.1.1): dependencies: @@ -21285,7 +21282,7 @@ snapshots: cosmiconfig@8.3.6(typescript@6.0.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -22565,7 +22562,7 @@ snapshots: gray-matter@4.0.3: dependencies: - js-yaml: 3.15.0 + js-yaml: 3.15.1 kind-of: 6.0.3 section-matter: 1.0.0 strip-bom-string: 1.0.0 @@ -22933,14 +22930,11 @@ snapshots: ignore@7.0.5: {} - image-size@0.5.5: - optional: true - image-size@2.0.2: {} immediate@3.0.6: {} - immutable@3.8.3: {} + immutable@4.3.9: {} import-fresh@3.3.1: dependencies: @@ -23323,12 +23317,12 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.15.0: + js-yaml@3.15.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.3.0: + js-yaml@4.3.1: dependencies: argparse: 2.0.1 @@ -23456,7 +23450,7 @@ snapshots: fast-glob: 3.3.3 formatly: 0.3.0 jiti: 2.6.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 minimist: 1.2.8 oxc-resolver: 11.14.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) picocolors: 1.1.1 @@ -23492,7 +23486,7 @@ snapshots: optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 - image-size: 0.5.5 + image-size: 2.0.2 make-dir: 2.1.0 mime: 1.6.0 needle: 3.3.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index acc8b7f6a..53f48ef58 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -65,6 +65,8 @@ auditConfig: - GHSA-q7rr-3cgh-j5r3 - GHSA-869p-cjfg-cm3x # jws@4.0.0: Improperly Verifies HMAC Signature (transitive from azurite) - GHSA-qwww-vcr4-c8h2 # react-router 7.x: defer upgrade until the react-router-dom v8 migration is completed + - GHSA-w3rx-r6r6-pgpr # image-size ICNS parser DoS advisory; no patched release available in the Docusaurus dependency tree + - GHSA-5p2g-fcmc-qvqq # image-size JXL/HEIF parser DoS advisory; no patched release available in the Docusaurus dependency tree allowBuilds: '@apollo/protobufjs': true @@ -123,8 +125,8 @@ overrides: fast-uri: ^4.1.2 '@babel/plugin-transform-modules-systemjs': 7.29.4 '@babel/core': ^7.29.6 - js-yaml: 4.3.0 - 'js-yaml@3.14.2': 3.15.0 + js-yaml: 4.3.1 + 'js-yaml@3.14.2': 3.15.1 'shell-quote@<1.8.4': 1.8.4 '@opentelemetry/exporter-prometheus@0.57.2': 0.217.0 '@opentelemetry/core@2.7.1': 2.8.0 @@ -134,12 +136,15 @@ overrides: '@grpc/grpc-js': '>=1.14.4' form-data: ^4.0.6 'webpack-dev-server>http-proxy-middleware': 3.0.7 + image-size: 2.0.2 nanoid: 3.3.17 joi: ^17.13.4 - brace-expansion@2: 2.0.3 + 'brace-expansion@2': 2.0.3 + 'adm-zip@0.5.16': 0.5.17 + 'immutable@3.8.3': 4.3.9 dottie: 2.0.7 http-proxy-middleware: 3.0.7 - immutable: 3.8.3 + immutable: 4.3.9 launch-editor: ^2.14.1 sequelize: 6.37.8 smol-toml: 1.6.1 From 7de7835e4c780b02a95763e0775ef342846e041a Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Sat, 15 Aug 2026 00:25:23 +0530 Subject: [PATCH 12/14] addressed review comments --- build-pipeline/core/monorepo-build-stage.yml | 16 +- .../member-management.operations.test.ts | 22 +-- .../community/member/member-management.ts | 5 - .../src/schema/types/member.resolvers.ts | 1 - .../src/pages/member-home.stories.tsx | 53 +++++++ .../src/pages/member-profile.stories.tsx | 61 ++++++++ .../member-profile.container.stories.tsx | 64 ++++++++ .../member-profile.container.test.ts | 76 ---------- .../member-profile.container.test.tsx | 138 ------------------ 9 files changed, 195 insertions(+), 241 deletions(-) create mode 100644 packages/ocom/ui-community-route-accounts/src/pages/member-home.stories.tsx create mode 100644 packages/ocom/ui-community-route-accounts/src/pages/member-profile.stories.tsx create mode 100644 packages/ocom/ui-community-shared/src/components/member-profile.container.stories.tsx delete mode 100644 packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts delete mode 100644 packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx diff --git a/build-pipeline/core/monorepo-build-stage.yml b/build-pipeline/core/monorepo-build-stage.yml index 1ab3e8be2..6d9b262a4 100644 --- a/build-pipeline/core/monorepo-build-stage.yml +++ b/build-pipeline/core/monorepo-build-stage.yml @@ -69,21 +69,15 @@ stages: path: '/opt/hostedtoolcache/func' cacheHitVar: FUNC_TOOLS_CACHE_HIT - # Azure Functions Core Tools are optional for this repository build. - # Some hosted agents fail to download the npm-installed CLI artifact; - # verify availability instead of failing the pipeline when it is not needed. + # Ensure the correct version of function tools are installed. + # Temporary change to use npm global install due to github repo being restricted - task: Bash@3 - displayName: 'Azure Functions Core Tools: verify availability' + displayName: 'Install: func tools - 4.2.1' + condition: and(ne(variables.FUNC_TOOLS_CACHE_HIT, 'true'), ne(variables.FUNC_TOOLS_CACHE_HIT, 'inexact')) inputs: targetType: 'inline' script: | - set -euo pipefail - if command -v func >/dev/null 2>&1; then - echo "Azure Functions Core Tools detected: $(command -v func)" - func --version - else - echo "Azure Functions Core Tools not installed; continuing because the repository build does not require it." - fi + npm install -g azure-functions-core-tools # Cache PNPM store to speed up build process. - task: Cache@2 diff --git a/packages/ocom/application-services/src/contexts/community/member/member-management.operations.test.ts b/packages/ocom/application-services/src/contexts/community/member/member-management.operations.test.ts index 5cc21e882..64f3a4d9d 100644 --- a/packages/ocom/application-services/src/contexts/community/member/member-management.operations.test.ts +++ b/packages/ocom/application-services/src/contexts/community/member/member-management.operations.test.ts @@ -321,7 +321,7 @@ describe('member-management operations', () => { expect(member.profile.showProfile).toBe(true); }); - it('throws when an actor tries to update a different member profile', async () => { + it('updates member profile without service-level actor permission checks', async () => { const member = { memberName: 'Old Name', profile: { @@ -336,16 +336,18 @@ describe('member-management operations', () => { }, }; memberRepository.getById.mockResolvedValue(member); + memberRepository.save.mockResolvedValue({ id: 'member-1' }); - await expect( - updateMemberProfile(dataSources)({ - memberId: 'member-1', - actorMemberId: 'member-2', - profile: { - name: 'Jane Doe', - }, - }), - ).rejects.toThrow('You do not have permission to update this profile'); + const result = await updateMemberProfile(dataSources)({ + memberId: 'member-1', + profile: { + name: 'Jane Doe', + }, + }); + + expect(result.id).toBe('member-1'); + expect(member.profile.name).toBe('Jane Doe'); + expect(member.memberName).toBe('Jane Doe'); }); it('throws when update member role save returns nothing', async () => { diff --git a/packages/ocom/application-services/src/contexts/community/member/member-management.ts b/packages/ocom/application-services/src/contexts/community/member/member-management.ts index 74c8cfe3d..ee4e7b8be 100644 --- a/packages/ocom/application-services/src/contexts/community/member/member-management.ts +++ b/packages/ocom/application-services/src/contexts/community/member/member-management.ts @@ -469,7 +469,6 @@ export interface MemberRemoveAccountCommand { export interface MemberUpdateProfileCommand { memberId: string; - actorMemberId?: string; profile: { name?: string | null; email?: string | null; @@ -519,10 +518,6 @@ export const updateMemberProfile = (dataSources: DataSources) => { const member = await repository.getById(command.memberId); const profile = member.profile; - if (command.actorMemberId && String(command.actorMemberId) !== String(command.memberId)) { - throw new Error('You do not have permission to update this profile'); - } - if (command.profile.name !== undefined && command.profile.name !== null) { profile.name = command.profile.name; member.memberName = command.profile.name; diff --git a/packages/ocom/graphql/src/schema/types/member.resolvers.ts b/packages/ocom/graphql/src/schema/types/member.resolvers.ts index f1100a78c..3abb10803 100644 --- a/packages/ocom/graphql/src/schema/types/member.resolvers.ts +++ b/packages/ocom/graphql/src/schema/types/member.resolvers.ts @@ -793,7 +793,6 @@ const member: Resolvers = { const command: MemberUpdateProfileCommand = { memberId: actorMemberId, - actorMemberId, profile: { name: args.input.name, email: args.input.email, diff --git a/packages/ocom/ui-community-route-accounts/src/pages/member-home.stories.tsx b/packages/ocom/ui-community-route-accounts/src/pages/member-home.stories.tsx new file mode 100644 index 000000000..c71b86f5f --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/pages/member-home.stories.tsx @@ -0,0 +1,53 @@ +import { MockedProvider } from '@apollo/client/testing'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { MemberHomeContainerMemberMyProfileDocument } from '../generated.tsx'; +import { MemberHome } from './member-home.tsx'; + +const meta = { + title: 'Pages/Accounts/MemberHome', + component: MemberHome, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const mocks = [ + { + request: { + query: MemberHomeContainerMemberMyProfileDocument, + variables: { communityId: 'community-1' }, + }, + result: { + data: { + memberMyProfile: { + __typename: 'Member', + id: 'member-1', + memberName: 'jane-doe', + community: { + __typename: 'Community', + id: 'community-1', + name: 'Sample Community', + }, + }, + }, + }, + }, +]; + +export const Default: Story = { + decorators: [ + (Story) => ( + + + + } /> + + + + ), + ], +}; diff --git a/packages/ocom/ui-community-route-accounts/src/pages/member-profile.stories.tsx b/packages/ocom/ui-community-route-accounts/src/pages/member-profile.stories.tsx new file mode 100644 index 000000000..07e68941b --- /dev/null +++ b/packages/ocom/ui-community-route-accounts/src/pages/member-profile.stories.tsx @@ -0,0 +1,61 @@ +import { MockedProvider } from '@apollo/client/testing'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { SharedMemberProfileContainerMemberSelfProfileDocument } from '@ocom/ui-community-shared/src/generated.tsx'; +import { MemberProfilePage } from './member-profile.tsx'; + +const meta = { + title: 'Pages/Accounts/MemberProfile', + component: MemberProfilePage, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const mocks = [ + { + request: { + query: SharedMemberProfileContainerMemberSelfProfileDocument, + variables: { communityId: 'community-1' }, + }, + result: { + data: { + memberMyProfile: { + __typename: 'Member', + id: 'member-1', + memberName: 'jane-doe', + createdAt: '2024-01-15T00:00:00.000Z', + updatedAt: '2024-02-15T00:00:00.000Z', + profile: { + __typename: 'MemberProfile', + name: 'Jane Doe', + email: 'jane@example.com', + bio: 'Community member and product designer.', + showInterests: true, + showEmail: true, + showProfile: true, + showLocation: true, + showProperties: false, + }, + }, + }, + }, + }, +]; + +export const Default: Story = { + decorators: [ + (Story) => ( + + + + } /> + + + + ), + ], +}; diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.stories.tsx b/packages/ocom/ui-community-shared/src/components/member-profile.container.stories.tsx new file mode 100644 index 000000000..c3f8b69b7 --- /dev/null +++ b/packages/ocom/ui-community-shared/src/components/member-profile.container.stories.tsx @@ -0,0 +1,64 @@ +import { MockedProvider } from '@apollo/client/testing'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { SharedMemberProfileContainerMemberSelfProfileDocument } from '../generated.tsx'; +import { MemberProfileContainer } from './member-profile.container.tsx'; + +const meta = { + title: 'Shared/Components/MemberProfileContainer', + component: MemberProfileContainer, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const mocks = [ + { + request: { + query: SharedMemberProfileContainerMemberSelfProfileDocument, + variables: { communityId: 'community-1' }, + }, + result: { + data: { + memberMyProfile: { + __typename: 'Member', + id: 'member-1', + memberName: 'jane-doe', + createdAt: '2024-01-15T00:00:00.000Z', + updatedAt: '2024-02-15T00:00:00.000Z', + profile: { + __typename: 'MemberProfile', + name: 'Jane Doe', + email: 'jane@example.com', + bio: 'Community member and product designer.', + showInterests: true, + showEmail: true, + showProfile: true, + showLocation: true, + showProperties: false, + }, + }, + }, + }, + }, +]; + +export const SelfProfile: Story = { + args: { + mode: 'self', + }, + decorators: [ + (Story) => ( + + + + } /> + + + + ), + ], +}; diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts deleted file mode 100644 index 11d1ca486..000000000 --- a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { buildMemberProfileSaveVariables } from './member-profile.container.tsx'; - -describe('buildMemberProfileSaveVariables', () => { - it('builds a self-profile mutation payload for community members', () => { - const result = buildMemberProfileSaveVariables({ - mode: 'self', - communityId: 'community-1', - values: { - name: 'Jane Doe', - email: 'jane@example.com', - bio: 'Hello there', - showInterests: true, - showEmail: true, - showProfile: false, - showLocation: true, - showProperties: false, - }, - }); - - expect(result).toEqual({ - variables: { - communityId: 'community-1', - input: { - name: 'Jane Doe', - email: 'jane@example.com', - bio: 'Hello there', - interests: [], - visibility: { - showEmail: true, - showBio: false, - showInterests: true, - showProfile: false, - showLocation: true, - showProperties: false, - }, - }, - }, - }); - }); - - it('builds an admin mutation payload for a specific member', () => { - const result = buildMemberProfileSaveVariables({ - mode: 'admin', - memberObjectId: 'member-1', - values: { - name: 'Jane Doe', - email: 'jane@example.com', - bio: 'Hello there', - showInterests: true, - showEmail: true, - showProfile: false, - showLocation: true, - showProperties: false, - }, - }); - - expect(result).toEqual({ - variables: { - input: { - memberId: 'member-1', - profile: { - name: 'Jane Doe', - email: 'jane@example.com', - bio: 'Hello there', - showInterests: true, - showEmail: true, - showProfile: false, - showLocation: true, - showProperties: false, - }, - }, - }, - }); - }); -}); diff --git a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx b/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx deleted file mode 100644 index d6d148b65..000000000 --- a/packages/ocom/ui-community-shared/src/components/member-profile.container.test.tsx +++ /dev/null @@ -1,138 +0,0 @@ -// @vitest-environment jsdom -import { act } from 'react'; -import { createRoot } from 'react-dom/client'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { MemberProfileContainer } from './member-profile.container.tsx'; - -const { messageMock, useMutationMock, useQueryMock, useParamsMock } = vi.hoisted(() => ({ - messageMock: { - success: vi.fn(), - error: vi.fn(), - }, - useMutationMock: vi.fn(() => [vi.fn(), { loading: false, error: undefined }]), - useQueryMock: vi.fn(() => ({ data: undefined, loading: false, error: undefined })), - useParamsMock: vi.fn(() => ({})), -})); - -vi.mock('antd', () => ({ - App: { - useApp: () => ({ message: messageMock }), - }, -})); - -vi.mock('@apollo/client', () => ({ - useMutation: () => useMutationMock(), - useQuery: () => useQueryMock(), -})); - -vi.mock('react-router-dom', () => ({ - useParams: () => useParamsMock(), -})); - -vi.mock('@cellix/ui-core', () => ({ - ComponentQueryLoader: ({ hasDataComponent }: { hasDataComponent?: React.ReactNode }) => <>{hasDataComponent}, -})); - -vi.mock('./member-profile.tsx', () => ({ - MemberProfile: ({ onSave }: { onSave: (values: Record) => Promise }) => ( - - ), -})); - -describe('MemberProfileContainer', () => { - let container: HTMLDivElement; - let root: ReturnType; - - beforeEach(() => { - vi.clearAllMocks(); - useMutationMock.mockImplementation(() => [vi.fn(), { loading: false, error: undefined }]); - useQueryMock.mockImplementation(() => ({ data: undefined, loading: false, error: undefined })); - useParamsMock.mockImplementation(() => ({})); - container = document.createElement('div'); - document.body.appendChild(container); - root = createRoot(container); - }); - - afterEach(() => { - act(() => { - root.unmount(); - }); - container.remove(); - }); - - function clickSave() { - act(() => { - container.querySelector('button')?.dispatchEvent(new MouseEvent('click', { bubbles: true })); - }); - } - - it('reports success when a self profile save succeeds', () => { - useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); - const selfMutation = vi.fn().mockResolvedValue({ - data: { - memberUpdateMyProfile: { - status: { success: true, errorMessage: undefined }, - }, - }, - }); - useMutationMock.mockImplementationOnce(() => [vi.fn(), { loading: false, error: undefined }]) - .mockImplementationOnce(() => [selfMutation, { loading: false, error: undefined }]); - - act(() => { - root.render(); - }); - clickSave(); - - expect(messageMock.success).toHaveBeenCalledWith('Profile updated'); - }); - - it('reports an error when a self profile save returns a failure status', () => { - useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); - const selfMutation = vi.fn().mockResolvedValue({ - data: { - memberUpdateMyProfile: { - status: { success: false, errorMessage: 'Self save failed' }, - }, - }, - }); - useMutationMock.mockImplementationOnce(() => [vi.fn(), { loading: false, error: undefined }]) - .mockImplementationOnce(() => [selfMutation, { loading: false, error: undefined }]); - - act(() => { - root.render(); - }); - clickSave(); - - expect(messageMock.error).toHaveBeenCalledWith('Self save failed'); - }); - - it('reports when an admin save is attempted without a member id', () => { - useParamsMock.mockImplementation(() => ({ communityId: 'community-1' })); - useMutationMock.mockImplementation(() => [vi.fn(), { loading: false, error: undefined }]); - - act(() => { - root.render(); - }); - clickSave(); - - expect(messageMock.error).toHaveBeenCalledWith('Error updating profile: Cannot read properties of undefined (reading \'data\')'); - }); - - it('reports a thrown save error for admin mode', () => { - useParamsMock.mockImplementation(() => ({ memberId: 'member-1' })); - const profileMutation = vi.fn().mockRejectedValue(new Error('boom')); - useMutationMock.mockImplementationOnce(() => [profileMutation, { loading: false, error: undefined }]) - .mockImplementationOnce(() => [vi.fn(), { loading: false, error: undefined }]); - - act(() => { - root.render(); - }); - clickSave(); - - expect(messageMock.error).toHaveBeenCalledWith('Error updating profile: boom'); - }); -}); From 5b306067cd2803474c6c9abb2f276b1f30f688e4 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Tue, 18 Aug 2026 22:33:47 +0530 Subject: [PATCH 13/14] fixing reviewer comments --- .../member-management.operations.test.ts | 23 +++++++++++++ .../community/member/features/member.feature | 5 +++ .../contexts/community/member/member.test.ts | 26 ++++++++++++++- .../contexts/community/member/member.ts | 2 +- .../features/member.community.visa.feature | 10 ++++++ .../contexts/member.community.passport.ts | 2 +- .../contexts/member.community.visa.test.ts | 32 +++++++++++++++++++ .../member/contexts/member.community.visa.ts | 7 ++-- 8 files changed, 102 insertions(+), 5 deletions(-) diff --git a/packages/ocom/application-services/src/contexts/community/member/member-management.operations.test.ts b/packages/ocom/application-services/src/contexts/community/member/member-management.operations.test.ts index 64f3a4d9d..81b6cf9b0 100644 --- a/packages/ocom/application-services/src/contexts/community/member/member-management.operations.test.ts +++ b/packages/ocom/application-services/src/contexts/community/member/member-management.operations.test.ts @@ -350,6 +350,29 @@ describe('member-management operations', () => { expect(member.memberName).toBe('Jane Doe'); }); + it('throws when a member tries to update another member profile', async () => { + const profile = {} as Record; + Object.defineProperty(profile, 'name', { + get: () => 'Old Name', + set: () => { + throw new Error('You do not have permission to update this profile'); + }, + enumerable: true, + }); + const member = { + memberName: 'Old Name', + profile, + }; + memberRepository.getById.mockResolvedValue(member); + + await expect( + updateMemberProfile(dataSources)({ + memberId: 'member-1', + profile: { name: 'New Name' }, + }), + ).rejects.toThrow('You do not have permission to update this profile'); + }); + it('throws when update member role save returns nothing', async () => { const role = { id: 'role-1', community: { id: 'community-1' } }; const member = { communityId: 'community-1', role: null }; diff --git a/packages/ocom/domain/src/domain/contexts/community/member/features/member.feature b/packages/ocom/domain/src/domain/contexts/community/member/features/member.feature index 6fd619bc8..99593a23b 100644 --- a/packages/ocom/domain/src/domain/contexts/community/member/features/member.feature +++ b/packages/ocom/domain/src/domain/contexts/community/member/features/member.feature @@ -32,6 +32,11 @@ Feature: Member When I set the memberName to "Bob" Then the member's memberName should be "Bob" + Scenario: Changing the memberName with permission to edit own member profile and is editing own member account + Given a Member aggregate with permission to edit own member profile and is editing own member account + When I set the memberName to "Bob" + Then the member's memberName should be "Bob" + Scenario: Changing the memberName without permission Given a Member aggregate without permission to manage members or system account When I try to set the memberName to "Bob" diff --git a/packages/ocom/domain/src/domain/contexts/community/member/member.test.ts b/packages/ocom/domain/src/domain/contexts/community/member/member.test.ts index 95d0fe438..51d67e991 100644 --- a/packages/ocom/domain/src/domain/contexts/community/member/member.test.ts +++ b/packages/ocom/domain/src/domain/contexts/community/member/member.test.ts @@ -18,15 +18,21 @@ const feature = await loadFeature(path.resolve(__dirname, 'features/member.featu function makePassport( overrides: Partial<{ canManageMembers: boolean; + canEditOwnMemberProfile: boolean; + isEditingOwnMemberAccount: boolean; isSystemAccount: boolean; }> = {}, ) { return vi.mocked({ community: { forCommunity: vi.fn(() => ({ - determineIf: (fn: (p: { canManageMembers: boolean; isSystemAccount: boolean }) => boolean) => + determineIf: ( + fn: (p: { canManageMembers: boolean; canEditOwnMemberProfile: boolean; isEditingOwnMemberAccount: boolean; isSystemAccount: boolean }) => boolean, + ) => fn({ canManageMembers: overrides.canManageMembers ?? true, + canEditOwnMemberProfile: overrides.canEditOwnMemberProfile ?? false, + isEditingOwnMemberAccount: overrides.isEditingOwnMemberAccount ?? false, isSystemAccount: overrides.isSystemAccount ?? false, }), })), @@ -216,6 +222,24 @@ test.for(feature, ({ Scenario, Background, BeforeEachScenario }) => { }); }); + Scenario('Changing the memberName with permission to edit own member profile and is editing own member account', ({ Given, When, Then }) => { + Given('a Member aggregate with permission to edit own member profile and is editing own member account', () => { + passport = makePassport({ + canManageMembers: false, + canEditOwnMemberProfile: true, + isEditingOwnMemberAccount: true, + isSystemAccount: false, + }); + member = new Member(makeBaseProps(), passport); + }); + When('I set the memberName to "Bob"', () => { + member.memberName = 'Bob'; + }); + Then('the member\'s memberName should be "Bob"', () => { + expect(member.memberName).toBe('Bob'); + }); + }); + Scenario('Changing the memberName without permission', ({ Given, When, Then }) => { let changeMemberNameWithoutPermission: () => void; Given('a Member aggregate without permission to manage members or system account', () => { diff --git a/packages/ocom/domain/src/domain/contexts/community/member/member.ts b/packages/ocom/domain/src/domain/contexts/community/member/member.ts index 43ceac195..9e792acd5 100644 --- a/packages/ocom/domain/src/domain/contexts/community/member/member.ts +++ b/packages/ocom/domain/src/domain/contexts/community/member/member.ts @@ -203,7 +203,7 @@ export class Member extends AggregateRoot domainPermissions.canManageMembers || domainPermissions.isSystemAccount)) { + if (!this.isNew && !this.visa.determineIf((domainPermissions) => domainPermissions.canManageMembers || domainPermissions.isSystemAccount || (domainPermissions.canEditOwnMemberProfile && domainPermissions.isEditingOwnMemberAccount))) { throw new PermissionError('Cannot set member name'); } this.props.memberName = new ValueObjects.MemberName(memberName).valueOf(); diff --git a/packages/ocom/domain/src/domain/iam/member/contexts/features/member.community.visa.feature b/packages/ocom/domain/src/domain/iam/member/contexts/features/member.community.visa.feature index 8a81be5b1..3cf5d1698 100644 --- a/packages/ocom/domain/src/domain/iam/member/contexts/features/member.community.visa.feature +++ b/packages/ocom/domain/src/domain/iam/member/contexts/features/member.community.visa.feature @@ -39,6 +39,16 @@ Feature: MemberCommunityVisa And I call determineIf with a function that returns canManageCommunitySettings Then the result should be false + Scenario: determineIf sets isEditingOwnMemberAccount to true when the actor has a matching member account + Given a MemberCommunityVisa for the member and community with a matching user account + When I call determineIf with a function that returns isEditingOwnMemberAccount + Then the result should be true + + Scenario: determineIf sets isEditingOwnMemberAccount to false when the actor does not own the member account + Given a MemberCommunityVisa for the member and community with a different user account + When I call determineIf with a function that returns isEditingOwnMemberAccount + Then the result should be false + Scenario: determineIf sets isEditingOwnMemberAccount to false Given a MemberCommunityVisa for the member and community When I call determineIf with a function that returns isEditingOwnMemberAccount diff --git a/packages/ocom/domain/src/domain/iam/member/contexts/member.community.passport.ts b/packages/ocom/domain/src/domain/iam/member/contexts/member.community.passport.ts index 45ba23ce7..5d4ed2469 100644 --- a/packages/ocom/domain/src/domain/iam/member/contexts/member.community.passport.ts +++ b/packages/ocom/domain/src/domain/iam/member/contexts/member.community.passport.ts @@ -6,6 +6,6 @@ import { MemberCommunityVisa } from './member.community.visa.ts'; export class MemberCommunityPassport extends MemberPassportBase implements CommunityPassport { forCommunity(root: CommunityEntityReference): CommunityVisa { - return new MemberCommunityVisa(root, this._member); + return new MemberCommunityVisa(root, this._member, this._user); } } diff --git a/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.test.ts b/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.test.ts index 86e729a4b..65996b60d 100644 --- a/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.test.ts +++ b/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.test.ts @@ -20,10 +20,12 @@ function makeMember( roleOverrides: Partial<{ communityPermissions: Record; }> = {}, + accountUserIds: string[] = [], ) { return { id, community: makeCommunity(communityId), + accounts: accountUserIds.map((userId) => ({ user: { id: userId } })), role: { permissions: { communityPermissions: { @@ -151,6 +153,36 @@ test.for(feature, ({ Scenario, Background, BeforeEachScenario }) => { }); }); + Scenario('determineIf sets isEditingOwnMemberAccount to true when the actor has a matching member account', ({ Given, When, Then }) => { + let result: boolean; + const currentUser = { id: 'user-42' } as any; + Given('a MemberCommunityVisa for the member and community with a matching user account', () => { + member = makeMember('member-1', 'community-1', {}, ['user-42']); + visa = new MemberCommunityVisa(community, member, currentUser); + }); + When('I call determineIf with a function that returns isEditingOwnMemberAccount', () => { + result = visa.determineIf((p) => p.isEditingOwnMemberAccount); + }); + Then('the result should be true', () => { + expect(result).toBe(true); + }); + }); + + Scenario('determineIf sets isEditingOwnMemberAccount to false when the actor does not own the member account', ({ Given, When, Then }) => { + let result: boolean; + const currentUser = { id: 'another-user' } as any; + Given('a MemberCommunityVisa for the member and community with a different user account', () => { + member = makeMember('member-1', 'community-1', {}, ['user-42']); + visa = new MemberCommunityVisa(community, member, currentUser); + }); + When('I call determineIf with a function that returns isEditingOwnMemberAccount', () => { + result = visa.determineIf((p) => p.isEditingOwnMemberAccount); + }); + Then('the result should be false', () => { + expect(result).toBe(false); + }); + }); + Scenario('determineIf sets isEditingOwnMemberAccount to false', ({ Given, When, Then }) => { let result: boolean; Given('a MemberCommunityVisa for the member and community', () => { diff --git a/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.ts b/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.ts index 10072b516..2afaea4af 100644 --- a/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.ts +++ b/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.ts @@ -2,14 +2,17 @@ import type { CommunityEntityReference } from '../../../contexts/community/commu import type { CommunityDomainPermissions } from '../../../contexts/community/community.domain-permissions.ts'; import type { CommunityVisa } from '../../../contexts/community/community.visa.ts'; import type { MemberEntityReference } from '../../../contexts/community/member/member.ts'; +import type { EndUserEntityReference } from '../../../contexts/user/end-user/end-user.ts'; export class MemberCommunityVisa implements CommunityVisa { private readonly root: root; private readonly member: MemberEntityReference; + private readonly user?: EndUserEntityReference; - constructor(root: root, member: MemberEntityReference) { + constructor(root: root, member: MemberEntityReference, user?: EndUserEntityReference) { this.root = root; this.member = member; + this.user = user; } determineIf(func: (permissions: CommunityDomainPermissions) => boolean): boolean { @@ -32,7 +35,7 @@ export class MemberCommunityVisa implemen canEditOwnMemberAccounts: communityPermissions.canEditOwnMemberAccounts, canManageEndUserRolesAndPermissions: communityPermissions.canManageEndUserRolesAndPermissions, canManageSiteContent: communityPermissions.canManageSiteContent, - isEditingOwnMemberAccount: false, + isEditingOwnMemberAccount: Boolean(this.user && this.member.accounts.some((account) => account.user.id === this.user?.id)), canCreateCommunities: true, //TODO: add a more complext rule here like can only create one community for free, otherwise need a paid plan canManageVendorUserRolesAndPermissions: false, // end user roles cannot manage vendor user roles isSystemAccount: false, From d8409a7e9202bfbf13cbd13283ca74986e339593 Mon Sep 17 00:00:00 2001 From: nikithaguduru Date: Wed, 19 Aug 2026 23:48:00 +0530 Subject: [PATCH 14/14] fixing reviewer comments --- .../src/domain/iam/member/contexts/member.community.visa.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.ts b/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.ts index 2afaea4af..71215fba2 100644 --- a/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.ts +++ b/packages/ocom/domain/src/domain/iam/member/contexts/member.community.visa.ts @@ -35,7 +35,9 @@ export class MemberCommunityVisa implemen canEditOwnMemberAccounts: communityPermissions.canEditOwnMemberAccounts, canManageEndUserRolesAndPermissions: communityPermissions.canManageEndUserRolesAndPermissions, canManageSiteContent: communityPermissions.canManageSiteContent, - isEditingOwnMemberAccount: Boolean(this.user && this.member.accounts.some((account) => account.user.id === this.user?.id)), + isEditingOwnMemberAccount: this.user + ? this.member.accounts.some((account) => account.user.id === this.user?.id) + : false, canCreateCommunities: true, //TODO: add a more complext rule here like can only create one community for free, otherwise need a paid plan canManageVendorUserRolesAndPermissions: false, // end user roles cannot manage vendor user roles isSystemAccount: false,