From e394f352e8aa98a329d504b9cc25bad1b62961b6 Mon Sep 17 00:00:00 2001 From: Thanakarn Klangkasame <77600906+Simulationable@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:01:28 +0700 Subject: [PATCH] Change Redeem Page --- .../(public)/redeem/[transactionId]/page.tsx | 129 +++++++++--------- 1 file changed, 68 insertions(+), 61 deletions(-) diff --git a/src/app/(public)/redeem/[transactionId]/page.tsx b/src/app/(public)/redeem/[transactionId]/page.tsx index b26ed2b..3ff3da3 100644 --- a/src/app/(public)/redeem/[transactionId]/page.tsx +++ b/src/app/(public)/redeem/[transactionId]/page.tsx @@ -1,8 +1,9 @@ +// File: src/app/(public)/redeem/[transactionId]/page.tsx "use client"; import { useState, useMemo } from "react"; import { useParams, useSearchParams } from "next/navigation"; -import BrandLogo from "@/app/component/common/BrandLogo" +import BrandLogo from "@/app/component/common/BrandLogo"; /* ======================= Config @@ -30,28 +31,25 @@ function uuidv4() { function getTenantKeyFromHost() { if (typeof window === "undefined") return process.env.NEXT_PUBLIC_DEFAULT_TENANT_KEY ?? "demo"; - const host = window.location.hostname; // e.g. tenant1.app.example.com + const host = window.location.hostname; const parts = host.split("."); - if (parts.length >= 3) return parts[0]; // subdomain as tenant key + if (parts.length >= 3) return parts[0]; return process.env.NEXT_PUBLIC_DEFAULT_TENANT_KEY ?? "demo"; } -// แปลงเลขไทย→อารบิก แล้วเหลือเฉพาะ 0-9 เท่านั้น function toAsciiDigitsOnly(input: string) { const map: Record = { "๐": "0", "๑": "1", "๒": "2", "๓": "3", "๔": "4", "๕": "5", "๖": "6", "๗": "7", "๘": "8", "๙": "9", }; const thToEn = input.replace(/[๐-๙]/g, (ch) => map[ch] ?? ch); - return thToEn.replace(/\D/g, ""); // keep digits only + return thToEn.replace(/\D/g, ""); } -// รับเฉพาะเบอร์มือถือไทย 10 หลัก ที่ขึ้นต้นด้วย 06/08/09 function isStrictThaiMobile10(digitsOnly: string) { return /^0(6|8|9)\d{8}$/.test(digitsOnly); } -// รูปแบบที่ดูเหมือน +66/66xxxxx (ไม่อนุญาต) function looksLikeCountryCodeFormat(raw: string) { const t = raw.trim(); return t.startsWith("+66") || /^66\d+/.test(t); @@ -63,7 +61,7 @@ function looksLikeCountryCodeFormat(raw: string) { async function redeemPoints(input: { tenantKey: string; transactionId: string; - phoneDigits10: string; // 0812345678 + phoneDigits10: string; idempotencyKey: string; }): Promise { const res = await fetch(`${API_BASE}/api/v1/loyalty/redeem`, { @@ -94,16 +92,14 @@ async function redeemPoints(input: { export default function RedeemPage() { const params = useParams<{ transactionId: string }>(); const search = useSearchParams(); - const transactionId = - params?.transactionId ?? search?.get("transactionId") ?? ""; + const transactionId = params?.transactionId ?? search?.get("transactionId") ?? ""; - const [phoneDigits, setPhoneDigits] = useState(""); // ตัวเลขล้วน + const [phoneDigits, setPhoneDigits] = useState(""); const [loading, setLoading] = useState(false); const [message, setMessage] = useState(null); const [error, setError] = useState(null); const [result, setResult] = useState(null); - // Validation message const phoneError: string | null = useMemo(() => { if (!phoneDigits) return null; if (looksLikeCountryCodeFormat(phoneDigits)) { @@ -120,29 +116,23 @@ export default function RedeemPage() { const phoneValid = phoneDigits.length === 10 && !phoneError && isStrictThaiMobile10(phoneDigits); - // --- Handlers: บังคับให้ "พิมพ์ได้แต่เลข" --- + // Input guards — allow only digits (รองรับเลขไทย) function handleChange(e: React.ChangeEvent) { const onlyDigits = toAsciiDigitsOnly(e.target.value).slice(0, 10); setPhoneDigits(onlyDigits); } - function handleBeforeInput(e: React.FormEvent) { - // ใช้ nativeEvent: InputEvent เพื่อเช็คตัวอักษรที่กำลังจะป้อน const ie = e.nativeEvent as InputEvent; const data = (ie && "data" in ie ? ie.data : undefined) ?? ""; - if (!data) return; // allow deletions/composition + if (!data) return; if (!/^[0-9๐-๙]+$/.test(data)) e.preventDefault(); } - function handleKeyDown(e: React.KeyboardEvent) { - // อนุญาตปุ่มควบคุมพื้นฐาน const allow = ["Backspace", "Delete", "Tab", "ArrowLeft", "ArrowRight", "Home", "End"]; if (allow.includes(e.key)) return; - // อนุญาตเฉพาะ 0-9 เท่านั้น if (/^[0-9]$/.test(e.key)) return; e.preventDefault(); } - function handlePaste(e: React.ClipboardEvent) { e.preventDefault(); const text = e.clipboardData.getData("text") ?? ""; @@ -170,7 +160,7 @@ export default function RedeemPage() { }); setResult(data); - setMessage(data.message ?? "ดำเนินการแลกคะแนนสำเร็จ"); + setMessage("สะสมคะแนนสำเร็จ"); } catch (err: unknown) { const msg = err instanceof Error ? err.message : "เกิดข้อผิดพลาด ไม่สามารถแลกคะแนนได้"; setError(msg); @@ -180,49 +170,66 @@ export default function RedeemPage() { } return ( -
-
- {/* Header / Branding */} -
+
+ {/* Background: mono grid + soft lights */} +
+
+
+ +
+ {/* Brand */} +
-

Redeem Points

-

- Transaction: {transactionId || "—"} -

+
+ {/* Headings */} +

+ Redeem Points +

+

+ Transaction: {transactionId || "—"} +

+ {/* Card */} -
+
{!transactionId && ( -
+
ไม่พบ TransactionId ใน URL โปรดสแกน QR ใหม่
)}
-
+
- -
+
+ + {/* subtle input gloss */} + +
+
รองรับเฉพาะเบอร์โทรศัพท์มือถือ
{phoneError && ( @@ -235,28 +242,28 @@ export default function RedeemPage() { + {/* Result banners */} {message && ( -
- {message} +
+ สะสมคะแนนสำเร็จ
)} {error && ( -
- {/*{error}*/} +
สะสมคะแนนไม่สำเร็จ
)} {result && ( -
+
รายละเอียด
-
+
{"balance" in result && (
ยอดคงเหลือใหม่: {result.balance}
)} @@ -271,7 +278,7 @@ export default function RedeemPage() {
)}
-
+
); }