|
|
@@ -27,6 +27,7 @@ import {
|
|
|
Spin,
|
|
|
Tooltip,
|
|
|
Collapsible,
|
|
|
+ Modal,
|
|
|
} from '@douyinfe/semi-ui';
|
|
|
import {
|
|
|
CalendarCheck,
|
|
|
@@ -35,11 +36,14 @@ import {
|
|
|
ChevronDown,
|
|
|
ChevronUp,
|
|
|
} from 'lucide-react';
|
|
|
+import Turnstile from 'react-turnstile';
|
|
|
import { API, showError, showSuccess, renderQuota } from '../../../../helpers';
|
|
|
|
|
|
-const CheckinCalendar = ({ t, status }) => {
|
|
|
+const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
const [checkinLoading, setCheckinLoading] = useState(false);
|
|
|
+ const [turnstileModalVisible, setTurnstileModalVisible] = useState(false);
|
|
|
+ const [turnstileWidgetKey, setTurnstileWidgetKey] = useState(0);
|
|
|
const [checkinData, setCheckinData] = useState({
|
|
|
enabled: false,
|
|
|
stats: {
|
|
|
@@ -109,11 +113,23 @@ const CheckinCalendar = ({ t, status }) => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- // 执行签到
|
|
|
- const doCheckin = async () => {
|
|
|
+ const postCheckin = async (token) => {
|
|
|
+ const url = token
|
|
|
+ ? `/api/user/checkin?turnstile=${encodeURIComponent(token)}`
|
|
|
+ : '/api/user/checkin';
|
|
|
+ return API.post(url);
|
|
|
+ };
|
|
|
+
|
|
|
+ const shouldTriggerTurnstile = (message) => {
|
|
|
+ if (!turnstileEnabled) return false;
|
|
|
+ if (typeof message !== 'string') return true;
|
|
|
+ return message.includes('Turnstile');
|
|
|
+ };
|
|
|
+
|
|
|
+ const doCheckin = async (token) => {
|
|
|
setCheckinLoading(true);
|
|
|
try {
|
|
|
- const res = await API.post('/api/user/checkin');
|
|
|
+ const res = await postCheckin(token);
|
|
|
const { success, data, message } = res.data;
|
|
|
if (success) {
|
|
|
showSuccess(
|
|
|
@@ -121,7 +137,19 @@ const CheckinCalendar = ({ t, status }) => {
|
|
|
);
|
|
|
// 刷新签到状态
|
|
|
fetchCheckinStatus(currentMonth);
|
|
|
+ setTurnstileModalVisible(false);
|
|
|
} else {
|
|
|
+ if (!token && shouldTriggerTurnstile(message)) {
|
|
|
+ if (!turnstileSiteKey) {
|
|
|
+ showError('Turnstile is enabled but site key is empty.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ setTurnstileModalVisible(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (token && shouldTriggerTurnstile(message)) {
|
|
|
+ setTurnstileWidgetKey((v) => v + 1);
|
|
|
+ }
|
|
|
showError(message || t('签到失败'));
|
|
|
}
|
|
|
} catch (error) {
|
|
|
@@ -186,6 +214,30 @@ const CheckinCalendar = ({ t, status }) => {
|
|
|
|
|
|
return (
|
|
|
<Card className='!rounded-2xl'>
|
|
|
+ <Modal
|
|
|
+ title='Security Check'
|
|
|
+ visible={turnstileModalVisible}
|
|
|
+ footer={null}
|
|
|
+ centered
|
|
|
+ onCancel={() => {
|
|
|
+ setTurnstileModalVisible(false);
|
|
|
+ setTurnstileWidgetKey((v) => v + 1);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div className='flex justify-center py-2'>
|
|
|
+ <Turnstile
|
|
|
+ key={turnstileWidgetKey}
|
|
|
+ sitekey={turnstileSiteKey}
|
|
|
+ onVerify={(token) => {
|
|
|
+ doCheckin(token);
|
|
|
+ }}
|
|
|
+ onExpire={() => {
|
|
|
+ setTurnstileWidgetKey((v) => v + 1);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </Modal>
|
|
|
+
|
|
|
{/* 卡片头部 */}
|
|
|
<div className='flex items-center justify-between'>
|
|
|
<div
|
|
|
@@ -221,7 +273,7 @@ const CheckinCalendar = ({ t, status }) => {
|
|
|
type='primary'
|
|
|
theme='solid'
|
|
|
icon={<Gift size={16} />}
|
|
|
- onClick={doCheckin}
|
|
|
+ onClick={() => doCheckin()}
|
|
|
loading={checkinLoading || !initialLoaded}
|
|
|
disabled={!initialLoaded || checkinData.stats?.checked_in_today}
|
|
|
className='!bg-green-600 hover:!bg-green-700'
|