diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml
index ab643ef..038ff16 100644
--- a/composeApp/src/androidMain/AndroidManifest.xml
+++ b/composeApp/src/androidMain/AndroidManifest.xml
@@ -19,6 +19,18 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/composeApp/src/androidMain/kotlin/com/example/mycrib/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/example/mycrib/MainActivity.kt
index 80b2733..628159c 100644
--- a/composeApp/src/androidMain/kotlin/com/example/mycrib/MainActivity.kt
+++ b/composeApp/src/androidMain/kotlin/com/example/mycrib/MainActivity.kt
@@ -1,10 +1,15 @@
package com.example.mycrib
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import com.mycrib.storage.TokenManager
import com.mycrib.storage.TokenStorage
@@ -12,6 +17,8 @@ import com.mycrib.storage.TaskCacheManager
import com.mycrib.storage.TaskCacheStorage
class MainActivity : ComponentActivity() {
+ private var deepLinkResetToken by mutableStateOf(null)
+
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
@@ -22,8 +29,33 @@ class MainActivity : ComponentActivity() {
// Initialize TaskCacheStorage for offline task caching
TaskCacheStorage.initialize(TaskCacheManager.getInstance(applicationContext))
+ // Handle deep link from intent
+ handleDeepLink(intent)
+
setContent {
- App()
+ App(
+ deepLinkResetToken = deepLinkResetToken,
+ onClearDeepLinkToken = {
+ deepLinkResetToken = null
+ }
+ )
+ }
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ handleDeepLink(intent)
+ }
+
+ private fun handleDeepLink(intent: Intent?) {
+ val data: Uri? = intent?.data
+ if (data != null && data.scheme == "mycrib" && data.host == "reset-password") {
+ // Extract token from query parameter
+ val token = data.getQueryParameter("token")
+ if (token != null) {
+ deepLinkResetToken = token
+ println("Deep link received with token: $token")
+ }
}
}
}
@@ -31,5 +63,5 @@ class MainActivity : ComponentActivity() {
@Preview
@Composable
fun AppAndroidPreview() {
- App()
+ App(deepLinkResetToken = null)
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/App.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/App.kt
index b058305..95ed65e 100644
--- a/composeApp/src/commonMain/kotlin/com/example/mycrib/App.kt
+++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/App.kt
@@ -19,13 +19,18 @@ import androidx.compose.ui.Modifier
import com.mycrib.android.ui.screens.AddResidenceScreen
import com.mycrib.android.ui.screens.EditResidenceScreen
import com.mycrib.android.ui.screens.EditTaskScreen
+import com.mycrib.android.ui.screens.ForgotPasswordScreen
import com.mycrib.android.ui.screens.HomeScreen
import com.mycrib.android.ui.screens.LoginScreen
import com.mycrib.android.ui.screens.RegisterScreen
+import com.mycrib.android.ui.screens.ResetPasswordScreen
import com.mycrib.android.ui.screens.ResidenceDetailScreen
import com.mycrib.android.ui.screens.ResidencesScreen
import com.mycrib.android.ui.screens.TasksScreen
import com.mycrib.android.ui.screens.VerifyEmailScreen
+import com.mycrib.android.ui.screens.VerifyResetCodeScreen
+import com.mycrib.android.viewmodel.PasswordResetViewModel
+import androidx.lifecycle.viewmodel.compose.viewModel
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
@@ -53,7 +58,10 @@ import mycrib.composeapp.generated.resources.compose_multiplatform
@Composable
@Preview
-fun App() {
+fun App(
+ deepLinkResetToken: String? = null,
+ onClearDeepLinkToken: () -> Unit = {}
+) {
var isLoggedIn by remember { mutableStateOf(TokenStorage.hasToken()) }
var isVerified by remember { mutableStateOf(false) }
var isCheckingAuth by remember { mutableStateOf(true) }
@@ -105,6 +113,7 @@ fun App() {
}
val startDestination = when {
+ deepLinkResetToken != null -> ForgotPasswordRoute
!isLoggedIn -> LoginRoute
!isVerified -> VerifyEmailRoute
else -> MainRoute
@@ -139,6 +148,9 @@ fun App() {
},
onNavigateToRegister = {
navController.navigate(RegisterRoute)
+ },
+ onNavigateToForgotPassword = {
+ navController.navigate(ForgotPasswordRoute)
}
)
}
@@ -160,6 +172,71 @@ fun App() {
)
}
+ composable { backStackEntry ->
+ // Create shared ViewModel for all password reset screens
+ val parentEntry = remember(backStackEntry) {
+ navController.getBackStackEntry()
+ }
+ val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) {
+ PasswordResetViewModel(deepLinkToken = deepLinkResetToken)
+ }
+
+ ForgotPasswordScreen(
+ onNavigateBack = {
+ // Clear deep link token when navigating back to login
+ onClearDeepLinkToken()
+ navController.popBackStack()
+ },
+ onNavigateToVerify = {
+ navController.navigate(VerifyResetCodeRoute)
+ },
+ onNavigateToReset = {
+ navController.navigate(ResetPasswordRoute)
+ },
+ viewModel = passwordResetViewModel
+ )
+ }
+
+ composable { backStackEntry ->
+ // Use shared ViewModel from ForgotPasswordRoute
+ val parentEntry = remember(backStackEntry) {
+ navController.getBackStackEntry()
+ }
+ val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel() }
+
+ VerifyResetCodeScreen(
+ onNavigateBack = {
+ navController.popBackStack()
+ },
+ onNavigateToReset = {
+ navController.navigate(ResetPasswordRoute)
+ },
+ viewModel = passwordResetViewModel
+ )
+ }
+
+ composable { backStackEntry ->
+ // Use shared ViewModel from ForgotPasswordRoute
+ val parentEntry = remember(backStackEntry) {
+ navController.getBackStackEntry()
+ }
+ val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel() }
+
+ ResetPasswordScreen(
+ onPasswordResetSuccess = {
+ // Clear deep link token and navigate back to login after successful password reset
+ onClearDeepLinkToken()
+ navController.navigate(LoginRoute) {
+ popUpTo { inclusive = true }
+ }
+ },
+ onNavigateBack = {
+ navController.popBackStack()
+ },
+ viewModel = passwordResetViewModel
+ )
+ }
+
composable {
VerifyEmailScreen(
onVerifySuccess = {
diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/models/User.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/models/User.kt
index 8287847..5e17529 100644
--- a/composeApp/src/commonMain/kotlin/com/example/mycrib/models/User.kt
+++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/models/User.kt
@@ -69,3 +69,38 @@ data class UpdateProfileRequest(
@SerialName("last_name") val lastName: String? = null,
val email: String? = null
)
+
+// Password Reset Models
+@Serializable
+data class ForgotPasswordRequest(
+ val email: String
+)
+
+@Serializable
+data class ForgotPasswordResponse(
+ val message: String
+)
+
+@Serializable
+data class VerifyResetCodeRequest(
+ val email: String,
+ val code: String
+)
+
+@Serializable
+data class VerifyResetCodeResponse(
+ val message: String,
+ @SerialName("reset_token") val resetToken: String
+)
+
+@Serializable
+data class ResetPasswordRequest(
+ @SerialName("reset_token") val resetToken: String,
+ @SerialName("new_password") val newPassword: String,
+ @SerialName("confirm_password") val confirmPassword: String
+)
+
+@Serializable
+data class ResetPasswordResponse(
+ val message: String
+)
diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/navigation/Routes.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/navigation/Routes.kt
index 91b49fb..b149c79 100644
--- a/composeApp/src/commonMain/kotlin/com/example/mycrib/navigation/Routes.kt
+++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/navigation/Routes.kt
@@ -85,3 +85,12 @@ object MainTabTasksRoute
@Serializable
object MainTabProfileRoute
+
+@Serializable
+object ForgotPasswordRoute
+
+@Serializable
+object VerifyResetCodeRoute
+
+@Serializable
+object ResetPasswordRoute
diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/network/AuthApi.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/network/AuthApi.kt
index 9ad4f9c..828f69a 100644
--- a/composeApp/src/commonMain/kotlin/com/example/mycrib/network/AuthApi.kt
+++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/network/AuthApi.kt
@@ -120,4 +120,84 @@ class AuthApi(private val client: HttpClient = ApiClient.httpClient) {
ApiResult.Error(e.message ?: "Unknown error occurred")
}
}
+
+ // Password Reset Methods
+ suspend fun forgotPassword(request: ForgotPasswordRequest): ApiResult {
+ return try {
+ val response = client.post("$baseUrl/auth/forgot-password/") {
+ contentType(ContentType.Application.Json)
+ setBody(request)
+ }
+
+ if (response.status.isSuccess()) {
+ ApiResult.Success(response.body())
+ } else {
+ val errorBody = try {
+ response.body