diff options
Diffstat (limited to 'app/(tabs)/settings.tsx')
| -rw-r--r-- | app/(tabs)/settings.tsx | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/app/(tabs)/settings.tsx b/app/(tabs)/settings.tsx new file mode 100644 index 0000000..b4493b4 --- /dev/null +++ b/app/(tabs)/settings.tsx @@ -0,0 +1,221 @@ +import { Ionicons } from "@expo/vector-icons"; +import { useEffect, useState } from "react"; +import { View } from "react-native"; +import { + Device, + getDevices, + getUserProfile, + renameDevice, + UserProfile, + verifyEmail, +} from "../../api"; +import { useAuth } from "../../lib/auth"; +import { t } from "../../lib/locales"; +import { colors } from "../../lib/theme"; +import { + ActionRow, + AlertDialog, + Button, + Card, + Divider, + H2, + Pill, + PromptDialog, + Row, + Screen, +} from "../../lib/ui"; + +export default function SettingsScreen() { + const { signOut } = useAuth(); + const [devices, setDevices] = useState<Device[]>([]); + const [profile, setProfile] = useState<UserProfile | null>(null); + const [alertVisible, setAlertVisible] = useState(false); + const [renameVisible, setRenameVisible] = useState(false); + const [selectedDevice, setSelectedDevice] = useState<Device | null>(null); + const [verifyEmailVisible, setVerifyEmailVisible] = useState(false); + const [verifyEmailResult, setVerifyEmailResult] = useState<{ + visible: boolean; + success: boolean; + message: string; + }>({ visible: false, success: false, message: "" }); + + const showNotWired = () => setAlertVisible(true); + + const refreshDevices = () => { + getDevices().then(setDevices); + }; + + const refreshProfile = () => { + getUserProfile().then(setProfile); + }; + + useEffect(() => { + refreshDevices(); + refreshProfile(); + }, []); + + const handleRename = async (newName: string) => { + if (selectedDevice && newName) { + const success = await renameDevice(selectedDevice.id, newName); + if (success) { + refreshDevices(); + } + } + setRenameVisible(false); + setSelectedDevice(null); + }; + + const handleVerifyEmail = async (code: string) => { + if (code) { + const result = await verifyEmail(code); + if (result.success) { + setVerifyEmailResult({ + visible: true, + success: true, + message: t("emailVerifiedSuccessfully"), + }); + refreshProfile(); + } else { + setVerifyEmailResult({ + visible: true, + success: false, + message: result.error || t("failedToVerifyEmail"), + }); + } + } + setVerifyEmailVisible(false); + }; + + return ( + <Screen> + <AlertDialog + visible={alertVisible} + title={t("notWiredYet")} + message={t("hookThisUpLater")} + onClose={() => setAlertVisible(false)} + /> + + <PromptDialog + visible={renameVisible} + title={t("renameDevice")} + message={`${t("enterNewNameFor")} ${selectedDevice?.name}:`} + onClose={() => setRenameVisible(false)} + onSubmit={handleRename} + initialValue={selectedDevice?.name} + /> + + <PromptDialog + visible={verifyEmailVisible} + title={t("verifyEmailTitle")} + message={t("enterVerificationCode")} + onClose={() => setVerifyEmailVisible(false)} + onSubmit={handleVerifyEmail} + initialValue="" + /> + + <AlertDialog + visible={verifyEmailResult.visible} + title={verifyEmailResult.success ? t("success") : t("error")} + message={verifyEmailResult.message} + onClose={() => + setVerifyEmailResult({ ...verifyEmailResult, visible: false }) + } + /> + + <Card> + <Row + left={ + <View + style={{ flexDirection: "row", alignItems: "center", gap: 10 }} + > + <Ionicons + name="phone-portrait" + size={18} + color={colors.onBackground} + /> + <H2>{t("devices")}</H2> + </View> + } + right={ + <Pill + label={`${devices.length} ${ + devices.length !== 1 ? t("devicesPlural") : t("device") + }`} + /> + } + /> + <Divider /> + {devices.map((device, index) => ( + <View key={device.id}> + <ActionRow + title={device.name} + subtitle={`${t("lastSeen")}: ${device.lastCheck}`} + onPress={() => { + setSelectedDevice(device); + setRenameVisible(true); + }} + right={ + <View + style={{ flexDirection: "row", alignItems: "center", gap: 8 }} + > + <Pill + label={ + device.status === "online" ? t("online") : t("offline") + } + tone={device.status === "online" ? "good" : "attention"} + /> + <Ionicons + name="chevron-forward" + size={18} + color={colors.onSurfaceVariant} + /> + </View> + } + /> + {index !== devices.length - 1 && <Divider />} + </View> + ))} + <Divider /> + </Card> + + <Card> + <H2>{t("transparency")}</H2> + <Divider /> + <ActionRow + title={t("privacyAndTerms")} + subtitle={t("legalAndPrivacyInfo")} + onPress={showNotWired} + right={ + <Ionicons + name="chevron-forward" + size={18} + color={colors.onSurfaceVariant} + /> + } + /> + </Card> + + <Card> + <H2>{t("account")}</H2> + <Divider /> + {profile && !profile.emailVerified && ( + <> + <ActionRow + title={t("verifyEmail")} + subtitle={t("verifyYourEmailAddress")} + onPress={() => setVerifyEmailVisible(true)} + right={ + <Ionicons + name="mail-outline" + size={18} + color={colors.onSurfaceVariant} + /> + } + /> + </> + )} + <Button title={t("signOut")} variant="secondary" onPress={signOut} /> + </Card> + </Screen> + ); +} |