This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-Mob/app/(root)/auth/otp.tsx
kirukib 7310a349c5 Add Gishen mobile app with complete demo shopping and care flows.
Ship Expo Router screens, seeded stores, and polished UX so auth, shop, checkout, prescriptions, loyalty, family, and account processes work end-to-end offline.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 01:21:32 +03:00

136 lines
4.3 KiB
TypeScript

import { useState } from "react";
import { Alert, View } from "react-native";
import { router } from "expo-router";
import * as LocalAuthentication from "expo-local-authentication";
import { useTranslation } from "react-i18next";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Button } from "@/components/ui/Button";
import { Field, SwitchRow } from "@/components/ui/Field";
import { Card } from "@/components/ui/Card";
import { Screen } from "@/components/ui/Screen";
import { Text } from "@/components/ui/Text";
import { DEMO_OTP } from "@/lib/commerce";
import { colors, gutter } from "@/lib/theme";
import { routes } from "@/lib/routes";
import { useAuthStore } from "@/lib/stores/authStore";
export default function OtpScreen() {
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const signIn = useAuthStore((s) => s.signIn);
const setHasOnboarded = useAuthStore((s) => s.setHasOnboarded);
const setBiometric = useAuthStore((s) => s.setBiometric);
const [phone, setPhone] = useState("+251911000000");
const [name, setName] = useState("Kirukib Abebe");
const [email, setEmail] = useState("kirukib@example.com");
const [code, setCode] = useState("");
const [sent, setSent] = useState(false);
const [bio, setBio] = useState(false);
const sendCode = () => {
if (!name.trim() || phone.replace(/\D/g, "").length < 9) {
Alert.alert("Missing info", "Enter your name and a valid phone number.");
return;
}
setSent(true);
Alert.alert(
"Code sent (demo)",
`Use OTP ${DEMO_OTP} to verify. No real SMS is sent in this build.`
);
};
const verify = async () => {
if (!sent) return;
if (code.trim() !== DEMO_OTP) {
Alert.alert("Invalid OTP", `Enter the demo code ${DEMO_OTP}.`);
return;
}
if (bio) {
const hw = await LocalAuthentication.hasHardwareAsync();
if (hw) {
const res = await LocalAuthentication.authenticateAsync({
promptMessage: "Enable biometric unlock",
});
if (!res.success) {
Alert.alert("Biometric", "Skipped — you can enable later in profile.");
}
}
}
signIn({
name: name.trim(),
phone: phone.trim(),
email: email.trim() || undefined,
isGuest: false,
});
setBiometric(bio);
setHasOnboarded(true);
router.replace(routes.home);
};
return (
<Screen>
<View
style={{
flex: 1,
paddingTop: insets.top + 24,
paddingBottom: insets.bottom + 20,
paddingHorizontal: gutter,
}}
>
<Text variant="h1">{t("common.signIn")}</Text>
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 8 }}>
{t("common.otpHint")} Demo OTP is always {DEMO_OTP}.
</Text>
<View style={{ marginTop: 20, gap: 12 }}>
<Field label="Full name" value={name} onChangeText={setName} icon="person-outline" />
<Field
label="Phone"
value={phone}
onChangeText={setPhone}
keyboardType="phone-pad"
icon="call-outline"
/>
<Field
label="Email (optional)"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
icon="mail-outline"
/>
{sent ? (
<Field
label="OTP code"
value={code}
onChangeText={setCode}
keyboardType="number-pad"
placeholder={DEMO_OTP}
icon="key-outline"
hint={`Demo code: ${DEMO_OTP}`}
/>
) : null}
<Card padded={false}>
<SwitchRow
icon="finger-print-outline"
title={t("common.enableBiometric")}
subtitle="Unlock the app faster next time"
value={bio}
onValueChange={setBio}
last
/>
</Card>
</View>
<View style={{ flex: 1 }} />
{!sent ? (
<Button title={t("common.sendOtp")} onPress={sendCode} />
) : (
<View style={{ gap: 10 }}>
<Button title={t("common.verify")} onPress={verify} />
<Button title="Resend code" variant="ghost" onPress={sendCode} />
</View>
)}
</View>
</Screen>
);
}