Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

123 lines
4.6 KiB
TypeScript

import { useAlipayStrings } from '../hooks/useAlipayStrings';
import React, { useEffect, useRef, useState } from 'react';
import { IcDelete, IcClose } from '../res/icons';
import { useAlipayGestures } from '../hooks/useAlipayGestures';
interface SimplePaymentPasswordModalProps {
visible: boolean;
expectedPassword?: string;
onSuccess: () => void;
}
export const SimplePaymentPasswordModal: React.FC<SimplePaymentPasswordModalProps> = ({
visible,
expectedPassword = '000000',
onSuccess,
}) => {
const s = useAlipayStrings();
const { bindTap, bindBack } = useAlipayGestures();
const [password, setPassword] = useState('');
const [error, setError] = useState(false);
const calledRef = useRef(false);
useEffect(() => {
if (visible) {
setPassword('');
setError(false);
calledRef.current = false;
}
}, [visible]);
useEffect(() => {
if (password.length === 6) {
if (calledRef.current) return;
if (password === expectedPassword) {
calledRef.current = true;
setTimeout(() => {
onSuccess();
}, 200);
} else {
setError(true);
setPassword('');
}
} else {
if (error && password.length > 0) setError(false);
}
}, [password, expectedPassword, error, onSuccess]);
const handleNumberClick = (num: string) => {
if (password.length < 6) setPassword((prev) => prev + num);
};
const handleDelete = () => {
setPassword((prev) => prev.slice(0, -1));
};
if (!visible) return null;
return (
<div className="fixed inset-0 z-[120] bg-black/60 flex flex-col justify-end">
<div className="flex-1 flex items-center justify-center px-6">
<div className="w-full max-w-[380px] bg-app-surface rounded-2xl shadow-xl relative">
<button {...bindBack<HTMLButtonElement>({ stopPropagation: true })} className="absolute left-4 top-4 text-gray-300">
<IcClose size={20} />
</button>
<div className="pt-10 pb-6 px-6 text-center">
<div className="text-lg font-medium text-gray-900">{s.enter_payment_password}</div>
<div className="h-6 mt-2 text-sm">
{error ? <span className="text-[#FF3B30]">{s.incorrect_payment_password}</span> : <span className="text-transparent">{s.placeholder}</span>}
</div>
<div className="flex justify-center gap-0 mt-2">
{[0, 1, 2, 3, 4, 5].map((index) => (
<div key={index} className="w-12 h-12 border border-app-border flex items-center justify-center first:rounded-l-lg last:rounded-r-lg">
{password.length > index && <div className="w-2.5 h-2.5 rounded-full bg-black" />}
</div>
))}
</div>
<button className="mt-5 text-app-primary text-base">{s.forgot_password_2}</button>
</div>
</div>
</div>
<div className="bg-app-surface">
<div className="grid grid-cols-3">
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => (
<button
key={num}
{...bindTap<HTMLButtonElement>(
{ kind: 'action', id: 'fastPayPassword.keypad.press' },
{ params: { digit: String(num) }, onTrigger: () => handleNumberClick(String(num)) },
)}
className="h-20 flex items-center justify-center text-2xl text-gray-900 active:bg-gray-100 border-t border-gray-100 border-r border-gray-100 nth-[3n]:border-r-0"
>
{num}
</button>
))}
<div className="h-20 border-t border-gray-100 border-r border-gray-100"></div>
<button
{...bindTap<HTMLButtonElement>(
{ kind: 'action', id: 'fastPayPassword.keypad.press' },
{ params: { digit: '0' }, onTrigger: () => handleNumberClick('0') },
)}
className="h-20 flex items-center justify-center text-2xl text-gray-900 active:bg-gray-100 border-t border-gray-100 border-r border-gray-100"
>
0
</button>
<button
{...bindTap<HTMLButtonElement>(
{ kind: 'action', id: 'fastPayPassword.keypad.delete' },
{ onTrigger: handleDelete },
)}
className="h-20 flex items-center justify-center text-gray-900 active:bg-gray-100 border-t border-gray-100"
>
<div className="w-12 h-8 rounded-lg border border-gray-300 flex items-center justify-center">
<IcDelete size={18} className="text-gray-700" />
</div>
</button>
</div>
<div style={{ paddingBottom: 'env(safe-area-inset-bottom)' }} />
</div>
</div>
);
};