Commit 27dac325 authored by ThinhNC's avatar ThinhNC

Merge branch 'feat/handover-email-otp-ui' into 'develop'

feat(handover): display masked email and instructions for production OTP verification

See merge request !54
parents 68d16cdd 513e88f6
...@@ -391,5 +391,24 @@ export const DownloadIcon: React.FC<IconProps> = ({ size = 20, ...props }) => ( ...@@ -391,5 +391,24 @@ export const DownloadIcon: React.FC<IconProps> = ({ size = 20, ...props }) => (
</svg> </svg>
); );
// Mail / Email Icon
export const MailIcon: React.FC<IconProps> = ({ size = 20, ...props }) => (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<rect width="20" height="16" x="2" y="4" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
);
...@@ -2453,6 +2453,7 @@ ...@@ -2453,6 +2453,7 @@
"warningNotice2": "All wallets, income/expense history, budgets, and goals will be permanently moved to the new Zalo account.", "warningNotice2": "All wallets, income/expense history, budgets, and goals will be permanently moved to the new Zalo account.",
"warningNotice3": "Your current account will be immediately logged out and securely locked.", "warningNotice3": "Your current account will be immediately logged out and securely locked.",
"warningNotice4": "The QR code and PIN code will automatically expire after 15 minutes.", "warningNotice4": "The QR code and PIN code will automatically expire after 15 minutes.",
"warningNoticeEmail": "An email address must be linked to your account to receive the transfer authorization OTP.",
"btnInitiate": "Create Code", "btnInitiate": "Create Code",
"initiateSuccess": "Handover session created successfully", "initiateSuccess": "Handover session created successfully",
"timeLeft": "Time remaining:", "timeLeft": "Time remaining:",
...@@ -2464,6 +2465,8 @@ ...@@ -2464,6 +2465,8 @@
"cancelled": "Handover session cancelled", "cancelled": "Handover session cancelled",
"targetConnected": "New Account Connected!", "targetConnected": "New Account Connected!",
"otpAuthNotice": "Two-way Mutual Authorization:", "otpAuthNotice": "Two-way Mutual Authorization:",
"otpEmailSentTitle": "OTP Sent to Email",
"otpEmailSentDesc": "Check your inbox (or Spam/Junk folder) of:",
"otpInputLabel": "OTP Authorization Code (6 digits)", "otpInputLabel": "OTP Authorization Code (6 digits)",
"otpRequired": "Please enter the 6-digit OTP code", "otpRequired": "Please enter the 6-digit OTP code",
"btnConfirmOtp": "Authorize & Transfer Data Now", "btnConfirmOtp": "Authorize & Transfer Data Now",
......
...@@ -2554,6 +2554,7 @@ ...@@ -2554,6 +2554,7 @@
"warningNotice2": "Toàn bộ ví tiền, lịch sử thu chi, ngân sách, mục tiêu sẽ được chuyển vĩnh viễn sang tài khoản Zalo mới.", "warningNotice2": "Toàn bộ ví tiền, lịch sử thu chi, ngân sách, mục tiêu sẽ được chuyển vĩnh viễn sang tài khoản Zalo mới.",
"warningNotice3": "Tài khoản hiện tại sẽ tự động đăng xuất và bị khóa an toàn.", "warningNotice3": "Tài khoản hiện tại sẽ tự động đăng xuất và bị khóa an toàn.",
"warningNotice4": "Mã QR và mã PIN sẽ tự động hết hạn sau 15 phút.", "warningNotice4": "Mã QR và mã PIN sẽ tự động hết hạn sau 15 phút.",
"warningNoticeEmail": "Tài khoản cần có liên kết Email để nhận mã xác thực OTP ký duyệt chuyển giao.",
"btnInitiate": "Tạo Mã", "btnInitiate": "Tạo Mã",
"initiateSuccess": "Đã tạo mã chuyển giao thành công", "initiateSuccess": "Đã tạo mã chuyển giao thành công",
"timeLeft": "Hiệu lực mã:", "timeLeft": "Hiệu lực mã:",
...@@ -2565,6 +2566,8 @@ ...@@ -2565,6 +2566,8 @@
"cancelled": "Đã hủy phiên chuyển giao", "cancelled": "Đã hủy phiên chuyển giao",
"targetConnected": "Tài Khoản Mới Đã Kết Nối!", "targetConnected": "Tài Khoản Mới Đã Kết Nối!",
"otpAuthNotice": "Xác thực bảo mật 2 chiều:", "otpAuthNotice": "Xác thực bảo mật 2 chiều:",
"otpEmailSentTitle": "Đã gửi mã OTP qua Email",
"otpEmailSentDesc": "Kiểm tra hộp thư đến (hoặc hòm thư Rác/Spam) của:",
"otpInputLabel": "Mã xác thực OTP (6 chữ số)", "otpInputLabel": "Mã xác thực OTP (6 chữ số)",
"otpRequired": "Vui lòng nhập mã OTP gồm 6 chữ số", "otpRequired": "Vui lòng nhập mã OTP gồm 6 chữ số",
"btnConfirmOtp": "Ký Duyệt & Chuyển Dữ Liệu Ngay", "btnConfirmOtp": "Ký Duyệt & Chuyển Dữ Liệu Ngay",
......
...@@ -24,6 +24,7 @@ import { ...@@ -24,6 +24,7 @@ import {
TransferIcon, TransferIcon,
BudgetIcon, BudgetIcon,
SavingGoalIcon, SavingGoalIcon,
MailIcon,
} from "@/components/ui/icons"; } from "@/components/ui/icons";
import { useAuthStore } from "@/stores/auth-store"; import { useAuthStore } from "@/stores/auth-store";
import { handoverService } from "@/services/handover.service"; import { handoverService } from "@/services/handover.service";
...@@ -57,6 +58,7 @@ export const AccountHandoverPage: React.FC = () => { ...@@ -57,6 +58,7 @@ export const AccountHandoverPage: React.FC = () => {
const [targetUserName, setTargetUserName] = useState<string>(""); const [targetUserName, setTargetUserName] = useState<string>("");
const [otpPrompt, setOtpPrompt] = useState<string>(""); const [otpPrompt, setOtpPrompt] = useState<string>("");
const [otpDevCode, setOtpDevCode] = useState<string>(""); const [otpDevCode, setOtpDevCode] = useState<string>("");
const [maskedEmail, setMaskedEmail] = useState<string>("");
const [otpInput, setOtpInput] = useState<string>(""); const [otpInput, setOtpInput] = useState<string>("");
const [senderTimeLeft, setSenderTimeLeft] = useState<number>(900); const [senderTimeLeft, setSenderTimeLeft] = useState<number>(900);
const [isInitiating, setIsInitiating] = useState<boolean>(false); const [isInitiating, setIsInitiating] = useState<boolean>(false);
...@@ -162,6 +164,7 @@ export const AccountHandoverPage: React.FC = () => { ...@@ -162,6 +164,7 @@ export const AccountHandoverPage: React.FC = () => {
if (statusRes.targetUserName) setTargetUserName(statusRes.targetUserName); if (statusRes.targetUserName) setTargetUserName(statusRes.targetUserName);
if (statusRes.otpPrompt) setOtpPrompt(statusRes.otpPrompt); if (statusRes.otpPrompt) setOtpPrompt(statusRes.otpPrompt);
if (statusRes.otpCodeDev) setOtpDevCode(statusRes.otpCodeDev); if (statusRes.otpCodeDev) setOtpDevCode(statusRes.otpCodeDev);
if (statusRes.sourceUserEmailMasked) setMaskedEmail(statusRes.sourceUserEmailMasked);
} else if (statusRes.status === "EXPIRED") { } else if (statusRes.status === "EXPIRED") {
setSenderStatus("EXPIRED"); setSenderStatus("EXPIRED");
} }
...@@ -222,6 +225,7 @@ export const AccountHandoverPage: React.FC = () => { ...@@ -222,6 +225,7 @@ export const AccountHandoverPage: React.FC = () => {
setSenderStatus("IDLE"); setSenderStatus("IDLE");
setInitiateData(null); setInitiateData(null);
setOtpInput(""); setOtpInput("");
setMaskedEmail("");
openSnackbar({ openSnackbar({
type: "info", type: "info",
text: t("handover.cancelled") || "Đã hủy phiên chuyển giao", text: t("handover.cancelled") || "Đã hủy phiên chuyển giao",
...@@ -461,6 +465,10 @@ export const AccountHandoverPage: React.FC = () => { ...@@ -461,6 +465,10 @@ export const AccountHandoverPage: React.FC = () => {
{t("handover.warningNotice4") || {t("handover.warningNotice4") ||
"Mã QR và mã PIN sẽ tự động hết hạn sau 15 phút."} "Mã QR và mã PIN sẽ tự động hết hạn sau 15 phút."}
</li> </li>
<li>
{t("handover.warningNoticeEmail") ||
"Tài khoản cần có liên kết Email để nhận mã xác thực OTP ký duyệt chuyển giao."}
</li>
</ul> </ul>
</div> </div>
</div> </div>
...@@ -619,6 +627,22 @@ export const AccountHandoverPage: React.FC = () => { ...@@ -619,6 +627,22 @@ export const AccountHandoverPage: React.FC = () => {
</div> </div>
</div> </div>
{/* Email Sent Banner (Production / Real Flow) */}
{maskedEmail && (
<div className="p-3.5 rounded-clay-sm bg-clay-income/10 border border-clay-income/25 flex items-start gap-2.5">
<MailIcon size={18} className="text-clay-income-dark shrink-0 mt-0.5" />
<div className="text-xs text-clay-text leading-relaxed">
<span className="font-bold text-clay-income-dark block">
{t("handover.otpEmailSentTitle") || "Đã gửi mã OTP qua Email"}
</span>
<span>
{t("handover.otpEmailSentDesc") || "Kiểm tra hộp thư đến (hoặc hòm thư Rác/Spam) của:"}{" "}
<strong className="text-clay-primary font-bold">{maskedEmail}</strong>
</span>
</div>
</div>
)}
{/* Sandbox / Dev OTP Code Notice (if present) */} {/* Sandbox / Dev OTP Code Notice (if present) */}
{otpDevCode && ( {otpDevCode && (
<div className="p-2.5 rounded-clay-sm bg-clay-income/15 border border-clay-income/30 text-center"> <div className="p-2.5 rounded-clay-sm bg-clay-income/15 border border-clay-income/30 text-center">
......
...@@ -35,6 +35,7 @@ export interface HandoverStatusResponse { ...@@ -35,6 +35,7 @@ export interface HandoverStatusResponse {
targetUserName?: string | null; targetUserName?: string | null;
otpPrompt?: string; otpPrompt?: string;
otpCodeDev?: string; otpCodeDev?: string;
sourceUserEmailMasked?: string;
} }
export interface ClaimHandoverRequest { export interface ClaimHandoverRequest {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment