This commit is contained in:
Trey t
2025-11-05 20:31:01 -06:00
parent fe99f67f81
commit bc289d6c88
17 changed files with 819 additions and 203 deletions

View File

@@ -126,6 +126,8 @@ fun HomeScreen(
is ApiResult.Error -> {
// Don't show error card, just let navigation cards show
}
else -> {}
}
// Residences Card

View File

@@ -21,7 +21,7 @@ import com.mycrib.shared.network.ApiResult
@Composable
fun LoginScreen(
onLoginSuccess: () -> Unit,
onLoginSuccess: (com.mycrib.shared.models.User) -> Unit,
onNavigateToRegister: () -> Unit,
viewModel: AuthViewModel = viewModel { AuthViewModel() }
) {
@@ -33,7 +33,8 @@ fun LoginScreen(
LaunchedEffect(loginState) {
when (loginState) {
is ApiResult.Success -> {
onLoginSuccess()
val user = (loginState as ApiResult.Success).data.user
onLoginSuccess(user)
}
else -> {}
}

View File

@@ -547,9 +547,13 @@ fun ResidenceDetailScreen(
}
}
}
else -> {}
}
}
}
else -> {}
}
}
}

View File

@@ -298,6 +298,8 @@ fun ResidencesScreen(
}
}
}
else -> {}
}
}
}

View File

@@ -105,6 +105,8 @@ fun TasksScreen(
}
}
}
else -> {}
}
}
}

View File

@@ -0,0 +1,195 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.mycrib.android.ui.components.auth.AuthHeader
import com.mycrib.android.ui.components.common.ErrorCard
import com.mycrib.android.viewmodel.AuthViewModel
import com.mycrib.shared.network.ApiResult
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun VerifyEmailScreen(
onVerifySuccess: () -> Unit,
onLogout: () -> Unit,
viewModel: AuthViewModel = viewModel { AuthViewModel() }
) {
var code by remember { mutableStateOf("") }
var errorMessage by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
val verifyState by viewModel.verifyEmailState.collectAsState()
LaunchedEffect(verifyState) {
when (verifyState) {
is ApiResult.Success -> {
viewModel.resetVerifyEmailState()
onVerifySuccess()
}
is ApiResult.Error -> {
errorMessage = (verifyState as ApiResult.Error).message
isLoading = false
}
is ApiResult.Loading -> {
isLoading = true
errorMessage = ""
}
else -> {}
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Verify Email", fontWeight = FontWeight.SemiBold) },
actions = {
TextButton(onClick = onLogout) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.Logout,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Text("Logout")
}
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
)
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Spacer(modifier = Modifier.height(8.dp))
AuthHeader(
icon = Icons.Default.MarkEmailRead,
title = "Verify Your Email",
subtitle = "You must verify your email address to continue"
)
Spacer(modifier = Modifier.height(16.dp))
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
),
shape = RoundedCornerShape(12.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
Icons.Default.Info,
contentDescription = null,
tint = MaterialTheme.colorScheme.onErrorContainer
)
Text(
text = "Email verification is required. Check your inbox for a 6-digit code.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold
)
}
}
OutlinedTextField(
value = code,
onValueChange = {
if (it.length <= 6 && it.all { char -> char.isDigit() }) {
code = it
}
},
label = { Text("Verification Code") },
leadingIcon = {
Icon(Icons.Default.Pin, contentDescription = null)
},
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(12.dp),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
placeholder = { Text("000000") }
)
if (errorMessage.isNotEmpty()) {
ErrorCard(
message = errorMessage
)
}
Button(
onClick = {
if (code.length == 6) {
isLoading = true
viewModel.verifyEmail(code)
} else {
errorMessage = "Please enter a valid 6-digit code"
}
},
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
shape = RoundedCornerShape(12.dp),
enabled = !isLoading && code.length == 6
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.onPrimary
)
} else {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.CheckCircle, contentDescription = null)
Text(
"Verify Email",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Didn't receive the code? Check your spam folder or contact support.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
}
}