student 3 주 전
부모
커밋
e67f6283ab

BIN
Content_million.xlsx


+ 1 - 1
website/Areas/LotteryV2/Views/Home/Index.cshtml

@@ -102,7 +102,7 @@
                     <div class="col-span-4 flex flex-col items-center justify-center text-center px-1">
                         <div class="text-[10px] uppercase font-bold text-gray-200 mb-0.5 whitespace-nowrap opacity-90 leading-none">@Lang.jackpot_prize</div>
                         <div class="flex items-start gap-0.5">
-                            <span class="text-[23px] font-black text-[#FBF3A7] leading-none" style="text-shadow: 0 4px 8px rgba(0,0,0,0.3)">500.000</span>
+                            <span class="text-[23px] font-black text-[#FBF3A7] leading-none" style="text-shadow: 0 4px 8px rgba(0,0,0,0.3)">30.000.000</span>
                             <span class="text-[8px] font-bold text-white uppercase opacity-90 mt-1">HTG</span>
                         </div>
                         <div class="text-[10px] text-white/90 font-medium whitespace-nowrap mt-1">@Lang.next : <span class="font-black text-yellow-300 countdown-timer">00:00:00</span></div>

+ 79 - 3
website/Areas/Millions/Controllers/HomeController.cs

@@ -877,6 +877,67 @@ namespace LotteryWebApp.Areas.Millions.Controllers
             return PartialView("_TermUserTicketHistory", model);
         }
 
+        public IActionResult RewardHistory(string seqPage)
+        {
+            RewardHistoryModel model = BuildRewardHistory(seqPage, fallbackRedirectOnExpired: true, out IActionResult redirect);
+            if (redirect != null) return redirect;
+            return View(model);
+        }
+
+        public IActionResult RewardHistoryPartial(string seqPage)
+        {
+            var token = HttpContext.Session.GetComplexData<string>("token");
+            if (string.IsNullOrEmpty(token))
+                return Json(new { responseCode = Code.SESSION_EXPIRED, responseMessage = "Session expired" });
+
+            RewardHistoryModel model = BuildRewardHistory(seqPage, fallbackRedirectOnExpired: false, out _);
+            return PartialView("_RewardHistoryList", model);
+        }
+
+        private RewardHistoryModel BuildRewardHistory(string seqPage, bool fallbackRedirectOnExpired, out IActionResult redirect)
+        {
+            redirect = null;
+            RewardHistoryModel model = new RewardHistoryModel
+            {
+                seqPage = string.IsNullOrEmpty(seqPage) ? "1" : seqPage
+            };
+
+            try
+            {
+                var token = HttpContext.Session.GetComplexData<string>("token");
+                if (string.IsNullOrEmpty(token))
+                {
+                    if (fallbackRedirectOnExpired)
+                        redirect = RedirectToAction("Login", "Account", new { area = "" });
+                    return model;
+                }
+
+                string msisdn = HttpContext.Session.GetComplexData<string>("msisdn");
+
+                TransactionInfoRequest request = new TransactionInfoRequest
+                {
+                    msisdn = msisdn,
+                    token = token,
+                    order = Constants.DECS,
+                    rowsOnPage = Constants.ROW_ON_PAGE,
+                    seqPage = model.seqPage
+                };
+
+                TransactionInfoResponse result = api.GetTransactionInfoApi(configuration, request);
+                if (result != null && result.responseCode == Code.SUCCESS)
+                {
+                    model.list = result.list ?? new List<Transaction>();
+                    model.totalPage = result.totalPage ?? "0";
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error(ex);
+            }
+
+            return model;
+        }
+
         [HttpPost]
         [ValidateAntiForgeryToken]
         public IActionResult TermResult(string termType)
@@ -884,7 +945,13 @@ namespace LotteryWebApp.Areas.Millions.Controllers
             try
             {
                 var token = HttpContext.Session.GetComplexData<string>("token");
-                if (string.IsNullOrEmpty(token)) return Json(new { responseCode = Code.SESSION_EXPIRED, responseMessage = "Session expired" });
+                var msisdn = HttpContext.Session.GetComplexData<string>("msisdn");
+                log.Info($"[Millions.TermResult] START - termType={termType}, msisdn={msisdn}");
+                if (string.IsNullOrEmpty(token))
+                {
+                    log.Info($"[Millions.TermResult] Session expired - termType={termType}, msisdn={msisdn}");
+                    return Json(new { responseCode = Code.SESSION_EXPIRED, responseMessage = "Session expired" });
+                }
 
                 string lang = CultureInfo.CurrentCulture.Name;
                 string langValue = (lang.StartsWith("en") || lang.StartsWith("fr")) ? "0" : "1";
@@ -902,11 +969,12 @@ namespace LotteryWebApp.Areas.Millions.Controllers
                 };
 
                 ResultOfTermResponse result = api.GetResultOfTermApi(configuration, request);
+                log.Info($"[Millions.TermResult] Response - termType={termType}, code={result?.responseCode}, msg={result?.responseMessage}, termCount={result?.listTerm?.Count ?? 0}");
                 return Json(result);
             }
             catch (Exception ex)
             {
-                log.Error(ex);
+                log.Error($"[Millions.TermResult] EXCEPTION - termType={termType}", ex);
                 return Json(new { responseCode = Code.ERROR, responseMessage = ex.Message });
             }
         }
@@ -1078,13 +1146,17 @@ namespace LotteryWebApp.Areas.Millions.Controllers
             try
             {
                 var token = HttpContext.Session.GetComplexData<string>("token");
+                var msisdn = HttpContext.Session.GetComplexData<string>("msisdn");
+                log.Info($"[Millions.BuyTicket] START - termType={termType}, msisdn={msisdn}");
                 if (string.IsNullOrEmpty(token) && !User.Identity.IsAuthenticated)
                 {
+                    log.Info($"[Millions.BuyTicket] Redirect login - missing token, termType={termType}, msisdn={msisdn}");
                     return RedirectToAction("Login", "Account", new { area = "" });
                 }
 
                 HomeIndex_ViewModel model = new HomeIndex_ViewModel();
                 model.termType = termType ?? Constants.Millions_CODE;
+                log.Info($"[Millions.BuyTicket] Check active term - gameId={model.termType}, msisdn={msisdn}");
 
                 Profile profile = HttpContext.Session.GetComplexData<Profile>("profile");
                 UserStatus userStatus = HttpContext.Session.GetComplexData<UserStatus>("userStatus");
@@ -1104,6 +1176,7 @@ namespace LotteryWebApp.Areas.Millions.Controllers
                 };
 
                 ResultOfTermResponse result = api.GetResultOfTermApi(configuration, request);
+                log.Info($"[Millions.BuyTicket] Active term response - gameId={model.termType}, code={result?.responseCode}, msg={result?.responseMessage}, termCount={result?.listTerm?.Count ?? 0}");
                 if (result.responseCode == Code.SUCCESS && result.listTerm != null && result.listTerm.Count > 0)
                 {
                     model.listTerm = result.listTerm;
@@ -1115,6 +1188,7 @@ namespace LotteryWebApp.Areas.Millions.Controllers
 
                 if (model.termType == Constants.PIC10_BIGSMALL_CODE || model.termType == Constants.PIC10_ODDEVEN_CODE)
                 {
+                    log.Info($"[Millions.BuyTicket] Check past terms - gameId={model.termType}, msisdn={msisdn}");
                     ResultOfTermRequest pastRequest = new ResultOfTermRequest
                     {
                         gameId = model.termType,
@@ -1129,6 +1203,7 @@ namespace LotteryWebApp.Areas.Millions.Controllers
                     };
 
                     ResultOfTermResponse pastResult = api.GetResultOfTermApi(configuration, pastRequest);
+                    log.Info($"[Millions.BuyTicket] Past term response - gameId={model.termType}, code={pastResult?.responseCode}, msg={pastResult?.responseMessage}, termCount={pastResult?.listTerm?.Count ?? 0}");
                     if (pastResult.responseCode == Code.SUCCESS && pastResult.listTerm != null)
                     {
                         var pastTerms = pastResult.listTerm.Take(5).ToList();
@@ -1141,11 +1216,12 @@ namespace LotteryWebApp.Areas.Millions.Controllers
                     }
                 }
 
+                log.Info($"[Millions.BuyTicket] END - gameId={model.termType}, activeTermCount={model.listTerm?.Count ?? 0}, msisdn={msisdn}");
                 return View(model);
             }
             catch (Exception ex)
             {
-                log.Error(ex);
+                log.Error($"[Millions.BuyTicket] EXCEPTION - termType={termType}", ex);
                 return RedirectToAction("GameHome", new { termType = termType });
             }
         }

