Step 9: Build Settings
What we are building
A patient account screen that edits fields on the active Patient profile, links to device management, lets the patient switch app language, surfaces the product's approved privacy and consent information, and signs out through the SDK.
What you should already have
- The authenticated route group and tabs from step 8.
useSessionreturning the active Patient profile.- CMS-backed i18next initialized in step 3.
- A product-approved privacy and consent destination.
The implementation
Create app/(patient)/settings.tsx:
import { LogoutButton, ProfileForm, useSession } from "@ovok/native";
import { LanguageSwitcher } from "@ovok/native/language-switcher";
import Constants from "expo-constants";
import { Link } from "expo-router";
import { useTranslation } from "react-i18next";
import { Alert, Linking, ScrollView } from "react-native";
import { Button, Card, Text } from "react-native-paper";
export default function SettingsScreen() {
const { t, i18n } = useTranslation();
const { status, profile } = useSession({ sessions: false });
if (status === "loading") {
return <Text>{t("settings.loading")}</Text>;
}
if (status !== "authenticated" || !profile) {
return <Text>{t("auth.sessionUnavailable")}</Text>;
}
const fields = [
{
name: "name[0].given",
label: t("settings.firstName"),
type: "text" as const,
value: profile.name?.[0]?.given?.[0] ?? "",
},
{
name: "name[0].family",
label: t("settings.lastName"),
type: "text" as const,
value: profile.name?.[0]?.family ?? "",
},
];
return (
<ScrollView contentContainerStyle={{ padding: 20, gap: 16 }}>
<Text variant="headlineMedium">{t("settings.title")}</Text>
<Card>
<Card.Title title={t("settings.profile")} />
<Card.Content>
<ProfileForm
fields={fields}
onSuccess={() =>
Alert.alert(t("settings.profileSaved"))
}
/>
</Card.Content>
</Card>
<Card>
<Card.Title title={t("settings.language")} />
<Card.Content>
<LanguageSwitcher
selectedLanguage={i18n.language}
onLanguageChange={(language) => void i18n.changeLanguage(language)}
>
<LanguageSwitcher.Option value="en">
<LanguageSwitcher.Option.RadioButton />
<LanguageSwitcher.Option.Label>
{t("languages.english")}
</LanguageSwitcher.Option.Label>
</LanguageSwitcher.Option>
<LanguageSwitcher.Option value="de">
<LanguageSwitcher.Option.RadioButton />
<LanguageSwitcher.Option.Label>
{t("languages.german")}
</LanguageSwitcher.Option.Label>
</LanguageSwitcher.Option>
</LanguageSwitcher>
</Card.Content>
</Card>
<Card>
<Card.Title title={t("settings.devices")} />
<Card.Content>
<Link href="/(patient)/devices">{t("settings.manageDevices")}</Link>
</Card.Content>
</Card>
<Card>
<Card.Title title={t("settings.privacyAndConsent")} />
<Card.Content>
<Text>{t("settings.privacyDescription")}</Text>
<Button
onPress={() => void Linking.openURL("https://actimi.com/privacy")}
>
{t("settings.privacyPolicy")}
</Button>
<Text>{t("settings.consentDescription")}</Text>
</Card.Content>
</Card>
<View>
<Text>{t("settings.notificationsNotConfigured")}</Text>
</View>
<LogoutButton
variant="button"
title={t("settings.signOut")}
onError={() =>
Alert.alert(t("settings.signOutFailed"), t("errors.tryAgain"))
}
/>
<Text>
{t("settings.appVersion", {
version: Constants.expoConfig?.version ?? "—",
})}
</Text>
</ScrollView>
);
}
Replace the privacy link with the privacy policy for your product, and connect the consent copy to the consent records or workflow your organization actually uses. The example link is only the public Actimi privacy page; it does not record consent.
The profile form patches only the fields passed to it. Check the project's Patient AccessPolicy and product rules before exposing editable fields. LogoutButton ends the current Ovok session; the protected layout routes the signed-out person back to sign-in.
This tutorial does not add notification toggles because no notification delivery channel has been configured. A settings control should reflect a real, supported capability.
Important Ovok decisions
- Profile fields are supplied from the loaded Patient profile;
ProfileFormdoes not fetch initial values itself. See ProfileForm. - Language selection changes the app's active i18next locale. The CMS hook fetches the matching published translation groups after the change. This tutorial keeps the choice in memory; persist it only if your app defines a preference-storage policy.
- Device pairing belongs on the Devices screen, not in account identity fields.
- A successful logout is followed by the auth guard; do not treat a rendered button callback as proof of server-side success.
Expected result
The Settings tab displays current profile fields, saves permitted edits, switches the CMS-backed app language between English and German, links to device management and privacy information, and signs the patient out.
Common errors and troubleshooting
- Profile values are blank: confirm
useSessionhas loaded the active Patient and the AccessPolicy permits the selected fields. - A profile edit is refused: check which Patient fields the policy and project allow the patient to update; remove unsupported fields from the form.
- Language changes but text does not: make sure the corresponding CMS translation groups are published for that locale and environment, and the screen uses
useTranslation()rather than hard-coded labels. - Logout shows an error: keep the user in the protected area until the auth state changes; show a retry path rather than assuming the request succeeded.
Next step
Continue to step 10: complete localisation.