Skip to content

Conversation

@dmp100
Copy link
Contributor

@dmp100 dmp100 commented Nov 13, 2025

ISSUE

❗ WORK DESCRIPTIONAdd commentMore actions

  • 커리어수정, 프로필수정은 마무리했습니다

📸 SCREENSHOT

2025-11-13.12.33.07.mov

📢 TO REVIEWERS

  • 로그아웃, 탈퇴까지 바로 마무리하겠습니다..
  • 나ㅣ의 게시물보기는 일단 컴포넌트 배제시켰습니다.
  • 링크수정은 3순위로 뒀습니다.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added career information management - users can view, edit, and manage their career history with company name, position, job type, and employment duration
    • Enhanced My Page profile updates including student number, name, major, mentor status, and job-seeking preferences
    • Integrated employment and academic status display on My Page
    • Added job type categorization (permanent, temporary, intern, freelancer) for career entries

@coderabbitai
Copy link

coderabbitai bot commented Nov 13, 2025

Walkthrough

This PR adds complete MyPage and Career management functionality, including DI wiring for new services, repository implementations, request/response DTOs, Retrofit service interfaces, view models for state management, and updates to UI screens and navigation to integrate with the new backend APIs.

Changes

Cohort / File(s) Change Summary
Dependency Injection Configuration
app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt, ServiceModule.kt, RepositoryModule.kt
Added provideCareerService() provider method for singleton CareerService; added provideMypageService() provider for singleton MypageService; added repository bindings for bindMypageRepository() and bindCareerRepository()
Request DTOs
app/src/main/java/com/hsLink/hslink/data/dto/request/common/JobType.kt, mypage/UpdateProfileRequestDto.kt, onboarding/CareerRequest.kt
Added JobType enum with PERMANENT, TEMPORARY, INTERN, FREELANCER; added UpdateProfileRequestDto with optional profile fields; added CareerUpdateRequestDto for career updates
Response DTOs
app/src/main/java/com/hsLink/hslink/data/dto/response/CareerResponse.kt, mypage/MyPageUserProfileDto.kt, mypage/MyPageUserSummaryDto.kt, mypage/ProfileUpdateResponse.kt
Added CareerDto for career data; added MyPageUserProfileDto with profile details and nested career/link lists; added MyPageUserSummaryDto for user summary; added ProfileUpdateResponse for update responses
Service Interfaces
app/src/main/java/com/hsLink/hslink/data/service/CareerService.kt, mypage/MypageService.kt
Added CareerService Retrofit interface with getMyCareers(), getCareer(), updateCareer(); added MypageService interface with getUserProfile(), updateProfile(), getUserSummary()
Domain Repository Interfaces
app/src/main/java/com/hsLink/hslink/domain/repository/CareerRepository.kt, mypage/MypageRepository.kt
Added CareerRepository interface with career CRUD operations; added MypageRepository interface with profile and summary operations
Repository Implementations
app/src/main/java/com/hsLink/hslink/data/repositoryimpl/CareerRepositoryImpl.kt, mypage/MypageRepositoryImpl.kt
Implemented CareerRepositoryImpl with try-catch error handling and Result mapping; implemented MypageRepositoryImpl with profile and summary data fetching with error propagation
ViewModels
app/src/main/java/com/hsLink/hslink/presentation/mypage/viewmodel/CareerViewModel.kt, MypageViewModel.kt
Added CareerViewModel with StateFlow-based state management for careers, career loading, and updates; added MypageViewModel with profile/summary loading and update operations
UI & Navigation Updates
app/src/main/java/com/hsLink/hslink/presentation/mypage/navigation/career/CareerEditNavigation.kt, screen/career/CareerEditScreen.kt, screen/main/MypageScreen.kt, screen/profile/ProfileEditScreen.kt
Updated CareerEdit from data object to data class with careerId parameter; enhanced CareerEditScreen with career data binding and form initialization from DTO; wired MypageRoute with ViewModel and state collection; updated MypageScreen to render dynamic user summary; integrated ProfileEditScreen with ViewModels, career list rendering, and form validation

Sequence Diagrams

