Skip to content

Conversation

@aleksandernsilva
Copy link
Contributor

@aleksandernsilva aleksandernsilva commented Dec 18, 2025

Proposed changes

This pull request introduces support for handling room invitations, including displaying invitation details to users and allowing them to accept or reject invites.

Invitation Handling and UI Integration:

  • Added a new InvitedRoom React component to display invitation information and provide accept/reject actions in the room view.
  • Updated the RoomView logic to detect invite subscriptions, render the invitation UI, and initialize the room only after the invitation is accepted.

Data Model and Schema Updates:

  • Extended the ISubscription interface with new status and inviter fields, and introduced IInviteSubscription and a type guard function for invite subscriptions.
  • Updated the database schema and migration logic to persist status and inviter fields in the subscriptions table.
  • Ensured merging logic includes the new fields when combining subscription and room data.

Room State Management:

  • Updated the list of room attributes to track and update with the new status and inviter fields. (app/views/RoomView/constants.ts)

Issue(s)

FB-157

How to test or reproduce

  • Access a federated workspace (A) (native-fed-1, native-fed-2 or local)
  • Access federated workspace (B) on mobile
  • Create a federated channel or DM and invite your user (A)
    • Create channel > Federated Toggle
  • New channel/dm should be listed (B)
  • Accessing the channel/dm should display invited state
  • Accepting the invite should grant access to the room messages
  • Rejecting the invite should remove the user from the room

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • New Features

    • Invitation support for channels and direct messages with a dedicated invitation view showing inviter, title and description.
    • Accept and reject actions for invites; invited rooms skip normal loading and disable header controls while pending.
    • Invitation indicator shown on room list items and Storybook previews.
  • Localization

    • Added invitation-related messages and an error message for invitation-reply failures across locales.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds invite support: subscription types gain optional status and inviter; DB schema and migration bumped; new REST endpoint and client for invite replies; new invite UI and RoomView integration with helpers/type-guards; room list invited indicator; i18n keys for invitation flows.

Changes

