Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
89d77c7
refactor: rename userName to proposedUsername in group metadata
Sotatek-DucPhung Oct 28, 2025
7487df9
feat: add groupUsername to IdentifierMetadataRecord and related services
Sotatek-DucPhung Oct 30, 2025
a41452e
refactor(EditProfile): streamline parameter handling for displayName …
Sotatek-DucPhung Oct 30, 2025
cc1e475
refactor(ui): simplify username handling and remove unused groupMetad…
Sotatek-DucPhung Oct 31, 2025
ced360e
chore: Merge remote-tracking branch 'origin/develop' into feature/VT2…
Sotatek-DucPhung Nov 3, 2025
8e30b6e
chore: fix type error after merge develop
Sotatek-DucPhung Nov 3, 2025
a07c5bd
test: add unit test for updating identifier groupUsername
Sotatek-DucPhung Nov 4, 2025
0ae64e7
refactor(ui): enhance username handling in various components and str…
Sotatek-DucPhung Nov 5, 2025
dee74b2
feat(ui): add setProfileMultisigConnections action and update group c…
Sotatek-DucPhung Nov 5, 2025
d6c5f33
test: enhance group creation handler tests
Sotatek-DucPhung Nov 5, 2025
9c7f9c7
chore: Merge remote-tracking branch 'origin/develop' into feature/VT2…
Sotatek-DucPhung Nov 5, 2025
e979588
feat(ui): update addGroupProfileAsync to fetch and dispatch multisig …
Sotatek-DucPhung Nov 6, 2025
40b0ad2
refactor(core): simplify groupId filtering logic
Sotatek-DucPhung Nov 6, 2025
cbef0a4
chore(ui): remove outdated comment
Sotatek-DucPhung Nov 6, 2025
703448b
refactor(core): streamline profilesCache logic and remove unused setP…
Sotatek-DucPhung Nov 6, 2025
1717223
refactor(core): update connection types to use MultisigConnectionDeta…
Sotatek-DucPhung Nov 6, 2025
4137cc9
test(ui): update ProfileSetup tests to utilize MultisigConnectionDetails
Sotatek-DucPhung Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/store/reducers/profileCache/profilesCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,20 @@ export const profilesCacheSlice = createSlice({
targetProfile.multisigConnections = [...existing, mapped];
},

setProfileMultisigConnections: (
state,
action: PayloadAction<{
profileId: string;
connections: MultisigConnectionDetails[];
}>
) => {
const { profileId, connections } = action.payload;
const targetProfile = state.profiles[profileId];
if (targetProfile) {
targetProfile.multisigConnections = connections;
}
},

// Wallet Connection Actions
setConnectedDApp: (state, action: PayloadAction<DAppConnection | null>) => {
// Store in global state for cross-profile access
Expand Down Expand Up @@ -488,6 +502,7 @@ export const {
updateOrAddConnectionCache,
removeConnectionCache,
updateOrAddMultisigConnectionCache,
setProfileMultisigConnections,
setOpenConnectionId,
setMissingAliasConnection,
setConnectedDApp,
Expand Down
11 changes: 9 additions & 2 deletions src/store/reducers/stateCache/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,16 @@ const filterProfileData = (
const profileConnections = allConnections.filter(
(conn) => conn.identifier === profileId
);

// For gHab (group identifier), get groupId from mHab; for mHab/pending, use own groupMetadata
let groupIdToFilter = profile.groupMetadata?.groupId;
if (profile.groupMemberPre && identifiers[profile.groupMemberPre]) {
groupIdToFilter =
identifiers[profile.groupMemberPre].groupMetadata?.groupId;
}

const profileMultisigConnections = allMultisigConnections.filter(
(conn) =>
"groupId" in conn && conn.groupId === profile.groupMetadata?.groupId
(conn) => "groupId" in conn && conn.groupId === groupIdToFilter
);
const profilePeerConnections = allPeerConnections.filter(
(conn) => conn.selectedAid === profileId
Expand Down
32 changes: 30 additions & 2 deletions src/ui/components/AppWrapper/AppWrapper.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
setConnectedDApp,
setPendingDAppConnection,
addGroupProfileAsync,
setProfileMultisigConnections,
} from "../../../store/reducers/profileCache";
import {
setQueueIncomingRequest,
Expand All @@ -50,6 +51,7 @@ import { IncomingRequestType } from "../../../store/reducers/stateCache/stateCac
import {
pendingGroupIdentifierFix,
pendingIdentifierFix,
pendingMemberIdentifierFix,
} from "../../__fixtures__/filteredIdentifierFix";
import { ToastMsgType } from "../../globals/types";
import {
Expand Down Expand Up @@ -97,6 +99,7 @@ jest.mock("../../../core/agent/agent", () => {
},
identifiers: {
getIdentifiers: jest.fn().mockResolvedValue([]),
getIdentifier: jest.fn().mockResolvedValue(null),
syncKeriaIdentifiers: jest.fn(),
onIdentifierAdded: jest.fn(),
getAvailableWitnesses: jest.fn(),
Expand Down Expand Up @@ -515,14 +518,39 @@ describe("Group state changed handler", () => {
const innerDispatch = jest.fn();
const getState = jest.fn(() => ({ profilesCache: { recentProfiles: [] } }));

dispatch.mockImplementation((func) => {
func(innerDispatch, getState);
dispatch.mockImplementation((action) => {
// Handle both thunk functions and plain actions
if (typeof action === "function") {
action(innerDispatch, getState);
} else {
innerDispatch(action);
}
});

// Mock getIdentifier to return mHab with groupMetadata
const mHabWithMetadata = pendingMemberIdentifierFix[0];
Agent.agent.identifiers.getIdentifier = jest
.fn()
.mockResolvedValue(mHabWithMetadata);

// Mock getMultisigConnections to return empty array
Agent.agent.connections.getMultisigConnections = jest
.fn()
.mockResolvedValue([]);

await groupCreatedHandler(groupCreatedEvent, dispatch);
expect(innerDispatch).toBeCalledWith(
addGroupProfile(pendingGroupIdentifierFix)
);
expect(Agent.agent.identifiers.getIdentifier).toBeCalledWith(
pendingGroupIdentifierFix.groupMemberPre
);
expect(dispatch).toBeCalledWith(
setProfileMultisigConnections({
profileId: pendingGroupIdentifierFix.id,
connections: [],
})
);
});
});

Expand Down
4 changes: 3 additions & 1 deletion src/ui/components/AppWrapper/AppWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ const AppWrapper = (props: { children: ReactNode }) => {
true
);
const storedIdentifiers = await Agent.agent.identifiers.getIdentifiers();
const allIdentifiersIncludingMember =
await Agent.agent.identifiers.getIdentifiers(false);
const storedPeerConnections =
await Agent.agent.peerConnectionPair.getAllPeerConnectionAccount();

Expand All @@ -397,7 +399,7 @@ const AppWrapper = (props: { children: ReactNode }) => {
dispatch(updateRecentProfiles(profileHistories));
}

const identifiersDict = storedIdentifiers.reduce(
const identifiersDict = allIdentifiersIncludingMember.reduce(
(acc: Record<string, IdentifierShortDetails>, identifier) => {
acc[identifier.id] = identifier;
return acc;
Expand Down
28 changes: 27 additions & 1 deletion src/ui/components/AppWrapper/coreEventListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Agent } from "../../../core/agent/agent";
import {
CreationStatus,
isRegularConnectionDetails,
MultisigConnectionDetails,
} from "../../../core/agent/agent.types";
import {
EventTypes,
Expand All @@ -17,6 +18,7 @@ import {
addNotification,
addOrUpdateProfileIdentity,
deleteNotificationById,
setProfileMultisigConnections,
updateOrAddConnectionCache,
updateProfileCreationStatus,
} from "../../../store/reducers/profileCache";
Expand Down Expand Up @@ -97,7 +99,31 @@ const groupCreatedHandler = async (
event: GroupCreatedEvent,
dispatch: ReturnType<typeof useAppDispatch>
) => {
dispatch(addGroupProfileAsync(event.payload.group));
await dispatch(addGroupProfileAsync(event.payload.group));

// Refresh multisig connections cache after group is created
// This ensures member names are displayed correctly without page refresh
const allConnections = await Agent.agent.connections.getMultisigConnections();
const multisigConnections = allConnections as MultisigConnectionDetails[];

const mHabId = event.payload.group.groupMemberPre;
if (mHabId) {
const mHab = await Agent.agent.identifiers.getIdentifier(mHabId);
const groupId = mHab?.groupMetadata?.groupId;

if (groupId) {
const groupConnections = multisigConnections.filter(
(conn) => conn.groupId === groupId
);

dispatch(
setProfileMultisigConnections({
profileId: event.payload.group.id,
connections: groupConnections,
})
);
}
}
};

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,19 @@ const IdentifierAttributeDetailModal = ({
const memberConnection = multisignConnectionsCache.find(
(c) => c.id === member
);
let name = memberConnection?.label || member;
const isCurrent = member === data.groupMemberPre;
const displayNameCandidate = isCurrent
? data.groupUsername || data.groupMetadata?.proposedUsername || ""
: member;

if (!memberConnection?.label) {
currentUserIndex = index;
name = data.groupUsername || data.groupMetadata?.proposedUsername || "";
}
const name = memberConnection?.label || displayNameCandidate;
if (isCurrent) currentUserIndex = index;

const rank = index >= 0 ? index % 5 : 0;

return {
name: name,
isCurrentUser: !memberConnection?.label,
name,
isCurrentUser: isCurrent,
avatar: (
<MemberAvatar
firstLetter={name.at(0)?.toLocaleUpperCase() || ""}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ const ProfileContent = ({
const memberConnection = multisignConnectionsCache.find(
(c) => c.id === member
);
let name = memberConnection?.label || member;

if (!memberConnection?.label) {
name =
cardData.groupUsername ||
cardData.groupMetadata?.proposedUsername ||
"";
}
const isCurrent = member === cardData.groupMemberPre;
const name =
memberConnection?.label ||
(isCurrent
? cardData.groupUsername ||
cardData.groupMetadata?.proposedUsername ||
""
: member);

const rank = index >= 0 ? index % 5 : 0;

Expand All @@ -112,7 +112,7 @@ const ProfileContent = ({
rank={rank}
/>
),
isCurrentUser: !memberConnection?.label,
isCurrentUser: isCurrent,
status: MemberAcceptStatus.None,
};
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ const PendingGroup = ({ state, isPendingGroup }: StageProps) => {
null
);

const isMember = !identity?.groupMetadata?.groupInitiator;
const isPendingMember = isMember && initGroupNotification;
const isPendingMember = !!initGroupNotification;

const rotationThreshold = isPendingMember
? multisigIcpDetails?.rotationThreshold
Expand Down Expand Up @@ -105,7 +104,10 @@ const PendingGroup = ({ state, isPendingGroup }: StageProps) => {
});

members.unshift({
name: identity?.groupMetadata?.proposedUsername || "",
name:
identity?.groupUsername ||
identity?.groupMetadata?.proposedUsername ||
"",
isCurrentUser: true,
status: groupDetails?.members.find(
(item) => item.aid === identity?.groupMemberPre
Expand All @@ -114,7 +116,7 @@ const PendingGroup = ({ state, isPendingGroup }: StageProps) => {
: MemberAcceptStatus.Waiting,
});

return members.map((member, index) => ({
const mapped = members.map((member, index) => ({
...member,
avatar: (
<MemberAvatar
Expand All @@ -123,6 +125,7 @@ const PendingGroup = ({ state, isPendingGroup }: StageProps) => {
/>
),
}));
return mapped;
}, [
state.selectedConnections,
identity?.groupMetadata?.proposedUsername,
Expand Down Expand Up @@ -175,28 +178,27 @@ const PendingGroup = ({ state, isPendingGroup }: StageProps) => {
};

const getInceptionStatus = useCallback(async () => {
if (!identity?.id || !identity?.groupMetadata) return;
// We can fetch inception status using gHab id even if groupMetadata is not stored on gHab
if (!identity?.id) return;

try {
setLoading(true);
const details = await Agent.agent.multiSigs.getInceptionStatus(
identity.id
);

setGroupDetails(details);
} catch (e) {
showError("Unable to load group: ", e, dispatch);
} finally {
setLoading(false);
}
}, [dispatch, identity?.groupMetadata, identity?.id]);
}, [dispatch, identity?.id]);

const fetchMultisigDetails = useCallback(async () => {
if (!initGroupNotification) return;
const details = await Agent.agent.multiSigs.getMultisigIcpDetails(
initGroupNotification.a.d as string
);

setMultisigIcpDetails(details);
}, [initGroupNotification]);

Expand All @@ -207,7 +209,6 @@ const PendingGroup = ({ state, isPendingGroup }: StageProps) => {
await fetchMultisigDetails();
return;
}

await getInceptionStatus();
}, [
fetchMultisigDetails,
Expand Down Expand Up @@ -262,7 +263,10 @@ const PendingGroup = ({ state, isPendingGroup }: StageProps) => {
customClass="pending-group"
header={
<PageHeader
title={identity?.groupMetadata?.proposedUsername}
title={
identity?.groupUsername ||
identity?.groupMetadata?.proposedUsername
}
additionalButtons={
identity?.id && (
<Avatar
Expand Down