summaryrefslogtreecommitdiff
path: root/app/(tabs)/settings.tsx
diff options
context:
space:
mode:
authorJustZvan <justzvan@justzvan.xyz>2026-02-06 13:22:33 +0100
committerJustZvan <justzvan@justzvan.xyz>2026-02-06 13:22:33 +0100
commit7eb8ccae48b0cc18a9dcaa9c3626a02df8e6d919 (patch)
tree57b7dd06ac9aa7053c671d916f7183e3b4fa9410 /app/(tabs)/settings.tsx
feat: initial commit!
Diffstat (limited to 'app/(tabs)/settings.tsx')
-rw-r--r--app/(tabs)/settings.tsx221
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>
+ );
+}