Skip to content

Commit

Permalink
feat: create example consent on sign up
Browse files Browse the repository at this point in the history
  • Loading branch information
mgramigna committed Dec 17, 2023
1 parent dd015c5 commit 8be1404
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 72 deletions.
42 changes: 35 additions & 7 deletions apps/expo/src/app/(auth)/sign-up.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import React, { useCallback, useState } from 'react';
import { Alert } from 'react-native';
import { Stack } from 'expo-router';
import { router, Stack } from 'expo-router';
import { BasicInfo } from '@/components/templates/SignUp/BasicInfo';
import { Complete } from '@/components/templates/SignUp/Complete';
import { Consents } from '@/components/templates/SignUp/Consents';
import { type BasicInfoFormType } from '@/components/templates/SignUp/types';
import { useAuth } from '@/context/AuthContext';
import { api } from '@/utils/api';
import { getCareTeamResource, getPatientResource } from '@/utils/fhir';
import { getCareTeamResource, getConsentToDisclose, getPatientResource } from '@/utils/fhir';
import { match } from 'ts-pattern';

type SignUpState = 'basic-info' | 'consents' | 'done';
type SignUpState = 'basic-info' | 'consents';

const SignUp = () => {
const [signUpState, setSignUpState] = useState<SignUpState>('basic-info');
const [createdPatientId, setCreatedPatientId] = useState<string>();

const { signIn } = useAuth();

const createCareteamMutation = api.careteam.update.useMutation({
onSuccess: () => {
setSignUpState('consents');
Expand All @@ -25,6 +27,17 @@ const SignUp = () => {
},
});

const createConsentMutation = api.consent.create.useMutation({
onSuccess: () => {
signIn(createdPatientId!);
router.replace('/home/');
},
onError: (e) => {
Alert.alert('Something went wrong');
Alert.alert(e.message);
},
});

const createCareteamForNewPatient = useCallback(
(patientId: string) => {
const careteamResource = getCareTeamResource({ patientId });
Expand Down Expand Up @@ -61,18 +74,33 @@ const SignUp = () => {
[createPatientMutation],
);

const handleConsentsContinue = useCallback(() => {
if (createdPatientId) {
const consentResource = getConsentToDisclose({ patientId: createdPatientId });

createConsentMutation.mutate(consentResource);
}
}, [createConsentMutation, createdPatientId]);

return (
<>
<Stack.Screen />
<Stack.Screen options={{ gestureEnabled: signUpState === 'basic-info' }} />
{match(signUpState)
.with('basic-info', () => (
<BasicInfo
onContinue={handleBasicInfoContinue}
isMutating={createPatientMutation.isPending}
/>
))
.with('consents', () => <Consents onContinue={() => setSignUpState('done')} />)
.with('done', () => <Complete patientId={createdPatientId!} />)
.with('consents', () =>
createdPatientId ? (
<Consents
onContinue={handleConsentsContinue}
patientId={createdPatientId}
isMutating={createConsentMutation.isPending}
/>
) : null,
)
.otherwise(() => null)}
</>
);
Expand Down
37 changes: 37 additions & 0 deletions apps/expo/src/components/atoms/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { TouchableWithoutFeedback, View } from 'react-native';
import { palette } from '@/theme/colors';
import { cn } from '@/utils/cn';
import { Ionicons } from '@expo/vector-icons';

import { Text } from './Text';

export const Checkbox = ({
onPress,
label,
checked,
}: {
onPress?: () => void;
label?: string;
checked?: boolean;
}) => {
return (
<TouchableWithoutFeedback onPress={onPress}>
<View className="flex flex-row items-center">
<View
className={cn(
'flex h-8 w-8 items-center justify-center rounded-md border-2 border-cyan-500',
checked && 'bg-cyan-500',
)}
>
<Ionicons
name="checkmark"
color={palette.coolGray[50]}
className={cn('m-auto')}
size={20}
/>
</View>
<Text className="ml-2">{label}</Text>
</View>
</TouchableWithoutFeedback>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback } from 'react';
import { ActivityIndicator, Modal, ScrollView, View } from 'react-native';
import { ActivityIndicator, Modal, View } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { getLocationAddress } from '@/fhirpath/location';
import { getPractitionerName } from '@/fhirpath/practitioner';
import { usePractitioner } from '@/hooks/usePractitioner';
Expand Down Expand Up @@ -75,7 +76,7 @@ export const ScheduleAppointmentModal = ({
onRequestClose={onClose}
>
<ScreenView>
<ScrollView className="flex h-full">
<KeyboardAwareScrollView className="flex h-full">
{isLoading ? (
<ActivityIndicator />
) : (
Expand Down Expand Up @@ -160,7 +161,7 @@ export const ScheduleAppointmentModal = ({
</View>
</>
)}
</ScrollView>
</KeyboardAwareScrollView>
</ScreenView>
</Modal>
);
Expand Down
58 changes: 25 additions & 33 deletions apps/expo/src/components/templates/SignUp/BasicInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useCallback, useState } from 'react';
import { ScrollView, TouchableOpacity, View } from 'react-native';
import DropDownPicker from 'react-native-dropdown-picker';
import { TouchableOpacity, View } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import DateTimePickerModal from 'react-native-modal-datetime-picker';
import { Link } from 'expo-router';
import { Button } from '@/components/atoms/Button';
import { RadioButton } from '@/components/atoms/RadioButton';
import { Text } from '@/components/atoms/Text';
import { TextInput } from '@/components/atoms/TextInput';
import { ScreenView } from '@/components/molecules/ScreenView';
Expand Down Expand Up @@ -32,10 +32,18 @@ export const BasicInfo = ({
} = useForm<BasicInfoFormType>({
resolver: zodResolver(BasicInfoFormSchema),
mode: 'onChange',
defaultValues: {
dateOfBirth: new Date('1996-07-19T00:00:00.0Z'),
gender: 'male',
email: 'matt@asdf.com',
lastName: 'Default',
firstName: 'Matt',
},
});

const currentBirthDate = watch('dateOfBirth');
const dobState = getFieldState('dateOfBirth');
const currentGender = watch('gender');

const onSubmit = useCallback(
(form: BasicInfoFormType) => {
Expand All @@ -44,8 +52,6 @@ export const BasicInfo = ({
[onContinue],
);

const [genderDropdownOpen, setGenderDropdownOpen] = useState(false);

return (
<ScreenView>
<KeyboardAwareScrollView showsVerticalScrollIndicator={false} className="h-full">
Expand Down Expand Up @@ -141,36 +147,22 @@ export const BasicInfo = ({
</View>
<View>
<Text className="mb-2 pl-1 text-xl">Gender</Text>
<Controller
name="gender"
control={control}
render={({ field: { value, onChange } }) => (
<DropDownPicker
open={genderDropdownOpen}
value={value}
items={genderOptions.map((option) => ({ label: option, value: option }))}
setOpen={setGenderDropdownOpen}
style={{
backgroundColor: palette.coolGray[100],
}}
listItemContainerStyle={{
backgroundColor: palette.coolGray[50],
borderWidth: 0,
}}
textStyle={{
fontFamily: 'OpenSans_400Regular',
}}
scrollViewProps={{
className: 'border-none',
}}
// eslint-disable-next-line @typescript-eslint/no-empty-function
setValue={() => {}}
onSelectItem={(item) => onChange(item.value)}
placeholder="Select one..."
listMode="SCROLLVIEW"
<View className="flex flex-row justify-evenly">
{genderOptions.map((gender) => (
<Controller
key={gender}
control={control}
name="gender"
render={({ field: { onChange } }) => (
<RadioButton
label={gender}
onPress={() => onChange(gender)}
selected={currentGender === gender}
/>
)}
/>
)}
/>
))}
</View>
</View>
</View>
<View className="mt-8 flex flex-row gap-8">
Expand Down
25 changes: 0 additions & 25 deletions apps/expo/src/components/templates/SignUp/Complete.tsx

This file was deleted.

87 changes: 83 additions & 4 deletions apps/expo/src/components/templates/SignUp/Consents.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,96 @@
import { useState } from 'react';
import { ScrollView, View } from 'react-native';
import { Button } from '@/components/atoms/Button';
import { Checkbox } from '@/components/atoms/Checkbox';
import { Skeleton } from '@/components/atoms/Skeleton';
import { Text } from '@/components/atoms/Text';
import { ScreenView } from '@/components/molecules/ScreenView';
import { isValid } from 'zod';
import { getFullName } from '@/fhirpath/patient';
import { api } from '@/utils/api';

export const Consents = ({
onContinue,
patientId,
isMutating,
}: {
onContinue: () => void;
patientId: string;
isMutating?: boolean;
}) => {
const [agreed, setAgreed] = useState(false);

const { data: patient } = api.patient.get.useQuery({
id: patientId,
});

export const Consents = ({ onContinue }: { onContinue: () => void }) => {
return (
<ScreenView>
<ScrollView className="h-full" showsVerticalScrollIndicator={false}>
<Text>consents</Text>
<Text className="pt-8 text-center text-5xl">🎉 Success 🎉</Text>
<View className="mt-4">
<Text>
Thanks for taking control of your healthcare! Before we get started, please read and
agree to the following consents:
</Text>
</View>
<View className="mt-8">
<Text>
1. I,{' '}
{patient ? (
<Text weight="bold">{getFullName(patient)}</Text>
) : (
<Skeleton className="bg-coolGray-300 h-4 w-10" />
)}{' '}
give permission for <Text weight="bold">Careforge</Text> to give me medical treatment.
</Text>
</View>
<View className="mt-4">
<Text>
2. I allow <Text weight="bold">Careforge</Text> to file for insurance benefits to pay
for the care I receive. I understand:
</Text>
<View className="mt-2 flex gap-2 pl-8">
<Text>
{'\u2022'} <Text weight="bold">Careforge</Text> will have to send my medical record
information to my insurance company.
</Text>

<Text>{'\u2022'} I must pay my share of the costs.</Text>
<Text>
{'\u2022'} I must pay for the cost of these services if my insurance does not pay or I
do not have insurance.
</Text>
</View>
</View>
<View className="mt-4">
<Text>3. I understand:</Text>
<View className="mt-2 flex gap-2 pl-8">
<Text>{'\u2022'} I have the right to refuse any procedure or treatment.</Text>

<Text>
{'\u2022'} I have the right to discuss all medical treatments with my clinician.
</Text>
<Text>
{'\u2022'} I must pay for the cost of these services if my insurance does not pay or I
do not have insurance.
</Text>
</View>
</View>
<View className="mt-4">
<Checkbox
checked={agreed}
onPress={() => setAgreed((curr) => !curr)}
label="I agree to the above consents"
/>
</View>
<View className="mt-8 flex flex-row gap-8">
<View className="flex-1">
<Button disabled={!isValid} text="Continue" onPress={onContinue} />
<Button
disabled={!agreed}
text="Continue"
onPress={onContinue}
isLoading={isMutating}
/>
</View>
</View>
</ScrollView>
Expand Down
Loading

0 comments on commit 8be1404

Please sign in to comment.