Skip to content

🚀 PulseVote: Complete Real-time Polling Application#1

Open
jacobluetzow wants to merge 12 commits intomainfrom
feature/pulsevote-complete
Open

🚀 PulseVote: Complete Real-time Polling Application#1
jacobluetzow wants to merge 12 commits intomainfrom
feature/pulsevote-complete

Conversation

@jacobluetzow
Copy link
Copy Markdown
Member

Hey live stream viewers! 👋 This PR contains the entire journey of building
PulseVote from scratch - a modern, real-time polling application built with
Phoenix LiveView.

🎯 What We Built

PulseVote is a production-ready polling platform that lets users create polls,
vote in real-time, and see live results with beautiful animations. Perfect for
teams, communities, events, or any situation where you need to gather opinions
quickly!

✨ Key Features

  • 🔴 Real-time Everything: Vote counts update instantly across all connected
    users
  • 🔄 Vote Changing: Users can change their mind and update their vote anytime
  • 👀 Results Preview: Non-voters can peek at results before casting their vote
  • 🎨 Beautiful UI: Modern gradient design with smooth animations and
    glassmorphism effects
  • 📱 Mobile-First: Responsive design that works perfectly on all devices
  • 🔐 User Authentication: Secure email-based registration and login
  • ⚡ Lightning Fast: Built with Phoenix LiveView for instant updates

🛠️ Technical Highlights

  • Phoenix LiveView for real-time interactivity without JavaScript
  • PostgreSQL with embedded schemas for flexible poll options
  • PubSub for broadcasting live updates to all connected users
  • Tailwind CSS with custom animations and gradient designs
  • Ecto with proper vote counting and data validation
  • Database-driven vote recalculation (prevents negative vote counts!)

🎬 Development Journey

This PR shows the complete evolution from:

  1. Initial Phoenix setup → Basic project structure
  2. Data modeling → Users, polls, and votes with proper relationships
  3. Core functionality → Poll creation, voting, and results
  4. Real-time features → Live updates and vote changing
  5. UI transformation → From basic Phoenix to stunning modern design

🧪 What to Test

As our live stream viewers, we'd love your feedback on:

  • 🎯 User Experience: Is the voting flow intuitive?
  • 🎨 Design & UI: How does the interface feel? Any suggestions?
  • ⚡ Performance: Does real-time updating feel snappy?
  • 📱 Mobile Experience: How does it work on your phone?
  • 🐛 Edge Cases: Can you break anything? Try hard!
  • 💡 Feature Ideas: What would make this even better?

🚀 Try It Live

  1. Clone the repo and run mix setup
  2. Start with mix phx.server
  3. Visit localhost:4000
  4. Create an account and start polling!

📊 Before & After

Before: Basic Phoenix welcome page
After: Full-featured polling platform with enterprise-grade UI

🎙️ Stream Notes

This entire application was built live on stream, showcasing:

  • Phoenix LiveView development patterns
  • Real-time application architecture
  • Modern UI/UX design principles
  • Elixir/Phoenix best practices
  • Problem-solving and debugging techniques

💬 Your Feedback Matters!

Drop your thoughts, suggestions, and any bugs you find in the comments below.
This is a community project and we want to make it awesome together!

Happy polling! 🗳️✨


Built live on stream with Phoenix LiveView, Elixir, and lots of ☕