Cohort / File(s) Summary
Type definitions
app/definitions/ISubscription.ts
Added optional inviter to ISubscription; added IInviteSubscription extending ISubscription with status: 'INVITED' and non-null inviter.
Database schema & migrations
app/lib/database/schema/app.js, app/lib/database/model/migrations.js
Bumped schema version to 28; added optional status and inviter columns to subscriptions; added migration to add these columns.
Model serialization
app/lib/database/model/Subscription.js
Added status field and inviter json field; included them in asPlain() output.
REST endpoint & client
app/definitions/rest/v1/rooms.ts, app/lib/services/restApi.ts
Declared rooms.invite POST endpoint (params `{ roomId, action: 'accept'
Business logic / helpers
app/lib/methods/replyRoomInvite.ts, app/lib/methods/getInvitationData.ts, app/lib/methods/isInviteSubscription.ts, app/lib/methods/helpers/mergeSubscriptionsRooms.ts
Added replyRoomInvite (calls API, shows i18n error on failure); getInvitationData builds invite UI payload with accept/reject callbacks; isInviteSubscription type guard; ensure merged subscriptions include status and inviter (explicit undefined when absent).
UI — Invite view & integration
app/views/RoomView/components/InvitedRoom.tsx, app/views/RoomView/index.tsx, app/views/RoomView/RightButtons.tsx, app/views/RoomView/constants.ts
New InvitedRoom component; RoomView updated to detect invite subscriptions, short-circuit loading/threads, disable header/right buttons for INVITED status, and render InvitedRoom; added 'status' and 'inviter' to roomAttrsUpdate.
Room list UI
app/containers/RoomItem/RoomItem.tsx, app/containers/RoomItem/index.tsx, app/containers/RoomItem/interfaces.ts
Added isInvited prop and invited indicator icon in room list; container passes isInvited using isInviteSubscription.
UI primitives & stories
app/containers/Chip/index.tsx, app/containers/Chip/Chip.stories.tsx
Added fullWidth?: boolean to IChip; Chip honors fullWidth (removes maxWidth) and story updated.
Localization
app/i18n/locales/* (many locales, e.g., app/i18n/locales/en.json)
Added invite-related keys across locales: error-invitation-reply-action, Invited, invited_room_description_*, invited_room_title_*, reject.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant InvitedRoom as InvitedRoom UI
    participant getInvitationData as getInvitationData
    participant replyRoomInvite as replyRoomInvite
    participant restApi as restApi
    participant Server as Backend

    User->>InvitedRoom: Tap Accept / Reject
    InvitedRoom->>getInvitationData: invoke action callback
    getInvitationData->>replyRoomInvite: replyRoomInvite(roomId, action)
    replyRoomInvite->>restApi: sendInvitationReply(roomId, action)
    restApi->>Server: POST rooms.invite { roomId, action }
    alt Success
        Server-->>restApi: 2xx
        restApi-->>replyRoomInvite: resolve
        replyRoomInvite-->>InvitedRoom: success (UI updates)
    else Error
        Server-->>restApi: error
        restApi-->>replyRoomInvite: throw
        replyRoomInvite-->>InvitedRoom: show i18n error alert
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • OtavioStasiak

Poem

🐰
I hopped through code to add a name and state,
An inviter waits with buttons to relate,
Schema bumped, migration made the leap,
Invite view blooms where threads were once deep,
A little rabbit cheers — the app wakes from sleep!

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Most changes are in-scope; however, the Chip component modifications (fullWidth prop) appear unrelated to the room invited state feature and should be reviewed as they may constitute out-of-scope changes. Clarify whether Chip fullWidth changes in app/containers/Chip/index.tsx and Chip.stories.tsx are intentional dependencies for the invited state feature or accidental inclusions.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Room Invited State' clearly and concisely summarizes the main feature: adding invited room state support. It accurately reflects the primary changes across UI, data model, and room state management.
Linked Issues check ✅ Passed The pull request implements room invited state functionality including UI components, data model extensions, database persistence, and state management, which aligns with the FB-157 issue objective to implement room invited state for mobile.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 54b7a6a and ea3312c.

📒 Files selected for processing (24)
  • app/i18n/locales/ar.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/cs.json
  • app/i18n/locales/de.json
  • app/i18n/locales/es.json
  • app/i18n/locales/fi.json
  • app/i18n/locales/fr.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/hu.json
  • app/i18n/locales/it.json
  • app/i18n/locales/ja.json
  • app/i18n/locales/nl.json
  • app/i18n/locales/nn.json
  • app/i18n/locales/no.json
  • app/i18n/locales/pt-BR.json
  • app/i18n/locales/pt-PT.json
  • app/i18n/locales/ru.json
  • app/i18n/locales/sl-SI.json
  • app/i18n/locales/sv.json
  • app/i18n/locales/ta-IN.json
  • app/i18n/locales/te-IN.json
  • app/i18n/locales/tr.json
  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/zh-TW.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@aleksandernsilva
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/views/RoomView/constants.ts (1)

39-50: Critical: Duplicate 'status' entry in roomAttrsUpdate array.

The status field appears twice in the roomAttrsUpdate array (line 39 and line 48). This duplication could cause unexpected behavior in room attribute updates.

🔎 Apply this diff to remove the duplicate:
 	'teamMain',
 	'teamId',
-	'status',
 	'lastMessage',
 	'onHold',
 	't',
 	'autoTranslate',
 	'autoTranslateLanguage',
 	'unmuted',
 	'E2EKey',
 	'encrypted',
 	'status',
 	'inviter'
🧹 Nitpick comments (6)
app/lib/methods/helpers/mergeSubscriptionsRooms.ts (1)

17-17: Consider refactoring instead of suppressing the complexity warning.

Suppressing the complexity warning without addressing the underlying issue is a code smell. The merge function is over 100 lines with multiple conditional branches. Consider extracting logical groups (e.g., room property merging, field normalization) into smaller helper functions to improve maintainability and testability.

app/views/RoomView/components/InvitedRoom.tsx (5)

28-29: Title truncation may break mid-word or mid-emoji.

The roomName.slice(0, 30) truncation could cut a word or multi-byte character in the middle. Consider using a more robust truncation that respects word boundaries or uses numberOfLines on the Text component instead.

🔎 Suggested approach
-	const title =
-		room.t === 'd' ? I18n.t('invited_room_title_dm') : I18n.t('invited_room_title_channel', { room_name: roomName.slice(0, 30) });
+	const truncatedRoomName = roomName.length > 30 ? `${roomName.slice(0, 27)}...` : roomName;
+	const title =
+		room.t === 'd' ? I18n.t('invited_room_title_dm') : I18n.t('invited_room_title_channel', { room_name: truncatedRoomName });

33-40: Loading state remains stuck if navigation happens after accept/reject.

If replyRoomInvite succeeds and the user navigates away (e.g., room view changes on accept), setLoading(false) still executes on an unmounted component, causing a React warning. Consider checking if the component is still mounted or using an abort pattern.

🔎 Suggested fix using mounted ref
+import React, { useState, useRef, useEffect, type ReactElement } from 'react';
...
 export const InvitedRoom = ({ room }: InvitedRoomProps): ReactElement => {
 	const { colors } = useTheme();
 	const styles = useStyle();
 	const [loading, setLoading] = useState(false);
+	const isMounted = useRef(true);
+
+	useEffect(() => {
+		return () => {
+			isMounted.current = false;
+		};
+	}, []);

 	const { rid, inviter } = room;
 	const roomName = getRoomTitle(room);
...
 	const handleReply = async (action: 'accept' | 'reject') => {
 		try {
 			setLoading(true);
 			await replyRoomInvite(rid, action);
 		} finally {
-			setLoading(false);
+			if (isMounted.current) {
+				setLoading(false);
+			}
 		}
 	};

51-53: Nesting Chip inside Text may cause layout issues on some platforms.

Wrapping a component like Chip (which likely contains its own View) inside a Text component can cause unexpected behavior in React Native. Consider using a View container instead.

🔎 Suggested fix
-				<Text style={styles.username}>
+				<View style={styles.username}>
 					<Chip avatar={inviter.username} text={inviter.name || inviter.username} />
-				</Text>
+				</View>

55-62: Both buttons share the same loading state, preventing user from switching action.

When the user taps Accept, both buttons show loading. If the request takes time or fails, the user cannot tap Reject (and vice versa). Consider disabling only the non-pressed button or showing loading only on the pressed one.


68-115: StyleSheet is recreated on every render.

useStyle calls StyleSheet.create on each render. While not a major performance issue, extracting static styles outside the component or memoizing with useMemo would be more efficient.

🔎 Suggested approach using useMemo
+import React, { useState, useMemo, type ReactElement } from 'react';
...
 const useStyle = () => {
 	const { colors } = useTheme();
-	const styles = StyleSheet.create({
+	const styles = useMemo(() => StyleSheet.create({
 		root: {
 			flex: 1,
 			backgroundColor: colors.surfaceRoom
 		},
 		// ... rest of styles
-	});
+	}), [colors]);
 	return styles;
 };
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c1f9ca and 7ea0ad1.

📒 Files selected for processing (12)
  • app/definitions/ISubscription.ts (2 hunks)
  • app/definitions/rest/v1/rooms.ts (1 hunks)
  • app/i18n/locales/en.json (3 hunks)
  • app/lib/database/model/Subscription.js (2 hunks)
  • app/lib/database/model/migrations.js (1 hunks)
  • app/lib/database/schema/app.js (2 hunks)
  • app/lib/methods/helpers/mergeSubscriptionsRooms.ts (2 hunks)
  • app/lib/methods/replyRoomInvite.ts (1 hunks)
  • app/lib/services/restApi.ts (1 hunks)
  • app/views/RoomView/components/InvitedRoom.tsx (1 hunks)
  • app/views/RoomView/constants.ts (1 hunks)
  • app/views/RoomView/index.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
app/views/RoomView/components/InvitedRoom.tsx (4)
app/definitions/ISubscription.ts (1)
  • IInviteSubscription (122-125)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/methods/helpers/helpers.ts (1)
  • getRoomTitle (43-60)
app/lib/methods/replyRoomInvite.ts (1)
  • replyRoomInvite (6-13)
app/lib/methods/replyRoomInvite.ts (2)
app/lib/services/restApi.ts (1)
  • sendInvitationReply (1103-1103)
app/lib/methods/helpers/info.ts (1)
  • showErrorAlert (5-6)
app/lib/database/model/Subscription.js (1)
app/lib/database/utils.ts (1)
  • sanitizer (19-19)
app/views/RoomView/index.tsx (2)
app/definitions/ISubscription.ts (1)
  • isInviteSubscription (191-192)
app/views/RoomView/components/InvitedRoom.tsx (1)
  • InvitedRoom (20-66)
app/definitions/ISubscription.ts (1)
app/definitions/IUser.ts (1)
  • IUser (127-151)
🔇 Additional comments (17)
app/lib/methods/helpers/mergeSubscriptionsRooms.ts (1)

116-117: LGTM: Proper normalization of invitation fields.

The explicit normalization of status and inviter fields using the nullish coalescing operator is correct and consistent with the surrounding code style (similar to blocker, blocked, and hideMentionStatus normalization on lines 118-120). This ensures these optional fields are explicitly set to undefined when not present, maintaining consistency across the subscription object.

app/definitions/rest/v1/rooms.ts (1)

57-59: LGTM!

The new rooms.invite endpoint definition is well-structured with type-safe action constraints and consistent parameter naming.

app/lib/services/restApi.ts (1)

1103-1103: LGTM!

The sendInvitationReply function is correctly implemented and matches the REST endpoint definition in app/definitions/rest/v1/rooms.ts.

app/lib/database/model/Subscription.js (2)

156-158: LGTM!

The new status and inviter fields are correctly decorated and follow existing patterns in the model. The use of @json for inviter is appropriate given it stores a user object.


226-228: LGTM!

The asPlain() method correctly includes the new status and inviter fields in the output.

app/lib/database/model/migrations.js (1)

335-346: LGTM!

The migration to version 28 correctly adds the status and inviter columns to the subscriptions table, following the established migration pattern and aligning with the schema changes.

app/definitions/ISubscription.ts (3)

122-125: LGTM!

The IInviteSubscription interface correctly extends ISubscription and narrows the types appropriately for invite-specific subscriptions.


191-192: LGTM!

The isInviteSubscription type guard correctly validates both the status and presence of inviter, providing proper type narrowing for TypeScript.


119-120: All referenced fields exist in IUser interface.

IUser contains _id (required string), username (required string via ILoggedUser), and name (optional string via ILoggedUser). The inviter field is correctly typed.

app/lib/database/schema/app.js (2)

4-4: LGTM!

Schema version correctly bumped to 28 to reflect the addition of new columns.


73-76: LGTM!

The new status and inviter columns are correctly defined as optional strings, aligning with the migration in app/lib/database/model/migrations.js.

app/i18n/locales/en.json (3)

420-423: LGTM!

The new localization strings follow the existing naming conventions and are grammatically correct. The invited_room_* keys properly support both channel and DM invitation scenarios with appropriate placeholders.


340-340: LGTM!

The error key follows the existing error-* naming pattern and provides a clear, user-friendly message.


692-692: LGTM!

The "reject" key maintains consistency with the "accept" key (line 15), both using sentence case capitalization.

app/views/RoomView/index.tsx (3)

69-71: LGTM!

The imports are properly structured, adding RoomType and isInviteSubscription type guard which are used correctly in the component logic.


647-651: LGTM!

The early return for invite subscriptions correctly bypasses message loading and member fetching, which would be unnecessary and potentially cause errors for unaccepted invitations. The type guard isInviteSubscription ensures type safety.


333-337: The existing guard already prevents unnecessary calls on every render.

The condition prevState.roomUpdate.status === 'INVITED' && roomUpdate.status !== 'INVITED' is a proper safeguard that only triggers when transitioning out of the INVITED status. Each legitimate transition would call init() once. Multiple calls would only occur if the status oscillates between INVITED and non-INVITED states—an unlikely scenario in normal subscription sync. If this oscillation is a genuine concern, consider tracking initialization with a flag to prevent re-initializing on redundant transitions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/views/RoomView/components/InvitedRoom.tsx (1)

53-99: StyleSheet.create is called on every render, creating unnecessary overhead.

StyleSheet.create should be called once outside the component. Although the styles depend on theme colors, consider extracting static styles and only computing dynamic color values within the hook.

🔎 Proposed refactor
+const staticStyles = StyleSheet.create({
+	root: {
+		flex: 1
+	},
+	container: {
+		flex: 1,
+		marginHorizontal: 24,
+		justifyContent: 'center'
+	},
+	textView: { alignItems: 'center' },
+	icon: {
+		width: 58,
+		height: 58,
+		borderRadius: 30,
+		marginBottom: GAP,
+		alignItems: 'center',
+		justifyContent: 'center'
+	},
+	title: {
+		...sharedStyles.textBold,
+		fontSize: 24,
+		lineHeight: 32,
+		textAlign: 'center',
+		marginBottom: GAP
+	},
+	description: {
+		...sharedStyles.textRegular,
+		fontSize: 16,
+		lineHeight: 24,
+		textAlign: 'center'
+	},
+	username: {
+		...sharedStyles.textRegular,
+		fontSize: 16,
+		lineHeight: 24,
+		textAlign: 'center',
+		marginBottom: GAP
+	}
+});
+
 const useStyle = () => {
 	const { colors } = useTheme();
-	const styles = StyleSheet.create({
-		root: {
-			flex: 1,
-			backgroundColor: colors.surfaceRoom
-		},
-		// ... all styles
-	});
-	return styles;
+	return {
+		...staticStyles,
+		root: [staticStyles.root, { backgroundColor: colors.surfaceRoom }],
+		icon: [staticStyles.icon, { backgroundColor: colors.surfaceNeutral }],
+		title: [staticStyles.title, { color: colors.fontTitlesLabels }],
+		description: [staticStyles.description, { color: colors.fontDefault }],
+		username: [staticStyles.username, { color: colors.fontDefault }]
+	};
 };
app/lib/methods/getInvitationData.ts (1)

10-10: Consider adding ellipsis for truncated room names.

The room name is truncated at 30 characters without indication. Long names may appear cut off abruptly.

🔎 Proposed improvement
+const truncateWithEllipsis = (text: string, maxLength: number) => {
+	if (text.length <= maxLength) return text;
+	return `${text.slice(0, maxLength - 1)}…`;
+};
+
 export const getInvitationData = (room: IInviteSubscription) => {
 	const title =
 		room.t === 'd'
 			? I18n.t('invited_room_title_dm')
-			: I18n.t('invited_room_title_channel', { room_name: getRoomTitle(room).slice(0, 30) });
+			: I18n.t('invited_room_title_channel', { room_name: truncateWithEllipsis(getRoomTitle(room), 30) });
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7ea0ad1 and 20802e7.

📒 Files selected for processing (4)
  • app/lib/methods/getInvitationData.ts
  • app/views/RoomView/RightButtons.tsx
  • app/views/RoomView/components/InvitedRoom.tsx
  • app/views/RoomView/index.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
app/views/RoomView/components/InvitedRoom.tsx (4)
app/definitions/ISubscription.ts (1)
  • IInviteSubscription (122-125)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/containers/message/Components/Attachments/Image/Button.tsx (1)
  • Button (13-26)
app/views/RoomView/index.tsx (3)
app/definitions/ISubscription.ts (1)
  • isInviteSubscription (191-192)
app/lib/methods/getInvitationData.ts (1)
  • getInvitationData (6-21)
app/views/RoomView/components/InvitedRoom.tsx (1)
  • InvitedRoom (23-51)
app/lib/methods/getInvitationData.ts (3)
app/definitions/ISubscription.ts (1)
  • IInviteSubscription (122-125)
app/lib/methods/helpers/helpers.ts (1)
  • getRoomTitle (43-60)
app/lib/methods/replyRoomInvite.ts (1)
  • replyRoomInvite (6-13)
🔇 Additional comments (6)
app/views/RoomView/RightButtons.tsx (1)

482-484: LGTM! The early return for invited rooms correctly hides header actions.

Minor note: Consider using a shared constant for 'INVITED' to stay consistent with isInviteSubscription usage elsewhere in the codebase, but this works correctly as-is.

app/views/RoomView/index.tsx (4)

334-338: LGTM! Correctly triggers initialization when an invitation is accepted, ensuring room data loads after the status transition.


649-653: LGTM! Appropriately skips message loading for invite subscriptions since there's no room content to display yet.


546-547: LGTM! Disabling the room header for invite subscriptions prevents navigation to room actions before acceptance.


1581-1589: SafeAreaView wrapping correctly addresses the safe area concern from previous review.

Note: The loading prop is not passed to InvitedRoom, so users won't see loading feedback during accept/reject. See the related comment on InvitedRoom.tsx for a fix suggestion.

app/lib/methods/getInvitationData.ts (1)

18-19: Use room.rid instead of room.id for the API call.

While both properties exist on ISubscription and hold the same value, the replyRoomInvite function signature explicitly expects rid: string as its first parameter. The interface comment states "the same as id" for the rid property, and Rocket.Chat conventions use rid as the standard room identifier. Update lines 18–19 to use room.rid for semantic consistency:

accept: () => replyRoomInvite(room.rid, 'accept'),
reject: () => replyRoomInvite(room.rid, 'reject')

Comment on lines 23 to 51
export const InvitedRoom = ({ title, description, inviter, loading, onAccept, onReject }: InvitedRoomProps): ReactElement => {
const { colors } = useTheme();
const styles = useStyle();

return (
<View style={styles.root}>
<View style={styles.container}>
<View style={styles.textView}>
<View style={styles.icon}>
<CustomIcon name='mail' size={42} color={colors.fontSecondaryInfo} />
</View>
<Text style={styles.title}>{title}</Text>
<Text style={styles.description}>{description}</Text>
<View style={styles.username}>
<Chip avatar={inviter.username} text={inviter.name || inviter.username} />
</View>
</View>
<Button title={I18n.t('accept')} loading={loading} onPress={onAccept} />
<Button
title={I18n.t('reject')}
type='secondary'
loading={loading}
backgroundColor={colors.surfaceTint}
onPress={onReject}
/>
</View>
</View>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Loading state is not wired up in the parent component.

The loading prop exists but is never passed from RoomView/index.tsx (line 1586). Users won't see feedback while accept/reject actions are in progress. Consider either:

  1. Managing loading state in the parent and passing it down
  2. Managing loading state internally in this component
🔎 Example: internal loading state
-export const InvitedRoom = ({ title, description, inviter, loading, onAccept, onReject }: InvitedRoomProps): ReactElement => {
+export const InvitedRoom = ({ title, description, inviter, onAccept, onReject }: InvitedRoomProps): ReactElement => {
 	const { colors } = useTheme();
 	const styles = useStyle();
+	const [loading, setLoading] = React.useState(false);
+
+	const handleAccept = async () => {
+		setLoading(true);
+		try {
+			await onAccept();
+		} finally {
+			setLoading(false);
+		}
+	};
+
+	const handleReject = async () => {
+		setLoading(true);
+		try {
+			await onReject();
+		} finally {
+			setLoading(false);
+		}
+	};
 
 	return (
 		// ...
-			<Button title={I18n.t('accept')} loading={loading} onPress={onAccept} />
+			<Button title={I18n.t('accept')} loading={loading} onPress={handleAccept} />
 			<Button
 				title={I18n.t('reject')}
 				type='secondary'
 				loading={loading}
 				backgroundColor={colors.surfaceTint}
-				onPress={onReject}
+				onPress={handleReject}
 			/>
 		// ...
 	);
 };
🤖 Prompt for AI Agents
In app/views/RoomView/components/InvitedRoom.tsx around lines 23-51: the
component accepts a loading prop but the parent (RoomView/index.tsx at ~line
1586) never passes it, so users get no feedback during accept/reject operations;
fix by either A) add loading state in the parent and pass it down: create a
boolean state (e.g., isInvitedActionLoading), set it true before awaiting the
accept/reject async call and false afterwards (handle errors and finally block),
then pass loading={isInvitedActionLoading} into <InvitedRoom>, or B) manage
loading internally in InvitedRoom: add a local useState loading flag, wrap
onAccept/onReject handlers to setLoading(true) before awaiting the provided
callback and setLoading(false) in finally, and use that state for the Button
loading prop; ensure callbacks remain async-safe and errors are handled.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c9f7a5e and a36ea13.

📒 Files selected for processing (4)
  • app/containers/RoomItem/RoomItem.tsx
  • app/containers/RoomItem/index.tsx
  • app/containers/RoomItem/interfaces.ts
  • app/i18n/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/i18n/locales/en.json
🧰 Additional context used
🧬 Code graph analysis (2)
app/containers/RoomItem/index.tsx (1)
app/lib/methods/isInviteSubscription.ts (1)
  • isInviteSubscription (3-4)
app/containers/RoomItem/RoomItem.tsx (3)
app/containers/RoomItem/interfaces.ts (1)
  • IRoomItemProps (101-128)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/constants/colors.ts (1)
  • colors (280-302)
🔇 Additional comments (5)
app/containers/RoomItem/interfaces.ts (1)

110-110: LGTM!

The addition of the optional isInvited boolean property to IRoomItemProps is clean and appropriate for tracking invitation state.

app/containers/RoomItem/RoomItem.tsx (2)

19-20: LGTM!

The imports for CustomIcon and useTheme are appropriate for rendering the invited state indicator.


58-59: LGTM!

The addition of the isInvited prop and the use of useTheme to access theme colors are correctly implemented.

Also applies to: 63-63

app/containers/RoomItem/index.tsx (2)

8-8: LGTM!

The import of isInviteSubscription is appropriate for determining invitation status.


65-65: LGTM!

The isInvited prop is correctly computed using the isInviteSubscription type guard and passed to the RoomItem component.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/views/RoomView/components/InvitedRoom.tsx (1)

23-51: Loading state is not wired up in the parent component.

The loading prop exists but is never passed from RoomView/index.tsx (line 1586). Users won't see feedback while accept/reject actions are in progress. This issue was previously flagged and remains unresolved.

🧹 Nitpick comments (2)
app/views/RoomView/components/InvitedRoom.tsx (2)

32-46: Add accessibility labels for better screen reader support.

The icon and buttons lack accessibility labels, which impacts users relying on screen readers. Consider adding:

  • accessibilityLabel to the CustomIcon (line 32)
  • accessibilityLabel or accessibilityHint to both Button components (lines 40, 41-46)
🔎 Suggested accessibility improvements
 <CustomIcon 
   name='mail' 
   size={42} 
   color={colors.fontSecondaryInfo}
+  accessibilityLabel={I18n.t('invitation_icon')}
 />
-<Button title={I18n.t('accept')} loading={loading} onPress={onAccept} />
+<Button 
+  title={I18n.t('accept')} 
+  loading={loading} 
+  onPress={onAccept}
+  accessibilityHint={I18n.t('accept_invitation_hint')}
+/>
 <Button
   title={I18n.t('reject')}
   type='secondary'
   loading={loading}
   backgroundColor={colors.surfaceTint}
   onPress={onReject}
+  accessibilityHint={I18n.t('reject_invitation_hint')}
 />

Note: You'll need to add the corresponding i18n keys to app/i18n/locales/en.json.


90-97: Simplify the username style definition.

The username style includes text-related properties (textRegular, fontSize, lineHeight, textAlign, color) but is applied to a View wrapping the Chip component (line 36-38). Only the marginBottom property will take effect. Consider simplifying to only the properties that apply:

🔎 Proposed simplification
 username: {
-  ...sharedStyles.textRegular,
-  fontSize: 16,
-  lineHeight: 24,
-  textAlign: 'center',
-  color: colors.fontDefault,
+  alignItems: 'center',
   marginBottom: GAP
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a36ea13 and a61df23.

⛔ Files ignored due to path filters (1)
  • app/containers/Chip/__snapshots__/Chip.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (3)
  • app/containers/Chip/Chip.stories.tsx
  • app/containers/Chip/index.tsx
  • app/views/RoomView/components/InvitedRoom.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
app/containers/Chip/Chip.stories.tsx (1)
app/containers/Chip/index.tsx (1)
  • IChip (35-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🔇 Additional comments (5)
app/containers/Chip/index.tsx (1)

41-65: LGTM! Clean implementation of fullWidth prop.

The fullWidth prop is well-implemented:

  • Properly typed as optional boolean
  • Correctly removes maxWidth constraints from both the pressable container and text container when enabled
  • Maintains text truncation via numberOfLines={1} to prevent overflow

The chip will now expand to fill its parent's width when fullWidth is true, while maintaining the original 192px maxWidth constraint by default.

app/containers/Chip/Chip.stories.tsx (1)

18-20: LGTM! Prop forwarding correctly implemented.

The ChipWrapped component properly accepts and forwards the fullWidth prop to the underlying Chip component.

app/views/RoomView/components/InvitedRoom.tsx (3)

1-12: LGTM!

Imports and constants are well-organized. The GAP constant provides consistent spacing throughout the component.


40-46: Verify shared loading state behavior between accept and reject buttons.

Both buttons use the same loading prop, meaning both will be disabled simultaneously during any action. Confirm this is intentional to prevent race conditions between accept/reject. If users should be able to cancel an accept action by clicking reject (or vice versa), consider separate loading states.


14-21: The inviter field is correctly defined as non-nullable.

The type definition in app/definitions/ISubscription.ts explicitly declares inviter: NonNullable<ISubscription['inviter']> within IInviteSubscription. This ensures the inviter prop cannot be null or undefined, making the direct property access on line 37 (inviter.username, inviter.name) type-safe without additional null checks.


export const ChipWithoutAvatarAndIcon = () => <ChipWrapped text='Without Avatar and Icon' />;

export const ChipFullWidth = () => <ChipWrapped text='Full Width Text With Long Text That Should Be Wrapped' fullWidth />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor discrepancy: Story text mentions "wrapped" but text actually truncates.

The story text says "Should Be Wrapped" but the Chip component uses numberOfLines={1}, which causes text to truncate with an ellipsis rather than wrap to multiple lines. Consider updating the story text to better reflect the actual behavior, e.g., "Full Width Chip With Long Text That Truncates".

🔎 Suggested text update
-export const ChipFullWidth = () => <ChipWrapped text='Full Width Text With Long Text That Should Be Wrapped' fullWidth />;
+export const ChipFullWidth = () => <ChipWrapped text='Full Width Chip With Long Text That Truncates' fullWidth />;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const ChipFullWidth = () => <ChipWrapped text='Full Width Text With Long Text That Should Be Wrapped' fullWidth />;
export const ChipFullWidth = () => <ChipWrapped text='Full Width Chip With Long Text That Truncates' fullWidth />;
🤖 Prompt for AI Agents
In app/containers/Chip/Chip.stories.tsx around line 34, the story label claims
the text "Should Be Wrapped" but the Chip uses numberOfLines={1} causing
truncation; update the story text to accurately describe behavior (e.g., "Full
Width Chip With Long Text That Truncates") or remove the "wrapped" wording so
the story matches the component's single-line truncation behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
app/containers/RoomItem/RoomItem.stories.tsx (1)

193-198: LGTM! Consider adding more realistic props.

The story correctly demonstrates the invited room state in both display modes and follows the established pattern.

For a more realistic visual representation, consider adding typical room properties like name, avatar, or lastMessage to better demonstrate how the invited state appears in actual usage scenarios.

Optional enhancement with more context
 export const InvitedRoom = () => (
 	<>
-		<RoomItem isInvited />
-		<RoomItem isInvited displayMode={DisplayMode.Condensed} />
+		<RoomItem isInvited name='general' avatar='general' />
+		<RoomItem isInvited name='support-team' avatar='support-team' displayMode={DisplayMode.Condensed} />
 	</>
 );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a61df23 and 54b7a6a.

⛔ Files ignored due to path filters (1)
  • app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • app/containers/RoomItem/RoomItem.stories.tsx
  • app/containers/RoomItem/RoomItem.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/containers/RoomItem/RoomItem.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test

Copy link
Member

@diegolmello diegolmello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job!

@diegolmello diegolmello merged commit a04c42a into develop Dec 23, 2025
4 of 6 checks passed
@diegolmello diegolmello deleted the feat/invited-room-state branch December 23, 2025 16:19
@diegolmello diegolmello requested a deployment to official_android_build December 23, 2025 16:20 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to experimental_ios_build December 23, 2025 16:20 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to experimental_android_build December 23, 2025 16:20 — with GitHub Actions Waiting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants