feat: Improve user experience after voting on proposals

- Redirect users to the proposal detail page with a success
  message after a successful vote, improving feedback.
- Automatically remove the success message from the URL after a
  short time to avoid URL clutter and maintain a clean browsing
  experience.
- Add a success alert message on the proposal detail page to
  provide immediate visual confirmation of a successful vote.
- Improve the visual presentation of the votes list on the
  proposal detail page by adding top margin for better spacing.
This commit is contained in:
Mahmoud-Emad 2025-05-22 17:05:26 +03:00
parent 52fbc77e3e
commit 3d8aca19cc
2 changed files with 31 additions and 13 deletions

View File

@ -196,9 +196,13 @@ impl GovernanceController {
/// Handles the proposal detail page route /// Handles the proposal detail page route
pub async fn proposal_detail( pub async fn proposal_detail(
path: web::Path<String>, path: web::Path<String>,
req: actix_web::HttpRequest,
tmpl: web::Data<Tera>, tmpl: web::Data<Tera>,
session: Session, session: Session,
) -> Result<impl Responder> { ) -> Result<impl Responder> {
// Extract query parameters from the request
let query_str = req.query_string();
let vote_success = query_str.contains("vote_success=true");
let proposal_id = path.into_inner(); let proposal_id = path.into_inner();
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
ctx.insert("active_page", "governance"); ctx.insert("active_page", "governance");
@ -221,6 +225,11 @@ impl GovernanceController {
let results = Self::calculate_voting_results_from_proposal(&proposal); let results = Self::calculate_voting_results_from_proposal(&proposal);
ctx.insert("results", &results); ctx.insert("results", &results);
// Check if vote_success parameter is present and add success message
if vote_success {
ctx.insert("success", "Your vote has been successfully recorded!");
}
render_template(&tmpl, "governance/proposal_detail.html", &ctx) render_template(&tmpl, "governance/proposal_detail.html", &ctx)
} else { } else {
// Proposal not found // Proposal not found
@ -392,18 +401,10 @@ impl GovernanceController {
form.comment.as_ref().map(|s| s.to_string()), // Pass the comment from the form form.comment.as_ref().map(|s| s.to_string()), // Pass the comment from the form
) { ) {
Ok(updated_proposal) => { Ok(updated_proposal) => {
ctx.insert("proposal", &updated_proposal); // Redirect to the proposal detail page with a success message
ctx.insert("success", "Your vote has been recorded!"); return Ok(HttpResponse::Found()
.append_header(("Location", format!("/governance/proposals/{}?vote_success=true", proposal_id)))
// Extract votes directly from the updated proposal .finish());
let votes = Self::extract_votes_from_proposal(&updated_proposal);
ctx.insert("votes", &votes);
// Calculate voting results directly from the updated proposal
let results = Self::calculate_voting_results_from_proposal(&updated_proposal);
ctx.insert("results", &results);
render_template(&tmpl, "governance/proposal_detail.html", &ctx)
} }
Err(e) => { Err(e) => {
ctx.insert("error", &format!("Failed to submit vote: {}", e)); ctx.insert("error", &format!("Failed to submit vote: {}", e));

View File

@ -240,7 +240,7 @@
</div> </div>
<!-- Votes List --> <!-- Votes List -->
<div class="row"> <div class="row mt-4">
<div class="col-12"> <div class="col-12">
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center flex-wrap"> <div class="card-header bg-light d-flex justify-content-between align-items-center flex-wrap">
@ -338,6 +338,23 @@
{% block scripts %} {% block scripts %}
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// Remove query parameters from URL without refreshing the page
if (window.location.search.includes('vote_success=true')) {
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
// Auto-hide the success alert after 5 seconds
const successAlert = document.querySelector('.alert-success');
if (successAlert) {
setTimeout(function() {
successAlert.classList.remove('show');
setTimeout(function() {
successAlert.remove();
}, 500);
}, 5000);
}
}
// Vote filtering using data-filter attributes // Vote filtering using data-filter attributes
const filterButtons = document.querySelectorAll('[data-filter]'); const filterButtons = document.querySelectorAll('[data-filter]');
const voteRows = document.querySelectorAll('.vote-row'); const voteRows = document.querySelectorAll('.vote-row');