+ 48 - 4
website/Areas/Millions/Views/Home/GameHome.cshtml

@@ -195,6 +195,25 @@
     @{
         ViewData["ActiveTab"] = "Home";
     }
+
+    <div id="notificationModal" class="fixed inset-0 z-[100] flex items-center justify-center hidden px-6 font-bricolage" style="background: linear-gradient(135deg, rgba(26, 26, 46, 0.9) 0%, rgba(22, 33, 62, 0.9) 100%);">
+        <div class="w-full max-w-[343px] min-h-[480px] bg-white rounded-[24px] overflow-hidden flex flex-col items-center p-8 animate__animated animate__zoomIn animate__faster shadow-2xl">
+            <div class="w-full flex justify-center mb-8 mt-6">
+                <img src="/Millions/img/modal/fail_icon.png" class="w-[180px] h-auto object-contain" alt="Notification icon" />
+            </div>
+
+            <div class="px-2 text-center mb-10 flex-1 flex items-center justify-center">
+                <p id="notificationMessage" class="text-black font-[800] text-[22px] leading-snug"></p>
+            </div>
+
+            <div class="w-full">
+                <button onclick="closeNotificationModal()" class="w-full bg-[#0062FF] text-white font-[800] text-[20px] py-[12px] rounded-[14px] shadow-lg active:scale-95 transition-all">
+                    @Lang.try_again
+                </button>
+            </div>
+        </div>
+    </div>
+
     @await Html.PartialAsync("~/Areas/Millions/Views/Shared/_BottomNavbar.cshtml")
 
 </div>
@@ -202,8 +221,29 @@
 @section Scripts {
     <script src="/Millions/js/all.min.js"></script>
     <script>
+        var systemUpgrading = false;
         let isCheckingTerm = false;
 
+        function showNotification(message, code) {
+            $("#notificationMessage").text(message);
+            const $btn = $("#notificationModal button");
+            if (code === "-2" || (message && message.includes("System is upgrading"))) {
+                systemUpgrading = true;
+                $btn.text("@Lang.login");
+            } else {
+                systemUpgrading = false;
+                $btn.text("@Lang.try_again");
+            }
+            $("#notificationModal").removeClass("hidden").addClass("flex");
+        }
+
+        function closeNotificationModal() {
+            $("#notificationModal").addClass("hidden").removeClass("flex");
+            if (systemUpgrading) {
+                window.location.href = subDomain + "/Account/Login";
+            }
+        }
+
         function checkTerm(termType) {
             if (isCheckingTerm) return;
             isCheckingTerm = true;
@@ -224,22 +264,26 @@
                             if (status === "0" || status === "1") {
                                 location.href = subDomain + '/Millions/Home/BuyTicket?termType=' + termType;
                                 return;
+                            } else if (status === "2") {
+                                showNotification("@Lang.millions_game_locked");
+                            } else if (status === "3") {
+                                showNotification("@Lang.millions_updating_results");
                             } else {
-                                alert("@Lang.millions_game_locked");
+                                showNotification("@Lang.millions_game_locked");
                             }
                         } else {
-                            alert("@Lang.no_results_found");
+                            showNotification("@Lang.no_results_found");
                         }
                     } else if (res.responseCode === "401") {
                         window.location.href = subDomain + "/Account/Login";
                         return;
                     } else {
-                        alert(res.responseMessage || "@Lang.error_happened");
+                        showNotification(res.responseMessage || "@Lang.error_happened", res.responseCode);
                     }
                     isCheckingTerm = false;
                 },
                 error: function (err) {
-                    alert("@Lang.error_happened");
+                    showNotification("@Lang.error_happened");
                     isCheckingTerm = false;
                 }
             });

+ 12 - 3
website/Areas/Millions/Views/Home/History.cshtml

