- Add governance activity tracker to record user actions. - Display recent activities on the governance dashboard. - Add a dedicated page to view all governance activities. - Improve header information and styling across governance pages. - Track proposal creation and voting activities.
229 lines
10 KiB
HTML
229 lines
10 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Create Proposal - Governance Dashboard{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Header -->
|
|
{% include "governance/_header.html" %}
|
|
|
|
<!-- Navigation Tabs -->
|
|
{% include "governance/_tabs.html" %}
|
|
|
|
<!-- Info Alert -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="alert alert-info alert-dismissible fade show">
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
<h5><i class="bi bi-info-circle"></i> About Creating Proposals</h5>
|
|
<p>Creating a proposal is an important step in our community governance process. Well-crafted proposals
|
|
clearly state the problem, solution, and implementation details. The community will review and vote
|
|
on your proposal, so be thorough and thoughtful in your submission.</p>
|
|
<div class="mt-2">
|
|
<a href="/governance/proposal-templates" class="btn btn-sm btn-outline-primary"><i
|
|
class="bi bi-file-earmark-text"></i> Proposal Templates</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Proposal Form and Guidelines in Flex Layout -->
|
|
<div class="row mb-4">
|
|
<!-- Proposal Form Column -->
|
|
<div class="col-lg-8">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">New Proposal</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form action="/governance/create" method="post" id="proposalForm" novalidate>
|
|
<div class="mb-3">
|
|
<label for="title" class="form-label">Title</label>
|
|
<input type="text" class="form-control" id="title" name="title" required minlength="5"
|
|
maxlength="100" placeholder="Enter a clear, concise title for your proposal">
|
|
<div class="invalid-feedback">Please provide a title (5-100 characters).</div>
|
|
<div class="form-text">Make it descriptive and specific</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="description" class="form-label">Description</label>
|
|
<textarea class="form-control" id="description" name="description" rows="8" required
|
|
minlength="50" maxlength="5000"
|
|
placeholder="Provide a detailed description of your proposal..."></textarea>
|
|
<div class="invalid-feedback">Please provide a detailed description (at least 50
|
|
characters).</div>
|
|
<div class="form-text">Explain the purpose, benefits, and implementation details</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label for="voting_start_date" class="form-label">Voting Start Date</label>
|
|
<input type="date" class="form-control" id="voting_start_date" name="voting_start_date">
|
|
<div class="invalid-feedback" id="start_date_feedback">Please select a valid start date.
|
|
</div>
|
|
<div class="form-text">When should voting begin?</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="voting_end_date" class="form-label">Voting End Date</label>
|
|
<input type="date" class="form-control" id="voting_end_date" name="voting_end_date">
|
|
<div class="invalid-feedback" id="end_date_feedback">End date must be after start date.
|
|
</div>
|
|
<div class="form-text">When should voting end?</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="draft" name="draft" value="true">
|
|
<label class="form-check-label" for="draft">
|
|
Save as draft (not ready for voting yet)
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-primary">Submit Proposal</button>
|
|
<a href="/governance" class="btn btn-outline-secondary">Cancel</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Guidelines Column -->
|
|
<div class="col-lg-4">
|
|
<div class="card bg-light h-100">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">Proposal Guidelines</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<ul class="list-group list-group-flush">
|
|
<li class="list-group-item bg-transparent">
|
|
<strong>Be specific:</strong> Clearly state what you're proposing and why.
|
|
</li>
|
|
<li class="list-group-item bg-transparent">
|
|
<strong>Provide context:</strong> Explain the current situation and why change is needed.
|
|
</li>
|
|
<li class="list-group-item bg-transparent">
|
|
<strong>Consider implementation:</strong> Outline how your proposal could be implemented.
|
|
</li>
|
|
<li class="list-group-item bg-transparent">
|
|
<strong>Address concerns:</strong> Anticipate potential objections and address them.
|
|
</li>
|
|
<li class="list-group-item bg-transparent">
|
|
<strong>Be respectful:</strong> Focus on ideas, not individuals or groups.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const form = document.getElementById('proposalForm');
|
|
const startDateInput = document.getElementById('voting_start_date');
|
|
const endDateInput = document.getElementById('voting_end_date');
|
|
const startDateFeedback = document.getElementById('start_date_feedback');
|
|
const endDateFeedback = document.getElementById('end_date_feedback');
|
|
|
|
// Set default dates
|
|
const today = new Date();
|
|
const tomorrow = new Date(today);
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
const nextWeek = new Date(today);
|
|
nextWeek.setDate(nextWeek.getDate() + 7);
|
|
|
|
// Format dates for input fields
|
|
const formatDate = (date) => {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
// Set default values
|
|
startDateInput.value = formatDate(tomorrow);
|
|
endDateInput.value = formatDate(nextWeek);
|
|
|
|
// Validate dates when they change
|
|
function validateDates() {
|
|
const startDate = new Date(startDateInput.value);
|
|
const endDate = new Date(endDateInput.value);
|
|
const currentDate = new Date();
|
|
currentDate.setHours(0, 0, 0, 0); // Reset time to start of day
|
|
|
|
let startValid = true;
|
|
let endValid = true;
|
|
|
|
// Validate start date is not in the past
|
|
if (startDate < currentDate) {
|
|
startDateInput.classList.add('is-invalid');
|
|
startDateFeedback.textContent = 'Start date cannot be in the past.';
|
|
startValid = false;
|
|
} else {
|
|
startDateInput.classList.remove('is-invalid');
|
|
}
|
|
|
|
// Validate end date is after start date
|
|
if (endDate < startDate) {
|
|
endDateInput.classList.add('is-invalid');
|
|
endDateFeedback.textContent = 'End date must be after start date.';
|
|
endValid = false;
|
|
} else {
|
|
endDateInput.classList.remove('is-invalid');
|
|
}
|
|
|
|
return startValid && endValid;
|
|
}
|
|
|
|
// Validate on input
|
|
startDateInput.addEventListener('change', validateDates);
|
|
endDateInput.addEventListener('change', validateDates);
|
|
|
|
// Form submission validation
|
|
form.addEventListener('submit', function (event) {
|
|
let formValid = true;
|
|
|
|
// Validate required fields
|
|
const requiredFields = form.querySelectorAll('[required]');
|
|
requiredFields.forEach(field => {
|
|
if (!field.value.trim()) {
|
|
field.classList.add('is-invalid');
|
|
formValid = false;
|
|
} else {
|
|
field.classList.remove('is-invalid');
|
|
}
|
|
|
|
// Check minlength if specified
|
|
if (field.minLength && field.value.length < field.minLength) {
|
|
field.classList.add('is-invalid');
|
|
formValid = false;
|
|
}
|
|
});
|
|
|
|
// Validate dates
|
|
const datesValid = validateDates();
|
|
formValid = formValid && datesValid;
|
|
|
|
// If form is not valid, prevent submission
|
|
if (!formValid) {
|
|
event.preventDefault();
|
|
// Scroll to the first invalid element
|
|
const firstInvalid = form.querySelector('.is-invalid');
|
|
if (firstInvalid) {
|
|
firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
firstInvalid.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initial validation
|
|
validateDates();
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% endblock %} |