mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 11:11:10 -05:00
Add park and ride card components with advanced search functionality
- Implemented park card component with image, status badge, favorite button, and quick stats overlay. - Developed ride card component featuring thrill level badge, status badge, favorite button, and detailed stats. - Created advanced search page with filters for parks and rides, including location, type, status, and thrill level. - Added dynamic quick search functionality with results display. - Enhanced user experience with JavaScript for filter toggling, range slider updates, and view switching. - Included custom CSS for improved styling of checkboxes and search results layout.
This commit is contained in:
@@ -48,7 +48,6 @@
|
||||
<!-- Preload Critical Resources -->
|
||||
{% block critical_resources %}
|
||||
<link rel="preload" href="{% static 'css/tailwind.css' %}" as="style" />
|
||||
<link rel="preload" href="{% static 'js/theme.js' %}?v={{ version|default:'1.0' }}" as="script" />
|
||||
<link rel="preload" href="{% static 'js/alpine.min.js' %}?v={{ version|default:'1.0' }}" as="script" />
|
||||
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" as="style" />
|
||||
{% endblock %}
|
||||
@@ -62,23 +61,15 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Prevent flash of wrong theme -->
|
||||
<script src="{% static 'js/theme.js' %}?v={{ version|default:'1.0' }}"></script>
|
||||
|
||||
<!-- HTMX -->
|
||||
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Alpine.js Components (must load before Alpine.js) -->
|
||||
<script src="{% static 'js/alpine-components.js' %}?v={{ version|default:'1.0' }}"></script>
|
||||
|
||||
<!-- Alpine.js (must load after components) -->
|
||||
<script defer src="{% static 'js/alpine.min.js' %}?v={{ version|default:'1.0' }}"></script>
|
||||
|
||||
<!-- Location Autocomplete -->
|
||||
<script defer src="{% static 'js/location-autocomplete.js' %}?v={{ version|default:'1.0' }}"></script>
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<link href="{% static 'css/tailwind.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'css/design-system.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'css/components.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'css/alerts.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'css/inline-styles.css' %}" rel="stylesheet" />
|
||||
@@ -181,9 +172,347 @@
|
||||
<!-- Global Toast Container -->
|
||||
<c-toast_container />
|
||||
|
||||
<!-- Custom JavaScript with cache control -->
|
||||
<script src="{% static 'js/main.js' %}?v={{ version|default:'1.0' }}"></script>
|
||||
<script src="{% static 'js/alerts.js' %}?v={{ version|default:'1.0' }}"></script>
|
||||
<!-- AlpineJS Components and Stores (Inline) -->
|
||||
<script>
|
||||
// Global Alpine.js stores and components
|
||||
document.addEventListener('alpine:init', () => {
|
||||
// Global Store for App State
|
||||
Alpine.store('app', {
|
||||
user: null,
|
||||
theme: localStorage.getItem('theme') || 'system',
|
||||
searchQuery: '',
|
||||
notifications: [],
|
||||
|
||||
setUser(user) {
|
||||
this.user = user;
|
||||
},
|
||||
|
||||
setTheme(theme) {
|
||||
this.theme = theme;
|
||||
localStorage.setItem('theme', theme);
|
||||
},
|
||||
|
||||
addNotification(notification) {
|
||||
this.notifications.push({
|
||||
id: Date.now(),
|
||||
...notification
|
||||
});
|
||||
},
|
||||
|
||||
removeNotification(id) {
|
||||
this.notifications = this.notifications.filter(n => n.id !== id);
|
||||
}
|
||||
});
|
||||
|
||||
// Global Toast Store
|
||||
Alpine.store('toast', {
|
||||
toasts: [],
|
||||
|
||||
show(message, type = 'info', duration = 5000) {
|
||||
const id = Date.now() + Math.random();
|
||||
const toast = {
|
||||
id,
|
||||
message,
|
||||
type,
|
||||
visible: true,
|
||||
progress: 100
|
||||
};
|
||||
|
||||
this.toasts.push(toast);
|
||||
|
||||
if (duration > 0) {
|
||||
const interval = setInterval(() => {
|
||||
toast.progress -= (100 / (duration / 100));
|
||||
if (toast.progress <= 0) {
|
||||
clearInterval(interval);
|
||||
this.hide(id);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
hide(id) {
|
||||
const toast = this.toasts.find(t => t.id === id);
|
||||
if (toast) {
|
||||
toast.visible = false;
|
||||
setTimeout(() => {
|
||||
this.toasts = this.toasts.filter(t => t.id !== id);
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
|
||||
success(message, duration = 5000) {
|
||||
return this.show(message, 'success', duration);
|
||||
},
|
||||
|
||||
error(message, duration = 7000) {
|
||||
return this.show(message, 'error', duration);
|
||||
},
|
||||
|
||||
warning(message, duration = 6000) {
|
||||
return this.show(message, 'warning', duration);
|
||||
},
|
||||
|
||||
info(message, duration = 5000) {
|
||||
return this.show(message, 'info', duration);
|
||||
}
|
||||
});
|
||||
|
||||
// Theme Toggle Component
|
||||
Alpine.data('themeToggle', () => ({
|
||||
theme: localStorage.getItem('theme') || 'system',
|
||||
|
||||
init() {
|
||||
this.updateTheme();
|
||||
|
||||
// Watch for system theme changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
if (this.theme === 'system') {
|
||||
this.updateTheme();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggleTheme() {
|
||||
const themes = ['light', 'dark', 'system'];
|
||||
const currentIndex = themes.indexOf(this.theme);
|
||||
this.theme = themes[(currentIndex + 1) % themes.length];
|
||||
localStorage.setItem('theme', this.theme);
|
||||
this.updateTheme();
|
||||
},
|
||||
|
||||
updateTheme() {
|
||||
const root = document.documentElement;
|
||||
|
||||
if (this.theme === 'dark' ||
|
||||
(this.theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Modal Component
|
||||
Alpine.data('modal', (initialOpen = false) => ({
|
||||
open: initialOpen,
|
||||
|
||||
show() {
|
||||
this.open = true;
|
||||
document.body.style.overflow = 'hidden';
|
||||
},
|
||||
|
||||
hide() {
|
||||
this.open = false;
|
||||
document.body.style.overflow = '';
|
||||
},
|
||||
|
||||
toggle() {
|
||||
if (this.open) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Dropdown Component
|
||||
Alpine.data('dropdown', (initialOpen = false) => ({
|
||||
open: initialOpen,
|
||||
|
||||
toggle() {
|
||||
this.open = !this.open;
|
||||
},
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
},
|
||||
|
||||
show() {
|
||||
this.open = true;
|
||||
}
|
||||
}));
|
||||
|
||||
// Search Component - HTMX-based (NO FETCH API)
|
||||
Alpine.data('searchComponent', () => ({
|
||||
query: '',
|
||||
loading: false,
|
||||
showResults: false,
|
||||
|
||||
init() {
|
||||
// Listen for HTMX events
|
||||
this.$el.addEventListener('htmx:beforeRequest', () => {
|
||||
this.loading = true;
|
||||
});
|
||||
|
||||
this.$el.addEventListener('htmx:afterRequest', () => {
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
this.$el.addEventListener('htmx:afterSettle', () => {
|
||||
const resultsContainer = document.getElementById('search-results');
|
||||
this.showResults = resultsContainer && resultsContainer.children.length > 0;
|
||||
});
|
||||
},
|
||||
|
||||
handleInput() {
|
||||
if (this.query.length < 2) {
|
||||
this.showResults = false;
|
||||
const resultsContainer = document.getElementById('search-results');
|
||||
if (resultsContainer) {
|
||||
resultsContainer.innerHTML = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
// HTMX will handle the actual search via hx-trigger
|
||||
},
|
||||
|
||||
selectResult(url) {
|
||||
window.location.href = url;
|
||||
this.showResults = false;
|
||||
this.query = '';
|
||||
},
|
||||
|
||||
clearSearch() {
|
||||
this.query = '';
|
||||
this.showResults = false;
|
||||
const resultsContainer = document.getElementById('search-results');
|
||||
if (resultsContainer) {
|
||||
resultsContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Browse Menu Component
|
||||
Alpine.data('browseMenu', () => ({
|
||||
open: false,
|
||||
|
||||
toggle() {
|
||||
this.open = !this.open;
|
||||
},
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
}
|
||||
}));
|
||||
|
||||
// Mobile Menu Component
|
||||
Alpine.data('mobileMenu', () => ({
|
||||
open: false,
|
||||
|
||||
toggle() {
|
||||
this.open = !this.open;
|
||||
|
||||
if (this.open) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
},
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
}));
|
||||
|
||||
// User Menu Component
|
||||
Alpine.data('userMenu', () => ({
|
||||
open: false,
|
||||
|
||||
toggle() {
|
||||
this.open = !this.open;
|
||||
},
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
}
|
||||
}));
|
||||
|
||||
// Auth Modal Component
|
||||
Alpine.data('authModal', (defaultMode = 'login') => ({
|
||||
open: false,
|
||||
mode: defaultMode,
|
||||
showPassword: false,
|
||||
socialProviders: [
|
||||
{id: 'google', name: 'Google', auth_url: '/accounts/google/login/'},
|
||||
{id: 'discord', name: 'Discord', auth_url: '/accounts/discord/login/'}
|
||||
],
|
||||
|
||||
loginForm: {
|
||||
username: '',
|
||||
password: ''
|
||||
},
|
||||
loginLoading: false,
|
||||
loginError: '',
|
||||
|
||||
registerForm: {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
username: '',
|
||||
password1: '',
|
||||
password2: ''
|
||||
},
|
||||
registerLoading: false,
|
||||
registerError: '',
|
||||
|
||||
init() {
|
||||
this.$watch('open', (value) => {
|
||||
if (value) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
this.resetForms();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
show(mode = 'login') {
|
||||
this.mode = mode;
|
||||
this.open = true;
|
||||
},
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
},
|
||||
|
||||
switchToLogin() {
|
||||
this.mode = 'login';
|
||||
this.resetForms();
|
||||
},
|
||||
|
||||
switchToRegister() {
|
||||
this.mode = 'register';
|
||||
this.resetForms();
|
||||
},
|
||||
|
||||
resetForms() {
|
||||
this.loginForm = { username: '', password: '' };
|
||||
this.registerForm = {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
username: '',
|
||||
password1: '',
|
||||
password2: ''
|
||||
};
|
||||
this.loginError = '';
|
||||
this.registerError = '';
|
||||
this.showPassword = false;
|
||||
},
|
||||
|
||||
getCSRFToken() {
|
||||
const token = document.querySelector('[name=csrfmiddlewaretoken]')?.value ||
|
||||
document.querySelector('meta[name=csrf-token]')?.getAttribute('content') ||
|
||||
document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1];
|
||||
return token || '';
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user