Repository: BetterAngelsLA/monorepo
Pull request #1166: demographic info (DEV-1191)
Author: login: Davit-BetterAngels
Created: 2025-04-01T16:26:21Z | Merged: 2025-04-08T23:10:03Z

DEV-1191

## Summary by Sourcery

Implement a comprehensive demographic information form for client profiles, adding detailed fields for personal characteristics and physical description.

New Features:
- Create a new demographic information form with fields for gender, pronouns, race, place of birth, height, eye color, hair color, marital status, and physical description

Enhancements:
- Add a custom height input component that converts feet and inches to total inches
- Implement form handling using react-hook-form for demographic information

Tests:
- Update types to support new demographic information fields


Diff:
diff --git a/libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx b/libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx
index 60998a9816..5913aff81a 100644
--- a/libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx
+++ b/libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx
@@ -74,6 +74,12 @@ export default function ClientProfileView(props: ProfileProps) {
         componentName: ClientProfileCardEnum.ContactInfo,
       },
     },
+    [ClientProfileCardEnum.Demographic]: {
+      pathname: editProfileRoute,
+      params: {
+        componentName: ClientProfileCardEnum.Demographic,
+      },
+    },
   };
 
   function onClickEdit(card: ClientProfileCardEnum) {
diff --git a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
new file mode 100644
index 0000000000..e60cd578ac
--- /dev/null
+++ b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
@@ -0,0 +1,71 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import {
+  feetInchesToInches,
+  Form,
+  inchesToFeetInches,
+  Input_V2 as Input,
+} from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
+  const { setValue, getValues, watch } = useFormContext<
+    UpdateClientProfileInput | CreateClientProfileInput
+  >();
+
+  const heightInInchesDefault = watch('heightInInches');
+
+  const [height, setHeight] = useState<{ feet: string; inches: string }>({
+    feet: '',
+    inches: '',
+  });
+
+  useEffect(() => {
+    const heightInInches = feetInchesToInches(height.feet, height.inches);
+    const currentHeightInInches = getValues('heightInInches');
+
+    if (currentHeightInInches !== heightInInches) {
+      setValue('heightInInches', heightInInches);
+    }
+  }, [height, setValue, getValues]);
+
+  useEffect(() => {
+    if (heightInInchesDefault) {
+      const { feet, inches } = inchesToFeetInches(heightInInchesDefault);
+
+      setHeight({ feet, inches });
+    }
+  }, [heightInInchesDefault]);
+
+  return (
+    <Form.Field title="Height">
+      <View style={{ flexDirection: 'row', gap: Spacings.xs }}>
+        <View style={{ flex: 1 }}>
+          <Input
+            maxLength={1}
+            inputMode="numeric"
+            label="Feet"
+            placeholder="Feet"
+            value={height?.feet}
+            onChangeText={(e) => setHeight({ ...height, feet: e })}
+          />
+        </View>
+        <View style={{ flex: 1 }}>
+          <Input
+            maxLength={2}
+            label="Inches"
+            placeholder="Inches"
+            inputMode="numeric"
+            value={height?.inches}
+            onChangeText={(e) => setHeight({ ...height, inches: e })}
+          />
+        </View>
+      </View>
+    </Form.Field>
+  );
+}
diff --git a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx
index ec7587ff56..fcb07676c6 100644
--- a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx
+++ b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx
@@ -1,10 +1,166 @@
-import { Slot } from 'expo-router';
-import { View } from 'react-native';
+import {
+  ControlledInput,
+  Form,
+  MultiSelect,
+  SingleSelect,
+} from '@monorepo/expo/shared/ui-components';
+import { useFormContext, useWatch } from 'react-hook-form';
+import {
+  AdaAccommodationEnum,
+  EyeColorEnum,
+  GenderEnum,
+  HairColorEnum,
+  MaritalStatusEnum,
+  PronounEnum,
+  RaceEnum,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+import {
+  enumDisplayAdaAccommodationEnum,
+  enumDisplayEyeColor,
+  enumDisplayGender,
+  enumDisplayHairColor,
+  enumDisplayMaritalStatus,
+  enumDisplayPronoun,
+  enumDisplayRace,
+} from '../../../static';
+import Height from './Height';
+
+type TOption = { id: AdaAccommodationEnum; label: string };
+
+const options: TOption[] = (
+  Object.entries(enumDisplayAdaAccommodationEnum) as [
+    AdaAccommodationEnum,
+    string
+  ][]
+).map(([enumValue, displayValue]) => ({
+  id: enumValue,
+  label: displayValue,
+}));
 
 export default function DemographicInfo() {
+  const { control, setValue, watch } =
+    useFormContext<UpdateClientProfileInput>();
+
+  const [gender, pronouns, race, eyeColor, hairColor, maritalStatus] = useWatch(
+    {
+      control,
+      name: [
+        'gender',
+        'pronouns',
+        'race',
+        'eyeColor',
+        'hairColor',
+        'maritalStatus',
+      ],
+    }
+  );
+
+  const adaAccommodation = watch('adaAccommodation') || [];
+
   return (
-    <View>
-      <Slot />
-    </View>
+    <Form>
+      <Form.Field title="Gender">
+        <SingleSelect
+          placeholder="Select gender"
+          selectedValue={gender || undefined}
+          items={Object.entries(enumDisplayGender).map(
+            ([value, displayValue]) => ({ value, displayValue })
+          )}
+          onChange={(e) => setValue('gender', e as GenderEnum)}
+        />
+      </Form.Field>
+
+      <Form.Field title="Pronouns">
+        <SingleSelect
+          placeholder="Select pronouns"
+          selectedValue={pronouns || undefined}
+          items={Object.entries(enumDisplayPronoun).map(
+            ([value, displayValue]) => ({ value, displayValue })
+          )}
+          onChange={(e) => setValue('pronouns', e as PronounEnum)}
+        />
+      </Form.Field>
+
+      <Form.Field title="Race">
+        <SingleSelect
+          placeholder="Select race"
+          selectedValue={race || undefined}
+          items={Object.entries(enumDisplayRace).map(
+            ([value, displayValue]) => ({ value, displayValue })
+          )}
+          onChange={(e) => setValue('race', e as RaceEnum)}
+        />
+      </Form.Field>
+
+      <Form.Field title="Place of Birth">
+        <ControlledInput
+          placeholder="Enter place of birth"
+          name="placeOfBirth"
+          control={control}
+          onDelete={() => setValue('placeOfBirth', '')}
+        />
+      </Form.Field>
+
+      <Height />
+
+      <Form.Field title="Eye Color">
+        <SingleSelect
+          placeholder="Select eye color"
+          selectedValue={eyeColor || undefined}
+          items={Object.entries(enumDisplayEyeColor).map(
+            ([value, displayValue]) => ({ value, displayValue })
+          )}
+          onChange={(e) => setValue('eyeColor', e as EyeColorEnum)}
+        />
+      </Form.Field>
+
+      <Form.Field title="Hair Color">
+        <SingleSelect
+          placeholder="Select hair color"
+          selectedValue={hairColor || undefined}
+          items={Object.entries(enumDisplayHairColor).map(
+            ([value, displayValue]) => ({ value, displayValue })
+          )}
+          onChange={(e) => setValue('hairColor', e as HairColorEnum)}
+        />
+      </Form.Field>
+
+      <Form.Field title="Marital Status">
+        <SingleSelect
+          placeholder="Select marital status"
+          selectedValue={maritalStatus || undefined}
+          items={Object.entries(enumDisplayMaritalStatus).map(
+            ([value, displayValue]) => ({ value, displayValue })
+          )}
+          onChange={(e) => setValue('maritalStatus', e as MaritalStatusEnum)}
+        />
+      </Form.Field>
+
+      <Form.Field title="Physical Description">
+        <ControlledInput
+          placeholder="Enter physical description"
+          name="physicalDescription"
+          control={control}
+          onDelete={() => setValue('physicalDescription', '')}
+        />
+      </Form.Field>
+      <Form.Field title="ADA Accommodation">
+        <MultiSelect<TOption>
+          onChange={(selected) => {
+            const selectedEnums = selected.map((item) => item.id);
+
+            setValue('adaAccommodation', selectedEnums);
+          }}
+          options={options}
+          selected={adaAccommodation.map((item) => ({
+            id: item,
+            label: enumDisplayAdaAccommodationEnum[item],
+          }))}
+          valueKey="id"
+          labelKey="label"
+        />
+      </Form.Field>
+    </Form>
   );
 }
diff --git a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/extractClientFormData.ts b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/extractClientFormData.ts
index 0316086e9d..9538fc6b31 100644
--- a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/extractClientFormData.ts
+++ b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/extractClientFormData.ts
@@ -123,6 +123,35 @@ export const extractClientFormData = (
         socialMediaProfiles: updatedSocialMediaProfiles,
       };
     }