@@ -205,7 +205,7 @@
             container.innerHTML = '';
             if (ticketCode) {
                 const balls = ticketCode.split(/[;,]/).filter(x => x.trim() !== '');
-                balls.forEach(val => {
+                balls.forEach((val, index) => {
                     let ballVal = val.trim();
                     let displayVal = ballVal;
                     let isLabel = false;
@@ -219,12 +219,21 @@
                     const div = document.createElement('div');
                     if (isLabel) {
                         div.className = "bg-[#0062FF] text-white px-4 py-1 rounded-full font-black text-[12px] shadow-sm uppercase tracking-wider";
+                        div.innerText = displayVal;
                     } else {
-                        div.className = "w-8 h-8 rounded-full flex items-center justify-center text-white text-[12px] font-black shadow-md";
+                        const isMbBall = index === balls.length - 1;
+                        div.className = "w-8 h-8 rounded-full flex items-center justify-center text-white text-[12px] font-black shadow-md relative" + (isMbBall ? " mr-1.5" : "");
                         div.style.background = "linear-gradient(135deg, #FF3D63 0%, #E3132D 60%, #BA0F21 100%)";
                         div.style.border = "1px solid rgba(255, 255, 255, 0.3)";
+                        div.innerText = displayVal;
+
+                        if (isMbBall) {
+                            const mbLabel = document.createElement('span');
+                            mbLabel.className = 'ticket-mb-label';
+                            mbLabel.innerText = 'MB';
+                            div.appendChild(mbLabel);
+                        }
                     }
-                    div.innerText = displayVal;
                     container.appendChild(div);
                 });
             }

+ 9 - 0
website/Areas/Millions/Views/Home/More.cshtml

@@ -63,6 +63,15 @@
             <i class="fa-solid fa-chevron-right text-gray-400 text-sm opacity-80"></i>
         </div>
 
+        <!-- Item: Reward History -->
+        <div class="flex items-center justify-between py-[18px] border-b-2 border-dashed border-gray-100 cursor-pointer transition-colors active:bg-gray-50" onclick="window.location.href='/Millions/Home/RewardHistory'">
+            <div class="flex items-center gap-4">
+                <i class="fa-solid fa-gift text-[#4A4A4A] text-[20px] w-6 text-center"></i>
+                <span class="text-[#212121] font-bold text-[13px]">Reward History</span>
+            </div>
+            <i class="fa-solid fa-chevron-right text-gray-400 text-sm opacity-80"></i>
+        </div>
+
         <!-- Item: How to play Pick 10 
         <div class="flex items-center justify-between py-[18px] border-b-2 border-dashed border-gray-100 cursor-pointer transition-colors active:bg-gray-50" onclick="window.location.href='/Millions/Home/HowToPlay'">
             <div class="flex items-center gap-4">

+ 131 - 0
website/Areas/Millions/Views/Home/RewardHistory.cshtml

@@ -0,0 +1,131 @@
+@model LotteryWebApp.Models.RewardHistoryModel
+@using LotteryWebApp.Languages
+@{
+    ViewData["Title"] = "Millions - Reward History";
+    Layout = "~/Areas/Millions/Views/Shared/_Layout.cshtml";
+    ViewData["ActiveTab"] = "More";
+}
+
+@section Styles {
+    <script src="https://cdn.tailwindcss.com"></script>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
+    <link rel="stylesheet" href="/Millions/css/site.css" />
+    <link rel="stylesheet" href="/Millions/css/results.css" />
+    <link rel="stylesheet" href="/Millions/css/reward-history.css" />
+}
+
+<div class="main-container reward-history-container animate__animated animate__fadeIn pb-40 relative">
+    <!-- Fixed Header -->
+    <div class="fixed top-0 left-0 md:left-1/2 md:-translate-x-1/2 w-full md:max-w-[430px] z-[100] bg-[#0062FF]">
+        <div class="results-top-header">
+            <a href="/Millions/Home/More" class="back-btn">
+                <i class="fa-solid fa-arrow-left"></i>
+            </a>
+            <h1 class="font-bricolage">Reward History</h1>
+        </div>
+        <!-- Spacer with rounded white top (matches History layout) -->
+        <div class="bg-white rounded-t-[30px] h-[16px]"></div>
+    </div>
+
+    <!-- List container (spaced below fixed header) -->
+    <div id="reward-list-container"
+         class="reward-list-container bg-white rounded-t-[30px] px-4 pt-4 mt-[80px] pb-[120px] min-h-[calc(100vh-80px)] shadow-sm">
+        <partial name="_RewardHistoryList" model="Model" />
+    </div>
+
+    <!-- Bottom Navbar -->
+    <partial name="_BottomNavbar" />
+
+    <!-- Notification Modal -->
+    <div id="notificationModal" class="fixed inset-0 z-[300] flex items-center justify-center hidden px-6 font-bricolage" style="background: linear-gradient(135deg, rgba(26, 26, 46, 0.9) 0%, rgba(22, 33, 62, 0.9) 100%);">
+        <div class="w-full max-w-[343px] min-h-[420px] bg-white rounded-[24px] overflow-hidden flex flex-col items-center p-8 animate__animated animate__zoomIn animate__faster shadow-2xl border border-white/50">
+            <div class="w-full flex justify-center mb-8 mt-4">
+                <img src="/Millions/img/modal/fail_icon.png" class="w-[160px] h-auto object-contain" alt="Notification icon" />
+            </div>
+            <div class="px-2 text-center mb-10 flex-1 flex items-center justify-center">
+                <p id="notificationMessage" class="text-black font-[800] text-[20px] leading-snug"></p>
+            </div>
+            <div class="w-full">
+                <button onclick="closeNotificationModal()" class="w-full bg-[#0062FF] text-white font-[800] text-[18px] py-[12px] rounded-[16px] shadow-lg active:scale-95 transition-all">
+                    @Lang.login
+                </button>
+            </div>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    <script>
+        let currentSeqPage = parseInt('@Model.seqPage' || '1');
+        let totalPages = parseInt('@Model.totalPage' || '1');
+        let systemUpgrading = false;
+
+        function changeRewardPage(page) {
+            if (page < 1 || page > totalPages) return;
+            currentSeqPage = page;
+            loadRewardHistory();
+        }
+
+        function loadRewardHistory() {
+            const container = document.getElementById("reward-list-container");
+            container.style.opacity = "0.5";
+
+            fetch(subDomain + `/Millions/Home/RewardHistoryPartial?seqPage=${currentSeqPage}`)
+                .then(response => {
+                    if (!response.ok) throw new Error('Network response was not ok');
+                    return response.text();
+                })
+                .then(html => {
+                    try {
+                        const json = JSON.parse(html);
+                        if (json.responseCode === "-2" || (json.responseMessage && json.responseMessage.includes("System is upgrading"))) {
+                            showNotification(json.responseMessage || "System is upgrading", "-2");
+                            return;
+                        }
+                    } catch (e) {
+                        // Not JSON, HTML partial
+                    }
+                    container.innerHTML = html;
+                    container.style.opacity = "1";
+                    updatePaginationUI();
+                })
+                .catch(error => {
+                    console.error("Error loading reward history:", error);
+                    container.style.opacity = "1";
+                    if (error.message && (error.message.includes("System is upgrading") || error.message.includes("-2"))) {
+                        showNotification(error.message, "-2");
+                    }
+                });
+        }
+
+        function updatePaginationUI() {
+            const pageDisplay = document.getElementById("pageDisplay");
+            const prevBtn = document.getElementById("prevPage");
+            const nextBtn = document.getElementById("nextPage");
+
+            if (pageDisplay) pageDisplay.innerText = `${currentSeqPage} / ${totalPages}`;
+            if (prevBtn) prevBtn.disabled = currentSeqPage <= 1;
+            if (nextBtn) nextBtn.disabled = currentSeqPage >= totalPages;
+        }
+
+        function showNotification(message, code) {
+            $("#notificationMessage").text(message);
+            const $btn = $("#notificationModal button");
+            if (code === "-2" || (message && message.includes("System is upgrading"))) {
+                systemUpgrading = true;
+                $btn.text("@Lang.login");
+            } else {
+                systemUpgrading = false;
+                $btn.text("OK");
+            }
+            $("#notificationModal").removeClass("hidden").addClass("flex");
+        }
+
+        function closeNotificationModal() {
+            $("#notificationModal").addClass("hidden").removeClass("flex");
+            if (systemUpgrading) {
+                window.location.href = subDomain + "/Account/Login";
+            }
+        }
+    </script>
+}

+ 2 - 22
website/Areas/Millions/Views/Home/Rule.cshtml

@@ -115,17 +115,12 @@
                     }
                     .millions-rule-page .match-header,
                     .millions-rule-page .match-col {
-                        width: 50%;
+                        width: 64%;
                         text-align: left;
                     }
                     .millions-rule-page .prize-header,
                     .millions-rule-page .prize-col {
-                        width: 32%;
-                        text-align: right;
-                    }
-                    .millions-rule-page .rate-header,
-                    .millions-rule-page .rate-col {
-                        width: 18%;
+                        width: 36%;
                         text-align: right;
                     }
                     .millions-rule-page .match-col {
@@ -144,11 +139,6 @@
                         text-transform: uppercase;
                         font-weight: 900;
                     }
-                    .millions-rule-page .rate-col {
-                        font-size: 13.5px;
-                        color: #666;
-                        font-weight: 700;
-                    }
                 </style>
                 <div class="section-header">
                     <span class="section-emoji">🔔</span>
@@ -159,54 +149,44 @@
                     <div class="prize-header-labels">
                         <div class="prize-label match-header">@Lang.millions_match_result_header</div>
                         <div class="prize-label prize-header">@Lang.millions_prize_htg_header</div>
-                        <div class="prize-label rate-header">@Lang.millions_rate_header</div>
                     </div>
 
                     <div class="prize-rows">
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_5_plus_mega</div>
                             <div class="prize-col jackpot">@Lang.millions_jackpot_label</div>
-                            <div class="rate-col">–</div>
                         </div>
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_5_numbers</div>
                             <div class="prize-col">400,000</div>
-                            <div class="rate-col">x20.000</div>
                         </div>
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_4_plus_mega</div>
                             <div class="prize-col">40,000</div>
-                            <div class="rate-col">x2.000</div>
                         </div>
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_4_numbers</div>
                             <div class="prize-col">2,000</div>
-                            <div class="rate-col">x100</div>
                         </div>
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_3_plus_mega</div>
                             <div class="prize-col">800</div>
-                            <div class="rate-col">x40</div>
                         </div>
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_3_numbers</div>
                             <div class="prize-col">40</div>
-                            <div class="rate-col">x2</div>
                         </div>
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_2_plus_mega</div>
                             <div class="prize-col">40</div>
-                            <div class="rate-col">x2</div>
                         </div>
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_1_plus_mega</div>
                             <div class="prize-col">30</div>
-                            <div class="rate-col">–</div>
                         </div>
                         <div class="prize-row">
                             <div class="match-col">@Lang.millions_match_mega_only</div>
                             <div class="prize-col">10</div>
-                            <div class="rate-col">–</div>
                         </div>
                     </div>
                 </div>

+ 116 - 0
website/Areas/Millions/Views/Home/_RewardHistoryList.cshtml

@@ -0,0 +1,116 @@
+@model LotteryWebApp.Models.RewardHistoryModel
+@using LotteryWebApp.Languages
+@using LotteryWebApp.Common
+
+@functions {
+    public string FormatMoney(string amount) {
+        if (string.IsNullOrEmpty(amount)) return "0";
+        var clean = new string(amount.Where(c => char.IsDigit(c)).ToArray());
+        if (long.TryParse(clean, out long val)) {
+            return val.ToString("#,##0", new System.Globalization.CultureInfo("vi-VN")).Replace(",", ".");
+        }
+        return amount;
+    }
+
+    public string FormatTime(string raw) {
+        if (string.IsNullOrEmpty(raw)) return "-";
+        string[] formats = { "M/d/yyyy h:mm:ss tt", "MM/dd/yyyy h:mm:ss tt", "dd/MM/yyyy HH:mm:ss", "yyyy-MM-dd HH:mm:ss" };
+        if (System.DateTime.TryParseExact(raw, formats, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var dt)
+            || System.DateTime.TryParse(raw, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out dt)) {
+            return dt.ToString("dd/MM/yyyy HH:mm", new System.Globalization.CultureInfo("en-US"));
+        }
+        return raw;
+    }
+
+    public (string cls, string label) MapStatus(string status) {
+        switch (status) {
+            case "1": return ("reward-status-success", "Success");
+            case "0": return ("reward-status-processing", "Processing");
+            case "2": return ("reward-status-pending", "Pending");
+            case "3": return ("reward-status-reverted", "Reverted");
+            default:  return ("reward-status-unknown", string.IsNullOrEmpty(status) ? "-" : status);
+        }
+    }
+
+    public string MapPayment(string channelPayment) {
+        if (channelPayment == "1") return "Wallet";
+        if (channelPayment == "0") return "Main Account";
+        return "-";
+    }
+}
+
+@if (Model != null && Model.list != null && Model.list.Count > 0)
+{
+    foreach (var t in Model.list)
+    {
+        var (statusCls, statusLabel) = MapStatus(t.status);
+        <div class="reward-card animate__animated animate__fadeInUp">
+            <div class="reward-card-top">
+                <div class="reward-card-header">
+                    <span class="reward-id">#@(t.seq ?? t.id ?? "-")</span>
+                    <span class="reward-status @statusCls">@statusLabel</span>
+                </div>
+                <div class="reward-amount-wrap">
+                    <span class="reward-amount">@FormatMoney(t.money)</span>
+                    <span class="reward-currency">HTG</span>
+                </div>
+            </div>
+
+            <div class="reward-card-perforation"></div>
+
+            <div class="reward-card-bottom">
+                <div class="reward-info-grid">
+                    <div class="reward-info-row">
+                        <span class="reward-info-label">From</span>
+                        <span class="reward-info-value">@(string.IsNullOrEmpty(t.acountSend) ? "-" : t.acountSend)</span>
+                    </div>
+                    <div class="reward-info-row">
+                        <span class="reward-info-label">To</span>
+                        <span class="reward-info-value">@(string.IsNullOrEmpty(t.acountRecieve) ? "-" : t.acountRecieve)</span>
+                    </div>
+                    <div class="reward-info-row">
+                        <span class="reward-info-label">Time</span>
+                        <span class="reward-info-value">@FormatTime(t.timeRequest)</span>
+                    </div>
+                    <div class="reward-info-row">
+                        <span class="reward-info-label">Method</span>
+                        <span class="reward-info-value">@MapPayment(t.channelPayment)</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    }
+
+    @if (!string.IsNullOrEmpty(Model.totalPage) && int.TryParse(Model.totalPage, out var total) && total > 1)
+    {
+        var seq = int.TryParse(Model.seqPage, out var s) ? s : 1;
+        <div class="reward-pagination">
+            <button onclick="changeRewardPage(currentSeqPage - 1)" class="reward-page-btn" id="prevPage" @(seq <= 1 ? "disabled" : "")>
+                <i class="fas fa-chevron-left mr-1"></i> Prev
+            </button>
+
+            <div class="reward-page-indicator">
+                <span class="reward-page-label">Page</span>
+                <span class="reward-page-value" id="pageDisplay">@Model.seqPage / @Model.totalPage</span>
+            </div>
+
+            <button onclick="changeRewardPage(currentSeqPage + 1)" class="reward-page-btn" id="nextPage" @(seq >= total ? "disabled" : "")>
+                Next <i class="fas fa-chevron-right ml-1"></i>
+            </button>
+        </div>
+        <script>
+            totalPages = parseInt('@Model.totalPage');
+        </script>
+    }
+    else
+    {
+        <div class="w-full pb-32"></div>
+    }
+}
+else
+{
+    <div class="reward-empty">
+        <i class="fa-solid fa-gift"></i>
+        <p>@Lang.no_results_found</p>
+    </div>
+}

+ 11 - 6
website/Areas/Millions/Views/Home/_TermResultHistoryGrouped.cshtml

@@ -83,14 +83,19 @@
                                 @if (!string.IsNullOrEmpty(item.result))
                                 {
                                     var balls = item.result.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
-                                    foreach (var ball in balls)
+                                    var isNumberResult = isBasic || (!isBigSmall && !isOddEven);
+                                    for (int bi = 0; bi < balls.Length; bi++)
                                     {
-                                        var cleanBall = ball.Trim();
-                                        if (isBasic)
+                                        var cleanBall = balls[bi].Trim();
+                                        if (isNumberResult)
                                         {
-                                            <div class="w-8 h-8 flex items-center justify-center rounded-full text-white text-[15px] font-black shadow-md transform hover:scale-110 transition-transform" 
-                                                 style="background: linear-gradient(135deg, #FF3D63 0%, #E3132D 60%, #BA0F21 100%);">
-                                                @cleanBall
+                                            var isMegaBall = bi == balls.Length - 1;
+                                            <div class="millions-result-ball @(isMegaBall ? "millions-result-ball-mb" : "")">
+                                                <span>@cleanBall</span>
+                                                @if (isMegaBall)
+                                                {
+                                                    <span class="millions-result-mb-label">MB</span>
+                                                }
                                             </div>
                                         }
                                         else

+ 8 - 1
website/Areas/Millions/Views/Home/_TermUserTicketHistory.cshtml

@@ -61,7 +61,14 @@
                                     ballClass = "ball-lose";
                                 }
 
-                                <div class="ticket-ball @ballClass">@ballValue</div>
+                                var isMbBall = bi == mbIndex;
+                                <div class="ticket-ball @ballClass @(isMbBall ? "ticket-ball-mb" : "")">
+                                    <span class="ticket-ball-value">@ballValue</span>
+                                    @if (isMbBall)
+                                    {
+                                        <span class="ticket-mb-label">MB</span>
+                                    }
+                                </div>
                             }
                         }
                     }

+ 81 - 0
website/Common/DailyRollingFileAppender.cs

