🎫 feat: Enhance redemption code expiry handling & improve UI responsiveness

Backend
• Introduced `validateExpiredTime` helper in `controller/redemption.go`; reused in both Add & Update endpoints to enforce “expiry time must not be earlier than now”, eliminating duplicated checks
• Removed `RedemptionCodeStatusExpired` constant and all related references – expiry is now determined exclusively by the `expired_time` field for simpler, safer state management
• Simplified `DeleteInvalidRedemptions`: deletes codes that are `used` / `disabled` or `enabled` but already expired, without relying on extra status codes
• Controller no longer mutates `status` when listing or fetching redemption codes; clients derive expiry status from timestamp

Frontend
• Added reusable `isExpired` helper in `RedemptionsTable.js`; leveraged for:
  – status rendering (orange “Expired” tag)
  – action-menu enable/disable logic
  – row styling
• Removed duplicated inline expiry logic, improving readability and performance
• Adjusted toolbar layout: on small screens the “Clear invalid codes” button now wraps onto its own line, while “Add” & “Copy” remain grouped

Result
The codebase is now more maintainable, secure, and performant with no redundant constants, centralized validation, and cleaner UI behaviour across devices.
This commit is contained in:
Apple\Apple
2025-06-13 20:51:20 +08:00
parent 60b624a329
commit 6c359839cc
6 changed files with 158 additions and 40 deletions

View File

@@ -20,6 +20,8 @@ import {
Typography,
Card,
Tag,
Form,
DatePicker,
} from '@douyinfe/semi-ui';
import {
IconCreditCard,
@@ -40,9 +42,10 @@ const EditRedemption = (props) => {
name: '',
quota: 100000,
count: 1,
expired_time: 0,
};
const [inputs, setInputs] = useState(originInputs);
const { name, quota, count } = inputs;
const { name, quota, count, expired_time } = inputs;
const handleCancel = () => {
props.handleClose();
@@ -85,6 +88,9 @@ const EditRedemption = (props) => {
localInputs.count = parseInt(localInputs.count);
localInputs.quota = parseInt(localInputs.quota);
localInputs.name = name;
if (localInputs.expired_time === null || localInputs.expired_time === undefined) {
localInputs.expired_time = 0;
}
let res;
if (isEdit) {
res = await API.put(`/api/redemption/`, {
@@ -220,6 +226,25 @@ const EditRedemption = (props) => {
required={!isEdit}
/>
</div>
<div>
<Text strong className="block mb-2">{t('过期时间')}</Text>
<DatePicker
type="dateTime"
placeholder={t('选择过期时间(可选,留空为永久)')}
showClear
value={expired_time ? new Date(expired_time * 1000) : null}
onChange={(value) => {
if (value === null || value === undefined) {
handleInputChange('expired_time', 0);
} else {
const timestamp = Math.floor(value.getTime() / 1000);
handleInputChange('expired_time', timestamp);
}
}}
size="large"
className="!rounded-lg w-full"
/>
</div>
</div>
</Card>