Update quick polls to fetch from the API and allow real voting
Refactors the PollsTab component to fetch polls from '/api/activity/polls', implements loading states, and modifies the createPoll function to make a POST request to the same endpoint. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: e7a14a32-92ae-42ad-a9b1-5a1d77bc7492 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/139vJay Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
02134b613b
commit
6a6f626ba3
1 changed files with 104 additions and 62 deletions
|
|
@ -911,69 +911,121 @@ interface Poll {
|
||||||
}
|
}
|
||||||
|
|
||||||
function PollsTab({ userId, username }: { userId?: string; username?: string }) {
|
function PollsTab({ userId, username }: { userId?: string; username?: string }) {
|
||||||
const [polls, setPolls] = useState<Poll[]>(() => {
|
const [polls, setPolls] = useState<Poll[]>([]);
|
||||||
try {
|
const [loading, setLoading] = useState(true);
|
||||||
const saved = localStorage.getItem('aethex_activity_polls');
|
|
||||||
const parsed = saved ? JSON.parse(saved) : [];
|
|
||||||
return parsed.filter((p: Poll) => p.expiresAt > Date.now());
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const [showCreate, setShowCreate] = useState(false);
|
const [showCreate, setShowCreate] = useState(false);
|
||||||
const [newQuestion, setNewQuestion] = useState('');
|
const [newQuestion, setNewQuestion] = useState('');
|
||||||
const [newOptions, setNewOptions] = useState(['', '']);
|
const [newOptions, setNewOptions] = useState(['', '']);
|
||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
const fetchPolls = async () => {
|
||||||
localStorage.setItem('aethex_activity_polls', JSON.stringify(polls));
|
try {
|
||||||
} catch {}
|
const response = await fetch('/api/activity/polls');
|
||||||
}, [polls]);
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const mapped = (data.data || data || []).map((p: any) => ({
|
||||||
|
id: p.id,
|
||||||
|
question: p.question,
|
||||||
|
options: (p.options || []).map((opt: any, i: number) => ({
|
||||||
|
id: opt.id || `opt-${i}`,
|
||||||
|
text: opt.text || opt.option_text,
|
||||||
|
votes: opt.votes || opt.vote_count || 0,
|
||||||
|
})),
|
||||||
|
createdBy: p.creator_id || p.created_by || 'system',
|
||||||
|
createdByName: p.creator_name || 'Anonymous',
|
||||||
|
createdAt: new Date(p.created_at).getTime(),
|
||||||
|
expiresAt: new Date(p.expires_at).getTime(),
|
||||||
|
votedUsers: p.voted_users || [],
|
||||||
|
}));
|
||||||
|
setPolls(mapped.filter((p: Poll) => p.expiresAt > Date.now()));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setPolls([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPolls();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const createPoll = () => {
|
const createPoll = async () => {
|
||||||
if (!userId || !newQuestion.trim() || newOptions.filter(o => o.trim()).length < 2) return;
|
if (!userId || !newQuestion.trim() || newOptions.filter(o => o.trim()).length < 2) return;
|
||||||
|
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
const poll: Poll = {
|
try {
|
||||||
id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
const response = await fetch('/api/activity/polls', {
|
||||||
question: newQuestion.trim(),
|
method: 'POST',
|
||||||
options: newOptions.filter(o => o.trim()).map((text, i) => ({
|
headers: { 'Content-Type': 'application/json' },
|
||||||
id: `opt-${i}`,
|
body: JSON.stringify({
|
||||||
text: text.trim(),
|
question: newQuestion.trim(),
|
||||||
votes: 0
|
options: newOptions.filter(o => o.trim()),
|
||||||
})),
|
creator_id: userId,
|
||||||
createdBy: userId,
|
creator_name: username || 'Anonymous',
|
||||||
createdByName: username || 'Anonymous',
|
}),
|
||||||
createdAt: Date.now(),
|
});
|
||||||
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
|
||||||
votedUsers: []
|
if (response.ok) {
|
||||||
};
|
const data = await response.json();
|
||||||
|
const newPoll: Poll = {
|
||||||
|
id: data.id || `${Date.now()}`,
|
||||||
|
question: newQuestion.trim(),
|
||||||
|
options: newOptions.filter(o => o.trim()).map((text, i) => ({
|
||||||
|
id: `opt-${i}`,
|
||||||
|
text: text.trim(),
|
||||||
|
votes: 0
|
||||||
|
})),
|
||||||
|
createdBy: userId,
|
||||||
|
createdByName: username || 'Anonymous',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
||||||
|
votedUsers: []
|
||||||
|
};
|
||||||
|
setPolls(prev => [newPoll, ...prev]);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
setPolls(prev => [poll, ...prev]);
|
|
||||||
setNewQuestion('');
|
setNewQuestion('');
|
||||||
setNewOptions(['', '']);
|
setNewOptions(['', '']);
|
||||||
setShowCreate(false);
|
setShowCreate(false);
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const vote = (pollId: string, optionId: string) => {
|
const vote = async (pollId: string, optionId: string) => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
setPolls(prev => prev.map(poll => {
|
const poll = polls.find(p => p.id === pollId);
|
||||||
if (poll.id !== pollId || poll.votedUsers.includes(userId)) return poll;
|
if (!poll || poll.votedUsers.includes(userId)) return;
|
||||||
|
|
||||||
|
setPolls(prev => prev.map(p => {
|
||||||
|
if (p.id !== pollId) return p;
|
||||||
return {
|
return {
|
||||||
...poll,
|
...p,
|
||||||
options: poll.options.map(opt =>
|
options: p.options.map(opt =>
|
||||||
opt.id === optionId ? { ...opt, votes: opt.votes + 1 } : opt
|
opt.id === optionId ? { ...opt, votes: opt.votes + 1 } : opt
|
||||||
),
|
),
|
||||||
votedUsers: [...poll.votedUsers, userId]
|
votedUsers: [...p.votedUsers, userId]
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(`/api/activity/polls/${pollId}/vote`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ user_id: userId, option_id: optionId }),
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deletePoll = (pollId: string) => {
|
const deletePoll = async (pollId: string) => {
|
||||||
setPolls(prev => prev.filter(p => p.id !== pollId));
|
setPolls(prev => prev.filter(p => p.id !== pollId));
|
||||||
|
try {
|
||||||
|
await fetch(`/api/activity/polls/${pollId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ user_id: userId }),
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTimeRemaining = (expiresAt: number) => {
|
const formatTimeRemaining = (expiresAt: number) => {
|
||||||
|
|
@ -985,25 +1037,13 @@ function PollsTab({ userId, username }: { userId?: string; username?: string })
|
||||||
return `${minutes}m left`;
|
return `${minutes}m left`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const samplePolls: Poll[] = [
|
if (loading) {
|
||||||
{
|
return (
|
||||||
id: 'sample1',
|
<div className="flex justify-center py-8">
|
||||||
question: 'What realm are you most excited about?',
|
<Loader2 className="w-6 h-6 animate-spin text-purple-400" />
|
||||||
options: [
|
</div>
|
||||||
{ id: 'opt-1', text: 'GameForge', votes: 12 },
|
);
|
||||||
{ id: 'opt-2', text: 'Labs', votes: 8 },
|
}
|
||||||
{ id: 'opt-3', text: 'Nexus', votes: 15 },
|
|
||||||
{ id: 'opt-4', text: 'Foundation', votes: 5 },
|
|
||||||
],
|
|
||||||
createdBy: 'system',
|
|
||||||
createdByName: 'AeThex',
|
|
||||||
createdAt: Date.now() - 3600000,
|
|
||||||
expiresAt: Date.now() + 20 * 60 * 60 * 1000,
|
|
||||||
votedUsers: []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const displayPolls = polls.length > 0 ? polls : samplePolls;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|
@ -1094,7 +1134,15 @@ function PollsTab({ userId, username }: { userId?: string; username?: string })
|
||||||
</motion.button>
|
</motion.button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{displayPolls.map((poll, pollIndex) => {
|
{polls.length === 0 && (
|
||||||
|
<div className="text-center py-6">
|
||||||
|
<Vote className="w-8 h-8 text-[#4e5058] mx-auto mb-2" />
|
||||||
|
<p className="text-[#949ba4] text-sm">No active polls</p>
|
||||||
|
<p className="text-[#4e5058] text-xs mt-1">Be the first to create one!</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{polls.map((poll, pollIndex) => {
|
||||||
const totalVotes = poll.options.reduce((sum, opt) => sum + opt.votes, 0);
|
const totalVotes = poll.options.reduce((sum, opt) => sum + opt.votes, 0);
|
||||||
const hasVoted = userId && poll.votedUsers.includes(userId);
|
const hasVoted = userId && poll.votedUsers.includes(userId);
|
||||||
const isOwner = userId === poll.createdBy;
|
const isOwner = userId === poll.createdBy;
|
||||||
|
|
@ -1166,12 +1214,6 @@ function PollsTab({ userId, username }: { userId?: string; username?: string })
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{polls.length === 0 && (
|
|
||||||
<p className="text-center text-[#949ba4] text-xs mt-2">
|
|
||||||
Polls expire after 24 hours. Create one to get opinions!
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue