Skip to main content

Bug Fix: Lucky Spin Countdown Timer — Update Label & Format to dd:hh:mm:ss

Last Updated: 15/07/2026
Type: UI Fix — Countdown Timer Format
Screen: Lucky Spin Result Dialog (LuckySpinResultScreen)
Severity: 🟠 Medium — Visual bug, impacts clarity of voucher expiration

Feedback Screenshot:


1. Current Issues

In the prize modal/result screen, the voucher expiration countdown is displaying incorrectly:

Kết thúc sau 719 : 59 : 52
^^^ ^^ ^^
(min?) (sec?) (?)

Two distinct issues:

  1. Wrong Label: "Kết thúc sau" (Ends in) → change to "Hết hạn sau" (Expires in).
  2. Incorrect Formatting: Displaying raw minutes (719) instead of calculating days : hours : minutes : seconds.
    • 719 minutes = 11 hours 59 minutes (less than 24 hours, so the day column should not show).
    • Example: If a voucher expires in 1 day 11 hours 59 minutes, it should render: 01 : 11 : 59 : 52

2. Formatting Rules

Mathematical Conversion

remainingSeconds (totalSeconds) = expiryTime - currentTime

dd = Math.floor(totalSeconds / 86400) // 86400 seconds in a day
hh = Math.floor((totalSeconds % 86400) / 3600) // remaining hours
mm = Math.floor((totalSeconds % 3600) / 60) // remaining minutes
ss = totalSeconds % 60 // remaining seconds

Visual Specifications

ConditionVisual FormatExample
Remaining time ≥ 24hdd : hh : mm : ss01 : 11 : 59 : 52
Remaining time < 24hhh : mm : ss11 : 59 : 52
Remaining time < 1hmm : ss59 : 52
ExpiredHide timer, show "Voucher đã hết hạn"

Note: For the specific case in the screenshot (719 minutes = 11h 59m), the format should automatically resolve to 11 : 59 : 52 (no day column).


3. Code Implementation

3.1 Label Change

// ❌ Before
<Text>Kết thúc sau</Text>

// ✅ After
<Text>Hết hạn sau</Text>

3.2 Time Breakdown Utility

/**
* Calculates remaining time chunks
* Returns { dd, hh, mm, ss, totalSeconds }
*/
const getCountdownParts = (expiredAt: string | Date) => {
const now = new Date().getTime();
const end = new Date(expiredAt).getTime();
const totalSeconds = Math.max(0, Math.floor((end - now) / 1000));

const dd = Math.floor(totalSeconds / 86400);
const hh = Math.floor((totalSeconds % 86400) / 3600);
const mm = Math.floor((totalSeconds % 3600) / 60);
const ss = totalSeconds % 60;

return { dd, hh, mm, ss, totalSeconds };
};

const pad = (n: number) => String(n).padStart(2, '0');

3.3 React Native Timer Component

const SpinPrizeCountdown = ({ expiredAt }: { expiredAt: string }) => {
const [parts, setParts] = useState(getCountdownParts(expiredAt));

useEffect(() => {
const timer = setInterval(() => {
setParts(getCountdownParts(expiredAt));
}, 1000);
return () => clearInterval(timer);
}, [expiredAt]);

const { dd, hh, mm, ss, totalSeconds } = parts;

if (totalSeconds <= 0) {
return <Text style={styles.expired}>Voucher đã hết hạn</Text>;
}

return (
<View style={styles.countdownRow}>
<Text style={styles.label}>Hết hạn sau</Text> {/* ← LABEL CHANGED */}

<View style={styles.timerBoxes}>
{/* Render Day block only if remaining time is 24h or more */}
{dd > 0 && (
<>
<TimerBox value={pad(dd)} />
<Text style={styles.separator}>:</Text>
</>
)}

{/* Render Hour block if days or hours exist */}
{(dd > 0 || hh > 0) && (
<>
<TimerBox value={pad(hh)} />
<Text style={styles.separator}>:</Text>
</>
)}

<TimerBox value={pad(mm)} />
<Text style={styles.separator}>:</Text>
<TimerBox value={pad(ss)} />
</View>
</View>
);
};

const TimerBox = ({ value }: { value: string }) => (
<View style={styles.timerBox}>
<Text style={styles.timerValue}>{value}</Text>
</View>
);

4. Visual Examples

Scenario A (719 minutes remaining):
719 minutes = 11h 59m (Under 24h)

Hết hạn sau 11 : 59 : 52
hh mm ss


Scenario B (2 days, 3 hours remaining):

Hết hạn sau 02 : 03 : 15 : 00
dd hh mm ss


Scenario C (45 minutes remaining):

Hết hạn sau 45 : 00
mm ss

Technical Checklist

  • Locate the countdown view in the spin result modal/screen (LuckySpinResultScreen or SpinPrizeModal).
  • Rename "Kết thúc sau" to "Hết hạn sau".
  • Implement the getCountdownParts logic to calculate dynamic units instead of raw minutes.
  • Integrate a 1-second interval timer using setInterval and handle cleanup.
  • Hide day dd block if dd === 0.
  • Display an expired state text when totalSeconds <= 0.
  • Test case: Voucher expires in 719 minutes → UI displays 11 : 59 : ss (no day column).
  • Test case: Voucher expires in 25 hours → UI displays 01 : 01 : 00 : ss.