Add client-side username check in auth flow

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2026-07-22 18:27:04 +03:00
Unverified
parent de21d7c217
commit f022abcf2a
3 changed files with 17 additions and 2 deletions
@@ -31,6 +31,7 @@
<string name="display_name_error">От 1 до 64 символов</string>
<string name="fill_all_fields">Заполните все поля</string>
<string name="username_length_error">Имя пользователя — от 3 до 20 символов</string>
<string name="username_chars_error">Имя пользователя может содержать только английские буквы, цифры, дефисы и подчеркивания</string>
<string name="password_length_error">Пароль — от 5 до 50 символов</string>
<string name="passwords_dont_match">Пароли не совпадают</string>
<string name="auth_welcome_title">Добро пожаловать в FromChat</string>
@@ -38,6 +38,7 @@
<!-- Validation Errors -->
<string name="fill_all_fields">Please fill in every field</string>
<string name="username_length_error">Username must be 3 to 20 characters</string>
<string name="username_chars_error">Username can only contain English letters, numbers, hyphens and underscores</string>
<string name="password_length_error">Password must be 5 to 50 characters</string>
<string name="passwords_dont_match">The two passwords dont match</string>
<string name="auth_welcome_title">Welcome to FromChat</string>
@@ -42,8 +42,13 @@ import ru.fromchat.ui.components.expressiveStepFieldColors
import ru.fromchat.ui.components.trackImeScrollTarget
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
import ru.fromchat.username
import ru.fromchat.username_chars_error
import ru.fromchat.username_length_error
/** Matches backend `is_valid_username`: English letters, digits, hyphen, underscore. */
private fun isAllowedUsernameChar(ch: Char) =
ch in 'a'..'z' || ch in 'A'..'Z' || ch in '0'..'9' || ch == '_' || ch == '-'
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
internal fun usernameStepPage(
@@ -59,9 +64,11 @@ internal fun usernameStepPage(
val fillAll = stringResource(Res.string.fill_all_fields)
val usernameLenError = stringResource(Res.string.username_length_error)
val usernameCharsError = stringResource(Res.string.username_chars_error)
val serverFail = stringResource(Res.string.auth_server_connect_failed)
val unexpected = stringResource(Res.string.error_unexpected)
val nextLabel = stringResource(Res.string.settings_next)
val hasProhibitedChars = username.trim().any { !isAllowedUsernameChar(it) }
return ExpressiveStepPage(
hero = ExpressiveHeroSpec(
@@ -90,6 +97,12 @@ internal fun usernameStepPage(
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
.padding(horizontal = SettingsStepHorizontalPadding),
singleLine = true,
isError = hasProhibitedChars,
supportingText = if (hasProhibitedChars) {
{ Text(usernameCharsError) }
} else {
null
},
colors = expressiveStepFieldColors(),
shape = SettingsPasswordOutlineFieldShape,
)
@@ -98,7 +111,7 @@ internal fun usernameStepPage(
button = {
ActionButton(
onClick = {
if (busy) return@ActionButton
if (busy || hasProhibitedChars) return@ActionButton
val trimmed = username.trim()
if (trimmed.isBlank()) {
onSnackbar(fillAll, null)
@@ -136,7 +149,7 @@ internal fun usernameStepPage(
}
}
},
enabled = !busy,
enabled = !busy && !hasProhibitedChars,
loading = busy,
modifier = Modifier.fillMaxWidth(),
) {