From aebf09220b0e09fa50abfc1224f5c1e4c3fc3e0b Mon Sep 17 00:00:00 2001 From: Thanakarn Klangkasame <77600906+Simulationable@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:04:50 +0700 Subject: [PATCH] Add Redeem Page --- .../images/messageImage_1756462829731.jpg | Bin 0 -> 10865 bytes .../(public)/redeem/[transactionId]/page.tsx | 290 ++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 public/assets/images/messageImage_1756462829731.jpg create mode 100644 src/app/(public)/redeem/[transactionId]/page.tsx diff --git a/public/assets/images/messageImage_1756462829731.jpg b/public/assets/images/messageImage_1756462829731.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a1692050e1044407c0796360fc2d873293aeb684 GIT binary patch literal 10865 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 literal 0 HcmV?d00001 diff --git a/src/app/(public)/redeem/[transactionId]/page.tsx b/src/app/(public)/redeem/[transactionId]/page.tsx new file mode 100644 index 0000000..da264b6 --- /dev/null +++ b/src/app/(public)/redeem/[transactionId]/page.tsx @@ -0,0 +1,290 @@ +// 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"; + +/* ======================= + Config +======================= */ +const LOGO_PATH = "/assets/images/messageImage_1756462829731.jpg"; +const API_BASE = process.env.NEXT_PUBLIC_EOP_PUBLIC_API_BASE ?? ""; + +/* ======================= + Utils +======================= */ +function uuidv4() { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID(); + return "idem-" + Date.now() + "-" + Math.random().toString(16).slice(2); +} + +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 parts = host.split("."); + if (parts.length >= 3) return parts[0]; // subdomain as tenant key + 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 +} + +// รับเฉพาะเบอร์มือถือไทย 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); +} + +/* ======================= + API +======================= */ +async function redeemPoints(input: { + tenantKey: string; + transactionId: string; + phoneDigits10: string; // 0812345678 + idempotencyKey: string; +}) { + const res = await fetch(`${API_BASE}/api/v1/loyalty/redeem`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Tenant-Key": input.tenantKey, + "Idempotency-Key": input.idempotencyKey, + }, + body: JSON.stringify({ + transactionId: input.transactionId, + contact: { phone: input.phoneDigits10 }, + metadata: { source: "qr-landing" }, + }), + cache: "no-store", + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(text || `HTTP ${res.status}`); + } + return res.json(); +} + +/* ======================= + Components +======================= */ +function BrandLogo() { + // ไม่มีกรอบ/เงา — ให้ภาพลอยบนพื้นหลังขาวของหน้า + return ( +
+ Brand Logo +
+ ); +} + +/* ======================= + Page +======================= */ +export default function RedeemPage() { + const params = useParams<{ transactionId: string }>(); + const search = useSearchParams(); + const transactionId = + (params?.transactionId as string) || search?.get("transactionId") || ""; + + 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)) { + return "กรุณาใส่เบอร์รูปแบบไทย 10 หลัก เช่น 0812345678 (ไม่ต้องใส่ +66)"; + } + if (phoneDigits.length > 0 && phoneDigits.length !== 10) { + return "กรุณากรอกเป็นตัวเลข 10 หลัก"; + } + if (phoneDigits.length === 10 && !isStrictThaiMobile10(phoneDigits)) { + return "รับเฉพาะเบอร์มือถือไทยที่ขึ้นต้นด้วย 06, 08 หรือ 09 เท่านั้น"; + } + return null; + }, [phoneDigits]); + + const phoneValid = phoneDigits.length === 10 && !phoneError && isStrictThaiMobile10(phoneDigits); + + // --- Handlers บังคับให้ "พิมพ์ได้แต่เลข" --- + function handleChange(e: React.ChangeEvent) { + const onlyDigits = toAsciiDigitsOnly(e.target.value).slice(0, 10); // cap 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 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") ?? ""; + const onlyDigits = toAsciiDigitsOnly(text); + if (!onlyDigits) return; + setPhoneDigits((prev) => (prev + onlyDigits).slice(0, 10)); + } + + async function onSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + setError(null); + setMessage(null); + setResult(null); + + try { + const tenantKey = getTenantKeyFromHost(); + const idempotencyKey = uuidv4(); + + const data = await redeemPoints({ + tenantKey, + transactionId, + phoneDigits10: phoneDigits, + idempotencyKey, + }); + + setResult(data); + setMessage(data.message ?? "ดำเนินการแลกคะแนนสำเร็จ"); + } catch (err: any) { + setError(err?.message ?? "เกิดข้อผิดพลาด ไม่สามารถแลกคะแนนได้"); + } finally { + setLoading(false); + } + } + + return ( +
+
+ {/* Header / Branding */} +
+ +

Redeem Points

+

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

+
+ + {/* Card */} +
+ {!transactionId && ( +
+ ไม่พบ TransactionId ใน URL โปรดสแกน QR ใหม่ +
+ )} + +
+
+ + +
+ รองรับเฉพาะเบอร์โทร 10 +
+ {phoneError && ( +
+ {phoneError} +
+ )} +
+ + +
+ + {message && ( +
+ {message} +
+ )} + {error && ( +
+ {/*{error}*/} + Redeem ไม่สำเร็จ +
+ )} + + {result && ( +
+
รายละเอียด
+
+ {"balance" in result && ( +
+ ยอดคงเหลือใหม่: {result.balance} +
+ )} + {"voucherCode" in result && ( +
+ รหัสคูปอง: {result.voucherCode} +
+ )} + {"ledgerEntryId" in result && ( +
+ Ledger: {result.ledgerEntryId} +
+ )} + {"redeemed" in result &&
สถานะ: {String(result.redeemed)}
} +
+
+ )} +
+
+
+ ); +} \ No newline at end of file