assets/js/wizard.js

/* ==========================================================================
   Installation wizard
   ========================================================================== */

/*
|--------------------------------------------------------------------------
| First-time setup
|--------------------------------------------------------------------------
|
| Handles the initial Brivacia installation process, including site
| configuration, validation and creation of the settings file.
|
*/

(() => {
    const form = document.getElementById('wizard-form');
    const sites = document.getElementById('wizard-sites');
    const template = document.getElementById('wizard-site-template');
    const message = document.getElementById('wizard-message');

    if (!form) return;

    const text = {
        installing: message?.dataset.installing ?? 'Installing Brivacia...',
        installed: message?.dataset.installed ?? 'Brivacia installed.',
        error: message?.dataset.error ?? 'Unable to install Brivacia.',
        networkError: message?.dataset.networkError ?? 'Network error.'
    };

    function setMessage(value, status = '') {
        if (!message) return;

        message.className = status;
        message.textContent = value || '';
    }

    function siteList() {
        return sites?.querySelector(':scope > div') || null;
    }

    /*
    |--------------------------------------------------------------------------
    | Site list management
    |--------------------------------------------------------------------------
    |
    | Site entries are stored as an indexed array. Whenever an entry is
    | removed, field names must be reindexed so PHP receives a continuous
    | array structure on submission.
    |
    */

    function reindexSites() {
        siteList()?.querySelectorAll(':scope > fieldset').forEach((fieldset, index) => {
            const code = fieldset.querySelector('input[name$="[code]"]');
            const domain = fieldset.querySelector('input[name$="[domain]"]');

            if (code) code.name = `sites[${index}][code]`;
            if (domain) domain.name = `sites[${index}][domain]`;
        });
    }

    function addSite() {
        const list = siteList();

        if (!list || !template) return;

        const wrapper = document.createElement('div');

        wrapper.innerHTML = template.innerHTML
            .replaceAll('__INDEX__', String(list.children.length))
            .trim();

        if (wrapper.firstElementChild) {
            list.appendChild(wrapper.firstElementChild);
        }
    }

    function removeSite(button) {
        const list = siteList();
        const fieldset = button.closest('fieldset');

        if (!list || !fieldset || list.children.length <= 1) return;

        fieldset.remove();
        reindexSites();
    }

    /*
    |--------------------------------------------------------------------------
    | Form serialization
    |--------------------------------------------------------------------------
    |
    | Convert the wizard form into a clean JSON payload before sending it
    | to the installation endpoint.
    |
    */

    function formToObject() {
        const data = new FormData(form);
        const result = {};

        for (const [name, value] of data.entries()) {
            if (!name.startsWith('sites[')) {
                result[name] = value;
            }
        }

        form.querySelectorAll('input[type="checkbox"]').forEach((input) => {
            result[input.name] = input.checked;
        });

        result.sites = [];

        siteList()?.querySelectorAll(':scope > fieldset').forEach((fieldset) => {
            const code = fieldset.querySelector('input[name$="[code]"]')?.value.trim() || '';
            const domain = fieldset.querySelector('input[name$="[domain]"]')?.value.trim() || '';

            if (code !== '' || domain !== '') {
                result.sites.push({ code, domain });
            }
        });

        return result;
    }

    form.addEventListener('click', (event) => {
        const button = event.target.closest('button');

        if (!button) return;

        if (button.value === 'add-site') {
            addSite();
            return;
        }

        if (button.value === 'remove-site') {
            removeSite(button);
        }
    });

    form.addEventListener('submit', async (event) => {
        event.preventDefault();

        setMessage(text.installing, 'warning');

        try {
            const response = await fetch('/api/settings.php?action=install', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(formToObject())
            });

            const data = await response.json();

            if (!response.ok || !data.ok) {
                setMessage(data.error ? `${text.error} ${data.error}` : text.error, 'danger');
                return;
            }

            setMessage(text.installed, 'success');

            setTimeout(() => {
                document.getElementById('wizard-modal')?.close();
                sessionStorage.setItem('brivacia.showPixelModal', '1');
                window.location.reload();
            }, 700);
        } catch {
            setMessage(text.networkError, 'danger');
        }
    });
})();