jacobluetzow and others added 12 commits June 7, 2025 10:58
- Added Poll schema with embedded options
- Set up user authentication system with phx.gen.auth
- Created Vote schema with user relationships
- Added unique constraint to prevent duplicate votes
- All migrations successfully applied

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Generated LiveView components for poll management
- Built dynamic form with add/remove options functionality
- Implemented complete Polls context with CRUD operations
- Added proper routing with authentication requirements
- Form includes title, description, and dynamic options
- Proper validation and error handling in place

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Use proper Option structs instead of plain maps in ensure_default_options
- Fix add_option event handler to create Option structs
- Resolves FunctionClauseError in Ecto.Changeset.cast/4
- Poll creation form should now work correctly

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Implement proper form parameter handling based on DockYard best practices
- Add form_to_params helper to preserve user input during add/remove operations
- Improve UI with better styling and accessibility
- Add SVG icons for add/remove buttons
- Prevent removing options when only 2 remain (minimum requirement)
- Use proper parameter extraction from changeset

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Convert Option structs to maps before passing to changeset
- Resolves Ecto.CastError when updating poll options
- Voting functionality now works correctly with proper data types
- Vote counts are properly incremented and stored

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Pass current_user from parent LiveViews to FormComponent
- Remove unnecessary on_mount hook from LiveComponent
- Make description field optional in poll validation
- Poll creation should now work correctly with proper user assignment

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add PubSub subscription for live poll updates
- Broadcast vote changes to all connected viewers
- Enhanced progress bars with smooth animations and gradients
- Real-time vote count updates without page refresh
- Improved voting UI with hover effects and visual feedback
- Added celebration emoji for successful votes
- All viewers now see live results as votes are cast

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Users can now view results without having to vote first
- Toggle button switches between voting interface and results view
- Results highlighting only shows user's choice when they have voted
- Clean UI with toggle positioned next to section headers
- Maintains real-time updates even when viewing results without voting

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add vote changing capability with proper UI states
- Replace manual vote count increment/decrement with database recalculation
- Fix struct/map handling in vote count updates to prevent casting errors
- Add real-time poll creation broadcasting to index page
- Improve vote success messages for new vs changed votes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Include 'mcp__tidewave__list_liveview_pages' and 'mcp__tidewave__execute_sql_query' to the command list
- Enhance functionality for managing LiveView pages and executing SQL queries

🤖 Generated with [Claude Code](https://claude.ai/code)
…ctions

- Added a comprehensive description of the PulseVote application
- Included features such as user authentication, poll creation, real-time voting, and responsive design
- Provided a step-by-step guide for getting started and using the application
- Outlined the technology stack and database schema for better understanding

🤖 Generated with [Claude Code](https://claude.ai/code)
- Stunning gradient navigation bar with animated logo and user avatars
- Beautiful homepage with hero section, features showcase, and call-to-actions
- Custom animations including floating blob backgrounds and smooth transitions
- Glassmorphism effects and modern gradient designs throughout
- Mobile-responsive design with professional SaaS-style aesthetics
- Updated plan.md to reflect project completion status

🎨 Transform PulseVote into a production-ready polling platform with enterprise-grade UI

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

<!-- User Menu -->
<div class="flex items-center space-x-4">
<%= if @current_user do %>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old liveview syntax, you could simply use :if={@current_user} in the parent div

Comment on lines +71 to +73
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"/>
</svg>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could have used heroicons (pre-installed in Phoenix) instead of a random SVG

@@ -0,0 +1,225 @@
defmodule PulseVoteWeb.PollLive.FormComponent do
use PulseVoteWeb, :live_component
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang, Claude generated a live component 👀
Also used the new {} syntax for interpolating variables

end

def handle_event("add_option", _params, socket) do
alias PulseVote.Polls.Poll.Option
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit weird to use an alias inside a function, but there are Elixir devs who like that, so it's just a personal preference

Comment on lines +126 to +127
current_params = form_to_params(socket.assigns.form)
existing_options = Map.get(current_params, "options", [])
Copy link
Copy Markdown

@danielbergholz danielbergholz Jun 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude is not a fan of the pipe operator

Suggested change
current_params = form_to_params(socket.assigns.form)
existing_options = Map.get(current_params, "options", [])
existing_options =
socket.assigns.form
|> form_to_params()
|> Map.get("options", [])

Copy link
Copy Markdown

@danielbergholz danielbergholz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final verdict: 9/10. I'm impressed! Very idiomatic Elixir code, could take some veeeery minor adjustments, but LGTM 🔥

Comment on lines +50 to +53
@impl true
def handle_info({PulseVoteWeb.PollLive.FormComponent, {:saved, poll}}, socket) do
{:noreply, stream_insert(socket, :polls, poll)}
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang, Claude is using streams correctly 👀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants