Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 7 additions & 2 deletions assets/i18n/strings.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"profile": {
"name": {
"label": "Group Name",
"hint": "인포팀 (Infoteam)"
"hint": "인포팀 (Infoteam)",
"sameNameError": "Group name already exists"
}
},
"introduce": {
Expand Down Expand Up @@ -87,7 +88,8 @@
"header": "Update Notion Page Link",
"hintText": "Enter Notion Link",
"loading": "Loading Notion...",
"error": "Failed Loading Notion Link"
"error": "Failed Loading Notion Link",
"empty": "Please enter the Notion link"
},
"invite": {
"header": "Generate Invite Link",
Expand All @@ -106,10 +108,13 @@
"memberCard": {
"role": {
"role": "Role",
"president": "President",
"admin": "Admin",
"manager": "Manager",
"member": "Member"
},
"changePresidentTitle": "Would you like to make $name a president?",
"changePresidentDescription": "Your permissions will be changed to Admin.",
"banish": "Banish",
"banishTitle": "Are you sure you want to banish this member?",
"banishDescription": "This action cannot be undone."
Expand Down
9 changes: 7 additions & 2 deletions assets/i18n/strings_jp.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"profile": {
"name": {
"label": "グループ名",
"hint": "インフォチーム (Infoteam)"
"hint": "インフォチーム (Infoteam)",
"sameNameError": "グループ名はすでに存在します"
}
},
"introduce": {
Expand Down Expand Up @@ -87,7 +88,8 @@
"header": "ノーションページリンクを更新",
"hintText": "ノーションリンクを入力してください",
"loading": "Notionを読み込み中...",
"error": "ノーションのリンクの読み込みに失敗しました"
"error": "ノーションのリンクの読み込みに失敗しました",
"empty": "ノーションリンクを入力してください"
},
"invite": {
"header": "招待リンクを生成",
Expand All @@ -106,10 +108,13 @@
"memberCard": {
"role": {
"role": "役割",
"president": "社長",
"admin": "管理者",
"manager": "マネージャー",
"member": "メンバー"
},
"changePresidentTitle": "? $name",
"changePresidentDescription": "?",
"banish": "追放",
Comment on lines +116 to 118
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

[중요] JP: 변경 모달 번역 누락(‘?’)

일본어 번역이 미완입니다. 아래처럼 제안드립니다.

-      "changePresidentTitle": "? $name",
-      "changePresidentDescription": "?",
+      "changePresidentTitle": "$name さんを社長にしますか?",
+      "changePresidentDescription": "あなたの役割は「管理者」に変更されます。"
📝 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
"changePresidentTitle": "? $name",
"changePresidentDescription": "?",
"banish": "追放",
"changePresidentTitle": "$name さんを社長にしますか?",
"changePresidentDescription": "あなたの役割は「管理者」に変更されます。",
"banish": "追放",
🤖 Prompt for AI Agents
In assets/i18n/strings_jp.i18n.json around lines 116 to 118, the keys
"changePresidentTitle" and "changePresidentDescription" contain placeholder '?'
values; replace them with proper Japanese strings: set "changePresidentTitle" to
"議長を $name に交代しますか?" and "changePresidentDescription" to "議長を変更してもよろしいですか?"
(ensure JSON string quoting/escaping is correct).

"banishTitle": "本当にこのメンバーを追放しますか?",
"banishDescription": "この操作は元に戻せません。"
Expand Down
9 changes: 7 additions & 2 deletions assets/i18n/strings_ko.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"profile": {
"name": {
"label": "그룹명",
"hint": "인포팀 (Infoteam)"
"hint": "인포팀 (Infoteam)",
"sameNameError": "그룹 이름이 이미 존재합니다"
}
},
"introduce": {
Expand Down Expand Up @@ -87,7 +88,8 @@
"header": "노션 페이지 링크 변경",
"hintText": "노션 링크 입력",
"loading": "노션 불러오는 중...",
"error": "노션 불러오기 실패"
"error": "노션 불러오기 실패",
"empty": "노션 링크를 입력해주세요"
},
"invite": {
"header": "초대 링크 생성",
Expand All @@ -106,10 +108,13 @@
"memberCard": {
"role": {
"role": "역할",
"president": "그룹장",
"admin": "관리자",
"manager": "매니저",
"member": "멤버"
},
"changePresidentTitle": "$name님을 그룹장으로 만드시겠습니까?",
"changePresidentDescription": "당신의 권한은 관리자로 변경됩니다.",
"banish": "추방",
"banishTitle": "정말 이 멤버를 추방하시겠습니까?",
"banishDescription": "이 작업은 되돌릴 수 없습니다."
Expand Down
9 changes: 7 additions & 2 deletions assets/i18n/strings_ru.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"profile": {
"name": {
"label": "Название группы",
"hint": "ИнфоТим (Infoteam)"
"hint": "ИнфоТим (Infoteam)",
"sameNameError": "Имя группы уже существует"
}
},
"introduce": {
Expand Down Expand Up @@ -87,7 +88,8 @@
"header": "Обновить ссылку на страницу Notion",
"hintText": "Введите ссылку на Notion",
"loading": "Загрузка Notion...",
"error": "Не удалось загрузить ссылку на Notion"
"error": "Не удалось загрузить ссылку на Notion",
"empty": "Пожалуйста, введите ссылку на Notion"
},
"invite": {
"header": "Создать ссылку для приглашения",
Expand All @@ -106,10 +108,13 @@
"memberCard": {
"role": {
"role": "Роль",
"president": "президент",
"admin": "Администратор",
"manager": "Менеджер",
"member": "член"
},
"changePresidentTitle": "? $name",
"changePresidentDescription": "?",
"banish": "Исключить",
Comment on lines +116 to 118
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

[중요] 변경 모달 러시아어 번역 누락(‘?’)

사용자 노출 문자열이 ‘?’로 남아있습니다. 아래처럼 자연스러운 문구로 교체해주세요.

-      "changePresidentTitle": "? $name",
-      "changePresidentDescription": "?",
+      "changePresidentTitle": "Назначить $name президентом группы?",
+      "changePresidentDescription": "Ваша роль будет изменена на «Администратор»."
📝 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
"changePresidentTitle": "? $name",
"changePresidentDescription": "?",
"banish": "Исключить",
"changePresidentTitle": "Назначить $name президентом группы?",
"changePresidentDescription": "Ваша роль будет изменена на «Администратор».",
"banish": "Исключить",
🤖 Prompt for AI Agents
assets/i18n/strings_ru.i18n.json around lines 116-118: the strings for
changePresidentTitle and changePresidentDescription are left as '?' and must be
replaced with natural Russian phrases; set changePresidentTitle to a concise
title like "Сменить президента: $name" and changePresidentDescription to a clear
confirmation like "Вы уверены, что хотите назначить $name президентом?" (keep
placeholders intact as $name).

"banishTitle": "Вы уверены, что хотите исключить этого участника?",
"banishDescription": "Это действие нельзя отменить."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ part 'create_role_model.g.dart';
sealed class CreateRoleModel with _$CreateRoleModel {
factory CreateRoleModel({
required String name,
required List<String> authorities,
required List<String> permissions,
}) = _CreateRoleModel;

factory CreateRoleModel.fromJson(Map<String, dynamic> json) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ sealed class RoleModel with _$RoleModel implements RoleEntity {
required int id,
required GroupMemberRole name,
required String groupUuid,
required List<String> authorities,
required List<String> permissions,
}) = _RoleModel;

factory RoleModel.fromJson(Map<String, dynamic> json) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:freezed_annotation/freezed_annotation.dart';

part 'update_president_model.freezed.dart';
part 'update_president_model.g.dart';

@freezed
sealed class UpdatePresidentModel with _$UpdatePresidentModel {
factory UpdatePresidentModel(
String newPresidentUuid,
) = _UpdatePresidentModel;

factory UpdatePresidentModel.fromJson(Map<String, dynamic> json) =>
_$UpdatePresidentModelFromJson(json);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ part 'update_role_model.g.dart';
@freezed
sealed class UpdateRoleModel with _$UpdateRoleModel {
factory UpdateRoleModel(
List<String> authorities,
List<String> permissions,
) = _UpdateRoleModel;

factory UpdateRoleModel.fromJson(Map<String, dynamic> json) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:ziggle/app/modules/groups/data/data_sources/models/member_list_m
import 'package:ziggle/app/modules/groups/data/data_sources/models/modify_group_model.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/models/role_list_model.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/models/role_model.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/models/update_president_model.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/models/update_role_model.dart';

part 'group_api.g.dart';
Expand Down Expand Up @@ -104,7 +105,7 @@ abstract class GroupApi {
@PATCH('{uuid}/president')
Future<void> updatePresident(
@Path('uuid') String uuid,
@Body() Map<String, String> newPresidentUuid,
@Body() UpdatePresidentModel newPresidentUuid,
);

@GET('{groupUuid}/role')
Expand Down
9 changes: 9 additions & 0 deletions lib/app/modules/groups/data/enums/group_member_role.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ part 'group_member_role.g.dart';

@JsonEnum(alwaysCreate: true)
enum GroupMemberRole {
@JsonValue('president')
president,

@JsonValue('admin')
admin,

Expand All @@ -17,6 +20,8 @@ enum GroupMemberRole {

int toInt() {
switch (this) {
case GroupMemberRole.president:
return 0;
case GroupMemberRole.admin:
return 1;
case GroupMemberRole.manager:
Expand All @@ -26,6 +31,8 @@ enum GroupMemberRole {
}
}

bool isPresident() => this == GroupMemberRole.president;

bool isAdmin() => this == GroupMemberRole.admin;

bool isManager() => this == GroupMemberRole.manager;
Expand All @@ -36,6 +43,8 @@ enum GroupMemberRole {
extension GroupMemberRoleX on GroupMemberRole {
String toLocalizedString(BuildContext context) {
switch (this) {
case GroupMemberRole.president:
return context.t.group.memberCard.role.president;
case GroupMemberRole.admin:
return context.t.group.memberCard.role.admin;
case GroupMemberRole.manager:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:rxdart/rxdart.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/models/create_group_model.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/models/group_list_model.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/models/modify_group_model.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/models/update_president_model.dart';
import 'package:ziggle/app/modules/groups/data/data_sources/remote/group_api.dart';
import 'package:ziggle/app/modules/groups/data/enums/group_member_role.dart';
import 'package:ziggle/app/modules/groups/domain/entities/authority_entity.dart';
Expand Down Expand Up @@ -130,6 +131,12 @@ class RestGroupRepository implements GroupRepository {
return _api.deleteUserRole(uuid, targetUuid, roleId);
}

@override
Future<void> changePresident(
{required String uuid, required String targetUuid}) {
return _api.updatePresident(uuid, UpdatePresidentModel(targetUuid));
}

@override
Future<void> createRole(String groupUuid, RoleEntity role) {
// TODO: implement createRole
Expand Down
4 changes: 2 additions & 2 deletions lib/app/modules/groups/domain/entities/authority_entity.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class AuthorityEntity {
final List<String> authorities;
final List<String> permissions;

AuthorityEntity({required this.authorities});
AuthorityEntity({required this.permissions});
}
4 changes: 2 additions & 2 deletions lib/app/modules/groups/domain/entities/role_entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ class RoleEntity {
final int id;
final GroupMemberRole name;
final String groupUuid;
final List<String> authorities;
final List<String> permissions;

RoleEntity({
required this.id,
required this.name,
required this.groupUuid,
required this.authorities,
required this.permissions,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ abstract class GroupRepository {
required String targetUuid,
required int roleId,
});
Future<void> changePresident({
required String uuid,
required String targetUuid,
});
Future<RoleListEntity> getRoles(String groupUuid);
Future<void> createRole(String groupUuid, RoleEntity role);
Future<void> updateRole(
Expand Down
31 changes: 28 additions & 3 deletions lib/app/modules/groups/presentation/blocs/group_member_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@ import 'package:injectable/injectable.dart';
import 'package:ziggle/app/modules/groups/data/enums/group_member_role.dart';
import 'package:ziggle/app/modules/groups/domain/entities/member_entity.dart';
import 'package:ziggle/app/modules/groups/domain/repository/group_repository.dart';
import 'package:ziggle/app/modules/user/data/repositories/groups_rest_auth_repository.dart';
import 'package:ziggle/app/modules/user/data/repositories/rest_auth_repository.dart';

part 'group_member_bloc.freezed.dart';

@injectable
class GroupMemberBloc extends Bloc<GroupMemberEvent, GroupMemberState> {
final GroupRepository _repository;
final RestAuthRepository _authRepository;

GroupMemberBloc(this._repository) : super(_Initial()) {
GroupMemberBloc(this._repository,
@Named.from(GroupsRestAuthRepository) this._authRepository)
: super(_Initial()) {
on<_Load>((event, emit) {
emit(_Loading());
});
on<_GetMembers>((event, emit) async {
emit(GroupMemberState.loading());
final members = await _repository.getMembers(event.uuid);
emit(GroupMemberState.loaded(members.list));
final group = await _repository.getGroup(event.uuid);
final myUuid = await _authRepository.info().then((u) => u.uuid);
emit(
GroupMemberState.loaded(members.list, myUuid, group.president!.uuid));
});
Comment on lines 23 to 30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

예외 처리 누락 + null 강제 연산자 때문에 런타임 크래시 위험

  • group.president!.uuid에서 president가 null이면 바로 크래시합니다.
  • API 실패 시 try/catch가 없어 Bloc 핸들러가 예외로 종료될 수 있습니다.
  • 3개의 I/O를 직렬로 호출하고 있어 지연이 불필요하게 길어집니다.

아래처럼 동시 실행 + 견고한 예외 처리 + null 가드로 보완해 주세요.

     on<_GetMembers>((event, emit) async {
-      emit(GroupMemberState.loading());
-      final members = await _repository.getMembers(event.uuid);
-      final group = await _repository.getGroup(event.uuid);
-      final myUuid = await _authRepository.info().then((u) => u.uuid);
-      emit(
-          GroupMemberState.loaded(members.list, myUuid, group.president!.uuid));
+      emit(GroupMemberState.loading());
+      try {
+        final membersF = _repository.getMembers(event.uuid);
+        final groupF = _repository.getGroup(event.uuid);
+        final meF = _authRepository.info();
+
+        final members = await membersF;
+        final group = await groupF;
+        final myUuid = (await meF).uuid;
+
+        final presidentUuid = group.president?.uuid;
+        if (presidentUuid == null) {
+          emit(const GroupMemberState.error('Group has no president'));
+          return;
+        }
+        emit(GroupMemberState.loaded(members.list, myUuid, presidentUuid));
+      } on Exception catch (e) {
+        emit(GroupMemberState.error(e.toString()));
+      }
     });
📝 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
on<_GetMembers>((event, emit) async {
emit(GroupMemberState.loading());
final members = await _repository.getMembers(event.uuid);
emit(GroupMemberState.loaded(members.list));
final group = await _repository.getGroup(event.uuid);
final myUuid = await _authRepository.info().then((u) => u.uuid);
emit(
GroupMemberState.loaded(members.list, myUuid, group.president!.uuid));
});
on<_GetMembers>((event, emit) async {
emit(GroupMemberState.loading());
try {
final membersF = _repository.getMembers(event.uuid);
final groupF = _repository.getGroup(event.uuid);
final meF = _authRepository.info();
final members = await membersF;
final group = await groupF;
final myUuid = (await meF).uuid;
final presidentUuid = group.president?.uuid;
if (presidentUuid == null) {
emit(const GroupMemberState.error('Group has no president'));
return;
}
emit(GroupMemberState.loaded(members.list, myUuid, presidentUuid));
} on Exception catch (e) {
emit(GroupMemberState.error(e.toString()));
}
});
🤖 Prompt for AI Agents
In lib/app/modules/groups/presentation/blocs/group_member_bloc.dart around lines
23 to 30, the handler lacks error handling, uses a null-forcing operator on
group.president which can crash, and performs three I/O calls serially causing
unnecessary latency; wrap the async block in try/catch and emit an error state
on failure, run getMembers, getGroup and authRepository.info concurrently (e.g.
Future.wait) to reduce latency, and guard against a null president by deriving
presidentUuid = group.presid ent?.uuid ?? '' (or emit a specific error/empty
value) before emitting GroupMemberState.loaded.

on<_RemoveMember>((event, emit) async {
emit(GroupMemberState.loading());
Expand Down Expand Up @@ -47,6 +55,19 @@ class GroupMemberBloc extends Bloc<GroupMemberEvent, GroupMemberState> {
emit(GroupMemberState.success());
add(GroupMemberEvent.getMembers(event.uuid));
});
on<_ChangePresident>((event, emit) async {
emit(GroupMemberState.loading());
try {
await _repository.changePresident(
uuid: event.uuid,
targetUuid: event.targetUuid,
);
} on Exception catch (e) {
emit(_Error(e.toString()));
}
emit(GroupMemberState.success());
add(GroupMemberEvent.getMembers(event.uuid));
});
Comment on lines +58 to +70
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

changePresident: 에러 후에도 success 상태를 내보내는 동일한 버그

실패 시에도 성공 상태를 방출합니다. 위 핸들러와 동일 패턴으로 수정 필요합니다.

     on<_ChangePresident>((event, emit) async {
       emit(GroupMemberState.loading());
       try {
         await _repository.changePresident(
           uuid: event.uuid,
           targetUuid: event.targetUuid,
         );
-      } on Exception catch (e) {
-        emit(_Error(e.toString()));
-      }
-      emit(GroupMemberState.success());
-      add(GroupMemberEvent.getMembers(event.uuid));
+        emit(GroupMemberState.success());
+        add(GroupMemberEvent.getMembers(event.uuid));
+      } on Exception catch (e) {
+        emit(GroupMemberState.error(e.toString()));
+      }
     });
📝 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
on<_ChangePresident>((event, emit) async {
emit(GroupMemberState.loading());
try {
await _repository.changePresident(
uuid: event.uuid,
targetUuid: event.targetUuid,
);
} on Exception catch (e) {
emit(_Error(e.toString()));
}
emit(GroupMemberState.success());
add(GroupMemberEvent.getMembers(event.uuid));
});
on<_ChangePresident>((event, emit) async {
emit(GroupMemberState.loading());
try {
await _repository.changePresident(
uuid: event.uuid,
targetUuid: event.targetUuid,
);
emit(GroupMemberState.success());
add(GroupMemberEvent.getMembers(event.uuid));
} on Exception catch (e) {
emit(GroupMemberState.error(e.toString()));
}
});
🤖 Prompt for AI Agents
In lib/app/modules/groups/presentation/blocs/group_member_bloc.dart around lines
58 to 70, the changePresident handler emits a success state and triggers
getMembers even when an exception occurs; move the
emit(GroupMemberState.success()) and add(GroupMemberEvent.getMembers(...)) into
the try block after the await so they only run on success, and ensure the catch
block only emits the error (or returns) so execution doesn't continue to the
success path.

}
}

Expand All @@ -58,13 +79,17 @@ class GroupMemberEvent with _$GroupMemberEvent {
_RemoveMember;
const factory GroupMemberEvent.grantRoleToUser(String uuid, String targetUuid,
GroupMemberRole role, GroupMemberRole previousRole) = _GrantRoleToUser;

const factory GroupMemberEvent.changePresident(
String uuid, String targetUuid) = _ChangePresident;
}

@freezed
class GroupMemberState with _$GroupMemberState {
const factory GroupMemberState.initial() = _Initial;
const factory GroupMemberState.loading() = _Loading;
const factory GroupMemberState.loaded(List<MemberEntity> list) = _Loaded;
const factory GroupMemberState.loaded(
List<MemberEntity> list, String myUuid, String presidentUuid) = _Loaded;
const factory GroupMemberState.success() = _Success;
const factory GroupMemberState.error(String error) = _Error;
}
Loading