+    case ClientProfileCardEnum.Demographic: {
+      const {
+        id,
+        gender,
+        pronouns,
+        race,
+        placeOfBirth,
+        heightInInches,
+        eyeColor,
+        hairColor,
+        maritalStatus,
+        physicalDescription,
+        adaAccommodation,
+      } = clientProfile;
+
+      return {
+        id,
+        gender,
+        pronouns,
+        race,
+        placeOfBirth,
+        heightInInches,
+        eyeColor,
+        hairColor,
+        maritalStatus,
+        physicalDescription,
+        adaAccommodation,
+      };
+    }
 
     default:
       return {};
diff --git a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/index.tsx b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/index.tsx
index 710c152f61..d457ee2158 100644
--- a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/index.tsx
+++ b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/index.tsx
@@ -43,7 +43,7 @@ const formConfigs: Record<
     content: <ContactInfo />,
   },
   [ClientProfileCardEnum.Demographic]: {
-    title: 'Edit Demographic Details',
+    title: 'Edit Demographic Info',
     content: <DemographicInfo />,
   },
   [ClientProfileCardEnum.FullName]: {
diff --git a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/types.ts b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/types.ts
index e6c2b0d7ed..32ea88dc1e 100644
--- a/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/types.ts
+++ b/libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/types.ts
@@ -1,7 +1,14 @@
 import {
+  AdaAccommodationEnum,
+  EyeColorEnum,
+  GenderEnum,
+  HairColorEnum,
   LanguageEnum,
   LivingSituationEnum,
+  MaritalStatusEnum,
   PreferredCommunicationEnum,
+  PronounEnum,
+  RaceEnum,
   SocialMediaEnum,
   VeteranStatusEnum,
 } from '../../apollo';
@@ -30,8 +37,16 @@ export type TPhoneNumber = {
 };
 
 export type DemographicInfoState = {
-  // TODO: implement actual demographic form
-  name: string;
+  gender?: GenderEnum | null;
+  pronouns?: PronounEnum | null;
+  race?: RaceEnum | null;
+  placeOfBirth?: string | null;
+  heightInInches?: number | null;
+  eyeColor?: EyeColorEnum | null;
+  hairColor?: HairColorEnum | null;
+  maritalStatus?: MaritalStatusEnum | null;
+  physicalDescription?: string | null;
+  adaAccommodation?: AdaAccommodationEnum[] | null;
 };
 
 export type FullnameState = {
diff --git a/libs/expo/shared/ui-components/src/lib/Length/feetInchesToInches.ts b/libs/expo/shared/ui-components/src/lib/Length/feetInchesToInches.ts
new file mode 100644
index 0000000000..45d1311c70
--- /dev/null
+++ b/libs/expo/shared/ui-components/src/lib/Length/feetInchesToInches.ts
@@ -0,0 +1,5 @@
+export function feetInchesToInches(feet: string, inches: string): number {
+  const feetNum = parseInt(feet, 10) || 0;
+  const inchesNum = parseInt(inches, 10) || 0;
+  return feetNum * 12 + inchesNum;
+}
diff --git a/libs/expo/shared/ui-components/src/lib/Length/inchesToFeetInches.ts b/libs/expo/shared/ui-components/src/lib/Length/inchesToFeetInches.ts
new file mode 100644
index 0000000000..7097e94e0f
--- /dev/null
+++ b/libs/expo/shared/ui-components/src/lib/Length/inchesToFeetInches.ts
@@ -0,0 +1,8 @@
+export function inchesToFeetInches(heightInInches: number): {
+  feet: string;
+  inches: string;
+} {
+  const feet = Math.floor(heightInInches / 12).toString();
+  const inches = (heightInInches % 12).toString();
+  return { feet, inches };
+}
diff --git a/libs/expo/shared/ui-components/src/lib/Length/index.ts b/libs/expo/shared/ui-components/src/lib/Length/index.ts
index caf6ea6c01..7f63b96339 100644
--- a/libs/expo/shared/ui-components/src/lib/Length/index.ts
+++ b/libs/expo/shared/ui-components/src/lib/Length/index.ts
@@ -1,3 +1,5 @@
-export { Length } from './Length';
+export { feetInchesToInches } from './feetInchesToInches';
 export { getFormattedLength } from './getFormattedLength';
+export { inchesToFeetInches } from './inchesToFeetInches';
+export { Length } from './Length';
 export { TLengthUnit } from './types';
diff --git a/libs/expo/shared/ui-components/src/lib/index.ts b/libs/expo/shared/ui-components/src/lib/index.ts
index 7138c24a49..8c58dbb092 100644
--- a/libs/expo/shared/ui-components/src/lib/index.ts
+++ b/libs/expo/shared/ui-components/src/lib/index.ts
@@ -39,8 +39,15 @@ export { default as FormFieldLabel } from './FormFieldLabel';
 export { default as IconButton } from './IconButton';
 export { default as ImageViewer } from './ImageViewer';
 export { default as Input } from './Input';
+export { default as Input_V2 } from './Input_V2';
 export { default as KeyboardAwareScrollView } from './KeyboardAwareScrollView';
-export { Length, TLengthUnit, getFormattedLength } from './Length';
+export {
+  feetInchesToInches,
+  getFormattedLength,
+  inchesToFeetInches,
+  Length,
+  TLengthUnit,
+} from './Length';
 export { default as Loading } from './Loading';
 export { default as LoadingView } from './LoadingView';
 export { default as MediaPickerModal } from './MediaPickerModal';
@@ -64,10 +71,10 @@ export { default as Snackbar } from './Snackbar';
 export { SocialMediaIcon, TSocialMediaType } from './SocialMediaIcon';
 export { default as StatusBadge } from './StatusBadge';
 export { default as Tag } from './Tag';
+export { default as Textarea } from './Textarea';
 export { default as TextBold } from './TextBold';
 export { default as TextButton } from './TextButton';
 export { default as TextMedium } from './TextMedium';
 export { default as TextOrNode } from './TextOrNode';
 export { default as TextRegular } from './TextRegular';
-export { default as Textarea } from './Textarea';
 export * from './types';


Reviews:
user: login: sourcery-ai[bot]
body: Hey @Davit-BetterAngels - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider extracting the repeated SingleSelect code into a reusable component to reduce redundancy.
- The Height component could benefit from input validation to ensure only numbers are entered.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 1 issue found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟡 **Complexity**: 1 issue found
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T16:27:02Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces demographic information support for client profiles (DEV-1191) by adding new API fields, corresponding UI components, and routing changes. The key changes include:
- Adding new exports and reordering module exports in the shared UI components.
- Extending types and data extraction to support demographic information.
- Creating a new DemographicInfo form component (with a Height sub-component) and updating client profile routing.

### Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| libs/expo/shared/ui-components/src/lib/index.ts | Added export for Input_V2 and adjusted export order for Length and Textarea. |
| libs/expo/shared/ui-components/src/lib/SingleSelect/SingleSelect.tsx | Updated selection value type to include null. |
| libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/types.ts | Expanded DemographicInfoState with new demographic fields. |
| libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/extractClientFormData.ts | Added a new case to extract demographic information. |
| libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx | Created a DemographicInfo form with various demographic fields. |
| libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx | Introduced a new Height component for handling height input. |
| libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx | Added routing for the Demographic profile section. |
</details>





state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T16:27:15Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements a comprehensive demographic information form for client profiles by adding new fields and components to capture detailed personal and physical information. Key changes include:
- Addition of new demographic fields and corresponding types in the client profile forms.
- Introduction of a new DemographicInfo component with multiple SingleSelect and ControlledInput fields.
- Implementation of a custom Height component that converts feet and inches to total inches.

### Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| libs/expo/shared/ui-components/src/lib/index.ts | Updated exports with removal of duplicate Textarea export and reordering for consistency. |
| libs/expo/shared/ui-components/src/lib/SingleSelect/SingleSelect.tsx | Updated the selectedValue type to include null for improved consistency. |
| libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/types.ts | Added new demographic-related fields to support additional client profile details. |
| libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/extractClientFormData.ts | Added a new case for extracting demographic information from client profiles. |
| libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx | Created a comprehensive form for demographic info with the necessary fields and mapping from enumerations. |
| libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx | Introduced a new Height component to handle conversion logic between feet/inches and total inches. |
| libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx | Extended profile navigation to include the new Demographic section. |
</details>



<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx:102**
* The mapping for 'Hair Color' is using 'enumDisplayEyeColor' instead of an expected enum display value specific to hair color. Please verify if a dedicated 'enumDisplayHairColor' is available and update accordingly.
```
items={Object.entries(enumDisplayEyeColor).map(([value, displayValue]) => ({ value, displayValue }))}
```
**libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx:113**
* The 'Marital Status' field is incorrectly using 'enumDisplayEyeColor' for its item mapping instead of a proper marital status enum display. Please update it to use the correct display mapping, such as 'enumDisplayMaritalStatus' if available.
```
items={Object.entries(enumDisplayEyeColor).map(([value, displayValue]) => ({ value, displayValue }))}
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T17:13:06Z
user: login: tglaz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T18:58:41Z
user: login: sourcery-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T18:58:53Z
user: login: tglaz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T19:00:23Z
user: login: tglaz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T19:11:44Z
user: login: tglaz
body: Mainly left a comment about about using existing `getFormattedLength` function, similar as already done in `DemographicInfoCard`
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T19:14:03Z
user: login: Davit-BetterAngels
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T04:43:04Z
user: login: sourcery-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-03T04:43:23Z
user: login: shiozakiyuko
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-03T17:43:27Z
user: login: tglaz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T17:54:13Z
user: login: sourcery-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-03T17:54:26Z
user: login: shiozakiyuko
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-03T18:07:22Z
user: login: tglaz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T16:49:02Z

Review comments:
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: sourcery-ai[bot]
body: **issue (complexity):** Consider consolidating the two useEffect hooks into one, potentially using useMemo to derive the height in inches and a single useEffect to manage state synchronization, to avoid duplicated side-effect logic and improve performance..

```markdown
Consider consolidating the dual effects into one custom hook or use a `useMemo` value to derive the computed height. For example, you might extract the conversion logic and avoid having two separate effects. One approach would be to derive the inches value with a memoized calculation and use a single effect to keep form state in sync. For example:

```tsx
const computedHeightInInches = useMemo(() => {
  const feet = parseInt(height.feet, 10) || 0;
  const inches = parseInt(height.inches, 10) || 0;
  return feet * 12 + inches;
}, [height]);

useEffect(() => {
  // Initialize local state from form if needed
  if (heightInInchesDefault && heightInInchesDefault !== computedHeightInInches) {
    const feet = Math.floor(heightInInchesDefault / 12).toString();
    const inches = (heightInInchesDefault % 12).toString();
    setHeight({ feet, inches });
  } else if (computedHeightInInches !== getValues('heightInInches')) {
    setValue('heightInInches', computedHeightInInches);
  }
}, [computedHeightInInches, heightInInchesDefault, getValues, setValue]);
```

This way you avoid duplicating side-effect logic and keep the value conversion in one place. Adjust as needed to preserve your intended behavior.
```
created_at: 2025-04-01T16:27:02Z
author_association: NONE
diff_hunk: @@ -129,6 +129,35 @@ export const extractClientFormData = (
         phoneNumbers: updatedPhoneNumbers,
       };
     }
+    case ClientProfileCardEnum.Demographic: {
+      const {
+        id,
+        gender,
+        pronouns,
+        race,
+        placeOfBirth,
+        heightInInches,
+        eyeColor,
+        hairColor,
+        maritalStatus,
+        physicalDescription,
+        adaAccommodation,
+      } = clientProfile;
+
+      return {
+        id,
+        gender,
+        pronouns,
+        race,
+        placeOfBirth,
+        heightInInches,
+        eyeColor,
+        hairColor,
+        maritalStatus,
+        physicalDescription,
+        adaAccommodation,
+      };
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/extractClientFormData.ts
user: login: sourcery-ai[bot]
body: **suggestion (code-quality):** Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/inline-immediately-returned-variable))

```suggestion
      return clientProfile;

```

<br/><details><summary>Explanation</summary>Something that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
</details>
created_at: 2025-04-01T16:27:02Z
author_association: NONE
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
+  const { setValue, getValues, watch } = useFormContext<
+    UpdateClientProfileInput | CreateClientProfileInput
+  >();
+
+  const heightInInchesDefault = watch('heightInInches');
+
+  const [height, setHeight] = useState<{ feet: string; inches: string }>({
+    feet: '',
+    inches: '',
+  });
+
+  useEffect(() => {
+    const feetToInches = parseInt(height.feet, 10) || 0;
+    const inches = parseInt(height.inches, 10) || 0;
+    const heightInInches = feetToInches * 12 + inches;
+    const currentHeightInInches = getValues('heightInInches');
+
+    if (currentHeightInInches !== heightInInches) {
+      setValue('heightInInches', heightInInches);
+    }
+  }, [height, setValue, getValues]);
+
+  useEffect(() => {
+    if (heightInInchesDefault) {
+      const feet = Math.floor(heightInInchesDefault / 12).toString();
+      const inches = (heightInInchesDefault % 12).toString();
+      setHeight({ feet, inches });
+    }
+  }, [heightInInchesDefault]);
+
+  return (
+    <Form.Field title="Height">
+      <View style={{ flexDirection: 'row', gap: Spacings.xs }}>
+        <View style={{ flex: 1 }}>
+          <Input
+            maxLength={1}
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: Copilot
body: [nitpick] The 'Feet' input field has a maxLength set to 1, which might be too restrictive if two-digit values are ever necessary. Consider increasing it to 2 to accommodate all possible inputs.
```suggestion
            maxLength={2}
```
created_at: 2025-04-01T17:13:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: tglaz
body: @Davit-BetterAngels we have a [Length component](https://github.com/BetterAngelsLA/monorepo/blob/7cf2b0f6727977a6038e2071f31fb02a90e313da/libs/expo/shared/ui-components/src/lib/Length/Length.tsx) with some helpers that may work here.

`Length` has `inchesToFeetInches` function inside which you can use. May just want to add a reverse `feetInchesToInches` function inside `conversions.ts`

There's also `formattedHeight`, though it returns an already formatted string.

it's used in the `DemographicInfoCard`
```javascript
  const formattedHeight = getFormattedLength({
    length: heightInInches,
    inputUnit: 'inches',
    outputUnit: 'inches',
    format: 'feet-inches-symbol',
  });
```



created_at: 2025-04-01T18:58:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: sourcery-ai[bot]
body: I'll help clarify and provide guidance based on the conversation.

To @tglaz's point about the Length component, this is an excellent suggestion. The existing Length component in the shared UI components library could potentially be leveraged to simplify the height input implementation. 

I recommend that @Davit-BetterAngels:
1. Review the existing Length component to see if it meets the requirements for the height input
2. If the component is sufficiently flexible, consider refactoring the current Height component to use the shared Length component
3. This approach would promote code reuse and maintain consistency across the application

My previous review comment about consolidating the useEffect hooks still stands. By potentially using the shared Length component and implementing a more streamlined state management approach (like the useMemo suggestion), the code can be made more performant and readable.

Would you like me to elaborate on how to integrate the Length component or provide a more detailed refactoring example?
created_at: 2025-04-01T18:58:53Z
author_association: NONE
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
+  const { setValue, getValues, watch } = useFormContext<
+    UpdateClientProfileInput | CreateClientProfileInput
+  >();
+
+  const heightInInchesDefault = watch('heightInInches');
+
+  const [height, setHeight] = useState<{ feet: string; inches: string }>({
+    feet: '',
+    inches: '',
+  });
+
+  useEffect(() => {
+    const feetToInches = parseInt(height.feet, 10) || 0;
+    const inches = parseInt(height.inches, 10) || 0;
+    const heightInInches = feetToInches * 12 + inches;
+    const currentHeightInInches = getValues('heightInInches');
+
+    if (currentHeightInInches !== heightInInches) {
+      setValue('heightInInches', heightInInches);
+    }
+  }, [height, setValue, getValues]);
+
+  useEffect(() => {
+    if (heightInInchesDefault) {
+      const feet = Math.floor(heightInInchesDefault / 12).toString();
+      const inches = (heightInInchesDefault % 12).toString();
+      setHeight({ feet, inches });
+    }
+  }, [heightInInchesDefault]);
+
+  return (
+    <Form.Field title="Height">
+      <View style={{ flexDirection: 'row', gap: Spacings.xs }}>
+        <View style={{ flex: 1 }}>
+          <Input
+            maxLength={1}
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: tglaz
body: love it that Copilot thinks there may be people 10 ft tall lol
created_at: 2025-04-01T19:00:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
+  const { setValue, getValues, watch } = useFormContext<
+    UpdateClientProfileInput | CreateClientProfileInput
+  >();
+
+  const heightInInchesDefault = watch('heightInInches');
+
+  const [height, setHeight] = useState<{ feet: string; inches: string }>({
+    feet: '',
+    inches: '',
+  });
+
+  useEffect(() => {
+    const feetToInches = parseInt(height.feet, 10) || 0;
+    const inches = parseInt(height.inches, 10) || 0;
+    const heightInInches = feetToInches * 12 + inches;
+    const currentHeightInInches = getValues('heightInInches');
+
+    if (currentHeightInInches !== heightInInches) {
+      setValue('heightInInches', heightInInches);
+    }
+  }, [height, setValue, getValues]);
+
+  useEffect(() => {
+    if (heightInInchesDefault) {
+      const feet = Math.floor(heightInInchesDefault / 12).toString();
+      const inches = (heightInInchesDefault % 12).toString();
+      setHeight({ feet, inches });
+    }
+  }, [heightInInchesDefault]);
+
+  return (
+    <Form.Field title="Height">
+      <View style={{ flexDirection: 'row', gap: Spacings.xs }}>
+        <View style={{ flex: 1 }}>
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: tglaz
body: Won't block on this, but would we want to create a special `LengthInput` here? It could handle the conversions internally.
I use length vs height cause it can be used for distance as well.
created_at: 2025-04-01T19:11:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: Davit-BetterAngels
body: can't see where can this help. maybe its better to have pre built lengthInput but not sure if the helper will help.
created_at: 2025-04-03T04:43:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: sourcery-ai[bot]
body: I'll help clarify the potential benefits of the Length component and provide some constructive guidance.

While the existing Length component might not be a direct drop-in solution, it could still offer valuable utility functions that could simplify your height input implementation. The key is to look beyond just using the component and instead focus on potential helper functions or conversion logic.

Specifically, I recommend:

1. Check the `conversions.ts` file for existing utility functions like `inchesToFeetInches` and consider adding a complementary `feetInchesToInches` function if it doesn't exist.

2. Even if the Length component isn't directly usable, its underlying conversion logic might provide a clean, reusable approach to height conversion.

Here's a quick example of how a utility function might look:

```typescript
export const feetInchesToInches = (feet: number, inches: number): number => {
  return (feet * 12) + inches;
};

export const inchesToFeetInches = (totalInches: number): { feet: number, inches: number } => {
  const feet = Math.floor(totalInches / 12);
  const remainingInches = totalInches % 12;
  return { feet, inches: remainingInches };
};
```

These utility functions could help simplify your current implementation and provide a consistent way of handling height conversions across the application.

Would you like me to elaborate on how these might be integrated into your current Height component?
created_at: 2025-04-03T04:43:23Z
author_association: NONE
diff_hunk: @@ -74,6 +74,12 @@ export default function ClientProfileView(props: ProfileProps) {
         componentName: ClientProfileCardEnum.ContactInfo,
path: libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx
user: login: shiozakiyuko
body: Hi @Davit-BetterAngels 

I tested on both iOS and Android and the outcomes were the same. 

1. The page title should be "Edit Demographic Info".

**Edit side**

1. Hair color and Martial Status were using the Eye color option list.
2. When I tapped on Submit, I got an error (red snackbar). 

**View Side**
1. Physical Description is missing
2. ADA Accommodation is missing

https://github.com/user-attachments/assets/73c3d280-06f7-4876-ab3b-0bf2652e8349

<img src="https://github.com/user-attachments/assets/38cca3ba-dd89-4ab8-bf94-33f9a23784c1" width="300" />
<img src="https://github.com/user-attachments/assets/56671e50-3e23-44bd-9d18-7934fb4fa2ca" width="300" />
<img src="https://github.com/user-attachments/assets/d08ed3d9-6172-4e46-bc7c-be7d19a7606a" width="300" />
<img src="https://github.com/user-attachments/assets/cc0d0ccf-fd75-475f-b7dd-d46a3a95d4ff" width="300" />
<img src="https://github.com/user-attachments/assets/052291ab-e5ae-41d4-bf4f-e59cc9a99777" width="300" />
created_at: 2025-04-03T17:42:58Z
author_association: NONE
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: tglaz
body: it could help by not redoing custom calculations. We already have `inchesToFeetInches` in a lib. And adding `feetInchesToFeet` util right next to it should be trivial. 
created_at: 2025-04-03T17:54:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { Spacings } from '@monorepo/expo/shared/static';
+import { Form, Input_V2 as Input } from '@monorepo/expo/shared/ui-components';
+import { useEffect, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+import { View } from 'react-native';
+import {
+  CreateClientProfileInput,
+  UpdateClientProfileInput,
+} from '../../../apollo';
+
+export default function Height() {
path: libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx
user: login: sourcery-ai[bot]
body: Based on the conversation, I'll provide a constructive response that bridges the different perspectives:

@Davit-BetterAngels, I understand your hesitation about directly using the Length component. However, @tglaz makes a valid point about leveraging existing utility functions to avoid reinventing the wheel. 

The key recommendation is not necessarily to use the entire Length component, but to utilize the existing conversion utilities in `conversions.ts`. Specifically:
- There's already an `inchesToFeetInches` function
- We could easily add a complementary `feetInchesToInches` function

This approach offers several benefits:
1. Reduces duplicate code
2. Ensures consistent height calculations across the application
3. Minimizes the chance of calculation errors
4. Provides a centralized, reusable solution

Here's a quick example of how a simple utility function could look:

```typescript
export const feetInchesToInches = (feet: number, inches: number): number => {
  return (feet * 12) + inches;
};
```

This function could be added alongside the existing `inchesToFeetInches` utility, providing a clean, simple way to convert between feet/inches and total inches.

Would you be open to exploring this approach? It doesn't require wholesale changes to your component but could simplify your height conversion logic.
created_at: 2025-04-03T17:54:26Z
author_association: NONE
diff_hunk: @@ -74,6 +74,12 @@ export default function ClientProfileView(props: ProfileProps) {
         componentName: ClientProfileCardEnum.ContactInfo,
path: libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx
user: login: shiozakiyuko
body: One more.

1. The placeholders on height are missing: "Feet" and "Inches"

<img src="https://github.com/user-attachments/assets/396841e3-9331-4ce2-97a9-28a2d569e288" width="300"  />

created_at: 2025-04-03T18:07:21Z
author_association: NONE

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-04-01T16:26:25Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request implements the Demographic Info form for the client profile, including UI components, form state management, data extraction, and type definitions. It also adds a route for the demographic info form and updates the shared UI components library.

_No diagrams generated as the changes look simple and do not need a visual representation._

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Implemented the Demographic Info form using UI components and React Hook Form. | <ul><li>Added SingleSelect components for gender, pronouns, race, eye color, hair color, and marital status.</li><li>Added ControlledInput components for place of birth and physical description.</li><li>Implemented a Height component with separate inputs for feet and inches, converting to inches for the form value.</li><li>Utilized useFormContext and useWatch from React Hook Form to manage form state.</li><li>Populated SingleSelect components with data from enumDisplay constants.</li><li>Implemented logic to pre-populate the height fields when editing a profile.</li><li>Added form fields for gender, pronouns, race, place of birth, height, eye color, hair color, marital status and physical description.</li></ul> | `libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/index.tsx`<br/>`libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/DemographicInfo/Height.tsx` |
| Updated extractClientFormData to include demographic information. | <ul><li>Added a case for ClientProfileCardEnum.Demographic to extract demographic fields from the client profile.</li><li>Extracted fields such as gender, pronouns, race, place of birth, height, eye color, hair color, marital status, physical description, and adaAccommodation.</li></ul> | `libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/extractClientFormData.ts` |
| Updated types to include demographic information. | <ul><li>Added DemographicInfoState type with fields for gender, pronouns, race, place of birth, height, eye color, hair color, marital status, physical description, and adaAccommodation.</li></ul> | `libs/expo/betterangels/src/lib/screens/ClientProfileForms-V2/types.ts` |
| Added a route for the demographic info form. | <ul><li>Added a new entry in the navigation configuration for the Demographic card.</li><li>Configured the route to point to the edit profile screen with the componentName parameter set to ClientProfileCardEnum.Demographic.</li></ul> | `libs/expo/betterangels/src/lib/screens/Client/ClientProfile_V2/index.tsx` |
| Added Input_V2 and Textarea components to the shared UI components library. | <ul><li>Exported Input_V2 and Textarea from the shared UI components library.</li></ul> | `libs/expo/shared/ui-components/src/lib/index.ts` |
| Allowed null values for selectedValue prop in SingleSelect component. | <ul><li>Updated the type definition of selectedValue in the SingleSelect component to accept null values.</li></ul> | `libs/expo/shared/ui-components/src/lib/SingleSelect/SingleSelect.tsx` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-01T17:16:45Z
body: <!-- continuous-deploy-fingerprint-projectId:53171ba4-60ca-40cb-b3e6-b0c2393677b8 -->
🚀 Expo continuous deployment is ready for **betterangels**!

- Project → **betterangels**
- Environment → **Preview**
- Platforms → **android**, **ios**
- Scheme → **betterangels**

&nbsp; | 🤖 Android | 🍎 iOS
--- | --- | ---
Runtime Version | `90e7644bb8a28fb03901eed8d489059bbb91dc17` | `90e7644bb8a28fb03901eed8d489059bbb91dc17`
Build Details | [Build Permalink](https://expo.dev/accounts/better-angels/projects/betterangels/builds/8467e519-8480-4f25-a550-4c01b0df5a4f)<br /><details><summary>Details</summary>Distribution: `INTERNAL`<br />Build profile: `preview`<br />Runtime version: `90e7644bb8a28fb03901eed8d489059bbb91dc17`<br />App version: `1.0.39`<br />Git commit: `38f51f8d04dfaf3b45d9dc186185f3289d6d52fc`</details> | [Build Permalink](https://expo.dev/accounts/better-angels/projects/betterangels/builds/62d42b5d-2b64-4e3c-89aa-265274083f18)<br /><details><summary>Details</summary>Distribution: `INTERNAL`<br />Build profile: `preview`<br />Runtime version: `90e7644bb8a28fb03901eed8d489059bbb91dc17`<br />App version: `1.0.39`<br />Git commit: `38f51f8d04dfaf3b45d9dc186185f3289d6d52fc`</details>
Update Details | [Update Permalink](https://expo.dev/projects/53171ba4-60ca-40cb-b3e6-b0c2393677b8/updates/4dcbebb3-049e-4636-b7f2-1ea8b3ce4ab7)<br /><details><summary>Details</summary>Branch: `DEV-1191/demographic-info-v2`<br />Runtime version: `90e7644bb8a28fb03901eed8d489059bbb91dc17`<br />Git commit: `ddcca63eaee4c4231b212ad344bd2469018d55d9`</details> | [Update Permalink](https://expo.dev/projects/53171ba4-60ca-40cb-b3e6-b0c2393677b8/updates/bd252d6b-6ee7-40e7-b9bf-0dc5b0cec4de)<br /><details><summary>Details</summary>Branch: `DEV-1191/demographic-info-v2`<br />Runtime version: `90e7644bb8a28fb03901eed8d489059bbb91dc17`<br />Git commit: `ddcca63eaee4c4231b212ad344bd2469018d55d9`</details>
Update QR   | <a href="https://qr.expo.dev/eas-update?appScheme=betterangels&projectId=53171ba4-60ca-40cb-b3e6-b0c2393677b8&groupId=35dfb849-3294-4168-9c48-73cbd292ac27"><img src="https://qr.expo.dev/eas-update?appScheme=betterangels&projectId=53171ba4-60ca-40cb-b3e6-b0c2393677b8&groupId=35dfb849-3294-4168-9c48-73cbd292ac27" width="250px" height="250px" /></a> | <a href="https://qr.expo.dev/eas-update?appScheme=betterangels&projectId=53171ba4-60ca-40cb-b3e6-b0c2393677b8&groupId=35dfb849-3294-4168-9c48-73cbd292ac27"><img src="https://qr.expo.dev/eas-update?appScheme=betterangels&projectId=53171ba4-60ca-40cb-b3e6-b0c2393677b8&groupId=35dfb849-3294-4168-9c48-73cbd292ac27" width="250px" height="250px" /></a>

**iOS Simulator Build:** [Simulator Build Link](https://expo.dev/accounts/better-angels/projects/betterangels/builds/d5ea8e8f-66ac-4092-baa7-e3179bee7993)
author_association: NONE