Introduces a new Settings page with configurable modules: - General company information - Calendar and booking settings - Billing and payment configurations - Tracking and analytics integrations Implements modular ViewComponents for each settings section Enhances user experience with toggle switches and detailed configuration options
289 lines
9.4 KiB
TypeScript
289 lines
9.4 KiB
TypeScript
/**
|
|
* Tracking Controller
|
|
*
|
|
* Handles toggle switches, input changes, and code preview generation
|
|
* for the Settings > Tracking page.
|
|
*
|
|
* Note: Toggle slider click behavior is handled by ControlsController.
|
|
* This controller listens for 'toggle' events to update card states and preview.
|
|
*/
|
|
|
|
export class TrackingController {
|
|
constructor() {
|
|
this.setupToggleListeners();
|
|
this.setupInputListeners();
|
|
this.setupCopyButton();
|
|
this.initializeCardStates();
|
|
this.updatePreview();
|
|
}
|
|
|
|
/**
|
|
* Listen for toggle events from ControlsController
|
|
*/
|
|
private setupToggleListeners(): void {
|
|
document.querySelectorAll<HTMLElement>('.tracking-card swp-toggle-slider').forEach(slider => {
|
|
slider.addEventListener('toggle', (e) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
const card = slider.closest('.tracking-card');
|
|
|
|
if (card) {
|
|
if (detail.value === 'no') {
|
|
card.classList.add('disabled');
|
|
} else {
|
|
card.classList.remove('disabled');
|
|
}
|
|
}
|
|
|
|
this.updatePreview();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup input listeners for contenteditable fields and textareas
|
|
*/
|
|
private setupInputListeners(): void {
|
|
// Contenteditable fields
|
|
const editableIds = [
|
|
'metaPixelId', 'ga4Id', 'gtmId', 'plausibleDomain',
|
|
'fathomSiteId', 'matomoUrl', 'matomoSiteId'
|
|
];
|
|
|
|
editableIds.forEach(id => {
|
|
const element = document.getElementById(id);
|
|
if (element) {
|
|
element.addEventListener('input', () => this.updatePreview());
|
|
}
|
|
});
|
|
|
|
// Textareas
|
|
const textareaIds = ['customHeadScripts', 'customBodyScripts'];
|
|
textareaIds.forEach(id => {
|
|
const element = document.getElementById(id) as HTMLTextAreaElement;
|
|
if (element) {
|
|
element.addEventListener('input', () => this.updatePreview());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup copy button functionality
|
|
*/
|
|
private setupCopyButton(): void {
|
|
const copyBtn = document.getElementById('copyTrackingCode');
|
|
if (!copyBtn) return;
|
|
|
|
copyBtn.addEventListener('click', () => this.copyCode());
|
|
}
|
|
|
|
/**
|
|
* Initialize card states based on toggle values
|
|
*/
|
|
private initializeCardStates(): void {
|
|
document.querySelectorAll<HTMLElement>('.tracking-card[data-tracker]').forEach(card => {
|
|
const toggle = card.querySelector<HTMLElement>('swp-toggle-slider');
|
|
if (toggle && toggle.dataset.value === 'no') {
|
|
card.classList.add('disabled');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if a tracker is enabled
|
|
*/
|
|
private isTrackerEnabled(trackerName: string): boolean {
|
|
const card = document.querySelector<HTMLElement>(`[data-tracker="${trackerName}"]`);
|
|
if (!card) return false;
|
|
const toggle = card.querySelector<HTMLElement>('swp-toggle-slider');
|
|
return toggle?.dataset.value === 'yes';
|
|
}
|
|
|
|
/**
|
|
* Get text content from an element by ID
|
|
*/
|
|
private getValue(id: string): string {
|
|
const element = document.getElementById(id);
|
|
return element?.textContent?.trim() || '';
|
|
}
|
|
|
|
/**
|
|
* Get value from textarea by ID
|
|
*/
|
|
private getTextareaValue(id: string): string {
|
|
const element = document.getElementById(id) as HTMLTextAreaElement;
|
|
return element?.value?.trim() || '';
|
|
}
|
|
|
|
/**
|
|
* Escape HTML entities
|
|
*/
|
|
private escapeHtml(text: string): string {
|
|
return text
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
}
|
|
|
|
/**
|
|
* Update the code preview based on active trackers
|
|
*/
|
|
private updatePreview(): void {
|
|
const preview = document.getElementById('trackingCodePreview');
|
|
if (!preview) return;
|
|
|
|
let code = '';
|
|
|
|
// Meta Pixel
|
|
if (this.isTrackerEnabled('meta')) {
|
|
const pixelId = this.getValue('metaPixelId');
|
|
if (pixelId) {
|
|
code += `<span class="comment"><!-- Meta Pixel --></span>
|
|
<span class="tag"><script></span>
|
|
!function(f,b,e,v,n,t,s)
|
|
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
|
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
|
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
|
n.queue=[];t=b.createElement(e);t.async=!0;
|
|
t.src=v;s=b.getElementsByTagName(e)[0];
|
|
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
|
'https://connect.facebook.net/en_US/fbevents.js');
|
|
fbq(<span class="string">'init'</span>, <span class="string">'${pixelId}'</span>);
|
|
fbq(<span class="string">'track'</span>, <span class="string">'PageView'</span>);
|
|
<span class="tag"></script></span>
|
|
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Google Analytics
|
|
if (this.isTrackerEnabled('ga4')) {
|
|
const measurementId = this.getValue('ga4Id');
|
|
if (measurementId) {
|
|
code += `<span class="comment"><!-- Google Analytics (GA4) --></span>
|
|
<span class="tag"><script</span> <span class="attr">async</span> <span class="attr">src</span>=<span class="string">"https://www.googletagmanager.com/gtag/js?id=${measurementId}"</span><span class="tag">></script></span>
|
|
<span class="tag"><script></span>
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
gtag(<span class="string">'js'</span>, new Date());
|
|
gtag(<span class="string">'config'</span>, <span class="string">'${measurementId}'</span>);
|
|
<span class="tag"></script></span>
|
|
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Google Tag Manager
|
|
if (this.isTrackerEnabled('gtm')) {
|
|
const containerId = this.getValue('gtmId');
|
|
if (containerId) {
|
|
code += `<span class="comment"><!-- Google Tag Manager --></span>
|
|
<span class="tag"><script></span>
|
|
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
|
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
|
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
|
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
|
})(window,document,'script','dataLayer',<span class="string">'${containerId}'</span>);
|
|
<span class="tag"></script></span>
|
|
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Plausible
|
|
if (this.isTrackerEnabled('plausible')) {
|
|
const domain = this.getValue('plausibleDomain');
|
|
if (domain) {
|
|
code += `<span class="comment"><!-- Plausible Analytics --></span>
|
|
<span class="tag"><script</span> <span class="attr">defer</span> <span class="attr">data-domain</span>=<span class="string">"${domain}"</span> <span class="attr">src</span>=<span class="string">"https://plausible.io/js/script.js"</span><span class="tag">></script></span>
|
|
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Fathom
|
|
if (this.isTrackerEnabled('fathom')) {
|
|
const siteId = this.getValue('fathomSiteId');
|
|
if (siteId) {
|
|
code += `<span class="comment"><!-- Fathom Analytics --></span>
|
|
<span class="tag"><script</span> <span class="attr">src</span>=<span class="string">"https://cdn.usefathom.com/script.js"</span> <span class="attr">data-site</span>=<span class="string">"${siteId}"</span> <span class="attr">defer</span><span class="tag">></script></span>
|
|
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Matomo
|
|
if (this.isTrackerEnabled('matomo')) {
|
|
const serverUrl = this.getValue('matomoUrl');
|
|
const siteId = this.getValue('matomoSiteId');
|
|
if (serverUrl && siteId) {
|
|
code += `<span class="comment"><!-- Matomo Analytics --></span>
|
|
<span class="tag"><script></span>
|
|
var _paq = window._paq = window._paq || [];
|
|
_paq.push([<span class="string">'trackPageView'</span>]);
|
|
_paq.push([<span class="string">'enableLinkTracking'</span>]);
|
|
(function() {
|
|
var u=<span class="string">"${serverUrl}/"</span>;
|
|
_paq.push([<span class="string">'setTrackerUrl'</span>, u+<span class="string">'matomo.php'</span>]);
|
|
_paq.push([<span class="string">'setSiteId'</span>, <span class="string">'${siteId}'</span>]);
|
|
var d=document, g=d.createElement(<span class="string">'script'</span>), s=d.getElementsByTagName(<span class="string">'script'</span>)[0];
|
|
g.async=true; g.src=u+<span class="string">'matomo.js'</span>; s.parentNode.insertBefore(g,s);
|
|
})();
|
|
<span class="tag"></script></span>
|
|
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Custom Head Scripts
|
|
const customHead = this.getTextareaValue('customHeadScripts');
|
|
if (customHead) {
|
|
code += `<span class="comment"><!-- Custom Scripts --></span>
|
|
${this.escapeHtml(customHead)}
|
|
|
|
`;
|
|
}
|
|
|
|
// Update preview
|
|
const codeElement = preview.querySelector('code');
|
|
if (codeElement) {
|
|
if (code) {
|
|
codeElement.innerHTML = code.trim();
|
|
} else {
|
|
codeElement.innerHTML = '<span class="comment"><!-- Ingen aktive tracking-koder --></span>';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copy generated code to clipboard
|
|
*/
|
|
private copyCode(): void {
|
|
const preview = document.getElementById('trackingCodePreview');
|
|
if (!preview) return;
|
|
|
|
const codeElement = preview.querySelector('code');
|
|
if (!codeElement) return;
|
|
|
|
// Get plain text version (strip HTML tags and decode entities)
|
|
const code = codeElement.innerHTML
|
|
.replace(/<span[^>]*>/g, '')
|
|
.replace(/<\/span>/g, '')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/&/g, '&');
|
|
|
|
navigator.clipboard.writeText(code).then(() => {
|
|
const btn = document.getElementById('copyTrackingCode');
|
|
if (!btn) return;
|
|
|
|
const originalHtml = btn.innerHTML;
|
|
btn.innerHTML = '<i class="ph ph-check"></i> Kopieret!';
|
|
btn.style.color = 'var(--color-green)';
|
|
|
|
setTimeout(() => {
|
|
btn.innerHTML = originalHtml;
|
|
btn.style.color = '';
|
|
}, 2000);
|
|
});
|
|
}
|
|
}
|