init
This commit is contained in:
157
assets/app.js
157
assets/app.js
@@ -1,10 +1,149 @@
|
||||
import './stimulus_bootstrap.js';
|
||||
/*
|
||||
* Welcome to your app's main JavaScript file!
|
||||
*
|
||||
* This file will be included onto the page via the importmap() Twig function,
|
||||
* which should already be in your base.html.twig.
|
||||
*/
|
||||
import './styles/app.css';
|
||||
import "./app.scss"
|
||||
|
||||
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
|
||||
// Membre / Super Admin : mutuellement exclusif
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const memberCheckbox = document.querySelector('input[value="gp_member"]');
|
||||
const adminCheckbox = document.querySelector('input[value="super_admin_asso"]');
|
||||
|
||||
if (memberCheckbox && adminCheckbox) {
|
||||
memberCheckbox.addEventListener('change', () => {
|
||||
if (memberCheckbox.checked) {
|
||||
document.querySelectorAll('input[name="groups[]"]').forEach(cb => {
|
||||
if (cb !== memberCheckbox) {
|
||||
cb.checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
adminCheckbox.addEventListener('change', () => {
|
||||
if (adminCheckbox.checked) {
|
||||
memberCheckbox.checked = false;
|
||||
document.querySelectorAll('input[name="groups[]"]').forEach(cb => {
|
||||
if (cb !== memberCheckbox) {
|
||||
cb.checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Stats period selector
|
||||
const periodSelect = document.getElementById('stats-period-select');
|
||||
const customRange = document.getElementById('stats-custom-range');
|
||||
if (periodSelect && customRange) {
|
||||
periodSelect.addEventListener('change', () => {
|
||||
customRange.classList.toggle('hidden', periodSelect.value !== 'custom');
|
||||
});
|
||||
}
|
||||
|
||||
// data-confirm
|
||||
document.querySelectorAll('form[data-confirm]').forEach(form => {
|
||||
form.addEventListener('submit', (e) => {
|
||||
if (!confirm(form.dataset.confirm)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Sidebar dropdown toggle
|
||||
document.querySelectorAll('.sidebar-dropdown-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const menu = btn.nextElementSibling;
|
||||
const arrow = btn.querySelector('.sidebar-dropdown-arrow');
|
||||
menu?.classList.toggle('hidden');
|
||||
arrow?.classList.toggle('rotate-180');
|
||||
});
|
||||
});
|
||||
|
||||
// Mobile sidebar toggle
|
||||
const sidebarToggle = document.getElementById('admin-sidebar-toggle');
|
||||
const sidebar = document.getElementById('admin-sidebar');
|
||||
const overlay = document.getElementById('admin-overlay');
|
||||
|
||||
if (sidebarToggle && sidebar && overlay) {
|
||||
sidebarToggle.addEventListener('click', () => {
|
||||
sidebar.classList.toggle('open');
|
||||
});
|
||||
|
||||
overlay.addEventListener('click', () => {
|
||||
sidebar.classList.remove('open');
|
||||
});
|
||||
}
|
||||
|
||||
// Mobile menu toggle (public)
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
const menuIconOpen = document.getElementById('menu-icon-open');
|
||||
const menuIconClose = document.getElementById('menu-icon-close');
|
||||
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
menuIconOpen?.classList.toggle('hidden');
|
||||
menuIconClose?.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Cookie banner
|
||||
const cookieBanner = document.getElementById('cookie-banner');
|
||||
const cookieAccept = document.getElementById('cookie-accept');
|
||||
const cookieRefuse = document.getElementById('cookie-refuse');
|
||||
|
||||
if (cookieBanner && !localStorage.getItem('cookie_consent')) {
|
||||
cookieBanner.classList.remove('hidden');
|
||||
}
|
||||
|
||||
cookieAccept?.addEventListener('click', () => {
|
||||
localStorage.setItem('cookie_consent', 'accepted');
|
||||
cookieBanner?.classList.add('hidden');
|
||||
});
|
||||
|
||||
cookieRefuse?.addEventListener('click', () => {
|
||||
localStorage.setItem('cookie_consent', 'refused');
|
||||
cookieBanner?.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Search (customers & revendeurs)
|
||||
const setupSearch = (inputId, resultsId, searchUrl, linkPrefix) => {
|
||||
const input = document.getElementById(inputId);
|
||||
const results = document.getElementById(resultsId);
|
||||
if (!input || !results) return;
|
||||
|
||||
let debounce;
|
||||
input.addEventListener('input', () => {
|
||||
clearTimeout(debounce);
|
||||
const q = input.value.trim();
|
||||
if (q.length < 2) {
|
||||
results.classList.add('hidden');
|
||||
results.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
debounce = setTimeout(async () => {
|
||||
const resp = await fetch(`${searchUrl}?q=${encodeURIComponent(q)}`);
|
||||
const hits = await resp.json();
|
||||
if (hits.length === 0) {
|
||||
results.innerHTML = '<div class="px-4 py-3 text-xs text-gray-400">Aucun resultat.</div>';
|
||||
} else {
|
||||
results.innerHTML = hits.map(h =>
|
||||
`<a href="${linkPrefix}${h.id}" class="block px-4 py-2 hover:bg-gray-50 border-b border-gray-100 text-xs">
|
||||
<span class="font-bold">${h.fullName || h.raisonSociale || (h.firstName + ' ' + h.lastName)}</span>
|
||||
${h.email ? `<span class="text-gray-400 ml-2">${h.email}</span>` : ''}
|
||||
${h.codeRevendeur ? `<span class="ml-2 px-1 py-0.5 bg-gray-900 text-[#fabf04] text-[9px] font-bold">${h.codeRevendeur}</span>` : ''}
|
||||
</a>`
|
||||
).join('');
|
||||
}
|
||||
results.classList.remove('hidden');
|
||||
}, 300);
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!results.contains(e.target) && e.target !== input) {
|
||||
results.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setupSearch('search-customers', 'search-results', '/admin/clients/search', '/admin/clients/');
|
||||
setupSearch('search-revendeurs', 'search-results-revendeurs', '/admin/revendeurs/search', '/admin/revendeurs/');
|
||||
});
|
||||
|
||||
28
assets/app.scss
Normal file
28
assets/app.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
.page-container {
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 0;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
width: 80%;
|
||||
padding: 3rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.heading-page {
|
||||
border-bottom: 4px solid #111827;
|
||||
display: inline-block;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.admin-content .page-container {
|
||||
width: 95%;
|
||||
max-width: none;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"controllers": {
|
||||
"@symfony/ux-turbo": {
|
||||
"turbo-core": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"mercure-turbo-stream": {
|
||||
"enabled": false,
|
||||
"fetch": "eager"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entrypoints": []
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
|
||||
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
|
||||
|
||||
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
|
||||
// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event
|
||||
// and thus this event-listener will not be executed.
|
||||
document.addEventListener('submit', function (event) {
|
||||
generateCsrfToken(event.target);
|
||||
}, true);
|
||||
|
||||
// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
|
||||
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
|
||||
document.addEventListener('turbo:submit-start', function (event) {
|
||||
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
|
||||
Object.keys(h).map(function (k) {
|
||||
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
|
||||
});
|
||||
});
|
||||
|
||||
// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
|
||||
document.addEventListener('turbo:submit-end', function (event) {
|
||||
removeCsrfToken(event.detail.formSubmission.formElement);
|
||||
});
|
||||
|
||||
export function generateCsrfToken (formElement) {
|
||||
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||
|
||||
if (!csrfField) {
|
||||
return;
|
||||
}
|
||||
|
||||
let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||
let csrfToken = csrfField.value;
|
||||
|
||||
if (!csrfCookie && nameCheck.test(csrfToken)) {
|
||||
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
|
||||
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
|
||||
}
|
||||
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
|
||||
if (csrfCookie && tokenCheck.test(csrfToken)) {
|
||||
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
|
||||
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
|
||||
}
|
||||
}
|
||||
|
||||
export function generateCsrfHeaders (formElement) {
|
||||
const headers = {};
|
||||
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||
|
||||
if (!csrfField) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||
|
||||
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
|
||||
headers[csrfCookie] = csrfField.value;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
export function removeCsrfToken (formElement) {
|
||||
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
|
||||
|
||||
if (!csrfField) {
|
||||
return;
|
||||
}
|
||||
|
||||
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
|
||||
|
||||
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
|
||||
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';
|
||||
|
||||
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
|
||||
}
|
||||
}
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default 'csrf-protection-controller';
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/*
|
||||
* This is an example Stimulus controller!
|
||||
*
|
||||
* Any element with a data-controller="hello" attribute will cause
|
||||
* this controller to be executed. The name "hello" comes from the filename:
|
||||
* hello_controller.js -> "hello"
|
||||
*
|
||||
* Delete this file or adapt it for your use!
|
||||
*/
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||
|
||||
const app = startStimulusApp();
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
@@ -1,3 +0,0 @@
|
||||
body {
|
||||
background-color: skyblue;
|
||||
}
|
||||
Reference in New Issue
Block a user