mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
commit
This commit is contained in:
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@@ -1 +1,7 @@
|
|||||||
{ "recommendations": ["expo.vscode-expo-tools"] }
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"expo.vscode-expo-tools",
|
||||||
|
"gruntfuggly.todo-tree",
|
||||||
|
"eamodio.gitlens"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,136 +1,39 @@
|
|||||||
import SSHClient, { PtyType } from '@dylankenneally/react-native-ssh-sftp'
|
import SSHClient, { PtyType } from '@dylankenneally/react-native-ssh-sftp'
|
||||||
import {
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
AnyFieldApi,
|
import { useFresshAppForm } from '../lib/app-form'
|
||||||
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,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const connectionForm = useAppForm({
|
const connectionForm = useFresshAppForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
host: '',
|
// host: '',
|
||||||
|
// port: 22,
|
||||||
|
// username: '',
|
||||||
|
// password: '',
|
||||||
|
// TODO: Remove this weird default
|
||||||
|
host: 'test.rebex.net',
|
||||||
port: 22,
|
port: 22,
|
||||||
username: '',
|
username: 'demo',
|
||||||
password: '',
|
password: 'password',
|
||||||
},
|
},
|
||||||
validators: {
|
validators: {
|
||||||
|
// TODO: Add a zod validator here
|
||||||
|
// onChange: z.object({
|
||||||
|
// email: z.email(),
|
||||||
|
// }),
|
||||||
onSubmitAsync: async ({ value }) => {
|
onSubmitAsync: async ({ value }) => {
|
||||||
// we will connect here.
|
console.log('Connecting to SSH server...')
|
||||||
// if connection fails, tanstack form will know the form is in an error state.
|
const sshClientConnection = await SSHClient.connectWithPassword(
|
||||||
|
|
||||||
// we can read that state from the field.state.meta.errors (or errorMap)
|
|
||||||
//
|
|
||||||
SSHClient.connectWithPassword(
|
|
||||||
value.host,
|
value.host,
|
||||||
value.port,
|
value.port,
|
||||||
value.username,
|
value.username,
|
||||||
value.password,
|
value.password,
|
||||||
).then(async (client) => {
|
)
|
||||||
alert('Connected')
|
console.log('Connected to SSH server')
|
||||||
client.on('Shell', (data) => {
|
|
||||||
console.log(data)
|
|
||||||
})
|
|
||||||
await client.startShell(PtyType.XTERM)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
sshClientConnection.on('Shell', (data) => {
|
||||||
client.disconnect()
|
console.log(data)
|
||||||
}, 5_000)
|
|
||||||
})
|
})
|
||||||
|
await sshClientConnection.startShell(PtyType.XTERM)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
12
src/app/shell.tsx
Normal file
12
src/app/shell.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
119
src/components/form-components.tsx
Normal file
119
src/components/form-components.tsx
Normal 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
20
src/lib/app-form.ts
Normal 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,
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user