PollUp is a modern, real-time online polling platform that enables users to create, share, and vote on polls with instant result updates. Built as part of the ProDev FE case study, this application demonstrates advanced front-end development skills, including real-time data synchronisation, state management with Context API, and dynamic data visualisation.
Live Demo: https://pollsup.vercel.app
- User Authentication: Secure signup/login with email verification
- Poll Creation: Multi-step form with validation (title, options, scheduling)
- Real-Time Voting: Live vote counting with device-based duplicate prevention
- Dynamic Results: Interactive charts and progress bars showing live poll results
- Poll Management: View, edit, share, and delete polls from a centralised dashboard
- Comprehensive Analytics: Track views, votes, conversion rates
- Device Distribution: Monitor voter engagement across desktop, mobile, and tablet
- Time-Based Insights: Daily activity and hourly voting patterns
- Visual Representations: Charts and graphs for easy data interpretation
- Responsive Design: Seamless experience across all devices
- Dark Mode Ready: Theme support for better accessibility
- Intuitive UI: Clean, modern interface with smooth animations
- Status Management: Active, upcoming, and past poll categorisation
- Multi-Platform Sharing: Share polls via Twitter, Facebook, LinkedIn, and Email
- Copy Link: Quick link copying for easy poll distribution
- Unique Poll URLs: Each poll has a shareable public URL
- React 18.3 - Component-based UI library
- TypeScript - Type-safe development
- Vite - Fast build tool and dev server
- TailwindCSS - Utility-first CSS framework
- Shadcn/ui - Accessible component library
- React Context API - Global state management
- React Hook Form - Form handling with validation
- Zod - Schema validation
- Supabase - Backend-as-a-Service
- PostgreSQL database
- Real-time subscriptions
- Row Level Security (RLS)
- Authentication
- Recharts - Chart library for analytics
- TanStack Table - Powerful table management
- Custom Progress Bars - Real-time result visualization
- ESLint - Code linting
- Prettier - Code formatting
- Git - Version control
- Vercel - Deployment platform
pollup/
├── src/
│ ├── api/
│ │ └── supabaseClient.ts # Supabase configuration
│ ├── components/
│ │ ├── ui/ # Reusable UI components
│ │ │ ├── Button.tsx
│ │ │ ├── Input.tsx
│ │ │ ├── Dialog.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ ├── Table.tsx
│ │ │ └── ...
│ │ ├── AppSidebar.tsx # Application sidebar
│ │ ├── Header.tsx # Navigation header
│ │ ├── CreatePollForm.tsx # Poll creation modal
│ │ ├── PollDetails.tsx # Poll detail view
│ │ └── ...
│ ├── context/
│ │ ├── AuthContext.tsx # Authentication state
│ │ └── PollsContext.tsx # Polls state management
│ ├── lib/
│ │ ├── analytics.ts # Analytics tracking utilities
│ │ ├── utils.ts # Helper functions
│ │ └── validations/
│ │ └── auth.ts # Authentication schemas
│ ├── pages/
│ │ ├── Dashboard.tsx # Dashboard overview
│ │ ├── Polls.tsx # Polls management page
│ │ ├── Login.tsx # Login page
│ │ ├── Register.tsx # Registration page
│ │ └── ...
│ ├── App.tsx # App entry point
│ └── main.tsx # React DOM root
├── public/
├── .env.example # Environment variables template
├── package.json
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts
- Node.js 18+ and npm/yarn/pnpm
- Supabase account
- Git
- Clone the repository
git clone https://github.com/yourusername/pollup.git
cd pollup- Install dependencies
npm install
# or
yarn install
# or
pnpm install- Set up environment variables
cp .env.example .envEdit .env with your Supabase credentials:
VITE_SUPABASE_URL=your_supabase_url
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key- Set up Supabase database
Run the following SQL in your Supabase SQL Editor:
-- Create profiles table
CREATE TABLE public.profiles (
id UUID REFERENCES auth.users ON DELETE CASCADE PRIMARY KEY,
email TEXT,
full_name TEXT,
avatar_url TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create polls table
CREATE TABLE public.polls (
id UUID NOT NULL DEFAULT extensions.uuid_generate_v4(),
title TEXT NOT NULL,
description TEXT NULL,
start_at TIMESTAMP WITHOUT TIME ZONE NULL DEFAULT NOW(),
end_at TIMESTAMP WITHOUT TIME ZONE NULL,
created_by UUID NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NULL DEFAULT NOW(),
CONSTRAINT polls_pkey PRIMARY KEY (id),
CONSTRAINT polls_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users (id)
);
-- Create poll_options table
CREATE TABLE public.poll_options (
id UUID NOT NULL DEFAULT extensions.uuid_generate_v4(),
poll_id UUID NOT NULL,
label TEXT NOT NULL,
image_url TEXT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NULL DEFAULT NOW(),
CONSTRAINT poll_options_pkey PRIMARY KEY (id),
CONSTRAINT poll_options_poll_id_fkey FOREIGN KEY (poll_id) REFERENCES polls (id) ON DELETE CASCADE
);
-- Create poll_votes table
CREATE TABLE public.poll_votes (
id UUID NOT NULL DEFAULT extensions.uuid_generate_v4(),
poll_id UUID NOT NULL,
option_id UUID NOT NULL,
device_id TEXT NOT NULL,
user_id UUID NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NULL DEFAULT NOW(),
CONSTRAINT poll_votes_pkey PRIMARY KEY (id),
CONSTRAINT poll_votes_option_id_fkey FOREIGN KEY (option_id) REFERENCES poll_options (id) ON DELETE CASCADE,
CONSTRAINT poll_votes_poll_id_fkey FOREIGN KEY (poll_id) REFERENCES polls (id) ON DELETE CASCADE,
CONSTRAINT poll_votes_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id)
);
-- Create unique index for one vote per device per poll
CREATE UNIQUE INDEX unique_device_vote_per_poll ON public.poll_votes USING btree (poll_id, device_id);
-- Create analytics table
CREATE TABLE public.analytics (
id UUID NOT NULL DEFAULT extensions.uuid_generate_v4(),
event_type TEXT NOT NULL,
poll_id UUID NULL,
option_id UUID NULL,
device_id TEXT NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NULL DEFAULT NOW(),
CONSTRAINT analytics_pkey PRIMARY KEY (id),
CONSTRAINT analytics_option_id_fkey FOREIGN KEY (option_id) REFERENCES poll_options (id),
CONSTRAINT analytics_poll_id_fkey FOREIGN KEY (poll_id) REFERENCES polls (id)
);
-- Enable Row Level Security
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.polls ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.poll_options ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.poll_votes ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.analytics ENABLE ROW LEVEL SECURITY;
-- Create RLS policies
-- Profiles policies
CREATE POLICY "Users can view their own profile" ON public.profiles FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update their own profile" ON public.profiles FOR UPDATE USING (auth.uid() = id);
-- Polls policies
CREATE POLICY "Users can view all polls" ON public.polls FOR SELECT USING (true);
CREATE POLICY "Users can create their own polls" ON public.polls FOR INSERT WITH CHECK (auth.uid() = created_by);
CREATE POLICY "Users can update their own polls" ON public.polls FOR UPDATE USING (auth.uid() = created_by);
CREATE POLICY "Users can delete their own polls" ON public.polls FOR DELETE USING (auth.uid() = created_by);
-- Poll options policies
CREATE POLICY "Anyone can view poll options" ON public.poll_options FOR SELECT USING (true);
CREATE POLICY "Poll creators can insert options" ON public.poll_options FOR INSERT WITH CHECK (
EXISTS (SELECT 1 FROM polls WHERE polls.id = poll_options.poll_id AND polls.created_by = auth.uid())
);
-- Poll votes policies
CREATE POLICY "Anyone can view votes" ON public.poll_votes FOR SELECT USING (true);
CREATE POLICY "Anyone can insert votes" ON public.poll_votes FOR INSERT WITH CHECK (true);
-- Analytics policies
CREATE POLICY "Anyone can insert analytics" ON public.analytics FOR INSERT WITH CHECK (true);
CREATE POLICY "Poll creators can view their poll analytics" ON public.analytics FOR SELECT USING (
EXISTS (SELECT 1 FROM polls WHERE polls.id = analytics.poll_id AND polls.created_by = auth.uid())
);
-- Create trigger for new user profile
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.profiles (id, email, full_name)
VALUES (
NEW.id,
NEW.email,
NEW.raw_user_meta_data->>'full_name'
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION public.handle_new_user();- Configure email redirect URL in Supabase
Go to Authentication > URL Configuration in your Supabase dashboard and add:
Site URL: https://your-deployed-app.vercel.app
Redirect URLs:
- https://your-deployed-app.vercel.app/auth/callback
- http://localhost:5173/auth/callback (for development)
- Start the development server
npm run devVisit http://localhost:5173 to see the app running.
npm run build
npm run preview # Preview production build locally- Push your code to GitHub
- Import project in Vercel
- Add environment variables
- Deploy!
vercel --prodnpm run build
netlify deploy --prod --dir=dist- Click "Create Poll" button
- Fill in poll details:
- Step 1: Title and description
- Step 2: Add 2-10 options
- Step 3: Set start/end dates (optional)
- Click "Create Poll"
- Share the poll link with your audience
- View Results: Click on any poll to see live results
- Share: Use the share button to distribute via social media
- Analytics: Switch to Analytics tab to view detailed insights
- Delete: Remove polls you no longer need
- Open a poll link
- Select your preferred option
- Click "Submit Vote"
- View updated results instantly
- Row Level Security (RLS): Database-level access control
- Device-based voting: Prevents duplicate votes per device
- Secure authentication: Email verification required
- Environment variables: Sensitive data protected
- Input validation: All forms validated with Zod schemas
- Code splitting: Dynamic imports for better load times
- Lazy loading: Components loaded on demand
- Memoization: Context values and computed data cached
- Debounced search: Optimized search functionality
- Optimistic updates: Immediate UI feedback
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Shakirat Akanji
- GitHub: @anikhe00
- LinkedIn: Shakirat Akanji
- Twitter: @i_am_anikhe
- Built as part of the ProDev FE case study
- Design inspiration from modern polling platforms
- Icons by Lucide
- UI components from Shadcn/ui
For support, email akanjishakirato@gmail.com or open an issue in the repository.