sequenceDiagram
    participant UI as CareerEditScreen
    participant VM as CareerViewModel
    participant Repo as CareerRepositoryImpl
    participant Svc as CareerService
    participant API as Backend API

    rect rgb(200, 230, 255)
    Note over UI,API: Load Career on Init
    UI->>VM: loadCareer(careerId)
    VM->>Repo: getCareer(careerId)
    Repo->>Svc: careerService.getCareer()
    Svc->>API: GET /careers/{careerId}
    API-->>Svc: BaseResponse<CareerDto>
    Svc-->>Repo: BaseResponse
    Repo->>Repo: map success to Result.success
    Repo-->>VM: Result<CareerDto>
    VM->>VM: update _selectedCareer
    VM-->>UI: selectedCareer StateFlow update
    UI->>UI: initialize form fields from career data
    end

    rect rgb(230, 200, 255)
    Note over UI,API: Update Career on Save
    UI->>UI: onSaveClick() with form values
    UI->>VM: updateCareer(careerId, CareerUpdateRequestDto)
    VM->>Repo: updateCareer(careerId, request)
    Repo->>Svc: careerService.updateCareer()
    Svc->>API: PUT /careers/{careerId}
    API-->>Svc: BaseResponse<CareerDto>
    Svc-->>Repo: BaseResponse
    Repo->>Repo: map to Result
    Repo-->>VM: Result<CareerDto>
    VM->>VM: reload via loadMyCareers()
    VM-->>UI: state update & navigate back
    end
Loading
sequenceDiagram
    participant UI as MypageScreen
    participant VM as MypageViewModel
    participant Repo as MypageRepositoryImpl
    participant Svc as MypageService
    participant API as Backend API

    rect rgb(230, 255, 200)
    Note over UI,API: Load User Summary
    UI->>VM: loadUserSummary()
    VM->>Repo: getUserSummary()
    Repo->>Svc: mypageService.getUserSummary()
    Svc->>API: GET /users/summary
    API-->>Svc: BaseResponse<MyPageUserSummaryDto>
    Svc-->>Repo: BaseResponse
    Repo->>Repo: extract data & return Result.success
    Repo-->>VM: Result<MyPageUserSummaryDto>
    VM->>VM: update _userSummary StateFlow
    VM-->>UI: re-render with summary (name, major, status)
    end

    rect rgb(255, 230, 200)
    Note over UI,API: Update Profile
    UI->>UI: ProfileEditScreen onSaveClick
    UI->>VM: updateProfile(name, major, mentor, jobSeeking)
    VM->>Repo: updateProfile(UpdateProfileRequestDto)
    Repo->>Svc: mypageService.updateProfile()
    Svc->>API: PATCH /users/myprofile
    API-->>Svc: ProfileUpdateResponse
    Svc-->>Repo: response
    Repo->>Repo: check isSuccess & map to Result.success
    Repo-->>VM: Result<Unit>
    VM->>VM: reload profile & summary on success
    VM-->>UI: navigate back + refresh summary
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Repository implementations (CareerRepositoryImpl.kt, MypageRepositoryImpl.kt): Verify try-catch error handling patterns, Result mapping logic, and consistency with BaseResponse envelope handling across both implementations.
  • ViewModel state management (CareerViewModel.kt, MypageViewModel.kt): Check coroutine lifecycle, viewModelScope usage, StateFlow initialization, and proper error/loading state transitions during API calls.
  • UI screen data flow (CareerEditScreen.kt, ProfileEditScreen.kt, MypageScreen.kt): Validate LaunchedEffect dependencies, form state initialization from DTOs, callback signature changes, and proper state collection from ViewModels.
  • Navigation parameter passing (CareerEditNavigation.kt): Ensure careerId parameter is correctly threaded through navigation stack and back-stack argument extraction uses correct type/default.
  • DTO field alignment: Cross-reference request/response DTO fields with API contracts and ensure nullable vs. non-nullable field handling matches service layer expectations.

Possibly related PRs

  • Connect-Front#19: Modifies MyPage screens and career-related UI components with overlapping screen updates and navigation changes
  • Connect-Front#2: Adds DI service and repository bindings in the same NetworkModule, ServiceModule, and RepositoryModule files
  • Connect-Front#29: Modifies the DI network module with service provider additions

