segmented control

This commit is contained in:
EthanShoeDev
2025-09-12 02:33:04 -04:00
parent ee7e4ce8d4
commit d3a76de164
3 changed files with 96 additions and 84 deletions

View File

@@ -28,6 +28,7 @@
"@expo/vector-icons": "^15.0.2",
"@fressh/assets": "workspace:*",
"@react-native-picker/picker": "2.11.1",
"@react-native-segmented-control/segmented-control": "2.5.7",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.4",
"@react-navigation/native": "^7.1.8",

View File

@@ -1,17 +1,10 @@
import SSHClient, { PtyType } from '@dylankenneally/react-native-ssh-sftp';
// Removed inline Picker usage in favor of modal selection
import SegmentedControl from '@react-native-segmented-control/segmented-control';
import { useStore } from '@tanstack/react-form';
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useRouter } from 'expo-router';
import React from 'react';
import {
Pressable,
ScrollView,
StyleSheet,
Text,
View,
Switch,
} from 'react-native';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { useAppForm, useFieldContext } from '../components/form-components';
import { KeyManagerModal } from '../components/key-manager-modal';
import {
@@ -31,77 +24,80 @@ const defaultValues: ConnectionDetails = {
},
};
export default function Index() {
const useSshConnMutation = () => {
const router = useRouter();
// const storedConnectionsQuery = useQuery(
// secretsManager.connections.query.list,
// );
return useMutation({
mutationFn: async (value: ConnectionDetails) => {
try {
console.log('Connecting to SSH server...');
const effective = await (async () => {
if (value.security.type === 'password') return value;
if (value.security.keyId) return value;
const keys = await secretsManager.keys.utils.listEntriesWithValues();
const def = keys.find((k) => k.metadata?.isDefault);
const pick = def ?? keys[0];
if (pick) {
return {
...value,
security: { type: 'key', keyId: pick.id },
} as ConnectionDetails;
}
return value;
})();
const sshClientConnection = await (async () => {
if (effective.security.type === 'password') {
return await SSHClient.connectWithPassword(
effective.host,
effective.port,
effective.username,
effective.security.password,
);
}
const privateKey = await secretsManager.keys.utils.getPrivateKey(
effective.security.keyId,
);
return await SSHClient.connectWithKey(
effective.host,
effective.port,
effective.username,
privateKey.value,
);
})();
await secretsManager.connections.utils.upsertConnection({
id: 'default',
details: effective,
priority: 0,
});
await sshClientConnection.startShell(PtyType.XTERM);
const sshConn = sshConnectionManager.addSession({
client: sshClientConnection,
});
console.log('Connected to SSH server', sshConn.sessionId);
router.push({
pathname: '/shell',
params: {
sessionId: sshConn.sessionId,
},
});
} catch (error) {
console.error('Error connecting to SSH server', error);
throw error;
}
},
});
};
export default function Index() {
const sshConnMutation = useSshConnMutation();
const connectionForm = useAppForm({
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
defaultValues,
validators: {
onChange: connectionDetailsSchema,
onSubmitAsync: async ({ value }) => {
try {
console.log('Connecting to SSH server...');
const effective = await (async () => {
if (value.security.type === 'password') return value;
if (value.security.keyId) return value;
const keys =
await secretsManager.keys.utils.listEntriesWithValues();
const def = keys.find((k) => k.metadata?.isDefault);
const pick = def ?? keys[0];
if (pick) {
return {
...value,
security: { type: 'key', keyId: pick.id },
} as ConnectionDetails;
}
return value;
})();
const sshClientConnection = await (async () => {
if (effective.security.type === 'password') {
return await SSHClient.connectWithPassword(
effective.host,
effective.port,
effective.username,
effective.security.password,
);
}
const privateKey = await secretsManager.keys.utils.getPrivateKey(
effective.security.keyId,
);
return await SSHClient.connectWithKey(
effective.host,
effective.port,
effective.username,
privateKey.value,
);
})();
await secretsManager.connections.utils.upsertConnection({
id: 'default',
details: effective,
priority: 0,
});
await sshClientConnection.startShell(PtyType.XTERM);
const sshConn = sshConnectionManager.addSession({
client: sshClientConnection,
});
console.log('Connected to SSH server', sshConn.sessionId);
router.push({
pathname: '/shell',
params: {
sessionId: sshConn.sessionId,
},
});
} catch (error) {
console.error('Error connecting to SSH server', error);
throw error;
}
},
onSubmitAsync: async ({ value }) => sshConnMutation.mutateAsync(value),
},
});
@@ -164,16 +160,17 @@ export default function Index() {
<connectionForm.AppField name="security.type">
{(field) => (
<View style={styles.inputGroup}>
<Text style={styles.label}>Use Private Key</Text>
<View>
{/* Map boolean switch to discriminated union */}
<Switch
value={field.state.value === 'key'}
onValueChange={(val) => {
field.handleChange(val ? 'key' : 'password');
}}
/>
</View>
<SegmentedControl
values={['Password', 'Private Key']}
selectedIndex={field.state.value === 'password' ? 0 : 1}
onChange={(event) => {
field.handleChange(
event.nativeEvent.selectedSegmentIndex === 0
? 'password'
: 'key',
);
}}
/>
</View>
)}
</connectionForm.AppField>

14
pnpm-lock.yaml generated
View File

@@ -47,6 +47,9 @@ importers:
'@react-native-picker/picker':
specifier: 2.11.1
version: 2.11.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
'@react-native-segmented-control/segmented-control':
specifier: 2.5.7
version: 2.5.7(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
'@react-navigation/bottom-tabs':
specifier: ^7.4.0
version: 7.4.7(@react-navigation/native@7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
@@ -1730,6 +1733,12 @@ packages:
react: '*'
react-native: '*'
'@react-native-segmented-control/segmented-control@2.5.7':
resolution: {integrity: sha512-l84YeVX8xAU3lvOJSvV4nK/NbGhIm2gBfveYolwaoCbRp+/SLXtc6mYrQmM9ScXNwU14mnzjQTpTHWl5YPnkzQ==}
peerDependencies:
react: '>=16.0'
react-native: '>=0.62'
'@react-native/assets-registry@0.81.4':
resolution: {integrity: sha512-AMcDadefBIjD10BRqkWw+W/VdvXEomR6aEZ0fhQRAv7igrBzb4PTn4vHKYg+sUK0e3wa74kcMy2DLc/HtnGcMA==}
engines: {node: '>= 20.19.4'}
@@ -8692,6 +8701,11 @@ snapshots:
react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
'@react-native-segmented-control/segmented-control@2.5.7(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)':
dependencies:
react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
'@react-native/assets-registry@0.81.4': {}
'@react-native/babel-plugin-codegen@0.81.4(@babel/core@7.28.3)':