You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
4.9 KiB
TypeScript
190 lines
4.9 KiB
TypeScript
import { useState, type CSSProperties } from 'react';
|
|
import type { DailyTaskResponse } from '../network/api';
|
|
|
|
interface DailyTasksProps {
|
|
tasks: DailyTaskResponse[];
|
|
onClaim: (taskId: string) => void;
|
|
}
|
|
|
|
const buttonStyle: CSSProperties = {
|
|
position: 'fixed',
|
|
top: 12,
|
|
right: 12,
|
|
zIndex: 50,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
padding: '5px 10px',
|
|
borderRadius: 8,
|
|
border: '1px solid rgba(255, 215, 0, 0.2)',
|
|
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
color: '#daa520',
|
|
fontSize: 11,
|
|
fontWeight: 600,
|
|
cursor: 'pointer',
|
|
pointerEvents: 'auto',
|
|
userSelect: 'none',
|
|
};
|
|
|
|
const panelStyle: CSSProperties = {
|
|
position: 'fixed',
|
|
top: 12,
|
|
right: 12,
|
|
zIndex: 50,
|
|
width: 230,
|
|
borderRadius: 10,
|
|
border: '1px solid rgba(255, 215, 0, 0.15)',
|
|
backgroundColor: 'rgba(10, 10, 20, 0.88)',
|
|
backdropFilter: 'blur(6px)',
|
|
overflow: 'hidden',
|
|
pointerEvents: 'auto',
|
|
};
|
|
|
|
const panelHeaderStyle: CSSProperties = {
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: '7px 10px',
|
|
borderBottom: '1px solid rgba(255,255,255,0.08)',
|
|
fontSize: 12,
|
|
fontWeight: 700,
|
|
color: '#daa520',
|
|
};
|
|
|
|
const closeButtonStyle: CSSProperties = {
|
|
background: 'none',
|
|
border: 'none',
|
|
color: '#888',
|
|
fontSize: 14,
|
|
cursor: 'pointer',
|
|
padding: '0 4px',
|
|
lineHeight: 1,
|
|
};
|
|
|
|
const taskListStyle: CSSProperties = {
|
|
padding: '6px 10px 8px',
|
|
};
|
|
|
|
const taskRowStyle: CSSProperties = {
|
|
marginBottom: 8,
|
|
};
|
|
|
|
const taskLabelStyle: CSSProperties = {
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
fontSize: 11,
|
|
color: '#ccc',
|
|
marginBottom: 3,
|
|
};
|
|
|
|
const progressBarBgStyle: CSSProperties = {
|
|
width: '100%',
|
|
height: 6,
|
|
borderRadius: 3,
|
|
backgroundColor: 'rgba(255,255,255,0.08)',
|
|
overflow: 'hidden',
|
|
};
|
|
|
|
function progressBarFillStyle(pct: number, done: boolean): CSSProperties {
|
|
return {
|
|
width: `${Math.min(100, pct)}%`,
|
|
height: '100%',
|
|
borderRadius: 3,
|
|
backgroundColor: done ? '#44cc44' : '#daa520',
|
|
transition: 'width 300ms ease',
|
|
};
|
|
}
|
|
|
|
const claimButtonStyle: CSSProperties = {
|
|
marginTop: 4,
|
|
padding: '2px 10px',
|
|
borderRadius: 4,
|
|
border: '1px solid rgba(255, 215, 0, 0.4)',
|
|
backgroundColor: 'rgba(255, 215, 0, 0.15)',
|
|
color: '#ffd700',
|
|
fontSize: 10,
|
|
fontWeight: 600,
|
|
cursor: 'pointer',
|
|
display: 'block',
|
|
width: '100%',
|
|
textAlign: 'center',
|
|
};
|
|
|
|
export function DailyTasks({ tasks, onClaim }: DailyTasksProps) {
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
const completedCount = tasks.filter((t) => t.completed).length;
|
|
const claimableCount = tasks.filter((t) => t.completed && !t.claimed).length;
|
|
|
|
if (!expanded) {
|
|
return (
|
|
<button style={buttonStyle} onClick={() => setExpanded(true)}>
|
|
<span style={{ fontSize: 13 }}>⭐</span>
|
|
<span>
|
|
Daily {completedCount}/{tasks.length}
|
|
{claimableCount > 0 && (
|
|
<span style={{ color: '#ffd700', marginLeft: 4 }}>!</span>
|
|
)}
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div style={panelStyle}>
|
|
<div style={panelHeaderStyle}>
|
|
<span>⭐ Daily Tasks</span>
|
|
<button style={closeButtonStyle} onClick={() => setExpanded(false)}>
|
|
✕
|
|
</button>
|
|
</div>
|
|
<div style={taskListStyle}>
|
|
{tasks.map((task) => {
|
|
const done = task.completed;
|
|
const pct = (task.progress / Math.max(1, task.objectiveCount)) * 100;
|
|
const canClaim = task.completed && !task.claimed;
|
|
|
|
return (
|
|
<div key={task.taskId} style={taskRowStyle}>
|
|
<div style={taskLabelStyle}>
|
|
<span style={{ opacity: task.claimed ? 0.5 : done ? 0.8 : 1 }}>
|
|
{task.claimed ? '\u2705 ' : done ? '\u2714 ' : ''}{task.title}
|
|
</span>
|
|
<span style={{
|
|
fontSize: 10,
|
|
color: task.claimed ? '#666' : done ? '#44cc44' : '#999',
|
|
fontWeight: 600,
|
|
}}>
|
|
{task.progress}/{task.objectiveCount}
|
|
</span>
|
|
</div>
|
|
<div style={progressBarBgStyle}>
|
|
<div style={progressBarFillStyle(pct, done)} />
|
|
</div>
|
|
{task.rewardAmount > 0 && (
|
|
<div style={{ fontSize: 9, color: '#b8860b', marginTop: 2 }}>
|
|
+{task.rewardAmount} {task.rewardType}
|
|
</div>
|
|
)}
|
|
{canClaim && (
|
|
<button
|
|
style={claimButtonStyle}
|
|
onClick={() => onClaim(task.taskId)}
|
|
>
|
|
Claim Reward
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
{tasks.length === 0 && (
|
|
<div style={{ textAlign: 'center', opacity: 0.5, padding: 8, fontSize: 11 }}>
|
|
No daily tasks available
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|