This commit is contained in:
EthanShoeDev
2025-09-07 23:47:02 -04:00
parent 4cfb92e232
commit efebb08ce4
6 changed files with 180 additions and 120 deletions

View File

@@ -1 +1,7 @@
{ "recommendations": ["expo.vscode-expo-tools"] }
{
"recommendations": [
"expo.vscode-expo-tools",
"gruntfuggly.todo-tree",
"eamodio.gitlens"
]
}

View File

@@ -1,136 +1,39 @@
import SSHClient, { PtyType } from '@dylankenneally/react-native-ssh-sftp'
import {
AnyFieldApi,
createFormHook,
createFormHookContexts,
} from '@tanstack/react-form'
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'
const { fieldContext, formContext } = createFormHookContexts()
// #region: UI Components
// https://tanstack.com/form/latest/docs/framework/react/quick-start
function TextField(
props: React.ComponentProps<typeof TextInput> & {
label?: string
field: AnyFieldApi
},
) {
const { label, field, style, ...rest } = props
const meta = field.state.meta
const errorMessage = meta?.errors?.[0] // TODO: typesafe errors
return (
<View style={styles.inputGroup}>
{label ? <Text style={styles.label}>{label}</Text> : null}
<TextInput
{...rest}
style={[styles.input, style]}
placeholderTextColor="#9AA0A6"
/>
{errorMessage ? (
<Text style={styles.errorText}>{String(errorMessage)}</Text>
) : null}
</View>
)
}
function NumberField(
props: React.ComponentProps<typeof TextInput> & {
label?: string
field: AnyFieldApi
},
) {
const { label, field, style, keyboardType, onChangeText, ...rest } = props
const meta = field.state.meta
const errorMessage = meta?.errors?.[0]
return (
<View style={styles.inputGroup}>
{label ? <Text style={styles.label}>{label}</Text> : null}
<TextInput
{...rest}
keyboardType={keyboardType ?? 'numeric'}
style={[styles.input, style]}
placeholderTextColor="#9AA0A6"
onChangeText={(text) => {
if (onChangeText) onChangeText(text)
}}
/>
{errorMessage ? (
<Text style={styles.errorText}>{String(errorMessage)}</Text>
) : null}
</View>
)
}
function SubmitButton(props: {
onPress?: () => void
title?: string
disabled?: boolean
}) {
const { onPress, title = 'Connect', disabled } = props
return (
<Pressable
style={[
styles.submitButton,
disabled ? styles.buttonDisabled : undefined,
]}
onPress={onPress}
disabled={disabled}
>
<Text style={styles.submitButtonText}>{title}</Text>
</Pressable>
)
}
// #endregion
// https://tanstack.com/form/latest/docs/framework/react/quick-start
const { useAppForm } = createFormHook({
fieldComponents: {
TextField,
NumberField,
},
formComponents: {
SubmitButton,
},
fieldContext,
formContext,
})
import { StyleSheet, Text, View } from 'react-native'
import { useFresshAppForm } from '../lib/app-form'
export default function Index() {
const connectionForm = useAppForm({
const connectionForm = useFresshAppForm({
defaultValues: {
host: '',
// host: '',
// port: 22,
// username: '',
// password: '',
// TODO: Remove this weird default
host: 'test.rebex.net',
port: 22,
username: '',
password: '',
username: 'demo',
password: 'password',
},
validators: {
// TODO: Add a zod validator here
// onChange: z.object({
// email: z.email(),
// }),
onSubmitAsync: async ({ value }) => {
// we will connect here.
// if connection fails, tanstack form will know the form is in an error state.
// we can read that state from the field.state.meta.errors (or errorMap)
//
SSHClient.connectWithPassword(
console.log('Connecting to SSH server...')
const sshClientConnection = await SSHClient.connectWithPassword(
value.host,
value.port,
value.username,
value.password,
).then(async (client) => {
alert('Connected')
client.on('Shell', (data) => {
console.log(data)
})
await client.startShell(PtyType.XTERM)
)
console.log('Connected to SSH server')
setTimeout(() => {
client.disconnect()
}, 5_000)
sshClientConnection.on('Shell', (data) => {
console.log(data)
})
await sshClientConnection.startShell(PtyType.XTERM)
},
},
})

12
src/app/shell.tsx Normal file
View File

@@ -0,0 +1,12 @@
/**
* This is the page that is shown after an ssh connection
*/
import { Text, View } from 'react-native'
export default function Shell() {
return (
<View>
<Text>Shell</Text>
</View>
)
}

View File

@@ -0,0 +1,119 @@
import { AnyFieldApi } from '@tanstack/react-form'
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'
// https://tanstack.com/form/latest/docs/framework/react/quick-start
export function TextField(
props: React.ComponentProps<typeof TextInput> & {
label?: string
field: AnyFieldApi
},
) {
const { label, field, style, ...rest } = props
const meta = field.state.meta
const errorMessage = meta?.errors?.[0] // TODO: typesafe errors
return (
<View style={styles.inputGroup}>
{label ? <Text style={styles.label}>{label}</Text> : null}
<TextInput
{...rest}
style={[styles.input, style]}
placeholderTextColor="#9AA0A6"
/>
{errorMessage ? (
<Text style={styles.errorText}>{String(errorMessage)}</Text>
) : null}
</View>
)
}
export function NumberField(
props: React.ComponentProps<typeof TextInput> & {
label?: string
field: AnyFieldApi
},
) {
const { label, field, style, keyboardType, onChangeText, ...rest } = props
const meta = field.state.meta
const errorMessage = meta?.errors?.[0]
return (
<View style={styles.inputGroup}>
{label ? <Text style={styles.label}>{label}</Text> : null}
<TextInput
{...rest}
keyboardType={keyboardType ?? 'numeric'}
style={[styles.input, style]}
placeholderTextColor="#9AA0A6"
onChangeText={(text) => {
if (onChangeText) onChangeText(text)
}}
/>
{errorMessage ? (
<Text style={styles.errorText}>{String(errorMessage)}</Text>
) : null}
</View>
)
}
export function SubmitButton(props: {
onPress?: () => void
title?: string
disabled?: boolean
}) {
const { onPress, title = 'Connect', disabled } = props
return (
<Pressable
style={[
styles.submitButton,
disabled ? styles.buttonDisabled : undefined,
]}
onPress={onPress}
disabled={disabled}
>
<Text style={styles.submitButtonText}>{title}</Text>
</Pressable>
)
}
const styles = StyleSheet.create({
inputGroup: {
marginBottom: 12,
},
label: {
marginBottom: 6,
fontSize: 14,
color: '#C6CBD3',
fontWeight: '600',
},
input: {
borderWidth: 1,
borderColor: '#2A3655',
backgroundColor: '#0E172B',
color: '#E5E7EB',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 12,
fontSize: 16,
},
errorText: {
marginTop: 6,
color: '#FCA5A5',
fontSize: 12,
},
submitButton: {
backgroundColor: '#2563EB',
borderRadius: 10,
paddingVertical: 14,
alignItems: 'center',
},
submitButtonText: {
color: '#FFFFFF',
fontWeight: '700',
fontSize: 16,
},
buttonDisabled: {
backgroundColor: '#3B82F6',
opacity: 0.6,
},
})

20
src/lib/app-form.ts Normal file
View File

@@ -0,0 +1,20 @@
import { createFormHook, createFormHookContexts } from '@tanstack/react-form'
import {
NumberField,
SubmitButton,
TextField,
} from '../components/form-components'
const { fieldContext, formContext } = createFormHookContexts()
// https://tanstack.com/form/latest/docs/framework/react/quick-start
export const { useAppForm: useFresshAppForm } = createFormHook({
fieldComponents: {
TextField,
NumberField,
},
formComponents: {
SubmitButton,
},
fieldContext,
formContext,
})