mirror of
https://github.com/abhay-raizada/PeerScribe.git
synced 2026-04-26 08:14:03 +00:00
Merge pull request #1 from abhay-raizada/add_inputs
Add Inputs to main screen
This commit is contained in:
32
App.tsx
32
App.tsx
@@ -5,37 +5,35 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { SafeAreaView, ScrollView, StatusBar, StyleSheet } from 'react-native';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
} from 'react-native';
|
||||
|
||||
import { getFormTemplate } from './formstr/formstr';
|
||||
import { Colors } from 'react-native/Libraries/NewAppScreen';
|
||||
import { PrescriptionCreator } from './components/PrescriptionCreator';
|
||||
import {Colors} from 'react-native/Libraries/NewAppScreen';
|
||||
import {PrescriptionCreator} from './components/PrescriptionCreator';
|
||||
import 'react-native-url-polyfill/auto';
|
||||
import PolyfillCrypto from 'react-native-webview-crypto';
|
||||
|
||||
function App(): React.JSX.Element {
|
||||
const backgroundStyle = {
|
||||
backgroundColor: Colors.darker,
|
||||
backgroundColor: 'black',
|
||||
color: 'white',
|
||||
};
|
||||
|
||||
const [form, setForm] = useState<{} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('inside useeffect');
|
||||
const fetchForm = async () => {
|
||||
if (!form) {
|
||||
console.log('fetchiiiing forrmmm!!!');
|
||||
let form = await getFormTemplate(
|
||||
'eb3df1f89653475f0bcbd22da35f8d2f126db8a68a88a7abedc53535c76c39b4',
|
||||
)
|
||||
setForm(form);
|
||||
}
|
||||
};
|
||||
fetchForm();
|
||||
}, [form]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={backgroundStyle}>
|
||||
<PolyfillCrypto />
|
||||
<StatusBar
|
||||
barStyle={'light-content'}
|
||||
backgroundColor={backgroundStyle.backgroundColor}
|
||||
|
||||
969
android/app/src/main/assets/index.android.bundle
Normal file
969
android/app/src/main/assets/index.android.bundle
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 208 B |
Binary file not shown.
|
After Width: | Height: | Size: 186 B |
129
components/PharmacyPicker/AddPharmacy.tsx
Normal file
129
components/PharmacyPicker/AddPharmacy.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import {useState} from 'react';
|
||||
import {Alert, Button, Modal, Text, TextInput, View} from 'react-native';
|
||||
import {Section} from '../common/Section';
|
||||
import {getData, storeData} from '../../utils/localStorage';
|
||||
|
||||
export const AddPharmacy = ({
|
||||
isVisible,
|
||||
onClose,
|
||||
onAdd,
|
||||
}: {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
onAdd: (npub: string, relay: string, name: string) => void;
|
||||
}) => {
|
||||
const [npub, setNpub] = useState('');
|
||||
const [relay, setRelay] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
|
||||
const handleNpub = (value: string) => {
|
||||
setNpub(value);
|
||||
};
|
||||
const handleRelay = (value: string) => {
|
||||
setRelay(value);
|
||||
};
|
||||
const handleName = (value: string) => {
|
||||
setName(value);
|
||||
};
|
||||
|
||||
async function handleAddClick() {
|
||||
if (!npub || !relay || !name) {
|
||||
Alert.alert(
|
||||
'Missing Inputs',
|
||||
'Please enter name, npub and relay of the Pharmacy',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (npub.length !== 63 || !npub.startsWith('npub1')) {
|
||||
Alert.alert('Invalid Npub');
|
||||
return;
|
||||
}
|
||||
let pharmacyListString = (await getData('pharmacyList')) || '[]';
|
||||
let pharmacyList = JSON.parse(pharmacyListString) as Array<{
|
||||
label: string;
|
||||
npub: string;
|
||||
relay: string;
|
||||
}>;
|
||||
pharmacyList = [...pharmacyList, {label: name, relay: relay, npub: npub}];
|
||||
await storeData('pharmacyList', JSON.stringify(pharmacyList));
|
||||
|
||||
onAdd(npub, relay, name);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={isVisible}
|
||||
onRequestClose={() => {
|
||||
console.log('closing....');
|
||||
onClose();
|
||||
return true;
|
||||
}}
|
||||
onDismiss={() => {
|
||||
onClose();
|
||||
}}
|
||||
transparent={true}
|
||||
style={{backgroundColor: 'black', margin: 0, padding: 0, height: '80%'}}
|
||||
animationType="slide">
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: 'black',
|
||||
justifyContent: 'center',
|
||||
minHeight: '80%',
|
||||
display: 'flex',
|
||||
margin: 30,
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<Section title="Add A Pharmacy">
|
||||
<View style={{margin: 5}}>
|
||||
<Text style={{color: 'white', margin: 5}}>Add Pharmacy Name</Text>
|
||||
<TextInput
|
||||
style={{
|
||||
borderColor: '#000000',
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
color: 'white',
|
||||
}}
|
||||
placeholderTextColor="grey"
|
||||
placeholder="Pharmacy X"
|
||||
onChangeText={handleName}
|
||||
/>
|
||||
|
||||
<Text style={{color: 'white', margin: 5}}>Add Pharmacy Npub</Text>
|
||||
<TextInput
|
||||
style={{
|
||||
borderColor: '#000000',
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
color: 'white',
|
||||
}}
|
||||
placeholderTextColor="grey"
|
||||
placeholder="npub1...."
|
||||
onChangeText={handleNpub}
|
||||
/>
|
||||
|
||||
<Text style={{color: 'white', margin: 5}}>Add Pharmacy Relay</Text>
|
||||
<TextInput
|
||||
style={{
|
||||
borderColor: '#000000',
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
color: 'white',
|
||||
}}
|
||||
placeholderTextColor="grey"
|
||||
placeholder="wss://<relay-url>"
|
||||
onChangeText={handleRelay}
|
||||
/>
|
||||
</View>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View style={{margin: 10}}>
|
||||
<Button title="Cancel" onPress={() => onClose()}></Button>
|
||||
</View>
|
||||
<View style={{margin: 10}}>
|
||||
<Button title="Add" onPress={handleAddClick}></Button>
|
||||
</View>
|
||||
</View>
|
||||
</Section>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
109
components/PharmacyPicker/index.tsx
Normal file
109
components/PharmacyPicker/index.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import {Dropdown} from 'react-native-element-dropdown';
|
||||
import {Section} from '../common/Section';
|
||||
import {Button, Dimensions, Text, View} from 'react-native';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {AddPharmacy} from './AddPharmacy';
|
||||
import {getData} from '../../utils/localStorage';
|
||||
|
||||
export const pharmacyData = [
|
||||
{
|
||||
label: 'Default pharmacy',
|
||||
value: 'default',
|
||||
npub: 'npub1tea09rtjeuzgk4gjajzry37wuyv7h02d4zw38cpadcrkg5yt0qhqncr7km',
|
||||
relay: 'wss://relay.damus.io',
|
||||
},
|
||||
{
|
||||
label: ' + Add Pharmacy',
|
||||
value: 'custom',
|
||||
},
|
||||
];
|
||||
|
||||
let width = Dimensions.get('window').width;
|
||||
|
||||
interface PharmacyPickerProps {
|
||||
handleLocationChange: (item: any) => void;
|
||||
}
|
||||
|
||||
export const PharmacyPicker: React.FC<PharmacyPickerProps> = ({
|
||||
handleLocationChange,
|
||||
}) => {
|
||||
const [showAddPharmacyModal, setShowAddPharmacyModal] = useState(false);
|
||||
const [pharmacyList, setPharmacyList] = useState(pharmacyData);
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
const initialize = async () => {
|
||||
let pharmacyListString = (await getData('pharmacyList')) || '[]';
|
||||
let newPharmacyList = JSON.parse(pharmacyListString);
|
||||
let storePharmacyList = [...newPharmacyList, ...pharmacyList];
|
||||
setPharmacyList(storePharmacyList);
|
||||
setInitialized(true);
|
||||
handleLocationChange(storePharmacyList[0]);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!initialized) initialize();
|
||||
}, []);
|
||||
|
||||
const renderItem = (item: any) => {
|
||||
if (item.value === 'custom') {
|
||||
return (
|
||||
<View>
|
||||
<Button
|
||||
title="Add Pharmacy"
|
||||
onPress={() => {
|
||||
setShowAddPharmacyModal(true);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: width,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: 10,
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
<Text style={{color: 'black', fontSize: 24}}>{item.label}</Text>
|
||||
<View style={{width: width - 100}}>
|
||||
<Text style={{color: 'grey', paddingBottom: 5}}>
|
||||
Npub: {item.npub}
|
||||
</Text>
|
||||
<Text style={{color: 'grey'}}>Relay: {item.relay}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Section title="Choose a Pharmacy">
|
||||
<View style={{width: width - 40}}>
|
||||
<Dropdown
|
||||
data={pharmacyList}
|
||||
labelField={'label'}
|
||||
valueField={'label'}
|
||||
onChange={handleLocationChange}
|
||||
value={pharmacyList[0]}
|
||||
renderItem={renderItem}
|
||||
style={{width: '100%'}}
|
||||
placeholderStyle={{color: 'white'}}
|
||||
selectedTextStyle={{color: 'white'}}
|
||||
/>
|
||||
</View>
|
||||
</Section>
|
||||
<AddPharmacy
|
||||
isVisible={showAddPharmacyModal}
|
||||
onClose={() => {
|
||||
setShowAddPharmacyModal(false);
|
||||
}}
|
||||
onAdd={() => {
|
||||
initialize();
|
||||
setShowAddPharmacyModal(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
90
components/PrescriptionCreator/AddressForm.tsx
Normal file
90
components/PrescriptionCreator/AddressForm.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import {Text, TextInput, View} from 'react-native';
|
||||
import {styles, TextTheme} from '../common/styles';
|
||||
import {useState} from 'react';
|
||||
import {Section} from '../common/Section';
|
||||
|
||||
interface AddressForm {
|
||||
address_line_1?: string;
|
||||
city?: string;
|
||||
state_province?: string;
|
||||
postal_code?: string;
|
||||
country_code?: string;
|
||||
}
|
||||
|
||||
interface AddressFormProps {
|
||||
nestedFormCallback: (tag: string, form: Object) => void;
|
||||
}
|
||||
|
||||
export const AddressForm: React.FC<AddressFormProps> = ({
|
||||
nestedFormCallback,
|
||||
}) => {
|
||||
const [form, setForm] = useState<AddressForm>({});
|
||||
|
||||
const handleTextChange = (tag: keyof AddressForm, text: string) => {
|
||||
let newForm = {...form};
|
||||
newForm[tag] = text;
|
||||
setForm(newForm);
|
||||
nestedFormCallback('Address', newForm);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Address Line 1</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter street"
|
||||
value={form.address_line_1}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) =>
|
||||
handleTextChange('address_line_1', text)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>City</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter city"
|
||||
value={form.city}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('city', text)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>State Provice</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="enter state..."
|
||||
value={form.state_province}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) =>
|
||||
handleTextChange('state_province', text)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Postal Code</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter postal code..."
|
||||
value={form.postal_code}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('postal_code', text)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Country Code</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter Country Code..."
|
||||
value={form.country_code}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) =>
|
||||
handleTextChange('country_code', text)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
95
components/PrescriptionCreator/MedicineForm.tsx
Normal file
95
components/PrescriptionCreator/MedicineForm.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import {Text, TextInput, View} from 'react-native';
|
||||
import {Section} from '../common/Section';
|
||||
import {styles, TextTheme} from '../common/styles';
|
||||
import {useState} from 'react';
|
||||
|
||||
interface MedicineForm {
|
||||
name?: string;
|
||||
dosage_form?: string;
|
||||
strength?: string;
|
||||
quantity?: string;
|
||||
refills?: string;
|
||||
directions?: string;
|
||||
}
|
||||
|
||||
interface MedicineFormProps {
|
||||
nestedFormCallback: (tag: string, form: Object) => void;
|
||||
}
|
||||
|
||||
export const MedicineForm: React.FC<MedicineFormProps> = ({
|
||||
nestedFormCallback,
|
||||
}) => {
|
||||
const [form, setForm] = useState<MedicineForm>({});
|
||||
|
||||
const handleTextChange = (tag: keyof MedicineForm, text: string) => {
|
||||
let newForm = {...form};
|
||||
newForm[tag] = text;
|
||||
setForm(newForm);
|
||||
nestedFormCallback('MedicationPrescribed', newForm);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Name of Medicine</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter name of medicine"
|
||||
value={form.name}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('name', text)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}> Form of Dosage</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="what is the dosage form"
|
||||
value={form.dosage_form}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('dosage_form', text)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Strength</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="enter strength..."
|
||||
value={form.strength}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('strength', text)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Quantity</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter quantity..."
|
||||
value={form.quantity}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('quantity', text)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Refills</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Refills"
|
||||
value={form.refills}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('refills', text)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Directions</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter directions"
|
||||
value={form.refills}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('directions', text)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
75
components/PrescriptionCreator/PatientForm.tsx
Normal file
75
components/PrescriptionCreator/PatientForm.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import {Text, TextInput, View, Button} from 'react-native';
|
||||
import {Section} from '../common/Section';
|
||||
import {styles, TextTheme} from '../common/styles';
|
||||
import {useState} from 'react';
|
||||
import DatePicker from 'react-native-date-picker';
|
||||
|
||||
interface PatientForm {
|
||||
name?: string;
|
||||
date_of_birth?: string;
|
||||
}
|
||||
|
||||
interface PatientFormProps {
|
||||
nestedFormCallback: (tag: string, form: Object) => void;
|
||||
}
|
||||
|
||||
export const PatientForm: React.FC<PatientFormProps> = ({
|
||||
nestedFormCallback,
|
||||
}) => {
|
||||
const [form, setForm] = useState<PatientForm>({});
|
||||
const [openDate, setOpenDate] = useState<boolean>(false);
|
||||
|
||||
const handleTextChange = (tag: 'name' | 'date_of_birth', text: string) => {
|
||||
let newForm = {...form};
|
||||
newForm[tag] = text;
|
||||
setForm(newForm);
|
||||
nestedFormCallback('patient', {human_patient: newForm});
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Name</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter Patients Name"
|
||||
value={form.name}
|
||||
placeholderTextColor="white"
|
||||
onChangeText={(text: string) => handleTextChange('name', text)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={TextTheme}>Date of Birth</Text>
|
||||
{form.date_of_birth ? (
|
||||
<View>
|
||||
<Text style={TextTheme}>{form.date_of_birth}</Text>
|
||||
<Button
|
||||
onPress={() => {
|
||||
setOpenDate(true);
|
||||
}}
|
||||
title="Edit"
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<Button
|
||||
onPress={() => {
|
||||
setOpenDate(true);
|
||||
}}
|
||||
title="Pick a date"
|
||||
/>
|
||||
)}
|
||||
<DatePicker
|
||||
modal
|
||||
mode={'date'}
|
||||
open={openDate}
|
||||
date={new Date(form.date_of_birth || '01-01-1999')}
|
||||
onCancel={() => setOpenDate(false)}
|
||||
onConfirm={(date: Date) => {
|
||||
handleTextChange('date_of_birth', date.toDateString());
|
||||
setOpenDate(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,67 +1,150 @@
|
||||
import {Dimensions, Image, StyleSheet, Text, View} from 'react-native';
|
||||
import SampleJSON from '../../formstr/sample.json';
|
||||
import {Alert, Dimensions, Image, View, Button} from 'react-native';
|
||||
import {Colors} from 'react-native/Libraries/NewAppScreen';
|
||||
import {PropsWithChildren} from 'react';
|
||||
import {Card} from '@ant-design/react-native';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {
|
||||
SimplePool,
|
||||
UnsignedEvent,
|
||||
finalizeEvent,
|
||||
getPublicKey,
|
||||
nip04,
|
||||
nip19,
|
||||
} from 'nostr-tools';
|
||||
import EncryptedStorage from 'react-native-encrypted-storage';
|
||||
import {ImportNsec} from '../common/ImportNsec';
|
||||
import {Section} from '../common/Section';
|
||||
import {PatientForm} from './PatientForm';
|
||||
import {AddressForm} from './AddressForm';
|
||||
import {MedicineForm} from './MedicineForm';
|
||||
import {PharmacyPicker, pharmacyData} from '../PharmacyPicker';
|
||||
|
||||
type SectionProps = PropsWithChildren<{
|
||||
title: string;
|
||||
}>;
|
||||
function OBJtoXML(obj: any) {
|
||||
var xml = '';
|
||||
for (var prop in obj) {
|
||||
xml += '<' + prop + '>';
|
||||
if (Array.isArray(obj[prop])) {
|
||||
for (var array of obj[prop]) {
|
||||
// A real botch fix here
|
||||
xml += '</' + prop + '>';
|
||||
xml += '<' + prop + '>';
|
||||
|
||||
const backgroundStyle = {
|
||||
backgroundColor: Colors.darker,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
sectionContainer: {
|
||||
marginTop: 32,
|
||||
paddingHorizontal: 24,
|
||||
width: Dimensions.get('window').width - 80,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '600',
|
||||
},
|
||||
sectionDescription: {
|
||||
marginTop: 8,
|
||||
fontSize: 18,
|
||||
fontWeight: '400',
|
||||
},
|
||||
highlight: {
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
|
||||
function Section({children, title}: SectionProps): React.JSX.Element {
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text
|
||||
style={[
|
||||
styles.sectionTitle,
|
||||
{
|
||||
color: Colors.white,
|
||||
},
|
||||
]}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.sectionDescription,
|
||||
{
|
||||
color: Colors.light,
|
||||
},
|
||||
]}>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
xml += OBJtoXML(new Object(array));
|
||||
}
|
||||
} else if (typeof obj[prop] == 'object') {
|
||||
xml += OBJtoXML(new Object(obj[prop]));
|
||||
} else {
|
||||
xml += obj[prop];
|
||||
}
|
||||
xml += '</' + prop + '>';
|
||||
}
|
||||
var xml = xml.replace(/<\/?[0-9]{1,}>/g, '');
|
||||
return xml;
|
||||
}
|
||||
|
||||
export const PrescriptionCreator = () => {
|
||||
const [showImportNsec, setShowImportNsec] = useState(false);
|
||||
const [loggedInNpub, setLoggedInNpub] = useState('');
|
||||
const [finalJSON, setFinalJson] = useState({});
|
||||
const [selectedPharmacyId, setSelectedPharmacyId] = useState(
|
||||
pharmacyData[0].npub,
|
||||
);
|
||||
const [selectedPharmacyRelays, setSelectedPharmacyRelays] = useState<
|
||||
Array<string>
|
||||
>([pharmacyData[0].relay!]);
|
||||
|
||||
const handleLocationChange = (item: {
|
||||
name: string;
|
||||
npub: string;
|
||||
relay: string;
|
||||
}) => {
|
||||
setSelectedPharmacyId(item.npub);
|
||||
setSelectedPharmacyRelays([item.relay]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initialize() {
|
||||
let doctorCredentials = null;
|
||||
try {
|
||||
doctorCredentials = await EncryptedStorage.getItem('user_credentials');
|
||||
if (!doctorCredentials) {
|
||||
setShowImportNsec(true);
|
||||
} else {
|
||||
setLoggedInNpub(
|
||||
nip19.npubEncode(
|
||||
getPublicKey(nip19.decode(doctorCredentials).data as Uint8Array),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error getting credentials', e);
|
||||
}
|
||||
}
|
||||
initialize();
|
||||
}, []);
|
||||
|
||||
const nestedFormCallback = (
|
||||
xmlTag: string,
|
||||
value: Object | Array<any> | string,
|
||||
) => {
|
||||
console.log('Filling', xmlTag, value);
|
||||
setFinalJson({...finalJSON, [xmlTag]: value});
|
||||
};
|
||||
|
||||
const handleImportNsec = (nsec: string) => {
|
||||
EncryptedStorage.setItem('user_credentials', nsec);
|
||||
if (nsec.startsWith('nsec1') && nsec.length !== 63) {
|
||||
Alert.alert('not a valid nsec!');
|
||||
return;
|
||||
}
|
||||
setLoggedInNpub(
|
||||
nip19.npubEncode(getPublicKey(nip19.decode(nsec).data as Uint8Array)),
|
||||
);
|
||||
setShowImportNsec(false);
|
||||
};
|
||||
|
||||
const handleButtonPress = () => {
|
||||
console.log('Final JSON is', finalJSON);
|
||||
const xml = OBJtoXML({prescription: finalJSON});
|
||||
console.log('XML is...', xml, typeof xml);
|
||||
sendPrescription(xml);
|
||||
};
|
||||
|
||||
const sendPrescription = async (xml: string) => {
|
||||
console.log('Will generate IDs');
|
||||
const sk = nip19.decode(
|
||||
(await EncryptedStorage.getItem('user_credentials')) as `nsec1${string}`,
|
||||
).data as Uint8Array;
|
||||
const pk = getPublicKey(sk);
|
||||
const pharmacyId = nip19.decode(selectedPharmacyId!).data as string;
|
||||
console.log('Got ids');
|
||||
console.log('content is ', xml);
|
||||
const baseKind4Event: UnsignedEvent = {
|
||||
kind: 4,
|
||||
tags: [['p', pharmacyId]],
|
||||
content: await nip04.encrypt(sk, pharmacyId, `${xml}`),
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
pubkey: pk,
|
||||
};
|
||||
const finalEvent = finalizeEvent(baseKind4Event, sk);
|
||||
console.log(
|
||||
'FINAL EVENT IS ',
|
||||
finalEvent,
|
||||
'relays are',
|
||||
selectedPharmacyRelays,
|
||||
);
|
||||
const pool = new SimplePool();
|
||||
console.log('publishing event');
|
||||
let messages = await Promise.allSettled(
|
||||
pool.publish(selectedPharmacyRelays, finalEvent),
|
||||
);
|
||||
console.log('Messages from relays', messages);
|
||||
Alert.alert('Prescription Sent to the pharmacy!');
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: Colors.black,
|
||||
minHeight: Dimensions.get('window').height,
|
||||
}}>
|
||||
<Image
|
||||
style={{
|
||||
@@ -69,45 +152,45 @@ export const PrescriptionCreator = () => {
|
||||
width: Dimensions.get('window').width,
|
||||
}}
|
||||
source={{
|
||||
uri: SampleJSON.settings.titleImageUrl,
|
||||
uri: 'https://www.studentdoctor.net/wp-content/uploads/2018/08/20180815_prescription.png',
|
||||
}}
|
||||
/>
|
||||
<Section title="PeerScribe">
|
||||
From the practice of {SampleJSON.name}
|
||||
From the practice of {loggedInNpub}
|
||||
<Button
|
||||
onPress={() => {
|
||||
setShowImportNsec(true);
|
||||
}}
|
||||
title="edit"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section title="Prescription">
|
||||
<View style={{display: 'flex', flexDirection: 'column'}}>
|
||||
{SampleJSON.fields.map(field => {
|
||||
return (
|
||||
<Card
|
||||
key={field.questionId}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: 10,
|
||||
backgroundColor: Colors.white,
|
||||
margin: 10,
|
||||
width: 500,
|
||||
height: 'auto',
|
||||
}}>
|
||||
<Text
|
||||
style={{
|
||||
color: Colors.green,
|
||||
}}>
|
||||
{field.question}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: Colors.light,
|
||||
}}>
|
||||
{field.answerType}
|
||||
</Text>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
<PharmacyPicker handleLocationChange={handleLocationChange} />
|
||||
|
||||
<Section title="Create Prescription">
|
||||
<View style={{display: 'flex', flexDirection: 'column', width: '100%'}}>
|
||||
<Section title="Patient" collapsible={true}>
|
||||
{' '}
|
||||
<PatientForm nestedFormCallback={nestedFormCallback} />
|
||||
</Section>
|
||||
<Section title="Address" collapsible={true}>
|
||||
<AddressForm nestedFormCallback={nestedFormCallback} />
|
||||
</Section>
|
||||
<Section title="Medicine" collapsible={true}>
|
||||
<MedicineForm nestedFormCallback={nestedFormCallback} />
|
||||
</Section>
|
||||
<View style={{margin: 15}}>
|
||||
<Button onPress={handleButtonPress} title="Create Rx" />
|
||||
</View>
|
||||
</View>
|
||||
</Section>
|
||||
<ImportNsec
|
||||
isVisible={showImportNsec}
|
||||
onClose={() => {
|
||||
setShowImportNsec(false);
|
||||
}}
|
||||
onPress={handleImportNsec}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
68
components/common/ImportNsec.tsx
Normal file
68
components/common/ImportNsec.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import {useState} from 'react';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
NativeSyntheticEvent,
|
||||
Text,
|
||||
TextInput,
|
||||
TextInputChangeEventData,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
export const ImportNsec = ({
|
||||
isVisible,
|
||||
onClose,
|
||||
onPress,
|
||||
}: {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
onPress: (nsec: `nsec1${string}`) => void;
|
||||
}) => {
|
||||
const [nsec, setNsec] = useState('');
|
||||
|
||||
const handleNsec = (value: string) => {
|
||||
setNsec(value);
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
visible={isVisible}
|
||||
onRequestClose={() => {
|
||||
console.log('closing....');
|
||||
onClose();
|
||||
return true;
|
||||
}}
|
||||
onDismiss={() => {
|
||||
onClose();
|
||||
}}
|
||||
presentationStyle="pageSheet">
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: '#ffffff',
|
||||
height: 500,
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
margin: 30,
|
||||
borderColor: 'red',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<View style={{margin: 5}}>
|
||||
<Text style={{color: '#000000', margin: 5}}>Import Your Nsec</Text>
|
||||
<TextInput
|
||||
style={{
|
||||
borderColor: '#000000',
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
color: '#000000',
|
||||
}}
|
||||
onChangeText={handleNsec}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
title="Import"
|
||||
onPress={() => onPress(nsec as `nsec1${string}`)}></Button>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
43
components/common/Section.tsx
Normal file
43
components/common/Section.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import {PropsWithChildren, useState} from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
StyleSheet,
|
||||
Text,
|
||||
Touchable,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {styles} from './styles';
|
||||
|
||||
type SectionProps = PropsWithChildren<{
|
||||
title: string;
|
||||
collapsible?: boolean;
|
||||
}>;
|
||||
|
||||
export function Section({
|
||||
children,
|
||||
title,
|
||||
collapsible,
|
||||
}: SectionProps): React.JSX.Element {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
{collapsible ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
setCollapsed(!collapsed);
|
||||
}}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
{title} {collapsed ? '→' : '↓'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
)}
|
||||
{!collapsible || !collapsed ? (
|
||||
<Text style={[styles.sectionDescription]}>{children}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
39
components/common/styles.ts
Normal file
39
components/common/styles.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {Dimensions, StyleSheet} from 'react-native';
|
||||
import {Colors} from 'react-native/Libraries/NewAppScreen';
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
input: {
|
||||
height: 40,
|
||||
margin: 12,
|
||||
borderWidth: 1,
|
||||
borderBottomColor: 'white',
|
||||
padding: 10,
|
||||
color: Colors.white,
|
||||
},
|
||||
sectionContainer: {
|
||||
marginTop: 32,
|
||||
paddingHorizontal: 24,
|
||||
width: Dimensions.get('window').width - 80,
|
||||
color: Colors.white,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '600',
|
||||
color: Colors.white,
|
||||
},
|
||||
sectionDescription: {
|
||||
marginTop: 8,
|
||||
fontSize: 18,
|
||||
fontWeight: '400',
|
||||
color: Colors.white,
|
||||
},
|
||||
highlight: {
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
|
||||
export const TextTheme = [
|
||||
{
|
||||
color: Colors.white,
|
||||
},
|
||||
];
|
||||
@@ -1,33 +0,0 @@
|
||||
import {SimplePool} from 'nostr-tools';
|
||||
import {V1FormSpec} from '@formstr/sdk/dist/interfaces';
|
||||
|
||||
const relayList = ['wss://relay.primal.net/', 'wss://relay.hllo.live/'];
|
||||
|
||||
export const getFormTemplate = async (formId: string): Promise<{}> => {
|
||||
console.log('inside getFormTemplate');
|
||||
const pool = new SimplePool();
|
||||
let formIdPubkey = formId;
|
||||
console.log('everything initialised');
|
||||
const filter = {
|
||||
kinds: [0],
|
||||
authors: [formIdPubkey], //formId is the npub of the form
|
||||
};
|
||||
let kind0 = null
|
||||
try {
|
||||
console.log('inside trydasdsad');
|
||||
kind0 = await pool.get(relayList, filter)
|
||||
console.log('Main thread is working, got event', kind0);
|
||||
} catch (e) {
|
||||
console.log('inside catch');
|
||||
console.log('error is', e);
|
||||
}
|
||||
|
||||
pool.close(relayList);
|
||||
let formTemplate;
|
||||
if (kind0) {
|
||||
formTemplate = JSON.parse(kind0.content);
|
||||
} else {
|
||||
throw Error('Form template not found');
|
||||
}
|
||||
return formTemplate;
|
||||
};
|
||||
@@ -1,121 +0,0 @@
|
||||
{
|
||||
"name": "X Pharmacy ",
|
||||
"schemaVersion": "v1",
|
||||
"settings": {
|
||||
"titleImageUrl": "https://www.studentdoctor.net/wp-content/uploads/2018/08/20180815_prescription.png",
|
||||
"description": "Testing formstr for getting prescriptions",
|
||||
"thankYouPage": true
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"question": "Location",
|
||||
"answerType": "dropdown",
|
||||
"answerSettings": {
|
||||
"choices": [
|
||||
{
|
||||
"label": "Narnia",
|
||||
"choiceId": "ae9DZi"
|
||||
},
|
||||
{
|
||||
"label": "Hogwarts",
|
||||
"choiceId": "Dx4gyn"
|
||||
},
|
||||
{
|
||||
"label": "Mordor",
|
||||
"choiceId": "oWygzo"
|
||||
},
|
||||
{
|
||||
"label": "Tattooine",
|
||||
"choiceId": "dLgxns"
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
"questionId": "V7yEc2"
|
||||
},
|
||||
{
|
||||
"question": "Patient",
|
||||
"answerType": "label",
|
||||
"answerSettings": {},
|
||||
"questionId": "BPfwxq"
|
||||
},
|
||||
{
|
||||
"question": "Name",
|
||||
"answerType": "shortText",
|
||||
"answerSettings": {},
|
||||
"questionId": "HLydbI"
|
||||
},
|
||||
{
|
||||
"question": "Date Of Birth ",
|
||||
"answerType": "date",
|
||||
"answerSettings": {},
|
||||
"questionId": "lzpBhA"
|
||||
},
|
||||
{
|
||||
"question": "Address",
|
||||
"answerType": "paragraph",
|
||||
"answerSettings": {},
|
||||
"questionId": "Mvajrv"
|
||||
},
|
||||
{
|
||||
"question": "Phone",
|
||||
"answerType": "shortText",
|
||||
"answerSettings": {},
|
||||
"questionId": "PjTnfM"
|
||||
},
|
||||
{
|
||||
"question": "Allergies",
|
||||
"answerType": "shortText",
|
||||
"answerSettings": {},
|
||||
"questionId": "UMfscG"
|
||||
},
|
||||
{
|
||||
"question": "Current Meds ",
|
||||
"answerType": "shortText",
|
||||
"answerSettings": {},
|
||||
"questionId": "Cc8lih"
|
||||
},
|
||||
{
|
||||
"question": "Medications",
|
||||
"answerType": "label",
|
||||
"answerSettings": {},
|
||||
"questionId": "0R7uhf"
|
||||
},
|
||||
{
|
||||
"question": "Name",
|
||||
"answerType": "shortText",
|
||||
"answerSettings": {},
|
||||
"questionId": "WXJScB"
|
||||
},
|
||||
{
|
||||
"question": "Dosage Form",
|
||||
"answerType": "shortText",
|
||||
"answerSettings": {},
|
||||
"questionId": "BxEwHQ"
|
||||
},
|
||||
{
|
||||
"question": "Strength",
|
||||
"answerType": "shortText",
|
||||
"answerSettings": {},
|
||||
"questionId": "q2nC6e"
|
||||
},
|
||||
{
|
||||
"question": "Quantiy ",
|
||||
"answerType": "number",
|
||||
"answerSettings": {},
|
||||
"questionId": "fz68UV"
|
||||
},
|
||||
{
|
||||
"question": "Re-fills",
|
||||
"answerType": "shortText",
|
||||
"answerSettings": {},
|
||||
"questionId": "9SdbyN"
|
||||
},
|
||||
{
|
||||
"question": "Directions",
|
||||
"answerType": "paragraph",
|
||||
"answerSettings": {},
|
||||
"questionId": "6FrcPv"
|
||||
}
|
||||
]
|
||||
}
|
||||
1
index.js
1
index.js
@@ -4,6 +4,7 @@
|
||||
|
||||
import {AppRegistry} from 'react-native';
|
||||
import 'text-encoding-polyfill';
|
||||
import 'react-native-get-random-values';
|
||||
import App from './App';
|
||||
import {name as appName} from './app.json';
|
||||
|
||||
|
||||
25
package.json
25
package.json
@@ -10,15 +10,33 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/react-native": "^5.1.0",
|
||||
"@formstr/sdk": "^0.0.4-alpha",
|
||||
"@react-native-async-storage/async-storage": "^1.24.0",
|
||||
"@react-native-community/datetimepicker": "^8.0.1",
|
||||
"@react-native-community/segmented-control": "^2.2.2",
|
||||
"@react-native-community/slider": "^4.5.0",
|
||||
"@react-native-picker/picker": "^2.6.1",
|
||||
"@rneui/base": "^4.0.0-rc.8",
|
||||
"@rneui/themed": "^4.0.0-rc.8",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"nostr-tools": "^2.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.73.4",
|
||||
"react-native-crypto": "^2.2.0",
|
||||
"react-native-date-picker": "^5.0.3",
|
||||
"react-native-element-dropdown": "^2.10.4",
|
||||
"react-native-encrypted-storage": "^4.0.3",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-picker-select": "^9.0.1",
|
||||
"react-native-randombytes": "^3.6.1",
|
||||
"react-native-safe-area-context": "^4.5.0",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"text-encoding-polyfill": "^0.6.7"
|
||||
"react-native-vector-icons": "^10.1.0",
|
||||
"react-native-webview": "^13.8.4",
|
||||
"react-native-webview-crypto": "^0.0.25",
|
||||
"stream": "^0.0.2",
|
||||
"text-encoding-polyfill": "^0.6.7",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
@@ -29,6 +47,7 @@
|
||||
"@react-native/metro-config": "0.73.5",
|
||||
"@react-native/typescript-config": "0.73.1",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"babel-jest": "^29.6.3",
|
||||
"eslint": "^8.19.0",
|
||||
|
||||
721109
static/medicine_names.json
Normal file
721109
static/medicine_names.json
Normal file
File diff suppressed because it is too large
Load Diff
29
utils/localStorage.ts
Normal file
29
utils/localStorage.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
export const storeData = async (key: string, value: string): Promise<void> => {
|
||||
try {
|
||||
await AsyncStorage.setItem(key, value);
|
||||
console.log('Data stored successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to store data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getData = async (key: string): Promise<string | null> => {
|
||||
try {
|
||||
const value = await AsyncStorage.getItem(key);
|
||||
return value;
|
||||
} catch (error) {
|
||||
console.error('Failed to retrieve data:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const removeData = async (key: string): Promise<void> => {
|
||||
try {
|
||||
await AsyncStorage.removeItem(key);
|
||||
console.log('Data removed successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to remove data:', error);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user