From 15e5fa1bb37273fea64bb7f67decda941ba4b69e Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 21:44:41 +0900 Subject: [PATCH 01/18] =?UTF-8?q?[CHORE]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=99=95=EC=9D=B8=20dto=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/ClubCodeConfirmResponseDto.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ClubCodeConfirmResponseDto.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ClubCodeConfirmResponseDto.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ClubCodeConfirmResponseDto.kt new file mode 100644 index 0000000..197b6b3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ClubCodeConfirmResponseDto.kt @@ -0,0 +1,24 @@ +package org.whosin.client.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ClubCodeConfirmResponseDto( + @SerialName("success") + val success: Boolean, + @SerialName("status") + val status: Int, + @SerialName("message") + val message: String, + @SerialName("data") + val data: ClubCodeConfirmData +) + +@Serializable +data class ClubCodeConfirmData( + @SerialName("clubId") + val clubId: Int, + @SerialName("clubName") + val clubName: String +) From fde50f8df7b9d14b8b691d4005464956ed4012bb Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 21:57:39 +0900 Subject: [PATCH 02/18] =?UTF-8?q?[CHORE]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20dto=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/dto/response/AddClubResponseDto.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/AddClubResponseDto.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/AddClubResponseDto.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/AddClubResponseDto.kt new file mode 100644 index 0000000..bde6b8f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/AddClubResponseDto.kt @@ -0,0 +1,16 @@ +package org.whosin.client.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AddClubResponseDto( + @SerialName("success") + val success: Boolean, + @SerialName("status") + val status: Int, + @SerialName("message") + val message: String, + @SerialName("data") + val data: String? = null +) From a1c8c25f5c4816c812c3cf7b38d820cb02d22918 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 21:58:38 +0900 Subject: [PATCH 03/18] =?UTF-8?q?[CHORE]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=ED=99=94=EB=A9=B4=20viewmodel=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=ED=8B=80=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/auth/clubcode/AddClubViewModel.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt new file mode 100644 index 0000000..4403760 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt @@ -0,0 +1,12 @@ +package org.whosin.client.presentation.auth.clubcode + +import org.whosin.client.data.repository.ClubRepository + +class AddClubViewModel( + private val repository: ClubRepository +) { + + // TODO: 동아리 번호 확인 함수 + + // TODO: 동아리 추가 함수 +} \ No newline at end of file From d2e3267bd0feda192ef68003c30a6a63e998b180 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 22:02:09 +0900 Subject: [PATCH 04/18] =?UTF-8?q?[CHORE]:=20datasource=EC=97=90=20?= =?UTF-8?q?=EB=8F=99=EC=95=84=EB=A6=AC=20=EC=B6=94=EA=B0=80,=20=EB=8F=99?= =?UTF-8?q?=EC=95=84=EB=A6=AC=20=EC=BD=94=EB=93=9C=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/remote/RemoteClubDataSource.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt index 7cb797b..fabc79a 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt @@ -4,10 +4,14 @@ import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.delete import io.ktor.client.request.get +import io.ktor.client.request.parameter import io.ktor.client.request.post import io.ktor.client.statement.HttpResponse import io.ktor.http.isSuccess +import io.ktor.http.parameters import org.whosin.client.core.network.ApiResult +import org.whosin.client.data.dto.response.AddClubResponseDto +import org.whosin.client.data.dto.response.ClubCodeConfirmResponseDto import org.whosin.client.data.dto.response.ClubPresencesResponseDto import org.whosin.client.data.dto.response.MyClubResponseDto @@ -93,4 +97,48 @@ class RemoteClubDataSource( ApiResult.Error(message = t.message, cause = t) } } + + // 동아리 번호 확인 + suspend fun confirmClubCode(clubCode: String): ApiResult{ + return try { + val response: HttpResponse = client.get(urlString = "clubs"){ + parameter("clubNumber", clubCode) + } + + if (response.status.isSuccess()) { + ApiResult.Success( + data = response.body(), + statusCode = response.status.value + ) + } else { + ApiResult.Error( + code = response.status.value, + message = "HTTP Error: ${response.status.value}" + ) + } + } catch (t: Throwable){ + ApiResult.Error(message = t.message, cause = t) + } + } + + // 동아리 추가 함수 + suspend fun addClub(clubId: Int): ApiResult { + return try { + val response: HttpResponse = client.get(urlString = "clubs/$clubId") + + if (response.status.isSuccess()) { + ApiResult.Success( + data = response.body(), + statusCode = response.status.value + ) + } else { + ApiResult.Error( + code = response.status.value, + message = "HTTP Error: ${response.status.value}" + ) + } + } catch (t: Throwable){ + ApiResult.Error(message = t.message, cause = t) + } + } } \ No newline at end of file From d8676dcbc114b27041366379d13d28e206da1fa6 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 22:02:19 +0900 Subject: [PATCH 05/18] =?UTF-8?q?[CHORE]:=20repository=EC=97=90=20?= =?UTF-8?q?=EB=8F=99=EC=95=84=EB=A6=AC=20=EC=B6=94=EA=B0=80,=20=EB=8F=99?= =?UTF-8?q?=EC=95=84=EB=A6=AC=20=EC=BD=94=EB=93=9C=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/whosin/client/data/repository/ClubRepository.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/data/repository/ClubRepository.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/data/repository/ClubRepository.kt index a11850c..37452aa 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/data/repository/ClubRepository.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/data/repository/ClubRepository.kt @@ -1,6 +1,8 @@ package org.whosin.client.data.repository import org.whosin.client.core.network.ApiResult +import org.whosin.client.data.dto.response.AddClubResponseDto +import org.whosin.client.data.dto.response.ClubCodeConfirmResponseDto import org.whosin.client.data.dto.response.ClubPresencesResponseDto import org.whosin.client.data.dto.response.MyClubResponseDto import org.whosin.client.data.remote.RemoteClubDataSource @@ -19,4 +21,10 @@ class ClubRepository( suspend fun checkOut(clubId: Int): ApiResult = dataSource.checkOut(clubId) + + suspend fun confirmClubCode(clubCode: String): ApiResult = + dataSource.confirmClubCode(clubCode) + + suspend fun addClub(clubId: Int): ApiResult = + dataSource.addClub(clubId) } \ No newline at end of file From a08c50aa3baa92a8bf45107049ff63fd3ff2988a Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 22:12:48 +0900 Subject: [PATCH 06/18] =?UTF-8?q?[CHORE]:=20di=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EC=97=90=20addclubviewmodel=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/commonMain/kotlin/org/whosin/client/di/DIModules.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt index 8d749dc..4838b27 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt @@ -10,6 +10,7 @@ import org.whosin.client.data.remote.RemoteMemberDataSource import org.whosin.client.data.repository.DummyRepository import org.whosin.client.data.repository.ClubRepository import org.whosin.client.data.repository.MemberRepository +import org.whosin.client.presentation.auth.clubcode.AddClubViewModel import org.whosin.client.presentation.dummy.DummyViewModel import org.whosin.client.presentation.dummy.TokenTestViewModel import org.whosin.client.presentation.auth.login.viewmodel.LoginViewModel @@ -49,4 +50,5 @@ val viewModelModule = module { viewModelOf(::MyPageViewModel) viewModelOf(::DummyViewModel) // TODO: 이후에 삭제 예정 viewModelOf(::TokenTestViewModel) // TODO: 이후에 삭제 예정 + viewModelOf(::AddClubViewModel) } From 7ee58f5e737438868a8dcfbdf547f1e1b6daaf71 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 22:29:18 +0900 Subject: [PATCH 07/18] =?UTF-8?q?[CHORE]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=99=95=EC=9D=B8=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/clubcode/AddClubViewModel.kt | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt index 4403760..4ca13a5 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt @@ -1,12 +1,70 @@ package org.whosin.client.presentation.auth.clubcode +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.whosin.client.core.network.ApiResult import org.whosin.client.data.repository.ClubRepository +data class AddClubUiState( + val isLoading: Boolean = false, + val verificationState: ClubCodeState = ClubCodeState.INPUT, + val clubName: String? = null, + val clubId: Int? = null, + val errorMessage: String? = null +) + class AddClubViewModel( private val repository: ClubRepository -) { +) : ViewModel() { + private val _uiState = MutableStateFlow(AddClubUiState()) + val uiState: StateFlow = _uiState.asStateFlow() - // TODO: 동아리 번호 확인 함수 + fun confirmClubCode(clubCode: String) { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + when (val result = repository.confirmClubCode(clubCode = clubCode)) { + is ApiResult.Success -> { + val response = result.data.data + _uiState.update { + it.copy( + isLoading = false, + verificationState = ClubCodeState.SUCCESS, + clubName = response.clubName, + clubId = response.clubId, + errorMessage = null + ) + } + } + is ApiResult.Error -> { + _uiState.update { + it.copy( + isLoading = false, + errorMessage = result.message ?: "조회시에 오류가 발생했습니다." + ) + } + } + } + } + } // TODO: 동아리 추가 함수 + fun addClub(clubId: Int) { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + when (val result = repository.addClub(clubId = clubId)) { + is ApiResult.Success -> { + + } + + is ApiResult.Error -> { + + } + } + } + } } \ No newline at end of file From 7ce62f5b32de032655879cf3670557a7808f86fb Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 22:32:08 +0900 Subject: [PATCH 08/18] =?UTF-8?q?[FEAT]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/clubcode/AddClubViewModel.kt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt index 4ca13a5..0af08bd 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt @@ -44,7 +44,7 @@ class AddClubViewModel( _uiState.update { it.copy( isLoading = false, - errorMessage = result.message ?: "조회시에 오류가 발생했습니다." + errorMessage = result.message ?: "동아리 이름 조회에 오류가 발생했습니다." ) } } @@ -52,17 +52,26 @@ class AddClubViewModel( } } - // TODO: 동아리 추가 함수 + // 동아리 추가 함수 fun addClub(clubId: Int) { viewModelScope.launch { _uiState.update { it.copy(isLoading = true) } when (val result = repository.addClub(clubId = clubId)) { is ApiResult.Success -> { - + _uiState.update { + it.copy( + isLoading = false, + errorMessage = null + ) + } } - is ApiResult.Error -> { - + _uiState.update { + it.copy( + isLoading = false, + errorMessage = result.message ?: "동아리 추가에 오류가 발생했습니다." + ) + } } } } From ef825a47a75de11ef4819feae23a3d46e8f0e40e Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 22:52:26 +0900 Subject: [PATCH 09/18] =?UTF-8?q?[FEAT]:=20Screen=EC=97=90=EC=84=9C=20uiSt?= =?UTF-8?q?ate=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/clubcode/ClubCodeInputScreen.kt | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt index 0cdec61..caefd98 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt @@ -31,10 +31,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.delay import coil3.compose.AsyncImage import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.viewmodel.koinViewModel import org.whosin.client.presentation.auth.login.component.CommonLoginButton import org.whosin.client.presentation.auth.login.component.NumberInputBox import whosinclient.composeapp.generated.resources.Res @@ -50,15 +52,16 @@ import whosinclient.composeapp.generated.resources.confirm_button fun ClubCodeInputScreen( modifier: Modifier = Modifier, onNavigateBack: () -> Unit = {}, - onNavigateToHome: (String) -> Unit = {}, - onVerifyClubCode: (String) -> Unit = {}, + onNavigateToHome: () -> Unit = {}, onErrorReset: () -> Unit = {}, - verificationState: ClubCodeState = ClubCodeState.INPUT, - clubName: String = "" + verificationState: ClubCodeState = ClubCodeState.INPUT, // TODO: viewmodel로 리팩토링 + viewModel: AddClubViewModel = koinViewModel() ) { + val uiState = viewModel.uiState.collectAsStateWithLifecycle().value + var clubCode by remember { mutableStateOf(arrayOf("", "", "", "", "", "")) } var currentFocusIndex by remember { mutableStateOf(0) } - val currentState = verificationState + val currentState = verificationState // TODO: uiState의 verificationState를 사용하도록 리팩토링 val focusRequesters = remember { List(6) { FocusRequester() } } val keyboardController = LocalSoftwareKeyboardController.current @@ -81,7 +84,7 @@ fun ClubCodeInputScreen( clubCode = arrayOf("", "", "", "", "", "") currentFocusIndex = 0 focusRequesters[0].requestFocus() - onErrorReset() +// onErrorReset() } } @@ -206,7 +209,7 @@ fun ClubCodeInputScreen( text = stringResource(Res.string.club_code_confirm_button), onClick = { if (isComplete) { - onVerifyClubCode(fullCode) + viewModel.confirmClubCode(clubCode = fullCode) } }, enabled = isComplete, @@ -237,7 +240,7 @@ fun ClubCodeInputScreen( contentAlignment = Alignment.Center ) { Text( - text = clubName, + text = uiState.clubName ?: "", fontSize = 16.sp, fontWeight = FontWeight.W500, color = Color.Black, @@ -252,7 +255,12 @@ fun ClubCodeInputScreen( text = stringResource(Res.string.confirm_button), onClick = { if (currentState == ClubCodeState.SUCCESS) { - onNavigateToHome(clubName) + if (uiState.clubId != null){ + viewModel.addClub(uiState.clubId) + // TODO: 추가 완료시 넘어가도록 + } + } else { + println("ClubCodeInputScreen : 확인 버튼 오류") } }, enabled = currentState == ClubCodeState.SUCCESS, @@ -273,17 +281,8 @@ fun ClubCodeInputScreenPreview() { ClubCodeInputScreen( modifier = Modifier, verificationState = verificationState, - clubName = clubName, onNavigateBack = {}, - onNavigateToHome = { name -> println("Navigate to home with: $name") }, - onVerifyClubCode = { code -> - if (code == "123456") { - verificationState = ClubCodeState.SUCCESS - clubName = "메이커스팜" - } else { - verificationState = ClubCodeState.ERROR - } - }, + onNavigateToHome = { }, onErrorReset = { verificationState = ClubCodeState.INPUT } From 777a26d399e4734374e3e998689dc9f78aea6108 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 23:00:03 +0900 Subject: [PATCH 10/18] =?UTF-8?q?[REFACTOR]:=20currentState=EC=97=90=20uiS?= =?UTF-8?q?tate=EC=9D=98=20=EA=B0=92=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/clubcode/AddClubViewModel.kt | 13 +++++++++++++ .../auth/clubcode/ClubCodeInputScreen.kt | 18 +++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt index 0af08bd..fe534d7 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt @@ -44,6 +44,7 @@ class AddClubViewModel( _uiState.update { it.copy( isLoading = false, + verificationState = ClubCodeState.ERROR, errorMessage = result.message ?: "동아리 이름 조회에 오류가 발생했습니다." ) } @@ -52,6 +53,18 @@ class AddClubViewModel( } } + // 에러 상태 리셋 함수 + fun resetErrorState() { + _uiState.update { + it.copy( + verificationState = ClubCodeState.INPUT, + errorMessage = null, + clubName = null, + clubId = null + ) + } + } + // 동아리 추가 함수 fun addClub(clubId: Int) { viewModelScope.launch { diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt index caefd98..64fca26 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt @@ -53,15 +53,13 @@ fun ClubCodeInputScreen( modifier: Modifier = Modifier, onNavigateBack: () -> Unit = {}, onNavigateToHome: () -> Unit = {}, - onErrorReset: () -> Unit = {}, - verificationState: ClubCodeState = ClubCodeState.INPUT, // TODO: viewmodel로 리팩토링 viewModel: AddClubViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsStateWithLifecycle().value var clubCode by remember { mutableStateOf(arrayOf("", "", "", "", "", "")) } var currentFocusIndex by remember { mutableStateOf(0) } - val currentState = verificationState // TODO: uiState의 verificationState를 사용하도록 리팩토링 + val currentState = uiState.verificationState val focusRequesters = remember { List(6) { FocusRequester() } } val keyboardController = LocalSoftwareKeyboardController.current @@ -84,7 +82,7 @@ fun ClubCodeInputScreen( clubCode = arrayOf("", "", "", "", "", "") currentFocusIndex = 0 focusRequesters[0].requestFocus() -// onErrorReset() + viewModel.resetErrorState() } } @@ -256,7 +254,8 @@ fun ClubCodeInputScreen( onClick = { if (currentState == ClubCodeState.SUCCESS) { if (uiState.clubId != null){ - viewModel.addClub(uiState.clubId) +// viewModel.addClub(uiState.clubId) + println("확인 버튼 클릭") // TODO: 추가 완료시 넘어가도록 } } else { @@ -275,16 +274,9 @@ fun ClubCodeInputScreen( @Preview @Composable fun ClubCodeInputScreenPreview() { - var verificationState by remember { mutableStateOf(ClubCodeState.INPUT) } - var clubName by remember { mutableStateOf("") } - ClubCodeInputScreen( modifier = Modifier, - verificationState = verificationState, onNavigateBack = {}, - onNavigateToHome = { }, - onErrorReset = { - verificationState = ClubCodeState.INPUT - } + onNavigateToHome = { } ) } \ No newline at end of file From 5238962f2e3ebf476b7677b9e9a3b2cd980460db Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 23:11:23 +0900 Subject: [PATCH 11/18] =?UTF-8?q?[CHORE]:=20API=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=EC=8B=9C=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/data/dto/response/ErrorResponseDto.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ErrorResponseDto.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ErrorResponseDto.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ErrorResponseDto.kt new file mode 100644 index 0000000..41ce98e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ErrorResponseDto.kt @@ -0,0 +1,16 @@ +package org.whosin.client.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ErrorResponseDto( + @SerialName("success") + val success: Boolean, + @SerialName("status") + val status: Int, + @SerialName("message") + val message: String, + @SerialName("timestamp") + val timestamp: String? = null +) From 3c6a24e40f0fda873ae5bceb98bf48fc9b1f7c12 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 23:12:11 +0900 Subject: [PATCH 12/18] =?UTF-8?q?[REFACTOR]:=20=EB=8F=99=EC=95=84=EB=A6=AC?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=ED=99=95=EC=9D=B8=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EC=8B=A4=ED=8C=A8=EC=8B=9C=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/remote/RemoteClubDataSource.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt index fabc79a..1b8750d 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt @@ -13,6 +13,7 @@ import org.whosin.client.core.network.ApiResult import org.whosin.client.data.dto.response.AddClubResponseDto import org.whosin.client.data.dto.response.ClubCodeConfirmResponseDto import org.whosin.client.data.dto.response.ClubPresencesResponseDto +import org.whosin.client.data.dto.response.ErrorResponseDto import org.whosin.client.data.dto.response.MyClubResponseDto class RemoteClubDataSource( @@ -111,10 +112,20 @@ class RemoteClubDataSource( statusCode = response.status.value ) } else { - ApiResult.Error( - code = response.status.value, - message = "HTTP Error: ${response.status.value}" - ) + // 에러 응답 파싱 시도 + try { + val errorResponse: ErrorResponseDto = response.body() + ApiResult.Error( + code = response.status.value, + message = errorResponse.message + ) + } catch (e: Exception) { + // 파싱 실패 시 기본 에러 메시지 + ApiResult.Error( + code = response.status.value, + message = "HTTP Error: ${response.status.value}" + ) + } } } catch (t: Throwable){ ApiResult.Error(message = t.message, cause = t) From de3165415b4ce40dc1e7d42c42596be5b6c74d06 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 23:13:04 +0900 Subject: [PATCH 13/18] =?UTF-8?q?[UI]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=99=95=EC=9D=B8=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EB=A9=94=EC=84=B8=EC=A7=80=EB=A5=BC=20ui?= =?UTF-8?q?=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/clubcode/AddClubViewModel.kt | 4 +++- .../client/presentation/auth/clubcode/ClubCodeInputScreen.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt index fe534d7..17fb89a 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt @@ -39,15 +39,17 @@ class AddClubViewModel( errorMessage = null ) } + println("AddClubViewModel : 조회 성공") } is ApiResult.Error -> { _uiState.update { it.copy( isLoading = false, verificationState = ClubCodeState.ERROR, - errorMessage = result.message ?: "동아리 이름 조회에 오류가 발생했습니다." + errorMessage = result.message?: "동아리 이름 조회에 오류가 발생했습니다." ) } + println("AddClubViewModel : 조회 실패") } } } diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt index 64fca26..18b70a3 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt @@ -186,7 +186,7 @@ fun ClubCodeInputScreen( // 에러 메시지 if (currentState == ClubCodeState.ERROR) { Text( - text = stringResource(Res.string.club_code_error_message), + text = uiState.errorMessage?:"예상치 못한 오류가 발생했습니다", fontSize = 16.sp, fontWeight = FontWeight.W500, color = Color(0xFFFF3636), From a2188920da82c5c7b7ba97b922db40be8ae8d487 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 23:34:12 +0900 Subject: [PATCH 14/18] =?UTF-8?q?[CHORE]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=9D=91=EB=8B=B5=20=EC=8B=A4=ED=8C=A8?= =?UTF-8?q?=EC=8B=9C=20=ED=8C=8C=EC=8B=B1=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/remote/RemoteClubDataSource.kt | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt index 1b8750d..1b07754 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt @@ -135,7 +135,7 @@ class RemoteClubDataSource( // 동아리 추가 함수 suspend fun addClub(clubId: Int): ApiResult { return try { - val response: HttpResponse = client.get(urlString = "clubs/$clubId") + val response: HttpResponse = client.post(urlString = "clubs/$clubId") if (response.status.isSuccess()) { ApiResult.Success( @@ -143,10 +143,20 @@ class RemoteClubDataSource( statusCode = response.status.value ) } else { - ApiResult.Error( - code = response.status.value, - message = "HTTP Error: ${response.status.value}" - ) + // 에러 응답 파싱 시도 + try { + val errorResponse: ErrorResponseDto = response.body() + ApiResult.Error( + code = response.status.value, + message = errorResponse.message + ) + } catch (e: Exception) { + // 파싱 실패 시 기본 에러 메시지 + ApiResult.Error( + code = response.status.value, + message = "HTTP Error: ${response.status.value}" + ) + } } } catch (t: Throwable){ ApiResult.Error(message = t.message, cause = t) From f68a663867bb7e6294fe057ef6ea95d11417566c Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 23:34:49 +0900 Subject: [PATCH 15/18] =?UTF-8?q?[FEAT]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=9D=91=EB=8B=B5=20=EC=97=AC=EB=B6=80?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EB=B3=80=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?uistate=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/clubcode/AddClubViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt index 17fb89a..58bb3cc 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/AddClubViewModel.kt @@ -15,7 +15,8 @@ data class AddClubUiState( val verificationState: ClubCodeState = ClubCodeState.INPUT, val clubName: String? = null, val clubId: Int? = null, - val errorMessage: String? = null + val errorMessage: String? = null, + val isAddClubSuccess: Boolean = false ) class AddClubViewModel( @@ -76,7 +77,8 @@ class AddClubViewModel( _uiState.update { it.copy( isLoading = false, - errorMessage = null + errorMessage = null, + isAddClubSuccess = true ) } } From 481752bdb0933c2740ee2681a318c1b5bf688340 Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 23:35:03 +0900 Subject: [PATCH 16/18] =?UTF-8?q?[FEAT]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=8B=A4=ED=8C=A8=EC=8B=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EB=A9=94=EC=84=B8=EC=A7=80=EB=A5=BC=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/clubcode/ClubCodeInputScreen.kt | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt index 18b70a3..7863c88 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt @@ -86,6 +86,13 @@ fun ClubCodeInputScreen( } } + // 동아리 추가 성공 시 홈으로 이동 + LaunchedEffect(uiState.isAddClubSuccess) { + if (uiState.isAddClubSuccess) { + onNavigateToHome() + } + } + val fullCode = clubCode.joinToString("") val isComplete = fullCode.length == 6 @@ -197,6 +204,7 @@ fun ClubCodeInputScreen( ) } + Box( modifier = Modifier .padding(top = if (currentState != ClubCodeState.ERROR) 48.dp else 0.dp) @@ -245,6 +253,20 @@ fun ClubCodeInputScreen( textAlign = TextAlign.Center ) } + + // 동아리 추가 실패 에러 메시지 + if (uiState.errorMessage != null && !uiState.isAddClubSuccess) { + Text( + text = uiState.errorMessage, + fontSize = 14.sp, + fontWeight = FontWeight.W500, + color = Color(0xFFFF3636), + textAlign = TextAlign.Center, + modifier = Modifier + .padding(top = 16.dp) + .fillMaxWidth() + ) + } } } @@ -254,9 +276,7 @@ fun ClubCodeInputScreen( onClick = { if (currentState == ClubCodeState.SUCCESS) { if (uiState.clubId != null){ -// viewModel.addClub(uiState.clubId) - println("확인 버튼 클릭") - // TODO: 추가 완료시 넘어가도록 + viewModel.addClub(uiState.clubId) } } else { println("ClubCodeInputScreen : 확인 버튼 오류") From f626ac2385141ded258d787aeba53b3ff84f68ba Mon Sep 17 00:00:00 2001 From: casperjr Date: Fri, 3 Oct 2025 23:41:08 +0900 Subject: [PATCH 17/18] =?UTF-8?q?[FEAT]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=84=B1=EA=B3=B5=EC=8B=9C=20=ED=82=A4?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EC=88=A8=EA=B9=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/clubcode/ClubCodeInputScreen.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt index 7863c88..10d3dbb 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt @@ -84,6 +84,9 @@ fun ClubCodeInputScreen( focusRequesters[0].requestFocus() viewModel.resetErrorState() } + if (currentState == ClubCodeState.SUCCESS){ + keyboardController?.hide() + } } // 동아리 추가 성공 시 홈으로 이동 From 16e926471050661aa24c7dae24c57123a103f0b2 Mon Sep 17 00:00:00 2001 From: casperjr Date: Sat, 4 Oct 2025 00:01:51 +0900 Subject: [PATCH 18/18] =?UTF-8?q?[FIX]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=84=B1=EA=B3=B5=EC=8B=9C=20=ED=82=A4?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EC=88=A8=EA=B9=80=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/clubcode/ClubCodeInputScreen.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt index 10d3dbb..037e787 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt @@ -219,6 +219,9 @@ fun ClubCodeInputScreen( onClick = { if (isComplete) { viewModel.confirmClubCode(clubCode = fullCode) + if (currentState == ClubCodeState.SUCCESS){ + keyboardController?.hide() + } } }, enabled = isComplete,