Files
handler/app/src/components/TextField.tsx
T
0xWheatyz c0342a9796 feat(app): replace mock data with live Handler API
Wire the mobile app to the real Handler API instead of the transcribed
prototype data:

- src/api/client.ts: typed client ported from frontend/lib/api.ts (bearer
  auth, AuthError/ApiError, base URL param, allow401 for admin-only resume)
- src/api/format.ts: relative-time + status label/tone/color helpers
- src/state/ServerConfig.tsx: endpoint+token persisted to AsyncStorage
- src/screens/ConnectScreen.tsx: first-open config, validated via
  /health then /projects
- src/state/AppState.tsx: data-driven store polling projects -> agents ->
  checkmarks -> logs every 10s with per-item failure isolation; derives
  fleet counts, waiting list, recent checkmarks, merged log; answer+resume,
  spawn, kill mutations; 401 routes back to ConnectScreen
- screens render live data; detail meta is Started/Status/Tests/Build;
  Pause removed (no endpoint); Kill confirms; log filters are per-project
- delete src/data/mock.ts

tsc clean; Hermes bundle builds (200).
2026-07-21 15:16:56 -04:00

98 lines
2.4 KiB
TypeScript

import React, { useState } from "react";
import {
StyleSheet,
TextInput,
View,
type KeyboardTypeOptions,
type StyleProp,
type TextInputProps,
type ViewStyle,
} from "react-native";
import { fonts, radius } from "../theme/tokens";
import { useTheme } from "../theme/useTheme";
/**
* Text field covering both the Leeworks single-line Input (answer reply) and
* the multiline task textarea on the spawn screen. Focus swaps the border to
* `--border-strong` per the Input component.
*/
export function TextField({
value,
onChangeText,
placeholder,
multiline = false,
height = 48,
style,
secureTextEntry = false,
autoCapitalize,
autoCorrect,
keyboardType,
}: {
value: string;
onChangeText: (t: string) => void;
placeholder?: string;
multiline?: boolean;
/** For single-line this is the control height; for multiline, the box height. */
height?: number;
style?: StyleProp<ViewStyle>;
secureTextEntry?: boolean;
autoCapitalize?: TextInputProps["autoCapitalize"];
autoCorrect?: boolean;
keyboardType?: KeyboardTypeOptions;
}) {
const { colors } = useTheme();
const [focus, setFocus] = useState(false);
return (
<View
style={[
styles.wrap,
{
height,
backgroundColor: colors.surfaceCard,
borderColor: focus ? colors.borderStrong : colors.borderDefault,
alignItems: multiline ? "stretch" : "center",
paddingVertical: multiline ? 12 : 0,
},
style,
]}
>
<TextInput
value={value}
onChangeText={onChangeText}
placeholder={placeholder}
placeholderTextColor={colors.textMuted}
multiline={multiline}
secureTextEntry={secureTextEntry}
autoCapitalize={autoCapitalize}
autoCorrect={autoCorrect}
keyboardType={keyboardType}
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
style={[
styles.input,
{
color: colors.textHeading,
textAlignVertical: multiline ? "top" : "center",
},
]}
/>
</View>
);
}
const styles = StyleSheet.create({
wrap: {
flexDirection: "row",
borderWidth: 1,
borderRadius: radius.md,
paddingHorizontal: 14,
},
input: {
flex: 1,
alignSelf: "stretch",
fontFamily: fonts.bodyRegular,
fontSize: 15,
padding: 0,
},
});