@@ -0,0 +1,81 @@
+using System;
+using System.IO;
+using System.Linq;
+using log4net.Appender;
+using log4net.Util;
+
+namespace LotteryWebApp.Common
+{
+    /// <summary>
+    /// RollingFileAppender ghi log mỗi ngày 1 file (rolling theo ngày) và chỉ giữ lại
+    /// N file log gần nhất — các file cũ hơn sẽ tự động bị xóa để tránh đầy ổ cứng.
+    /// Toàn bộ cấu hình nằm trong log4net.config, không cần task bên ngoài.
+    /// </summary>
+    public class DailyRollingFileAppender : RollingFileAppender
+    {
+        /// <summary>
+        /// Số file log tối đa được giữ lại. Cấu hình qua &lt;maxNumberOfFiles value="30" /&gt;
+        /// trong log4net.config. Mặc định 30. Đặt &lt;= 0 để tắt việc tự xóa.
+        /// </summary>
+        public int MaxNumberOfFiles { get; set; } = 30;
+
+        // Chỉ dọn log 1 lần mỗi ngày để không ảnh hưởng hiệu năng ghi log.
+        private DateTime _lastCleanupDate = DateTime.MinValue;
+        private readonly object _cleanupLock = new object();
+
+        protected override void AdjustFileBeforeAppend()
+        {
+            // Để log4net xử lý việc cuộn file theo ngày trước.
+            base.AdjustFileBeforeAppend();
+
+            if (_lastCleanupDate == DateTime.Today)
+                return;
+
+            lock (_cleanupLock)
+            {
+                if (_lastCleanupDate == DateTime.Today)
+                    return;
+                _lastCleanupDate = DateTime.Today;
+            }
+
+            CleanupOldFiles();
+        }
+
+        /// <summary>Giữ lại MaxNumberOfFiles file .log mới nhất, xóa phần còn lại.</summary>
+        private void CleanupOldFiles()
+        {
+            try
+            {
+                if (MaxNumberOfFiles <= 0)
+                    return;
+
+                string dir = Path.GetDirectoryName(File);
+                if (string.IsNullOrEmpty(dir) || !Directory.Exists(dir))
+                    return;
+
+                var oldFiles = new DirectoryInfo(dir)
+                    .GetFiles("*.log")
+                    .OrderByDescending(f => f.LastWriteTimeUtc)
+                    .Skip(MaxNumberOfFiles)
+                    .ToList();
+
+                foreach (FileInfo file in oldFiles)
+                {
+                    try
+                    {
+                        file.Delete();
+                        LogLog.Debug(GetType(), "Da xoa file log cu: " + file.Name);
+                    }
+                    catch (Exception ex)
+                    {
+                        LogLog.Warn(GetType(), "Khong xoa duoc file log: " + file.FullName, ex);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                LogLog.Error(GetType(), "Loi khi don dep file log cu", ex);
+            }
+        }
+    }
+}

+ 41 - 41
website/Languages/Lang.Designer.cs

@@ -1513,7 +1513,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Millions.
+        ///   Looks up a localized string similar to Milyone.
         /// </summary>
         public static string millions {
             get {
@@ -1522,7 +1522,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Kont Loto.
+        ///   Looks up a localized string similar to Kont Bee Loto.
         /// </summary>
         public static string millions_account_name {
             get {
@@ -1594,7 +1594,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Retour à la liste des résultats.
+        ///   Looks up a localized string similar to Tounen nan rezilta.
         /// </summary>
         public static string millions_back_to_results_list {
             get {
@@ -1603,7 +1603,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Be the next millions.
+        ///   Looks up a localized string similar to Pouw pwochen milyone a .
         /// </summary>
         public static string millions_be_the_next_millionaire {
             get {
@@ -1612,7 +1612,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Gwo  PITI .
+        ///   Looks up a localized string similar to Gwo  Piti.
         /// </summary>
         public static string millions_big_small {
             get {
@@ -1621,7 +1621,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Bill Code.
+        ///   Looks up a localized string similar to Kod bil.
         /// </summary>
         public static string millions_bill_code {
             get {
@@ -1630,7 +1630,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Achte pick 10.
+        ///   Looks up a localized string similar to Achte Basic pick 10.
         /// </summary>
         public static string millions_buy_classic_pick_10 {
             get {
@@ -1657,7 +1657,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Chanel achte.
+        ///   Looks up a localized string similar to Chanel.
         /// </summary>
         public static string millions_channel {
             get {
@@ -1792,7 +1792,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Draw frequency: 2 times per week (Tuesday &amp; Friday).
+        ///   Looks up a localized string similar to Frekans tiraj: 2fwa pa semen ( Madi ak vandredi ).
         /// </summary>
         public static string millions_draw_frequency {
             get {
@@ -1801,7 +1801,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Informations sur le tirage.
+        ///   Looks up a localized string similar to Enfomasyon tiraj.
         /// </summary>
         public static string millions_draw_information {
             get {
@@ -1819,7 +1819,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Draw Schedule.
+        ///   Looks up a localized string similar to Ore tiraj .
         /// </summary>
         public static string millions_draw_schedule {
             get {
@@ -1828,7 +1828,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Draw time: 11:00 PM (Eastern Time - ET).
+        ///   Looks up a localized string similar to Le tiraj: 11: 00 pM (Eastern Time-ET).
         /// </summary>
         public static string millions_draw_time {
             get {
@@ -1918,7 +1918,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Game rules.
+        ///   Looks up a localized string similar to Regleman jwet la.
         /// </summary>
         public static string millions_game_rules_title {
             get {
@@ -1927,7 +1927,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Game Type.
+        ///   Looks up a localized string similar to Tip jwet.
         /// </summary>
         public static string millions_game_type {
             get {
@@ -1936,7 +1936,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to How to play.
+        ///   Looks up a localized string similar to Kijan pouw jwe .
         /// </summary>
         public static string millions_how_to_play {
             get {
@@ -1954,7 +1954,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Balans sa pa sifi..
+        ///   Looks up a localized string similar to Balans sa pa sifizan..
         /// </summary>
         public static string millions_insufficient_balance {
             get {
@@ -1972,7 +1972,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Tikè ki achte apre 10:30 yo ap antre nan pwochen tiraj la. Montan final jackpot la pral konfime apre anons rezilta 11:00 lan..
+        ///   Looks up a localized string similar to Ou achte tike a apre 10:30 lap konte pou pwochen tiraj la. Montan jakpot final la ap konfime apre 11:00 rezilta a ap anonse..
         /// </summary>
         public static string millions_jackpot_disclaimer {
             get {
@@ -1990,7 +1990,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Prix Jackpot.
+        ///   Looks up a localized string similar to Pri Jakpot la.
         /// </summary>
         public static string millions_jackpot_prize {
             get {
@@ -1999,7 +1999,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Win Up To.
+        ///   Looks up a localized string similar to Genyen jiska.
         /// </summary>
         public static string millions_jackpot_prize_to {
             get {
@@ -2008,7 +2008,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Résultats Jackpot.
+        ///   Looks up a localized string similar to Rezilta Jakpot.
         /// </summary>
         public static string millions_jackpot_results_title {
             get {
@@ -2017,7 +2017,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Y ap bay Mega Jackpot la pou kliyan ki jwenn tout 6 boul yo..
+        ///   Looks up a localized string similar to Mega jakpot la se kliyan ki jwenn 6 boul ki match yo kap resevwa li.
         /// </summary>
         public static string millions_jackpot_rule1_desc1 {
             get {
@@ -2026,7 +2026,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Kantite lajan pri a ap peye soti nan Kès Mega Jackpot la..
+        ///   Looks up a localized string similar to Prim nan se Mega jakpot kap bay li.
         /// </summary>
         public static string millions_jackpot_rule1_desc2 {
             get {
@@ -2035,7 +2035,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to 1. Pri Mega Jackpot.
+        ///   Looks up a localized string similar to 1.Pri Mega jakpot la.
         /// </summary>
         public static string millions_jackpot_rule1_title {
             get {
@@ -2044,7 +2044,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Si gen de oswa plis tikè genyen ki jwenn tout 6 boul yo, Kès Jackpot la ap pataje egalego ant tout tikè genyen yo..
+        ///   Looks up a localized string similar to Si gen 2 oswa plis tike ki genyen 6 boul ki match, Jakpot la ap separe pou chak jwenm menm kantite.
         /// </summary>
         public static string millions_jackpot_rule2_desc1 {
             get {
@@ -2053,7 +2053,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Yon kliyan ki gen plizyè tikè genyen ka resevwa plizyè pati nan pri a..
+        ///   Looks up a localized string similar to Yon kliyan ki gen plizye tike ka resevwa plizye prim.
         /// </summary>
         public static string millions_jackpot_rule2_desc2 {
             get {
@@ -2062,7 +2062,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to 2. Règleman pou pataje Jackpot.
+        ///   Looks up a localized string similar to 2. Politik pou jakpot la pataje.
         /// </summary>
         public static string millions_jackpot_rule2_title {
             get {
@@ -2071,7 +2071,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Kès Mega Jackpot la kòmanse nan 2,000,000 HTG..
+        ///   Looks up a localized string similar to Mega jakpot la komanse nan 2,000,000 HTG.
         /// </summary>
         public static string millions_jackpot_rule3_desc1 {
             get {
@@ -2080,7 +2080,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Valè maksimòm Jackpot la se 30,000,000 HTG..
+        ///   Looks up a localized string similar to Pigwo montan an se 30,000,000HTG.
         /// </summary>
         public static string millions_jackpot_rule3_desc2 {
             get {
@@ -2089,7 +2089,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to 3. Valè Jackpot.
+        ///   Looks up a localized string similar to 3. Jakpot.
         /// </summary>
         public static string millions_jackpot_rule3_title {
             get {
@@ -2098,7 +2098,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Yon fwa yon moun genyen Mega Jackpot la, Kès Jackpot la ap rekòmanse otomatikman nan 2,000,000 HTG..
+        ///   Looks up a localized string similar to Depi gen yon moun ki genyen Mega jakpot la, jakpot la ap tou reyinisyalize otomatikman a 2,000,000 HTG.
         /// </summary>
         public static string millions_jackpot_rule4_desc1 {
             get {
@@ -2107,7 +2107,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Sik akimilasyon Jackpot la ap rekòmanse otomatikman..
+        ///   Looks up a localized string similar to Jakpot la ap rekomanse otomatikman.
         /// </summary>
         public static string millions_jackpot_rule4_desc2 {
             get {
@@ -2116,7 +2116,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to 4. Règleman pou Rekòmanse Jackpot.
+        ///   Looks up a localized string similar to 4.Politik pou reyinisyalize jakpot la.
         /// </summary>
         public static string millions_jackpot_rule4_title {
             get {
@@ -2125,7 +2125,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Pri Mega Jackpot yo ka peye atravè:.
+        ///   Looks up a localized string similar to Mega Jakpot la ka peye apati:.
         /// </summary>
         public static string millions_jackpot_rule5_desc1 {
             get {
@@ -2134,7 +2134,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Metòd peman an ap depann de valè Jackpot la ak règleman operasyonèl yo nan moman peman an..
+        ///   Looks up a localized string similar to Metod pewol pou jakpot la depann de montan an epi politik operasyonel pou peryod la.
         /// </summary>
         public static string millions_jackpot_rule5_desc2 {
             get {
@@ -2143,7 +2143,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Kont Prensipal.
+        ///   Looks up a localized string similar to Kont prensipal.
         /// </summary>
         public static string millions_jackpot_rule5_item1 {
             get {
@@ -2152,7 +2152,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Kont Natcash.
+        ///   Looks up a localized string similar to Kont Natcom.
         /// </summary>
         public static string millions_jackpot_rule5_item2 {
             get {
@@ -2161,7 +2161,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Peman an kach (si sa aplikab).
+        ///   Looks up a localized string similar to Pewol Kach .
         /// </summary>
         public static string millions_jackpot_rule5_item3 {
             get {
@@ -2170,7 +2170,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to 5. Metòd Peman Pri.
+        ///   Looks up a localized string similar to 5. Metod pou peye pri.
         /// </summary>
         public static string millions_jackpot_rule5_title {
             get {
@@ -2179,7 +2179,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Natcom gen dwa pou verifye tout tikè genyen yo anvan li peye lajan Jackpot la..
+        ///   Looks up a localized string similar to Natcom gen dwa pou verifye tout tike ki genyen yo avan pewol jakpot la fet.
         /// </summary>
         public static string millions_jackpot_rule6_desc1 {
             get {
@@ -2602,7 +2602,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Lajan 💸 an transfere ak sikse!.
+        ///   Looks up a localized string similar to Lajan ’¸ an transfere ak sikse!.
         /// </summary>
         public static string millions_payment_successfully {
             get {
@@ -3034,7 +3034,7 @@ namespace LotteryWebApp.Languages {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Sa ou chwazi.
+        ///   Looks up a localized string similar to Chwazi.
         /// </summary>
         public static string millions_you_choose {
             get {

+ 2 - 2
website/Languages/Lang.fr.resx

@@ -1287,7 +1287,7 @@ We’ll help you create an account in a few easy steps.</value>
     <value>Confirm</value>
   </data>
   <data name="v2_payment_successfully" xml:space="preserve">
-    <value>Money 💸 has been transferred successfully!</value>
+    <value>The money has been successfully transferred!</value>
   </data>
   <data name="v2_your_choice" xml:space="preserve">
     <value>Your choice</value>
@@ -1781,7 +1781,7 @@ We’ll help you create an account in a few easy steps.</value>
     <value>Number Matched</value>
   </data>
   <data name="millions_prize_htg_header" xml:space="preserve">
-    <value>Prizes (win coin)</value>
+    <value>Prizes (HTG)</value>
   </data>
   <data name="millions_rate_header" xml:space="preserve">
     <value>Rate</value>

+ 41 - 41
website/Languages/Lang.resx

@@ -1491,7 +1491,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Prim ou ka genyen</value>
   </data>
   <data name="millions_you_choose" xml:space="preserve">
-    <value>Sa ou chwazi</value>
+    <value>Chwazi</value>
   </data>
   <data name="millions_win" xml:space="preserve">
     <value>Genyen</value>
@@ -1509,7 +1509,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Pack 10</value>
   </data>
   <data name="millions_big_small" xml:space="preserve">
-    <value>Gwo  PITI </value>
+    <value>Gwo  Piti</value>
   </data>
   <data name="millions_odd_even" xml:space="preserve">
     <value>Enpe Pe</value>
@@ -1530,7 +1530,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Rezilta yo disponib</value>
   </data>
   <data name="millions_buy_classic_pick_10" xml:space="preserve">
-    <value>Achte pick 10</value>
+    <value>Achte Basic pick 10</value>
   </data>
   <data name="millions_select_10_lucky_numbers" xml:space="preserve">
     <value>Chwazi nimewo chans</value>
@@ -1587,7 +1587,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Konfime</value>
   </data>
   <data name="millions_payment_successfully" xml:space="preserve">
-    <value>Lajan 💸 an transfere ak sikse!</value>
+    <value>Lajan ’¸ an transfere ak sikse!</value>
   </data>
   <data name="millions_your_choice" xml:space="preserve">
     <value>Chwazi</value>
@@ -1617,7 +1617,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Kontinye</value>
   </data>
   <data name="millions_account_name" xml:space="preserve">
-    <value>Kont Loto</value>
+    <value>Kont Bee Loto</value>
   </data>
   <data name="millions_fee" xml:space="preserve">
     <value>Gratis</value>
@@ -1629,7 +1629,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Tanpri antre yon montan ki valab.</value>
   </data>
   <data name="millions_insufficient_balance" xml:space="preserve">
-    <value>Balans sa pa sifi.</value>
+    <value>Balans sa pa sifizan.</value>
   </data>
   <data name="millions_confirm_transaction" xml:space="preserve">
     <value>Konfime tranzaksyon</value>
@@ -1686,7 +1686,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>detay tike </value>
   </data>
   <data name="millions_bill_code" xml:space="preserve">
-    <value>Bill Code</value>
+    <value>Kod bil</value>
   </data>
   <data name="millions_money_ticket" xml:space="preserve">
     <value>Money Ticket</value>
@@ -1698,7 +1698,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Dat ou achte a</value>
   </data>
   <data name="millions_channel" xml:space="preserve">
-    <value>Chanel achte</value>
+    <value>Chanel</value>
   </data>
   <data name="millions_payment_method" xml:space="preserve">
     <value>Metod peman</value>
@@ -1725,7 +1725,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>now</value>
   </data>
   <data name="millions" xml:space="preserve">
-    <value>Millions</value>
+    <value>Milyone</value>
   </data>
   <data name="millions_rule_title" xml:space="preserve">
     <value>Rule</value>
@@ -1734,19 +1734,19 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Mega Millions</value>
   </data>
   <data name="millions_draw_schedule" xml:space="preserve">
-    <value>Draw Schedule</value>
+    <value>Ore tiraj </value>
   </data>
   <data name="millions_draw_frequency" xml:space="preserve">
-    <value>Draw frequency: 2 times per week (Tuesday &amp; Friday)</value>
+    <value>Frekans tiraj: 2fwa pa semen ( Madi ak vandredi )</value>
   </data>
   <data name="millions_draw_time" xml:space="preserve">
-    <value>Draw time: 11:00 PM (Eastern Time - ET)</value>
+    <value>Le tiraj: 11: 00 pM (Eastern Time-ET)</value>
   </data>
   <data name="millions_ticket_closing_time" xml:space="preserve">
     <value>Ticket closing time: 10:30 PM (ET)</value>
   </data>
   <data name="millions_how_to_play" xml:space="preserve">
-    <value>How to play</value>
+    <value>Kijan pouw jwe </value>
   </data>
   <data name="millions_ticket_includes_6" xml:space="preserve">
     <value>Each ticket includes 6 numbers:</value>
@@ -1764,7 +1764,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Payment via: Account balance</value>
   </data>
   <data name="millions_game_rules_title" xml:space="preserve">
-    <value>Game rules</value>
+    <value>Regleman jwet la</value>
   </data>
   <data name="millions_results_based_on" xml:space="preserve">
     <value>Results are based on official lottery draw.</value>
@@ -1845,10 +1845,10 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Estimate Jackpot Prize</value>
   </data>
   <data name="millions_be_the_next_millionaire" xml:space="preserve">
-    <value>Be the next millions</value>
+    <value>Pouw pwochen milyone a </value>
   </data>
   <data name="millions_jackpot_results_title" xml:space="preserve">
-    <value>Résultats Jackpot</value>
+    <value>Rezilta Jakpot</value>
   </data>
   <data name="millions_list_jackpot_results" xml:space="preserve">
     <value>Liste des résultats Jackpot</value>
@@ -1872,7 +1872,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Note: Les résultats sont mis à jour après chaque tirage.</value>
   </data>
   <data name="millions_jackpot_prize" xml:space="preserve">
-    <value>Prix Jackpot</value>
+    <value>Pri Jakpot la</value>
   </data>
   <data name="millions_winning_numbers" xml:space="preserve">
     <value>Numéros gagnants</value>
@@ -1887,10 +1887,10 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Pas encore de gagnants</value>
   </data>
   <data name="millions_draw_information" xml:space="preserve">
-    <value>Informations sur le tirage</value>
+    <value>Enfomasyon tiraj</value>
   </data>
   <data name="millions_game_type" xml:space="preserve">
-    <value>Game Type</value>
+    <value>Tip jwet</value>
   </data>
   <data name="millions_total_winners" xml:space="preserve">
     <value>Total gagnants</value>
@@ -1908,10 +1908,10 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Merci pour votre confiance et votre soutien</value>
   </data>
   <data name="millions_back_to_results_list" xml:space="preserve">
-    <value>Retour à la liste des résultats</value>
+    <value>Tounen nan rezilta</value>
   </data>
   <data name="millions_jackpot_disclaimer" xml:space="preserve">
-    <value>Tikè ki achte apre 10:30 yo ap antre nan pwochen tiraj la. Montan final jackpot la pral konfime apre anons rezilta 11:00 lan.</value>
+    <value>Ou achte tike a apre 10:30 lap konte pou pwochen tiraj la. Montan jakpot final la ap konfime apre 11:00 rezilta a ap anonse.</value>
   </data>
   <data name="millions_view" xml:space="preserve">
     <value>Gade</value>
@@ -1920,7 +1920,7 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Pwochen tou</value>
   </data>
   <data name="millions_jackpot_prize_to" xml:space="preserve">
-    <value>Win Up To</value>
+    <value>Genyen jiska</value>
   </data>
   <data name="millions_view_all_results" xml:space="preserve">
     <value>Voir tous les résultats</value>
@@ -1962,64 +1962,64 @@ Apre yo fin tire 20 nimewo, sistem nan konte konbyen nimewo ki enpe oswa pe: &lt
     <value>Règ pou Mega Jackpot</value>
   </data>
   <data name="millions_jackpot_rule1_title" xml:space="preserve">
-    <value>1. Pri Mega Jackpot</value>
+    <value>1.Pri Mega jakpot la</value>
   </data>
   <data name="millions_jackpot_rule1_desc1" xml:space="preserve">
-    <value>Y ap bay Mega Jackpot la pou kliyan ki jwenn tout 6 boul yo.</value>
+    <value>Mega jakpot la se kliyan ki jwenn 6 boul ki match yo kap resevwa li</value>
   </data>
   <data name="millions_jackpot_rule1_desc2" xml:space="preserve">
-    <value>Kantite lajan pri a ap peye soti nan Kès Mega Jackpot la.</value>
+    <value>Prim nan se Mega jakpot kap bay li</value>
   </data>
   <data name="millions_jackpot_rule2_title" xml:space="preserve">
-    <value>2. Règleman pou pataje Jackpot</value>
+    <value>2. Politik pou jakpot la pataje</value>
   </data>
   <data name="millions_jackpot_rule2_desc1" xml:space="preserve">
-    <value>Si gen de oswa plis tikè genyen ki jwenn tout 6 boul yo, Kès Jackpot la ap pataje egalego ant tout tikè genyen yo.</value>
+    <value>Si gen 2 oswa plis tike ki genyen 6 boul ki match, Jakpot la ap separe pou chak jwenm menm kantite</value>
   </data>
   <data name="millions_jackpot_rule2_desc2" xml:space="preserve">
-    <value>Yon kliyan ki gen plizyè tikè genyen ka resevwa plizyè pati nan pri a.</value>
+    <value>Yon kliyan ki gen plizye tike ka resevwa plizye prim</value>
   </data>
   <data name="millions_jackpot_rule3_title" xml:space="preserve">
-    <value>3. Valè Jackpot</value>
+    <value>3. Jakpot</value>
   </data>
   <data name="millions_jackpot_rule3_desc1" xml:space="preserve">
-    <value>Kès Mega Jackpot la kòmanse nan 2,000,000 HTG.</value>
+    <value>Mega jakpot la komanse nan 2,000,000 HTG</value>
   </data>
   <data name="millions_jackpot_rule3_desc2" xml:space="preserve">
-    <value>Valè maksimòm Jackpot la se 30,000,000 HTG.</value>
+    <value>Pigwo montan an se 30,000,000HTG</value>
   </data>
   <data name="millions_jackpot_rule4_title" xml:space="preserve">
-    <value>4. Règleman pou Rekòmanse Jackpot</value>
+    <value>4.Politik pou reyinisyalize jakpot la</value>
   </data>
   <data name="millions_jackpot_rule4_desc1" xml:space="preserve">
-    <value>Yon fwa yon moun genyen Mega Jackpot la, Kès Jackpot la ap rekòmanse otomatikman nan 2,000,000 HTG.</value>
+    <value>Depi gen yon moun ki genyen Mega jakpot la, jakpot la ap tou reyinisyalize otomatikman a 2,000,000 HTG</value>
   </data>
   <data name="millions_jackpot_rule4_desc2" xml:space="preserve">
-    <value>Sik akimilasyon Jackpot la ap rekòmanse otomatikman.</value>
+    <value>Jakpot la ap rekomanse otomatikman</value>
   </data>
   <data name="millions_jackpot_rule5_title" xml:space="preserve">
-    <value>5. Metòd Peman Pri</value>
+    <value>5. Metod pou peye pri</value>
   </data>
   <data name="millions_jackpot_rule5_desc1" xml:space="preserve">
-    <value>Pri Mega Jackpot yo ka peye atravè:</value>
+    <value>Mega Jakpot la ka peye apati:</value>
   </data>
   <data name="millions_jackpot_rule5_item1" xml:space="preserve">
-    <value>Kont Prensipal</value>
+    <value>Kont prensipal</value>
   </data>
   <data name="millions_jackpot_rule5_item2" xml:space="preserve">
-    <value>Kont Natcash</value>
+    <value>Kont Natcom</value>
   </data>
   <data name="millions_jackpot_rule5_item3" xml:space="preserve">
-    <value>Peman an kach (si sa aplikab)</value>
+    <value>Pewol Kach </value>
   </data>
   <data name="millions_jackpot_rule5_desc2" xml:space="preserve">
-    <value>Metòd peman an ap depann de valè Jackpot la ak règleman operasyonèl yo nan moman peman an.</value>
+    <value>Metod pewol pou jakpot la depann de montan an epi politik operasyonel pou peryod la</value>
   </data>
   <data name="millions_jackpot_rule6_title" xml:space="preserve">
     <value>6. Verifikasyon Pri</value>
   </data>
   <data name="millions_jackpot_rule6_desc1" xml:space="preserve">
-    <value>Natcom gen dwa pou verifye tout tikè genyen yo anvan li peye lajan Jackpot la.</value>
+    <value>Natcom gen dwa pou verifye tout tike ki genyen yo avan pewol jakpot la fet</value>
   </data>
   <data name="millions_jackpot_rule6_desc2" xml:space="preserve">
     <value>Yo ka bay yon lèt konfimasyon ofisyèl si sa nesesè.</value>

+ 12 - 0
website/Models/RewardHistoryViewModel.cs

@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using LotteryWebApp.Service;
+
+namespace LotteryWebApp.Models
+{
+    public class RewardHistoryModel
+    {
+        public List<Transaction> list { get; set; } = new List<Transaction>();
+        public string totalPage { get; set; } = "0";
+        public string seqPage { get; set; } = "1";
+    }
+}

+ 3 - 3
website/Properties/PublishProfiles/FolderProfile1.pubxml

@@ -8,12 +8,12 @@
     <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
     <LastUsedPlatform>Any CPU</LastUsedPlatform>
     <PublishProvider>FileSystem</PublishProvider>
-    <PublishUrl>D:\Common\Publish</PublishUrl>
+    <PublishUrl>E:\Ex_publish\LotoNatcomV2</PublishUrl>
     <WebPublishMethod>FileSystem</WebPublishMethod>
     <_TargetId>Folder</_TargetId>
     <SiteUrlToLaunchAfterPublish />
-    <TargetFramework>netcoreapp3.1</TargetFramework>
-    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+    <TargetFramework>net7.0</TargetFramework>
+    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
     <ProjectGuid>847e0916-075b-4836-93bc-919c422fa9fe</ProjectGuid>
     <SelfContained>true</SelfContained>
   </PropertyGroup>

+ 9 - 8
website/log4net.config

@@ -10,20 +10,21 @@
     </layout>
     <threshold value="ALL" />
   </appender>
-  <appender name="file" type="log4net.Appender.RollingFileAppender">
-    <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
+  <!-- Custom appender: mỗi ngày 1 file + tự xóa, chỉ giữ lại maxNumberOfFiles file gần nhất -->
+  <appender name="file" type="LotteryWebApp.Common.DailyRollingFileAppender, LotteryWebApp">
+    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+    <!-- Thư mục chứa log (phải kết thúc bằng dấu \) -->
     <file value="D:\01-Projects\Natcom\Lottery\LotteryWebApp\Logs\" />
     <appendToFile value="true" />
-    <datePattern value="dd.MM.yyyy'.log'" />
-    <staticLogFileName value="false" />
-    <appendToFile value="true" />
+    <!-- Rolling theo NGÀY: mỗi ngày 1 file riêng, vd 21.05.2026.log -->
     <rollingStyle value="Date" />
-    <maxSizeRollBackups value="10" />
-    <maximumFileSize value="100MB" />
+    <datePattern value="dd.MM.yyyy'.log'" />
     <staticLogFileName value="false" />
+    <!-- Chỉ giữ 30 file log gần nhất; file cũ hơn sẽ TỰ ĐỘNG bị xóa -->
+    <maxNumberOfFiles value="30" />
     <layout type="log4net.Layout.PatternLayout">
       <conversionPattern value="%date [%thread] %level - %message%newline" />
     </layout>
     <threshold value="ALL" />
   </appender>
-</log4net>
+</log4net>

+ 34 - 0
website/wwwroot/Millions/css/history.css

@@ -140,6 +140,40 @@
     color: #4B5563;
     transition: all 0.2s;
     flex-shrink: 0;
+    position: relative;
+    overflow: visible;
+}
+
+.ticket-ball-value {
+    position: relative;
+    z-index: 1;
+}
+
+.ticket-ball-mb {
+    margin-right: 6px;
+}
+
+.ticket-mb-label {
+    position: absolute;
+    right: -5px;
+    bottom: -6px;
+    z-index: 2;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    min-width: 18px;
+    height: 12px;
+    padding: 0 3px;
+    border: 1px solid #EAA800;
+    border-radius: 3px;
+    background: #FFD700;
+    color: #111827;
+    font-size: 8px;
+    line-height: 1;
+    font-weight: 900;
+    letter-spacing: 0;
+    box-shadow: 0 1px 3px rgba(0,0,0,0.18);
+    pointer-events: none;
 }
 
 .ticket-ball.ball-win {

+ 57 - 0
website/wwwroot/Millions/css/results.css

@@ -187,6 +187,63 @@
     box-shadow: 0 4px 8px rgba(186, 15, 33, 0.4);
 }
 
+.millions-result-ball {
+    position: relative;
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    flex: 0 0 32px;
+    color: #fff;
+    font-size: 13px;
+    font-weight: 900;
+    line-height: 1;
+    background: linear-gradient(135deg, #FF3D63 0%, #E3132D 60%, #BA0F21 100%);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    box-shadow: 0 4px 10px rgba(186, 15, 33, 0.25);
+    transition: transform 0.2s ease;
+}
+
+.millions-result-ball:hover {
+    transform: scale(1.08);
+}
+
+.millions-result-ball-mb {
+    margin-right: 6px;
+    background: radial-gradient(circle at 50% 50%,
+        #0062ff 0%,
+        #0062ff 60%,
+        #0149f0 75%,
+        #0231e0 85%,
+        #0218d1 92%,
+        #0300c2 100%);
+    box-shadow: 0 4px 10px rgba(0, 98, 255, 0.28), inset 0 0 8px 3px rgba(0, 24, 209, 0.35);
+}
+
+.millions-result-mb-label {
+    position: absolute;
+    right: -5px;
+    bottom: -7px;
+    z-index: 2;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    min-width: 18px;
+    height: 12px;
+    padding: 0 3px;
+    border-radius: 3px;
+    background: #ee0033;
+    color: #fff;
+    font-size: 8px;
+    line-height: 1;
+    font-weight: 900;
+    letter-spacing: 0;
+    box-shadow: 0 1px 3px rgba(0,0,0,0.18);
+    pointer-events: none;
+}
+
 .results-items-list {
     padding-bottom: 240px; /* Extra space for the sticky jackpot bar */
 }

+ 227 - 0
website/wwwroot/Millions/css/reward-history.css

@@ -0,0 +1,227 @@
+/* ===== Reward History page ===== */
+
+.reward-history-container {
+    background-color: #F5F5F5;
+    height: 100vh !important;
+    overflow: hidden !important;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+}
+
+/* Card */
+.reward-card {
+    background: #fff;
+    border-radius: 14px;
+    margin: 14px 0;
+    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.06);
+    border: 1px solid #F1F1F1;
+    overflow: hidden;
+    position: relative;
+}
+
+.reward-card-top {
+    padding: 14px 16px 14px 16px;
+    background: #fff;
+}
+
+.reward-card-bottom {
+    padding: 12px 16px;
+    background: #FDFDFD;
+    border-top: 1px dashed #E5E7EB;
+}
+
+.reward-card-perforation {
+    height: 4px;
+    background-image: radial-gradient(circle, #F3F4F6 6px, transparent 6px);
+    background-size: 24px 24px;
+    background-position: center;
+    background-repeat: repeat-x;
+    margin: 0 -10px;
+}
+
+/* Header row */
+.reward-card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 10px;
+}
+
+.reward-id {
+    font-size: 13px;
+    color: #9CA3AF;
+    font-weight: 700;
+    font-family: 'Bricolage Grotesque', sans-serif;
+}
+
+/* Status pill */
+.reward-status {
+    padding: 6px 14px;
+    border-radius: 30px;
+    font-size: 11px;
+    font-weight: 900;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    white-space: nowrap;
+}
+
+.reward-status-success    { background: #DCFCE7; color: #15803D; }
+.reward-status-processing { background: #FFF9C4; color: #B45309; }
+.reward-status-pending    { background: #DBEAFE; color: #1D4ED8; }
+.reward-status-reverted   { background: #FEE2E2; color: #B91C1C; }
+.reward-status-unknown    { background: #F3F4F6; color: #6B7280; }
+
+/* Amount display */
+.reward-amount-wrap {
+    display: flex;
+    align-items: baseline;
+    gap: 8px;
+    margin-top: 4px;
+}
+
+.reward-amount {
+    font-size: 28px;
+    font-weight: 900;
+    color: #0062FF;
+    line-height: 1;
+    font-family: 'Bricolage Grotesque', sans-serif;
+}
+
+.reward-currency {
+    font-size: 13px;
+    font-weight: 800;
+    color: #0062FF;
+    text-transform: uppercase;
+}
+
+/* Info grid */
+.reward-info-grid {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+}
+
+.reward-info-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: 12px;
+}
+
+.reward-info-label {
+    font-size: 12px;
+    color: #9CA3AF;
+    font-weight: 700;
+    text-transform: uppercase;
+    letter-spacing: 0.4px;
+    flex-shrink: 0;
+}
+
+.reward-info-value {
+    font-size: 13px;
+    color: #1F2937;
+    font-weight: 800;
+    text-align: right;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+/* Empty state */
+.reward-empty {
+    width: 100%;
+    padding: 96px 24px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    color: #9CA3AF;
+    opacity: 0.7;
+}
+
+.reward-empty i {
+    font-size: 48px;
+    margin-bottom: 16px;
+}
+
+.reward-empty p {
+    font-weight: 700;
+    font-size: 14px;
+}
+
+/* List container */
+.reward-list-container {
+    flex: 1;
+    height: calc(100vh - 80px) !important;
+    overflow-y: auto !important;
+    overflow-x: hidden !important;
+    padding-bottom: calc(120px + env(safe-area-inset-bottom));
+
+    scrollbar-width: thin !important;
+    scrollbar-color: #fce303 transparent !important;
+}
+
+.reward-list-container::-webkit-scrollbar {
+    width: 6px !important;
+    display: block !important;
+}
+
+.reward-list-container::-webkit-scrollbar-track {
+    background: transparent !important;
+}
+
+.reward-list-container::-webkit-scrollbar-thumb {
+    background-color: #fce303 !important;
+    border-radius: 20px !important;
+}
+
+/* Pagination */
+.reward-pagination {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 16px;
+    padding: 32px 0 96px 0;
+}
+
+.reward-page-btn {
+    padding: 8px 24px;
+    background: #fff;
+    border-radius: 12px;
+    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
+    font-weight: 800;
+    color: #6B7280;
+    transition: transform 0.15s;
+    cursor: pointer;
+    border: none;
+}
+
+.reward-page-btn:active:not(:disabled) {
+    transform: scale(0.95);
+}
+
+.reward-page-btn:disabled {
+    opacity: 0.3;
+    pointer-events: none;
+}
+
+.reward-page-indicator {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+.reward-page-label {
+    font-size: 10px;
+    color: #9CA3AF;
+    font-weight: 800;
+    text-transform: uppercase;
+    letter-spacing: 0.15em;
+}
+
+.reward-page-value {
+    font-weight: 900;
+    color: #0062FF;
+    font-size: 18px;
+}