diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts index 8a1a45b..105eb9e 100644 --- a/frontend/src/api/users.ts +++ b/frontend/src/api/users.ts @@ -18,6 +18,7 @@ export interface UserCreateRequest { full_name: string is_active?: boolean is_admin?: boolean + give_frontend_permission?: boolean } export interface UserUpdateRequest { diff --git a/frontend/src/pages/ManageUsers.tsx b/frontend/src/pages/ManageUsers.tsx index 829b3f9..f42134d 100644 --- a/frontend/src/pages/ManageUsers.tsx +++ b/frontend/src/pages/ManageUsers.tsx @@ -4,6 +4,60 @@ import { authUsersApi, AuthUserResponse, AuthUserCreateRequest, AuthUserUpdateRe type TabType = 'backend' | 'frontend' +/** + * Format Pydantic validation errors into user-friendly messages + */ +function formatValidationError(error: any): string { + // Get the field name from location array (e.g., ['body', 'email'] -> 'email') + const fieldPath = error.loc || [] + const fieldName = fieldPath[fieldPath.length - 1] || 'field' + + // Convert field names to friendly labels + const fieldLabels: Record = { + email: 'Email', + password: 'Password', + username: 'Username', + full_name: 'Full Name', + name: 'Name', + is_admin: 'Role', + is_active: 'Status', + has_write_access: 'Write Access', + give_frontend_permission: 'Frontend Permission', + } + + const friendlyFieldName = fieldLabels[fieldName] || fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + + // Get the error message + let message = error.msg || 'Invalid value' + + // Simplify common error messages + if (message.includes('not a valid email address') || message.includes('email address must have an @-sign')) { + message = 'Please enter a valid email address' + } else if (message.includes('ensure this value has at least') || message.includes('at least')) { + const match = message.match(/at least (\d+)/i) + if (match && fieldName === 'password') { + message = `Password must be at least ${match[1]} characters long` + } else if (match) { + message = `Must be at least ${match[1]} characters long` + } + } else if (message.includes('ensure this value has at most') || message.includes('at most')) { + const match = message.match(/at most (\d+)/i) + if (match) { + message = `Must be no more than ${match[1]} characters long` + } + } else if (message.includes('field required') || message.includes('required')) { + message = 'This field is required' + } else if (message.includes('string type expected')) { + message = 'Please enter text' + } else if (message.includes('value is not a valid')) { + // Remove the technical "value is not a valid" prefix + message = message.replace(/^value is not a valid\s*/i, '') + message = `Please enter a valid ${message}` + } + + return `${friendlyFieldName}: ${message}` +} + export default function ManageUsers() { const [activeTab, setActiveTab] = useState('backend') @@ -23,6 +77,7 @@ export default function ManageUsers() { full_name: '', is_active: true, is_admin: false, + give_frontend_permission: false, }) const [editForm, setEditForm] = useState({ @@ -95,6 +150,25 @@ export default function ManageUsers() { const handleCreate = async () => { try { setError(null) + + // Frontend validation + if (!createForm.username || createForm.username.trim() === '') { + setError('Username is required') + return + } + if (!createForm.password || createForm.password.length < 6) { + setError('Password must be at least 6 characters long') + return + } + if (!createForm.email || createForm.email.trim() === '') { + setError('Email is required') + return + } + if (!createForm.full_name || createForm.full_name.trim() === '') { + setError('Full name is required') + return + } + await usersApi.createUser(createForm) setShowCreateModal(false) setCreateForm({ @@ -104,16 +178,56 @@ export default function ManageUsers() { full_name: '', is_active: true, is_admin: false, + give_frontend_permission: false, }) loadUsers() } catch (err: any) { - setError(err.response?.data?.detail || 'Failed to create user') + // Handle validation errors (422) - Pydantic returns array of errors + if (err.response?.status === 422) { + const detail = err.response?.data?.detail + if (Array.isArray(detail)) { + // Format Pydantic validation errors into user-friendly messages + const errorMessages = detail.map(formatValidationError) + setError(errorMessages.join('; ') || 'Please check the form and try again') + } else { + setError(detail || 'Please check the form and try again') + } + } else { + // Handle other errors (400, 500, etc.) + const detail = err.response?.data?.detail + if (typeof detail === 'string') { + setError(detail) + } else if (Array.isArray(detail)) { + const errorMessages = detail.map((error: any) => { + if (typeof error === 'string') return error + return formatValidationError(error) + }) + setError(errorMessages.join('; ')) + } else { + setError(err.response?.data?.message || 'Failed to create user') + } + } } } const handleAuthCreate = async () => { try { setAuthError(null) + + // Frontend validation + if (!authCreateForm.email || authCreateForm.email.trim() === '') { + setAuthError('Email is required') + return + } + if (!authCreateForm.name || authCreateForm.name.trim() === '') { + setAuthError('Name is required') + return + } + if (!authCreateForm.password || authCreateForm.password.length < 6) { + setAuthError('Password must be at least 6 characters long') + return + } + await authUsersApi.createUser(authCreateForm) setShowAuthCreateModal(false) setAuthCreateForm({ @@ -125,7 +239,31 @@ export default function ManageUsers() { }) loadAuthUsers() } catch (err: any) { - setAuthError(err.response?.data?.detail || 'Failed to create auth user') + // Handle validation errors (422) - Pydantic returns array of errors + if (err.response?.status === 422) { + const detail = err.response?.data?.detail + if (Array.isArray(detail)) { + // Format Pydantic validation errors into user-friendly messages + const errorMessages = detail.map(formatValidationError) + setAuthError(errorMessages.join('; ') || 'Please check the form and try again') + } else { + setAuthError(detail || 'Please check the form and try again') + } + } else { + // Handle other errors (400, 500, etc.) + const detail = err.response?.data?.detail + if (typeof detail === 'string') { + setAuthError(detail) + } else if (Array.isArray(detail)) { + const errorMessages = detail.map((error: any) => { + if (typeof error === 'string') return error + return formatValidationError(error) + }) + setAuthError(errorMessages.join('; ')) + } else { + setAuthError(err.response?.data?.message || 'Failed to create auth user') + } + } } } @@ -243,8 +381,26 @@ export default function ManageUsers() {