diff --git a/src/app/(public)/redeem/[transactionId]/page.tsx b/src/app/(public)/redeem/[transactionId]/page.tsx index da264b6..52e9866 100644 --- a/src/app/(public)/redeem/[transactionId]/page.tsx +++ b/src/app/(public)/redeem/[transactionId]/page.tsx @@ -1,16 +1,25 @@ -// File: src/app/(public)/redeem/[transactionId]/page.tsx "use client"; -import Image from "next/image"; import { useState, useMemo } from "react"; import { useParams, useSearchParams } from "next/navigation"; +import BrandLogo from "@/app/component/common/BrandLogo" /* ======================= Config ======================= */ -const LOGO_PATH = "/assets/images/messageImage_1756462829731.jpg"; const API_BASE = process.env.NEXT_PUBLIC_EOP_PUBLIC_API_BASE ?? ""; +/* ======================= + Types +======================= */ +type RedeemResponse = { + balance?: number; + voucherCode?: string; + ledgerEntryId?: string; + redeemed?: boolean; + message?: string; +}; + /* ======================= Utils ======================= */ @@ -56,7 +65,7 @@ async function redeemPoints(input: { transactionId: string; phoneDigits10: string; // 0812345678 idempotencyKey: string; -}) { +}): Promise { const res = await fetch(`${API_BASE}/api/v1/loyalty/redeem`, { method: "POST", headers: { @@ -76,26 +85,7 @@ async function redeemPoints(input: { const text = await res.text(); throw new Error(text || `HTTP ${res.status}`); } - return res.json(); -} - -/* ======================= - Components -======================= */ -function BrandLogo() { - // ไม่มีกรอบ/เงา — ให้ภาพลอยบนพื้นหลังขาวของหน้า - return ( -
- Brand Logo -
- ); + return res.json() as Promise; } /* ======================= @@ -105,13 +95,13 @@ export default function RedeemPage() { const params = useParams<{ transactionId: string }>(); const search = useSearchParams(); const transactionId = - (params?.transactionId as string) || search?.get("transactionId") || ""; + params?.transactionId ?? search?.get("transactionId") ?? ""; - const [phoneDigits, setPhoneDigits] = useState(""); // เก็บเฉพาะตัวเลขอย่างเดียว - const [loading, setLoading] = useState(false); + const [phoneDigits, setPhoneDigits] = useState(""); // ตัวเลขล้วน + const [loading, setLoading] = useState(false); const [message, setMessage] = useState(null); const [error, setError] = useState(null); - const [result, setResult] = useState(null); + const [result, setResult] = useState(null); // Validation message const phoneError: string | null = useMemo(() => { @@ -130,20 +120,22 @@ export default function RedeemPage() { const phoneValid = phoneDigits.length === 10 && !phoneError && isStrictThaiMobile10(phoneDigits); - // --- Handlers บังคับให้ "พิมพ์ได้แต่เลข" --- + // --- Handlers: บังคับให้ "พิมพ์ได้แต่เลข" --- function handleChange(e: React.ChangeEvent) { - const onlyDigits = toAsciiDigitsOnly(e.target.value).slice(0, 10); // cap 10 ตัวเลย + const onlyDigits = toAsciiDigitsOnly(e.target.value).slice(0, 10); setPhoneDigits(onlyDigits); } - function handleBeforeInput(e: React.FormEvent & { data?: string }) { - // block non-digits ที่กำลังจะป้อน (รองรับเลขไทย) - if (!e.data) return; // allow deletions/composition - if (!/^[0-9๐-๙]+$/.test(e.data)) e.preventDefault(); + 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 (!/^[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 เท่านั้น @@ -179,8 +171,9 @@ export default function RedeemPage() { setResult(data); setMessage(data.message ?? "ดำเนินการแลกคะแนนสำเร็จ"); - } catch (err: any) { - setError(err?.message ?? "เกิดข้อผิดพลาด ไม่สามารถแลกคะแนนได้"); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : "เกิดข้อผิดพลาด ไม่สามารถแลกคะแนนได้"; + setError(msg); } finally { setLoading(false); } @@ -208,7 +201,7 @@ export default function RedeemPage() {
- +
- รองรับเฉพาะเบอร์โทร 10 + รองรับเฉพาะตัวเลข 10 หลัก (06 / 08 / 09xxxxxxxx)
{phoneError && (
@@ -255,8 +248,7 @@ export default function RedeemPage() { )} {error && (
- {/*{error}*/} - Redeem ไม่สำเร็จ + {error}
)} @@ -265,19 +257,13 @@ export default function RedeemPage() {
รายละเอียด
{"balance" in result && ( -
- ยอดคงเหลือใหม่: {result.balance} -
+
ยอดคงเหลือใหม่: {result.balance}
)} {"voucherCode" in result && ( -
- รหัสคูปอง: {result.voucherCode} -
+
รหัสคูปอง: {result.voucherCode}
)} {"ledgerEntryId" in result && ( -
- Ledger: {result.ledgerEntryId} -
+
Ledger: {result.ledgerEntryId}
)} {"redeemed" in result &&
สถานะ: {String(result.redeemed)}
}
@@ -287,4 +273,4 @@ export default function RedeemPage() {
); -} \ No newline at end of file +} diff --git a/src/app/component/common/BrandLogo.tsx b/src/app/component/common/BrandLogo.tsx new file mode 100644 index 0000000..ef7fd5c --- /dev/null +++ b/src/app/component/common/BrandLogo.tsx @@ -0,0 +1,20 @@ +import Image from "next/image"; + +const LOGO_PATH = "/assets/images/messageImage_1756462829731.jpg"; + +function BrandLogo() { + return ( +
+ Brand Logo +
+ ); +} + +export default BrandLogo; \ No newline at end of file