Suggested labels

feature ✨, 규현 🐻

Poem

🐰 A career path now springs to life,
ViewModels dance with data so bright,
From DTOs deep to screens held high,
MyPage takes form—no need to sigh!
Repositories hop through Retrofit's way, 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.52% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat/#28 mypage api' is directly related to the main change: implementing MyPage API integration with career and profile management features.
Description check ✅ Passed The description follows the template structure with ISSUE section (#28), WORK DESCRIPTION, SCREENSHOT, and TO REVIEWERS sections, though some details could be more explicit.
Linked Issues check ✅ Passed The PR implements the core MyPage API objectives from issue #28: completes career and profile edit features with full API integration through services, repositories, DTOs, and ViewModels.
Out of Scope Changes check ✅ Passed The PR stays focused on MyPage API implementation. Navigation changes for CareerEdit are necessary for passing careerId between screens, and exclude unrelated features (posts, link editing) as noted.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#28-mypageAPI

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.

@dmp100 dmp100 self-assigned this Nov 13, 2025
@dmp100 dmp100 merged commit 55615b8 into develop Nov 13, 2025
1 check was pending
Copy link

@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: 7

🧹 Nitpick comments (1)
app/src/main/java/com/hsLink/hslink/data/service/mypage/MypageService.kt (1)

9-9: Remove unused import.

UserProfileDto is imported but not referenced in this file.

Apply this diff:

-import com.hsLink.hslink.data.dto.response.mypage.UserProfileDto
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5253775 and a996816.

📒 Files selected for processing (22)
  • app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt (2 hunks)
  • app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt (2 hunks)
  • app/src/main/java/com/hsLink/hslink/data/di/ServiceModule.kt (2 hunks)
  • app/src/main/java/com/hsLink/hslink/data/dto/request/common/JobType.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/dto/request/mypage/UpdateProfileRequestDto.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/dto/request/onboarding/CareerRequest.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/dto/response/CareerResponse.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/dto/response/mypage/MyPageUserProfileDto.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/dto/response/mypage/MyPageUserSummaryDto.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/dto/response/mypage/ProfileUpdateResponse.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/repositoryimpl/CareerRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/repositoryimpl/mypage/MypageRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/service/CareerService.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/data/service/mypage/MypageService.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/domain/repository/CareerRepository.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/domain/repository/mypage/MypageRepository.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/presentation/mypage/navigation/career/CareerEditNavigation.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/presentation/mypage/screen/career/CareerEditScreen.kt (8 hunks)
  • app/src/main/java/com/hsLink/hslink/presentation/mypage/screen/main/MypageScreen.kt (3 hunks)
  • app/src/main/java/com/hsLink/hslink/presentation/mypage/screen/profile/ProfileEditScreen.kt (13 hunks)
  • app/src/main/java/com/hsLink/hslink/presentation/mypage/viewmodel/CareerViewModel.kt (1 hunks)
  • app/src/main/java/com/hsLink/hslink/presentation/mypage/viewmodel/MypageViewModel.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/src/main/java/com/hsLink/hslink/presentation/mypage/viewmodel/MypageViewModel.kt (3)
app/src/main/java/com/hsLink/hslink/data/repositoryimpl/mypage/MypageRepositoryImpl.kt (2)
  • getUserSummary (48-59)
  • getUserProfile (16-33)
app/src/main/java/com/hsLink/hslink/data/service/mypage/MypageService.kt (3)
  • getUserSummary (24-25)
  • getUserProfile (15-26)
  • getUserProfile (16-17)
app/src/main/java/com/hsLink/hslink/domain/repository/mypage/MypageRepository.kt (3)
  • getUserSummary (14-14)
  • getUserProfile (9-16)
  • getUserProfile (10-10)
app/src/main/java/com/hsLink/hslink/presentation/mypage/screen/career/CareerEditScreen.kt (1)
app/src/main/java/com/hsLink/hslink/core/designsystem/component/HsLinkSelectButton.kt (1)
  • HsLinkSelectButton (38-94)
app/src/main/java/com/hsLink/hslink/presentation/mypage/navigation/career/CareerEditNavigation.kt (1)
app/src/main/java/com/hsLink/hslink/presentation/mypage/screen/career/CareerEditScreen.kt (1)
  • CareerEditRoute (56-93)
app/src/main/java/com/hsLink/hslink/presentation/mypage/screen/profile/ProfileEditScreen.kt (1)
app/src/main/java/com/hsLink/hslink/presentation/mypage/component/profile/CareerCard.kt (1)
  • CareerCard (45-101)
🔇 Additional comments (6)
app/src/main/java/com/hsLink/hslink/data/dto/response/mypage/ProfileUpdateResponse.kt (1)

5-10: LGTM!

The response DTO structure is clean and follows standard serialization patterns for API responses.

app/src/main/java/com/hsLink/hslink/data/dto/request/mypage/UpdateProfileRequestDto.kt (1)

6-13: LGTM!

The nullable fields with default null values correctly support partial updates for PATCH semantics, allowing callers to specify only the fields they want to update.

app/src/main/java/com/hsLink/hslink/data/dto/response/mypage/MyPageUserSummaryDto.kt (1)

6-15: LGTM!

The DTO structure is clean with appropriate field types. The inline Korean comments provide helpful context for the development team.

app/src/main/java/com/hsLink/hslink/data/di/ServiceModule.kt (1)

27-31: LGTM!

The DI provider for MypageService follows the established pattern and is correctly configured with singleton scope.

app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt (1)

80-84: LGTM!

The DI provider for CareerService is correctly implemented with singleton scope and follows the established pattern in this module.

app/src/main/java/com/hsLink/hslink/data/service/mypage/MypageService.kt (1)

16-25: Standardize response wrapping across MypageService endpoints for consistent error handling.

The inconsistency is confirmed. The three endpoints handle HTTP-level errors differently:

  • getUserProfile() returns Response<BaseResponse<>> — HTTP errors are caught in response.isSuccessful
  • updateProfile() returns ProfileUpdateResponse — HTTP errors will throw exceptions
  • getUserSummary() returns BaseResponse<> — HTTP errors will throw exceptions

While the repository layer currently handles each case, this creates inconsistent error handling behavior. The ProfileUpdateResponse structure mirrors BaseResponse, suggesting updateProfile should also be wrapped in Response<BaseResponse<>> for uniform HTTP-level error handling across all endpoints.

Standardize all three endpoints to use Response<BaseResponse<T>> wrapping to ensure consistent error handling throughout the repository layer.

Comment on lines +1 to +12
package com.hsLink.hslink.data.dto.request.common

import kotlinx.serialization.Serializable

// data/dto/common/JobType.kt
@Serializable
enum class JobType {
PERMANENT, // 정규직
TEMPORARY, // 계약직
INTERN, // 인턴
FREELANCER // 프리랜서
} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Consider relocating to common package and consolidating duplicate JobType.

Two concerns:

  1. Package location: This enum is placed in data.dto.request.common, but it's also used in response DTOs (e.g., CareerDto). Consider moving it to data.dto.common for better organization.

  2. Duplicate enum: Based on imports in CareerResponse.kt, there's already a JobType enum in presentation.onboarding.model. Having two separate JobType enums violates DRY and can cause import confusion.

Recommendation:

  • Consolidate to a single JobType enum in data.dto.common (or domain.model.common)
  • Remove the duplicate from presentation.onboarding.model
  • Update all references throughout the codebase to use the canonical version

This will prevent future bugs from mixing the two types and improve maintainability.


import kotlinx.serialization.Serializable

// data/dto/common/JobType.kt
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect file path in comment.

The comment states data/dto/common/JobType.kt, but the actual package is com.hsLink.hslink.data.dto.request.common (line 1).

Apply this diff:

-// data/dto/common/JobType.kt
+// data/dto/request/common/JobType.kt
📝 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
// data/dto/common/JobType.kt
// data/dto/request/common/JobType.kt
🤖 Prompt for AI Agents
In app/src/main/java/com/hsLink/hslink/data/dto/request/common/JobType.kt around
line 5, the top-of-file comment incorrectly shows the path
"data/dto/common/JobType.kt"; update that comment to the correct path matching
the package (com/hsLink/hslink/data/dto/request/common/JobType.kt) or remove the
stale path comment so it no longer misrepresents the file location.

Comment on lines +29 to +38
@Serializable
data class CareerDto(
val id: Long,
val companyName: String,
val position: String,
val jobType: JobType,
val employed: Boolean,
val startYm: String,
val endYm: String?
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

ID type inconsistency and duplicate JobType enum.

Two critical issues:

  1. ID type mismatch: CareerResponse.id is Int (line 12), but CareerDto.id is Long (line 31). Since both represent career entities from the same backend, this inconsistency can cause casting errors or data loss.

  2. Duplicate JobType enum: The file imports JobType from presentation.onboarding.model (line 5) for CareerResponse, while CareerDto appears to use a different JobType from data.dto.request.common. Having two separate enums with the same name violates DRY and can lead to confusion and serialization issues.

Recommendations:

  • Standardize the ID type (use Long consistently for all career IDs)
  • Consolidate to a single JobType enum (preferably in the data layer) and remove the duplicate from presentation.onboarding.model
  • Update all references to use the canonical JobType

Apply this diff to fix the ID type:

 @Serializable
 data class CareerResponse(
-    @SerialName("id") val id: Int,
+    @SerialName("id") val id: Long,
     @SerialName("companyName") val companyName: String,
     @SerialName("position") val position: String,
     @SerialName("jobType") val jobType: JobType,
     @SerialName("employed") val employed: Boolean,
     @SerialName("startYm") val startYm: String,
     @SerialName("endYm") val endYm: String?,
 )

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/java/com/hsLink/hslink/data/dto/response/CareerResponse.kt
around lines 29 to 38, the CareerResponse and CareerDto disagree on id type (Int
vs Long) and two different JobType enums are being used; change
CareerResponse.id to Long to match CareerDto, replace the import of JobType from
presentation.onboarding.model with the canonical JobType from
data.dto.request.common (or the shared data-layer enum), remove the duplicate
presentation-layer enum, and update all usages/imports across the module to
reference the single data-layer JobType so serialization and type-safety are
consistent.

Comment on lines +21 to 31
composable<CareerEdit> { backStackEntry ->
val careerEdit = backStackEntry.arguments?.let {
// ← careerId 추출
CareerEdit(careerId = it.getLong("careerId"))
} ?: CareerEdit(careerId = 0L)

CareerEditRoute(
paddingValues = padding,
navController = navController
navController = navController,
careerId = careerEdit.careerId // ← careerId 전달
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't force a missing careerId into 0

CareerEditRoute (see app/src/main/java/com/hsLink/hslink/presentation/mypage/screen/career/CareerEditScreen.kt) still treats a null id as “new career”, skipping loadCareer/updateCareer. By defaulting the bundle to CareerEdit(careerId = 0L) we now drive the screen into edit mode and fire loadCareer(0) / updateCareer(0, …), which will inevitably come back as a 404 when the argument is genuinely absent (e.g., create flow or arg loss after process recreation/deep link). Instead of inventing the sentinel, bail out when the id isn’t there:

     composable<CareerEdit> { backStackEntry ->
-        val careerEdit = backStackEntry.arguments?.let {
-            CareerEdit(careerId = it.getLong("careerId"))
-        } ?: CareerEdit(careerId = 0L)
-
-        CareerEditRoute(
-            paddingValues = padding,
-            navController = navController,
-            careerId = careerEdit.careerId // ← careerId 전달
-        )
+        val args = backStackEntry.arguments
+        if (args == null || !args.containsKey("careerId")) {
+            navController.popBackStack()
+            return@composable
+        }
+
+        CareerEditRoute(
+            paddingValues = padding,
+            navController = navController,
+            careerId = args.getLong("careerId")
+        )
     }

That preserves the nullable contract and prevents bogus API calls.

🤖 Prompt for AI Agents
In
app/src/main/java/com/hsLink/hslink/presentation/mypage/navigation/career/CareerEditNavigation.kt
around lines 21–31, do not default a missing careerId to 0L; instead preserve
the nullability and pass a nullable id to the route so the screen can treat a
missing id as “create” rather than attempting to load/update id 0. Change the
extraction to produce a Long? (or pass null) when the argument is absent and
forward that nullable value into CareerEditRoute (and ensure the route/screen
handles null appropriately), removing the sentinel 0L fallback which causes
bogus API calls.

Comment on lines +66 to +77
LaunchedEffect(Unit) {
viewModel.loadUserSummary() // 또는 loadMypage()
}

// ← 프로필 수정 후 돌아왔을 때 새로고침
val currentBackStackEntry by navController.currentBackStackEntryAsState()
LaunchedEffect(currentBackStackEntry) {
// 프로필 수정 화면에서 돌아왔을 때만 새로고침
if (currentBackStackEntry?.destination?.route?.contains("mypage") == true) {
viewModel.loadUserSummary()
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid double-loading user summary on first render.

Both LaunchedEffect(Unit) and the back-stack LaunchedEffect fire on first composition because the current destination already contains “mypage”, so the screen fires two identical loadUserSummary() requests back-to-back. That extra network call shows up as duplicate loading and wastes bandwidth. Gate the second effect’s first emission (and add the needed remember/mutableStateOf imports) so it only refreshes when you actually return from another screen.

+    var hasHandledInitialBackStack by remember { mutableStateOf(false) }
@@
-    LaunchedEffect(currentBackStackEntry) {
-        // 프로필 수정 화면에서 돌아왔을 때만 새로고침
-        if (currentBackStackEntry?.destination?.route?.contains("mypage") == true) {
-            viewModel.loadUserSummary()
-        }
-    }
+    LaunchedEffect(currentBackStackEntry) {
+        if (!hasHandledInitialBackStack) {
+            hasHandledInitialBackStack = true
+            return@LaunchedEffect
+        }
+        if (currentBackStackEntry?.destination?.route?.contains("mypage") == true) {
+            viewModel.loadUserSummary()
+        }
+    }
📝 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
LaunchedEffect(Unit) {
viewModel.loadUserSummary() // 또는 loadMypage()
}
// ← 프로필 수정 후 돌아왔을 때 새로고침
val currentBackStackEntry by navController.currentBackStackEntryAsState()
LaunchedEffect(currentBackStackEntry) {
// 프로필 수정 화면에서 돌아왔을 때만 새로고침
if (currentBackStackEntry?.destination?.route?.contains("mypage") == true) {
viewModel.loadUserSummary()
}
}
LaunchedEffect(Unit) {
viewModel.loadUserSummary() // 또는 loadMypage()
}
// ← 프로필 수정 후 돌아왔을 때 새로고침
val currentBackStackEntry by navController.currentBackStackEntryAsState()
var hasHandledInitialBackStack by remember { mutableStateOf(false) }
LaunchedEffect(currentBackStackEntry) {
if (!hasHandledInitialBackStack) {
hasHandledInitialBackStack = true
return@LaunchedEffect
}
if (currentBackStackEntry?.destination?.route?.contains("mypage") == true) {
viewModel.loadUserSummary()
}
}

Comment on lines 357 to +365
Box {
CareerCard(
name = "투썸플레이스",
title = "영업",
dateRange = "2024.02 ~ 2024.10", // ← subtitle을 dateRange로 변경
onClick = onCareerClick
)
careers.forEach { career ->
CareerCard(
name = career.companyName,
title = career.position,
dateRange = "${career.startYm} ~ ${career.endYm ?: "현재"}",
onClick = { onCareerClick(career) }
)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix overlapping career cards rendering

Line 359 currently iterates inside a Box, which stacks each CareerCard in the same position. With multiple careers, only the last card remains visible/clickable. Please replace the Box with a vertical layout (Column/LazyColumn) so the cards render as a list.

-                Box {
-                    careers.forEach { career ->
-                        CareerCard(
-                            name = career.companyName,
-                            title = career.position,
-                            dateRange = "${career.startYm} ~ ${career.endYm ?: "현재"}",
-                            onClick = { onCareerClick(career) }
-                        )
-                    }
-                }
+                Column(
+                    verticalArrangement = Arrangement.spacedBy(12.dp)
+                ) {
+                    careers.forEach { career ->
+                        CareerCard(
+                            name = career.companyName,
+                            title = career.position,
+                            dateRange = "${career.startYm} ~ ${career.endYm ?: "현재"}",
+                            onClick = { onCareerClick(career) }
+                        )
+                    }
+                }
📝 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
Box {
CareerCard(
name = "투썸플레이스",
title = "영업",
dateRange = "2024.02 ~ 2024.10", // ← subtitle을 dateRange로 변경
onClick = onCareerClick
)
careers.forEach { career ->
CareerCard(
name = career.companyName,
title = career.position,
dateRange = "${career.startYm} ~ ${career.endYm ?: "현재"}",
onClick = { onCareerClick(career) }
)
}
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
careers.forEach { career ->
CareerCard(
name = career.companyName,
title = career.position,
dateRange = "${career.startYm} ~ ${career.endYm ?: "현재"}",
onClick = { onCareerClick(career) }
)
}
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/hsLink/hslink/presentation/mypage/screen/profile/ProfileEditScreen.kt
around lines 357 to 365, the code uses Box { careers.forEach { ... } } which
stacks every CareerCard in the same position causing overlap; replace the Box
with a vertical layout — either Column { careers.forEach { ... } } for a simple
static list or LazyColumn { items(careers) { career -> CareerCard(...) } } for a
performant scrollable list — ensure you remove the Box and use appropriate
imports and modifiers (e.g., Modifier.fillMaxWidth() and spacing/padding) so
each CareerCard is laid out vertically and remains clickable.

Comment on lines +91 to +106
private fun getUserSummary() {
viewModelScope.launch {
_isLoading.value = true
Log.d(TAG, "Summary API 호출 시작")

mypageRepository.getUserSummary().fold(
onSuccess = { summary ->
_userSummary.value = summary
Log.d(TAG, "Summary API 성공: ${summary.name}")
},
onFailure = { exception ->
Log.e(TAG, "Summary API 실패", exception)
}
).also {
_isLoading.value = false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restore error propagation for summary fetch.

getUserSummary() never touches _error, so a failed summary call doesn't surface to the UI and a previous error message lingers even after a successful refresh. That breaks the screen's ability to report failures and to clear stale errors after recovering. Please set _error on both branches so the state stays accurate.

-            mypageRepository.getUserSummary().fold(
-                onSuccess = { summary ->
-                    _userSummary.value = summary
-                    Log.d(TAG, "Summary API 성공: ${summary.name}")
-                },
-                onFailure = { exception ->
-                    Log.e(TAG, "Summary API 실패", exception)
-                }
-            ).also {
-                _isLoading.value = false
-            }
+            mypageRepository.getUserSummary().fold(
+                onSuccess = { summary ->
+                    _userSummary.value = summary
+                    _error.value = null
+                    Log.d(TAG, "Summary API 성공: ${summary.name}")
+                },
+                onFailure = { exception ->
+                    _error.value = exception.message
+                    Log.e(TAG, "Summary API 실패", exception)
+                }
+            ).also {
+                _isLoading.value = false
+            }
🤖 Prompt for AI Agents
In
app/src/main/java/com/hsLink/hslink/presentation/mypage/viewmodel/MypageViewModel.kt
around lines 91 to 106, the getUserSummary() call never updates the _error state
so failures aren’t surfaced and stale errors persist; update the onSuccess
branch to clear _error (e.g., _error.value = null) and update the onFailure
branch to set _error to a descriptive message or the exception message/object,
ensuring both success and failure paths update _error and leave _isLoading
handling as-is.

@dmp100 dmp100 added feature ✨ 기능 구현 규현 🐻 규현 전용 라벨 labels Nov 13, 2025
@coderabbitai coderabbitai bot mentioned this pull request Nov 13, 2025
@vvan2 vvan2 changed the title Feat/#28 mypage api [feat/#28] mypage api Nov 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature ✨ 기능 구현 규현 🐻 규현 전용 라벨

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 마이페이지 API

2 participants