updated layout and volunteer

change the ui and now able to remove assigned addresses from volunteer
This commit is contained in:
Mann Patel
2025-08-28 00:15:10 -06:00
parent d7f6c794b4
commit 9dd24e71e7
20 changed files with 302 additions and 536 deletions

View File

@@ -34,6 +34,7 @@ type PageNumber struct {
// AddressWithDetails extends AddressDatabase with appointment and user info
type AddressWithDetails struct {
models.AddressDatabase
UserID *int
UserName string
UserEmail string
AppointmentDate string
@@ -97,6 +98,7 @@ func AddressHandler(w http.ResponseWriter, r *http.Request) {
WHEN ap.sched_id IS NOT NULL THEN true
ELSE false
END as assigned,
ap.user_id,
COALESCE(u.first_name || ' ' || u.last_name, '') as user_name,
COALESCE(u.email, '') as user_email,
COALESCE(ap.appointment_date::text, '') as appointment_date,
@@ -133,6 +135,7 @@ func AddressHandler(w http.ResponseWriter, r *http.Request) {
&a.CreatedAt,
&a.UpdatedAt,
&a.Assigned,
&a.UserID,
&a.UserName,
&a.UserEmail,
&a.AppointmentDate,
@@ -325,3 +328,65 @@ func AssignAddressHandler(w http.ResponseWriter, r *http.Request) {
// Redirect back to addresses page with success
http.Redirect(w, r, "/addresses?success=assigned", http.StatusSeeOther)
}
func RemoveAssignedAddressHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/addresses", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Invalid form", http.StatusBadRequest)
return
}
userIDStr := r.FormValue("user_id")
addressIDStr := r.FormValue("address_id")
if userIDStr == "" || addressIDStr == "" {
http.Error(w, "User ID and Address ID are required", http.StatusBadRequest)
return
}
userID, err := strconv.Atoi(userIDStr)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
addressID, err := strconv.Atoi(addressIDStr)
if err != nil {
http.Error(w, "Invalid address ID", http.StatusBadRequest)
return
}
// Verify the user is managed by current admin
currentAdminID := r.Context().Value("user_id").(int)
var userExists int
err = models.DB.QueryRow(`
SELECT COUNT(*)
FROM admin_volunteers av
JOIN appointment ap ON av.volunteer_id = ap.user_id
WHERE av.admin_id = $1 AND ap.user_id = $2 AND ap.address_id = $3
`, currentAdminID, userID, addressID).Scan(&userExists)
if err != nil {
log.Println("Verification error:", err)
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
if userExists == 0 {
http.Error(w, "Unauthorized removal", http.StatusForbidden)
return
}
// Remove volunteer assignment
_, err = models.DB.Exec(`DELETE FROM appointment WHERE user_id = $1 AND address_id = $2`, userID, addressID)
if err != nil {
log.Println("Remove assignment error:", err)
http.Error(w, "Failed to remove assignment", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/addresses?success=removed", http.StatusSeeOther)
}

View File

@@ -1,83 +0,0 @@
package handlers
import (
"log"
"net/http"
"github.com/patel-mann/poll-system/app/internal/models"
"github.com/patel-mann/poll-system/app/internal/utils"
)
type AssignedAddress struct {
AddressID int
Address string
StreetName string
StreetType string
StreetQuadrant string
HouseNumber string
HouseAlpha *string
Longitude float64
Latitude float64
VisitedValidated bool
CreatedAt string
UpdatedAt string
Assigned bool
UserName string
UserEmail string
UserPhone string
AppointmentDate *string
AppointmentTime *string
}
func AssignedAddressesHandler(w http.ResponseWriter, r *http.Request) {
username,_ := models.GetCurrentUserName(r)
rows, err := models.DB.Query(`
SELECT
a.address_id, a.address, a.street_name, a.street_type, a.street_quadrant,
a.house_number, a.house_alpha, a.longitude, a.latitude, a.visited_validated,
a.created_at, a.updated_at,
CASE WHEN ap.user_id IS NOT NULL THEN true ELSE false END as assigned,
COALESCE(u.first_name || ' ' || u.last_name, '') as user_name,
COALESCE(u.email, '') as user_email,
COALESCE(u.phone, '') as user_phone,
TO_CHAR(ap.appointment_date, 'YYYY-MM-DD') as appointment_date,
TO_CHAR(ap.appointment_time, 'HH24:MI') as appointment_time
FROM address_database a
LEFT JOIN appointment ap ON a.address_id = ap.address_id
LEFT JOIN users u ON ap.user_id = u.user_id
ORDER BY a.address_id;
`)
if err != nil {
log.Printf("query error: %v", err)
http.Error(w, "query error", http.StatusInternalServerError)
return
}
defer rows.Close()
var assignedAddresses []AssignedAddress
for rows.Next() {
var addr AssignedAddress
err := rows.Scan(
&addr.AddressID, &addr.Address, &addr.StreetName, &addr.StreetType, &addr.StreetQuadrant,
&addr.HouseNumber, &addr.HouseAlpha, &addr.Longitude, &addr.Latitude, &addr.VisitedValidated,
&addr.CreatedAt, &addr.UpdatedAt, &addr.Assigned, &addr.UserName, &addr.UserEmail,
&addr.UserPhone, &addr.AppointmentDate, &addr.AppointmentTime,
)
if err != nil {
log.Printf("scan error: %v", err)
continue
}
assignedAddresses = append(assignedAddresses, addr)
}
utils.Render(w, "address_assigned.html", map[string]interface{}{
"Title": "Assigned Addresses",
"IsAuthenticated": true,
"AssignedList": assignedAddresses,
"ShowAdminNav": true,
"Role": "admin",
"UserName": username,
"ActiveSection": "assigned",
})
}

View File

@@ -51,6 +51,8 @@ func VolunteerHandler(w http.ResponseWriter, r *http.Request) {
}
func EditVolunteerHandler(w http.ResponseWriter, r *http.Request) {
username,_ := models.GetCurrentUserName(r)
if r.Method == http.MethodGet {
volunteerID := r.URL.Query().Get("id")
var user models.User
@@ -69,6 +71,7 @@ func EditVolunteerHandler(w http.ResponseWriter, r *http.Request) {
"Title": "Edit Volunteer",
"IsAuthenticated": true,
"ShowAdminNav": true,
"UserName": username,
"Volunteer": user,
"ActiveSection": "volunteer",
})

View File

@@ -39,7 +39,7 @@ type User struct {
Phone string
Password string
RoleID int
AdminCode string
AdminCode *string
CreatedAt time.Time
UpdatedAt time.Time
}

View File

@@ -104,6 +104,7 @@
<th class="px-6 py-3 whitespace-nowrap">Assigned User</th>
<th class="px-6 py-3 whitespace-nowrap">Appointment</th>
<th class="px-6 py-3 whitespace-nowrap">Assign</th>
<th class="px-6 py-3 whitespace-nowrap">Remove</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
@@ -163,6 +164,26 @@
</button>
{{ end }}
</td>
<td class="px-6 py-3 whitespace-nowrap">
{{ if .Assigned }}
<form
action="/remove_assigned_address"
method="POST"
class="inline-block"
>
<input type="hidden" name="address_id" value="{{ .AddressID }}" />
<input type="hidden" name="user_id" value="{{ .UserID }}" />
<button
type="submit"
class="text-red-600 hover:text-red-800 font-medium text-xs px-2 py-1 hover:bg-red-50 transition-colors"
>
Delete
</button>
</form>
{{ else }}
<span class="text-gray-400 text-xs">-</span>
{{ end }}
</td>
</tr>
{{ else }}
<tr>

View File

@@ -4,6 +4,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="../../static/favicon.ico">
<title>{{if .Title}}{{.Title}}{{else}}Poll System{{end}}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="//unpkg.com/alpinejs" defer></script>
@@ -42,8 +44,8 @@
<div class="hidden lg:flex bg-gray-100 px-4 py-3 items-center justify-between border-b border-gray-200">
<!-- Left Side: Logo + Title -->
<div class="flex items-center gap-2">
<div class="w-6 h-6 bg-orange-500 rounded-full text-white text-xs flex items-center justify-center font-bold">
L
<div class="w-6 h-6 flex items-center justify-center">
<img src="../../static/icon-512.png" alt="Logo" class="w-6 h-6"/>
</div>
<span class="text-sm font-medium">Poll System</span>
</div>
@@ -178,62 +180,66 @@
</div>
{{else}}
<!-- Landing Page -->
<div class="min-h-screen bg-gradient-to-br from-slate-50 to-gray-100" x-data="{ mobileMenuOpen: false }">
<!-- Fixed Navigation -->
<nav class="fixed top-0 w-full bg-white/90 backdrop-blur-md shadow-sm border-b border-gray-200 z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Linq - Poll System</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body class="bg-gray-50">
<div class="min-h-screen" x-data="{ mobileMenuOpen: false }">
<!-- Navigation -->
<nav class="bg-white border-b border-gray-200">
<div class="max-w-7xl mx-auto px-6">
<div class="flex justify-between items-center h-16">
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-blue-600 text-white text-sm flex items-center justify-center font-bold">
L
</div>
<span class="text-xl font-semibold text-gray-900">Poll System</span>
<!-- Logo -->
<div class="flex items-center gap-3">
<img src="../../static/icon-512.png" alt="Logo" class="w-8 h-8"/>
<span class="text-xl font-semibold text-gray-900">Linq</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center gap-6">
<a href="#home" class="text-gray-600 hover:text-gray-900 font-medium transition-colors">Home</a>
<a href="#features" class="text-gray-600 hover:text-gray-900 font-medium transition-colors">Features</a>
<a href="#about" class="text-gray-600 hover:text-gray-900 font-medium transition-colors">About</a>
<div class="hidden md:flex items-center gap-8">
<a href="#home" class="text-gray-600 hover:text-gray-900">Home</a>
<a href="#products" class="text-gray-600 hover:text-gray-900">Products</a>
<a href="#about" class="text-gray-600 hover:text-gray-900">About</a>
</div>
<!-- Desktop Auth Buttons -->
<!-- Auth Buttons -->
<div class="hidden md:flex items-center gap-3">
<button onclick="openLoginModal()" class="px-4 py-2 text-gray-600 hover:text-gray-900 font-medium transition-colors">
<button onclick="openLoginModal()" class="px-4 py-2 text-gray-600 hover:text-gray-900">
Sign In
</button>
<button onclick="openRegisterModal()" class="px-4 py-2 bg-blue-600 text-white hover:bg-blue-700 font-medium transition-colors rounded">
<button onclick="openRegisterModal()" class="px-6 py-2 bg-blue-600 text-white hover:bg-blue-700 rounded">
Get Started
</button>
</div>
<!-- Mobile Menu Button -->
<div class="md:hidden">
<button @click="mobileMenuOpen = !mobileMenuOpen" class="p-2 text-gray-600 hover:text-gray-900">
<i class="fas fa-bars text-xl" x-show="!mobileMenuOpen"></i>
<i class="fas fa-times text-xl" x-show="mobileMenuOpen"></i>
<button @click="mobileMenuOpen = !mobileMenuOpen" class="md:hidden p-2">
<i class="fas fa-bars text-gray-600" x-show="!mobileMenuOpen"></i>
<i class="fas fa-times text-gray-600" x-show="mobileMenuOpen"></i>
</button>
</div>
</div>
<!-- Mobile Menu -->
<div x-show="mobileMenuOpen"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="md:hidden bg-white border-t border-gray-200">
<div class="px-4 py-4 space-y-3">
<a href="#home" @click="mobileMenuOpen = false" class="block text-gray-600 hover:text-gray-900 font-medium py-2">Home</a>
<a href="#features" @click="mobileMenuOpen = false" class="block text-gray-600 hover:text-gray-900 font-medium py-2">Features</a>
<a href="#about" @click="mobileMenuOpen = false" class="block text-gray-600 hover:text-gray-900 font-medium py-2">About</a>
<div x-show="mobileMenuOpen" class="md:hidden border-t border-gray-200 bg-white">
<div class="px-6 py-4 space-y-3">
<a href="#home" @click="mobileMenuOpen = false" class="block py-2 text-gray-600">Home</a>
<a href="#products" @click="mobileMenuOpen = false" class="block py-2 text-gray-600">Products</a>
<a href="#about" @click="mobileMenuOpen = false" class="block py-2 text-gray-600">About</a>
<div class="border-t border-gray-200 pt-3 space-y-2">
<button onclick="openLoginModal(); document.querySelector('[x-data]').__x.$data.mobileMenuOpen = false" class="block w-full text-left px-4 py-2 text-gray-600 hover:bg-gray-100 rounded font-medium">
<button onclick="openLoginModal(); document.querySelector('[x-data]').__x.$data.mobileMenuOpen = false"
class="block w-full text-left py-2 text-gray-600">
Sign In
</button>
<button onclick="openRegisterModal(); document.querySelector('[x-data]').__x.$data.mobileMenuOpen = false" class="block w-full px-4 py-2 bg-blue-600 text-white hover:bg-blue-700 font-medium rounded">
<button onclick="openRegisterModal(); document.querySelector('[x-data]').__x.$data.mobileMenuOpen = false"
class="block w-full py-2 bg-blue-600 text-white rounded">
Get Started
</button>
</div>
@@ -242,184 +248,145 @@
</div>
</nav>
<!-- Hero Section -->
<section id="home" class="max-w-4xl mx-auto h-[600px] px-4 pt-[200px] pb-32 text-center">
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 mb-6 leading-tight">
Streamline Your<br>
<span class="text-blue-600">Polling Operations</span>
<!-- Hero -->
<section id="home" class="pt-20 pb-16 px-6">
<div class="max-w-4xl mx-auto text-center">
<h1 class="text-4xl sm:text-5xl font-bold text-gray-900 mb-6">
Simple Polling
<span class="text-blue-600">Management</span>
</h1>
<p class="text-lg sm:text-xl text-gray-600 mb-8 max-w-2xl mx-auto leading-relaxed">
Manage volunteers, organize addresses, and track progress with our comprehensive polling system.
<p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
Manage volunteers and track polling operations efficiently.
</p>
<div class="flex flex-col sm:flex-row justify-center gap-4">
<button onclick="openRegisterModal()" class="px-8 py-3 bg-blue-600 text-white hover:bg-blue-700 font-semibold transition-colors rounded">
Start Now
<button onclick="openRegisterModal()" class="px-8 py-3 bg-blue-600 text-white hover:bg-blue-700 font-medium rounded">
Start Free
</button>
<button onclick="openLoginModal()" class="px-8 py-3 border border-gray-300 text-gray-700 hover:bg-gray-50 font-semibold transition-colors rounded">
<button onclick="openLoginModal()" class="px-8 py-3 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium rounded">
Sign In
</button>
</div>
</div>
</section>
<!-- Features Section -->
<section id="features" class="max-w-6xl mx-auto px-4 py-20">
<div class="text-center mb-16">
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-4">Powerful Features</h2>
<p class="text-lg sm:text-xl text-gray-600 max-w-3xl mx-auto">Everything you need to manage your polling operations efficiently and effectively.</p>
<!-- Products Section -->
<section id="products" class="py-16 bg-white">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-gray-900 mb-4">Our Products</h2>
<p class="text-lg text-gray-600">Tools built for modern polling operations</p>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
<div class="bg-white p-8 shadow-sm border border-gray-200 hover:shadow-md transition-shadow rounded-lg">
<div class="w-12 h-12 bg-blue-100 rounded flex items-center justify-center mb-4">
<i class="fas fa-users text-blue-600 text-xl"></i>
<div class="grid md:grid-cols-3 gap-8">
<div class="p-6 border border-gray-200 rounded-lg">
<img src="../../static/icon-512.png" alt="Poll Manager" class="w-12 h-12 mb-4"/>
<h3 class="text-xl font-semibold text-gray-900 mb-3">Poll Manager</h3>
<p class="text-gray-600 mb-4">Complete polling campaign management with real-time tracking and coordination tools.</p>
<ul class="text-sm text-gray-500 space-y-1">
<li>• Volunteer coordination</li>
<li>• Address management</li>
<li>• Progress tracking</li>
</ul>
</div>
<h3 class="text-xl font-semibold mb-3">Volunteer Management</h3>
<p class="text-gray-600">Organize and coordinate your volunteer teams efficiently with role-based access and scheduling.</p>
<div class="p-6 border border-gray-200 rounded-lg">
<img src="../../static/icon-512.png" alt="Analytics Suite" class="w-12 h-12 mb-4"/>
<h3 class="text-xl font-semibold text-gray-900 mb-3">Analytics Suite</h3>
<p class="text-gray-600 mb-4">Advanced reporting and analytics dashboard for data-driven insights.</p>
<ul class="text-sm text-gray-500 space-y-1">
<li>• Performance metrics</li>
<li>• Custom reports</li>
<li>• Data visualization</li>
</ul>
</div>
<div class="bg-white p-8 shadow-sm border border-gray-200 hover:shadow-md transition-shadow rounded-lg">
<div class="w-12 h-12 bg-green-100 rounded flex items-center justify-center mb-4">
<i class="fas fa-map-marker-alt text-green-600 text-xl"></i>
<div class="p-6 border border-gray-200 rounded-lg">
<img src="../../static/icon-512.png" alt="Team Builder" class="w-12 h-12 mb-4"/>
<h3 class="text-xl font-semibold text-gray-900 mb-3">Team Builder</h3>
<p class="text-gray-600 mb-4">Organize teams and assign roles with automated scheduling and notifications.</p>
<ul class="text-sm text-gray-500 space-y-1">
<li>• Role assignment</li>
<li>• Schedule management</li>
<li>• Team communication</li>
</ul>
</div>
<h3 class="text-xl font-semibold mb-3">Address Tracking</h3>
<p class="text-gray-600">Keep track of all polling locations and assignments with real-time updates and mapping.</p>
</div>
<div class="bg-white p-8 shadow-sm border border-gray-200 hover:shadow-md transition-shadow rounded-lg sm:col-span-2 lg:col-span-1">
<div class="w-12 h-12 bg-purple-100 rounded flex items-center justify-center mb-4">
<i class="fas fa-chart-bar text-purple-600 text-xl"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Real-time Reports</h3>
<p class="text-gray-600">Monitor progress with comprehensive analytics and detailed reporting dashboards.</p>
</div>
</div>
</section>
<!-- About Section -->
<section id="about" class="bg-white py-20">
<div class="max-w-6xl mx-auto px-4">
<!-- About -->
<section id="about" class="py-16 bg-gray-50">
<div class="max-w-6xl mx-auto px-6">
<div class="grid lg:grid-cols-2 gap-12 items-center">
<div>
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 mb-6">About Poll System</h2>
<p class="text-base sm:text-lg text-gray-600 mb-6">
Poll System was created to simplify and streamline the complex process of managing polling operations.
Our platform brings together volunteers, administrators, and team leaders in one unified system.
<h2 class="text-3xl font-bold text-gray-900 mb-6">About Linq</h2>
<p class="text-lg text-gray-600 mb-6">
Built for organizations that need efficient polling management.
Our platform simplifies volunteer coordination and progress tracking.
</p>
<p class="text-base sm:text-lg text-gray-600 mb-8">
With years of experience in civic technology, we understand the challenges faced by polling organizations.
Our solution provides the tools needed to coordinate effectively and ensure smooth operations.
</p>
<div class="space-y-4">
<div class="flex items-center gap-3">
<div class="w-6 h-6 bg-green-100 rounded flex items-center justify-center flex-shrink-0">
<i class="fas fa-check text-green-600 text-sm"></i>
</div>
<span class="text-gray-700">Streamlined volunteer coordination</span>
</div>
<div class="flex items-center gap-3">
<div class="w-6 h-6 bg-green-100 rounded flex items-center justify-center flex-shrink-0">
<i class="fas fa-check text-green-600 text-sm"></i>
</div>
<span class="text-gray-700">Real-time progress tracking</span>
</div>
<div class="flex items-center gap-3">
<div class="w-6 h-6 bg-green-100 rounded flex items-center justify-center flex-shrink-0">
<i class="fas fa-check text-green-600 text-sm"></i>
</div>
<span class="text-gray-700">Comprehensive reporting tools</span>
</div>
</div>
</div>
<div class="relative">
<div class="bg-gradient-to-br from-blue-500 to-blue-700 p-8 text-white rounded-lg">
<div class="grid grid-cols-3 gap-6 mb-8">
<div class="text-center">
<i class="fas fa-users text-6xl mb-6 opacity-20"></i>
<h3 class="text-2xl font-bold mb-4">Trusted by Organizations</h3>
<p class="text-lg opacity-90 mb-6">
Join hundreds of organizations already using Poll System to manage their operations efficiently.
<div class="text-2xl font-bold text-gray-900">500+</div>
<div class="text-sm text-gray-500">Volunteers</div>
</div>
<!-- <div class="text-center">
<div class="text-2xl font-bold text-gray-900">50+</div>
<div class="text-sm text-gray-500">Organizations</div>
</div> -->
<div class="text-center">
<div class="text-2xl font-bold text-gray-900">400,000+</div>
<div class="text-sm text-gray-500">Addresses</div>
</div>
</div>
</div>
<div class="bg-white p-8 rounded-lg border border-gray-200">
<img src="../../static/feature-mobile4.jpg" alt="Dashboard Preview" class="w-full h-48 object-cover rounded mb-6 bg-gray-100"/>
<h3 class="text-xl font-semibold text-gray-900 mb-3">Simple Dashboard</h3>
<p class="text-gray-600">
Everything you need in one place. Track progress, manage teams, and generate reports.
</p>
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<div class="text-2xl font-bold">500+</div>
<div class="text-sm opacity-80">Volunteers</div>
</div>
<div>
<div class="text-2xl font-bold">50+</div>
<div class="text-sm opacity-80">Organizations</div>
</div>
<div>
<div class="text-2xl font-bold">1000+</div>
<div class="text-sm opacity-80">Addresses</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="bg-gray-900 text-white py-12">
<div class="max-w-6xl mx-auto px-4">
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-8">
<div class="sm:col-span-2 lg:col-span-2">
<div class="flex items-center gap-2 mb-4">
<div class="w-8 h-8 bg-blue-600 text-white text-sm flex items-center justify-center font-bold rounded">
L
<footer class="bg-white border-t border-gray-200 py-8">
<div class="max-w-6xl mx-auto px-6">
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
<div class="flex items-center gap-3">
<img src="../../static/icon-512.png" alt="Logo" class="w-6 h-6"/>
<span class="font-semibold text-gray-900">Linq</span>
</div>
<span class="text-xl font-semibold">Poll System</span>
<div class="flex items-center gap-6 text-sm text-gray-500">
<a href="#" class="hover:text-gray-900">Privacy</a>
<a href="#" class="hover:text-gray-900">Terms</a>
<a href="#" class="hover:text-gray-900">Contact</a>
</div>
<p class="text-gray-400 mb-4 max-w-md">
Streamlining polling operations with comprehensive volunteer management,
address tracking, and real-time reporting capabilities.
</p>
<div class="flex gap-4">
<a href="#" class="w-10 h-10 bg-gray-800 rounded flex items-center justify-center hover:bg-blue-600 transition-colors">
<i class="fab fa-twitter"></i>
</a>
<a href="#" class="w-10 h-10 bg-gray-800 rounded flex items-center justify-center hover:bg-blue-600 transition-colors">
<i class="fab fa-linkedin"></i>
</a>
<a href="#" class="w-10 h-10 bg-gray-800 rounded flex items-center justify-center hover:bg-blue-600 transition-colors">
<i class="fab fa-github"></i>
</a>
</div>
</div>
<div>
<h4 class="font-semibold mb-4">Platform</h4>
<ul class="space-y-2 text-gray-400">
<li><a href="#" class="hover:text-white transition-colors">Dashboard</a></li>
<li><a href="#" class="hover:text-white transition-colors">Volunteers</a></li>
<li><a href="#" class="hover:text-white transition-colors">Addresses</a></li>
<li><a href="#" class="hover:text-white transition-colors">Reports</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold mb-4">Support</h4>
<ul class="space-y-2 text-gray-400">
<li><a href="#" class="hover:text-white transition-colors">Help Center</a></li>
<li><a href="#" class="hover:text-white transition-colors">Contact Us</a></li>
<li><a href="#" class="hover:text-white transition-colors">Privacy Policy</a></li>
<li><a href="#" class="hover:text-white transition-colors">Terms of Service</a></li>
</ul>
</div>
</div>
<div class="border-t border-gray-800 mt-8 pt-8 text-center text-gray-400">
<p>&copy; 2025 Poll System. All rights reserved.</p>
<p class="text-sm text-gray-500">&copy; 2025 Linq. All rights reserved.</p>
</div>
</div>
</footer>
</div>
<!-- Login Modal -->
<div id="loginModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50 p-4">
<div class="bg-white shadow-2xl max-w-4xl w-full overflow-hidden rounded-lg">
<div class="flex flex-col lg:flex-row min-h-[500px]">
<!-- Left Side - Image -->
<div class="flex-1 bg-gradient-to-br from-blue-500 to-blue-700 flex items-center justify-center p-8">
<div class="text-center text-white">
<div class="hidden lg:flex flex-1 bg-gradient-to-br from-blue-500 to-blue-700 flex-col items-center justify-center">
<!-- <div class="text-center text-white">
<i class="fas fa-chart-line text-4xl sm:text-6xl mb-6"></i>
<h2 class="text-2xl sm:text-3xl font-bold mb-4">Welcome Back</h2>
<p class="text-base sm:text-lg opacity-90">Continue managing your polling operations</p>
</div>
</div> -->
<img src="../../static/feature-mobile2.jpg" alt="Welcome Image" class="object-cover h-full rounded-lg shadow-lg">
</div>
<!-- Right Side - Form -->
<div class="flex-1 p-6 sm:p-8">
@@ -458,13 +425,15 @@
<div class="bg-white shadow-2xl max-w-4xl w-full overflow-hidden rounded-lg">
<div class="flex flex-col lg:flex-row min-h-[600px]">
<!-- Left Side - Image -->
<div class="flex-1 bg-gradient-to-br from-blue-600 to-blue-800 flex items-center justify-center p-8">
<div class="text-center text-white">
<div class="hidden lg:flex flex-1 bg-gradient-to-br from-blue-600 to-blue-800 flex-col items-center justify-center">
<!-- <div class="text-center text-white">
<i class="fas fa-rocket text-4xl sm:text-6xl mb-6"></i>
<h2 class="text-2xl sm:text-3xl font-bold mb-4">Get Started</h2>
<p class="text-base sm:text-lg opacity-90">Join our platform and streamline your operations</p>
</div> -->
<img src="../../static/feature-mobile1.jpg" alt="Welcome Image" class="object-cover h-full rounded-lg shadow-lg">
</div>
</div>
<!-- Right Side - Form -->
<div class="flex-1 p-6 sm:p-8 overflow-y-auto">
<div class="flex justify-between items-center mb-6">
@@ -533,7 +502,6 @@
</div>
</div>
</div>
</div>
{{end}}
<script>

View File

@@ -134,34 +134,9 @@
</div>
{{end}}
<!-- Post Actions -->
<div class="px-6 py-3">
<div class="flex items-center space-x-6">
<!-- Like Button -->
<button
class="reaction-btn flex items-center space-x-2 text-gray-600 hover:text-blue-500 transition-colors"
data-post-id="{{.PostID}}"
data-reaction="like"
>
<i class="fa-solid fa-thumbs-up text-lg"></i>
<span class="text-sm font-medium like-count">0</span>
</button>
<!-- Dislike Button -->
<button
class="reaction-btn flex items-center space-x-2 text-gray-600 hover:text-red-500 transition-colors"
data-post-id="{{.PostID}}"
data-reaction="dislike"
>
<i class="fa-solid fa-thumbs-down text-lg"></i>
<span class="text-sm font-medium dislike-count">0</span>
</button>
</div>
</div>
<!-- Post Content -->
{{if .Content}}
<div class="px-6 pb-4">
<div class="px-6 pt-2 pb-4">
<p class="text-gray-900 leading-relaxed">
<span class="font-semibold">{{.AuthorName}}</span> {{.Content}}
</p>

View File

@@ -24,21 +24,26 @@
</h3>
<p class="text-gray-600">{{ .User.Email }}</p>
<div class="flex items-center mt-2 space-x-4">
<span class="text-gray-600">User Role:</span>
<span
class="inline-flex items-center px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 border border-blue-200"
>
<i class="fas fa-user-check mr-1"></i>
Active User
</span>
<span class="text-gray-600">Signup Code:</span>
<span class="font-mono text-gray-900">{{ .User.AdminCode }}</span>
<span class="text-gray-600">User ID:</span>
<span class="font-mono text-gray-900">{{ .User.UserID }}</span>
<span class="text-gray-600">Role:</span>
<span class="text-gray-900">
{{ if eq .User.RoleID 1 }}Admin {{ else if eq .User.RoleID 2
}}Team Leader {{ else }}Volunteer {{ end }}
</span>
<span class="text-gray-600">User ID:</span>
<span
class="inline-flex items-center px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 border border-blue-200"
>
<i class="fas fa-user-check mr-1"></i>
{{ .User.UserID }}</span
>
{{if eq .User.RoleID 1}}
<span class="text-gray-600">Signup Code:</span>
<span class="font-mono text-gray-900">{{.User.AdminCode }} </span>
{{end}}
</div>
</div>
</div>
@@ -152,99 +157,6 @@
</div>
</form>
</div>
<!-- Configuration Settings Section -->
<div class="border-t border-gray-200 pt-8 mt-8">
<h3 class="text-lg font-semibold text-gray-900 mb-6 flex items-center">
<i class="fas fa-cog text-blue-600 mr-3"></i>
Configuration Settings
</h3>
<form method="post" action="/profile/settings" id="settingsForm">
<div class="space-y-6">
<!-- Add New Setting -->
<div class="bg-white border border-gray-200 p-6">
<h4 class="text-md font-semibold text-gray-800 mb-4">
Add New Setting
</h4>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">
Setting Name
</label>
<input
type="text"
id="newSettingName"
class="w-full px-4 py-3 border border-gray-300 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-white"
placeholder="Enter setting name"
/>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">
Setting Value
</label>
<input
type="text"
id="newSettingValue"
class="w-full px-4 py-3 border border-gray-300 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-white"
placeholder="Enter setting value"
/>
</div>
<div class="flex items-end">
<button
type="button"
onclick="addSetting()"
class="w-full px-6 py-3 bg-green-600 text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 font-medium"
>
<i class="fas fa-plus mr-2"></i>
Add Setting
</button>
</div>
</div>
</div>
<!-- Current Settings -->
<div class="bg-white border border-gray-200 p-6">
<h4 class="text-md font-semibold text-gray-800 mb-4">
Current Settings
</h4>
<div id="settingsList" class="space-y-3">
<!-- Settings will be dynamically added here -->
<div class="text-gray-500 text-sm" id="noSettingsMessage">
No settings configured yet. Add your first setting above.
</div>
</div>
</div>
</div>
<!-- Settings Form Actions -->
<div
class="mt-8 pt-6 border-t border-gray-200 flex justify-between items-center"
>
<div class="flex items-center text-sm text-gray-500">
<i class="fas fa-info-circle text-blue-500 mr-2"></i>
Settings are applied immediately when added or removed
</div>
<div class="flex space-x-3">
<button
type="button"
onclick="clearAllSettings()"
class="px-6 py-2 border border-red-300 text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 font-medium"
>
<i class="fas fa-trash mr-2"></i>
Clear All
</button>
<button
type="submit"
class="px-6 py-2 bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 font-medium"
>
<i class="fas fa-save mr-2"></i>
Save Settings
</button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@@ -27,19 +27,22 @@
</h3>
<p class="text-gray-600">{{ .Volunteer.Email }}</p>
<div class="flex items-center mt-2 space-x-4">
<span class="text-gray-600">User Role:</span>
<span
class="inline-flex items-center px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 border border-blue-200"
>
<i class="fas fa-user-check mr-1"></i>
Volunteer
</span>
<span class="text-gray-600">User ID:</span>
<span class="font-mono text-gray-900">{{ .Volunteer.UserID }}</span>
<span class="text-gray-600">Current Role:</span>
<span class="text-gray-900">
{{ if eq .Volunteer.RoleID 2 }}Team Leader{{ else }}Volunteer{{
end }}
</span>
<span class="text-gray-600">User ID:</span>
<span
class="inline-flex items-center px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 border border-blue-200"
>
<i class="fas fa-user-check mr-1"></i>
{{ .Volunteer.UserID }}</span
>
</div>
</div>
</div>
@@ -107,6 +110,7 @@
<label class="block text-sm font-semibold text-gray-700 mb-2">
Phone Number
</label>
r
<input
type="text"
name="phone"
@@ -127,13 +131,13 @@
required
class="w-full px-4 py-3 border border-gray-300 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-white"
>
<option value="">--Select Role--</option>
<option value="3" {{if eq .Volunteer.RoleID 3}}selected{{end}}>
Volunteer
</option>
<option value="" disabled selected hidden>Select Role</option>
<option value="2" {{if eq .Volunteer.RoleID 2}}selected{{end}}>
Team Leader
</option>
<option value="3" {{if eq .Volunteer.RoleID 3}}selected{{end}}>
Volunteer
</option>
</select>
<p class="text-xs text-gray-500 mt-1">
Team Leaders can manage volunteers and access additional features
@@ -169,107 +173,6 @@
</div>
</form>
</div>
<!-- Configuration Settings Section -->
<div class="border-t border-gray-200 pt-8 mt-8">
<h3 class="text-lg font-semibold text-gray-900 mb-6 flex items-center">
<i class="fas fa-cog text-blue-600 mr-3"></i>
Volunteer Settings
</h3>
<form
method="post"
action="/volunteer/settings"
id="volunteerSettingsForm"
>
<div class="space-y-6">
<!-- Add New Setting -->
<div class="bg-white border border-gray-200 p-6">
<h4 class="text-md font-semibold text-gray-800 mb-4">
Add New Setting
</h4>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">
Setting Name
</label>
<input
type="text"
id="newVolunteerSettingName"
class="w-full px-4 py-3 border border-gray-300 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-white"
placeholder="Enter setting name"
/>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">
Setting Value
</label>
<input
type="text"
id="newVolunteerSettingValue"
class="w-full px-4 py-3 border border-gray-300 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-white"
placeholder="Enter setting value"
/>
</div>
<div class="flex items-end">
<button
type="button"
onclick="addVolunteerSetting()"
class="w-full px-6 py-3 bg-green-600 text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 font-medium"
>
<i class="fas fa-plus mr-2"></i>
Add Setting
</button>
</div>
</div>
</div>
<!-- Current Settings -->
<div class="bg-white border border-gray-200 p-6">
<h4 class="text-md font-semibold text-gray-800 mb-4">
Current Settings
</h4>
<div id="volunteerSettingsList" class="space-y-3">
<!-- Settings will be dynamically added here -->
<div
class="text-gray-500 text-sm"
id="noVolunteerSettingsMessage"
>
No settings configured for this volunteer yet. Add settings
above.
</div>
</div>
</div>
</div>
<!-- Settings Form Actions -->
<div
class="mt-8 pt-6 border-t border-gray-200 flex justify-between items-center"
>
<div class="flex items-center text-sm text-gray-500">
<i class="fas fa-info-circle text-blue-500 mr-2"></i>
Settings are specific to this volunteer and applied immediately
</div>
<div class="flex space-x-3">
<button
type="button"
onclick="clearAllVolunteerSettings()"
class="px-6 py-2 border border-red-300 text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 font-medium"
>
<i class="fas fa-trash mr-2"></i>
Clear All
</button>
<button
type="submit"
class="px-6 py-2 bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 font-medium"
>
<i class="fas fa-save mr-2"></i>
Save Settings
</button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@@ -185,6 +185,8 @@ func main() {
http.HandleFunc("/addresses", adminMiddleware(handlers.AddressHandler))
http.HandleFunc("/assign_address", adminMiddleware(handlers.AssignAddressHandler))
http.HandleFunc("/remove_assigned_address", adminMiddleware(handlers.RemoveAssignedAddressHandler))
http.HandleFunc("/posts", adminMiddleware(handlers.PostsHandler))

BIN
app/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

BIN
app/static/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB