Amba-Agent-App/app/(root)/(screens)/changepin.tsx
2026-01-16 00:22:35 +03:00

306 lines
8.5 KiB
TypeScript

import React, { useState, useRef, useEffect } from "react";
import { View, TouchableOpacity } from "react-native";
import { Text } from "~/components/ui/text";
import { Button } from "~/components/ui/button";
import BackButton from "~/components/ui/backButton";
import ScreenWrapper from "~/components/ui/ScreenWrapper";
import { PhonePinKeypad } from "~/components/ui/PhonePinKeypad";
import { router } from "expo-router";
import { useAuthWithProfile } from "~/lib/hooks/useAuthWithProfile";
import ModalToast from "~/components/ui/toast";
import { X } from "lucide-react-native";
type Step = "old" | "new" | "confirm";
export default function ChangePin() {
const { user } = useAuthWithProfile();
const [currentStep, setCurrentStep] = useState<Step>("old");
const [oldPin, setOldPin] = useState("");
const [newPin, setNewPin] = useState("");
const [confirmPin, setConfirmPin] = useState("");
const [loading, setLoading] = useState(false);
const [toastVisible, setToastVisible] = useState(false);
const [toastTitle, setToastTitle] = useState("");
const [toastDescription, setToastDescription] = useState<string | undefined>(
undefined
);
const [toastVariant, setToastVariant] = useState<
"success" | "error" | "warning" | "info"
>("info");
const toastTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const showToast = (
title: string,
description?: string,
variant: "success" | "error" | "warning" | "info" = "info"
) => {
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
}
setToastTitle(title);
setToastDescription(description);
setToastVariant(variant);
setToastVisible(true);
toastTimeoutRef.current = setTimeout(() => {
setToastVisible(false);
toastTimeoutRef.current = null;
}, 2500);
};
useEffect(() => {
return () => {
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
}
};
}, []);
const getCurrentPin = () => {
switch (currentStep) {
case "old":
return oldPin;
case "new":
return newPin;
case "confirm":
return confirmPin;
}
};
const handleKeyPress = (key: string) => {
const currentPin = getCurrentPin();
if (key === "clear") {
switch (currentStep) {
case "old":
setOldPin("");
break;
case "new":
setNewPin("");
break;
case "confirm":
setConfirmPin("");
break;
}
} else if (key === "backspace") {
switch (currentStep) {
case "old":
setOldPin((prev) => prev.slice(0, -1));
break;
case "new":
setNewPin((prev) => prev.slice(0, -1));
break;
case "confirm":
setConfirmPin((prev) => prev.slice(0, -1));
break;
}
} else if (currentPin.length < 6) {
switch (currentStep) {
case "old":
setOldPin((prev) => prev + key);
break;
case "new":
setNewPin((prev) => prev + key);
break;
case "confirm":
setConfirmPin((prev) => prev + key);
break;
}
}
};
const handleContinue = async () => {
const currentPin = getCurrentPin();
if (currentPin.length !== 6) {
showToast("Error", "Please enter a 6-digit PIN", "error");
return;
}
if (currentStep === "old") {
// TODO: Verify old PIN with backend
// For now, just move to next step
setLoading(true);
setTimeout(() => {
setLoading(false);
setCurrentStep("new");
}, 500);
} else if (currentStep === "new") {
if (newPin === oldPin) {
showToast("Error", "New PIN must be different from old PIN", "error");
return;
}
setCurrentStep("confirm");
} else if (currentStep === "confirm") {
if (confirmPin !== newPin) {
showToast("Error", "PINs do not match", "error");
setConfirmPin("");
return;
}
// TODO: Update PIN in backend
setLoading(true);
setTimeout(() => {
setLoading(false);
showToast("Success", "PIN changed successfully", "success");
setTimeout(() => {
router.back();
}, 1500);
}, 1000);
}
};
const getStepTitle = () => {
switch (currentStep) {
case "old":
return "Enter Old PIN";
case "new":
return "Enter New PIN";
case "confirm":
return "Confirm New PIN";
}
};
const getStepDescription = () => {
switch (currentStep) {
case "old":
return "Enter your current PIN";
case "new":
return "Enter your new PIN";
case "confirm":
return "Re-enter your new PIN to confirm";
}
};
const renderPinDots = () => {
const currentPin = getCurrentPin();
return (
<View className="mb-8">
<Text className="text-center text-base font-dmsans text-gray-600 mb-6">
{getStepDescription()}
</Text>
<View className="flex-row justify-between items-center px-5">
{[0, 1, 2, 3, 4, 5].map((index) => (
<View
key={index}
style={{
width: 18,
height: 18,
borderRadius: 999,
borderWidth: 2,
borderColor: index < currentPin.length ? "#105D38" : "#D1D5DB",
backgroundColor:
index < currentPin.length ? "#105D38" : "transparent",
}}
/>
))}
</View>
</View>
);
};
return (
<ScreenWrapper edges={[]}>
<BackButton />
<View className="flex-1 bg-white px-5">
{/* Header */}
<View className="py-8">
<Text className="text-3xl font-dmsans-bold text-gray-900 mb-2">
Change PIN
</Text>
<Text className="text-base font-dmsans text-gray-500">
{getStepDescription()}
</Text>
</View>
{/* Step Indicator */}
<View className="flex-row justify-center items-center mb-8">
<View
className={`w-8 h-8 rounded-full items-center justify-center ${
currentStep === "old" ? "bg-primary" : "bg-gray-300"
}`}
>
<Text
className={`font-dmsans-bold text-sm ${
currentStep === "old" ? "text-white" : "text-gray-600"
}`}
>
1
</Text>
</View>
<View className="w-12 h-1 bg-gray-300 mx-2" />
<View
className={`w-8 h-8 rounded-full items-center justify-center ${
currentStep === "new" || currentStep === "confirm"
? "bg-primary"
: "bg-gray-300"
}`}
>
<Text
className={`font-dmsans-bold text-sm ${
currentStep === "new" || currentStep === "confirm"
? "text-white"
: "text-gray-600"
}`}
>
2
</Text>
</View>
<View className="w-12 h-1 bg-gray-300 mx-2" />
<View
className={`w-8 h-8 rounded-full items-center justify-center ${
currentStep === "confirm" ? "bg-primary" : "bg-gray-300"
}`}
>
<Text
className={`font-dmsans-bold text-sm ${
currentStep === "confirm" ? "text-white" : "text-gray-600"
}`}
>
3
</Text>
</View>
</View>
{/* Spacer */}
<View className="flex-1" />
{/* PIN Dots - positioned right above keypad */}
{renderPinDots()}
{/* Keypad */}
<View className="mb-6">
<PhonePinKeypad onKeyPress={handleKeyPress} />
</View>
{/* Continue Button */}
<View className="pb-6">
<Button
className="bg-primary rounded-3xl"
onPress={handleContinue}
disabled={loading || getCurrentPin().length !== 6}
>
<Text className="font-dmsans text-white">
{loading
? "Processing..."
: currentStep === "confirm"
? "Change PIN"
: "Continue"}
</Text>
</Button>
</View>
</View>
<ModalToast
visible={toastVisible}
title={toastTitle}
description={toastDescription}
variant={toastVariant}
/>
</ScreenWrapper>
);
}