{
  "id": "rn-05-form-validation",
  "files": [
    {
      "path": "src/services/credentials.ts",
      "content": "import * as Keychain from 'react-native-keychain';\n\nexport async function saveCredentials(email: string, password: string): Promise<void> {\n  await Keychain.setGenericPassword(email, password, {\n    service: 'com.example.app.session',\n    accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,\n  });\n}\n\nexport async function clearCredentials(): Promise<void> {\n  await Keychain.resetGenericPassword();\n}\n"
    },
    {
      "path": "src/screens/SignUpScreen.tsx",
      "content": "import { useState, useCallback } from 'react';\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  StyleSheet,\n  KeyboardAvoidingView,\n  Platform,\n} from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { saveCredentials } from '../services/credentials';\n\ninterface FormErrors {\n  name?: string;\n  email?: string;\n  password?: string;\n}\n\nfunction validate(name: string, email: string, password: string): FormErrors {\n  const errors: FormErrors = {};\n  if (name.trim().length === 0) errors.name = 'Name is required';\n  if (!/\\S+@\\S+\\.\\S+/.test(email)) errors.email = 'Enter a valid email';\n  if (password.length < 8) errors.password = 'Use at least 8 characters';\n  return errors;\n}\n\nexport function SignUpScreen(): JSX.Element {\n  const [name, setName] = useState('');\n  const [email, setEmail] = useState('');\n  const [password, setPassword] = useState('');\n  const [errors, setErrors] = useState<FormErrors>({});\n  const [saving, setSaving] = useState(false);\n  const [saved, setSaved] = useState(false);\n\n  const handleSubmit = useCallback(async () => {\n    const nextErrors = validate(name, email, password);\n    setErrors(nextErrors);\n    if (Object.keys(nextErrors).length > 0) return;\n    setSaving(true);\n    try {\n      await saveCredentials(email, password);\n      setSaved(true);\n    } catch (err) {\n      setErrors({ password: err instanceof Error ? err.message : 'Could not save' });\n    } finally {\n      setSaving(false);\n    }\n  }, [name, email, password]);\n\n  return (\n    <SafeAreaView style={styles.safe}>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === 'ios' ? 'padding' : undefined}\n      >\n        <TextInput\n          style={styles.input}\n          value={name}\n          onChangeText={setName}\n          placeholder=\"Full name\"\n          accessibilityLabel=\"Full name\"\n        />\n        {errors.name !== undefined && <Text style={styles.error}>{errors.name}</Text>}\n        <TextInput\n          style={styles.input}\n          value={email}\n          onChangeText={setEmail}\n          placeholder=\"Email\"\n          keyboardType=\"email-address\"\n          autoCapitalize=\"none\"\n          accessibilityLabel=\"Email\"\n        />\n        {errors.email !== undefined && <Text style={styles.error}>{errors.email}</Text>}\n        <TextInput\n          style={styles.input}\n          value={password}\n          onChangeText={setPassword}\n          placeholder=\"Password\"\n          secureTextEntry\n          accessibilityLabel=\"Password\"\n        />\n        {errors.password !== undefined && <Text style={styles.error}>{errors.password}</Text>}\n        <TouchableOpacity\n          style={styles.button}\n          onPress={handleSubmit}\n          disabled={saving}\n          accessibilityLabel=\"Create account\"\n        >\n          <Text style={styles.buttonText}>{saving ? 'Saving…' : 'Create account'}</Text>\n        </TouchableOpacity>\n        {saved && <Text style={styles.success}>Account created securely.</Text>}\n      </KeyboardAvoidingView>\n    </SafeAreaView>\n  );\n}\n\nconst styles = StyleSheet.create({\n  safe: { flex: 1 },\n  container: { flex: 1, justifyContent: 'center', padding: 24, gap: 12 },\n  input: { borderWidth: 1, borderColor: '#C7C7CC', borderRadius: 8, padding: 12 },\n  error: { color: '#FF3B30', fontSize: 13 },\n  success: { color: '#34C759', marginTop: 8 },\n  button: { backgroundColor: '#007AFF', padding: 14, borderRadius: 8, alignItems: 'center' },\n  buttonText: { color: '#fff', fontWeight: '600' },\n});\n"
    }
  ]
}
