With the following script, I cannot open the modal when click on the button with error "Uncaught TypeError: bootstrap.Modal.getOrCreateInstance is not a function", how to fix it?:
document.addEventListener('DOMContentLoaded', function () {
  const starRatingEl = document.getElementById('rating-comp-starRating');
  const ratingInput = document.getElementById('rating-comp-rating');
  const commentInput = document.getElementById('rating-comp-comment');
  const previewImagesEl = document.getElementById('rating-comp-preview-images');
  const submitBtn = document.getElementById('rating-comp-submitRatingBtn');
  const formError = document.getElementById('rating-comp-formError');
  const formSuccess = document.getElementById('rating-comp-formSuccess');
  const productIdInput = document.getElementById('rating-comp-product_id');
  const userIdInput = document.getElementById('rating-comp-user_id');
  const imagesInput = document.getElementById('rating-comp-images');
  const ratingForm = document.getElementById('rating-comp-ratingForm');

  let selectedRating = parseInt(ratingInput.value) || 0;

  // Render stars UI
  function renderStars(selected = 0) {
    starRatingEl.innerHTML = '';
    for (let i = 1; i <= 5; i++) {
      const starDiv = document.createElement('div');
      starDiv.classList.add('rating-comp-star');
      starDiv.setAttribute('data-value', i);
      starDiv.setAttribute('title', `${i} star${i > 1 ? 's' : ''}`);
      starDiv.setAttribute('aria-label', `${i} stars`);
      starDiv.setAttribute('role', 'button');
      starDiv.setAttribute('tabindex', '0');
      starDiv.style.display = 'inline-block';
      starDiv.style.cursor = 'pointer';
      const svgNS = 'http://www.w3.org/2000/svg';
      const svg = document.createElementNS(svgNS, 'svg');
      svg.setAttribute('width', 32);
      svg.setAttribute('height', 32);
      svg.setAttribute('viewBox', '0 0 24 24');
      svg.setAttribute('stroke', 'none');
      svg.setAttribute('stroke-width', '1');
      svg.setAttribute('fill', i <= selected ? '#ffc107' : '#ddd');

      const path = document.createElementNS(svgNS, 'path');
      path.setAttribute(
        'd',
        'M12 17.27l5.18 3.73-1.64-6.81L21 9.5l-6.91-.61L12 2l-2.09 6.89-6.91.61 5.46 4.69-1.64 6.81z'
      );
      svg.appendChild(path);
      starDiv.appendChild(svg);
      starRatingEl.appendChild(starDiv);
    }
  }

  // Initialize stars
  renderStars(selectedRating);

  // STAR EVENTS

  starRatingEl.addEventListener('mouseover', (e) => {
    let star = e.target.closest('.rating-comp-star');
    if (!star) return;
    const hoverValue = Number(star.getAttribute('data-value'));
    Array.from(starRatingEl.querySelectorAll('svg')).forEach((svg, idx) => {
      svg.setAttribute('fill', idx < hoverValue ? '#ffc107' : '#ddd');
    });
  });

  starRatingEl.addEventListener('mouseleave', () => {
    renderStars(selectedRating);
  });

  starRatingEl.addEventListener('click', (e) => {
    let star = e.target.closest('.rating-comp-star');
    if (!star) return;
    selectedRating = Number(star.getAttribute('data-value'));
    ratingInput.value = selectedRating;
    renderStars(selectedRating);
  });

  starRatingEl.addEventListener('keydown', (e) => {
    if (!e.target.classList.contains('rating-comp-star')) return;
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      selectedRating = Number(e.target.getAttribute('data-value'));
      ratingInput.value = selectedRating;
      renderStars(selectedRating);
    }
  });

  // Image preview
  imagesInput.addEventListener('change', function () {
    previewImagesEl.innerHTML = '';
    const files = Array.from(imagesInput.files);
    const maxImages = 5;
    if (files.length > maxImages) {
      alert(`Maximum ${maxImages} images are allowed.`);
      imagesInput.value = '';
      return;
    }
    files.forEach((file) => {
      if (!file.type.startsWith('image/')) return;
      const reader = new FileReader();
      reader.onload = function (event) {
        const img = document.createElement('img');
        img.src = event.target.result;
        img.alt = 'Review Image';
        img.style.width = '70px';
        img.style.height = '70px';
        img.style.objectFit = 'cover';
        img.style.marginRight = '10px';
        img.style.marginBottom = '10px';
        img.style.borderRadius = '6px';
        img.style.border = '1px solid #ccc';
        previewImagesEl.appendChild(img);
      };
      reader.readAsDataURL(file);
    });
  });

  // CLEAR FORM ON MODAL CLOSE
  const ratingModalEl = document.getElementById('rating-comp-ratingModal');
  const ratingModalInstance = bootstrap.Modal.getOrCreateInstance(ratingModalEl);

  ratingModalEl.addEventListener('hidden.bs.modal', function () {
    selectedRating = 0;
    ratingInput.value = '';
    commentInput.value = '';
    imagesInput.value = '';
    previewImagesEl.innerHTML = '';
    formError.textContent = '';
    formSuccess.textContent = '';
    submitBtn.disabled = false;
    submitBtn.textContent = 'Submit Rating';
    renderStars(selectedRating);
  });

  // FETCH EXISTING RATING from your backend GET /controller/get_rating?product_id=...&user_id=...

  function loadExistingRating(productId, userId) {
    if (!productId || !userId) return;

    const params = new URLSearchParams({
      product_id: productId,
      user_id: userId,
      limit: 1,
    });

    fetch(`${base_url}/products/get_rating?${params.toString()}`, {
      method: 'GET',
      headers: {
        Accept: 'application/json',
      },
    })
      .then((response) => response.json())
      .then((res) => {
        if (
          res.error === false &&
          res.data &&
          res.data.ratings &&
          res.data.ratings.length > 0
        ) {
          const rating = res.data.ratings[0];
          selectedRating = Math.round(rating.rating);
          ratingInput.value = selectedRating;
          renderStars(selectedRating);
          commentInput.value = rating.comment || '';

          // Show existing images (replace current preview)
          previewImagesEl.innerHTML = '';
          if (rating.images && rating.images.length > 0) {
            rating.images.forEach((imgUrl) => {
              const img = document.createElement('img');
              img.src = imgUrl;
              img.alt = 'Review Image';
              img.style.width = '70px';
              img.style.height = '70px';
              img.style.objectFit = 'cover';
              img.style.marginRight = '10px';
              img.style.marginBottom = '10px';
              img.style.borderRadius = '6px';
              img.style.border = '1px solid #ccc';
              previewImagesEl.appendChild(img);
            });
          }
        } else {
          // No rating found; reset selections
          selectedRating = 0;
          ratingInput.value = '';
          commentInput.value = '';
          previewImagesEl.innerHTML = '';
          renderStars(0);
        }
      })
      .catch((err) => {
        console.error('Failed to load existing rating:', err);
      });
  }

  // FORM SUBMISSION HANDLER - POST to /controller/save_rating

  ratingForm.addEventListener('submit', function (e) {
    e.preventDefault();

    formError.textContent = '';
    formSuccess.textContent = '';

    if (!ratingInput.value || ratingInput.value === '0') {
      formError.textContent = 'Please select a rating.';
      return;
    }

    const maxImages = 5;
    if (imagesInput.files.length > maxImages) {
      formError.textContent = `Please upload maximum ${maxImages} images only.`;
      return;
    }

    submitBtn.disabled = true;
    submitBtn.textContent = 'Submitting...';

    const formData = new FormData(ratingForm);

    fetch(`${base_url}/products/save_rating`, {
      method: 'POST',
      body: formData,
      headers: {
        Accept: 'application/json',
      },
    })
      .then((response) => response.json())
      .then((response) => {
        if (!response.error) {
          formSuccess.textContent = response.message || 'Product Rated Successfully';
          // Optionally refresh the page ratings display here

          setTimeout(() => {
            ratingModalInstance.hide();
            submitBtn.disabled = false;
            submitBtn.textContent = 'Submit Rating';
          }, 1500);
        } else {
          // Optionally handle CSRF tokens here if your backend requires
          if (response.csrfName && response.csrfHash) {
            let csrfInput = ratingForm.querySelector(`input[name="${response.csrfName}"]`);
            if (csrfInput) csrfInput.value = response.csrfHash;
          }
          formError.textContent = response.message || 'Unable to submit rating.';
          submitBtn.disabled = false;
          submitBtn.textContent = 'Submit Rating';
        }
      })
      .catch((err) => {
        formError.textContent = 'An error occurred. Please try again later.';
        submitBtn.disabled = false;
        submitBtn.textContent = 'Submit Rating';
        console.error(err);
      });
  });

  document.addEventListener('click', function(e) {
    const btn = e.target.closest('.rating-comp-open-btn');
    if (!btn) return;

    e.preventDefault();

    const productData = {
        productId: btn.getAttribute('data-product-id'),
        userId: btn.getAttribute('data-user-id'),
        name: btn.getAttribute('data-product-name'),
        store: btn.getAttribute('data-store-name'),
        //orderNumber: btn.getAttribute('data-order-number'),
        //purchaseDate: btn.getAttribute('data-purchase-date'),
    };

    // Validate required fields
    if (!productData.productId || !productData.userId) {
        console.error('Missing productId or userId in the button.');
        return;
    }

    openRatingModal(productData);
});

  // OPEN MODAL BUTTON HANDLER
  // Expose global function to open rating modal anywhere on page
    window.openRatingModal = function(productData) {
    if (!productData || !productData.productId || !productData.userId) {
        console.error('productData with productId and userId is required to open rating modal.');
        return;
    }

    // Populate product info section (safer fallback text)
    document.getElementById('rating-comp-product-name').textContent = productData.name || '-';
    document.getElementById('rating-comp-store-name').textContent = productData.store || '-';
    //document.getElementById('rating-comp-order-number').textContent = productData.orderNumber || '-';

    // Format purchase date nicely or just show raw if missing
    /*
    function formatDateTime(dateTimeStr) {
        if (!dateTimeStr) return '-';
        const d = new Date(dateTimeStr);
        if (isNaN(d)) return dateTimeStr;
        return d.toLocaleString();
    }
    document.getElementById('rating-comp-purchase-date').textContent = formatDateTime(productData.purchaseDate);
    */

    // Set hidden form inputs
    document.getElementById('rating-comp-product_id').value = productData.productId;
    document.getElementById('rating-comp-user_id').value = productData.userId;

    // Reset stars and comment initially
    const ratingInput = document.getElementById('rating-comp-rating');
    ratingInput.value = '0';
    document.getElementById('rating-comp-comment').value = '';
    document.getElementById('rating-comp-preview-images').innerHTML = '';
    selectedRating = 0;
    renderStars(selectedRating);

    // Load existing rating from server to edit if exists
    loadExistingRating(productData.productId, productData.userId);

    // Show modal
    ratingModalInstance.show();
    };
});