Limit the amount of people who can attend an event

While I wish that in the future this feature comes up along with a waiting list management, here is a temporary solution, note that you will get a second page refresh on every topic where there is an that is full, it can be bothersome for some, for me its ok

How to?

1° add the script at the bottom in admin>appearance>theme>edit>edit code>head(make sure it is placed between tags

2° Create an event that in its title contains any number with the letter ‘p’ after like 2p for two participants(any number to 500 will work), you may change what comes after the number in the script at the bottom by modifying the 2 occurences of if (location.pathname.includes(i + “p”)) { )

This will appear in the url and we can match it with the number of persons going to the event.

3° Test it, notice that there is a page refresh any time the event becomes full or when it passes from full to not full, this is necessary because discourse is a single page application.

If anyone knows how to show the registration timestamp in the participant list, some kind of waiting list could be achieved.

Enjoy !

The code :(updated 14/8 11am utc 0 because of Edge issues)

<style>
/* Smooth text fade when the label changes */
.d-button-label {
  transition: opacity 0.25s ease;
}
.d-button-label.updating {
  opacity: 0;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {

  const refreshFlagKey = "p-refreshed";
  const capacityRegex = /([1-9]|[1-9][0-9]|[1-4][0-9]{2}|500)p/;
  let capacityCheckDone = false; // Prevent repeated checks

  function getCapacityFromURL() {
    const match = location.pathname.match(capacityRegex);
    return match ? parseInt(match[0].replace('p', ''), 10) : null;
  }

  function isEventFull() {
    const capacity = getCapacityFromURL();
    if (!capacity) return false;
    return Array.from(document.querySelectorAll('span.going'))
      .some(span => parseInt(span.textContent.trim(), 10) === capacity);
  }

  function markFull() {
    const button = document.querySelector('.btn.btn-icon-text.going-button');
    const label = button ? button.querySelector('.d-button-label') : null;
    if (label) {
      label.classList.add('updating'); // fade out
      setTimeout(() => {
        label.textContent = "full";
        label.classList.remove('updating'); // fade back in
      }, 150);
    }
    if (button) {
      button.style.pointerEvents = 'none';
      button.style.opacity = '0.6';
    }
  }

  function checkCapacity() {
    if (capacityCheckDone) return; // Avoid infinite loops
    const capacity = getCapacityFromURL();
    if (!capacity) return;

    let isFull = false;
    document.querySelectorAll('span.going').forEach(function (span) {
      if (parseInt(span.textContent.trim(), 10) === capacity) {
        markFull();
        isFull = true;
      }
    });

    if (isFull) {
      capacityCheckDone = true;

      const notGoingBtn = document.querySelector('.btn.btn-icon-text.not-going-button');
      if (notGoingBtn) {
        notGoingBtn.addEventListener('click', function () {
          location.reload();
        });
      }

      const interestedBtn = document.querySelector('.btn.btn-icon-text.interested-button');
      if (interestedBtn) {
        interestedBtn.addEventListener('click', function () {
          location.reload();
        });
      }
    }

    const goingBtn = document.querySelector('.btn.btn-icon-text.going-button');
    if (goingBtn) {
      goingBtn.addEventListener('click', function () {
        setTimeout(function () {
          const count = parseInt(document.querySelector('span.going')?.textContent.trim(), 10);
          if (count === capacity) {
            location.reload();
          }
        }, 300);
      });
    }
  }

  function observeUntilReady() {
    const observer = new MutationObserver(() => {
      if (document.querySelector('.btn.btn-icon-text.going-button') &&
        document.querySelector('span.going')) {
        observer.disconnect();
        checkCapacity();
      }
    });
    observer.observe(document.body, { childList: true, subtree: true });
  }

  function refreshIfNeeded() {
    if (!sessionStorage.getItem(refreshFlagKey)) {
      sessionStorage.setItem(refreshFlagKey, "true");
      setTimeout(() => location.reload(), 100);
    }
  }

  function patchHistoryMethod(method) {
    const original = history[method];
    history[method] = function () {
      const result = original.apply(this, arguments);
      window.dispatchEvent(new Event("locationchange"));
      return result;
    };
  }

  patchHistoryMethod("pushState");
  patchHistoryMethod("replaceState");

  addEventListener("popstate", () => dispatchEvent(new Event("locationchange")));
  addEventListener("locationchange", function () {
    if (getCapacityFromURL() && isEventFull()) {
      refreshIfNeeded();
    } else {
      sessionStorage.removeItem(refreshFlagKey);
    }
  });

  observeUntilReady();

  // Initial check — only refresh if already full
  if (isEventFull()) {
    refreshIfNeeded();
  }

});
</script>



1 Like